├── .gitignore ├── main.go ├── app ├── api │ ├── routers │ │ ├── user.go │ │ └── index.go │ └── controllers │ │ └── user.go ├── admin │ ├── entity │ │ ├── admin_sys_role_dept.go │ │ ├── admin_sys_role_menu.go │ │ ├── admin_sys_user_role.go │ │ ├── admin_uploads_type.go │ │ ├── admin_uploads.go │ │ ├── admin_sys_dept.go │ │ ├── admin_sys_dict_type.go │ │ ├── admin_sys_dict_data.go │ │ ├── admin_sys_role.go │ │ ├── admin_sys_log.go │ │ ├── admin_sys_user.go │ │ └── admin_sys_menu.go │ ├── response │ │ ├── dept.go │ │ ├── role.go │ │ ├── log.go │ │ ├── menu.go │ │ ├── server.go │ │ └── user.go │ ├── routers │ │ ├── common.go │ │ ├── server.go │ │ ├── log.go │ │ ├── dept.go │ │ ├── role.go │ │ ├── menu.go │ │ ├── index.go │ │ ├── dict.go │ │ └── user.go │ ├── request │ │ ├── log.go │ │ ├── dept.go │ │ ├── menu.go │ │ ├── role.go │ │ ├── dict.go │ │ └── user.go │ ├── controllers │ │ ├── server.go │ │ ├── common.go │ │ ├── log.go │ │ ├── dept.go │ │ ├── role.go │ │ ├── menu.go │ │ ├── dict.go │ │ └── user.go │ └── services │ │ ├── log.go │ │ ├── common.go │ │ ├── dept.go │ │ ├── server.go │ │ ├── dict.go │ │ ├── menu.go │ │ ├── role.go │ │ └── user.go └── index.go ├── core ├── middlewares │ ├── recovery.go │ ├── middlewares.go │ ├── cors.go │ └── logger.go ├── zap │ ├── lumberjack │ │ ├── chown.go │ │ ├── chown_linux.go │ │ └── lumberjack.go │ └── zap.go ├── redis │ └── redis.go ├── index.go ├── config │ └── config.go └── xorm │ └── xorm.go ├── common ├── common.go ├── middlewares │ ├── jwt.go │ └── record.go ├── response.go └── auth │ └── auth.go ├── utils ├── claims.go ├── validator.go ├── md5.go ├── file.go ├── slice.go ├── jwt.go ├── captcha.go └── cache.go ├── Dockerfile ├── LICENSE ├── config.toml ├── go.mod ├── README.md └── sql └── seed-admin.sql /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .vscode 4 | *.local 5 | /log/* -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "seed-admin/core" 4 | 5 | func main() { 6 | core.Run() 7 | } 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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_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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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_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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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_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/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/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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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"] -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
4 |
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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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