├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── admin │ ├── controllers │ │ ├── common.go │ │ ├── dept.go │ │ ├── dict.go │ │ ├── log.go │ │ ├── menu.go │ │ ├── role.go │ │ ├── server.go │ │ └── user.go │ ├── entity │ │ ├── admin_sys_dept.go │ │ ├── admin_sys_dict_data.go │ │ ├── admin_sys_dict_type.go │ │ ├── admin_sys_log.go │ │ ├── admin_sys_menu.go │ │ ├── admin_sys_role.go │ │ ├── admin_sys_role_dept.go │ │ ├── admin_sys_role_menu.go │ │ ├── admin_sys_user.go │ │ ├── admin_sys_user_role.go │ │ ├── admin_uploads.go │ │ └── admin_uploads_type.go │ ├── request │ │ ├── dept.go │ │ ├── dict.go │ │ ├── log.go │ │ ├── menu.go │ │ ├── role.go │ │ └── user.go │ ├── response │ │ ├── dept.go │ │ ├── log.go │ │ ├── menu.go │ │ ├── role.go │ │ ├── server.go │ │ └── user.go │ ├── routers │ │ ├── common.go │ │ ├── dept.go │ │ ├── dict.go │ │ ├── index.go │ │ ├── log.go │ │ ├── menu.go │ │ ├── role.go │ │ ├── server.go │ │ └── user.go │ └── services │ │ ├── common.go │ │ ├── dept.go │ │ ├── dict.go │ │ ├── log.go │ │ ├── menu.go │ │ ├── role.go │ │ ├── server.go │ │ └── user.go ├── api │ ├── controllers │ │ └── user.go │ └── routers │ │ ├── index.go │ │ └── user.go └── index.go ├── common ├── auth │ └── auth.go ├── common.go ├── middlewares │ ├── jwt.go │ └── record.go └── response.go ├── config.toml ├── core ├── config │ └── config.go ├── index.go ├── middlewares │ ├── cors.go │ ├── logger.go │ ├── middlewares.go │ └── recovery.go ├── redis │ └── redis.go ├── xorm │ └── xorm.go └── zap │ ├── lumberjack │ ├── chown.go │ ├── chown_linux.go │ └── lumberjack.go │ └── zap.go ├── go.mod ├── go.sum ├── main.go ├── sql └── seed-admin.sql └── utils ├── cache.go ├── captcha.go ├── claims.go ├── file.go ├── jwt.go ├── md5.go ├── slice.go └── validator.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .vscode 4 | *.local 5 | /log/* -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # 编译阶段 2 | FROM golang:alpine AS go-builder 3 | 4 | # 进入工作目录 5 | WORKDIR /www 6 | COPY . . 7 | # 配置模块代理 国外服务器可以不配 8 | ENV GO111MODULE=on 9 | ENV GOPROXY=https://goproxy.cn,direct 10 | # 打包 linux/AMD64 架构 11 | RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o server 12 | 13 | # 运行阶段 14 | FROM alpine AS admin-runner 15 | 16 | # 进入工作目录 17 | WORKDIR /www 18 | 19 | # 复制打包的Go文件到系统用户可执行程序目录下 20 | COPY --from=go-builder /www/server /www 21 | # 复制配置文件到系统用户可执行程序目录下 22 | COPY --from=go-builder /www/config.toml /www 23 | # 复制sql到系统用户可执行程序目录下 24 | COPY --from=go-builder /www/sql /www 25 | # 复制静态目录到系统用户可执行程序目录下 26 | COPY --from=go-builder /www/wwwroot /www 27 | # 将时区设置为东八区 28 | RUN echo "https://mirrors.aliyun.com/alpine/v3.8/main/" > /etc/apk/repositories \ 29 | && echo "https://mirrors.aliyun.com/alpine/v3.8/community/" >> /etc/apk/repositories \ 30 | && apk add --no-cache tzdata \ 31 | && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ 32 | && echo Asia/Shanghai > /etc/timezone \ 33 | && apk del tzdata 34 | 35 | # 暴露服务端口 36 | EXPOSE 8080 37 | # 启动服务 38 | ENTRYPOINT ["/www/server"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 pya789 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | seed logo 3 |

4 |

Seed - Admin

5 |

基于Go/C#(开发中)/Vue的前后端分离后台权限管理系统

6 |
7 | 8 | 9 | 10 | 11 |
12 | 13 | ## 项目简介 14 | seed-admin是一个纯粹的、完全开源的高可维护权限管理系统,减少了依赖,设计简单化,减少二次开发难度,提高可定制性。支持docker/常规上传部署。 15 | - 前端采用vue3、naive-ui... 支持template和jsx两种方式开发亦可混合使用。 16 | - 后端提供Go/C#两种解决方案可根据自身对语言的熟悉程度择其一使用 17 | - Go方案采用gin、xorm、mysql、redis、jwt 18 | - C#方案正在开发中... 19 | - 即时性动态权限菜单 权限修改后用户无需刷新即可生效 20 | - 高效率开发 前端封装CURD组件仅需填写json即可生成CURD页面 21 | 22 | ## 默认功能 23 | - 仪表盘:好像并没有没什么用...有数据展示需求请自由发挥 24 | - 用户管理:提供部门配置和用户权限与信息设置。 25 | - 部门系统(内嵌在用户管理中):配置组织机构(公司,部门,小组等)。 26 | - 角色管理:控制角色菜单权限、按钮权限、部门数据权限等。 27 | - 菜单管理:配置系统菜单、操作权限标识、按钮权限标识等。 28 | - 操作日志:负责记录系统登录日志、操作日志和查询。 29 | - 系统监控:负责监控系统CPU、内存、硬盘、服务等相关信息。 30 | - 字典管理:负责对数据库字段值做字典解析。 31 | - 个人管理:编辑修改个人信息。 32 | 33 | 为了维持精简性前端echarts等均无引入,后端工作流等业务组件均无开发计划有需求请自行fork开发 34 | 35 | ## 技术栈 36 | - 前端:**`vue3` `vuex` `vue-Router` `naive-ui` `...`** 37 | - Go后端:**`gin` `xorm` `mysql` `redis` `...`** 38 | - C#后端:**`开发中...`** 39 | ## 项目演示 40 | 演示地址: 41 | [https://admin.seed-app.com](https://admin.seed-app.com) 42 | 43 | - 账户:admin 44 | - 密码:123123 45 | 46 | > 提示:演示系统数据库每10分钟会自动还原一次 47 | 48 | 文档地址: 49 | [https://www.seed-app.com/admin](https://www.seed-app.com/admin/) 50 | 51 | seed home 52 | 53 | ## 项目关联仓库(GitHub) 54 | Vue3前端:[https://github.com/seed-app/seed-admin-vue](https://github.com/seed-app/seed-admin-vue) 55 | Go服务端:[https://github.com/seed-app/seed-admin-go](https://github.com/seed-app/seed-admin-go) 56 | C#服务端:[https://github.com/seed-app/seed-admin-dotnet](https://github.com/seed-app/seed-admin-dotnet) (开发中...) 57 | ## 项目关联仓库(Gitee) 58 | Vue3前端:[https://gitee.com/seed-app/seed-admin-vue](https://gitee.com/seed-app/seed-admin-vue) 59 | Go服务端:[https://gitee.com/seed-app/seed-admin-go](https://gitee.com/seed-app/seed-admin-go) 60 | C#服务端:[https://gitee.com/seed-app/seed-admin-dotnet](https://gitee.com/seed-app/seed-admin-dotnet) (开发中...) 61 | 62 | ## 学(kai)习(che)交(mo)流(yu)群 63 | 企鹅1群:8455822 -------------------------------------------------------------------------------- /app/admin/controllers/common.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "seed-admin/app/admin/services" 5 | "seed-admin/common" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | type Common struct{} 11 | 12 | var commonService services.CommonService 13 | 14 | // 上传图片 15 | func (*Common) UploadImages(ctx *gin.Context) { 16 | res, err := commonService.Uploads(ctx) 17 | if err != nil { 18 | common.LOG.Error(err.Error()) 19 | common.FailMsg(ctx, err.Error()) 20 | return 21 | } 22 | common.OkMsgData(ctx, "上传成功", res) 23 | } 24 | -------------------------------------------------------------------------------- /app/admin/controllers/dept.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "seed-admin/app/admin/request" 5 | "seed-admin/app/admin/services" 6 | "seed-admin/common" 7 | "seed-admin/utils" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | type Dept struct{} 13 | 14 | var deptService services.DeptService 15 | 16 | func (*Dept) List(ctx *gin.Context) { 17 | res, err := deptService.GetAllDept() 18 | if err != nil { 19 | common.LOG.Error(err.Error()) 20 | } 21 | common.OkData(ctx, res) 22 | } 23 | 24 | // 增加部门 25 | func (*Dept) Add(ctx *gin.Context) { 26 | var params request.DeptAdd 27 | _ = ctx.ShouldBindJSON(¶ms) 28 | // 验证参数合法性 29 | if err := utils.ParamsVerify(¶ms); err != nil { 30 | common.FailMsg(ctx, err.Error()) 31 | return 32 | } 33 | if err := deptService.AddDept(¶ms); err != nil { 34 | common.LOG.Error(err.Error()) 35 | common.FailMsg(ctx, err.Error()) 36 | return 37 | } 38 | common.OkMsg(ctx, "增加部门成功") 39 | } 40 | 41 | // 更新部门 42 | func (*Dept) Update(ctx *gin.Context) { 43 | var params request.DeptUpdate 44 | _ = ctx.ShouldBindJSON(¶ms) 45 | // 验证参数合法性 46 | if err := utils.ParamsVerify(¶ms); err != nil { 47 | common.FailMsg(ctx, err.Error()) 48 | return 49 | } 50 | if err := deptService.UpdateDept(¶ms); err != nil { 51 | common.LOG.Error(err.Error()) 52 | common.FailMsg(ctx, err.Error()) 53 | return 54 | } 55 | common.OkMsg(ctx, "更新部门成功") 56 | } 57 | 58 | // 删除部门 59 | func (*Dept) Del(ctx *gin.Context) { 60 | var params request.DeptDel 61 | _ = ctx.ShouldBindJSON(¶ms) 62 | // 验证参数合法性 63 | if err := utils.ParamsVerify(¶ms); err != nil { 64 | common.FailMsg(ctx, err.Error()) 65 | return 66 | } 67 | if err := deptService.DelDept(¶ms); err != nil { 68 | common.LOG.Error(err.Error()) 69 | common.FailMsg(ctx, err.Error()) 70 | return 71 | } 72 | common.OkMsg(ctx, "删除部门成功") 73 | } 74 | 75 | // 部门信息 76 | func (*Dept) Info(ctx *gin.Context) { 77 | var params request.DeptInfo 78 | _ = ctx.ShouldBindQuery(¶ms) 79 | // 验证参数合法性 80 | if err := utils.ParamsVerify(¶ms); err != nil { 81 | common.FailMsg(ctx, err.Error()) 82 | return 83 | } 84 | res, err := deptService.GetInfo(params.Id) 85 | if err != nil { 86 | common.LOG.Error(err.Error()) 87 | common.FailMsg(ctx, err.Error()) 88 | return 89 | } 90 | common.OkData(ctx, res) 91 | } 92 | -------------------------------------------------------------------------------- /app/admin/controllers/dict.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "seed-admin/app/admin/request" 5 | "seed-admin/app/admin/services" 6 | "seed-admin/common" 7 | "seed-admin/utils" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | type Dict struct{} 13 | 14 | var dictService services.DictService 15 | 16 | // 字典列表 17 | func (*Dict) List(ctx *gin.Context) { 18 | var params request.DictList 19 | _ = ctx.ShouldBindQuery(¶ms) 20 | // 验证参数合法性 21 | if err := utils.ParamsVerify(¶ms); err != nil { 22 | common.FailMsg(ctx, err.Error()) 23 | return 24 | } 25 | res, count, err := dictService.GetAllDictType(¶ms) 26 | if err != nil { 27 | common.LOG.Error(err.Error()) 28 | common.FailMsg(ctx, err.Error()) 29 | return 30 | } 31 | common.OkData(ctx, map[string]any{ 32 | "list": res, 33 | "count": count, 34 | }) 35 | } 36 | 37 | // 增加字典 38 | func (*Dict) Add(ctx *gin.Context) { 39 | var params request.DictAdd 40 | _ = ctx.ShouldBindJSON(¶ms) 41 | // 验证参数合法性 42 | if err := utils.ParamsVerify(¶ms); err != nil { 43 | common.FailMsg(ctx, err.Error()) 44 | return 45 | } 46 | if err := dictService.AddDictType(¶ms); err != nil { 47 | common.LOG.Error(err.Error()) 48 | common.FailMsg(ctx, err.Error()) 49 | } 50 | common.OkMsg(ctx, "增加字典成功") 51 | } 52 | func (*Dict) Update(ctx *gin.Context) { 53 | var params request.DictUpdate 54 | _ = ctx.ShouldBindJSON(¶ms) 55 | // 验证参数合法性 56 | if err := utils.ParamsVerify(¶ms); err != nil { 57 | common.FailMsg(ctx, err.Error()) 58 | return 59 | } 60 | if err := dictService.UpdateDictType(¶ms); err != nil { 61 | common.LOG.Error(err.Error()) 62 | common.FailMsg(ctx, err.Error()) 63 | } 64 | common.OkMsg(ctx, "更新字典成功") 65 | } 66 | 67 | // 删除字典 68 | func (*Dict) Del(ctx *gin.Context) { 69 | var params request.DictDel 70 | _ = ctx.ShouldBindJSON(¶ms) 71 | // 验证参数合法性 72 | if err := utils.ParamsVerify(¶ms); err != nil { 73 | common.FailMsg(ctx, err.Error()) 74 | return 75 | } 76 | if err := dictService.DelDictType(¶ms); err != nil { 77 | common.LOG.Error(err.Error()) 78 | common.FailMsg(ctx, err.Error()) 79 | } 80 | common.OkMsg(ctx, "删除字典成功") 81 | } 82 | 83 | // 获取字典信息 84 | func (*Dict) Info(ctx *gin.Context) { 85 | var params request.DictInfo 86 | _ = ctx.ShouldBindQuery(¶ms) 87 | // 验证参数合法性 88 | if err := utils.ParamsVerify(¶ms); err != nil { 89 | common.FailMsg(ctx, err.Error()) 90 | return 91 | } 92 | res, err := dictService.GetDictTypeInfo(params.Id) 93 | if err != nil { 94 | common.LOG.Error(err.Error()) 95 | common.FailMsg(ctx, err.Error()) 96 | return 97 | } 98 | common.OkData(ctx, res) 99 | } 100 | 101 | // 字典数据列表 102 | func (*Dict) DataList(ctx *gin.Context) { 103 | var params request.DictDataList 104 | _ = ctx.ShouldBindQuery(¶ms) 105 | // 验证参数合法性 106 | if err := utils.ParamsVerify(¶ms); err != nil { 107 | common.FailMsg(ctx, err.Error()) 108 | return 109 | } 110 | res, count, err := dictService.GetAllDictData(¶ms) 111 | if err != nil { 112 | common.LOG.Error(err.Error()) 113 | common.FailMsg(ctx, err.Error()) 114 | return 115 | } 116 | common.OkData(ctx, map[string]any{ 117 | "list": res, 118 | "count": count, 119 | }) 120 | } 121 | 122 | // 增加字典数据 123 | func (*Dict) DataAdd(ctx *gin.Context) { 124 | var params request.DictDataAdd 125 | _ = ctx.ShouldBindJSON(¶ms) 126 | // 验证参数合法性 127 | if err := utils.ParamsVerify(¶ms); err != nil { 128 | common.FailMsg(ctx, err.Error()) 129 | return 130 | } 131 | if err := dictService.AddDictData(¶ms); err != nil { 132 | common.LOG.Error(err.Error()) 133 | common.FailMsg(ctx, err.Error()) 134 | } 135 | common.OkMsg(ctx, "增加字典数据成功") 136 | } 137 | 138 | // 字典数据更新 139 | func (*Dict) DataUpdate(ctx *gin.Context) { 140 | var params request.DictDataUpdate 141 | _ = ctx.ShouldBindJSON(¶ms) 142 | // 验证参数合法性 143 | if err := utils.ParamsVerify(¶ms); err != nil { 144 | common.FailMsg(ctx, err.Error()) 145 | return 146 | } 147 | if err := dictService.UpdateDictData(¶ms); err != nil { 148 | common.LOG.Error(err.Error()) 149 | common.FailMsg(ctx, err.Error()) 150 | } 151 | common.OkMsg(ctx, "更新字典数据成功") 152 | } 153 | 154 | // 删除字典数据 155 | func (*Dict) DataDel(ctx *gin.Context) { 156 | var params request.DictDataDel 157 | _ = ctx.ShouldBindJSON(¶ms) 158 | // 验证参数合法性 159 | if err := utils.ParamsVerify(¶ms); err != nil { 160 | common.FailMsg(ctx, err.Error()) 161 | return 162 | } 163 | if err := dictService.DelDictData(¶ms); err != nil { 164 | common.LOG.Error(err.Error()) 165 | common.FailMsg(ctx, err.Error()) 166 | } 167 | common.OkMsg(ctx, "删除字典数据成功") 168 | } 169 | 170 | // 获取字典数据信息 171 | func (*Dict) DataInfo(ctx *gin.Context) { 172 | var params request.DictDataInfo 173 | _ = ctx.ShouldBindQuery(¶ms) 174 | // 验证参数合法性 175 | if err := utils.ParamsVerify(¶ms); err != nil { 176 | common.FailMsg(ctx, err.Error()) 177 | return 178 | } 179 | res, err := dictService.GetDictDataInfo(params.Id) 180 | if err != nil { 181 | common.LOG.Error(err.Error()) 182 | common.FailMsg(ctx, err.Error()) 183 | return 184 | } 185 | common.OkData(ctx, res) 186 | } 187 | 188 | // 根据类型获取字典 189 | func (*Dict) TypeData(ctx *gin.Context) { 190 | dictType := ctx.Query("type") 191 | res, err := dictService.GetTypeData(dictType) 192 | if err != nil { 193 | common.LOG.Error(err.Error()) 194 | common.FailMsg(ctx, err.Error()) 195 | return 196 | } 197 | common.OkData(ctx, res) 198 | } 199 | -------------------------------------------------------------------------------- /app/admin/controllers/log.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "seed-admin/app/admin/request" 5 | "seed-admin/app/admin/services" 6 | "seed-admin/common" 7 | "seed-admin/utils" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | type Log struct{} 13 | 14 | var logService services.LogService 15 | 16 | func (*Log) List(ctx *gin.Context) { 17 | var params request.LogList 18 | _ = ctx.ShouldBindQuery(¶ms) 19 | // 验证参数合法性 20 | if err := utils.ParamsVerify(¶ms); err != nil { 21 | common.FailMsg(ctx, err.Error()) 22 | return 23 | } 24 | res, count, err := logService.GetAllLog(¶ms) 25 | if err != nil { 26 | common.LOG.Error(err.Error()) 27 | } 28 | common.OkData(ctx, map[string]any{ 29 | "list": res, 30 | "count": count, 31 | }) 32 | } 33 | func (*Log) Del(ctx *gin.Context) { 34 | var params request.LogDel 35 | _ = ctx.ShouldBindJSON(¶ms) 36 | // 验证参数合法性 37 | if err := utils.ParamsVerify(¶ms); err != nil { 38 | common.FailMsg(ctx, err.Error()) 39 | return 40 | } 41 | if err := logService.DelLog(¶ms); err != nil { 42 | common.LOG.Error(err.Error()) 43 | common.FailMsg(ctx, err.Error()) 44 | return 45 | } 46 | common.OkMsg(ctx, "删除日志成功") 47 | } 48 | -------------------------------------------------------------------------------- /app/admin/controllers/menu.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "seed-admin/app/admin/request" 5 | "seed-admin/app/admin/services" 6 | "seed-admin/common" 7 | "seed-admin/utils" 8 | "strconv" 9 | 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | type Menu struct{} 14 | 15 | var menuService services.MenuService 16 | 17 | // 菜单和权限 18 | func (*Menu) PermMenu(ctx *gin.Context) { 19 | menus, err := menuService.GetMenu(utils.GetUserId(ctx)) 20 | if err != nil { 21 | common.LOG.Error(err.Error()) 22 | } 23 | perms, err := menuService.GetPerms(utils.GetUserId(ctx)) 24 | if err != nil { 25 | common.LOG.Error(err.Error()) 26 | } 27 | common.OkData(ctx, map[string]any{ 28 | "menus": menus, 29 | "perms": perms, 30 | }) 31 | } 32 | 33 | // 菜单列表 34 | func (*Menu) List(ctx *gin.Context) { 35 | name := ctx.Query("name") 36 | status := ctx.Query("status") 37 | res, err := menuService.GetAllMenu(name, status) 38 | if err != nil { 39 | common.LOG.Error(err.Error()) 40 | } 41 | common.OkData(ctx, res) 42 | } 43 | 44 | // 获取菜单信息 45 | func (*Menu) Info(ctx *gin.Context) { 46 | id, _ := strconv.Atoi(ctx.Query("id")) 47 | res, err := menuService.GetInfo(id) 48 | if err != nil { 49 | common.LOG.Error(err.Error()) 50 | } 51 | common.OkData(ctx, res) 52 | } 53 | 54 | // 增加菜单 55 | func (*Menu) Add(ctx *gin.Context) { 56 | var params request.Menu 57 | _ = ctx.ShouldBindJSON(¶ms) 58 | // 验证参数合法性 59 | if err := utils.ParamsVerify(¶ms); err != nil { 60 | common.FailMsg(ctx, err.Error()) 61 | return 62 | } 63 | if err := menuService.AddMenu(¶ms); err != nil { 64 | common.LOG.Error(err.Error()) 65 | common.FailMsg(ctx, err.Error()) 66 | return 67 | } 68 | common.OkMsg(ctx, "增加菜单成功") 69 | } 70 | 71 | // 编辑菜单 72 | func (*Menu) Update(ctx *gin.Context) { 73 | var params request.Menu 74 | _ = ctx.ShouldBindJSON(¶ms) 75 | // 验证参数合法性 76 | if err := utils.ParamsVerify(¶ms); err != nil { 77 | common.FailMsg(ctx, err.Error()) 78 | return 79 | } 80 | err := menuService.UpdateMenu(¶ms) 81 | if err != nil { 82 | common.LOG.Error(err.Error()) 83 | common.FailMsg(ctx, err.Error()) 84 | return 85 | } 86 | common.OkMsg(ctx, "更新菜单成功") 87 | } 88 | 89 | // 删除菜单 90 | func (*Menu) Del(ctx *gin.Context) { 91 | var params request.MenuDel 92 | _ = ctx.ShouldBindJSON(¶ms) 93 | // 验证参数合法性 94 | if err := utils.ParamsVerify(¶ms); err != nil { 95 | common.FailMsg(ctx, err.Error()) 96 | return 97 | } 98 | if err := menuService.DelMenu(¶ms); err != nil { 99 | common.LOG.Error(err.Error()) 100 | common.FailMsg(ctx, err.Error()) 101 | return 102 | } 103 | common.OkMsg(ctx, "删除菜单成功") 104 | } 105 | -------------------------------------------------------------------------------- /app/admin/controllers/role.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "seed-admin/app/admin/request" 5 | "seed-admin/app/admin/services" 6 | "seed-admin/common" 7 | "seed-admin/utils" 8 | "strconv" 9 | 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | type Role struct{} 14 | 15 | var roleService services.RoleService 16 | 17 | // 获取角色列表 18 | func (*Role) List(ctx *gin.Context) { 19 | var params request.RoleList 20 | _ = ctx.ShouldBindQuery(¶ms) 21 | // 验证参数合法性 22 | if err := utils.ParamsVerify(¶ms); err != nil { 23 | common.FailMsg(ctx, err.Error()) 24 | return 25 | } 26 | res, count, err := roleService.GetAllRole(¶ms) 27 | if err != nil { 28 | common.LOG.Error(err.Error()) 29 | } 30 | common.OkData(ctx, map[string]any{ 31 | "list": res, 32 | "count": count, 33 | }) 34 | } 35 | 36 | // 获取角色信息 37 | func (*Role) Info(ctx *gin.Context) { 38 | id, _ := strconv.Atoi(ctx.Query("id")) 39 | res, err := roleService.GetInfo(id) 40 | if err != nil { 41 | common.LOG.Error(err.Error()) 42 | } 43 | common.OkData(ctx, res) 44 | } 45 | 46 | // 新增角色 47 | func (*Role) Add(ctx *gin.Context) { 48 | var params request.Role 49 | _ = ctx.ShouldBindJSON(¶ms) 50 | // 验证参数合法性 51 | if err := utils.ParamsVerify(¶ms); err != nil { 52 | common.FailMsg(ctx, err.Error()) 53 | return 54 | } 55 | if err := roleService.AddRole(¶ms); err != nil { 56 | common.LOG.Error(err.Error()) 57 | common.FailMsg(ctx, err.Error()) 58 | return 59 | } 60 | common.Ok(ctx) 61 | } 62 | 63 | // 编辑角色 64 | func (*Role) Update(ctx *gin.Context) { 65 | var params request.RoleUpdate 66 | _ = ctx.ShouldBindJSON(¶ms) 67 | // 验证参数合法性 68 | if err := utils.ParamsVerify(¶ms); err != nil { 69 | common.FailMsg(ctx, err.Error()) 70 | return 71 | } 72 | if err := roleService.UpdateRole(¶ms); err != nil { 73 | common.LOG.Error(err.Error()) 74 | common.FailMsg(ctx, err.Error()) 75 | return 76 | } 77 | common.OkMsg(ctx, "更新角色成功") 78 | } 79 | 80 | // 删除角色 81 | func (*Role) Del(ctx *gin.Context) { 82 | var params request.RoleDel 83 | _ = ctx.ShouldBindJSON(¶ms) 84 | // 验证参数合法性 85 | if err := utils.ParamsVerify(¶ms); err != nil { 86 | common.FailMsg(ctx, err.Error()) 87 | return 88 | } 89 | if err := roleService.DelRole(¶ms); err != nil { 90 | common.LOG.Error(err.Error()) 91 | common.FailMsg(ctx, err.Error()) 92 | return 93 | } 94 | common.OkMsg(ctx, "删除角色成功") 95 | } 96 | -------------------------------------------------------------------------------- /app/admin/controllers/server.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "seed-admin/app/admin/services" 5 | "seed-admin/common" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | type Server struct{} 11 | 12 | var serverService services.ServerService 13 | 14 | // 服务器信息 15 | func (*Server) Info(ctx *gin.Context) { 16 | res, err := serverService.GetSystemInfo() 17 | if err != nil { 18 | common.FailMsg(ctx, "获取服务器信息失败") 19 | return 20 | } 21 | common.OkData(ctx, res) 22 | } 23 | -------------------------------------------------------------------------------- /app/admin/controllers/user.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "seed-admin/app/admin/request" 5 | "seed-admin/app/admin/services" 6 | "seed-admin/common" 7 | "seed-admin/utils" 8 | "strconv" 9 | 10 | "github.com/gin-gonic/gin" 11 | ) 12 | 13 | type User struct{} 14 | 15 | var userService services.UserService 16 | 17 | // 登录 18 | func (*User) Login(ctx *gin.Context) { 19 | // 绑定body参数到结构 20 | var params request.Login 21 | _ = ctx.ShouldBindJSON(¶ms) 22 | // 验证参数合法性 23 | if err := utils.ParamsVerify(¶ms); err != nil { 24 | common.FailMsg(ctx, err.Error()) 25 | return 26 | } 27 | // 验证验证码合法性 注意CaptchaVerify调用后验证码已销毁 所以需要让前端再次获取新的验证码 28 | if err := utils.CaptchaVerify(params.CaptchaId, params.Captcha); err != nil { 29 | common.Message(ctx, common.REFRESH_CAPTCHA, err.Error()) 30 | return 31 | } 32 | // 调用服务登录 33 | user, roleIds, err := userService.Login(¶ms) 34 | if err != nil { 35 | common.Message(ctx, common.REFRESH_CAPTCHA, err.Error()) 36 | return 37 | } 38 | if len(roleIds) == 0 { 39 | common.Message(ctx, common.REFRESH_CAPTCHA, "您没有任何角色权限,无法登录") 40 | return 41 | } 42 | // 获取token 43 | token, err := userService.GetToken(user) 44 | if err != nil { 45 | common.Message(ctx, common.REFRESH_CAPTCHA, err.Error()) 46 | return 47 | } 48 | common.OkMsgData(ctx, "登录成功", map[string]string{ 49 | "token": token, 50 | }) 51 | } 52 | 53 | // 获取登录人的用户信息 54 | func (*User) Person(ctx *gin.Context) { 55 | userInfo, err := userService.GetPerson(utils.GetUserId(ctx)) 56 | if err != nil { 57 | common.Message(ctx, common.AUTHORIZATION_FAIL, err.Error()) 58 | return 59 | } 60 | common.OkData(ctx, userInfo) 61 | } 62 | 63 | // 生成验证码 64 | func (*User) Captcha(ctx *gin.Context) { 65 | captchaId := ctx.Query("captchaId") 66 | cap := utils.NewCaptcha() 67 | var image []byte 68 | var err error 69 | // 是否进入重载 70 | if captchaId != "" { 71 | _ = cap.Reload(captchaId) 72 | image, err = cap.ImageByte(captchaId) 73 | if err != nil { 74 | common.FailMsg(ctx, err.Error()) 75 | return 76 | } 77 | } else { 78 | captchaId = cap.CreateImage() 79 | image, err = cap.ImageByte(captchaId) 80 | if err != nil { 81 | common.FailMsg(ctx, err.Error()) 82 | return 83 | } 84 | } 85 | data := map[string]any{ 86 | "id": captchaId, 87 | "image": image, 88 | } 89 | common.OkData(ctx, data) 90 | } 91 | 92 | // 获取用户列表 93 | func (*User) List(ctx *gin.Context) { 94 | var params request.UserList 95 | _ = ctx.ShouldBindJSON(¶ms) 96 | // 验证参数合法性 97 | if err := utils.ParamsVerify(¶ms); err != nil { 98 | common.FailMsg(ctx, err.Error()) 99 | return 100 | } 101 | res, count, err := userService.GetAllUser(¶ms, utils.GetUserId(ctx)) 102 | if err != nil { 103 | common.LOG.Error(err.Error()) 104 | common.FailMsg(ctx, err.Error()) 105 | return 106 | } 107 | common.OkData(ctx, map[string]any{ 108 | "list": res, 109 | "count": count, 110 | }) 111 | } 112 | 113 | // 修改用户角色 114 | func (*User) UpdateUserRole(ctx *gin.Context) { 115 | var params request.UserRoleUpdate 116 | _ = ctx.ShouldBindJSON(¶ms) 117 | // 验证参数合法性 118 | if err := utils.ParamsVerify(¶ms); err != nil { 119 | common.FailMsg(ctx, err.Error()) 120 | return 121 | } 122 | if err := userService.UpdateUserRole(¶ms); err != nil { 123 | common.LOG.Error(err.Error()) 124 | common.FailMsg(ctx, err.Error()) 125 | return 126 | } 127 | common.OkMsg(ctx, "更新用户角色成功") 128 | } 129 | 130 | // 修改用户头像 131 | func (*User) UpdateAvatar(ctx *gin.Context) { 132 | var params request.UserAvatarUpdate 133 | _ = ctx.ShouldBindJSON(¶ms) 134 | // 验证参数合法性 135 | if err := utils.ParamsVerify(¶ms); err != nil { 136 | common.FailMsg(ctx, err.Error()) 137 | return 138 | } 139 | if err := userService.UpdateAvatar(¶ms, utils.GetUserId(ctx)); err != nil { 140 | common.LOG.Error(err.Error()) 141 | common.FailMsg(ctx, err.Error()) 142 | return 143 | } 144 | common.OkMsg(ctx, "修改用户头像成功") 145 | } 146 | 147 | // 更新用户基础信息 148 | func (*User) UpdateBaseInfo(ctx *gin.Context) { 149 | var params request.UserBaseInfoUpdate 150 | _ = ctx.ShouldBindJSON(¶ms) 151 | // 验证参数合法性 152 | if err := utils.ParamsVerify(¶ms); err != nil { 153 | common.FailMsg(ctx, err.Error()) 154 | return 155 | } 156 | if err := userService.UpdateBaseInfo(¶ms, utils.GetUserId(ctx)); err != nil { 157 | common.LOG.Error(err.Error()) 158 | common.FailMsg(ctx, err.Error()) 159 | return 160 | } 161 | common.OkMsg(ctx, "修改基础信息成功") 162 | } 163 | 164 | // 更新用户密码 165 | func (*User) UpdatePassword(ctx *gin.Context) { 166 | var params request.UserPasswordUpdate 167 | _ = ctx.ShouldBindJSON(¶ms) 168 | // 验证参数合法性 169 | if err := utils.ParamsVerify(¶ms); err != nil { 170 | common.FailMsg(ctx, err.Error()) 171 | return 172 | } 173 | if err := userService.UpdatePassword(¶ms, utils.GetUserId(ctx)); err != nil { 174 | common.LOG.Error(err.Error()) 175 | common.FailMsg(ctx, err.Error()) 176 | return 177 | } 178 | common.OkMsg(ctx, "修改基础信息成功") 179 | } 180 | 181 | // 获取用户信息 182 | func (*User) Info(ctx *gin.Context) { 183 | id, _ := strconv.Atoi(ctx.Query("id")) 184 | userInfo, err := userService.GetInfo(id) 185 | if err != nil { 186 | common.FailMsg(ctx, err.Error()) 187 | return 188 | } 189 | common.OkData(ctx, userInfo) 190 | } 191 | 192 | // 新增用户 193 | func (*User) Add(ctx *gin.Context) { 194 | var params request.User 195 | _ = ctx.ShouldBindJSON(¶ms) 196 | // 验证参数合法性 197 | if err := utils.ParamsVerify(¶ms); err != nil { 198 | common.FailMsg(ctx, err.Error()) 199 | return 200 | } 201 | if err := userService.AddUser(¶ms); err != nil { 202 | common.LOG.Error(err.Error()) 203 | common.FailMsg(ctx, err.Error()) 204 | return 205 | } 206 | common.OkMsg(ctx, "新增用户成功") 207 | } 208 | 209 | // 修改用户 210 | func (*User) Update(ctx *gin.Context) { 211 | var params request.UserUpdate 212 | _ = ctx.ShouldBindJSON(¶ms) 213 | // 验证参数合法性 214 | if err := utils.ParamsVerify(¶ms); err != nil { 215 | common.FailMsg(ctx, err.Error()) 216 | return 217 | } 218 | if err := userService.UpdateUser(¶ms); err != nil { 219 | common.LOG.Error(err.Error()) 220 | common.FailMsg(ctx, err.Error()) 221 | return 222 | } 223 | common.OkMsg(ctx, "用户信息修改成功") 224 | } 225 | 226 | // 删除用户 227 | func (*User) Del(ctx *gin.Context) { 228 | var params request.UserDel 229 | _ = ctx.ShouldBindJSON(¶ms) 230 | // 验证参数合法性 231 | if err := utils.ParamsVerify(¶ms); err != nil { 232 | common.FailMsg(ctx, err.Error()) 233 | return 234 | } 235 | if err := userService.DelUser(¶ms); err != nil { 236 | common.LOG.Error(err.Error()) 237 | common.FailMsg(ctx, err.Error()) 238 | return 239 | } 240 | common.OkMsg(ctx, "删除用户成功") 241 | } 242 | 243 | // 转移部门 244 | func (*User) Move(ctx *gin.Context) { 245 | var params request.UserMove 246 | _ = ctx.ShouldBindJSON(¶ms) 247 | // 验证参数合法性 248 | if err := utils.ParamsVerify(¶ms); err != nil { 249 | common.FailMsg(ctx, err.Error()) 250 | return 251 | } 252 | if err := userService.MoveDept(¶ms); err != nil { 253 | common.LOG.Error(err.Error()) 254 | common.FailMsg(ctx, err.Error()) 255 | return 256 | } 257 | common.OkMsg(ctx, "转移部门成功") 258 | } 259 | -------------------------------------------------------------------------------- /app/admin/entity/admin_sys_dept.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type AdminSysDept struct { 6 | Id int `json:"id" xorm:"pk autoincr comment('主键')"` 7 | Name string `json:"name" xorm:"notnull comment('部门名称')"` 8 | ParentId int `json:"parentId" xorm:"comment('父级ID')"` 9 | Sort int `json:"sort" xorm:"comment('排序')"` 10 | CreatedTime time.Time `json:"createdTime" xorm:"created comment('创建时间')"` 11 | UpdatedTime time.Time `json:"updatedTime" xorm:"updated comment('更新时间')"` 12 | } 13 | -------------------------------------------------------------------------------- /app/admin/entity/admin_sys_dict_data.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type AdminSysDictData struct { 6 | Id int `json:"id" xorm:"pk autoincr comment('主键')"` 7 | Pid int `json:"pid" xorm:"notnull comment('主键')"` 8 | Label string `json:"label" xorm:"comment('字典标签')"` 9 | Value string `json:"value" xorm:"comment('字典值')"` 10 | Status int `json:"status" xorm:"bool notnull default(0) comment('状态0.正常 1.禁用')"` 11 | CreatedTime time.Time `json:"createdTime" xorm:"created comment('创建时间')"` 12 | UpdatedTime time.Time `json:"updatedTime" xorm:"updated comment('更新时间')"` 13 | } 14 | -------------------------------------------------------------------------------- /app/admin/entity/admin_sys_dict_type.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type AdminSysDictType struct { 6 | Id int `json:"id" xorm:"pk autoincr comment('主键')"` 7 | Name string `json:"name" xorm:"comment('字典名称')"` 8 | Type string `json:"type" xorm:"comment('字典类型')"` 9 | Status int `json:"status" xorm:"bool notnull default(0) comment('状态0.正常 1.禁用')"` 10 | CreatedTime time.Time `json:"createdTime" xorm:"created comment('创建时间')"` 11 | UpdatedTime time.Time `json:"updatedTime" xorm:"updated comment('更新时间')"` 12 | } 13 | -------------------------------------------------------------------------------- /app/admin/entity/admin_sys_log.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type AdminSysLog struct { 6 | Id int `json:"id" xorm:"pk autoincr comment('主键')"` 7 | UserId int `json:"userId" xorm:"notnull comment('角色ID')"` 8 | Method string `json:"method" xorm:"notnull comment('请求方式')"` 9 | Action string `json:"action" xorm:"notnull comment('行为')"` 10 | Ip string `json:"ip" xorm:"comment('IP')"` 11 | StatusCode int `json:"statusCode" xorm:"comment('响应状态')"` 12 | Params string `json:"params" xorm:"longtext comment('参数')"` 13 | Results string `json:"results" xorm:"longtext comment('响应结果')"` 14 | CreatedTime time.Time `json:"createdTime" xorm:"created comment('创建时间')"` 15 | UpdatedTime time.Time `json:"updatedTime" xorm:"updated comment('更新时间')"` 16 | } 17 | -------------------------------------------------------------------------------- /app/admin/entity/admin_sys_menu.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type AdminSysMenu struct { 6 | Id int `json:"id" xorm:"pk autoincr comment('主键')"` 7 | ParentId int `json:"parentId" xorm:"notnull default(0) comment('父级ID 0为根项目 主键不要设置为0')"` 8 | Name string `json:"name" xorm:"notnull comment('菜单名称')"` 9 | RouterName string `json:"routerName" xorm:"comment('路由名称')"` 10 | RouterPath string `json:"routerPath" xorm:"comment('路由地址')"` 11 | PagePath string `json:"pagePath" xorm:"comment('页面路径')"` 12 | Perms string `json:"perms" xorm:"comment('权限标识')"` 13 | Type int `json:"type" xorm:"bool notnull default(0) comment('类型0.目录 1.菜单 2.按钮')"` 14 | Icon string `json:"icon" xorm:"comment('图标')"` 15 | Sort int `json:"sort" xorm:"comment('排序')"` 16 | Visible int `json:"visible" xorm:"bool notnull default(0) comment('隐藏0.显示 1.隐藏')"` 17 | KeepAlive int `json:"keepAlive" xorm:"bool notnull default(0) comment('页面缓存0.否 1.是')"` 18 | Status int `json:"status" xorm:"bool notnull default(0) comment('状态0.正常 1.禁用')"` 19 | CreatedTime time.Time `json:"createdTime" xorm:"created comment('创建时间')"` 20 | UpdatedTime time.Time `json:"updatedTime" xorm:"updated comment('更新时间')"` 21 | } 22 | -------------------------------------------------------------------------------- /app/admin/entity/admin_sys_role.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type AdminSysRole struct { 6 | Id int `json:"id" xorm:"pk autoincr comment('主键')"` // 主键 7 | Name string `json:"name" xorm:"notnull comment('角色名称')"` 8 | Label string `json:"label" xorm:"comment('角色标签')"` 9 | Remark string `json:"remark" xorm:"comment('备注')"` 10 | Relevance int `json:"relevance" xorm:"bool notnull default(1) comment('上下级数据权限是否关联0.是 1.否')"` 11 | CreatedTime time.Time `json:"createdTime" xorm:"created comment('创建时间')"` 12 | UpdatedTime time.Time `json:"updatedTime" xorm:"updated comment('更新时间')"` 13 | } 14 | -------------------------------------------------------------------------------- /app/admin/entity/admin_sys_role_dept.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type AdminSysRoleDept struct { 4 | Id int `xorm:"pk autoincr comment('主键')"` 5 | RoleId int `xorm:"notnull comment('角色ID')"` 6 | DeptId int `xorm:"notnull comment('部门ID')"` 7 | } 8 | -------------------------------------------------------------------------------- /app/admin/entity/admin_sys_role_menu.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type AdminSysRoleMenu struct { 4 | Id int `xorm:"pk autoincr comment('主键')"` 5 | RoleId int `xorm:"notnull comment('角色ID')"` 6 | MenuId int `xorm:"notnull comment('菜单ID')"` 7 | } 8 | -------------------------------------------------------------------------------- /app/admin/entity/admin_sys_user.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type AdminSysUser struct { 6 | Id int `json:"id" xorm:"pk autoincr comment('主键')"` 7 | DeptId int `json:"deptId" xorm:"comment('部门ID')"` 8 | Username string `json:"username" xorm:"comment('用户名')"` 9 | Password string `json:"password" xorm:"comment('密码')"` 10 | NickName string `json:"nickName" xorm:"default('未命名') comment('用户名称')"` 11 | Phone string `json:"phone" xorm:"comment('手机')"` 12 | Email string `json:"email" xorm:"comment('邮箱')"` 13 | Avatar string `json:"avatar" xorm:"comment('头像')"` 14 | Status int `json:"status" xorm:"bool notnull default(0) comment('状态0.正常 1.禁用')"` 15 | IsDeleted int `json:"isDeleted" xorm:"bool notnull default(0) comment('软删除0.正常 1.删除')"` 16 | CreatedTime time.Time `json:"createdTime" xorm:"created comment('创建时间')"` 17 | UpdatedTime time.Time `json:"updatedTime" xorm:"updated comment('更新时间')"` 18 | } 19 | -------------------------------------------------------------------------------- /app/admin/entity/admin_sys_user_role.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type AdminSysUserRole struct { 4 | Id int `xorm:"pk autoincr comment('主键')"` 5 | UserId int `xorm:"notnull comment('用户ID')"` 6 | RoleId int `xorm:"notnull comment('角色ID')"` 7 | } 8 | -------------------------------------------------------------------------------- /app/admin/entity/admin_uploads.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type AdminUploads struct { 6 | Id int `json:"id" xorm:"pk autoincr comment('主键')"` 7 | Name string `json:"name" xorm:"comment('文件名')"` 8 | Url string `json:"url" xorm:"comment('地址')"` 9 | Type int `json:"type" xorm:"comment('分类')"` 10 | CreatedTime time.Time `json:"createdTime" xorm:"created comment('创建时间')"` 11 | UpdatedTime time.Time `json:"updatedTime" xorm:"updated comment('更新时间')"` 12 | } 13 | -------------------------------------------------------------------------------- /app/admin/entity/admin_uploads_type.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | import "time" 4 | 5 | type AdminUploadsType struct { 6 | Id int `json:"id" xorm:"pk autoincr comment('主键')"` 7 | Name string `json:"name" xorm:"comment('分类名称')"` 8 | Label string `json:"label" xorm:"comment('分类标识')"` 9 | CreatedTime time.Time `json:"createdTime" xorm:"created comment('创建时间')"` 10 | UpdatedTime time.Time `json:"updatedTime" xorm:"updated comment('更新时间')"` 11 | } 12 | -------------------------------------------------------------------------------- /app/admin/request/dept.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type DeptInfo struct { 4 | Id *int `form:"id" validate:"required"` 5 | } 6 | type DeptAdd struct { 7 | Name *string `json:"name" validate:"required"` 8 | ParentId *int `json:"parentId" validate:"required"` 9 | Sort int `json:"sort" validate:"-"` 10 | } 11 | type DeptUpdate struct { 12 | Id *int `form:"id" validate:"required"` 13 | Name *string `json:"name" validate:"required"` 14 | ParentId *int `json:"parentId" validate:"required"` 15 | Sort int `json:"sort" validate:"-"` 16 | } 17 | type DeptDel struct { 18 | Pid *int `json:"pid" validate:"required"` 19 | Ids []int `json:"ids" validate:"required"` 20 | UserDel bool `json:"userDel" validate:"-"` 21 | } 22 | -------------------------------------------------------------------------------- /app/admin/request/dict.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type DictList struct { 4 | Name string `form:"name" validate:"-"` 5 | Status string `form:"status" validate:"-"` 6 | PageNum *int `form:"pageNum" validate:"required"` 7 | PageSize *int `form:"pageSize" validate:"required"` 8 | } 9 | type DictAdd struct { 10 | Name *string `json:"name" validate:"required"` 11 | Type *string `json:"type" validate:"required"` 12 | Status int `json:"status" validate:"-"` 13 | } 14 | type DictUpdate struct { 15 | Id *int `json:"id" validate:"required"` 16 | Name *string `json:"name" validate:"required"` 17 | Type *string `json:"type" validate:"required"` 18 | Status int `json:"status" validate:"-"` 19 | } 20 | type DictDel struct { 21 | Ids []int `json:"ids" validate:"required"` 22 | } 23 | type DictInfo struct { 24 | Id *int `form:"id" validate:"required"` 25 | } 26 | type DictDataList struct { 27 | Pid *int `form:"pid" validate:"required"` 28 | Label string `form:"label" validate:"-"` 29 | Status string `form:"status" validate:"-"` 30 | PageNum *int `form:"pageNum" validate:"required"` 31 | PageSize *int `form:"pageSize" validate:"required"` 32 | } 33 | type DictDataAdd struct { 34 | Pid *int `json:"pid" validate:"required"` 35 | Label *string `json:"label" validate:"required"` 36 | Value *string `json:"value" validate:"required"` 37 | Status int `json:"status" validate:"-"` 38 | } 39 | type DictDataInfo struct { 40 | Id *int `form:"id" validate:"required"` 41 | } 42 | type DictDataUpdate struct { 43 | Id *int `json:"id" validate:"required"` 44 | Pid *int `json:"pid" validate:"required"` 45 | Label *string `json:"label" validate:"required"` 46 | Value *string `json:"value" validate:"required"` 47 | Status int `json:"status" validate:"-"` 48 | } 49 | type DictDataDel struct { 50 | Ids []int `json:"ids" validate:"required"` 51 | } 52 | -------------------------------------------------------------------------------- /app/admin/request/log.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type LogList struct { 4 | UserName string `form:"username" validate:"-"` 5 | Method string `form:"method" validate:"-"` 6 | Action string `form:"action" validate:"-"` 7 | Ip string `form:"ip" validate:"-"` 8 | PageNum *int `form:"pageNum" validate:"required"` 9 | PageSize *int `form:"pageSize" validate:"required"` 10 | } 11 | type LogDel struct { 12 | Ids []int `json:"ids" validate:"required"` 13 | } 14 | -------------------------------------------------------------------------------- /app/admin/request/menu.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type Menu struct { 4 | Id int `json:"id" validate:"-"` 5 | Type *int `json:"type" validate:"required"` 6 | ParentId *int `json:"parentId" validate:"required"` 7 | Name *string `json:"name" validate:"required"` 8 | RouterName string `json:"routerName" validate:"-"` 9 | RouterPath string `json:"routerPath" validate:"-"` 10 | PagePath string `json:"pagePath" validate:"-"` 11 | Perms string `json:"perms" validate:"-"` 12 | Icon string `json:"icon" validate:"-"` 13 | Sort int `json:"sort" validate:"-"` 14 | KeepAlive int `json:"keepAlive" validate:"-"` 15 | Status int `json:"status" validate:"-"` 16 | Visible int `json:"visible" validate:"-"` 17 | } 18 | 19 | type MenuDel struct { 20 | Ids []int `json:"ids" validate:"required"` 21 | } 22 | -------------------------------------------------------------------------------- /app/admin/request/role.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type Role struct { 4 | Name *string `json:"name" validate:"required"` 5 | Label *string `json:"label" validate:"required"` 6 | Remark string `json:"remark" validate:"-"` 7 | Relevance int `json:"relevance" validate:"-"` 8 | MenuIds []int `json:"menuIds" validate:"-"` 9 | DeptIds []int `json:"deptIds" validate:"-"` 10 | } 11 | type RoleUpdate struct { 12 | Id *int `json:"id" validate:"required"` 13 | Name *string `json:"name" validate:"required"` 14 | Label *string `json:"label" validate:"required"` 15 | Remark string `json:"remark" validate:"-"` 16 | Relevance int `json:"relevance" validate:"-"` 17 | MenuIds []int `json:"menuIds" validate:"-"` 18 | DeptIds []int `json:"deptIds" validate:"-"` 19 | } 20 | type RoleList struct { 21 | Name string `form:"name" validate:"-"` 22 | PageNum *int `form:"pageNum" validate:"required"` 23 | PageSize *int `form:"pageSize" validate:"required"` 24 | } 25 | type RoleDel struct { 26 | Ids []int `json:"ids" validate:"required"` 27 | } 28 | -------------------------------------------------------------------------------- /app/admin/request/user.go: -------------------------------------------------------------------------------- 1 | package request 2 | 3 | type Login struct { 4 | Username string `json:"username" validate:"required|alphaNum|minLen:1|maxLen:18"` 5 | Password string `json:"password" validate:"required|alphaDash|minLen:6|maxLen:18"` 6 | CaptchaId string `json:"captchaId" validata:"required"` 7 | Captcha string `json:"captcha" validate:"required|minLen:4|maxLen:4"` 8 | } 9 | type User struct { 10 | Username string `json:"username" validate:"required|alphaNum|minLen:1|maxLen:18"` 11 | NickName *string `json:"nickName" validate:"required|minLen:1|maxLen:12"` 12 | Password string `json:"password" validate:"-"` 13 | Avatar string `json:"avatar" validate:"-"` 14 | Phone string `json:"phone" validate:"-"` 15 | Email string `json:"email" validate:"-"` 16 | Status int `json:"status" validate:"-"` 17 | DeptId int `json:"deptId" validate:"-"` 18 | RoleIds []int `json:"roleIds" validate:"ints"` 19 | } 20 | type UserList struct { 21 | Username string `json:"username" validate:"-"` 22 | NickName string `json:"nickName" validata:"-"` 23 | Phone string `json:"phone" validate:"-"` 24 | Status *int `json:"status" validate:"-"` 25 | DeptId []int `json:"deptId" validate:"-"` 26 | PageNum *int `json:"pageNum" validate:"required"` 27 | PageSize *int `json:"pageSize" validate:"required"` 28 | } 29 | 30 | type UserRoleUpdate struct { 31 | Id *int `json:"id" validate:"required"` 32 | RoleIds []int `json:"roleIds" validate:"ints"` 33 | } 34 | 35 | type UserUpdate struct { 36 | Id *int `json:"id" validate:"required"` 37 | Username string `json:"username" validate:"required|alphaNum|minLen:1|maxLen:18"` 38 | NickName *string `json:"nickName" validate:"required|minLen:1|maxLen:12"` 39 | Password string `json:"password" validate:"-"` 40 | Avatar string `json:"avatar" validate:"-"` 41 | Phone string `json:"phone" validate:"-"` 42 | Email string `json:"email" validate:"-"` 43 | Status int `json:"status" validate:"-"` 44 | DeptId int `json:"deptId" validate:"-"` 45 | RoleIds []int `json:"roleIds" validate:"ints"` 46 | } 47 | type UserDel struct { 48 | Ids []int `json:"ids" validate:"required"` 49 | } 50 | type UserMove struct { 51 | DeptId *int `json:"deptId" validate:"required"` 52 | Ids []int `json:"ids" validate:"required"` 53 | } 54 | type UserAvatarUpdate struct { 55 | Url string `json:"url"` 56 | } 57 | type UserBaseInfoUpdate struct { 58 | NickName *string `json:"nickName" validate:"required|minLen:1|maxLen:12"` 59 | Phone string `json:"phone" validate:"-"` 60 | Email string `json:"email" validate:"-"` 61 | } 62 | type UserPasswordUpdate struct { 63 | OldPassword string `json:"oldPassword" validate:"required|alphaDash|minLen:6|maxLen:18"` 64 | NewPassword string `json:"newPassword" validate:"required|alphaDash|minLen:6|maxLen:18"` 65 | } 66 | -------------------------------------------------------------------------------- /app/admin/response/dept.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type DeptTree struct { 4 | Id int `json:"id"` 5 | Name string `json:"name"` 6 | ParentId int `json:"parentId"` 7 | Sort int `json:"sort"` 8 | Children []DeptTree `json:"children"` 9 | } 10 | -------------------------------------------------------------------------------- /app/admin/response/log.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import "time" 4 | 5 | type LogList struct { 6 | Id int `json:"id"` 7 | Username string `json:"username" xorm:"'username'"` 8 | Method string `json:"method"` 9 | Action string `json:"action"` 10 | Ip string `json:"ip"` 11 | StatusCode int `json:"statusCode"` 12 | Params string `json:"params"` 13 | Results string `json:"results"` 14 | CreatedTime time.Time `json:"createdTime"` 15 | } 16 | -------------------------------------------------------------------------------- /app/admin/response/menu.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type Meta struct { 4 | Icon string `json:"icon"` 5 | Sort int `json:"sort"` 6 | IsRoot bool `json:"isRoot"` 7 | Title string `json:"title"` 8 | Type int `json:"type"` 9 | Perms string `json:"perms"` 10 | KeepAlive bool `json:"keepAlive"` 11 | Status int `json:"status"` 12 | Visible bool `json:"visible"` 13 | } 14 | type MenuTree struct { 15 | Path string `json:"path"` 16 | Name string `json:"name"` 17 | Meta Meta `json:"meta"` 18 | Component string `json:"component"` 19 | Id int `json:"id"` 20 | ParentId int `json:"parentId"` 21 | Children []MenuTree `json:"children"` 22 | } 23 | -------------------------------------------------------------------------------- /app/admin/response/role.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type Role struct { 4 | Id int `json:"id"` 5 | Name string `json:"name"` 6 | Label string `json:"label"` 7 | Remark string `json:"remark"` 8 | Relevance int `json:"relevance"` 9 | Status int `json:"status"` 10 | MenuIds []int `json:"menuIds"` 11 | DeptIds []int `json:"deptIds"` 12 | } 13 | -------------------------------------------------------------------------------- /app/admin/response/server.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | type ServerInfo struct { 4 | CPU CPU `json:"cpu"` 5 | Host Host `json:"host"` 6 | RAM RAM `json:"ram"` 7 | Disk Disk `json:"disk"` 8 | Runtime Runtime `json:"runtime"` 9 | } 10 | type CPU struct { 11 | Name string `json:"name"` 12 | Cores int32 `json:"cores"` 13 | Percent int `json:"percent"` 14 | } 15 | type Host struct { 16 | OS string `json:"os"` 17 | Kernel string `json:"kernel"` 18 | Runtime string `json:"runtime"` 19 | } 20 | type RAM struct { 21 | Total float64 `json:"total"` 22 | Available float64 `json:"available"` 23 | Used float64 `json:"used"` 24 | Percent int `json:"percent"` 25 | } 26 | type Disk struct { 27 | Total float64 `json:"total"` 28 | Available float64 `json:"available"` 29 | Used float64 `json:"used"` 30 | Percent int `json:"percent"` 31 | } 32 | type Runtime struct { 33 | Version string `json:"version"` 34 | Language string `json:"language"` 35 | StartTime string `json:"startTime"` 36 | Runtime string `json:"runtime"` 37 | } 38 | -------------------------------------------------------------------------------- /app/admin/response/user.go: -------------------------------------------------------------------------------- 1 | package response 2 | 3 | import "time" 4 | 5 | type UserList struct { 6 | Id int `json:"id"` 7 | DeptId int `json:"deptId"` 8 | Username string `json:"username"` 9 | NickName string `json:"nickName"` 10 | Phone string `json:"phone"` 11 | Email string `json:"email"` 12 | Avatar string `json:"avatar"` 13 | Status int `json:"status"` 14 | IsDeleted int `json:"isDeleted"` 15 | CreatedTime time.Time `json:"createdTime"` 16 | UpdatedTime time.Time `json:"updatedTime"` 17 | RoleIds string `json:"roleIds"` 18 | DeptName string `json:"deptName"` 19 | } 20 | type UserInfo struct { 21 | Id int `json:"id"` 22 | DeptId int `json:"deptId"` 23 | Username string `json:"username"` 24 | NickName string `json:"nickName"` 25 | Phone string `json:"phone"` 26 | Email string `json:"email"` 27 | Avatar string `json:"avatar"` 28 | Status int `json:"status"` 29 | RoleIds []int `json:"roleIds"` 30 | } 31 | type UserProfile struct { 32 | Id int `json:"id"` 33 | DeptId int `json:"deptId"` 34 | Username string `json:"username"` 35 | NickName string `json:"nickName"` 36 | Phone string `json:"phone"` 37 | Email string `json:"email"` 38 | Avatar string `json:"avatar"` 39 | Status int `json:"status"` 40 | IsDeleted int `json:"isDeleted"` 41 | Roles []string `json:"roles"` 42 | DeptName string `json:"deptName" xorm:"'name'"` 43 | RoleNames []string `json:"roleNames"` 44 | CreatedTime time.Time `json:"createdTime"` 45 | UpdatedTime time.Time `json:"updatedTime"` 46 | } 47 | -------------------------------------------------------------------------------- /app/admin/routers/common.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "seed-admin/common/middlewares" 5 | ) 6 | 7 | func (admin *Admin) useCommon() { 8 | router := admin.router.Group("common").Use(middlewares.JwtAuth()) 9 | { 10 | router.POST("/uploadImages", admin.Common.UploadImages) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app/admin/routers/dept.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "seed-admin/common/auth" 5 | "seed-admin/common/middlewares" 6 | ) 7 | 8 | func (admin *Admin) useDept() { 9 | router := admin.router.Group("dept").Use(middlewares.JwtAuth()).Use(middlewares.OperationRecorder()) 10 | { 11 | router.GET("/list", auth.Perms([]string{"sys:dept:list"}), admin.Dept.List) 12 | router.GET("/info", auth.Perms([]string{"sys:dept:info"}), admin.Dept.Info) 13 | router.POST("/add", auth.Perms([]string{"sys:dept:add"}), admin.Dept.Add) 14 | router.POST("/update", auth.Perms([]string{"sys:dept:update"}), admin.Dept.Update) 15 | router.POST("/del", auth.Perms([]string{"sys:dept:del"}), admin.Dept.Del) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/admin/routers/dict.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "seed-admin/common/auth" 5 | "seed-admin/common/middlewares" 6 | ) 7 | 8 | func (admin *Admin) useDict() { 9 | router := admin.router.Group("dict").Use(middlewares.JwtAuth()).Use(middlewares.OperationRecorder()) 10 | { 11 | router.GET("/list", auth.Perms([]string{"sys:dict:list"}), admin.Dict.List) 12 | router.GET("/info", auth.Perms([]string{"sys:dict:info"}), admin.Dict.Info) 13 | router.POST("/add", auth.Perms([]string{"sys:dict:add"}), admin.Dict.Add) 14 | router.POST("/update", auth.Perms([]string{"sys:dict:update"}), admin.Dict.Update) 15 | router.POST("/del", auth.Perms([]string{"sys:dict:del"}), admin.Dict.Del) 16 | router.GET("/dataList", auth.Perms([]string{"sys:dict:details:list"}), admin.Dict.DataList) 17 | router.GET("/dataInfo", auth.Perms([]string{"sys:dict:details:info"}), admin.Dict.DataInfo) 18 | router.POST("/dataAdd", auth.Perms([]string{"sys:dict:details:add"}), admin.Dict.DataAdd) 19 | router.POST("/dataUpdate", auth.Perms([]string{"sys:dict:details:update"}), admin.Dict.DataUpdate) 20 | router.POST("/dataDel", auth.Perms([]string{"sys:dict:details:del"}), admin.Dict.DataDel) 21 | router.GET("/typeData", admin.Dict.TypeData) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/admin/routers/index.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "seed-admin/app/admin/controllers" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | type Admin struct { 10 | router *gin.RouterGroup 11 | User *controllers.User 12 | Menu *controllers.Menu 13 | Role *controllers.Role 14 | Dept *controllers.Dept 15 | Dict *controllers.Dict 16 | Log *controllers.Log 17 | Server *controllers.Server 18 | Common *controllers.Common 19 | } 20 | 21 | func New(router *gin.RouterGroup) { 22 | controllers := new(Admin) 23 | controllers.router = router.Group("admin") 24 | controllers.useUser() 25 | controllers.useMenu() 26 | controllers.useRole() 27 | controllers.useDept() 28 | controllers.useDict() 29 | controllers.useLog() 30 | controllers.useServer() 31 | controllers.useCommon() 32 | } 33 | -------------------------------------------------------------------------------- /app/admin/routers/log.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "seed-admin/common/auth" 5 | "seed-admin/common/middlewares" 6 | ) 7 | 8 | func (admin *Admin) useLog() { 9 | router := admin.router.Group("log").Use(middlewares.JwtAuth()) 10 | { 11 | router.GET("/list", auth.Perms([]string{"sys:log:list"}), admin.Log.List) 12 | router.POST("/del", auth.Perms([]string{"sys:log:del"}), admin.Log.Del) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/admin/routers/menu.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "seed-admin/common/auth" 5 | "seed-admin/common/middlewares" 6 | ) 7 | 8 | func (admin *Admin) useMenu() { 9 | router := admin.router.Group("menu").Use(middlewares.JwtAuth()).Use(middlewares.OperationRecorder()) 10 | { 11 | router.GET("/permMenu", admin.Menu.PermMenu) 12 | router.GET("/list", auth.Perms([]string{"sys:menu:list"}), admin.Menu.List) 13 | router.GET("/info", auth.Perms([]string{"sys:menu:info"}), admin.Menu.Info) 14 | router.POST("/add", auth.Perms([]string{"sys:menu:add"}), admin.Menu.Add) 15 | router.POST("/update", auth.Perms([]string{"sys:menu:update"}), admin.Menu.Update) 16 | router.POST("/del", auth.Perms([]string{"sys:menu:del"}), admin.Menu.Del) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/admin/routers/role.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "seed-admin/common/auth" 5 | "seed-admin/common/middlewares" 6 | ) 7 | 8 | func (admin *Admin) useRole() { 9 | router := admin.router.Group("role").Use(middlewares.JwtAuth()).Use(middlewares.OperationRecorder()) 10 | { 11 | router.GET("/list", auth.Perms([]string{"sys:role:list"}), admin.Role.List) 12 | router.GET("/info", auth.Perms([]string{"sys:role:info"}), admin.Role.Info) 13 | router.POST("/add", auth.Perms([]string{"sys:role:add"}), admin.Role.Add) 14 | router.POST("/update", auth.Perms([]string{"sys:role:update"}), admin.Role.Update) 15 | router.POST("/del", auth.Perms([]string{"sys:role:del"}), admin.Role.Del) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/admin/routers/server.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "seed-admin/common/auth" 5 | "seed-admin/common/middlewares" 6 | ) 7 | 8 | func (admin *Admin) useServer() { 9 | router := admin.router.Group("server").Use(middlewares.JwtAuth()) 10 | { 11 | router.GET("/info", auth.Perms([]string{"sys:server:info"}), admin.Server.Info) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/admin/routers/user.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "seed-admin/common/auth" 5 | "seed-admin/common/middlewares" 6 | ) 7 | 8 | func (admin *Admin) useUser() { 9 | router := admin.router.Group("user") 10 | { 11 | router.POST("/login", middlewares.OperationRecorder(), admin.User.Login) 12 | router.GET("/captcha", admin.User.Captcha) 13 | jwtAuth := router.Group("").Use(middlewares.JwtAuth(), middlewares.OperationRecorder()) 14 | { 15 | jwtAuth.GET("/person", admin.User.Person) 16 | jwtAuth.POST("/list", auth.Perms([]string{"sys:user:list"}), admin.User.List) 17 | jwtAuth.POST("/add", auth.Perms([]string{"sys:user:add"}), admin.User.Add) 18 | jwtAuth.POST("/del", auth.Perms([]string{"sys:user:del"}), admin.User.Del) 19 | jwtAuth.GET("/info", auth.Perms([]string{"sys:user:info"}), admin.User.Info) 20 | jwtAuth.POST("/update", auth.Perms([]string{"sys:user:update"}), admin.User.Update) 21 | jwtAuth.POST("/move", auth.Perms([]string{"sys:user:move"}), admin.User.Move) 22 | jwtAuth.POST("/updateUserRole", auth.Perms([]string{"sys:user:updateUserRole"}), admin.User.UpdateUserRole) 23 | jwtAuth.POST("/updateAvatar", admin.User.UpdateAvatar) 24 | jwtAuth.POST("/updateBaseInfo", admin.User.UpdateBaseInfo) 25 | jwtAuth.POST("/updatePassword", admin.User.UpdatePassword) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/admin/services/common.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io/ioutil" 7 | "mime/multipart" 8 | "os" 9 | "seed-admin/app/admin/entity" 10 | "seed-admin/common" 11 | "seed-admin/utils" 12 | "strings" 13 | "time" 14 | 15 | "github.com/gin-gonic/gin" 16 | "github.com/gofrs/uuid" 17 | ) 18 | 19 | type CommonService struct{} 20 | 21 | // 上传文件 22 | func (*CommonService) Uploads(ctx *gin.Context) (map[string]any, error) { 23 | fileType := ctx.Request.FormValue("type") 24 | file, header, err := ctx.Request.FormFile("file") 25 | if err != nil { 26 | return nil, err 27 | } 28 | // 允许上传的后缀列表 29 | allowList := []string{"image/png", "image/jpeg", "image/jpg", "image/gif"} 30 | // 判断大小 31 | if header.Size > (1024*1024)*common.CONFIG.Int64("upload.fileSize") { 32 | return nil, errors.New("上传的文件大小超过限制") 33 | } 34 | // 判断格式 35 | if !utils.SliceIncludes(allowList, header.Header.Get("Content-Type")) { 36 | return nil, errors.New("禁止上传此格式") 37 | } 38 | url, id, err := local(file, header, fileType, ctx.Request.Host, ctx.Request.Header.Get("X-Forwarded-Proto")) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return map[string]any{ 43 | "url": url, 44 | "id": id, 45 | }, nil 46 | } 47 | 48 | // 本地上传 49 | func local(file multipart.File, header *multipart.FileHeader, fileType string, host string, scheme string) (string, int, error) { 50 | // 读取byte 51 | b, err := ioutil.ReadAll(file) 52 | if err != nil { 53 | return "", 0, err 54 | } 55 | // 如果uploads目录不存在则创建 56 | uploadsPath := fmt.Sprintf(".%v%v", common.CONFIG.String("app.staticPath"), common.CONFIG.String("upload.path")) 57 | if ok, _ := utils.PathExists(uploadsPath); !ok { 58 | if err := os.Mkdir(uploadsPath, 0777); err != nil { 59 | return "", 0, err 60 | } 61 | } 62 | // 当日的目录不存在则创建 63 | toDayPath := fmt.Sprintf("%v/%v", uploadsPath, time.Now().Format("2006-01-02")) 64 | if ok, _ := utils.PathExists(toDayPath); !ok { 65 | if err := os.Mkdir(toDayPath, 0777); err != nil { 66 | return "", 0, err 67 | } 68 | } 69 | // 生成UUID文件名 70 | fileName, err := uuid.NewV4() 71 | if err != nil { 72 | return "", 0, err 73 | } 74 | // 获取格式 75 | suffix := strings.Split(header.Filename, ".")[1] 76 | // 组装路径输出到文件 77 | filePath := toDayPath + "/" + fileName.String() + "." + suffix 78 | if err := ioutil.WriteFile(filePath, b, 0777); err != nil { 79 | return "", 0, err 80 | } 81 | // 判断是否有证书 82 | schemeStr := "" 83 | if scheme == "" || scheme == "http" { 84 | schemeStr = "http://" 85 | } else { 86 | schemeStr = "https://" 87 | } 88 | // 组装url 89 | url := fmt.Sprintf("%v%v%v", schemeStr, host, strings.TrimPrefix(filePath, ".")) 90 | typeId := 1 91 | if ok, err := common.DB.Table("admin_uploads_type").Where("label = ?", fileType).Cols("id").Get(&typeId); !ok { 92 | if err != nil { 93 | return "", 0, err 94 | } 95 | return "", 0, errors.New("图片分类不存在") 96 | } 97 | // 入库 98 | upload := &entity.AdminUploads{ 99 | Name: header.Filename, 100 | Url: url, 101 | Type: typeId, 102 | } 103 | if _, err := common.DB.Insert(upload); err != nil { 104 | return "", 0, err 105 | } 106 | return upload.Url, upload.Id, nil 107 | } 108 | -------------------------------------------------------------------------------- /app/admin/services/dept.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | "seed-admin/app/admin/entity" 6 | "seed-admin/app/admin/request" 7 | "seed-admin/app/admin/response" 8 | "seed-admin/common" 9 | "seed-admin/utils" 10 | ) 11 | 12 | type DeptService struct { 13 | authCache utils.AuthCache 14 | } 15 | 16 | // 获取所有部门 17 | func (*DeptService) GetAllDept() ([]response.DeptTree, error) { 18 | depts := make([]response.DeptTree, 0) 19 | sql := ` 20 | SELECT 21 | * 22 | FROM 23 | admin_sys_dept 24 | ORDER BY 25 | sort 26 | ` 27 | if err := common.DB.SQL(sql).Find(&depts); err != nil { 28 | return nil, err 29 | } 30 | res := deptTree(depts, 0) 31 | return res, nil 32 | } 33 | 34 | // 增加部门 35 | func (*DeptService) AddDept(params *request.DeptAdd) error { 36 | dept := &entity.AdminSysDept{ 37 | Name: *params.Name, 38 | ParentId: *params.ParentId, 39 | Sort: params.Sort, 40 | } 41 | if _, err := common.DB.Insert(dept); err != nil { 42 | return err 43 | } 44 | return nil 45 | } 46 | 47 | // 更新部门 48 | func (*DeptService) UpdateDept(params *request.DeptUpdate) error { 49 | if *params.Id == 1 { 50 | if *params.ParentId != 0 { 51 | return errors.New("顶级部门的上级不可更改") 52 | } 53 | } 54 | if *params.Id == *params.ParentId { 55 | return errors.New("不能自己成为自己的上级") 56 | } 57 | dept := &entity.AdminSysDept{ 58 | Name: *params.Name, 59 | ParentId: *params.ParentId, 60 | Sort: params.Sort, 61 | } 62 | if _, err := common.DB.Where("id = ?", params.Id).AllCols().Update(dept); err != nil { 63 | return err 64 | } 65 | return nil 66 | } 67 | 68 | // 删除部门 69 | func (*DeptService) DelDept(params *request.DeptDel) error { 70 | if *params.Pid == 1 { 71 | return errors.New("顶级部门不可删除") 72 | } 73 | session := common.DB.NewSession() 74 | defer session.Close() 75 | // 事务开启 76 | if err := session.Begin(); err != nil { 77 | return err 78 | } 79 | pid := 0 80 | if ok, err := session.Table("admin_sys_dept").Where("id = ?", params.Pid).Cols("parent_id").Get(&pid); !ok { 81 | if err != nil { 82 | return err 83 | } 84 | return errors.New("获取父级的上级ID失败") 85 | } 86 | // 删除部门及子部门 87 | if _, err := session.Table("admin_sys_dept").In("id", params.Ids).Delete(); err != nil { 88 | return err 89 | } 90 | // 处理用户 91 | if params.UserDel { 92 | if _, err := session.Table("admin_sys_user").In("dept_id", params.Ids).Delete(); err != nil { 93 | return err 94 | } 95 | } else { 96 | user := new(entity.AdminSysUser) 97 | user.DeptId = pid 98 | if _, err := session.In("dept_id", params.Ids).Cols("dept_id").Update(user); err != nil { 99 | return err 100 | } 101 | } 102 | return session.Commit() 103 | } 104 | 105 | // 获取部门信息 106 | func (*DeptService) GetInfo(id *int) (*entity.AdminSysDept, error) { 107 | dept := new(entity.AdminSysDept) 108 | if ok, err := common.DB.Where("id = ?", id).Get(dept); !ok { 109 | if err != nil { 110 | return nil, err 111 | } 112 | return nil, errors.New("获取部门信息失败") 113 | } 114 | return dept, nil 115 | } 116 | 117 | // 根据用户ID获取部门权限 118 | func (deptService *DeptService) GetIds(userId int) ([]int, error) { 119 | deptIds := make([]int, 0) 120 | roleIds, err := deptService.authCache.GetRoleIds(userId) 121 | if err != nil { 122 | return nil, err 123 | } 124 | if err := common.DB.Table("admin_sys_role_dept").In("role_id", roleIds).Cols("dept_id").Find(&deptIds); err != nil { 125 | if err != nil { 126 | return nil, err 127 | } 128 | } 129 | return deptIds, nil 130 | } 131 | 132 | // 递归获取部门树 133 | func deptTree(depts []response.DeptTree, pid int) []response.DeptTree { 134 | var nodes = make([]response.DeptTree, 0, len(depts)) 135 | for _, item := range depts { 136 | if item.ParentId == pid { 137 | item.Children = append(item.Children, deptTree(depts, item.Id)...) 138 | nodes = append(nodes, item) 139 | } 140 | } 141 | return nodes 142 | } 143 | -------------------------------------------------------------------------------- /app/admin/services/dict.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | "seed-admin/app/admin/entity" 6 | "seed-admin/app/admin/request" 7 | "seed-admin/common" 8 | ) 9 | 10 | type DictService struct{} 11 | 12 | // 获取所有字典 13 | func (*DictService) GetAllDictType(params *request.DictList) ([]entity.AdminSysDictType, int64, error) { 14 | dict := new(entity.AdminSysDictType) 15 | dicts := make([]entity.AdminSysDictType, 0) 16 | count, err := common.DB. 17 | Where("name LIKE ? AND status LIKE ?", "%"+params.Name+"%", "%"+params.Status+"%"). 18 | Count(dict) 19 | if err != nil { 20 | return nil, 0, err 21 | } 22 | if err := common.DB. 23 | Where("name LIKE ? AND status LIKE ?", "%"+params.Name+"%", "%"+params.Status+"%"). 24 | Limit(*params.PageSize, (*params.PageNum-1)*(*params.PageSize)). 25 | Find(&dicts); err != nil { 26 | return nil, 0, err 27 | } 28 | return dicts, count, nil 29 | } 30 | 31 | // 增加字典 32 | func (*DictService) AddDictType(params *request.DictAdd) error { 33 | dictType := &entity.AdminSysDictType{ 34 | Name: *params.Name, 35 | Type: *params.Type, 36 | Status: params.Status, 37 | } 38 | // 插入角色表 39 | if _, err := common.DB.Insert(dictType); err != nil { 40 | return err 41 | } 42 | return nil 43 | } 44 | 45 | // 获取字典类型信息 46 | func (*DictService) GetDictTypeInfo(id *int) (*entity.AdminSysDictType, error) { 47 | dictType := new(entity.AdminSysDictType) 48 | if ok, err := common.DB.Where("id = ?", id).Get(dictType); !ok { 49 | if err != nil { 50 | return nil, err 51 | } 52 | return nil, errors.New("获取字典类型信息失败") 53 | } 54 | return dictType, nil 55 | } 56 | 57 | // 更新字典 58 | func (*DictService) UpdateDictType(params *request.DictUpdate) error { 59 | dictType := &entity.AdminSysDictType{ 60 | Name: *params.Name, 61 | Type: *params.Type, 62 | Status: params.Status, 63 | } 64 | if _, err := common.DB.Where("id = ?", params.Id).AllCols().Update(dictType); err != nil { 65 | return err 66 | } 67 | return nil 68 | } 69 | 70 | // 删除字典 71 | func (*DictService) DelDictType(params *request.DictDel) error { 72 | session := common.DB.NewSession() 73 | defer session.Close() 74 | if err := session.Begin(); err != nil { 75 | return err 76 | } 77 | // 删除类型 78 | dictType := new(entity.AdminSysDictType) 79 | if _, err := common.DB.In("id", params.Ids).Delete(dictType); err != nil { 80 | return err 81 | } 82 | dictData := new(entity.AdminSysDictData) 83 | // 删除类型下的数据 84 | if _, err := session.In("pid", params.Ids).Delete(dictData); err != nil { 85 | return err 86 | } 87 | return session.Commit() 88 | } 89 | 90 | // 获取词典的全部数据 91 | func (*DictService) GetAllDictData(params *request.DictDataList) ([]entity.AdminSysDictData, int64, error) { 92 | dictData := new(entity.AdminSysDictData) 93 | dictDatas := make([]entity.AdminSysDictData, 0) 94 | count, err := common.DB. 95 | Where("label LIKE ? AND status LIKE ?", "%"+params.Label+"%", "%"+params.Status+"%"). 96 | Where("pid = ?", params.Pid).Count(dictData) 97 | if err != nil { 98 | return nil, 0, err 99 | } 100 | if err := common.DB. 101 | Where("label LIKE ? AND status LIKE ?", "%"+params.Label+"%", "%"+params.Status+"%"). 102 | Where("pid = ?", params.Pid). 103 | Limit(*params.PageSize, (*params.PageNum-1)*(*params.PageSize)). 104 | Find(&dictDatas); err != nil { 105 | return nil, 0, err 106 | } 107 | return dictDatas, count, nil 108 | } 109 | 110 | // 增加字典数据 111 | func (*DictService) AddDictData(params *request.DictDataAdd) error { 112 | dictData := &entity.AdminSysDictData{ 113 | Pid: *params.Pid, 114 | Label: *params.Label, 115 | Value: *params.Value, 116 | Status: params.Status, 117 | } 118 | // 插入角色表 119 | if _, err := common.DB.Insert(dictData); err != nil { 120 | return err 121 | } 122 | return nil 123 | } 124 | 125 | // 获取字典数据信息 126 | func (*DictService) GetDictDataInfo(id *int) (*entity.AdminSysDictData, error) { 127 | dictData := new(entity.AdminSysDictData) 128 | if ok, err := common.DB.Where("id = ?", id).Get(dictData); !ok { 129 | if err != nil { 130 | return nil, err 131 | } 132 | return nil, errors.New("获取字典数据信息失败") 133 | } 134 | return dictData, nil 135 | } 136 | 137 | // 更新字典数据 138 | func (*DictService) UpdateDictData(params *request.DictDataUpdate) error { 139 | dictData := &entity.AdminSysDictData{ 140 | Pid: *params.Pid, 141 | Label: *params.Label, 142 | Value: *params.Value, 143 | Status: params.Status, 144 | } 145 | if _, err := common.DB.Where("id = ?", params.Id).AllCols().Update(dictData); err != nil { 146 | return err 147 | } 148 | return nil 149 | } 150 | 151 | // 删除字典数据 152 | func (*DictService) DelDictData(params *request.DictDataDel) error { 153 | dictData := new(entity.AdminSysDictData) 154 | // 删除类型下的数据 155 | if _, err := common.DB.In("id", params.Ids).Delete(dictData); err != nil { 156 | return err 157 | } 158 | return nil 159 | } 160 | 161 | // 根据类型获取数据 162 | func (*DictService) GetTypeData(dictType string) ([]entity.AdminSysDictData, error) { 163 | dictData := make([]entity.AdminSysDictData, 0) 164 | if err := common.DB.Table("admin_sys_dict_data"). 165 | Alias("data"). 166 | Join("INNER", []string{"admin_sys_dict_type", "type"}, "data.pid = type.id"). 167 | Where("type = ?", dictType). 168 | Find(&dictData); err != nil { 169 | return nil, err 170 | } 171 | return dictData, nil 172 | } 173 | -------------------------------------------------------------------------------- /app/admin/services/log.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "seed-admin/app/admin/entity" 5 | "seed-admin/app/admin/request" 6 | "seed-admin/app/admin/response" 7 | "seed-admin/common" 8 | ) 9 | 10 | type LogService struct{} 11 | 12 | // 增加日志 13 | func (*LogService) AddLog(log *entity.AdminSysLog) error { 14 | if _, err := common.DB.Insert(log); err != nil { 15 | return err 16 | } 17 | return nil 18 | } 19 | 20 | // 删除日志 21 | func (*LogService) DelLog(params *request.LogDel) error { 22 | // 删除用户 23 | if _, err := common.DB.Table("admin_sys_log").In("id", params.Ids).Delete(); err != nil { 24 | return err 25 | } 26 | return nil 27 | } 28 | 29 | // 获取全部日志 30 | func (*LogService) GetAllLog(params *request.LogList) ([]response.LogList, int64, error) { 31 | logs := make([]response.LogList, 0) 32 | count, err := common.DB.SQL(` 33 | SELECT count(l.id) FROM 34 | admin_sys_log AS l 35 | LEFT JOIN 36 | admin_sys_user AS u 37 | ON 38 | l.user_id = u.id 39 | WHERE 40 | l.method LIKE ? 41 | AND 42 | l.action LIKE ? 43 | AND 44 | l.ip LIKE ?`, "%"+params.Method+"%", "%"+params.Action+"%", "%"+params.Ip+"%"). 45 | Count() 46 | if err != nil { 47 | return nil, 0, err 48 | } 49 | if err := common.DB.SQL(` 50 | SELECT * FROM 51 | admin_sys_log AS l 52 | LEFT JOIN 53 | admin_sys_user AS u 54 | ON 55 | l.user_id = u.id 56 | WHERE 57 | l.method LIKE ? 58 | AND 59 | l.action LIKE ? 60 | AND 61 | l.ip LIKE ? 62 | ORDER BY l.id DESC 63 | LIMIT ?,?`, "%"+params.Method+"%", "%"+params.Action+"%", "%"+params.Ip+"%", (*params.PageNum-1)*(*params.PageSize), *params.PageSize). 64 | Find(&logs); err != nil { 65 | return nil, 0, err 66 | } 67 | return logs, count, nil 68 | } 69 | -------------------------------------------------------------------------------- /app/admin/services/menu.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | "seed-admin/app/admin/entity" 6 | "seed-admin/app/admin/request" 7 | "seed-admin/app/admin/response" 8 | "seed-admin/common" 9 | "seed-admin/utils" 10 | "strconv" 11 | ) 12 | 13 | type MenuService struct { 14 | authCache utils.AuthCache 15 | } 16 | 17 | // 获取用户菜单 18 | func (menuService *MenuService) GetMenu(userId int) ([]response.MenuTree, error) { 19 | roleIds, err := menuService.authCache.GetRoleIds(userId) 20 | if err != nil { 21 | return nil, err 22 | } 23 | // 利用工具库函数把权限ID数组转成字符串 24 | roleIdsIn := utils.SliceToInStr(roleIds) 25 | // 组装sql 26 | sql := fmt.Sprintf(` 27 | SELECT m.* FROM admin_sys_menu m 28 | JOIN admin_sys_role_menu rm 29 | on m.id = rm.menu_id AND rm.role_id in (%v) 30 | WHERE status = 0 GROUP BY m.id ORDER BY m.sort`, roleIdsIn) 31 | res, err := common.DB.QueryString(sql) 32 | if err != nil { 33 | return nil, err 34 | } 35 | menus := assemblyMenu(res) 36 | return menus, nil 37 | } 38 | 39 | // 获取用户权限 40 | func (menuService *MenuService) GetPerms(userId int) ([]string, error) { 41 | roleIds, err := menuService.authCache.GetRoleIds(userId) 42 | perms := make([]string, 0) 43 | if err != nil { 44 | return nil, err 45 | } 46 | if len(roleIds) == 0 { 47 | return perms, nil 48 | } 49 | // 利用工具库函数把权限ID数组转成字符串 50 | roleIdsIn := utils.SliceToInStr(roleIds) 51 | // 组装SQL 52 | sql := fmt.Sprintf(` 53 | SELECT DISTINCT m.perms FROM admin_sys_menu m 54 | JOIN admin_sys_role_menu rm 55 | on m.id = rm.menu_id AND rm.role_id in(%v) 56 | WHERE m.perms IS NOT NULL AND m.perms != "" AND m.status = 0 57 | `, roleIdsIn) 58 | if err := common.DB.SQL(sql).Find(&perms); err != nil { 59 | return nil, err 60 | } 61 | menuService.authCache.SetMenuPerms(userId, perms) 62 | return perms, nil 63 | } 64 | 65 | // 获取全部菜单 66 | func (*MenuService) GetAllMenu(name string, status string) ([]response.MenuTree, error) { 67 | // 组装sql 68 | sql := fmt.Sprintf(` 69 | SELECT * FROM admin_sys_menu 70 | WHERE name LIKE '%s' AND status LIKE '%s' 71 | ORDER BY sort`, "%"+name+"%", "%"+status+"%") 72 | res, err := common.DB.QueryString(sql) 73 | if err != nil { 74 | return nil, err 75 | } 76 | menus := assemblyMenu(res) 77 | return menus, nil 78 | } 79 | 80 | // 添加菜单 81 | func (*MenuService) AddMenu(params *request.Menu) error { 82 | menu := entity.AdminSysMenu{ 83 | ParentId: *params.ParentId, 84 | Name: *params.Name, 85 | RouterName: params.RouterName, 86 | RouterPath: params.RouterPath, 87 | PagePath: params.PagePath, 88 | Perms: params.Perms, 89 | Type: *params.Type, 90 | Icon: params.Icon, 91 | Sort: params.Sort, 92 | KeepAlive: params.KeepAlive, 93 | Status: params.Status, 94 | } 95 | if _, err := common.DB.Insert(menu); err != nil { 96 | return err 97 | } 98 | return nil 99 | } 100 | 101 | // 编辑菜单 102 | func (menuService *MenuService) UpdateMenu(params *request.Menu) error { 103 | menu := entity.AdminSysMenu{ 104 | ParentId: *params.ParentId, 105 | Name: *params.Name, 106 | RouterName: params.RouterName, 107 | RouterPath: params.RouterPath, 108 | PagePath: params.PagePath, 109 | Perms: params.Perms, 110 | Type: *params.Type, 111 | Icon: params.Icon, 112 | Sort: params.Sort, 113 | KeepAlive: params.KeepAlive, 114 | Status: params.Status, 115 | Visible: params.Visible, 116 | } 117 | if _, err := common.DB.Where("id = ?", params.Id).AllCols().Update(menu); err != nil { 118 | return err 119 | } 120 | if err := menuService.authCache.UpdateAllPerm(); err != nil { 121 | return err 122 | } 123 | return nil 124 | } 125 | 126 | // 删除菜单 127 | func (menuService *MenuService) DelMenu(params *request.MenuDel) error { 128 | if _, err := common.DB.Table("admin_sys_menu").In("id", params.Ids).Delete(); err != nil { 129 | return err 130 | } 131 | if err := menuService.authCache.UpdateAllPerm(); err != nil { 132 | return err 133 | } 134 | return nil 135 | } 136 | 137 | // 获取菜单信息 138 | func (*MenuService) GetInfo(id int) (*entity.AdminSysMenu, error) { 139 | menu := &entity.AdminSysMenu{ 140 | Id: id, 141 | } 142 | if ok, err := common.DB.Get(menu); ok { 143 | return menu, nil 144 | } else { 145 | if err != nil { 146 | return nil, err 147 | } 148 | return nil, fmt.Errorf("获取菜单信息失败") 149 | } 150 | } 151 | 152 | // 组装菜单 153 | func assemblyMenu(res []map[string]string) []response.MenuTree { 154 | menus := make([]response.MenuTree, 0, len(res)) 155 | if len(res) == 0 { 156 | return menus 157 | } 158 | pid, _ := strconv.Atoi(res[0]["parent_id"]) 159 | for _, item := range res { 160 | parentId, _ := strconv.Atoi(item["parent_id"]) 161 | if pid > parentId { 162 | pid = parentId 163 | } 164 | isRoot := false 165 | if parentId == 0 { 166 | isRoot = true 167 | } 168 | visible := false 169 | if item["visible"] == "0" { 170 | visible = true 171 | } 172 | id, _ := strconv.Atoi(item["id"]) 173 | t, _ := strconv.Atoi(item["type"]) 174 | sort, _ := strconv.Atoi(item["sort"]) 175 | keepAlive, _ := strconv.ParseBool(item["keep_alive"]) 176 | status, _ := strconv.Atoi(item["status"]) 177 | menus = append(menus, response.MenuTree{ 178 | Path: item["router_path"], 179 | Name: item["router_name"], 180 | Component: item["page_path"], 181 | Id: id, 182 | ParentId: parentId, 183 | Meta: response.Meta{ 184 | Icon: item["icon"], 185 | Sort: sort, 186 | IsRoot: isRoot, 187 | Title: item["name"], 188 | Type: t, 189 | Perms: item["perms"], 190 | KeepAlive: keepAlive, 191 | Status: status, 192 | Visible: visible, 193 | }, 194 | Children: []response.MenuTree{}, 195 | }) 196 | } 197 | m := menuTree(menus, pid) 198 | return m 199 | } 200 | 201 | // 递归获取菜单树 202 | func menuTree(menus []response.MenuTree, pid int) []response.MenuTree { 203 | var nodes = make([]response.MenuTree, 0, len(menus)) 204 | for _, item := range menus { 205 | if item.ParentId == pid { 206 | item.Children = append(item.Children, menuTree(menus, item.Id)...) 207 | nodes = append(nodes, item) 208 | } 209 | } 210 | return nodes 211 | } 212 | -------------------------------------------------------------------------------- /app/admin/services/role.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "seed-admin/app/admin/entity" 7 | "seed-admin/app/admin/request" 8 | "seed-admin/app/admin/response" 9 | "seed-admin/common" 10 | "seed-admin/utils" 11 | ) 12 | 13 | type RoleService struct { 14 | authCache utils.AuthCache 15 | } 16 | 17 | // 获取全部角色 18 | func (*RoleService) GetAllRole(params *request.RoleList) ([]entity.AdminSysRole, int64, error) { 19 | role := new(entity.AdminSysRole) 20 | roles := make([]entity.AdminSysRole, 0) 21 | count, err := common.DB.Where("name LIKE ?", "%"+params.Name+"%").Count(role) 22 | if err != nil { 23 | return nil, 0, err 24 | } 25 | if err := common.DB.Where("name LIKE ?", "%"+params.Name+"%"). 26 | Limit(*params.PageSize, (*params.PageNum-1)*(*params.PageSize)). 27 | Find(&roles); err != nil { 28 | return nil, 0, err 29 | } 30 | return roles, count, nil 31 | } 32 | 33 | // 获取角色信息 34 | func (*RoleService) GetInfo(id int) (*response.Role, error) { 35 | role := &entity.AdminSysRole{ 36 | Id: id, 37 | } 38 | menuIds := make([]int, 0) 39 | deptIds := make([]int, 0) 40 | if ok, err := common.DB.Get(role); ok { 41 | if err := common.DB.Table("admin_sys_role_menu").Where("role_id = ?", role.Id).Cols("menu_id").Find(&menuIds); err != nil { 42 | return nil, err 43 | } 44 | if err := common.DB.Table("admin_sys_role_dept").Where("role_id = ?", role.Id).Cols("dept_id").Find(&deptIds); err != nil { 45 | return nil, err 46 | } 47 | res := &response.Role{ 48 | Id: role.Id, 49 | Name: role.Name, 50 | Label: role.Label, 51 | Remark: role.Remark, 52 | Relevance: role.Relevance, 53 | MenuIds: menuIds, 54 | DeptIds: deptIds, 55 | } 56 | return res, nil 57 | } else { 58 | if err != nil { 59 | return nil, err 60 | } 61 | return nil, fmt.Errorf("获取角色信息失败") 62 | } 63 | } 64 | 65 | // 增加一个角色 66 | func (*RoleService) AddRole(params *request.Role) error { 67 | session := common.DB.NewSession() 68 | defer session.Close() 69 | if err := session.Begin(); err != nil { 70 | return err 71 | } 72 | role := &entity.AdminSysRole{ 73 | Name: *params.Name, 74 | Label: *params.Label, 75 | Remark: params.Remark, 76 | Relevance: params.Relevance, 77 | } 78 | // 插入角色表 79 | if _, err := session.Insert(role); err != nil { 80 | return err 81 | } 82 | if len(params.MenuIds) == 0 { 83 | return errors.New("至少需要选择一个菜单") 84 | } 85 | // 插入角色菜单关联表 86 | roleMenu := make([]entity.AdminSysRoleMenu, 0, len(params.MenuIds)) 87 | for _, menuId := range params.MenuIds { 88 | roleMenu = append(roleMenu, entity.AdminSysRoleMenu{ 89 | RoleId: role.Id, 90 | MenuId: menuId, 91 | }) 92 | } 93 | if _, err := session.Insert(roleMenu); err != nil { 94 | return err 95 | } 96 | if len(params.DeptIds) == 0 { 97 | return session.Commit() 98 | } 99 | // 插入角色部门关联表 100 | roleDept := make([]entity.AdminSysRoleDept, 0, len(params.DeptIds)) 101 | for _, deptId := range params.DeptIds { 102 | roleDept = append(roleDept, entity.AdminSysRoleDept{ 103 | RoleId: role.Id, 104 | DeptId: deptId, 105 | }) 106 | } 107 | if _, err := session.Insert(roleDept); err != nil { 108 | return err 109 | } 110 | return session.Commit() 111 | } 112 | 113 | // 更新角色 114 | func (roleService *RoleService) UpdateRole(params *request.RoleUpdate) error { 115 | session := common.DB.NewSession() 116 | defer session.Close() 117 | if err := session.Begin(); err != nil { 118 | return err 119 | } 120 | role := entity.AdminSysRole{ 121 | Name: *params.Name, 122 | Label: *params.Label, 123 | Remark: params.Remark, 124 | Relevance: params.Relevance, 125 | } 126 | // 更新角色表 127 | if _, err := session.Where("id = ?", params.Id).AllCols().Update(role); err != nil { 128 | return err 129 | } 130 | // 删除角色原有菜单权限 131 | if _, err := session.Table("admin_sys_role_menu").Where("role_id = ?", params.Id).Delete(); err != nil { 132 | return err 133 | } 134 | // 删除角色原有部门权限 135 | if _, err := session.Table("admin_sys_role_dept").Where("role_id = ?", params.Id).Delete(); err != nil { 136 | return err 137 | } 138 | if len(params.MenuIds) != 0 { 139 | // 增加新的角色权限 140 | roleMenu := make([]entity.AdminSysRoleMenu, 0, len(params.MenuIds)) 141 | for _, menuId := range params.MenuIds { 142 | roleMenu = append(roleMenu, entity.AdminSysRoleMenu{ 143 | RoleId: *params.Id, 144 | MenuId: menuId, 145 | }) 146 | } 147 | if _, err := session.Insert(roleMenu); err != nil { 148 | return err 149 | } 150 | } 151 | if len(params.DeptIds) != 0 { 152 | // 增加新的角色部门 153 | roleDept := make([]entity.AdminSysRoleDept, 0, len(params.DeptIds)) 154 | for _, deptId := range params.DeptIds { 155 | roleDept = append(roleDept, entity.AdminSysRoleDept{ 156 | RoleId: *params.Id, 157 | DeptId: deptId, 158 | }) 159 | } 160 | if _, err := session.Insert(roleDept); err != nil { 161 | return err 162 | } 163 | } 164 | if err := session.Commit(); err != nil { 165 | return err 166 | } 167 | if err := roleService.authCache.UpdateAllRole(); err != nil { 168 | return err 169 | } 170 | if err := roleService.authCache.UpdateAllPerm(); err != nil { 171 | return err 172 | } 173 | return nil 174 | } 175 | 176 | // 删除角色 177 | func (roleService *RoleService) DelRole(params *request.RoleDel) error { 178 | session := common.DB.NewSession() 179 | defer session.Close() 180 | if err := session.Begin(); err != nil { 181 | return err 182 | } 183 | // 删除角色所属菜单权限 184 | if _, err := session.Table("admin_sys_role_menu").In("role_id", params.Ids).Delete(); err != nil { 185 | return err 186 | } 187 | // 删除角色原有部门权限 188 | if _, err := session.Table("admin_sys_role_dept").In("role_id", params.Ids).Delete(); err != nil { 189 | return err 190 | } 191 | // 删除用户此角色 192 | if _, err := session.Table("admin_sys_user_role").In("role_id", params.Ids).Delete(); err != nil { 193 | return err 194 | } 195 | // 删除角色 196 | if _, err := session.Table("admin_sys_role").In("id", params.Ids).Delete(); err != nil { 197 | return err 198 | } 199 | if err := session.Commit(); err != nil { 200 | return err 201 | } 202 | if err := roleService.authCache.UpdateAllRole(); err != nil { 203 | return err 204 | } 205 | if err := roleService.authCache.UpdateAllPerm(); err != nil { 206 | return err 207 | } 208 | return nil 209 | } 210 | -------------------------------------------------------------------------------- /app/admin/services/server.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | "seed-admin/app/admin/response" 7 | "seed-admin/common" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/shirou/gopsutil/v3/cpu" 12 | "github.com/shirou/gopsutil/v3/disk" 13 | "github.com/shirou/gopsutil/v3/host" 14 | "github.com/shirou/gopsutil/v3/mem" 15 | ) 16 | 17 | type ServerService struct{} 18 | 19 | func (*ServerService) GetSystemInfo() (*response.ServerInfo, error) { 20 | cpu, err := GetCPUInfo() 21 | if err != nil { 22 | return nil, err 23 | } 24 | host, err := GetHostInfo() 25 | if err != nil { 26 | return nil, err 27 | } 28 | ram, err := GetRAMInfo() 29 | if err != nil { 30 | return nil, err 31 | } 32 | disk, err := GetDiskInfo() 33 | if err != nil { 34 | return nil, err 35 | } 36 | runtime, err := GetRuntimeInfo() 37 | if err != nil { 38 | return nil, err 39 | } 40 | res := &response.ServerInfo{ 41 | CPU: *cpu, 42 | RAM: *ram, 43 | Disk: *disk, 44 | Host: *host, 45 | Runtime: *runtime, 46 | } 47 | return res, nil 48 | } 49 | 50 | // 获取CPU信息 51 | func GetCPUInfo() (*response.CPU, error) { 52 | info, err := cpu.Info() 53 | if err != nil { 54 | common.LOG.Error("获取CPU信息出错:" + err.Error()) 55 | return nil, err 56 | } 57 | cpuPercent, err := cpu.Percent(time.Second, false) 58 | if err != nil { 59 | common.LOG.Error("获取CPU百分比出错:" + err.Error()) 60 | return nil, err 61 | } 62 | res := &response.CPU{ 63 | Name: info[0].ModelName, 64 | Cores: info[0].Cores, 65 | Percent: int(cpuPercent[0]), 66 | } 67 | return res, nil 68 | } 69 | 70 | // 获取主机信息 71 | func GetHostInfo() (*response.Host, error) { 72 | info, err := host.Info() 73 | if err != nil { 74 | common.LOG.Error("获取内存信息出错:" + err.Error()) 75 | return nil, err 76 | } 77 | res := &response.Host{ 78 | OS: info.OS, 79 | Kernel: info.KernelArch, 80 | Runtime: resolveTime(info.Uptime), 81 | } 82 | return res, nil 83 | } 84 | 85 | // 获取内存信息 86 | func GetRAMInfo() (*response.RAM, error) { 87 | info, err := mem.VirtualMemory() 88 | if err != nil { 89 | common.LOG.Error("获取内存信息出错:" + err.Error()) 90 | return nil, err 91 | } 92 | res := &response.RAM{ 93 | Total: BtoGb(info.Total), 94 | Available: BtoGb(info.Available), 95 | Used: BtoGb(info.Used), 96 | Percent: int(info.UsedPercent), 97 | } 98 | return res, nil 99 | } 100 | 101 | // 获取硬盘信息 102 | func GetDiskInfo() (*response.Disk, error) { 103 | parts, err := disk.Partitions(true) 104 | if err != nil { 105 | common.LOG.Error("获取硬盘盘符出错:" + err.Error()) 106 | return nil, err 107 | } 108 | var total uint64 109 | var free uint64 110 | var used uint64 111 | var percent float64 112 | for _, part := range parts { 113 | info, err := disk.Usage(part.Mountpoint) 114 | if err != nil { 115 | common.LOG.Error("获取硬盘信息出错:" + err.Error()) 116 | return nil, err 117 | } 118 | total += info.Total 119 | free += info.Free 120 | used += info.Used 121 | percent += info.UsedPercent 122 | } 123 | res := &response.Disk{ 124 | Total: BtoGb(total), 125 | Available: BtoGb(free), 126 | Used: BtoGb(used), 127 | Percent: int(percent), 128 | } 129 | return res, nil 130 | } 131 | 132 | // 获取运行信息 133 | func GetRuntimeInfo() (*response.Runtime, error) { 134 | res := &response.Runtime{ 135 | Version: runtime.Version(), 136 | Language: "Go", 137 | StartTime: common.StartTime.Format("2006-01-02 15:04:05"), 138 | Runtime: resolveTime(uint64(time.Since(common.StartTime).Seconds())), 139 | } 140 | return res, nil 141 | } 142 | 143 | // B转Gb 144 | func BtoGb(b uint64) float64 { 145 | str := fmt.Sprintf("%.2f", float64(b)/1024/1024/1024) 146 | res, err := strconv.ParseFloat(str, 64) 147 | if err != nil { 148 | common.LOG.Error("B转GB出错:" + err.Error()) 149 | return 0 150 | } 151 | return res 152 | } 153 | 154 | // 秒转换时间 155 | func resolveTime(seconds uint64) string { 156 | days := seconds / (24 * 3600) 157 | hours := seconds % (24 * 3600) / 3600 158 | minutes := seconds % 3600 / 60 159 | return fmt.Sprintf("%v天%v小时%v分钟", days, hours, minutes) 160 | } 161 | -------------------------------------------------------------------------------- /app/admin/services/user.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "seed-admin/app/admin/entity" 7 | "seed-admin/app/admin/request" 8 | "seed-admin/app/admin/response" 9 | "seed-admin/common" 10 | "seed-admin/utils" 11 | "strconv" 12 | "time" 13 | 14 | "github.com/golang-jwt/jwt" 15 | ) 16 | 17 | type UserService struct { 18 | authCache utils.AuthCache 19 | menuService MenuService 20 | deptService DeptService 21 | } 22 | 23 | // 登录 24 | func (*UserService) Login(params *request.Login) (*entity.AdminSysUser, []int, error) { 25 | // MD5加盐 盐值一旦设定并在生产模式使用后切勿更改 随意更改后会造成之前盐值不同的用户无法登录 26 | params.Password = utils.Md5Salt(params.Password, common.CONFIG.String("app.md5Salt")) 27 | // 使用帐密做查询 28 | user := &entity.AdminSysUser{ 29 | Username: params.Username, 30 | Password: params.Password, 31 | } 32 | if ok, err := common.DB.Get(user); ok { 33 | if user.Status == 1 { 34 | return nil, nil, errors.New("该用户已被禁用") 35 | } 36 | // 查询用户拥有的角色ID 37 | roleIds := make([]int, 0) 38 | if err = common.DB.Table("admin_sys_user_role"). 39 | Where("user_id = ?", user.Id).Cols("role_id"). 40 | Find(&roleIds); err != nil { 41 | return nil, nil, err 42 | } 43 | return user, roleIds, nil 44 | } else { 45 | if err != nil { 46 | return nil, nil, err 47 | } 48 | return nil, nil, fmt.Errorf("帐号或密码错误") 49 | } 50 | } 51 | 52 | // 生成一个token 53 | func (*UserService) GetToken(user *entity.AdminSysUser) (string, error) { 54 | j := utils.NewJwt() 55 | token, err := j.CreateToken(utils.CustomerClaims{ 56 | UserId: user.Id, 57 | StandardClaims: &jwt.StandardClaims{ 58 | Issuer: common.CONFIG.String("jwt.Issuer"), 59 | IssuedAt: time.Now().Unix(), 60 | NotBefore: time.Now().Unix(), 61 | ExpiresAt: time.Now().Unix() + common.CONFIG.Int64("jwt.ExpireSeconds"), 62 | }, 63 | }) 64 | return token, err 65 | } 66 | 67 | // 获取个人用户信息 68 | func (userService *UserService) GetPerson(userId int) (*response.UserProfile, error) { 69 | user := new(response.UserProfile) 70 | if ok, err := common.DB. 71 | Table("admin_sys_user"). 72 | Alias("u"). 73 | Join("INNER", []string{"admin_sys_dept", "d"}, "u.dept_id = d.id"). 74 | Where("u.status = ? AND u.id = ?", 0, userId). 75 | Get(user); ok { 76 | // 查询用户拥有的角色ID 77 | roleMap := make([]map[string]any, 0) 78 | if err = common.DB.Table("admin_sys_user_role").Alias("ur"). 79 | Join("INNER", []string{"admin_sys_role", "r"}, "ur.role_id = r.id"). 80 | Where("user_id = ?", user.Id).Cols("r.id", "r.label", "r.name"). 81 | Find(&roleMap); err != nil { 82 | return nil, err 83 | } 84 | if len(roleMap) <= 0 { 85 | return nil, errors.New("该用户没有权限") 86 | } 87 | roleIds := make([]int, 0, len(roleMap)) 88 | roleLabels := make([]string, 0, len(roleMap)) 89 | roleNames := make([]string, 0, len(roleMap)) 90 | for _, item := range roleMap { 91 | roleIds = append(roleIds, int(item["id"].(int32))) 92 | roleLabels = append(roleLabels, item["label"].(string)) 93 | roleNames = append(roleNames, item["name"].(string)) 94 | } 95 | userService.authCache.SetRoleIds(userId, roleIds) 96 | userService.authCache.SetRoleLabels(userId, roleLabels) 97 | user.Roles = roleLabels 98 | user.RoleNames = roleNames 99 | return user, nil 100 | } else { 101 | if err != nil { 102 | return nil, err 103 | } 104 | return nil, fmt.Errorf("获取用户信息失败/用户被禁止使用") 105 | } 106 | } 107 | 108 | // 获取用户信息 109 | func (*UserService) GetInfo(userId int) (*response.UserInfo, error) { 110 | user := &entity.AdminSysUser{ 111 | Id: userId, 112 | } 113 | if ok, err := common.DB.Omit("password").Get(user); ok { 114 | // 查询用户拥有的角色ID 115 | roleIds := make([]int, 0) 116 | if err = common.DB.Table("admin_sys_user_role"). 117 | Where("user_id = ?", user.Id).Cols("role_id"). 118 | Find(&roleIds); err != nil { 119 | return nil, err 120 | } 121 | res := &response.UserInfo{ 122 | Id: user.Id, 123 | DeptId: user.DeptId, 124 | Username: user.Username, 125 | NickName: user.NickName, 126 | Phone: user.Phone, 127 | Email: user.Email, 128 | Avatar: user.Avatar, 129 | Status: user.Status, 130 | RoleIds: roleIds, 131 | } 132 | return res, nil 133 | } else { 134 | if err != nil { 135 | return nil, err 136 | } 137 | return nil, fmt.Errorf("获取用户信息失败") 138 | } 139 | } 140 | 141 | // 更新头像 142 | func (*UserService) UpdateAvatar(params *request.UserAvatarUpdate, userId int) error { 143 | user := &entity.AdminSysUser{ 144 | Avatar: params.Url, 145 | } 146 | if _, err := common.DB.Where("id = ?", userId).Cols("avatar").Update(user); err != nil { 147 | return err 148 | } 149 | return nil 150 | } 151 | 152 | // 获取全部用户 153 | func (userService *UserService) GetAllUser(params *request.UserList, userId int) ([]response.UserList, int64, error) { 154 | user := new(entity.AdminSysUser) 155 | users := make([]response.UserList, 0) 156 | // 获取当前操作用户的部门权限ID组 157 | userDeptIds, err := userService.deptService.GetIds(userId) 158 | if err != nil { 159 | return nil, 0, err 160 | } 161 | // 处理用户部门权限条件 162 | userDeptInStr := "" 163 | userDeptCountInStr := "" 164 | 165 | if len(userDeptIds) != 0 { 166 | userDeptIdsIn := utils.SliceToInStr(userDeptIds) 167 | userDeptInStr = fmt.Sprintf("AND user.dept_id in(%v)", userDeptIdsIn) 168 | userDeptCountInStr = fmt.Sprintf("dept_id in(%v)", userDeptIdsIn) 169 | } 170 | deptInStr := "" 171 | deptCountInStr := "" 172 | if len(params.DeptId) != 0 { 173 | deptIdsIn := utils.SliceToInStr(params.DeptId) 174 | deptInStr = fmt.Sprintf("AND user.dept_id in(%v)", deptIdsIn) 175 | deptCountInStr = fmt.Sprintf("dept_id in(%v)", deptIdsIn) 176 | } 177 | status := "" 178 | if params.Status != nil { 179 | status = strconv.Itoa(*params.Status) 180 | } 181 | count, err := common.DB. 182 | Where("username LIKE ?", "%"+params.Username+"%"). 183 | Where("nick_name LIKE ?", "%"+params.NickName+"%"). 184 | Where("phone LIKE ?", "%"+params.Phone+"%"). 185 | Where("status LIKE ?", "%"+status+"%"). 186 | Where(deptCountInStr). 187 | Where(userDeptCountInStr). 188 | Count(user) 189 | if err != nil { 190 | return nil, 0, err 191 | } 192 | sql := fmt.Sprintf(` 193 | SELECT 194 | user.*, 195 | GROUP_CONCAT(role.id) AS role_ids, 196 | dept.name AS dept_name 197 | FROM 198 | admin_sys_user user 199 | LEFT JOIN admin_sys_user_role user_role ON user.id = user_role.user_id 200 | LEFT JOIN admin_sys_role role ON user_role.role_id = role.id 201 | LEFT JOIN admin_sys_dept dept ON user.dept_id = dept.id 202 | WHERE 203 | user.username LIKE '%s' AND 204 | user.nick_name LIKE '%s' AND 205 | user.phone LIKE '%s' AND 206 | user.status LIKE '%s' 207 | %v %v 208 | GROUP BY user.id 209 | LIMIT %v,%v 210 | `, "%"+params.Username+"%", "%"+params.NickName+"%", "%"+params.Phone+"%", "%"+status+"%", userDeptInStr, deptInStr, (*params.PageNum-1)*(*params.PageSize), *params.PageSize) 211 | if err := common.DB.SQL(sql).Find(&users); err != nil { 212 | return nil, 0, err 213 | } 214 | return users, count, nil 215 | } 216 | 217 | // 更新用户角色 218 | func (userService *UserService) UpdateUserRole(params *request.UserRoleUpdate) error { 219 | session := common.DB.NewSession() 220 | defer session.Close() 221 | if err := session.Begin(); err != nil { 222 | return err 223 | } 224 | // 删除用户原有角色 225 | if _, err := session.Table("admin_sys_user_role").Where("user_id = ?", params.Id).Delete(); err != nil { 226 | return err 227 | } 228 | if len(params.RoleIds) != 0 { 229 | // 给用户增加新的角色 230 | userRole := make([]entity.AdminSysUserRole, 0, len(params.RoleIds)) 231 | for _, roleId := range params.RoleIds { 232 | userRole = append(userRole, entity.AdminSysUserRole{ 233 | UserId: *params.Id, 234 | RoleId: roleId, 235 | }) 236 | } 237 | if _, err := session.Insert(userRole); err != nil { 238 | return err 239 | } 240 | } 241 | if err := session.Commit(); err != nil { 242 | return err 243 | } 244 | // 处理缓存权限 245 | role := make([]entity.AdminSysRole, 0, len(params.RoleIds)) 246 | if err := common.DB.In("id", params.RoleIds).Find(&role); err != nil { 247 | return err 248 | } 249 | userService.authCache.SetRoleIds(*params.Id, params.RoleIds) 250 | roleLabels := make([]string, 0, len(role)) 251 | for _, item := range role { 252 | roleLabels = append(roleLabels, item.Label) 253 | } 254 | userService.authCache.SetRoleLabels(*params.Id, roleLabels) 255 | perms, err := userService.menuService.GetPerms(*params.Id) 256 | if err != nil { 257 | return err 258 | } 259 | userService.authCache.SetMenuPerms(*params.Id, perms) 260 | return nil 261 | } 262 | 263 | // 新增用户 264 | func (*UserService) AddUser(params *request.User) error { 265 | session := common.DB.NewSession() 266 | defer session.Close() 267 | if err := session.Begin(); err != nil { 268 | return err 269 | } 270 | user := &entity.AdminSysUser{ 271 | Username: params.Username, 272 | NickName: *params.NickName, 273 | Password: utils.Md5Salt(params.Password, common.CONFIG.String("app.md5Salt")), 274 | DeptId: params.DeptId, 275 | Phone: params.Phone, 276 | Email: params.Email, 277 | Avatar: params.Avatar, 278 | Status: params.Status, 279 | } 280 | if _, err := session.Insert(user); err != nil { 281 | return err 282 | } 283 | if len(params.RoleIds) == 0 { 284 | return session.Commit() 285 | } 286 | // 增加新的角色权限 287 | userRole := make([]entity.AdminSysUserRole, 0, len(params.RoleIds)) 288 | for _, roleId := range params.RoleIds { 289 | userRole = append(userRole, entity.AdminSysUserRole{ 290 | UserId: user.Id, 291 | RoleId: roleId, 292 | }) 293 | } 294 | if _, err := session.Insert(userRole); err != nil { 295 | return err 296 | } 297 | return session.Commit() 298 | } 299 | 300 | // 更新用户基础信息 301 | func (*UserService) UpdateBaseInfo(params *request.UserBaseInfoUpdate, userId int) error { 302 | // 更新用户表 303 | user := &entity.AdminSysUser{ 304 | NickName: *params.NickName, 305 | Phone: params.Phone, 306 | Email: params.Email, 307 | } 308 | if _, err := common.DB.Where("id = ?", userId).Cols("nick_name", "phone", "email").Update(user); err != nil { 309 | return err 310 | } 311 | return nil 312 | } 313 | 314 | // 更新用户密码 315 | func (*UserService) UpdatePassword(params *request.UserPasswordUpdate, userId int) error { 316 | user := new(entity.AdminSysUser) 317 | if ok, err := common.DB.Where("id = ?", userId).Cols("password").Get(user); !ok { 318 | if err != nil { 319 | return err 320 | } 321 | return errors.New("获取旧密码失败") 322 | } 323 | if user.Password != utils.Md5Salt(params.OldPassword, common.CONFIG.String("app.md5Salt")) { 324 | return errors.New("旧密码错误,请检查旧密码是否输入正确") 325 | } 326 | user.Password = utils.Md5Salt(params.NewPassword, common.CONFIG.String("app.md5Salt")) 327 | // 更新用户表 328 | if _, err := common.DB.Where("id = ?", userId).Cols("password").Update(user); err != nil { 329 | return err 330 | } 331 | return nil 332 | } 333 | 334 | // 更新用户 335 | func (userService *UserService) UpdateUser(params *request.UserUpdate) error { 336 | session := common.DB.NewSession() 337 | defer session.Close() 338 | if err := session.Begin(); err != nil { 339 | return err 340 | } 341 | // 更新用户表 342 | user := &entity.AdminSysUser{ 343 | Username: params.Username, 344 | NickName: *params.NickName, 345 | Password: utils.Md5Salt(params.Password, common.CONFIG.String("app.md5Salt")), 346 | DeptId: params.DeptId, 347 | Phone: params.Phone, 348 | Email: params.Email, 349 | Avatar: params.Avatar, 350 | Status: params.Status, 351 | } 352 | if _, err := session.Where("id = ?", params.Id).AllCols().Update(user); err != nil { 353 | return err 354 | } 355 | userRoleUpdate := &request.UserRoleUpdate{ 356 | Id: params.Id, 357 | RoleIds: params.RoleIds, 358 | } 359 | if err := userService.UpdateUserRole(userRoleUpdate); err != nil { 360 | return err 361 | } 362 | if params.Status != 0 { 363 | userService.authCache.Del([]int{*params.Id}) 364 | } 365 | return session.Commit() 366 | } 367 | 368 | // 删除用户 369 | func (userService *UserService) DelUser(params *request.UserDel) error { 370 | session := common.DB.NewSession() 371 | defer session.Close() 372 | if err := session.Begin(); err != nil { 373 | return err 374 | } 375 | // 删除用户所有角色 376 | if _, err := session.Table("admin_sys_user_role").In("user_id", params.Ids).Delete(); err != nil { 377 | return err 378 | } 379 | // 删除用户 380 | if _, err := session.Table("admin_sys_user").In("id", params.Ids).Delete(); err != nil { 381 | return err 382 | } 383 | if err := session.Commit(); err != nil { 384 | return err 385 | } 386 | if err := userService.authCache.Del(params.Ids); err != nil { 387 | return err 388 | } 389 | return nil 390 | } 391 | 392 | // 移动部门 393 | func (*UserService) MoveDept(params *request.UserMove) error { 394 | user := new(entity.AdminSysUser) 395 | user.DeptId = *params.DeptId 396 | if _, err := common.DB.In("id", params.Ids).Update(user); err != nil { 397 | return err 398 | } 399 | return nil 400 | } 401 | -------------------------------------------------------------------------------- /app/api/controllers/user.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "seed-admin/common" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | type User struct{} 10 | 11 | func (*User) Demo(ctx *gin.Context) { 12 | common.OkMsg(ctx, "我只是个demo啊") 13 | } 14 | -------------------------------------------------------------------------------- /app/api/routers/index.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | import ( 4 | "seed-admin/app/api/controllers" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | type Api struct { 10 | router *gin.RouterGroup 11 | User *controllers.User 12 | } 13 | 14 | func New(router *gin.RouterGroup) { 15 | controllers := new(Api) 16 | controllers.router = router.Group("api") 17 | controllers.useUser() 18 | } 19 | -------------------------------------------------------------------------------- /app/api/routers/user.go: -------------------------------------------------------------------------------- 1 | package routers 2 | 3 | func (api *Api) useUser() { 4 | router := api.router.Group("user") 5 | { 6 | router.GET("/demo", api.User.Demo) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/index.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "seed-admin/app/admin/entity" 5 | admin "seed-admin/app/admin/routers" 6 | api "seed-admin/app/api/routers" 7 | "seed-admin/common" 8 | 9 | "github.com/gin-gonic/gin" 10 | ) 11 | 12 | func Load(r *gin.Engine) { 13 | admin.New(&r.RouterGroup) 14 | api.New(&r.RouterGroup) 15 | dataTableSync() 16 | } 17 | 18 | // 需要同步到数据库的实体 19 | // 只允许增量同步,只允许在开发模式模式下使用 20 | // 如有需要自行添加所需同步的表... 21 | func dataTableSync() { 22 | if gin.Mode() == gin.DebugMode { 23 | err := common.DB.Sync( 24 | new(entity.AdminSysUser), 25 | new(entity.AdminSysUserRole), 26 | new(entity.AdminSysRole), 27 | new(entity.AdminSysMenu), 28 | new(entity.AdminSysRoleMenu), 29 | new(entity.AdminSysDept), 30 | new(entity.AdminSysRoleDept), 31 | new(entity.AdminSysLog), 32 | new(entity.AdminSysDictType), 33 | new(entity.AdminSysDictData), 34 | new(entity.AdminUploads), 35 | new(entity.AdminUploadsType), 36 | ) 37 | if err != nil { 38 | panic(err.Error()) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /common/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | import ( 4 | "errors" 5 | "seed-admin/common" 6 | "seed-admin/utils" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | const ( 12 | // OR逻辑常量 13 | OR = "or" 14 | // AND逻辑常量 15 | AND = "and" 16 | ) 17 | 18 | // 验证角色 19 | // roleLabels:要验证的角色label 20 | // logical:auth.OR || auth.AND 默认OR 21 | func Roles(roleLabels []string, logical ...string) gin.HandlerFunc { 22 | return func(ctx *gin.Context) { 23 | userId := utils.GetUserId(ctx) 24 | AuthCache := new(utils.AuthCache) 25 | labelsCache, err := AuthCache.GetRoleLabels(userId) 26 | if err != nil { 27 | common.FailMsg(ctx, err.Error()) 28 | ctx.Abort() 29 | return 30 | } 31 | if len(logical) > 0 { 32 | switch logical[0] { 33 | case "or": 34 | if err := orVerify(labelsCache, roleLabels); err != nil { 35 | common.FailMsg(ctx, "未满足接口所需角色") 36 | ctx.Abort() 37 | return 38 | } 39 | case "and": 40 | if err := andVerify(labelsCache, roleLabels); err != nil { 41 | common.FailMsg(ctx, "未满足接口所需角色") 42 | ctx.Abort() 43 | return 44 | } 45 | default: 46 | common.FailMsg(ctx, "角色验证参数错误") 47 | ctx.Abort() 48 | return 49 | } 50 | } else { 51 | // 默认的验证条件 需要默认OR可以把函数改成orVerify 52 | if err := andVerify(labelsCache, roleLabels); err != nil { 53 | common.FailMsg(ctx, "未满足接口所需角色") 54 | ctx.Abort() 55 | return 56 | } 57 | } 58 | 59 | ctx.Next() 60 | } 61 | } 62 | 63 | // 验证权限 64 | // menuPerms:要验证的菜单权限 65 | // logical:auth.OR || auth.AND (默认AND) 66 | func Perms(menuPerms []string, logical ...string) gin.HandlerFunc { 67 | return func(ctx *gin.Context) { 68 | userId := utils.GetUserId(ctx) 69 | AuthCache := new(utils.AuthCache) 70 | permsCache, err := AuthCache.GetMenuPerms(userId) 71 | if err != nil { 72 | common.FailMsg(ctx, err.Error()) 73 | ctx.Abort() 74 | return 75 | } 76 | if len(logical) > 0 { 77 | switch logical[0] { 78 | case "or": 79 | if err := orVerify(permsCache, menuPerms); err != nil { 80 | common.FailMsg(ctx, "权限验证失败") 81 | ctx.Abort() 82 | return 83 | } 84 | case "and": 85 | if err := andVerify(permsCache, menuPerms); err != nil { 86 | common.FailMsg(ctx, "权限验证失败") 87 | ctx.Abort() 88 | return 89 | } 90 | default: 91 | common.FailMsg(ctx, "权限验证参数错误") 92 | ctx.Abort() 93 | return 94 | } 95 | 96 | } else { 97 | // 默认的验证条件 需要默认OR可以把函数改成orVerify 98 | if err := andVerify(permsCache, menuPerms); err != nil { 99 | common.FailMsg(ctx, "权限验证失败") 100 | ctx.Abort() 101 | return 102 | } 103 | } 104 | ctx.Next() 105 | } 106 | } 107 | 108 | // OR验证 109 | func orVerify(cacheData []string, verifyData []string) error { 110 | is := false 111 | for _, perm := range verifyData { 112 | if utils.SliceIncludes(cacheData, perm) { 113 | is = true 114 | } 115 | } 116 | if !is { 117 | return errors.New("验证失败") 118 | } 119 | return nil 120 | } 121 | 122 | // AND验证 123 | func andVerify(cacheData []string, verifyData []string) error { 124 | count := 0 125 | for _, perm := range cacheData { 126 | if utils.SliceIncludes(verifyData, perm) { 127 | count++ 128 | } 129 | } 130 | if count != len(verifyData) { 131 | return errors.New("验证失败") 132 | } 133 | return nil 134 | } 135 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/go-redis/redis" 7 | "github.com/gookit/config/v2" 8 | "go.uber.org/zap" 9 | "xorm.io/xorm" 10 | ) 11 | 12 | var ( 13 | DB *xorm.Engine 14 | CONFIG *config.Config 15 | LOG *zap.Logger 16 | Redis *redis.Client 17 | StartTime time.Time 18 | ) 19 | -------------------------------------------------------------------------------- /common/middlewares/jwt.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "seed-admin/common" 5 | "seed-admin/utils" 6 | "strings" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | // 鉴权中间件 12 | func JwtAuth() gin.HandlerFunc { 13 | return func(ctx *gin.Context) { 14 | tokenString := ctx.Request.Header.Get("Authorization") 15 | if tokenString == "" || tokenString == "Bearer " { 16 | common.Message(ctx, common.AUTHORIZATION_FAIL, "未携带token,认证失败") 17 | ctx.Abort() 18 | return 19 | } 20 | jwt := utils.NewJwt() 21 | tokenString = strings.TrimPrefix(tokenString, "Bearer ") 22 | claims, err := jwt.ParseToken(tokenString) 23 | if err != nil { 24 | common.Message(ctx, common.AUTHORIZATION_FAIL, "非法token或token已经过期,请重新登录") 25 | ctx.Abort() 26 | return 27 | } 28 | ctx.Set("claims", claims) 29 | ctx.Next() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /common/middlewares/record.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | "seed-admin/app/admin/entity" 8 | "seed-admin/app/admin/services" 9 | "seed-admin/common" 10 | "seed-admin/utils" 11 | "strconv" 12 | "time" 13 | 14 | "github.com/gin-gonic/gin" 15 | ) 16 | 17 | var logService services.LogService 18 | 19 | // 操作记录器 20 | func OperationRecorder() gin.HandlerFunc { 21 | return func(ctx *gin.Context) { 22 | body := make([]byte, 0) 23 | // 只考虑常规的浏览器取参方式 get携带body post携带url等不再进行取参处理 有需要自己可以添加 24 | // 本项目不遵守Restful规范 delete,put等请求方法如有需要请自行添加case分支 这里只处理GETQuery和POSTBody的请求方式 25 | switch ctx.Request.Method { 26 | case http.MethodGet: 27 | body = []byte(ctx.Request.URL.Query().Encode()) 28 | case http.MethodPost: 29 | var err error 30 | body, err = io.ReadAll(ctx.Request.Body) 31 | if err != nil { 32 | common.LOG.Error(err.Error()) 33 | } else { 34 | ctx.Request.Body = io.NopCloser(bytes.NewBuffer(body)) 35 | } 36 | } 37 | userId := 0 38 | if claims, ok := ctx.Get("claims"); ok { 39 | userId = claims.(*utils.CustomerClaims).UserId 40 | } 41 | // 5分钟内重复操作直接next不再重复记录 42 | if userId != 0 { 43 | ok, err := common.Redis.SIsMember("record"+strconv.Itoa(userId), ctx.Request.URL.Path).Result() 44 | if err != nil { 45 | common.LOG.Error(err.Error()) 46 | } 47 | if ok { 48 | ctx.Next() 49 | return 50 | } 51 | } 52 | log := &entity.AdminSysLog{ 53 | UserId: userId, 54 | Method: ctx.Request.Method, 55 | Action: ctx.Request.URL.Path, 56 | Ip: ctx.ClientIP(), 57 | Params: string(body), 58 | } 59 | writer := &responseBodyWriter{body: bytes.NewBufferString(""), ResponseWriter: ctx.Writer} 60 | ctx.Writer = writer 61 | ctx.Next() 62 | log.StatusCode = ctx.Writer.Status() 63 | log.Results = writer.body.String() 64 | if err := logService.AddLog(log); err != nil { 65 | common.LOG.Error(err.Error()) 66 | } 67 | // 把操作用redis记录下来 68 | if userId != 0 { 69 | common.Redis.SAdd("record"+strconv.Itoa(userId), ctx.Request.URL.Path).Result() 70 | common.Redis.Expire("record"+strconv.Itoa(userId), time.Second*300).Result() 71 | } 72 | } 73 | } 74 | 75 | type responseBodyWriter struct { 76 | gin.ResponseWriter 77 | body *bytes.Buffer 78 | } 79 | 80 | func (r responseBodyWriter) Write(b []byte) (int, error) { 81 | r.body.Write(b) 82 | return r.ResponseWriter.Write(b) 83 | } 84 | -------------------------------------------------------------------------------- /common/response.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | const ( 10 | SUCCESS = 1000 // 请求成功 进入前端处理逻辑 11 | FAIL = 1001 // 请求错误 前端会自动抛出异常 12 | REFRESH_CAPTCHA = 1002 // 需要前端手动判断code == 1002处理的失败 13 | AUTHORIZATION_FAIL = 1004 // 鉴权失败 前端会自动抛出异常并退出登录 14 | ) 15 | 16 | // 自定义通用消息 17 | func Message(ctx *gin.Context, status int, message string, data ...any) { 18 | var obj gin.H 19 | if len(data) == 0 { 20 | obj = gin.H{ 21 | "code": status, 22 | "message": message, 23 | } 24 | } else { 25 | obj = gin.H{ 26 | "code": status, 27 | "message": message, 28 | "data": data[0], 29 | } 30 | } 31 | ctx.JSON(http.StatusOK, obj) 32 | } 33 | 34 | // 默认的成功响应 35 | func Ok(ctx *gin.Context) { 36 | obj := gin.H{ 37 | "code": SUCCESS, 38 | "message": "操作成功", 39 | } 40 | ctx.JSON(http.StatusOK, obj) 41 | } 42 | 43 | // 携带消息的成功响应 44 | func OkMsg(ctx *gin.Context, message string) { 45 | obj := gin.H{ 46 | "code": SUCCESS, 47 | "message": message, 48 | } 49 | ctx.JSON(http.StatusOK, obj) 50 | } 51 | 52 | // 携带数据的成功响应 53 | func OkData(ctx *gin.Context, data any) { 54 | obj := gin.H{ 55 | "code": SUCCESS, 56 | "message": "操作成功", 57 | "data": data, 58 | } 59 | ctx.JSON(http.StatusOK, obj) 60 | } 61 | 62 | // 携带消息和数据的成功响应 63 | func OkMsgData(ctx *gin.Context, message string, data any) { 64 | obj := gin.H{ 65 | "code": SUCCESS, 66 | "message": message, 67 | "data": data, 68 | } 69 | ctx.JSON(http.StatusOK, obj) 70 | } 71 | 72 | // 默认的失败响应 73 | func Fail(ctx *gin.Context) { 74 | obj := gin.H{ 75 | "code": FAIL, 76 | "message": "操作失败", 77 | } 78 | ctx.JSON(http.StatusOK, obj) 79 | } 80 | 81 | // 携带消息的失败响应 82 | func FailMsg(ctx *gin.Context, message string) { 83 | obj := gin.H{ 84 | "code": FAIL, 85 | "message": message, 86 | } 87 | ctx.JSON(http.StatusOK, obj) 88 | } 89 | 90 | // 携带数据的失败响应 91 | func FailData(ctx *gin.Context, data any) { 92 | obj := gin.H{ 93 | "code": FAIL, 94 | "message": "操作失败", 95 | "data": data, 96 | } 97 | ctx.JSON(http.StatusOK, obj) 98 | } 99 | 100 | // 携带消息和数据的失败响应 101 | func FailMsgData(ctx *gin.Context, message string, data any) { 102 | obj := gin.H{ 103 | "code": FAIL, 104 | "message": message, 105 | "data": data, 106 | } 107 | ctx.JSON(http.StatusOK, obj) 108 | } 109 | -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | [app] 2 | mode = "debug" #运行环境 开发-debug 线上-release 测试-test 3 | name = "Seed-Admin" #应用名称 4 | port = 8080 #运行端口 5 | staticPath = "/wwwroot" #静态目录 6 | #盐值在生产模式使用后切勿随意更改 随意更改后会造成前后盐值不同算出的MD5值也不同导致用户无法登录 7 | md5Salt = "seed" #MD5盐值 8 | 9 | [mysql] 10 | server = "127.0.0.1" #数据库地址 11 | port = 3306 #端口 12 | user = "root" #帐号 13 | password = "123123asd" #密码 14 | database = "seed-admin" #库 15 | config = "" #杂项配置 16 | maxIdleConns = 2 #连接池的空闲数大小 17 | maxOpenConns = 0 #最大打开连接数 0为无限制 18 | 19 | [redis] 20 | server = "127.0.0.1" #地址 21 | port = 6379 #端口 22 | password = "123123" #密码 23 | database = 0 #库 24 | 25 | [jwt] 26 | signingKey = "seed" 27 | Issuer = "seed-admin" 28 | ExpireSeconds = 604800 #token过期时间(second) 29 | 30 | [captcha] 31 | len = 4 #验证码位数 修改后记得修改login参数的tag位数验证 32 | height = 80 #图片高度 33 | width = 200 #图片宽度 34 | 35 | [upload] 36 | fileSize = 5 #上传文件的大小(MB) 37 | path = "/uploads" 38 | 39 | [log] 40 | level = "info" #日志等级 debug || info || warn || error || dpanic || panic || fatal 41 | showLine = true #是否显示行 42 | outType = "all" #输出位置 console || file || all (console:输出到控制台 file:输出到日志文件 all:我全都要.jpg) 43 | console_format = "console" #输出到控制台时的输出格式 json || console 44 | file_format = "json" #输出到文件时的输出格式 json || console 45 | director = "log/runtime" #日志输出目录 46 | maxSize = 1 #切割大小(单位:mb) 47 | maxBackups = 10 #保留旧日志文件的最大数目(个) 48 | maxAge = 7 #保留旧日志文件的最大天数 49 | compress = false #是否压缩 50 | 51 | [gin_log] 52 | outType = "all" #输出位置 console || file || all (console:输出到控制台 file:输出到日志文件 all:我全都要.jpg) 53 | director = "log/http" #日志输出目录 54 | maxSize = 1 #切割大小(单位:mb) 55 | maxBackups = 10 #保留旧日志文件的最大数目(个) 56 | maxAge = 7 #保留旧日志文件的最大天数 57 | compress = false #是否压缩 58 | 59 | [xorm_log] 60 | level = "warn" #日志等级 debug || info || warn || error 61 | outType = "console" #输出位置 console || file (console:输出到控制台 file:输出到日志文件) 62 | showSql = true #在日志记录器上显示SQL语句(debug级别大于info时生效) 63 | director = "log/db" #日志输出目录 64 | maxSize = 1 #切割大小(单位:mb) 65 | maxBackups = 10 #保留旧日志文件的最大数目(个) 66 | maxAge = 7 #保留旧日志文件的最大天数 67 | compress = false #是否压缩 68 | -------------------------------------------------------------------------------- /core/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "flag" 5 | "sync" 6 | 7 | "github.com/fsnotify/fsnotify" 8 | "github.com/gookit/config/v2" 9 | "github.com/gookit/config/v2/toml" 10 | "github.com/gookit/goutil/cliutil" 11 | ) 12 | 13 | // 添加配置文件 14 | func Add(path ...string) *config.Config { 15 | filePath := "" 16 | // 判断是否使用函数入参设置配置文件 17 | if len(path) == 0 { 18 | // 判断启动时是否使用-c设置配置文件 19 | flag.StringVar(&filePath, "c", "", "您的配置文件.") 20 | flag.Parse() 21 | if filePath == "" { 22 | // 没有的话使用默认 23 | filePath = "config.toml" 24 | } 25 | } else { 26 | filePath = path[0] 27 | } 28 | // 创建新的配置实例 29 | c := config.NewWithOptions("appConfig", func(opts *config.Options) { 30 | opts.ParseEnv = true 31 | opts.HookFunc = hookFunc 32 | }) 33 | // 加载驱动 34 | c.AddDriver(toml.Driver) 35 | // 加载配置文件 36 | if err := c.LoadFiles(filePath); err != nil { 37 | panic(err.Error()) 38 | } 39 | // 监听配置文件热修改 40 | watchConfigFiles(c) 41 | return c 42 | } 43 | 44 | // 监听配置文件热修改 45 | func watchConfigFiles(cfg *config.Config) { 46 | // 开一个新线程防止主线程被卡住 47 | readyTask := new(sync.WaitGroup) 48 | readyTask.Add(1) 49 | go func() { 50 | watcher, err := fsnotify.NewWatcher() 51 | if err != nil { 52 | cliutil.Errorln(err.Error()) 53 | return 54 | } 55 | defer watcher.Close() 56 | // 获取加载的配置文件 57 | files := cfg.LoadedFiles() 58 | if len(files) == 0 { 59 | cliutil.Errorln("未读取到配置文件") 60 | return 61 | } 62 | // 处理出错或通道关闭时的退出问题 63 | eventsTask := new(sync.WaitGroup) 64 | eventsTask.Add(1) 65 | go func() { 66 | for { 67 | select { 68 | case event, ok := <-watcher.Events: 69 | if !ok { 70 | eventsTask.Done() 71 | return 72 | } 73 | // 只有写入时才重新创建数据 74 | switch event.Op.String() { 75 | case "WRITE": 76 | // 重载数据 77 | if err := cfg.ReloadFiles(); err != nil { 78 | eventsTask.Done() 79 | cliutil.Errorf("重载%s数据出错,err:%s\n", event.Name, err.Error()) 80 | return 81 | } 82 | cliutil.Infof("监听到%s变动\n", event.Name) 83 | case "REMOVE": 84 | eventsTask.Done() 85 | cliutil.Errorf("重载%s数据出错,err:文件被删除,请不要删除配置文件\n", event.Name) 86 | return 87 | default: 88 | cliutil.Infof("监听到%s变动 Op->%s\n", event.Name, event.Op.String()) 89 | } 90 | case err, ok := <-watcher.Errors: 91 | if ok { 92 | cliutil.Errorln(err.Error()) 93 | } 94 | if err != nil { 95 | cliutil.Errorln(err.Error()) 96 | } 97 | eventsTask.Done() 98 | return 99 | } 100 | } 101 | }() 102 | // 加载文件的监听 103 | for _, path := range files { 104 | if err := watcher.Add(path); err != nil { 105 | cliutil.Errorln(err.Error()) 106 | } 107 | } 108 | // 加载文件监听成功后释放创建监听的线程 109 | readyTask.Done() 110 | // 等待事件释放 111 | eventsTask.Wait() 112 | }() 113 | // 等待监听成功 114 | readyTask.Wait() 115 | } 116 | 117 | // 监听配置修改钩子 118 | func hookFunc(event string, c *config.Config) { 119 | // if event == "set.value" || event == "set.data" { 120 | // buf := new(buffer.Buffer) 121 | // // 第二个参数是导出格式,有config.JSON | config.Toml | config.Yaml等等等 根据你到导出的格式选用 122 | // _, err := c.DumpTo(buf, config.Toml) 123 | // if err != nil { 124 | // common.LOG.Error(err.Error()) 125 | // return 126 | // } 127 | // if err = ioutil.WriteFile("需要导出的文件地址", buf.Bytes(), 0755); err != nil { 128 | // common.LOG.Error(err.Error()) 129 | // return 130 | // } 131 | // } 132 | } 133 | -------------------------------------------------------------------------------- /core/index.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | routers "seed-admin/app" 7 | common "seed-admin/common" 8 | config "seed-admin/core/config" 9 | middlewares "seed-admin/core/middlewares" 10 | redis "seed-admin/core/redis" 11 | xorm "seed-admin/core/xorm" 12 | log "seed-admin/core/zap" 13 | "time" 14 | 15 | "github.com/gin-gonic/gin" 16 | ) 17 | 18 | // 初始化 19 | func Run() { 20 | common.CONFIG = config.Add() 21 | common.LOG = log.AddZap() 22 | common.DB = xorm.AddXorm() 23 | common.Redis = redis.AddGoRedis() 24 | // 应用启动时间 25 | common.StartTime = time.Now() 26 | // 运行环境 27 | gin.SetMode(common.CONFIG.String("app.mode")) 28 | app := gin.New() 29 | // 加载全局中间件 30 | middlewares.Load(app) 31 | // 加载路由 32 | routers.Load(app) 33 | // 静态目录 34 | app.StaticFS(common.CONFIG.String("app.staticPath"), http.Dir("."+common.CONFIG.String("app.staticPath"))) 35 | // 广告 36 | fmt.Println(` 37 | 欢迎使用 Seed-Admin 38 | 当前版本:V1.0.0 39 | QQ交流1群:8455822 40 | `) 41 | common.LOG.Info("滑稽") 42 | common.LOG.Error("错误的滑稽") 43 | // 冲 44 | app.Run(":" + common.CONFIG.String("app.port")) 45 | } 46 | -------------------------------------------------------------------------------- /core/middlewares/cors.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func UseCors(r *gin.Engine) { 10 | r.Use(func(c *gin.Context) { 11 | method := c.Request.Method 12 | origin := c.Request.Header.Get("Origin") 13 | // 允许来源 14 | c.Header("Access-Control-Allow-Origin", origin) 15 | // 请求方式 16 | c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") 17 | // 允许的标头 18 | c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization") 19 | // 暴露标头 20 | c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type") 21 | // 是否允许携带cookie 22 | c.Header("Access-Control-Allow-Credentials", "true") 23 | // 预检时间(秒) 24 | c.Header("Access-Control-Max-Age", "3600") 25 | //放行所有OPTIONS方法 26 | if method == "OPTIONS" { 27 | c.AbortWithStatus(http.StatusNoContent) 28 | } 29 | c.Next() 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /core/middlewares/logger.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "seed-admin/common" 5 | 6 | "seed-admin/core/zap/lumberjack" 7 | 8 | "github.com/gin-gonic/gin" 9 | ) 10 | 11 | // // zap接管gin日志(有需要自行加载) 12 | // func UseZapLogger(r *gin.Engine) { 13 | // logger := common.LOG 14 | // r.Use(func(ctx *gin.Context) { 15 | // start := time.Now() 16 | // ctx.Next() 17 | // cost := time.Since(start) 18 | // logger.Info(ctx.Request.URL.Path, 19 | // zap.Int("status", ctx.Writer.Status()), 20 | // zap.String("method", ctx.Request.Method), 21 | // zap.String("path", ctx.Request.URL.Path), 22 | // zap.String("query", ctx.Request.URL.RawQuery), 23 | // zap.String("ip", ctx.ClientIP()), 24 | // zap.String("user-agent", ctx.Request.UserAgent()), 25 | // zap.String("errors", ctx.Errors.ByType(gin.ErrorTypePrivate).String()), 26 | // zap.Duration("cost", cost), 27 | // ) 28 | // }) 29 | // } 30 | func UseLogger(r *gin.Engine) { 31 | var loggerConfig = gin.LoggerConfig{ 32 | Output: &lumberjack.Logger{ 33 | Filename: "./" + common.CONFIG.String("gin_log.director") + "/http.log", 34 | MaxSize: common.CONFIG.Int("gin_log.maxSize"), 35 | MaxBackups: common.CONFIG.Int("gin_log.maxBackups"), 36 | MaxAge: common.CONFIG.Int("gin_log.maxAge"), 37 | Compress: common.CONFIG.Bool("gin_log.compress"), 38 | }, 39 | } 40 | switch common.CONFIG.String("gin_log.outType") { 41 | case "console": 42 | r.Use(gin.Logger()) 43 | case "file": 44 | r.Use(gin.LoggerWithConfig(loggerConfig)) 45 | default: 46 | r.Use(gin.Logger()).Use(gin.LoggerWithConfig(loggerConfig)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/middlewares/middlewares.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | func Load(r *gin.Engine) { 8 | UseRecovery(r) 9 | UseLogger(r) 10 | // useCors(r) 11 | } 12 | -------------------------------------------------------------------------------- /core/middlewares/recovery.go: -------------------------------------------------------------------------------- 1 | package middlewares 2 | 3 | import "github.com/gin-gonic/gin" 4 | 5 | func UseRecovery(r *gin.Engine) { 6 | // 加载官方的恢复 如有必要自己写 7 | r.Use(gin.Recovery()) 8 | } 9 | -------------------------------------------------------------------------------- /core/redis/redis.go: -------------------------------------------------------------------------------- 1 | package redis 2 | 3 | import ( 4 | "seed-admin/common" 5 | 6 | "github.com/go-redis/redis" 7 | ) 8 | 9 | func AddGoRedis() *redis.Client { 10 | address := common.CONFIG.String("redis.server") + ":" + common.CONFIG.String("redis.port") 11 | password := common.CONFIG.String("redis.password") 12 | db := common.CONFIG.Int("redis.database") 13 | client := redis.NewClient(&redis.Options{ 14 | Addr: address, 15 | Password: password, 16 | DB: db, 17 | }) 18 | if _, err := client.Ping().Result(); err != nil { 19 | common.LOG.Error(err.Error()) 20 | } 21 | return client 22 | } 23 | -------------------------------------------------------------------------------- /core/xorm/xorm.go: -------------------------------------------------------------------------------- 1 | package xorm 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | goLog "log" 7 | "os" 8 | "seed-admin/common" 9 | "seed-admin/core/zap/lumberjack" 10 | "strings" 11 | "sync" 12 | 13 | _ "github.com/go-sql-driver/mysql" 14 | "xorm.io/xorm" 15 | "xorm.io/xorm/log" 16 | ) 17 | 18 | var wirteSqlTask sync.WaitGroup 19 | 20 | func AddXorm() *xorm.Engine { 21 | if common.CONFIG.String("mysql.database") == "" { 22 | panic("配置mysql.database为空,请检查config.toml配置文件") 23 | } 24 | db, err := newXorm() 25 | if err != nil { 26 | panic(err.Error()) 27 | } 28 | if err = db.Ping(); err != nil { 29 | if strings.Contains(err.Error(), "Unknown database") { 30 | return initDb() 31 | } 32 | panic(err.Error()) 33 | } 34 | return db 35 | } 36 | func newXorm() (*xorm.Engine, error) { 37 | var config = common.CONFIG.StringMap("mysql") 38 | // 组装连接字符串 39 | dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?%v", config["user"], config["password"], config["server"], config["port"], config["database"], config["config"]) 40 | db, err := xorm.NewEngine("mysql", dsn) 41 | if common.CONFIG.String("xorm_log.outType") == "file" { 42 | db.SetLogger(log.NewSimpleLogger(&lumberjack.Logger{ 43 | Filename: "./" + common.CONFIG.String("xorm_log.director") + "/db.log", 44 | MaxSize: common.CONFIG.Int("xorm_log.maxSize"), 45 | MaxBackups: common.CONFIG.Int("xorm_log.maxBackups"), 46 | MaxAge: common.CONFIG.Int("xorm_log.maxAge"), 47 | Compress: common.CONFIG.Bool("xorm_log.compress"), 48 | })) 49 | } else { 50 | db.SetLogger(log.NewSimpleLogger(os.Stdout)) 51 | } 52 | db.ShowSQL(common.CONFIG.Bool("xorm_log.showSql")) 53 | db.SetLogLevel(newLogger()) 54 | db.SetMaxIdleConns(common.CONFIG.Int("mysql.maxIdleConns")) 55 | db.SetMaxOpenConns(common.CONFIG.Int("mysql.maxOpenConns")) 56 | return db, err 57 | } 58 | 59 | func initDb() *xorm.Engine { 60 | var config = common.CONFIG.StringMap("mysql") 61 | goLog.Println("未发现数据库,将自动创建...") 62 | dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/", config["user"], config["password"], config["server"], config["port"]) 63 | db, err := sql.Open("mysql", dsn) 64 | if err != nil { 65 | panic(err.Error()) 66 | } 67 | defer func(db *sql.DB) { 68 | if err := db.Close(); err != nil { 69 | common.LOG.Error(err.Error()) 70 | } 71 | }(db) 72 | if err = db.Ping(); err != nil { 73 | panic(err.Error()) 74 | } 75 | createSql := fmt.Sprintf("CREATE DATABASE `%s` DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;", config["database"]) 76 | _, err = db.Exec(createSql) 77 | if err != nil { 78 | panic(err.Error()) 79 | } 80 | goLog.Printf("数据库 %v 创建完毕...\n", config["database"]) 81 | xormDb, err := newXorm() 82 | if err != nil { 83 | panic(err.Error()) 84 | } 85 | wirteSql(xormDb) 86 | return xormDb 87 | } 88 | 89 | // 加载文件 90 | func loadFiles(path string) []string { 91 | file, err := os.OpenFile(path, os.O_RDONLY, os.ModeDir) 92 | if err != nil { 93 | panic(err.Error()) 94 | } 95 | defer file.Close() 96 | fileInfo, _ := file.ReadDir(-1) 97 | files := []string{} 98 | for _, item := range fileInfo { 99 | files = append(files, item.Name()) 100 | } 101 | return files 102 | } 103 | 104 | // 导入数据表和数据 105 | func wirteSql(db *xorm.Engine) { 106 | db.ShowSQL(false) 107 | goLog.Println("开始导入数据表与数据...") 108 | path := "sql/" 109 | files := loadFiles(path) 110 | for _, file := range files { 111 | wirteSqlTask.Add(1) 112 | go importFile(db, path+file) 113 | } 114 | wirteSqlTask.Wait() 115 | goLog.Println("数据表与数据导入完成...") 116 | db.ShowSQL(common.CONFIG.Bool("xorm_log.showSql")) 117 | } 118 | 119 | func importFile(db *xorm.Engine, path string) { 120 | _, err := db.ImportFile(path) 121 | if err != nil { 122 | goLog.Fatalln(path + "导入失败 error:" + err.Error()) 123 | } 124 | wirteSqlTask.Done() 125 | } 126 | 127 | // 日志配置 128 | func newLogger() log.LogLevel { 129 | var level log.LogLevel 130 | switch common.CONFIG.String("xorm_log.level") { 131 | case "debug": 132 | level = log.LOG_DEBUG 133 | case "info": 134 | level = log.LOG_INFO 135 | case "warn": 136 | level = log.LOG_WARNING 137 | case "error": 138 | level = log.LOG_ERR 139 | default: 140 | level = log.LOG_INFO 141 | } 142 | return level 143 | } 144 | -------------------------------------------------------------------------------- /core/zap/lumberjack/chown.go: -------------------------------------------------------------------------------- 1 | //go:build !linux 2 | // +build !linux 3 | 4 | package lumberjack 5 | 6 | import ( 7 | "os" 8 | ) 9 | 10 | func chown(_ string, _ os.FileInfo) error { 11 | return nil 12 | } 13 | -------------------------------------------------------------------------------- /core/zap/lumberjack/chown_linux.go: -------------------------------------------------------------------------------- 1 | package lumberjack 2 | 3 | import ( 4 | "os" 5 | "syscall" 6 | ) 7 | 8 | // os_Chown is a var so we can mock it out during tests. 9 | var os_Chown = os.Chown 10 | 11 | func chown(name string, info os.FileInfo) error { 12 | f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode()) 13 | if err != nil { 14 | return err 15 | } 16 | f.Close() 17 | stat := info.Sys().(*syscall.Stat_t) 18 | return os_Chown(name, int(stat.Uid), int(stat.Gid)) 19 | } 20 | -------------------------------------------------------------------------------- /core/zap/lumberjack/lumberjack.go: -------------------------------------------------------------------------------- 1 | // Package lumberjack provides a rolling logger. 2 | // 3 | // Note that this is v2.0 of lumberjack, and should be imported using gopkg.in 4 | // thusly: 5 | // 6 | // import "gopkg.in/natefinch/lumberjack.v2" 7 | // 8 | // The package name remains simply lumberjack, and the code resides at 9 | // https://github.com/natefinch/lumberjack under the v2.0 branch. 10 | // 11 | // Lumberjack is intended to be one part of a logging infrastructure. 12 | // It is not an all-in-one solution, but instead is a pluggable 13 | // component at the bottom of the logging stack that simply controls the files 14 | // to which logs are written. 15 | // 16 | // Lumberjack plays well with any logging package that can write to an 17 | // io.Writer, including the standard library's log package. 18 | // 19 | // Lumberjack assumes that only one process is writing to the output files. 20 | // Using the same lumberjack configuration from multiple processes on the same 21 | // machine will result in improper behavior. 22 | package lumberjack 23 | 24 | import ( 25 | "compress/gzip" 26 | "errors" 27 | "fmt" 28 | "io" 29 | "io/ioutil" 30 | "os" 31 | "path/filepath" 32 | "sort" 33 | "strings" 34 | "sync" 35 | "time" 36 | ) 37 | 38 | const ( 39 | compressSuffix = ".gz" 40 | defaultMaxSize = 100 41 | ) 42 | 43 | // ensure we always implement io.WriteCloser 44 | var _ io.WriteCloser = (*Logger)(nil) 45 | 46 | // Logger is an io.WriteCloser that writes to the specified filename. 47 | // 48 | // Logger opens or creates the logfile on first Write. If the file exists and 49 | // is less than MaxSize megabytes, lumberjack will open and append to that file. 50 | // If the file exists and its size is >= MaxSize megabytes, the file is renamed 51 | // by putting the current time in a timestamp in the name immediately before the 52 | // file's extension (or the end of the filename if there's no extension). A new 53 | // log file is then created using original filename. 54 | // 55 | // Whenever a write would cause the current log file exceed MaxSize megabytes, 56 | // the current file is closed, renamed, and a new log file created with the 57 | // original name. Thus, the filename you give Logger is always the "current" log 58 | // file. 59 | // 60 | // Backups use the log file name given to Logger, in the form 61 | // `name-timestamp.ext` where name is the filename without the extension, 62 | // timestamp is the time at which the log was rotated formatted with the 63 | // time.Time format of `2006-01-02T15-04-05.000` and the extension is the 64 | // original extension. For example, if your Logger.Filename is 65 | // `/var/log/foo/server.log`, a backup created at 6:30pm on Nov 11 2016 would 66 | // use the filename `/var/log/foo/server-2016-11-04T18-30-00.000.log` 67 | // 68 | // Cleaning Up Old Log Files 69 | // 70 | // Whenever a new logfile gets created, old log files may be deleted. The most 71 | // recent files according to the encoded timestamp will be retained, up to a 72 | // number equal to MaxBackups (or all of them if MaxBackups is 0). Any files 73 | // with an encoded timestamp older than MaxAge days are deleted, regardless of 74 | // MaxBackups. Note that the time encoded in the timestamp is the rotation 75 | // time, which may differ from the last time that file was written to. 76 | // 77 | // If MaxBackups and MaxAge are both 0, no old log files will be deleted. 78 | type Logger struct { 79 | // Filename is the file to write logs to. Backup log files will be retained 80 | // in the same directory. It uses -lumberjack.log in 81 | // os.TempDir() if empty. 82 | Filename string `json:"filename" yaml:"filename"` 83 | 84 | // MaxSize is the maximum size in megabytes of the log file before it gets 85 | // rotated. It defaults to 100 megabytes. 86 | MaxSize int `json:"maxsize" yaml:"maxsize"` 87 | 88 | // MaxAge is the maximum number of days to retain old log files based on the 89 | // timestamp encoded in their filename. Note that a day is defined as 24 90 | // hours and may not exactly correspond to calendar days due to daylight 91 | // savings, leap seconds, etc. The default is not to remove old log files 92 | // based on age. 93 | MaxAge int `json:"maxage" yaml:"maxage"` 94 | 95 | // MaxBackups is the maximum number of old log files to retain. The default 96 | // is to retain all old log files (though MaxAge may still cause them to get 97 | // deleted.) 98 | MaxBackups int `json:"maxbackups" yaml:"maxbackups"` 99 | 100 | // LocalTime determines if the time used for formatting the timestamps in 101 | // backup files is the computer's local time. The default is to use UTC 102 | // time. 103 | LocalTime bool `json:"localtime" yaml:"localtime"` 104 | 105 | // Compress determines if the rotated log files should be compressed 106 | // using gzip. The default is not to perform compression. 107 | Compress bool `json:"compress" yaml:"compress"` 108 | BackupTimeFormat string `json:"backupTimeFormat" yaml:"backupTimeFormat"` 109 | size int64 110 | file *os.File 111 | mu sync.Mutex 112 | 113 | millCh chan bool 114 | startMill sync.Once 115 | } 116 | 117 | var ( 118 | // currentTime exists so it can be mocked out by tests. 119 | currentTime = time.Now 120 | 121 | // os_Stat exists so it can be mocked out by tests. 122 | os_Stat = os.Stat 123 | 124 | // megabyte is the conversion factor between MaxSize and bytes. It is a 125 | // variable so tests can mock it out and not need to write megabytes of data 126 | // to disk. 127 | megabyte = 1024 * 1024 128 | ) 129 | 130 | // Write implements io.Writer. If a write would cause the log file to be larger 131 | // than MaxSize, the file is closed, renamed to include a timestamp of the 132 | // current time, and a new log file is created using the original log file name. 133 | // If the length of the write is greater than MaxSize, an error is returned. 134 | func (l *Logger) Write(p []byte) (n int, err error) { 135 | l.mu.Lock() 136 | defer l.mu.Unlock() 137 | 138 | writeLen := int64(len(p)) 139 | if writeLen > l.max() { 140 | return 0, fmt.Errorf( 141 | "write length %d exceeds maximum file size %d", writeLen, l.max(), 142 | ) 143 | } 144 | 145 | if l.file == nil { 146 | if err = l.openExistingOrNew(len(p)); err != nil { 147 | return 0, err 148 | } 149 | } 150 | 151 | if l.size+writeLen > l.max() { 152 | if err := l.rotate(); err != nil { 153 | return 0, err 154 | } 155 | } 156 | 157 | n, err = l.file.Write(p) 158 | l.size += int64(n) 159 | 160 | return n, err 161 | } 162 | 163 | // Close implements io.Closer, and closes the current logfile. 164 | func (l *Logger) Close() error { 165 | l.mu.Lock() 166 | defer l.mu.Unlock() 167 | return l.close() 168 | } 169 | 170 | // close closes the file if it is open. 171 | func (l *Logger) close() error { 172 | if l.file == nil { 173 | return nil 174 | } 175 | err := l.file.Close() 176 | l.file = nil 177 | return err 178 | } 179 | 180 | // Rotate causes Logger to close the existing log file and immediately create a 181 | // new one. This is a helper function for applications that want to initiate 182 | // rotations outside of the normal rotation rules, such as in response to 183 | // SIGHUP. After rotating, this initiates compression and removal of old log 184 | // files according to the configuration. 185 | func (l *Logger) Rotate() error { 186 | l.mu.Lock() 187 | defer l.mu.Unlock() 188 | return l.rotate() 189 | } 190 | 191 | // rotate closes the current file, moves it aside with a timestamp in the name, 192 | // (if it exists), opens a new file with the original filename, and then runs 193 | // post-rotation processing and removal. 194 | func (l *Logger) rotate() error { 195 | if err := l.close(); err != nil { 196 | return err 197 | } 198 | if err := l.openNew(); err != nil { 199 | return err 200 | } 201 | l.mill() 202 | return nil 203 | } 204 | 205 | // openNew opens a new log file for writing, moving any old log file out of the 206 | // way. This methods assumes the file has already been closed. 207 | func (l *Logger) openNew() error { 208 | err := os.MkdirAll(l.dir(), 0744) 209 | if err != nil { 210 | return fmt.Errorf("can't make directories for new logfile: %s", err) 211 | } 212 | 213 | name := l.filename() 214 | mode := os.FileMode(0644) 215 | info, err := os_Stat(name) 216 | if err == nil { 217 | // Copy the mode off the old logfile. 218 | mode = info.Mode() 219 | // move the existing file 220 | newname := l.backupName(name, l.LocalTime) 221 | if err := os.Rename(name, newname); err != nil { 222 | return fmt.Errorf("can't rename log file: %s", err) 223 | } 224 | 225 | // this is a no-op anywhere but linux 226 | if err := chown(name, info); err != nil { 227 | return err 228 | } 229 | } 230 | 231 | // we use truncate here because this should only get called when we've moved 232 | // the file ourselves. if someone else creates the file in the meantime, 233 | // just wipe out the contents. 234 | f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, mode) 235 | if err != nil { 236 | return fmt.Errorf("can't open new logfile: %s", err) 237 | } 238 | l.file = f 239 | l.size = 0 240 | return nil 241 | } 242 | 243 | // backupName creates a new filename from the given name, inserting a timestamp 244 | // between the filename and the extension, using the local time if requested 245 | // (otherwise UTC). 246 | func (l *Logger) backupName(name string, local bool) string { 247 | dir := filepath.Dir(name) 248 | filename := filepath.Base(name) 249 | ext := filepath.Ext(filename) 250 | prefix := filename[:len(filename)-len(ext)] 251 | t := currentTime() 252 | if !local { 253 | t = t.Local() 254 | } 255 | 256 | timestamp := t.Format(l.BackupTimeFormat) 257 | return filepath.Join(dir, fmt.Sprintf("%s-%s%s", prefix, timestamp, ext)) 258 | } 259 | 260 | // openExistingOrNew opens the logfile if it exists and if the current write 261 | // would not put it over MaxSize. If there is no such file or the write would 262 | // put it over the MaxSize, a new file is created. 263 | func (l *Logger) openExistingOrNew(writeLen int) error { 264 | l.mill() 265 | 266 | filename := l.filename() 267 | info, err := os_Stat(filename) 268 | if os.IsNotExist(err) { 269 | return l.openNew() 270 | } 271 | if err != nil { 272 | return fmt.Errorf("error getting log file info: %s", err) 273 | } 274 | 275 | if info.Size()+int64(writeLen) >= l.max() { 276 | return l.rotate() 277 | } 278 | 279 | file, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY, 0644) 280 | if err != nil { 281 | // if we fail to open the old log file for some reason, just ignore 282 | // it and open a new log file. 283 | return l.openNew() 284 | } 285 | l.file = file 286 | l.size = info.Size() 287 | return nil 288 | } 289 | 290 | // genFilename generates the name of the logfile from the current time. 291 | func (l *Logger) filename() string { 292 | if l.Filename != "" { 293 | return l.Filename 294 | } 295 | name := filepath.Base(os.Args[0]) + "-lumberjack.log" 296 | return filepath.Join(os.TempDir(), name) 297 | } 298 | 299 | // millRunOnce performs compression and removal of stale log files. 300 | // Log files are compressed if enabled via configuration and old log 301 | // files are removed, keeping at most l.MaxBackups files, as long as 302 | // none of them are older than MaxAge. 303 | func (l *Logger) millRunOnce() error { 304 | if l.MaxBackups == 0 && l.MaxAge == 0 && !l.Compress { 305 | return nil 306 | } 307 | 308 | files, err := l.oldLogFiles() 309 | if err != nil { 310 | return err 311 | } 312 | 313 | var compress, remove []logInfo 314 | 315 | if l.MaxBackups > 0 && l.MaxBackups < len(files) { 316 | preserved := make(map[string]bool) 317 | var remaining []logInfo 318 | for _, f := range files { 319 | // Only count the uncompressed log file or the 320 | // compressed log file, not both. 321 | fn := f.Name() 322 | if strings.HasSuffix(fn, compressSuffix) { 323 | fn = fn[:len(fn)-len(compressSuffix)] 324 | } 325 | preserved[fn] = true 326 | 327 | if len(preserved) > l.MaxBackups { 328 | remove = append(remove, f) 329 | } else { 330 | remaining = append(remaining, f) 331 | } 332 | } 333 | files = remaining 334 | } 335 | if l.MaxAge > 0 { 336 | diff := time.Duration(int64(24*time.Hour) * int64(l.MaxAge)) 337 | cutoff := currentTime().Add(-1 * diff) 338 | var remaining []logInfo 339 | for _, f := range files { 340 | if f.timestamp.Before(cutoff) { 341 | remove = append(remove, f) 342 | } else { 343 | remaining = append(remaining, f) 344 | } 345 | } 346 | files = remaining 347 | } 348 | 349 | if l.Compress { 350 | for _, f := range files { 351 | if !strings.HasSuffix(f.Name(), compressSuffix) { 352 | compress = append(compress, f) 353 | } 354 | } 355 | } 356 | 357 | for _, f := range remove { 358 | errRemove := os.Remove(filepath.Join(l.dir(), f.Name())) 359 | if err == nil && errRemove != nil { 360 | err = errRemove 361 | } 362 | } 363 | for _, f := range compress { 364 | fn := filepath.Join(l.dir(), f.Name()) 365 | errCompress := compressLogFile(fn, fn+compressSuffix) 366 | if err == nil && errCompress != nil { 367 | err = errCompress 368 | } 369 | } 370 | 371 | return err 372 | } 373 | 374 | // millRun runs in a goroutine to manage post-rotation compression and removal 375 | // of old log files. 376 | func (l *Logger) millRun() { 377 | for _ = range l.millCh { 378 | // what am I going to do, log this? 379 | _ = l.millRunOnce() 380 | } 381 | } 382 | 383 | // mill performs post-rotation compression and removal of stale log files, 384 | // starting the mill goroutine if necessary. 385 | func (l *Logger) mill() { 386 | l.startMill.Do(func() { 387 | l.millCh = make(chan bool, 1) 388 | go l.millRun() 389 | }) 390 | select { 391 | case l.millCh <- true: 392 | default: 393 | } 394 | } 395 | 396 | // oldLogFiles returns the list of backup log files stored in the same 397 | // directory as the current log file, sorted by ModTime 398 | func (l *Logger) oldLogFiles() ([]logInfo, error) { 399 | files, err := ioutil.ReadDir(l.dir()) 400 | if err != nil { 401 | return nil, fmt.Errorf("can't read log file directory: %s", err) 402 | } 403 | logFiles := []logInfo{} 404 | 405 | prefix, ext := l.prefixAndExt() 406 | 407 | for _, f := range files { 408 | if f.IsDir() { 409 | continue 410 | } 411 | if t, err := l.timeFromName(f.Name(), prefix, ext); err == nil { 412 | logFiles = append(logFiles, logInfo{t, f}) 413 | continue 414 | } 415 | if t, err := l.timeFromName(f.Name(), prefix, ext+compressSuffix); err == nil { 416 | logFiles = append(logFiles, logInfo{t, f}) 417 | continue 418 | } 419 | // error parsing means that the suffix at the end was not generated 420 | // by lumberjack, and therefore it's not a backup file. 421 | } 422 | 423 | sort.Sort(byFormatTime(logFiles)) 424 | 425 | return logFiles, nil 426 | } 427 | 428 | // timeFromName extracts the formatted time from the filename by stripping off 429 | // the filename's prefix and extension. This prevents someone's filename from 430 | // confusing time.parse. 431 | func (l *Logger) timeFromName(filename, prefix, ext string) (time.Time, error) { 432 | if !strings.HasPrefix(filename, prefix) { 433 | return time.Time{}, errors.New("mismatched prefix") 434 | } 435 | if !strings.HasSuffix(filename, ext) { 436 | return time.Time{}, errors.New("mismatched extension") 437 | } 438 | ts := filename[len(prefix) : len(filename)-len(ext)] 439 | return time.Parse(l.BackupTimeFormat, ts) 440 | } 441 | 442 | // max returns the maximum size in bytes of log files before rolling. 443 | func (l *Logger) max() int64 { 444 | if l.MaxSize == 0 { 445 | return int64(defaultMaxSize * megabyte) 446 | } 447 | return int64(l.MaxSize) * int64(megabyte) 448 | } 449 | 450 | // dir returns the directory for the current filename. 451 | func (l *Logger) dir() string { 452 | return filepath.Dir(l.filename()) 453 | } 454 | 455 | // prefixAndExt returns the filename part and extension part from the Logger's 456 | // filename. 457 | func (l *Logger) prefixAndExt() (prefix, ext string) { 458 | filename := filepath.Base(l.filename()) 459 | ext = filepath.Ext(filename) 460 | prefix = filename[:len(filename)-len(ext)] + "-" 461 | return prefix, ext 462 | } 463 | 464 | // compressLogFile compresses the given log file, removing the 465 | // uncompressed log file if successful. 466 | func compressLogFile(src, dst string) (err error) { 467 | f, err := os.Open(src) 468 | if err != nil { 469 | return fmt.Errorf("failed to open log file: %v", err) 470 | } 471 | defer f.Close() 472 | 473 | fi, err := os_Stat(src) 474 | if err != nil { 475 | return fmt.Errorf("failed to stat log file: %v", err) 476 | } 477 | 478 | if err := chown(dst, fi); err != nil { 479 | return fmt.Errorf("failed to chown compressed log file: %v", err) 480 | } 481 | 482 | // If this file already exists, we presume it was created by 483 | // a previous attempt to compress the log file. 484 | gzf, err := os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, fi.Mode()) 485 | if err != nil { 486 | return fmt.Errorf("failed to open compressed log file: %v", err) 487 | } 488 | defer gzf.Close() 489 | 490 | gz := gzip.NewWriter(gzf) 491 | 492 | defer func() { 493 | if err != nil { 494 | os.Remove(dst) 495 | err = fmt.Errorf("failed to compress log file: %v", err) 496 | } 497 | }() 498 | 499 | if _, err := io.Copy(gz, f); err != nil { 500 | return err 501 | } 502 | if err := gz.Close(); err != nil { 503 | return err 504 | } 505 | if err := gzf.Close(); err != nil { 506 | return err 507 | } 508 | 509 | if err := f.Close(); err != nil { 510 | return err 511 | } 512 | if err := os.Remove(src); err != nil { 513 | return err 514 | } 515 | 516 | return nil 517 | } 518 | 519 | // logInfo is a convenience struct to return the filename and its embedded 520 | // timestamp. 521 | type logInfo struct { 522 | timestamp time.Time 523 | os.FileInfo 524 | } 525 | 526 | // byFormatTime sorts by newest time formatted in the name. 527 | type byFormatTime []logInfo 528 | 529 | func (b byFormatTime) Less(i, j int) bool { 530 | return b[i].timestamp.After(b[j].timestamp) 531 | } 532 | 533 | func (b byFormatTime) Swap(i, j int) { 534 | b[i], b[j] = b[j], b[i] 535 | } 536 | 537 | func (b byFormatTime) Len() int { 538 | return len(b) 539 | } 540 | -------------------------------------------------------------------------------- /core/zap/zap.go: -------------------------------------------------------------------------------- 1 | package zap 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "seed-admin/common" 7 | "time" 8 | 9 | "seed-admin/core/zap/lumberjack" 10 | 11 | "github.com/gookit/color" 12 | "go.uber.org/zap" 13 | "go.uber.org/zap/zapcore" 14 | ) 15 | 16 | func AddZap() *zap.Logger { 17 | options := []zap.Option{} 18 | if common.CONFIG.Bool("log.showLine") { 19 | options = append(options, zap.AddCaller()) 20 | } 21 | // 如果日志等级为Debug/Error等级时显示堆栈信息 22 | if level() == zap.DebugLevel || level() == zap.ErrorLevel { 23 | options = append(options, zap.AddStacktrace(level())) 24 | } 25 | return zap.New(core(), options...) 26 | } 27 | 28 | func core() zapcore.Core { 29 | var teeCore []zapcore.Core 30 | consoleCore := zapcore.NewCore(encoder(true), zapcore.Lock(os.Stdout), level()) 31 | fileCore := zapcore.NewCore(encoder(false), writerSyncerConfig(), level()) 32 | 33 | switch common.CONFIG.Get("log.outType") { 34 | case "console": 35 | teeCore = append(teeCore, consoleCore) 36 | case "file": 37 | teeCore = append(teeCore, fileCore) 38 | default: 39 | teeCore = append(teeCore, consoleCore, fileCore) 40 | } 41 | return zapcore.NewTee(teeCore...) 42 | } 43 | func encoder(isConsole bool) zapcore.Encoder { 44 | var format string 45 | if isConsole { 46 | format = "log.console_format" 47 | } else { 48 | format = "log.file_format" 49 | } 50 | if common.CONFIG.Get(format) == "json" { 51 | return zapcore.NewJSONEncoder(encoderConfig(isConsole)) 52 | } 53 | return zapcore.NewConsoleEncoder(encoderConfig(isConsole)) 54 | } 55 | 56 | func encoderConfig(isConsole bool) zapcore.EncoderConfig { 57 | var encoderTime zapcore.TimeEncoder 58 | var encodeLevel zapcore.LevelEncoder 59 | if isConsole { 60 | encoderTime = consoleEncoderTime 61 | encodeLevel = consoleEncodeLevel 62 | } else { 63 | encoderTime = fileEncoderTime 64 | encodeLevel = fileEncodeLevel 65 | } 66 | encoderConfig := zapcore.EncoderConfig{ 67 | TimeKey: "time", 68 | MessageKey: "message", 69 | LevelKey: "level", 70 | CallerKey: "caller", 71 | StacktraceKey: "stacktrace", 72 | EncodeTime: encoderTime, 73 | EncodeLevel: encodeLevel, 74 | EncodeCaller: zapcore.ShortCallerEncoder, // FullCallerEncoder || ShortCallerEncoder 75 | EncodeDuration: zapcore.SecondsDurationEncoder, 76 | ConsoleSeparator: " ", 77 | } 78 | return encoderConfig 79 | } 80 | func writerSyncerConfig() zapcore.WriteSyncer { 81 | lumberJackLogger := &lumberjack.Logger{ 82 | Filename: "./" + common.CONFIG.String("log.director") + "/runtime.log", 83 | MaxSize: common.CONFIG.Int("log.maxSize"), 84 | MaxBackups: common.CONFIG.Int("log.maxBackups"), 85 | MaxAge: common.CONFIG.Int("log.maxAge"), 86 | Compress: common.CONFIG.Bool("log.compress"), 87 | BackupTimeFormat: "2006-01-02T15-04-05", 88 | } 89 | return zapcore.AddSync(lumberJackLogger) 90 | } 91 | 92 | // 获取配置里的日志等级 93 | func level() zapcore.Level { 94 | l := new(zapcore.Level) 95 | err := l.UnmarshalText([]byte(common.CONFIG.String("log.level"))) 96 | if err != nil { 97 | return zap.InfoLevel 98 | } 99 | return *l 100 | } 101 | func consoleEncodeLevel(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { 102 | if common.CONFIG.Get("log.console_format") == "json" { 103 | enc.AppendString(level.CapitalString()) 104 | return 105 | } 106 | enc.AppendString(colorEnc(string(fmt.Sprint(level)), "["+level.CapitalString()+"]")) 107 | } 108 | func consoleEncoderTime(time time.Time, enc zapcore.PrimitiveArrayEncoder) { 109 | if common.CONFIG.Get("log.console_format") == "json" { 110 | enc.AppendString(time.Format("2006/01/02 15:04:05")) 111 | return 112 | } 113 | enc.AppendString("[" + common.CONFIG.String("app.name") + "]") 114 | enc.AppendString("[" + time.Format("2006/01/02 15:04:05") + "]") 115 | } 116 | func fileEncodeLevel(level zapcore.Level, enc zapcore.PrimitiveArrayEncoder) { 117 | if common.CONFIG.Get("log.file_format") == "json" { 118 | enc.AppendString(level.CapitalString()) 119 | return 120 | } 121 | enc.AppendString("[" + level.CapitalString() + "]") 122 | } 123 | func fileEncoderTime(time time.Time, enc zapcore.PrimitiveArrayEncoder) { 124 | if common.CONFIG.Get("log.file_format") == "json" { 125 | enc.AppendString(time.Format("2006/01/02 15:04:05")) 126 | return 127 | } 128 | enc.AppendString("[" + common.CONFIG.String("app.name") + "]") 129 | enc.AppendString("[" + time.Format("2006/01/02 15:04:05") + "]") 130 | } 131 | 132 | // 给标签上个色 133 | func colorEnc(level string, value string) string { 134 | switch level { 135 | case "debug": 136 | return color.Debug.Sprintf(value) 137 | case "info": 138 | return color.Info.Sprintf(value) 139 | case "warn": 140 | return color.Warn.Sprintf(value) 141 | case "error": 142 | return color.Error.Sprintf(value) 143 | default: 144 | return color.Light.Sprintf(value) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module seed-admin 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f 7 | github.com/fsnotify/fsnotify v1.5.4 8 | github.com/gin-gonic/gin v1.8.1 9 | github.com/go-redis/redis v6.15.9+incompatible 10 | github.com/go-sql-driver/mysql v1.6.0 11 | github.com/gofrs/uuid v4.2.0+incompatible 12 | github.com/golang-jwt/jwt v3.2.2+incompatible 13 | github.com/gookit/color v1.5.2 14 | github.com/gookit/config/v2 v2.1.8 15 | github.com/gookit/validate v1.4.2 16 | go.uber.org/zap v1.21.0 17 | xorm.io/xorm v1.3.1 18 | ) 19 | 20 | require ( 21 | github.com/BurntSushi/toml v1.2.1 // indirect 22 | github.com/gin-contrib/sse v0.1.0 // indirect 23 | github.com/go-ole/go-ole v1.2.6 // indirect 24 | github.com/go-playground/locales v0.14.0 // indirect 25 | github.com/go-playground/universal-translator v0.18.0 // indirect 26 | github.com/go-playground/validator/v10 v10.11.0 // indirect 27 | github.com/goccy/go-json v0.9.8 // indirect 28 | github.com/golang/snappy v0.0.4 // indirect 29 | github.com/gookit/filter v1.1.2 // indirect 30 | github.com/gookit/goutil v0.5.15 31 | github.com/imdario/mergo v0.3.13 // indirect 32 | github.com/json-iterator/go v1.1.12 // indirect 33 | github.com/leodido/go-urn v1.2.1 // indirect 34 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 35 | github.com/mattn/go-isatty v0.0.16 // indirect 36 | github.com/mitchellh/mapstructure v1.5.0 // indirect 37 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 38 | github.com/modern-go/reflect2 v1.0.2 // indirect 39 | github.com/pelletier/go-toml/v2 v2.0.2 // indirect 40 | github.com/pkg/errors v0.9.1 // indirect 41 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 42 | github.com/shirou/gopsutil/v3 v3.22.7 43 | github.com/syndtr/goleveldb v1.0.0 // indirect 44 | github.com/tklauser/go-sysconf v0.3.10 // indirect 45 | github.com/tklauser/numcpus v0.4.0 // indirect 46 | github.com/ugorji/go/codec v1.2.7 // indirect 47 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect 48 | github.com/yusufpapurcu/wmi v1.2.2 // indirect 49 | go.uber.org/atomic v1.9.0 // indirect 50 | go.uber.org/multierr v1.8.0 // indirect; inr5t30936793adb5e 51 | golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 // indirect 52 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect 53 | golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 // indirect 54 | golang.org/x/term v0.0.0-20220722155259-a9ba230a4035 // indirect 55 | golang.org/x/text v0.3.8 // indirect 56 | google.golang.org/protobuf v1.28.0 // indirect 57 | gopkg.in/yaml.v2 v2.4.0 // indirect 58 | xorm.io/builder v0.3.11 // indirect 59 | ) 60 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "seed-admin/core" 4 | 5 | func main() { 6 | core.Run() 7 | } 8 | -------------------------------------------------------------------------------- /sql/seed-admin.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Navicat MySQL Data Transfer 3 | 4 | Source Server : seed-admin 5 | Source Server Type : MySQL 6 | Source Server Version : 50739 7 | Source Host : 119.8.52.66:3306 8 | Source Schema : seed-admin 9 | 10 | Target Server Type : MySQL 11 | Target Server Version : 50739 12 | File Encoding : 65001 13 | 14 | Date: 12/08/2022 08:54:45 15 | */ 16 | 17 | SET NAMES utf8mb4; 18 | SET FOREIGN_KEY_CHECKS = 0; 19 | 20 | -- ---------------------------- 21 | -- Table structure for admin_sys_dept 22 | -- ---------------------------- 23 | DROP TABLE IF EXISTS `admin_sys_dept`; 24 | CREATE TABLE `admin_sys_dept` ( 25 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 26 | `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '部门名称', 27 | `parent_id` int(11) NULL DEFAULT NULL COMMENT '父级ID', 28 | `sort` int(11) NULL DEFAULT NULL COMMENT '排序', 29 | `created_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 30 | `updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间', 31 | PRIMARY KEY (`id`) USING BTREE 32 | ) ENGINE = InnoDB AUTO_INCREMENT = 32 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 33 | 34 | -- ---------------------------- 35 | -- Records of admin_sys_dept 36 | -- ---------------------------- 37 | INSERT INTO `admin_sys_dept` VALUES (1, '心脏跳动科技', 0, 0, '2022-07-02 14:48:55', '2022-07-21 17:34:35'); 38 | INSERT INTO `admin_sys_dept` VALUES (2, '北京总公司', 1, 0, '2022-07-02 14:51:09', '2022-07-08 18:52:48'); 39 | INSERT INTO `admin_sys_dept` VALUES (5, '后勤部', 2, 0, '2022-07-02 14:52:48', '2022-07-08 19:31:37'); 40 | INSERT INTO `admin_sys_dept` VALUES (6, '西藏分公司', 1, 0, '2022-07-02 14:52:26', '2022-07-08 19:35:38'); 41 | INSERT INTO `admin_sys_dept` VALUES (21, '财务部', 6, 0, '2022-07-08 17:46:46', '2022-07-08 19:35:49'); 42 | INSERT INTO `admin_sys_dept` VALUES (23, '工程部', 6, 0, '2022-07-08 19:53:15', '2022-07-08 19:53:15'); 43 | INSERT INTO `admin_sys_dept` VALUES (25, '江浙沪分公司', 1, 0, '2022-07-10 10:50:02', '2022-07-10 10:50:02'); 44 | INSERT INTO `admin_sys_dept` VALUES (26, '开发部', 25, 0, '2022-07-10 10:50:08', '2022-07-10 10:50:08'); 45 | INSERT INTO `admin_sys_dept` VALUES (27, '财务部', 25, 0, '2022-07-10 10:50:18', '2022-07-10 10:50:18'); 46 | INSERT INTO `admin_sys_dept` VALUES (28, '工程部', 25, 0, '2022-07-10 10:50:23', '2022-07-10 10:50:23'); 47 | INSERT INTO `admin_sys_dept` VALUES (29, '东北分公司', 1, 0, '2022-07-10 10:50:32', '2022-07-10 10:50:32'); 48 | INSERT INTO `admin_sys_dept` VALUES (30, '销售部', 29, 0, '2022-07-10 10:50:39', '2022-07-10 10:50:39'); 49 | INSERT INTO `admin_sys_dept` VALUES (31, '后勤2部', 2, 0, '2022-07-19 18:30:04', '2022-07-22 23:04:24'); 50 | 51 | -- ---------------------------- 52 | -- Table structure for admin_sys_dict_data 53 | -- ---------------------------- 54 | DROP TABLE IF EXISTS `admin_sys_dict_data`; 55 | CREATE TABLE `admin_sys_dict_data` ( 56 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 57 | `pid` int(11) NOT NULL COMMENT '主键', 58 | `label` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典标签', 59 | `value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典值', 60 | `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态0.正常 1.禁用', 61 | `created_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 62 | `updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间', 63 | PRIMARY KEY (`id`) USING BTREE 64 | ) ENGINE = InnoDB AUTO_INCREMENT = 28 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 65 | 66 | -- ---------------------------- 67 | -- Records of admin_sys_dict_data 68 | -- ---------------------------- 69 | INSERT INTO `admin_sys_dict_data` VALUES (18, 7, '正常', '0', 0, '2022-07-16 15:49:08', '2022-07-16 19:09:14'); 70 | INSERT INTO `admin_sys_dict_data` VALUES (19, 7, '禁用', '1', 0, '2022-07-16 15:49:14', '2022-07-17 14:22:40'); 71 | INSERT INTO `admin_sys_dict_data` VALUES (20, 8, '目录', '0', 0, '2022-07-17 17:04:45', '2022-07-17 17:04:45'); 72 | INSERT INTO `admin_sys_dict_data` VALUES (21, 8, '菜单', '1', 0, '2022-07-17 17:04:51', '2022-07-17 17:04:51'); 73 | INSERT INTO `admin_sys_dict_data` VALUES (22, 8, '按钮', '2', 0, '2022-07-17 17:04:59', '2022-07-17 17:04:59'); 74 | INSERT INTO `admin_sys_dict_data` VALUES (23, 9, '否', '0', 0, '2022-07-17 17:17:26', '2022-07-17 17:17:26'); 75 | INSERT INTO `admin_sys_dict_data` VALUES (24, 9, '是', '1', 0, '2022-07-17 17:17:31', '2022-07-17 17:17:31'); 76 | INSERT INTO `admin_sys_dict_data` VALUES (26, 19, '显示', '0', 0, '2022-07-27 15:10:11', '2022-07-27 15:10:11'); 77 | INSERT INTO `admin_sys_dict_data` VALUES (27, 19, '隐藏', '1', 0, '2022-07-27 15:10:16', '2022-07-27 15:10:16'); 78 | 79 | -- ---------------------------- 80 | -- Table structure for admin_sys_dict_type 81 | -- ---------------------------- 82 | DROP TABLE IF EXISTS `admin_sys_dict_type`; 83 | CREATE TABLE `admin_sys_dict_type` ( 84 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 85 | `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典名称', 86 | `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典类型', 87 | `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态0.正常 1.禁用', 88 | `created_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 89 | `updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间', 90 | PRIMARY KEY (`id`) USING BTREE 91 | ) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 92 | 93 | -- ---------------------------- 94 | -- Records of admin_sys_dict_type 95 | -- ---------------------------- 96 | INSERT INTO `admin_sys_dict_type` VALUES (7, '状态', 'sys_common_status', 0, '2022-07-16 15:48:41', '2022-07-16 19:14:34'); 97 | INSERT INTO `admin_sys_dict_type` VALUES (8, '菜单类型', 'sys_menu_type', 0, '2022-07-17 17:04:25', '2022-07-17 17:04:31'); 98 | INSERT INTO `admin_sys_dict_type` VALUES (9, '页面缓存', 'sys_page_keepAlive', 0, '2022-07-17 17:17:00', '2022-07-17 17:17:00'); 99 | INSERT INTO `admin_sys_dict_type` VALUES (19, '菜单显示', 'sys_menu_visible', 0, '2022-07-27 15:09:48', '2022-07-27 15:10:25'); 100 | 101 | -- ---------------------------- 102 | -- Table structure for admin_sys_log 103 | -- ---------------------------- 104 | DROP TABLE IF EXISTS `admin_sys_log`; 105 | CREATE TABLE `admin_sys_log` ( 106 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 107 | `user_id` int(11) NOT NULL COMMENT '角色ID', 108 | `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '请求方式', 109 | `action` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '行为', 110 | `ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'IP', 111 | `status_code` int(11) NULL DEFAULT NULL COMMENT '响应状态', 112 | `params` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '参数', 113 | `results` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL, 114 | `created_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 115 | `updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间', 116 | PRIMARY KEY (`id`) USING BTREE 117 | ) ENGINE = InnoDB AUTO_INCREMENT = 1587 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 118 | 119 | -- ---------------------------- 120 | -- Records of admin_sys_log 121 | -- ---------------------------- 122 | 123 | -- ---------------------------- 124 | -- Table structure for admin_sys_menu 125 | -- ---------------------------- 126 | DROP TABLE IF EXISTS `admin_sys_menu`; 127 | CREATE TABLE `admin_sys_menu` ( 128 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 129 | `parent_id` int(11) NOT NULL DEFAULT 0, 130 | `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '菜单名称', 131 | `router_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由名称', 132 | `router_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由地址', 133 | `page_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '页面路径', 134 | `perms` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识', 135 | `type` tinyint(1) NOT NULL DEFAULT 0 COMMENT '类型0.目录 1.菜单 2.按钮', 136 | `icon` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标', 137 | `sort` int(11) NULL DEFAULT NULL COMMENT '排序', 138 | `visible` tinyint(1) NOT NULL DEFAULT 0 COMMENT '隐藏0.显示 1.隐藏', 139 | `keep_alive` tinyint(1) NOT NULL DEFAULT 0 COMMENT '页面缓存0.否 1.是', 140 | `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态0.正常 1.禁用', 141 | `created_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 142 | `updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间', 143 | PRIMARY KEY (`id`) USING BTREE 144 | ) ENGINE = InnoDB AUTO_INCREMENT = 80 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 145 | 146 | -- ---------------------------- 147 | -- Records of admin_sys_menu 148 | -- ---------------------------- 149 | INSERT INTO `admin_sys_menu` VALUES (1, 0, '仪表盘', 'dashboard', '/dashboard/index', '/dashboard/index.vue', 'sys:dashboard', 1, 'dashboardOutlined', 0, 0, 0, 0, '2022-06-18 04:46:24', '2022-07-12 17:12:33'); 150 | INSERT INTO `admin_sys_menu` VALUES (2, 0, '系统管理', 'system', '/system', '', '', 0, 'settingOutlined', 1, 0, 0, 0, '2022-06-18 10:28:30', '2022-06-28 10:17:08'); 151 | INSERT INTO `admin_sys_menu` VALUES (3, 2, '用户管理', 'user', '/system/user', '/system/user/index.vue', 'sys:user:list', 1, 'userOutlined', 0, 0, 0, 0, '2022-06-18 10:34:04', '2022-06-29 12:36:52'); 152 | INSERT INTO `admin_sys_menu` VALUES (4, 2, '菜单管理', 'menu', '/system/menu', '/system/menu/index.vue', 'sys:menu:list', 1, 'menuOutlined', 2, 0, 0, 0, '2022-06-18 10:37:23', '2022-07-24 14:19:08'); 153 | INSERT INTO `admin_sys_menu` VALUES (5, 2, '角色管理', 'role', '/system/role', '/system/role/index.vue', 'sys:role:list', 1, 'robotOutlined', 1, 0, 0, 0, '2022-06-18 10:37:26', '2022-07-24 14:19:05'); 154 | INSERT INTO `admin_sys_menu` VALUES (6, 2, '操作日志', 'log', '/system/log', '/system/log/index.vue', 'sys:log:list', 1, 'minusSquareOutlined', 3, 0, 0, 0, '2022-06-18 10:38:48', '2022-07-29 08:13:25'); 155 | INSERT INTO `admin_sys_menu` VALUES (7, 2, '系统监控', 'server', '/system/server', '/system/server/index.vue', 'sys:server:info', 1, 'fundProjectionScreenOutlined', 4, 0, 0, 0, '2022-06-20 13:13:36', '2022-08-01 16:38:20'); 156 | INSERT INTO `admin_sys_menu` VALUES (8, 2, '字典管理', 'dict', '/system/dict', '/system/dict/index.vue', 'sys:dict:list', 1, 'bookOutlined', 5, 0, 0, 0, '2022-06-21 09:02:15', '2022-07-23 18:32:51'); 157 | INSERT INTO `admin_sys_menu` VALUES (46, 5, '新增', NULL, NULL, NULL, 'sys:role:add', 2, '', 0, 0, 0, 0, '2022-06-26 19:27:46', '2022-06-26 19:27:46'); 158 | INSERT INTO `admin_sys_menu` VALUES (50, 2, '字典数据', 'dictDetails', '/system/dict/details/:id', '/system/dict/details/index.vue', 'sys:dict:details:list', 1, '', 10, 1, 0, 0, '2022-07-11 17:20:33', '2022-07-22 22:12:06'); 159 | INSERT INTO `admin_sys_menu` VALUES (51, 8, '信息', '', '', '', 'sys:dict:info', 2, '', 0, 0, 0, 0, '2022-07-22 18:44:20', '2022-07-22 18:44:20'); 160 | INSERT INTO `admin_sys_menu` VALUES (52, 8, '新增', '', '', '', 'sys:dict:add', 2, '', 0, 0, 0, 0, '2022-07-22 22:04:53', '2022-07-22 22:04:53'); 161 | INSERT INTO `admin_sys_menu` VALUES (53, 8, '编辑', '', '', '', 'sys:dict:update', 2, '', 0, 0, 0, 0, '2022-07-22 22:05:12', '2022-07-22 22:06:24'); 162 | INSERT INTO `admin_sys_menu` VALUES (54, 8, '删除', '', '', '', 'sys:dict:del', 2, '', 0, 0, 0, 0, '2022-07-22 22:06:18', '2022-07-22 22:06:18'); 163 | INSERT INTO `admin_sys_menu` VALUES (55, 50, '信息', '', '', '', 'sys:dict:details:info', 2, '', 0, 0, 0, 0, '2022-07-22 22:13:23', '2022-07-22 22:13:23'); 164 | INSERT INTO `admin_sys_menu` VALUES (56, 50, '新增', '', '', '', 'sys:dict:details:add', 2, '', 0, 0, 0, 0, '2022-07-22 22:14:40', '2022-07-22 22:14:40'); 165 | INSERT INTO `admin_sys_menu` VALUES (57, 50, '编辑', '', '', '', 'sys:dict:details:update', 2, '', 0, 0, 0, 0, '2022-07-22 22:15:00', '2022-07-22 22:15:00'); 166 | INSERT INTO `admin_sys_menu` VALUES (58, 50, '删除', '', '', '', 'sys:dict:details:del', 2, '', 0, 0, 0, 0, '2022-07-22 22:15:12', '2022-07-22 22:15:12'); 167 | INSERT INTO `admin_sys_menu` VALUES (59, 3, '新增', '', '', '', 'sys:user:add', 2, '', 0, 0, 0, 0, '2022-07-22 22:29:04', '2022-07-22 22:29:04'); 168 | INSERT INTO `admin_sys_menu` VALUES (60, 3, '删除', '', '', '', 'sys:user:del', 2, '', 0, 0, 0, 0, '2022-07-22 22:29:15', '2022-07-22 22:29:15'); 169 | INSERT INTO `admin_sys_menu` VALUES (61, 3, '信息', '', '', '', 'sys:user:info', 2, '', 0, 0, 0, 0, '2022-07-22 22:29:27', '2022-07-22 22:29:27'); 170 | INSERT INTO `admin_sys_menu` VALUES (62, 3, '编辑', '', '', '', 'sys:user:update', 2, '', 0, 0, 0, 0, '2022-07-22 22:29:38', '2022-07-22 22:29:38'); 171 | INSERT INTO `admin_sys_menu` VALUES (63, 3, '移动用户部门', '', '', '', 'sys:user:move', 2, '', 0, 0, 0, 0, '2022-07-22 22:29:58', '2022-07-22 22:29:58'); 172 | INSERT INTO `admin_sys_menu` VALUES (64, 3, '更新用户角色', '', '', '', 'sys:user:updateUserRole', 2, '', 0, 0, 0, 0, '2022-07-22 22:30:45', '2022-07-22 22:30:45'); 173 | INSERT INTO `admin_sys_menu` VALUES (65, 5, '信息', '', '', '', 'sys:role:info', 2, '', 0, 0, 0, 0, '2022-07-22 22:35:42', '2022-07-22 22:35:42'); 174 | INSERT INTO `admin_sys_menu` VALUES (66, 5, '编辑', '', '', '', 'sys:role:update', 2, '', 0, 0, 0, 0, '2022-07-22 22:35:53', '2022-07-22 22:35:53'); 175 | INSERT INTO `admin_sys_menu` VALUES (67, 5, '删除', '', '', '', 'sys:role:del', 2, '', 0, 0, 0, 0, '2022-07-22 22:36:03', '2022-07-22 22:36:03'); 176 | INSERT INTO `admin_sys_menu` VALUES (68, 4, '信息', '', '', '', 'sys:menu:info', 2, '', 0, 0, 0, 0, '2022-07-22 22:42:14', '2022-07-22 22:42:14'); 177 | INSERT INTO `admin_sys_menu` VALUES (69, 4, '新增', '', '', '', 'sys:menu:add', 2, '', 0, 0, 0, 0, '2022-07-22 22:42:33', '2022-07-22 22:42:33'); 178 | INSERT INTO `admin_sys_menu` VALUES (70, 4, '编辑', '', '', '', 'sys:menu:update', 2, '', 0, 0, 0, 0, '2022-07-22 22:42:43', '2022-07-22 22:42:43'); 179 | INSERT INTO `admin_sys_menu` VALUES (71, 4, '删除', '', '', '', 'sys:menu:del', 2, '', 0, 0, 0, 0, '2022-07-22 22:42:55', '2022-07-22 22:42:55'); 180 | INSERT INTO `admin_sys_menu` VALUES (72, 3, '部门列表', '', '', '', 'sys:dept:list', 2, '', 0, 0, 0, 0, '2022-07-22 22:52:04', '2022-07-22 22:52:04'); 181 | INSERT INTO `admin_sys_menu` VALUES (73, 3, '部门信息', '', '', '', 'sys:dept:info', 2, '', 0, 0, 0, 0, '2022-07-22 22:53:14', '2022-07-22 22:53:14'); 182 | INSERT INTO `admin_sys_menu` VALUES (74, 3, '部门新增', '', '', '', 'sys:dept:add', 2, '', 0, 0, 0, 0, '2022-07-22 22:53:31', '2022-07-22 22:53:31'); 183 | INSERT INTO `admin_sys_menu` VALUES (75, 3, '部门编辑', '', '', '', 'sys:dept:update', 2, '', 0, 0, 0, 0, '2022-07-22 22:53:44', '2022-07-22 22:53:44'); 184 | INSERT INTO `admin_sys_menu` VALUES (76, 3, '部门删除', '', '', '', 'sys:dept:del', 2, '', 0, 0, 0, 0, '2022-07-22 22:53:56', '2022-07-22 22:53:56'); 185 | INSERT INTO `admin_sys_menu` VALUES (79, 6, '删除', '', '', '', 'sys:log:del', 2, '', 0, 0, 0, 0, '2022-07-29 11:59:19', '2022-07-29 11:59:19'); 186 | 187 | -- ---------------------------- 188 | -- Table structure for admin_sys_role 189 | -- ---------------------------- 190 | DROP TABLE IF EXISTS `admin_sys_role`; 191 | CREATE TABLE `admin_sys_role` ( 192 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 193 | `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '角色名称', 194 | `label` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色标签', 195 | `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', 196 | `relevance` tinyint(1) NOT NULL DEFAULT 1 COMMENT '上下级数据权限是否关联0.是 1.否', 197 | `created_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 198 | `updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间', 199 | PRIMARY KEY (`id`) USING BTREE 200 | ) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 201 | 202 | -- ---------------------------- 203 | -- Records of admin_sys_role 204 | -- ---------------------------- 205 | INSERT INTO `admin_sys_role` VALUES (1, '超级管理员', 'admin', '超级管理员', 0, '2022-06-18 10:49:48', '2022-08-09 18:31:37'); 206 | INSERT INTO `admin_sys_role` VALUES (35, '普通角色', 'test', '我只是一个普通的角色', 0, '2022-08-09 18:04:04', '2022-08-09 18:04:04'); 207 | 208 | -- ---------------------------- 209 | -- Table structure for admin_sys_role_dept 210 | -- ---------------------------- 211 | DROP TABLE IF EXISTS `admin_sys_role_dept`; 212 | CREATE TABLE `admin_sys_role_dept` ( 213 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 214 | `role_id` int(11) NOT NULL COMMENT '角色ID', 215 | `dept_id` int(11) NOT NULL COMMENT '部门ID', 216 | PRIMARY KEY (`id`) USING BTREE 217 | ) ENGINE = InnoDB AUTO_INCREMENT = 119 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 218 | 219 | -- ---------------------------- 220 | -- Records of admin_sys_role_dept 221 | -- ---------------------------- 222 | INSERT INTO `admin_sys_role_dept` VALUES (92, 35, 1); 223 | INSERT INTO `admin_sys_role_dept` VALUES (93, 35, 2); 224 | INSERT INTO `admin_sys_role_dept` VALUES (94, 35, 5); 225 | INSERT INTO `admin_sys_role_dept` VALUES (95, 35, 31); 226 | INSERT INTO `admin_sys_role_dept` VALUES (96, 35, 6); 227 | INSERT INTO `admin_sys_role_dept` VALUES (97, 35, 21); 228 | INSERT INTO `admin_sys_role_dept` VALUES (98, 35, 23); 229 | INSERT INTO `admin_sys_role_dept` VALUES (99, 35, 25); 230 | INSERT INTO `admin_sys_role_dept` VALUES (100, 35, 26); 231 | INSERT INTO `admin_sys_role_dept` VALUES (101, 35, 27); 232 | INSERT INTO `admin_sys_role_dept` VALUES (102, 35, 28); 233 | INSERT INTO `admin_sys_role_dept` VALUES (103, 35, 29); 234 | INSERT INTO `admin_sys_role_dept` VALUES (104, 35, 30); 235 | INSERT INTO `admin_sys_role_dept` VALUES (105, 1, 24); 236 | INSERT INTO `admin_sys_role_dept` VALUES (106, 1, 5); 237 | INSERT INTO `admin_sys_role_dept` VALUES (107, 1, 31); 238 | INSERT INTO `admin_sys_role_dept` VALUES (108, 1, 21); 239 | INSERT INTO `admin_sys_role_dept` VALUES (109, 1, 23); 240 | INSERT INTO `admin_sys_role_dept` VALUES (110, 1, 26); 241 | INSERT INTO `admin_sys_role_dept` VALUES (111, 1, 27); 242 | INSERT INTO `admin_sys_role_dept` VALUES (112, 1, 28); 243 | INSERT INTO `admin_sys_role_dept` VALUES (113, 1, 30); 244 | INSERT INTO `admin_sys_role_dept` VALUES (114, 1, 1); 245 | INSERT INTO `admin_sys_role_dept` VALUES (115, 1, 2); 246 | INSERT INTO `admin_sys_role_dept` VALUES (116, 1, 6); 247 | INSERT INTO `admin_sys_role_dept` VALUES (117, 1, 25); 248 | INSERT INTO `admin_sys_role_dept` VALUES (118, 1, 29); 249 | 250 | -- ---------------------------- 251 | -- Table structure for admin_sys_role_menu 252 | -- ---------------------------- 253 | DROP TABLE IF EXISTS `admin_sys_role_menu`; 254 | CREATE TABLE `admin_sys_role_menu` ( 255 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 256 | `role_id` int(11) NOT NULL COMMENT '角色ID', 257 | `menu_id` int(11) NOT NULL COMMENT '菜单ID', 258 | PRIMARY KEY (`id`) USING BTREE 259 | ) ENGINE = InnoDB AUTO_INCREMENT = 3198 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 260 | 261 | -- ---------------------------- 262 | -- Records of admin_sys_role_menu 263 | -- ---------------------------- 264 | INSERT INTO `admin_sys_role_menu` VALUES (3152, 35, 2); 265 | INSERT INTO `admin_sys_role_menu` VALUES (3153, 35, 7); 266 | INSERT INTO `admin_sys_role_menu` VALUES (3154, 35, 1); 267 | INSERT INTO `admin_sys_role_menu` VALUES (3155, 35, 2); 268 | INSERT INTO `admin_sys_role_menu` VALUES (3156, 35, 7); 269 | INSERT INTO `admin_sys_role_menu` VALUES (3157, 35, 1); 270 | INSERT INTO `admin_sys_role_menu` VALUES (3158, 35, 2); 271 | INSERT INTO `admin_sys_role_menu` VALUES (3159, 35, 7); 272 | INSERT INTO `admin_sys_role_menu` VALUES (3160, 35, 1); 273 | INSERT INTO `admin_sys_role_menu` VALUES (3161, 1, 1); 274 | INSERT INTO `admin_sys_role_menu` VALUES (3162, 1, 59); 275 | INSERT INTO `admin_sys_role_menu` VALUES (3163, 1, 60); 276 | INSERT INTO `admin_sys_role_menu` VALUES (3164, 1, 61); 277 | INSERT INTO `admin_sys_role_menu` VALUES (3165, 1, 62); 278 | INSERT INTO `admin_sys_role_menu` VALUES (3166, 1, 63); 279 | INSERT INTO `admin_sys_role_menu` VALUES (3167, 1, 64); 280 | INSERT INTO `admin_sys_role_menu` VALUES (3168, 1, 72); 281 | INSERT INTO `admin_sys_role_menu` VALUES (3169, 1, 73); 282 | INSERT INTO `admin_sys_role_menu` VALUES (3170, 1, 74); 283 | INSERT INTO `admin_sys_role_menu` VALUES (3171, 1, 75); 284 | INSERT INTO `admin_sys_role_menu` VALUES (3172, 1, 76); 285 | INSERT INTO `admin_sys_role_menu` VALUES (3173, 1, 46); 286 | INSERT INTO `admin_sys_role_menu` VALUES (3174, 1, 65); 287 | INSERT INTO `admin_sys_role_menu` VALUES (3175, 1, 66); 288 | INSERT INTO `admin_sys_role_menu` VALUES (3176, 1, 67); 289 | INSERT INTO `admin_sys_role_menu` VALUES (3177, 1, 68); 290 | INSERT INTO `admin_sys_role_menu` VALUES (3178, 1, 69); 291 | INSERT INTO `admin_sys_role_menu` VALUES (3179, 1, 70); 292 | INSERT INTO `admin_sys_role_menu` VALUES (3180, 1, 71); 293 | INSERT INTO `admin_sys_role_menu` VALUES (3181, 1, 79); 294 | INSERT INTO `admin_sys_role_menu` VALUES (3182, 1, 7); 295 | INSERT INTO `admin_sys_role_menu` VALUES (3183, 1, 51); 296 | INSERT INTO `admin_sys_role_menu` VALUES (3184, 1, 52); 297 | INSERT INTO `admin_sys_role_menu` VALUES (3185, 1, 53); 298 | INSERT INTO `admin_sys_role_menu` VALUES (3186, 1, 54); 299 | INSERT INTO `admin_sys_role_menu` VALUES (3187, 1, 55); 300 | INSERT INTO `admin_sys_role_menu` VALUES (3188, 1, 56); 301 | INSERT INTO `admin_sys_role_menu` VALUES (3189, 1, 57); 302 | INSERT INTO `admin_sys_role_menu` VALUES (3190, 1, 58); 303 | INSERT INTO `admin_sys_role_menu` VALUES (3191, 1, 2); 304 | INSERT INTO `admin_sys_role_menu` VALUES (3192, 1, 3); 305 | INSERT INTO `admin_sys_role_menu` VALUES (3193, 1, 5); 306 | INSERT INTO `admin_sys_role_menu` VALUES (3194, 1, 4); 307 | INSERT INTO `admin_sys_role_menu` VALUES (3195, 1, 6); 308 | INSERT INTO `admin_sys_role_menu` VALUES (3196, 1, 8); 309 | INSERT INTO `admin_sys_role_menu` VALUES (3197, 1, 50); 310 | 311 | -- ---------------------------- 312 | -- Table structure for admin_sys_user 313 | -- ---------------------------- 314 | DROP TABLE IF EXISTS `admin_sys_user`; 315 | CREATE TABLE `admin_sys_user` ( 316 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 317 | `dept_id` int(11) NULL DEFAULT NULL COMMENT '部门ID', 318 | `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名', 319 | `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '密码', 320 | `nick_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '未命名' COMMENT '用户名称', 321 | `phone` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 322 | `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', 323 | `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', 324 | `status` tinyint(1) NOT NULL DEFAULT 0 COMMENT '状态0.正常 1.禁用', 325 | `is_deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '软删除0.正常 1.删除', 326 | `created_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 327 | `updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间', 328 | PRIMARY KEY (`id`) USING BTREE 329 | ) ENGINE = InnoDB AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 330 | 331 | -- ---------------------------- 332 | -- Records of admin_sys_user 333 | -- ---------------------------- 334 | INSERT INTO `admin_sys_user` VALUES (1, 1, 'admin', '73d77a08df7a0026706cd4be8317e3cd', '超级管理员', '13938749240', '120547546@qq.com', '', 0, 0, '2022-06-18 10:04:52', '2022-08-09 18:29:21'); 335 | INSERT INTO `admin_sys_user` VALUES (2, 2, 'test', '73D77A08DF7A0026706CD4BE8317E3CD', '普通用户', '13503635596', '', '', 0, 0, '2022-06-18 10:40:02', '2022-07-17 14:39:13'); 336 | 337 | -- ---------------------------- 338 | -- Table structure for admin_sys_user_role 339 | -- ---------------------------- 340 | DROP TABLE IF EXISTS `admin_sys_user_role`; 341 | CREATE TABLE `admin_sys_user_role` ( 342 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 343 | `user_id` int(11) NOT NULL COMMENT '用户ID', 344 | `role_id` int(11) NOT NULL COMMENT '角色ID', 345 | PRIMARY KEY (`id`) USING BTREE 346 | ) ENGINE = InnoDB AUTO_INCREMENT = 747 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 347 | 348 | -- ---------------------------- 349 | -- Records of admin_sys_user_role 350 | -- ---------------------------- 351 | INSERT INTO `admin_sys_user_role` VALUES (743, 1, 1); 352 | INSERT INTO `admin_sys_user_role` VALUES (746, 2, 35); 353 | 354 | -- ---------------------------- 355 | -- Table structure for admin_uploads 356 | -- ---------------------------- 357 | DROP TABLE IF EXISTS `admin_uploads`; 358 | CREATE TABLE `admin_uploads` ( 359 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 360 | `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '文件名', 361 | `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地址', 362 | `type` int(11) NULL DEFAULT NULL COMMENT '分类', 363 | `created_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 364 | `updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间', 365 | PRIMARY KEY (`id`) USING BTREE 366 | ) ENGINE = InnoDB AUTO_INCREMENT = 93 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 367 | 368 | -- ---------------------------- 369 | -- Records of admin_uploads 370 | -- ---------------------------- 371 | INSERT INTO `admin_uploads` VALUES (91, 'logo.png', 'https://admin.seed-app.com/wwwroot/uploads/2022-08-09/db41cb1e-47d0-481c-b928-17165ef9ea6d.png', 2, '2022-08-09 18:29:00', '2022-08-09 18:29:00'); 372 | INSERT INTO `admin_uploads` VALUES (92, '微信截图_20220702182931.png', 'https://admin.seed-app.com/wwwroot/uploads/2022-08-09/5e963439-eb00-4250-8251-a029fb6eb2ea.png', 2, '2022-08-09 18:29:21', '2022-08-09 18:29:21'); 373 | 374 | -- ---------------------------- 375 | -- Table structure for admin_uploads_type 376 | -- ---------------------------- 377 | DROP TABLE IF EXISTS `admin_uploads_type`; 378 | CREATE TABLE `admin_uploads_type` ( 379 | `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键', 380 | `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类名称', 381 | `label` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '分类标识', 382 | `created_time` datetime NULL DEFAULT NULL COMMENT '创建时间', 383 | `updated_time` datetime NULL DEFAULT NULL COMMENT '更新时间', 384 | PRIMARY KEY (`id`) USING BTREE 385 | ) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = DYNAMIC; 386 | 387 | -- ---------------------------- 388 | -- Records of admin_uploads_type 389 | -- ---------------------------- 390 | INSERT INTO `admin_uploads_type` VALUES (1, 'admin端通用上传', 'admin', '2022-07-04 15:17:13', '2022-07-04 15:17:16'); 391 | INSERT INTO `admin_uploads_type` VALUES (2, 'admin头像上传', 'admin_avatar', '2022-08-03 17:09:46', '2022-08-03 17:09:48'); 392 | 393 | SET FOREIGN_KEY_CHECKS = 1; 394 | -------------------------------------------------------------------------------- /utils/cache.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "seed-admin/common" 5 | "strconv" 6 | "strings" 7 | "time" 8 | 9 | "github.com/gookit/goutil/jsonutil" 10 | ) 11 | 12 | // 过期时间 13 | const TTL = time.Hour * 24 14 | 15 | // 前缀 16 | const PATTERN = "auth" 17 | 18 | type AuthCache struct{} 19 | 20 | func (*AuthCache) SetRoleIds(userId int, roleIds []int) { 21 | key := PATTERN + strconv.Itoa(userId) 22 | value, _ := jsonutil.Encode(roleIds) 23 | common.Redis.HMSet(key, map[string]any{ 24 | "roleIds": value, 25 | }).Result() 26 | common.Redis.Expire(key, TTL).Result() 27 | } 28 | func (*AuthCache) GetRoleIds(userId int) ([]int, error) { 29 | key := PATTERN + strconv.Itoa(userId) 30 | // redis里取出权限并解码 31 | roleIds := make([]int, 0) 32 | roleIdsStr, err := common.Redis.HGet(key, "roleIds").Result() 33 | if err != nil { 34 | return nil, err 35 | } 36 | jsonutil.DecodeString(roleIdsStr, &roleIds) 37 | return roleIds, nil 38 | } 39 | 40 | // 设置角色权限 41 | func (*AuthCache) SetRoleLabels(userId int, roleLabels []string) { 42 | key := PATTERN + strconv.Itoa(userId) 43 | value, _ := jsonutil.Encode(roleLabels) 44 | common.Redis.HMSet(key, map[string]any{ 45 | "roleLabels": value, 46 | }).Result() 47 | common.Redis.Expire(key, TTL).Result() 48 | } 49 | func (*AuthCache) GetRoleLabels(userId int) ([]string, error) { 50 | key := PATTERN + strconv.Itoa(userId) 51 | // redis里取出权限并解码 52 | roleLabels := make([]string, 0) 53 | roleIdsStr, err := common.Redis.HGet(key, "roleLabels").Result() 54 | if err != nil { 55 | return nil, err 56 | } 57 | jsonutil.DecodeString(roleIdsStr, &roleLabels) 58 | return roleLabels, nil 59 | } 60 | 61 | // 设置菜单权限 62 | func (*AuthCache) SetMenuPerms(userId int, menuPerms []string) { 63 | key := PATTERN + strconv.Itoa(userId) 64 | value, _ := jsonutil.Encode(menuPerms) 65 | common.Redis.HMSet(key, map[string]any{ 66 | "menuPerms": value, 67 | }).Result() 68 | common.Redis.Expire(key, TTL).Result() 69 | } 70 | func (*AuthCache) GetMenuPerms(userId int) ([]string, error) { 71 | key := PATTERN + strconv.Itoa(userId) 72 | // redis里取出权限并解码 73 | menuPerms := make([]string, 0) 74 | roleIdsStr, err := common.Redis.HGet(key, "menuPerms").Result() 75 | if err != nil { 76 | return nil, err 77 | } 78 | jsonutil.DecodeString(roleIdsStr, &menuPerms) 79 | return menuPerms, nil 80 | } 81 | 82 | func (*AuthCache) Del(userIds []int) error { 83 | keyStrs := make([]string, 0, len(userIds)) 84 | for _, item := range userIds { 85 | keyStrs = append(keyStrs, PATTERN+strconv.Itoa(item)) 86 | } 87 | if _, err := common.Redis.Del(keyStrs...).Result(); err != nil { 88 | return err 89 | } 90 | return nil 91 | } 92 | 93 | // 获取缓存ID 94 | func getIds() ([]int, error) { 95 | // 拿到缓存所有ID 96 | data, err := common.Redis.Keys(PATTERN + "*").Result() 97 | if err != nil { 98 | return nil, err 99 | } 100 | // 去除PATTERN转换成int切片 101 | ids := make([]int, 0, len(data)) 102 | for _, idstr := range data { 103 | idNum, _ := strconv.Atoi(strings.TrimPrefix(idstr, PATTERN)) 104 | ids = append(ids, idNum) 105 | } 106 | return ids, nil 107 | } 108 | 109 | // 更新所有角色权限 110 | func (authCache *AuthCache) UpdateAllRole() error { 111 | ids, err := getIds() 112 | if err != nil { 113 | return err 114 | } 115 | // 查询用户拥有的角色ID和角色label 116 | roleMap := make([]map[string]any, 0) 117 | if err = common.DB.Table("admin_sys_user_role").Alias("ur"). 118 | Join("INNER", []string{"admin_sys_role", "r"}, "ur.role_id = r.id"). 119 | In("user_id", ids).Cols("ur.user_id", "r.id", "r.label"). 120 | Find(&roleMap); err != nil { 121 | return err 122 | } 123 | // 通过userId进行分组 124 | roleIdsMap := map[int][]int{} 125 | roleLabelsMap := map[int][]string{} 126 | for _, role := range roleMap { 127 | key := int(role["user_id"].(int32)) 128 | roleIdsMap[key] = append(roleIdsMap[key], int(role["id"].(int32))) 129 | roleLabelsMap[key] = append(roleLabelsMap[key], role["label"].(string)) 130 | } 131 | // 修改缓存的角色id 132 | delIds := make([]int, 0) 133 | for userId, roleIds := range roleIdsMap { 134 | authCache.SetRoleIds(userId, roleIds) 135 | delIds = SliceDelete(ids, userId) 136 | ids = ids[:len(ids)-1] 137 | } 138 | 139 | authCache.Del(delIds) 140 | // 修改缓存的角色label 141 | for userId, labels := range roleLabelsMap { 142 | authCache.SetRoleLabels(userId, labels) 143 | } 144 | return nil 145 | } 146 | 147 | // 更新所有菜单接口权限 148 | func (authCache *AuthCache) UpdateAllPerm() error { 149 | ids, err := getIds() 150 | if err != nil { 151 | return err 152 | } 153 | // 查询用户拥有的菜单权限 154 | menusMap := make([]map[string]any, 0) 155 | if err = common.DB.Table("admin_sys_menu").Alias("m"). 156 | Join("INNER", []string{"admin_sys_role_menu", "rm"}, "rm.menu_id = m.id"). 157 | Join("INNER", []string{"admin_sys_user_role", "ur"}, "ur.role_id = rm.role_id"). 158 | Where(`m.perms IS NOT NULL AND m.perms != "" AND m.status = 0`). 159 | In("ur.user_id", ids). 160 | Distinct("ur.user_id", "m.perms"). 161 | Find(&menusMap); err != nil { 162 | return err 163 | } 164 | // 通过userId进行分组 165 | permsMap := map[int][]string{} 166 | for _, menu := range menusMap { 167 | key := int(menu["user_id"].(int32)) 168 | permsMap[key] = append(permsMap[key], menu["perms"].(string)) 169 | } 170 | 171 | // 修改缓存的菜单权限 172 | delIds := make([]int, 0) 173 | for userId, perms := range permsMap { 174 | authCache.SetMenuPerms(userId, perms) 175 | delIds = SliceDelete(ids, userId) 176 | ids = ids[:len(ids)-1] 177 | } 178 | authCache.Del(delIds) 179 | return nil 180 | } 181 | -------------------------------------------------------------------------------- /utils/captcha.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "seed-admin/common" 7 | "sync" 8 | 9 | "github.com/dchest/captcha" 10 | "go.uber.org/zap" 11 | ) 12 | 13 | type Captcha struct { 14 | } 15 | 16 | var once sync.Once 17 | var cap *Captcha 18 | 19 | // 单例 20 | func NewCaptcha() *Captcha { 21 | once.Do(func() { 22 | cap = &Captcha{} 23 | }) 24 | return cap 25 | } 26 | 27 | // 验证 28 | func CaptchaVerify(captchaId string, value string) error { 29 | if captcha.VerifyString(captchaId, value) { 30 | return nil 31 | } 32 | return fmt.Errorf("验证码错误") 33 | } 34 | 35 | // 创建一个图片验证码 36 | func (c *Captcha) CreateImage() string { 37 | captchaId := captcha.NewLen(common.CONFIG.Int("captcha.len")) 38 | return captchaId 39 | } 40 | 41 | // 重载验证码 42 | func (c *Captcha) Reload(captchaId string) bool { 43 | return captcha.Reload(captchaId) 44 | } 45 | 46 | // 获取二进制图片 47 | func (c *Captcha) ImageByte(captchaId string) ([]byte, error) { 48 | var content bytes.Buffer 49 | if err := captcha.WriteImage(&content, captchaId, common.CONFIG.Int("captcha.width"), common.CONFIG.Int("captcha.height")); err != nil { 50 | common.LOG.Error("验证码获取失败", zap.String("err", err.Error())) 51 | return nil, err 52 | } 53 | return content.Bytes(), nil 54 | } 55 | -------------------------------------------------------------------------------- /utils/claims.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "seed-admin/common" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | // 从token里解析userId 10 | func GetUserId(ctx *gin.Context) int { 11 | if claims, ok := ctx.Get("claims"); !ok { 12 | common.LOG.Error("解析userId出错,") 13 | return 0 14 | } else { 15 | return claims.(*CustomerClaims).UserId 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /utils/file.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "strings" 7 | ) 8 | 9 | // 获取当前运行路径 10 | func CurrentDirectory() (string, error) { 11 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 12 | if err != nil { 13 | return "", err 14 | } 15 | return strings.Replace(dir, "\\", "/", -1), nil 16 | } 17 | 18 | // 判断目录是否存在 19 | func PathExists(path string) (bool, error) { 20 | _, err := os.Stat(path) 21 | if err == nil { 22 | return true, nil 23 | } 24 | if os.IsNotExist(err) { 25 | return false, nil 26 | } 27 | return false, err 28 | } 29 | 30 | // 检查拓展名 31 | func CheckExtension(path string) string { 32 | for i := len(path) - 1; i > 0; i-- { 33 | if path[i] == '.' { 34 | return path[i+1:] 35 | } 36 | } 37 | return "" 38 | } 39 | -------------------------------------------------------------------------------- /utils/jwt.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "seed-admin/common" 5 | 6 | "github.com/golang-jwt/jwt" 7 | ) 8 | 9 | type Jwt struct { 10 | SigningKey []byte 11 | } 12 | type CustomerClaims struct { 13 | *jwt.StandardClaims //标准字段 14 | UserId int `json:"user_id"` 15 | } 16 | 17 | func NewJwt(key ...[]byte) *Jwt { 18 | if len(key) != 0 { 19 | return &Jwt{key[0]} 20 | } 21 | return &Jwt{[]byte(common.CONFIG.String("jwt.signingKey"))} 22 | } 23 | 24 | // 创建Token 25 | func (j *Jwt) CreateToken(claims jwt.Claims) (string, error) { 26 | token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) 27 | return token.SignedString(j.SigningKey) 28 | } 29 | 30 | // 解析Token 31 | func (j *Jwt) ParseToken(tokenString string) (*CustomerClaims, error) { 32 | token, err := jwt.ParseWithClaims(tokenString, &CustomerClaims{}, func(t *jwt.Token) (any, error) { return j.SigningKey, nil }) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return token.Claims.(*CustomerClaims), nil 37 | } 38 | -------------------------------------------------------------------------------- /utils/md5.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | ) 7 | 8 | // 未加盐的MD5 9 | func Md5(v string) string { 10 | data := md5.Sum([]byte(v)) 11 | return hex.EncodeToString(data[:]) 12 | } 13 | 14 | // 加盐的MD5 15 | func Md5Salt(v, salt string) string { 16 | hash := md5.New() 17 | hash.Write([]byte(v)) 18 | hash.Write([]byte(salt)) 19 | return hex.EncodeToString(hash.Sum(nil)) 20 | } 21 | -------------------------------------------------------------------------------- /utils/slice.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "fmt" 4 | 5 | type cumsum interface { 6 | int | int8 | int16 | int32 | int64 | uint | uint8 | uint16 | uint32 | uint64 | string 7 | } 8 | 9 | // 切片转in()参数字符 10 | func SliceToInStr[T cumsum](arr []T) string { 11 | str := "" 12 | for _, item := range arr { 13 | if len(str) == 0 { 14 | str += fmt.Sprint(item) 15 | } else { 16 | str += "," + fmt.Sprint(item) 17 | } 18 | } 19 | return str 20 | } 21 | 22 | // 查询切片内是否包含某个值 23 | func SliceIncludes[T cumsum](arr []T, formIndex T) bool { 24 | is := false 25 | for _, item := range arr { 26 | if item == formIndex { 27 | is = true 28 | } 29 | } 30 | return is 31 | } 32 | 33 | // 切片删除 34 | func SliceDelete[T cumsum](arr []T, elem T) []T { 35 | j := 0 36 | for _, item := range arr { 37 | if item != elem { 38 | arr[j] = item 39 | j++ 40 | } 41 | } 42 | return arr[:j] 43 | } 44 | -------------------------------------------------------------------------------- /utils/validator.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/gookit/validate" 5 | "github.com/gookit/validate/locales/zhcn" 6 | ) 7 | 8 | // 验证数据 9 | func ParamsVerify(st any) error { 10 | // 中文输出注册到全局 如有需要可按照RegisterGlobal自定义msg信息 11 | zhcn.RegisterGlobal() 12 | v := validate.New(st) 13 | if !v.Validate() { 14 | return v.Errors.OneError() 15 | } 16 | return nil 17 | } 18 | --------------------------------------------------------------------------------