diff --git a/apps/system/api/api.go b/apps/system/api/api.go index 471c3a5..0f98a3a 100644 --- a/apps/system/api/api.go +++ b/apps/system/api/api.go @@ -7,6 +7,7 @@ import ( "log" "pandax/apps/system/entity" "pandax/apps/system/services" + "pandax/pkg/global" "strconv" ) @@ -121,5 +122,6 @@ func (s *SystemApiApi) GetAllApis(rc *ginx.ReqCtx) { func (s *SystemApiApi) GetPolicyPathByRoleId(rc *ginx.ReqCtx) { roleKey := rc.GinCtx.Query("roleKey") tenantId := strconv.Itoa(int(rc.LoginAccount.TenantId)) - rc.ResData = casbin.GetPolicyPathByRoleId(tenantId, roleKey) + ca := casbin.CasbinS{ModelPath: global.Conf.Casbin.ModelPath} + rc.ResData = ca.GetPolicyPathByRoleId(tenantId, roleKey) } diff --git a/apps/system/api/role.go b/apps/system/api/role.go index 72b5249..4a9318c 100644 --- a/apps/system/api/role.go +++ b/apps/system/api/role.go @@ -88,7 +88,8 @@ func (r *RoleApi) InsertRole(rc *ginx.ReqCtx) { r.RoleMenuApp.Insert(insert.RoleId, role.MenuIds) //添加权限 tenantId := strconv.Itoa(int(rc.LoginAccount.TenantId)) - casbin.UpdateCasbin(tenantId, role.RoleKey, role.ApiIds) + ca := casbin.CasbinS{ModelPath: global.Conf.Casbin.ModelPath} + ca.UpdateCasbin(tenantId, role.RoleKey, role.ApiIds) } // @Summary 修改用户角色 @@ -112,7 +113,8 @@ func (r *RoleApi) UpdateRole(rc *ginx.ReqCtx) { r.RoleMenuApp.Insert(role.RoleId, role.MenuIds) //修改api权限 tenantId := strconv.Itoa(int(rc.LoginAccount.TenantId)) - casbin.UpdateCasbin(tenantId, role.RoleKey, role.ApiIds) + ca := casbin.CasbinS{ModelPath: global.Conf.Casbin.ModelPath} + ca.UpdateCasbin(tenantId, role.RoleKey, role.ApiIds) } // @Summary 修改用户角色状态 @@ -176,7 +178,8 @@ func (r *RoleApi) DeleteRole(rc *ginx.ReqCtx) { if len(*list) == 0 { delList = append(delList, rid) //删除角色绑定api - casbin.ClearCasbin(0, role.RoleKey) + ca := casbin.CasbinS{ModelPath: global.Conf.Casbin.ModelPath} + ca.ClearCasbin(0, role.RoleKey) } else { global.Log.Info(fmt.Sprintf("role:%d 存在用户无法删除", rid)) } diff --git a/apps/system/api/system.go b/apps/system/api/system.go index af8a742..857c5c2 100644 --- a/apps/system/api/system.go +++ b/apps/system/api/system.go @@ -9,6 +9,7 @@ import ( "github.com/gorilla/websocket" "github.com/kakuilan/kgo" "net/http" + "pandax/pkg/middleware" "runtime" ) @@ -72,7 +73,7 @@ func (s *System) ConnectWs(g *gin.Context) { } // 权限校验 rc := ginx.NewReqCtx(g) - if err = ginx.PermissionHandler(rc); err != nil { + if err = middleware.PermissionHandler(rc); err != nil { panic(any(biz.NewBizErr("没有权限"))) } diff --git a/apps/system/services/api.go b/apps/system/services/api.go index 3d57db2..48d64f4 100644 --- a/apps/system/services/api.go +++ b/apps/system/services/api.go @@ -107,7 +107,8 @@ func (m *sysApiModelImpl) Update(api entity.SysApi) *entity.SysApi { biz.IsTrue(errors.Is(err, gorm.ErrRecordNotFound), "存在相同api路径") } // 异常直接抛错误 - casbin.UpdateCasbinApi(oldA.Path, api.Path, oldA.Method, api.Method) + ca := casbin.CasbinS{ModelPath: global.Conf.Casbin.ModelPath} + ca.UpdateCasbinApi(oldA.Path, api.Path, oldA.Method, api.Method) err = global.Db.Table(m.table).Model(&api).Updates(&api).Error biz.ErrIsNil(err, "修改api信息失败") return &api diff --git a/main.go b/main.go index fa2abc8..c2ce272 100644 --- a/main.go +++ b/main.go @@ -1,16 +1,17 @@ package main import ( - "github.com/XM-GO/PandaKit/config" "github.com/XM-GO/PandaKit/ginx" "github.com/XM-GO/PandaKit/logger" - "github.com/XM-GO/PandaKit/starter" + gStarter "github.com/XM-GO/PandaKit/starter" "github.com/spf13/cobra" "os" "pandax/apps/job/jobs" + "pandax/pkg/config" "pandax/pkg/global" "pandax/pkg/initialize" "pandax/pkg/middleware" + "pandax/pkg/starter" ) var ( @@ -23,8 +24,20 @@ var rootCmd = &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { if configFile != "" { global.Conf = config.InitConfig(configFile) - global.Log = logger.InitLog(global.Conf.Log) - global.Db = starter.GormInit(global.Conf.Server.DbType) + global.Log = logger.InitLog(global.Conf.Log.File.GetFilename(), global.Conf.Log.Level) + + dbGorm := gStarter.DbGorm{Type: global.Conf.Server.DbType} + if dbGorm.Type == "mysql" { + dbGorm.Dsn = global.Conf.Mysql.Dsn() + dbGorm.MaxIdleConns = global.Conf.Mysql.MaxIdleConns + dbGorm.MaxOpenConns = global.Conf.Mysql.MaxOpenConns + } else { + dbGorm.Dsn = global.Conf.Postgresql.PgDsn() + dbGorm.MaxIdleConns = global.Conf.Postgresql.MaxIdleConns + dbGorm.MaxOpenConns = global.Conf.Postgresql.MaxOpenConns + } + global.Db = dbGorm.GormInit() + initialize.InitTable() } else { global.Log.Panic("请配置config") @@ -33,9 +46,9 @@ var rootCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { ginx.UseAfterHandlerInterceptor(middleware.OperationHandler) // gin前置 函数 - ginx.UseBeforeHandlerInterceptor(ginx.PermissionHandler) + ginx.UseBeforeHandlerInterceptor(middleware.PermissionHandler) // gin后置 函数 - ginx.UseAfterHandlerInterceptor(ginx.LogHandler) + ginx.UseAfterHandlerInterceptor(middleware.LogHandler) go func() { // 启动系统调度任务 jobs.InitJob() diff --git a/pkg/config/app.go b/pkg/config/app.go new file mode 100644 index 0000000..bef8b38 --- /dev/null +++ b/pkg/config/app.go @@ -0,0 +1,12 @@ +package config + +import "fmt" + +type App struct { + Name string `yaml:"name"` + Version string `yaml:"version"` +} + +func (a *App) GetAppInfo() string { + return fmt.Sprintf("[%s:%s]", a.Name, a.Version) +} diff --git a/pkg/config/casbin.go b/pkg/config/casbin.go new file mode 100644 index 0000000..4b5b196 --- /dev/null +++ b/pkg/config/casbin.go @@ -0,0 +1,5 @@ +package config + +type Casbin struct { + ModelPath string `mapstructure:"model-path" json:"model-path" yaml:"model-path"` +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..8dedb78 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,65 @@ +package config + +import ( + "flag" + "fmt" + "github.com/XM-GO/PandaKit/biz" + "github.com/XM-GO/PandaKit/utils" + "path/filepath" +) + +var Conf *Config + +func InitConfig(configFilePath string) *Config { + // 获取启动参数中,配置文件的绝对路径 + path, _ := filepath.Abs(configFilePath) + startConfigParam = &CmdConfigParam{ConfigFilePath: path} + // 读取配置文件信息 + yc := &Config{} + if err := utils.LoadYml(startConfigParam.ConfigFilePath, yc); err != nil { + panic(any(fmt.Sprintf("读取配置文件[%s]失败: %s", startConfigParam.ConfigFilePath, err.Error()))) + } + // 校验配置文件内容信息 + yc.Valid() + + Conf = yc + return yc + +} + +// 启动配置参数 +type CmdConfigParam struct { + ConfigFilePath string // -e 配置文件路径 +} + +// 启动可执行文件时的参数 +var startConfigParam *CmdConfigParam + +// yaml配置文件映射对象 +type Config struct { + App *App `yaml:"app"` + Server *Server `yaml:"server"` + Jwt *Jwt `yaml:"jwt"` + Redis *Redis `yaml:"redis"` + Mysql *Mysql `yaml:"mysql"` + Postgresql *Postgresql `yaml:"postgresql"` + Casbin *Casbin `yaml:"casbin"` + Gen *Gen `yaml:"gen"` + Log *Log `yaml:"log"` +} + +// 配置文件内容校验 +func (c *Config) Valid() { + biz.IsTrue(c.Jwt != nil, "配置文件的[jwt]信息不能为空") + c.Jwt.Valid() +} + +// 获取执行可执行文件时,指定的启动参数 +func getStartConfig() *CmdConfigParam { + configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录") + flag.Parse() + // 获取配置文件绝对路径 + path, _ := filepath.Abs(*configFilePath) + sc := &CmdConfigParam{ConfigFilePath: path} + return sc +} diff --git a/pkg/config/db.go b/pkg/config/db.go new file mode 100644 index 0000000..c2a2a26 --- /dev/null +++ b/pkg/config/db.go @@ -0,0 +1,33 @@ +package config + +import "fmt" + +type Mysql struct { + Host string `mapstructure:"host" json:"host" yaml:"host"` + Config string `mapstructure:"config" json:"config" yaml:"config"` + Dbname string `mapstructure:"db-name" json:"dbname" yaml:"db-name"` + Username string `mapstructure:"username" json:"username" yaml:"username"` + Password string `mapstructure:"password" json:"password" yaml:"password"` + MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` + MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` + LogMode bool `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"` + LogZap string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"` +} + +func (m *Mysql) Dsn() string { + return m.Username + ":" + m.Password + "@tcp(" + m.Host + ")/" + m.Dbname + "?" + m.Config +} + +type Postgresql struct { + Host string `mapstructure:"host" json:"host" yaml:"host"` + Port int `mapstructure:"port" json:"port" yaml:"port"` + Dbname string `mapstructure:"db-name" json:"dbname" yaml:"db-name"` + Username string `mapstructure:"username" json:"username" yaml:"username"` + Password string `mapstructure:"password" json:"password" yaml:"password"` + MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` + MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` +} + +func (m *Postgresql) PgDsn() string { + return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", m.Host, m.Port, m.Username, m.Password, m.Dbname) +} diff --git a/pkg/config/gen.go b/pkg/config/gen.go new file mode 100644 index 0000000..fc6468c --- /dev/null +++ b/pkg/config/gen.go @@ -0,0 +1,11 @@ +package config + +/** + * @Description + * @Author Panda + * @Date 2021/12/31 15:13 + **/ +type Gen struct { + Dbname string `mapstructure:"dbname" json:"dbname" yaml:"dbname"` + Frontpath string `mapstructure:"frontpath" json:"frontpath" yaml:"frontpath"` +} diff --git a/pkg/config/jwt.go b/pkg/config/jwt.go new file mode 100644 index 0000000..c13b546 --- /dev/null +++ b/pkg/config/jwt.go @@ -0,0 +1,15 @@ +package config + +import ( + "github.com/XM-GO/PandaKit/biz" +) + +type Jwt struct { + Key string `yaml:"key"` + ExpireTime int64 `yaml:"expire-time"` // 过期时间,单位分钟 +} + +func (j *Jwt) Valid() { + biz.IsTrue(j.Key != "", "config.yml之 [jwt.key] 不能为空") + biz.IsTrue(j.ExpireTime != 0, "config.yml之 [jwt.expire-time] 不能为空") +} diff --git a/pkg/config/log.go b/pkg/config/log.go new file mode 100644 index 0000000..e2de254 --- /dev/null +++ b/pkg/config/log.go @@ -0,0 +1,33 @@ +package config + +import "path" + +type Log struct { + Level string `yaml:"level"` + File *LogFile `yaml:"file"` +} + +type LogFile struct { + Name string `yaml:"name"` + Path string `yaml:"path"` +} + +// 获取完整路径文件名 +func (l *LogFile) GetFilename() string { + var filepath, filename string + if l == nil { + return "" + } + if fp := l.Path; fp == "" { + filepath = "./" + } else { + filepath = fp + } + if fn := l.Name; fn == "" { + filename = "default.log" + } else { + filename = fn + } + + return path.Join(filepath, filename) +} diff --git a/pkg/config/redis.go b/pkg/config/redis.go new file mode 100644 index 0000000..10316a4 --- /dev/null +++ b/pkg/config/redis.go @@ -0,0 +1,8 @@ +package config + +type Redis struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + Password string `yaml:"password"` + Db int `yaml:"db"` +} diff --git a/pkg/config/server.go b/pkg/config/server.go new file mode 100644 index 0000000..2424a61 --- /dev/null +++ b/pkg/config/server.go @@ -0,0 +1,41 @@ +package config + +import "fmt" + +type Server struct { + Port int `yaml:"port"` + Model string `yaml:"model"` + Cors bool `yaml:"cors"` + Rate *Rate `yaml:"rate"` + IsInitTable bool `yaml:"isInitTable"` + DbType string `yaml:"db-type"` + ExcelDir string `yaml:"excel-dir"` + Tls *Tls `yaml:"tls"` + Static *[]*Static `yaml:"static"` + StaticFile *[]*StaticFile `yaml:"static-file"` +} + +func (s *Server) GetPort() string { + return fmt.Sprintf(":%d", s.Port) +} + +type Static struct { + RelativePath string `yaml:"relative-path"` + Root string `yaml:"root"` +} + +type StaticFile struct { + RelativePath string `yaml:"relative-path"` + Filepath string `yaml:"filepath"` +} + +type Tls struct { + Enable bool `yaml:"enable"` // 是否启用tls + KeyFile string `yaml:"key-file"` // 私钥文件路径 + CertFile string `yaml:"cert-file"` // 证书文件路径 +} + +type Rate struct { + Enable bool `yaml:"enable"` // 是否限流 + RateNum float64 `yaml:"rate-num"` // 限流数量 +} diff --git a/pkg/global/global.go b/pkg/global/global.go index 4cb1ff5..eeee04c 100644 --- a/pkg/global/global.go +++ b/pkg/global/global.go @@ -1,9 +1,9 @@ package global import ( - "github.com/XM-GO/PandaKit/config" "github.com/sirupsen/logrus" "gorm.io/gorm" + "pandax/pkg/config" ) var ( diff --git a/pkg/middleware/log.go b/pkg/middleware/log.go new file mode 100644 index 0000000..34cc274 --- /dev/null +++ b/pkg/middleware/log.go @@ -0,0 +1,70 @@ +package middleware + +import ( + "encoding/json" + "fmt" + "github.com/XM-GO/PandaKit/biz" + "github.com/XM-GO/PandaKit/ginx" + "github.com/XM-GO/PandaKit/logger" + "github.com/XM-GO/PandaKit/utils" + "github.com/sirupsen/logrus" + "reflect" + "runtime/debug" +) + +func LogHandler(rc *ginx.ReqCtx) error { + li := rc.LogInfo + if li == nil { + return nil + } + + lfs := logrus.Fields{} + if la := rc.LoginAccount; la != nil { + lfs["uid"] = la.UserId + lfs["uname"] = la.UserName + } + + req := rc.GinCtx.Request + lfs[req.Method] = req.URL.Path + + if err := rc.Err; err != nil { + logger.Log.WithFields(lfs).Error(getErrMsg(rc, err)) + return nil + } + logger.Log.WithFields(lfs).Info(getLogMsg(rc)) + return nil +} + +func getLogMsg(rc *ginx.ReqCtx) string { + msg := rc.LogInfo.Description + fmt.Sprintf(" ->%dms", rc.Timed) + if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) { + rb, _ := json.Marshal(rc.ReqParam) + msg = msg + fmt.Sprintf("\n--> %s", string(rb)) + } + + // 返回结果不为空,则记录返回结果 + if rc.LogInfo.LogResp && !utils.IsBlank(reflect.ValueOf(rc.ResData)) { + respB, _ := json.Marshal(rc.ResData) + msg = msg + fmt.Sprintf("\n<-- %s", string(respB)) + } + return msg +} + +func getErrMsg(rc *ginx.ReqCtx, err any) string { + msg := rc.LogInfo.Description + if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) { + rb, _ := json.Marshal(rc.ReqParam) + msg = msg + fmt.Sprintf("\n--> %s", string(rb)) + } + + var errMsg string + switch t := err.(type) { + case *biz.BizError: + errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error()) + case error: + errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t.Error(), string(debug.Stack())) + case string: + errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t, string(debug.Stack())) + } + return (msg + errMsg) +} diff --git a/pkg/middleware/permission.go b/pkg/middleware/permission.go new file mode 100644 index 0000000..7882382 --- /dev/null +++ b/pkg/middleware/permission.go @@ -0,0 +1,47 @@ +package middleware + +import ( + "github.com/XM-GO/PandaKit/biz" + "github.com/XM-GO/PandaKit/casbin" + "github.com/XM-GO/PandaKit/ginx" + "github.com/XM-GO/PandaKit/token" + "github.com/dgrijalva/jwt-go" + "pandax/pkg/global" + "strconv" +) + +func PermissionHandler(rc *ginx.ReqCtx) error { + permission := rc.RequiredPermission + // 如果需要的权限信息不为空,并且不需要token,则不返回错误,继续后续逻辑 + if permission != nil && !permission.NeedToken { + return nil + } + tokenStr := rc.GinCtx.Request.Header.Get("X-TOKEN") + // header不存在则从查询参数token中获取 + if tokenStr == "" { + tokenStr = rc.GinCtx.Query("token") + } + if tokenStr == "" { + return biz.PermissionErr + } + j := token.NewJWT("", []byte("PandaX"), jwt.SigningMethodHS256) + loginAccount, err := j.ParseToken(tokenStr) + if err != nil || loginAccount == nil { + return biz.PermissionErr + } + rc.LoginAccount = loginAccount + + if !permission.NeedCasbin { + return nil + } + ca := casbin.CasbinS{ModelPath: global.Conf.Casbin.ModelPath} + e := ca.Casbin() + // 判断策略中是否存在 + tenantId := strconv.Itoa(int(rc.LoginAccount.TenantId)) + success, err := e.Enforce(tenantId, loginAccount.RoleKey, rc.GinCtx.Request.URL.Path, rc.GinCtx.Request.Method) + if !success { + return biz.CasbinErr + } + + return nil +} diff --git a/pkg/starter/http_server.go b/pkg/starter/http_server.go new file mode 100644 index 0000000..ac08ac2 --- /dev/null +++ b/pkg/starter/http_server.go @@ -0,0 +1,22 @@ +package starter + +import ( + "github.com/gin-gonic/gin" + "pandax/pkg/global" +) + +func RunWebServer(web *gin.Engine) { + server := global.Conf.Server + port := server.GetPort() + if app := global.Conf.App; app != nil { + global.Log.Infof("%s- Listening and serving HTTP on %s", app.GetAppInfo(), port) + } else { + global.Log.Infof("Listening and serving HTTP on %s", port) + } + + if server.Tls != nil && server.Tls.Enable { + web.RunTLS(port, server.Tls.CertFile, server.Tls.KeyFile) + } else { + web.Run(port) + } +}