diff --git a/features/cm/param.go b/features/cm/param.go index d273661..74089cd 100644 --- a/features/cm/param.go +++ b/features/cm/param.go @@ -12,6 +12,7 @@ import ( "io" "net/http" + tokenConst "ems.agt/src/framework/constants/token" "github.com/go-resty/resty/v2" "github.com/gorilla/mux" ) @@ -43,7 +44,7 @@ func GetParamConfigFromNF(w http.ResponseWriter, r *http.Request) { } restHostPort := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port) - getNeInfoPattern := fmt.Sprintf(config.UriPrefix+"/databaseManagement/v1/%s/ne_info", config.GetYamlConfig().Database.Name) + getNeInfoPattern := fmt.Sprintf(config.DefaultUriPrefix+"/databaseManagement/v1/%s/ne_info", config.GetYamlConfig().Database.Name) getNeInfoURI := restHostPort + getNeInfoPattern neId := services.GetUriParamString(r, "ne_id", ",", true, false) if neId == "" { @@ -56,6 +57,7 @@ func GetParamConfigFromNF(w http.ResponseWriter, r *http.Request) { client := resty.New() resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). diff --git a/features/firewall/api_firewall.go b/features/firewall/api_firewall.go index 136f3b6..e35caa6 100644 --- a/features/firewall/api_firewall.go +++ b/features/firewall/api_firewall.go @@ -75,7 +75,7 @@ func (s *FirewallApi) Rule(w http.ResponseWriter, r *http.Request) { var body model.RuleQuerys err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Type == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } data, err := s.firewallService.RulePage(body) diff --git a/features/mml/mml.go b/features/mml/mml.go index 045d055..229aee0 100644 --- a/features/mml/mml.go +++ b/features/mml/mml.go @@ -16,9 +16,8 @@ import ( "ems.agt/lib/services" "ems.agt/restagent/config" + tokenConst "ems.agt/src/framework/constants/token" "github.com/gorilla/mux" - - _ "github.com/go-sql-driver/mysql" ) const ( @@ -153,7 +152,7 @@ func PostMMLToNF(w http.ResponseWriter, r *http.Request) { for _, mml := range mmlRequest.MML { mmlCommand := fmt.Sprintf("%s\n", mml) log.Debug("mml command:", mmlCommand) - n, err = conn.Write([]byte(mmlCommand)) + _, err = conn.Write([]byte(mmlCommand)) if err != nil { log.Errorf("Error: %s", err.Error()) return @@ -211,14 +210,15 @@ func PostMMLToOMC(w http.ResponseWriter, r *http.Request) { hostUri := fmt.Sprintf("http://%s:%s", neInfo.Ip, neInfo.Port) omcMmlVar := &mmlp.MmlVar{ - Version: "16.1.1", - Output: mmlp.DefaultFormatType, - MmlHome: config.GetYamlConfig().MML.MmlHome, - Limit: 50, - User: "", - SessionToken: token, - HttpUri: hostUri, - UserAgent: config.GetDefaultUserAgent(), + Version: "16.1.1", + Output: mmlp.DefaultFormatType, + MmlHome: config.GetYamlConfig().MML.MmlHome, + Limit: 50, + User: "", + SessionToken: token, // 旧token + Authorization: r.Header.Get(tokenConst.HEADER_KEY), // 请求Token + HttpUri: hostUri, + UserAgent: config.GetDefaultUserAgent(), } mmlRequest := new(MMLRequest) _ = json.Unmarshal(body, mmlRequest) diff --git a/features/security/account.go b/features/security/account.go index 36a36ea..d8c7d3a 100644 --- a/features/security/account.go +++ b/features/security/account.go @@ -14,7 +14,6 @@ import ( sysConfigService "ems.agt/features/sys_config/service" "ems.agt/lib/core/account" "ems.agt/lib/core/cache" - "ems.agt/lib/core/conf" "ems.agt/lib/core/constants/cachekey" "ems.agt/lib/core/utils/ctx" "ems.agt/lib/core/vo/result" @@ -24,7 +23,8 @@ import ( "ems.agt/lib/oauth" "ems.agt/lib/services" "ems.agt/restagent/config" - "github.com/go-admin-team/go-admin-core/logger" + srcConfig "ems.agt/src/framework/config" + "ems.agt/src/framework/redis" "github.com/mojocn/base64Captcha" ) @@ -136,9 +136,10 @@ func LoginFromOMC(w http.ResponseWriter, r *http.Request) { if user != nil { // 缓存用户信息 account.CacheLoginUser(user) + redis.SetByExpire("", "session_token", token, time.Second*1800) // 角色权限集合,管理员拥有所有权限 userId := fmt.Sprint(user.Id) - isAdmin := conf.IsAdmin(userId) + isAdmin := srcConfig.IsAdmin(userId) roles, perms := service.NewServiceAccount.RoleAndMenuPerms(userId, isAdmin) services.ResponseStatusOK200LoginWhitRP(w, token, user, roles, perms) return @@ -237,13 +238,13 @@ func LoginOMC(w http.ResponseWriter, r *http.Request) { err := ctx.ShouldBindJSON(r, &body) if err != nil { log.Error("Invalid Json Format") - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // response 400-5 if body.Username == "" || body.Password == "" { log.Error("Wrong parameter value") - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -257,20 +258,20 @@ func LoginOMC(w http.ResponseWriter, r *http.Request) { if captchaEnabled { if body.Code == "" || body.UUID == "" { log.Error("Authentication failed, mismatch captcha") - ctx.JSON(w, 400, result.CodeMsg(400, "验证码信息错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "Verification code information error")) return } verifyKey := cachekey.CAPTCHA_CODE_KEY + body.UUID captcha, ok := cache.GetLocalTTL(verifyKey) if captcha == nil || !ok { log.Error("Authentication failed, captcha emtry") - ctx.JSON(w, 400, result.CodeMsg(400, "验证码已失效")) + ctx.JSON(w, 400, result.CodeMsg(400, "The verification code has expired")) return } cache.DeleteLocalTTL(verifyKey) if captcha.(string) != body.Code { log.Error("Authentication failed, not match captcha") - ctx.JSON(w, 400, result.CodeMsg(400, "验证码错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "Verification code error")) return } } @@ -363,7 +364,7 @@ func CaptchaImage(w http.ResponseWriter, r *http.Request) { // 验证码表达式解析输出 item, err := driverCaptcha.DrawCaptcha(question) if err != nil { - logger.Infof("Generate Id Question Answer %s : %v", question, err) + log.Infof("Generate Id Question Answer %s : %v", question, err) } else { data["uuid"] = id data["img"] = item.EncodeB64string() @@ -388,7 +389,7 @@ func UserInfo(w http.ResponseWriter, r *http.Request) { } // 角色权限集合,管理员拥有所有权限 userId := fmt.Sprint(loginUser.UserID) - isAdmin := conf.IsAdmin(userId) + isAdmin := srcConfig.IsAdmin(userId) roles, perms := service.NewServiceAccount.RoleAndMenuPerms(userId, isAdmin) ctx.JSON(w, 200, result.OkData(map[string]any{ @@ -403,7 +404,7 @@ func Routers(w http.ResponseWriter, r *http.Request) { userID := ctx.LoginUserToUserID(r) // 前端路由,管理员拥有所有 - isAdmin := conf.IsAdmin(userID) + isAdmin := srcConfig.IsAdmin(userID) buildMenus := service.NewServiceAccount.RouteMenus(userID, isAdmin) ctx.JSON(w, 200, result.OkData(buildMenus)) } diff --git a/features/sm/backup.go b/features/sm/backup.go index ffb7051..5000618 100644 --- a/features/sm/backup.go +++ b/features/sm/backup.go @@ -3,15 +3,51 @@ package sm import ( "database/sql" "fmt" + "net/http" "os" "os/exec" "time" "ems.agt/lib/log" + "ems.agt/lib/services" "ems.agt/restagent/config" _ "github.com/go-sql-driver/mysql" ) +var ( + // Get OMC local time + UriOMCLocalTime = config.DefaultUriPrefix + "/systemManagement/{apiVersion}/elementType/OMC/objectType/time" + + CustomUriOMCLocalTime = config.UriPrefix + "/systemManagement/{apiVersion}/elementType/OMC/objectType/time" +) + +type OMCLocalTime struct { + Timestamp int64 `json:"timestamp"` // 时间戳 (单位:毫秒) + TimeZone int `json:"timeZone"` // 本地时区偏移(单位:秒) +} + +func GetOMCLocalTime(w http.ResponseWriter, r *http.Request) { + log.Debug("GetOMCLocalTime processing... ") + + _, err := services.CheckFrontValidRequest(w, r) + if err != nil { + log.Error("Http request error:", err) + return + } + + t := time.Now() + _, offset := t.Zone() + + localTime := OMCLocalTime{ + Timestamp: t.UnixMilli(), + TimeZone: offset, + } + response := services.DataResponse{ + Data: localTime, + } + services.ResponseWithJson(w, http.StatusOK, response) +} + var dbConfig = config.GetYamlConfig().Database func DatabaseWhoreBackup() { diff --git a/features/state/getstate.go b/features/state/getstate.go index bcffc13..1b1b2a3 100644 --- a/features/state/getstate.go +++ b/features/state/getstate.go @@ -17,6 +17,7 @@ import ( "ems.agt/lib/log" "ems.agt/lib/services" "ems.agt/restagent/config" + tokenConst "ems.agt/src/framework/constants/token" ) type CpuUsage struct { @@ -238,6 +239,7 @@ func GetOneLicenseInfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -342,6 +344,7 @@ func GetAllLicenseInfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -461,6 +464,7 @@ func GetOneSysinfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -594,6 +598,7 @@ func GetAllSysinfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -704,13 +709,14 @@ func GetStateFromNF(w http.ResponseWriter, r *http.Request) { // query all NFs // create rest client restHostPort := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port) - getNeInfoPattern := fmt.Sprintf(config.UriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info", + getNeInfoPattern := fmt.Sprintf(config.DefaultUriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info", config.GetYamlConfig().Database.Name) getNeInfoURI := restHostPort + getNeInfoPattern + "?WHERE=status='0'" log.Debug("getNeInfoPattern:", getNeInfoPattern) resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"AccessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -724,7 +730,7 @@ func GetStateFromNF(w http.ResponseWriter, r *http.Request) { neList, _ = dborm.XormParseResult(resp.Body()) default: restHostPort := fmt.Sprintf("http://127.0.0.1:%d", config.GetYamlConfig().Rest[0].Port) - getNeInfoPattern := fmt.Sprintf(config.UriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info", + getNeInfoPattern := fmt.Sprintf(config.DefaultUriPrefix+"/databaseManagement/v1/elementType/%s/objectType/ne_info", config.GetYamlConfig().Database.Name) getNeInfoURI := restHostPort + getNeInfoPattern neId := services.GetUriParamString(r, "ne_id", ",", true, false) @@ -737,6 +743,7 @@ func GetStateFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -767,6 +774,7 @@ func GetStateFromNF(w http.ResponseWriter, r *http.Request) { result["ipAddress"] = ne.Ip resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). diff --git a/features/sys_config/api_sys_config.go b/features/sys_config/api_sys_config.go index 37019f9..c3eae1f 100644 --- a/features/sys_config/api_sys_config.go +++ b/features/sys_config/api_sys_config.go @@ -105,7 +105,7 @@ func (s *SysConfigApi) List(w http.ResponseWriter, r *http.Request) { func (s *SysConfigApi) Info(w http.ResponseWriter, r *http.Request) { configId := ctx.Param(r, "configId") if configId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } data := s.sysConfigService.SelectConfigById(configId) @@ -123,14 +123,14 @@ func (s *SysConfigApi) Add(w http.ResponseWriter, r *http.Request) { var body model.SysConfig err := ctx.ShouldBindJSON(r, &body) if err != nil || body.ConfigID != "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查属性值唯一 uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, "") if !uniqueConfigKey { - msg := fmt.Sprintf("参数配置新增【%s】失败,参数键名已存在", body.ConfigKey) + msg := fmt.Sprintf("[%s] Parameter key name already exists", body.ConfigKey) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -151,14 +151,14 @@ func (s *SysConfigApi) Edit(w http.ResponseWriter, r *http.Request) { var body model.SysConfig err := ctx.ShouldBindJSON(r, &body) if err != nil || body.ConfigID == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查属性值唯一 uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, body.ConfigID) if !uniqueConfigKey { - msg := fmt.Sprintf("参数配置修改【%s】失败,参数键名已存在", body.ConfigKey) + msg := fmt.Sprintf("[%s] Parameter key name already exists", body.ConfigKey) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -166,7 +166,7 @@ func (s *SysConfigApi) Edit(w http.ResponseWriter, r *http.Request) { // 检查是否存在 config := s.sysConfigService.SelectConfigById(body.ConfigID) if config.ConfigID != body.ConfigID { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问参数配置数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access parameter configuration data!")) return } @@ -185,7 +185,7 @@ func (s *SysConfigApi) Edit(w http.ResponseWriter, r *http.Request) { func (s *SysConfigApi) Remove(w http.ResponseWriter, r *http.Request) { configIds := ctx.Param(r, "configIds") if configIds == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 处理字符转id数组后去重 @@ -218,7 +218,7 @@ func (s *SysConfigApi) RefreshCache(w http.ResponseWriter, r *http.Request) { func (s *SysConfigApi) ConfigKey(w http.ResponseWriter, r *http.Request) { configKey := ctx.Param(r, "configKey") if configKey == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } key := s.sysConfigService.SelectConfigValueByKey(configKey) diff --git a/features/sys_dict_data/api_sys_dict_data.go b/features/sys_dict_data/api_sys_dict_data.go index 05dbb08..128c236 100644 --- a/features/sys_dict_data/api_sys_dict_data.go +++ b/features/sys_dict_data/api_sys_dict_data.go @@ -103,7 +103,7 @@ func (s *SysDictDataApi) List(w http.ResponseWriter, r *http.Request) { func (s *SysDictDataApi) Info(w http.ResponseWriter, r *http.Request) { dictCode := ctx.Param(r, "dictCode") if dictCode == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } data := s.sysDictDataService.SelectDictDataByCode(dictCode) @@ -121,21 +121,21 @@ func (s *SysDictDataApi) Add(w http.ResponseWriter, r *http.Request) { var body model.SysDictData err := ctx.ShouldBindJSON(r, &body) if err != nil || body.DictCode != "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查字典类型是否存在 sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType) if sysDictType.DictType != body.DictType { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典类型数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access dictionary type data!")) return } // 检查字典标签唯一 uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, "") if !uniqueDictLabel { - msg := fmt.Sprintf("数据新增【%s】失败,该字典类型下标签名已存在", body.DictLabel) + msg := fmt.Sprintf("[%s] The subscript signature of this dictionary type already exists", body.DictLabel) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -143,7 +143,7 @@ func (s *SysDictDataApi) Add(w http.ResponseWriter, r *http.Request) { // 检查字典键值唯一 uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, "") if !uniqueDictValue { - msg := fmt.Sprintf("数据新增【%s】失败,该字典类型下标签值已存在", body.DictValue) + msg := fmt.Sprintf("[%s] The label value under this dictionary type already exists", body.DictValue) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -164,28 +164,28 @@ func (s *SysDictDataApi) Edit(w http.ResponseWriter, r *http.Request) { var body model.SysDictData err := ctx.ShouldBindJSON(r, &body) if err != nil || body.DictCode == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查字典类型是否存在 sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType) if sysDictType.DictType != body.DictType { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典类型数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access dictionary type data!")) return } // 检查字典编码是否存在 SysDictDataApi := s.sysDictDataService.SelectDictDataByCode(body.DictCode) if SysDictDataApi.DictCode != body.DictCode { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典编码数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access dictionary encoding data!")) return } // 检查字典标签唯一 uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, body.DictCode) if !uniqueDictLabel { - msg := fmt.Sprintf("数据修改【%s】失败,该字典类型下标签名已存在", body.DictLabel) + msg := fmt.Sprintf("Data modification failed for [%s], the dictionary type subscript signature already exists", body.DictLabel) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -193,7 +193,7 @@ func (s *SysDictDataApi) Edit(w http.ResponseWriter, r *http.Request) { // 检查字典键值唯一 uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, body.DictCode) if !uniqueDictValue { - msg := fmt.Sprintf("数据修改【%s】失败,该字典类型下标签值已存在", body.DictValue) + msg := fmt.Sprintf("Data modification failed for [%s], label value already exists under this dictionary type", body.DictValue) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -213,7 +213,7 @@ func (s *SysDictDataApi) Edit(w http.ResponseWriter, r *http.Request) { func (s *SysDictDataApi) Remove(w http.ResponseWriter, r *http.Request) { dictCodes := ctx.Param(r, "dictCodes") if dictCodes == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 处理字符转id数组后去重 @@ -228,7 +228,7 @@ func (s *SysDictDataApi) Remove(w http.ResponseWriter, r *http.Request) { ctx.JSON(w, 200, result.ErrMsg(err.Error())) return } - msg := fmt.Sprintf("删除成功:%d", rows) + msg := fmt.Sprintf("Successfully deleted: %d", rows) ctx.JSON(w, 200, result.OkMsg(msg)) } @@ -238,7 +238,7 @@ func (s *SysDictDataApi) Remove(w http.ResponseWriter, r *http.Request) { func (s *SysDictDataApi) DictType(w http.ResponseWriter, r *http.Request) { dictType := ctx.Param(r, "dictType") if dictType == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } diff --git a/features/sys_dict_type/api_sys_dict_type.go b/features/sys_dict_type/api_sys_dict_type.go index 480ff01..d3f8cdd 100644 --- a/features/sys_dict_type/api_sys_dict_type.go +++ b/features/sys_dict_type/api_sys_dict_type.go @@ -105,7 +105,7 @@ func (s *SysDictTypeApi) List(w http.ResponseWriter, r *http.Request) { func (s *SysDictTypeApi) Info(w http.ResponseWriter, r *http.Request) { dictId := ctx.Param(r, "dictId") if dictId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } data := s.sysDictTypeService.SelectDictTypeByID(dictId) @@ -123,14 +123,14 @@ func (s *SysDictTypeApi) Add(w http.ResponseWriter, r *http.Request) { var body model.SysDictType err := ctx.ShouldBindJSON(r, &body) if err != nil || body.DictID != "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查字典名称唯一 uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, "") if !uniqueDictName { - msg := fmt.Sprintf("字典新增【%s】失败,字典名称已存在", body.DictName) + msg := fmt.Sprintf("Failed to add dictionary '%s', dictionary name already exists", body.DictName) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -138,7 +138,7 @@ func (s *SysDictTypeApi) Add(w http.ResponseWriter, r *http.Request) { // 检查字典类型唯一 uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, "") if !uniqueDictType { - msg := fmt.Sprintf("字典新增【%s】失败,字典类型已存在", body.DictType) + msg := fmt.Sprintf("Failed to add dictionary '%s', dictionary type already exists", body.DictType) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -159,21 +159,21 @@ func (s *SysDictTypeApi) Edit(w http.ResponseWriter, r *http.Request) { var body model.SysDictType err := ctx.ShouldBindJSON(r, &body) if err != nil || body.DictID == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查数据是否存在 dictInfo := s.sysDictTypeService.SelectDictTypeByID(body.DictID) if dictInfo.DictID != body.DictID { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典类型数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access dictionary type data!")) return } // 检查字典名称唯一 uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, body.DictID) if !uniqueDictName { - msg := fmt.Sprintf("字典修改【%s】失败,字典名称已存在", body.DictName) + msg := fmt.Sprintf("Dictionary modification failed for [%s], dictionary name already exists", body.DictName) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -181,7 +181,7 @@ func (s *SysDictTypeApi) Edit(w http.ResponseWriter, r *http.Request) { // 检查字典类型唯一 uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, body.DictID) if !uniqueDictType { - msg := fmt.Sprintf("字典修改【%s】失败,字典类型已存在", body.DictType) + msg := fmt.Sprintf("Dictionary modification failed for [%s], dictionary type already exists", body.DictType) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -201,7 +201,7 @@ func (s *SysDictTypeApi) Edit(w http.ResponseWriter, r *http.Request) { func (s *SysDictTypeApi) Remove(w http.ResponseWriter, r *http.Request) { dictIds := ctx.Param(r, "dictIds") if dictIds == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 处理字符转id数组后去重 @@ -216,7 +216,7 @@ func (s *SysDictTypeApi) Remove(w http.ResponseWriter, r *http.Request) { ctx.JSON(w, 200, result.ErrMsg(err.Error())) return } - msg := fmt.Sprintf("删除成功:%d", rows) + msg := fmt.Sprintf("Successfully deleted: %d", rows) ctx.JSON(w, 200, result.OkMsg(msg)) } diff --git a/features/sys_menu/api_sys_menu.go b/features/sys_menu/api_sys_menu.go index a78e0d8..e7f644b 100644 --- a/features/sys_menu/api_sys_menu.go +++ b/features/sys_menu/api_sys_menu.go @@ -7,13 +7,13 @@ import ( "ems.agt/features/sys_menu/consts" "ems.agt/features/sys_menu/model" "ems.agt/features/sys_menu/service" - "ems.agt/lib/core/conf" "ems.agt/lib/core/utils/ctx" "ems.agt/lib/core/utils/regular" "ems.agt/lib/core/vo/result" "ems.agt/lib/midware" "ems.agt/lib/services" "ems.agt/restagent/config" + srcConfig "ems.agt/src/framework/config" ) // 菜单接口添加到路由 @@ -109,7 +109,7 @@ func (s *SysMenuApi) List(w http.ResponseWriter, r *http.Request) { } userId := ctx.LoginUserToUserID(r) - if conf.IsAdmin(userId) { + if srcConfig.IsAdmin(userId) { userId = "*" } data := s.sysMenuService.SelectMenuList(query, userId) @@ -122,7 +122,7 @@ func (s *SysMenuApi) List(w http.ResponseWriter, r *http.Request) { func (s *SysMenuApi) Info(w http.ResponseWriter, r *http.Request) { menuId := ctx.Param(r, "menuId") if menuId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } data := s.sysMenuService.SelectMenuById(menuId) @@ -140,7 +140,7 @@ func (s *SysMenuApi) Add(w http.ResponseWriter, r *http.Request) { var body model.SysMenu err := ctx.ShouldBindJSON(r, &body) if err != nil || body.MenuID != "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -185,13 +185,13 @@ func (s *SysMenuApi) Edit(w http.ResponseWriter, r *http.Request) { var body model.SysMenu err := ctx.ShouldBindJSON(r, &body) if err != nil || body.MenuID == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 上级菜单不能选自己 if body.MenuID == body.ParentID { - msg := fmt.Sprintf("菜单修改【%s】失败,上级菜单不能选择自己", body.MenuName) + msg := fmt.Sprintf("Menu modification failed for [%s], parent menu cannot select itself", body.MenuName) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -199,19 +199,19 @@ func (s *SysMenuApi) Edit(w http.ResponseWriter, r *http.Request) { // 检查数据是否存在 menuInfo := s.sysMenuService.SelectMenuById(body.MenuID) if menuInfo.MenuID != body.MenuID { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问菜单数据")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access menu data")) return } // 父级ID不为0是要检查 if body.ParentID != "0" { menuParent := s.sysMenuService.SelectMenuById(body.ParentID) if menuParent.MenuID != body.ParentID { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问菜单数据")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access menu data")) return } // 禁用菜单时检查父菜单是否使用 if body.Status == "1" && menuParent.Status == "0" { - ctx.JSON(w, 200, result.ErrMsg("父菜单未启用!")) + ctx.JSON(w, 200, result.ErrMsg("Parent menu not enabled!")) return } } @@ -266,21 +266,21 @@ func (s *SysMenuApi) Edit(w http.ResponseWriter, r *http.Request) { func (s *SysMenuApi) Remove(w http.ResponseWriter, r *http.Request) { menuId := ctx.Param(r, "menuId") if menuId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查数据是否存在 menu := s.sysMenuService.SelectMenuById(menuId) if menu.MenuID != menuId { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问菜单数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access menu data!")) return } // 检查是否存在子菜单 hasChild := s.sysMenuService.HasChildByMenuIdAndStatus(menuId, "") if hasChild > 0 { - msg := fmt.Sprintf("不允许删除,存在子菜单数:%d", hasChild) + msg := fmt.Sprintf("Deletion not allowed, there are sub orders: %d", hasChild) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -288,14 +288,14 @@ func (s *SysMenuApi) Remove(w http.ResponseWriter, r *http.Request) { // 检查是否分配给角色 existRole := s.sysMenuService.CheckMenuExistRole(menuId) if existRole > 0 { - msg := fmt.Sprintf("不允许删除,菜单已分配给角色数:%d", existRole) + msg := fmt.Sprintf("Deletion not allowed, menu already assigned to roles: %d", existRole) ctx.JSON(w, 200, result.ErrMsg(msg)) return } rows := s.sysMenuService.DeleteMenuById(menuId) if rows > 0 { - msg := fmt.Sprintf("删除成功:%d", rows) + msg := fmt.Sprintf("Successfully deleted: %d", rows) ctx.JSON(w, 200, result.OkMsg(msg)) return } @@ -315,7 +315,7 @@ func (s *SysMenuApi) TreeSelect(w http.ResponseWriter, r *http.Request) { } userId := ctx.LoginUserToUserID(r) - if conf.IsAdmin(userId) { + if srcConfig.IsAdmin(userId) { userId = "*" } data := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId) @@ -329,7 +329,7 @@ func (s *SysMenuApi) TreeSelect(w http.ResponseWriter, r *http.Request) { func (s *SysMenuApi) RoleMenuTreeSelect(w http.ResponseWriter, r *http.Request) { roleId := ctx.Param(r, "roleId") if roleId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -342,7 +342,7 @@ func (s *SysMenuApi) RoleMenuTreeSelect(w http.ResponseWriter, r *http.Request) } userId := ctx.LoginUserToUserID(r) - if conf.IsAdmin(userId) { + if srcConfig.IsAdmin(userId) { userId = "*" } menuTreeSelect := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId) diff --git a/features/sys_role/api_sys_role.go b/features/sys_role/api_sys_role.go index 5ba0582..57a11c0 100644 --- a/features/sys_role/api_sys_role.go +++ b/features/sys_role/api_sys_role.go @@ -121,7 +121,7 @@ func (s *SysRoleApi) List(w http.ResponseWriter, r *http.Request) { func (s *SysRoleApi) Info(w http.ResponseWriter, r *http.Request) { roleId := ctx.Param(r, "roleId") if roleId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } data := s.sysRoleService.SelectRoleById(roleId) @@ -139,14 +139,14 @@ func (s *SysRoleApi) Add(w http.ResponseWriter, r *http.Request) { var body model.SysRole err := ctx.ShouldBindJSON(r, &body) if err != nil || body.RoleID != "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 判断角色名称是否唯一 uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, "") if !uniqueRoleName { - msg := fmt.Sprintf("角色新增【%s】失败,角色名称已存在", body.RoleName) + msg := fmt.Sprintf("[%s] Role name already exists", body.RoleName) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -154,7 +154,7 @@ func (s *SysRoleApi) Add(w http.ResponseWriter, r *http.Request) { // 判断角色键值是否唯一 uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, "") if !uniqueRoleKey { - msg := fmt.Sprintf("角色新增【%s】失败,角色键值已存在", body.RoleName) + msg := fmt.Sprintf("[%s] The role key value already exists", body.RoleName) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -175,27 +175,27 @@ func (s *SysRoleApi) Edit(w http.ResponseWriter, r *http.Request) { var body model.SysRole err := ctx.ShouldBindJSON(r, &body) if err != nil || body.RoleID == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查是否管理员角色 if body.RoleID == "1" { - ctx.JSON(w, 200, result.ErrMsg("不允许操作管理员角色")) + ctx.JSON(w, 200, result.ErrMsg("Operation of administrator role is not allowed")) return } // 检查是否存在 role := s.sysRoleService.SelectRoleById(body.RoleID) if role.RoleID != body.RoleID { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问角色数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access role data!")) return } // 判断角色名称是否唯一 uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, body.RoleID) if !uniqueRoleName { - msg := fmt.Sprintf("角色修改【%s】失败,角色名称已存在", body.RoleName) + msg := fmt.Sprintf("[%s] Role name already exists", body.RoleName) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -203,7 +203,7 @@ func (s *SysRoleApi) Edit(w http.ResponseWriter, r *http.Request) { // 判断角色键值是否唯一 uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, body.RoleID) if !uniqueRoleKey { - msg := fmt.Sprintf("角色修改【%s】失败,角色键值已存在", body.RoleName) + msg := fmt.Sprintf("[%s] The role key value already exists", body.RoleName) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -223,7 +223,7 @@ func (s *SysRoleApi) Edit(w http.ResponseWriter, r *http.Request) { func (s *SysRoleApi) Remove(w http.ResponseWriter, r *http.Request) { roleIds := ctx.Param(r, "roleIds") if roleIds == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 处理字符转id数组后去重 @@ -236,7 +236,7 @@ func (s *SysRoleApi) Remove(w http.ResponseWriter, r *http.Request) { // 检查是否管理员角色 for _, id := range uniqueIDs { if id == "1" { - ctx.JSON(w, 200, result.ErrMsg("不允许操作管理员角色")) + ctx.JSON(w, 200, result.ErrMsg("Operation of administrator role is not allowed")) return } } @@ -245,7 +245,7 @@ func (s *SysRoleApi) Remove(w http.ResponseWriter, r *http.Request) { ctx.JSON(w, 200, result.ErrMsg(err.Error())) return } - msg := fmt.Sprintf("删除成功:%d", rows) + msg := fmt.Sprintf("Successfully deleted: %d", rows) ctx.JSON(w, 200, result.OkMsg(msg)) } @@ -261,26 +261,26 @@ func (s *SysRoleApi) Status(w http.ResponseWriter, r *http.Request) { } err := ctx.ShouldBindJSON(r, &body) if err != nil { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查是否管理员角色 if body.RoleID == "1" { - ctx.JSON(w, 200, result.ErrMsg("不允许操作管理员角色")) + ctx.JSON(w, 200, result.ErrMsg("Operation of administrator role is not allowed")) return } // 检查是否存在 role := s.sysRoleService.SelectRoleById(body.RoleID) if role.RoleID != body.RoleID { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问角色数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access role data!")) return } // 与旧值相等不变更 if role.Status == body.Status { - ctx.JSON(w, 200, result.ErrMsg("变更状态与旧值相等!")) + ctx.JSON(w, 200, result.ErrMsg("Change status equals old value!")) return } @@ -306,14 +306,14 @@ func (s *SysRoleApi) AuthUserAllocatedList(w http.ResponseWriter, r *http.Reques querys := ctx.QueryMap(r) roleId, ok := querys["roleId"] if !ok || roleId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查是否存在 role := s.sysRoleService.SelectRoleById(roleId.(string)) if role.RoleID != roleId { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问角色数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access role data!")) return } @@ -335,7 +335,7 @@ func (s *SysRoleApi) AuthUserChecked(w http.ResponseWriter, r *http.Request) { } err := ctx.ShouldBindJSON(r, &body) if err != nil { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -350,7 +350,7 @@ func (s *SysRoleApi) AuthUserChecked(w http.ResponseWriter, r *http.Request) { // 检查是否存在 role := s.sysRoleService.SelectRoleById(body.RoleID) if role.RoleID != body.RoleID { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问角色数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access role data!")) return } diff --git a/features/sys_user/api_sys_user.go b/features/sys_user/api_sys_user.go index b16a5b5..93018c4 100644 --- a/features/sys_user/api_sys_user.go +++ b/features/sys_user/api_sys_user.go @@ -9,13 +9,13 @@ import ( sysRoleService "ems.agt/features/sys_role/service" sysUserModel "ems.agt/features/sys_user/model" "ems.agt/features/sys_user/service" - "ems.agt/lib/core/conf" "ems.agt/lib/core/utils/ctx" "ems.agt/lib/core/utils/parse" "ems.agt/lib/core/vo/result" "ems.agt/lib/midware" "ems.agt/lib/services" "ems.agt/restagent/config" + srcConfig "ems.agt/src/framework/config" ) // 用户接口添加到路由 @@ -107,14 +107,14 @@ func (s *SysUserApi) List(w http.ResponseWriter, r *http.Request) { func (s *SysUserApi) Info(w http.ResponseWriter, r *http.Request) { userId := ctx.Param(r, "userId") if userId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 查询系统角色列表 roles := s.sysRoleService.SelectRoleList(sysRoleModel.SysRole{}) // 不是系统指定管理员需要排除其角色 - if !conf.IsAdmin(userId) { + if !srcConfig.IsAdmin(userId) { rolesFilter := make([]sysRoleModel.SysRole, 0) for _, r := range roles { if r.RoleID != "1" { @@ -137,7 +137,7 @@ func (s *SysUserApi) Info(w http.ResponseWriter, r *http.Request) { // 检查用户是否存在 user := s.sysUserService.SelectUserById(userId) if user.Id != userId { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问用户数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!")) return } @@ -161,14 +161,14 @@ func (s *SysUserApi) Add(w http.ResponseWriter, r *http.Request) { var body sysUserModel.SysUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Id != "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查用户登录账号是否唯一 uniqueUserName := s.sysUserService.CheckUniqueUserName(body.AccountId, "") if !uniqueUserName { - msg := fmt.Sprintf("新增用户【%s】失败,登录账号已存在", body.AccountId) + msg := fmt.Sprintf("[%s] Login account already exists", body.AccountId) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -188,26 +188,26 @@ func (s *SysUserApi) Edit(w http.ResponseWriter, r *http.Request) { var body sysUserModel.SysUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Id == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查是否管理员用户 - // if conf.IsAdmin(body.Id) { + // if srcConfig.IsAdmin(body.Id) { // ctx.JSON(w, 200, result.ErrMsg("不允许操作管理员用户")) // return // } user := s.sysUserService.SelectUserById(body.Id) if user.Id != body.Id { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问用户数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!")) return } // 检查用户登录账号是否唯一 uniqueUserName := s.sysUserService.CheckUniqueUserName(body.AccountId, body.Id) if !uniqueUserName { - msg := fmt.Sprintf("修改用户【%s】失败,登录账号已存在", body.AccountId) + msg := fmt.Sprintf("[%s] Login account already exists", body.AccountId) ctx.JSON(w, 200, result.ErrMsg(msg)) return } @@ -228,7 +228,7 @@ func (s *SysUserApi) Edit(w http.ResponseWriter, r *http.Request) { func (s *SysUserApi) Remove(w http.ResponseWriter, r *http.Request) { userIds := ctx.Param(r, "userIds") if userIds == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 处理字符转id数组后去重 @@ -243,7 +243,7 @@ func (s *SysUserApi) Remove(w http.ResponseWriter, r *http.Request) { ctx.JSON(w, 200, result.ErrMsg(err.Error())) return } - msg := fmt.Sprintf("删除成功:%d", rows) + msg := fmt.Sprintf("Successfully deleted: %d", rows) ctx.JSON(w, 200, result.OkMsg(msg)) } @@ -256,19 +256,19 @@ func (s *SysUserApi) ResetPwd(w http.ResponseWriter, r *http.Request) { Password string `json:"password" binding:"required"` } if err := ctx.ShouldBindJSON(r, &body); err != nil { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查是否管理员用户 - if conf.IsAdmin(body.UserID) { - ctx.JSON(w, 200, result.ErrMsg("不允许操作管理员用户")) + if srcConfig.IsAdmin(body.UserID) { + ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!")) return } user := s.sysUserService.SelectUserById(body.UserID) if user.Id != body.UserID { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问用户数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!")) return } @@ -293,20 +293,20 @@ func (s *SysUserApi) Status(w http.ResponseWriter, r *http.Request) { Status string `json:"status" binding:"required"` } if err := ctx.ShouldBindJSON(r, &body); err != nil { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } // 检查是否存在 user := s.sysUserService.SelectUserById(body.UserID) if user.Id != body.UserID { - ctx.JSON(w, 200, result.ErrMsg("没有权限访问用户数据!")) + ctx.JSON(w, 200, result.ErrMsg("No permission to access user data!")) return } // 与旧值相等不变更 if user.Status == body.Status { - ctx.JSON(w, 200, result.ErrMsg("变更状态与旧值相等!")) + ctx.JSON(w, 200, result.ErrMsg("Change status equals old value!")) return } diff --git a/features/sys_user/service/repo_sys_user.go b/features/sys_user/service/repo_sys_user.go index 622b7e6..93c7781 100644 --- a/features/sys_user/service/repo_sys_user.go +++ b/features/sys_user/service/repo_sys_user.go @@ -8,10 +8,10 @@ import ( sysRoleModel "ems.agt/features/sys_role/model" sysUserModel "ems.agt/features/sys_user/model" "ems.agt/lib/core/datasource" - "ems.agt/lib/core/utils/crypto" "ems.agt/lib/core/utils/date" "ems.agt/lib/core/utils/parse" "ems.agt/lib/log" + "ems.agt/src/framework/utils/crypto" ) // 实例化数据层 RepoSysUser 结构体 diff --git a/features/trace/tcpdump.go b/features/trace/tcpdump.go index e3d4c4f..ccab0b5 100644 --- a/features/trace/tcpdump.go +++ b/features/trace/tcpdump.go @@ -51,7 +51,7 @@ func TcpdumpNeTask(w http.ResponseWriter, r *http.Request) { } err := ctx.ShouldBindJSON(r, &body) if err != nil || body.NeType == "" || body.NeId == "" || body.Timeout < 5 || body.Cmd == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -89,7 +89,7 @@ func TcpdumpPcapDownload(w http.ResponseWriter, r *http.Request) { } err := ctx.ShouldBindJSON(r, &body) if err != nil || body.NeType == "" || body.NeId == "" || body.FileName == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -120,7 +120,7 @@ func TcpdumpNeUPFTask(w http.ResponseWriter, r *http.Request) { } err := ctx.ShouldBindJSON(r, &body) if err != nil || body.NeType != "UPF" || body.NeId == "" || body.Cmd == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } diff --git a/features/udm_user/api_udm_user.go b/features/udm_user/api_udm_user.go index e034acb..29e4f5a 100644 --- a/features/udm_user/api_udm_user.go +++ b/features/udm_user/api_udm_user.go @@ -228,7 +228,7 @@ func (s *UdmUserApi) UdmAuthUserList(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmAuthUserSave(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -244,7 +244,7 @@ func (s *UdmUserApi) UdmAuthUserInfo(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") imsi := ctx.Param(r, "imsi") if neId == "" || imsi == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -290,14 +290,14 @@ func (s *UdmUserApi) UdmAuthUserInfo(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmAuthUserAdd(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } var body model.UdmAuthUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Imsi == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -331,14 +331,14 @@ func (s *UdmUserApi) UdmAuthUserAdds(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") num := ctx.Param(r, "num") if neId == "" || num == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } var body model.UdmAuthUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Imsi == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -371,14 +371,14 @@ func (s *UdmUserApi) UdmAuthUserAdds(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmAuthUserEdit(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } var body model.UdmAuthUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Imsi == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -425,7 +425,7 @@ func (s *UdmUserApi) UdmAuthUserRemove(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") imsi := ctx.Param(r, "imsi") if neId == "" || imsi == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -460,7 +460,7 @@ func (s *UdmUserApi) UdmAuthUserRemoves(w http.ResponseWriter, r *http.Request) imsi := ctx.Param(r, "imsi") num := ctx.Param(r, "num") if neId == "" || imsi == "" || num == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -497,16 +497,16 @@ func (s *UdmUserApi) UdmAuthUserExport(w http.ResponseWriter, r *http.Request) { } err := ctx.ShouldBindJSON(r, &body) if err != nil || body.NeId == "" || body.Type == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } if !(body.Type == "csv" || body.Type == "txt") { - ctx.JSON(w, 200, result.ErrMsg("导出文件类型支持csv、txt")) + ctx.JSON(w, 200, result.ErrMsg("Export file types support CSV and txt")) return } - neId := "-" + neId := "" list := s.authUser.List(model.UdmAuthUser{NeID: neId}) // 文件名 fileName := fmt.Sprintf("OMC_AUTH_USER_EXPORT_%s_%d.%s", neId, time.Now().UnixMilli(), body.Type) @@ -554,7 +554,7 @@ func (s *UdmUserApi) UdmAuthUserExport(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmAuthUserImport(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -565,7 +565,7 @@ func (s *UdmUserApi) UdmAuthUserImport(w http.ResponseWriter, r *http.Request) { return } if !(strings.HasSuffix(fileHeader.Filename, ".csv") || strings.HasSuffix(fileHeader.Filename, ".txt")) { - ctx.JSON(w, 200, result.ErrMsg("请上传.csv、.txt格式文件,内容字段imsi,ki,algo,amf,opc")) + ctx.JSON(w, 200, result.ErrMsg("Please upload files in. csv and. txt formats with content fields: imsi,ki,algo,amf,opc")) return } @@ -634,7 +634,7 @@ func (s *UdmUserApi) UdmSubUserList(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmSubUserSave(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -650,7 +650,7 @@ func (s *UdmUserApi) UdmSubUserInfo(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") imsi := ctx.Param(r, "imsi") if neId == "" || imsi == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -714,14 +714,14 @@ func (s *UdmUserApi) UdmSubUserInfo(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmSubUserAdd(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } var body model.UdmSubUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Imsi == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -760,14 +760,14 @@ func (s *UdmUserApi) UdmSubUserAdds(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") num := ctx.Param(r, "num") if neId == "" || num == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } var body model.UdmSubUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Imsi == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -805,14 +805,14 @@ func (s *UdmUserApi) UdmSubUserAdds(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmSubUserAdd4G(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } var body model.UdmSubUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Imsi == "" || body.SubNum == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -845,14 +845,14 @@ func (s *UdmUserApi) UdmSubUserAdd4G(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmSubUserEdit(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } var body model.UdmSubUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Imsi == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -940,14 +940,14 @@ func (s *UdmUserApi) UdmSubUserEdit(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmSubUser4GIP(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } var body model.UdmSubUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Imsi == "" || body.SubNum == "" || body.StaticIp == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -979,14 +979,14 @@ func (s *UdmUserApi) UdmSubUser4GIP(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmSubUserSmData(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } var body model.UdmSubUser err := ctx.ShouldBindJSON(r, &body) if err != nil || body.Imsi == "" || body.SubNum == "" || body.SmData == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -1020,7 +1020,7 @@ func (s *UdmUserApi) UdmSubUserRemove(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") imsi := ctx.Param(r, "imsi") if neId == "" || imsi == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -1055,7 +1055,7 @@ func (s *UdmUserApi) UdmSubUserRemoves(w http.ResponseWriter, r *http.Request) { imsi := ctx.Param(r, "imsi") num := ctx.Param(r, "num") if neId == "" || imsi == "" || num == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -1092,16 +1092,16 @@ func (s *UdmUserApi) UdmSubUserExport(w http.ResponseWriter, r *http.Request) { } err := ctx.ShouldBindJSON(r, &body) if err != nil || body.NeId == "" || body.Type == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } if !(body.Type == "csv" || body.Type == "txt") { - ctx.JSON(w, 200, result.ErrMsg("导出文件类型支持csv、txt")) + ctx.JSON(w, 200, result.ErrMsg("Export file type support csv、txt")) return } - neId := "-" + neId := "" list := s.subUser.List(model.UdmSubUser{NeID: neId}) // 文件名 fileName := fmt.Sprintf("OMC_AUTH_USER_EXPORT_%s_%d.%s", neId, time.Now().UnixMilli(), body.Type) @@ -1145,7 +1145,7 @@ func (s *UdmUserApi) UdmSubUserExport(w http.ResponseWriter, r *http.Request) { func (s *UdmUserApi) UdmSubUserImport(w http.ResponseWriter, r *http.Request) { neId := ctx.Param(r, "neId") if neId == "" { - ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + ctx.JSON(w, 400, result.CodeMsg(400, "parameter error")) return } @@ -1156,7 +1156,7 @@ func (s *UdmUserApi) UdmSubUserImport(w http.ResponseWriter, r *http.Request) { return } if !(strings.HasSuffix(fileHeader.Filename, ".csv") || strings.HasSuffix(fileHeader.Filename, ".txt")) { - ctx.JSON(w, 200, result.ErrMsg("请上传.csv、.txt格式文件,内容字段imsi,msisdn,ambr,nssai,arfb,sar,rat,cn,smf_sel,sm_dat,eps_dat")) + ctx.JSON(w, 200, result.ErrMsg("Please upload files in. csv and. txt formats with content fields: imsi,msisdn,ambr,nssai,arfb,sar,rat,cn,smf_sel,sm_dat,eps_dat")) return } diff --git a/features/udm_user/service/service_redis_data.go b/features/udm_user/service/service_redis_data.go index 1f208d2..f1c691e 100644 --- a/features/udm_user/service/service_redis_data.go +++ b/features/udm_user/service/service_redis_data.go @@ -4,7 +4,7 @@ import ( "strings" "ems.agt/features/udm_user/model" - "ems.agt/lib/core/redis" + "ems.agt/src/framework/redis" ) // phoneImsiList 获取所有imsi diff --git a/features/ue/ue.go b/features/ue/ue.go index 15013f8..7f77458 100644 --- a/features/ue/ue.go +++ b/features/ue/ue.go @@ -11,6 +11,7 @@ import ( "ems.agt/lib/log" "ems.agt/lib/services" "ems.agt/restagent/config" + tokenConst "ems.agt/src/framework/constants/token" "github.com/go-resty/resty/v2" "github.com/gorilla/mux" ) @@ -129,6 +130,7 @@ func GetUEInfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -192,6 +194,7 @@ func GetUENumFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -255,6 +258,7 @@ func GetNBInfoFromNF(w http.ResponseWriter, r *http.Request) { resp, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: r.Header.Get(tokenConst.HEADER_KEY)}). SetHeaders(map[string]string{"accessToken": token}). SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). diff --git a/go.mod b/go.mod index 130fb00..cee2625 100644 --- a/go.mod +++ b/go.mod @@ -4,13 +4,18 @@ go 1.20 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/go-admin-team/go-admin-core v1.3.12-0.20221121065133-27b7dbe27a8f + github.com/dlclark/regexp2 v1.10.0 + github.com/gin-gonic/gin v1.9.1 github.com/go-resty/resty/v2 v2.7.0 github.com/go-sql-driver/mysql v1.7.1 + github.com/golang-jwt/jwt/v5 v5.0.0 github.com/gorilla/mux v1.8.0 github.com/gorilla/websocket v1.5.0 github.com/gosnmp/gosnmp v1.35.0 github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f + github.com/matoous/go-nanoid/v2 v2.0.0 + github.com/mojocn/base64Captcha v1.3.5 + github.com/mssola/user_agent v0.6.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/redis/go-redis/v9 v9.1.0 github.com/robfig/cron/v3 v3.0.1 @@ -18,30 +23,38 @@ require ( github.com/shirou/gopsutil/v3 v3.23.7 github.com/spf13/afero v1.9.5 github.com/spf13/viper v1.16.0 + github.com/xuri/excelize/v2 v2.7.1 golang.org/x/crypto v0.12.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/yaml.v3 v3.0.1 + gorm.io/driver/mysql v1.5.1 + gorm.io/gorm v1.25.2 xorm.io/xorm v1.3.2 ) require ( - github.com/mattn/go-sqlite3 v1.14.15 // indirect - github.com/onsi/ginkgo v1.16.5 // indirect - github.com/onsi/gomega v1.21.1 // indirect -) - -require ( + github.com/bytedance/sonic v1.9.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/fastly/go-utils v0.0.0-20180712184237-d95a45783239 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 // indirect github.com/lestrrat/go-strftime v0.0.0-20180220042222-ba3bf9c1d042 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect @@ -50,24 +63,32 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/mojocn/base64Captcha v1.3.5 + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/richardlehane/mscfb v1.0.4 // indirect + github.com/richardlehane/msoleps v1.0.3 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.5 github.com/subosito/gotenv v1.4.2 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect github.com/tebeka/strftime v0.1.5 // indirect github.com/tklauser/go-sysconf v0.3.11 // indirect github.com/tklauser/numcpus v0.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect + github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect github.com/yusufpapurcu/wmi v1.2.3 // indirect + golang.org/x/arch v0.3.0 // indirect golang.org/x/image v0.5.0 // indirect golang.org/x/net v0.10.0 // indirect golang.org/x/sys v0.11.0 // indirect golang.org/x/text v0.12.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index a962ae4..94d5243 100644 --- a/go.sum +++ b/go.sum @@ -66,12 +66,18 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bsm/ginkgo/v2 v2.9.5 h1:rtVBYPs3+TC5iLUVOis1B9tjLTup7Cj5IfzosKtvTJ0= github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -98,6 +104,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= @@ -118,12 +126,15 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-admin-team/go-admin-core v1.3.12-0.20221121065133-27b7dbe27a8f h1:2xHpluWqY/ZlYoUpOU8VwDponYSnukRDhkOr7rk3ffU= -github.com/go-admin-team/go-admin-core v1.3.12-0.20221121065133-27b7dbe27a8f/go.mod h1:a9/XW1rCChPLVJ3bST13hB6R8YfVjYeF0GYjb8If6Yg= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -135,14 +146,21 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= @@ -152,6 +170,8 @@ github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFG github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= @@ -181,6 +201,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= @@ -197,6 +218,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -258,6 +280,7 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -313,6 +336,10 @@ github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dv github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869 h1:IPJ3dvxmJ4uczJe5YQdrYB16oTJlGSC/OyZDqUk9xX4= github.com/jehiah/go-strftime v0.0.0-20171201141054-1d33003b3869/go.mod h1:cJ6Cj7dQo+O6GJNiMx+Pa94qKj+TG8ONdKHgMNIyyag= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -329,6 +356,9 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= @@ -339,6 +369,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570 h1:0iQektZGS248WXmGIYOwRXSQhD4qn3icjMpuxwO7qlo= github.com/lestrrat/go-envload v0.0.0-20180220120943-6ed08b54a570/go.mod h1:BLt8L9ld7wVsvEWQbuLrUZnCMnUmLZ+CGDzKtclrTlE= github.com/lestrrat/go-file-rotatelogs v0.0.0-20180223000712-d3151e2a480f h1:sgUSP4zdTUZYZgAGGtN5Lxk92rK+JUFOwf+FT99EEI4= @@ -357,6 +389,9 @@ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2 github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/matoous/go-nanoid v1.5.0/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U= +github.com/matoous/go-nanoid/v2 v2.0.0 h1:d19kur2QuLeHmJBkvYkFdhFBzLoo1XVm2GgTpL+9Tj0= +github.com/matoous/go-nanoid/v2 v2.0.0/go.mod h1:FtS4aGPVfEkxKxhdWPAspZpZSh1cOjtM7Ej/So3hR0g= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= @@ -371,9 +406,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA= github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= -github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -392,8 +426,12 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mojocn/base64Captcha v1.3.5 h1:Qeilr7Ta6eDtG4S+tQuZ5+hO+QHbiGAJdi4PfoagaA0= github.com/mojocn/base64Captcha v1.3.5/go.mod h1:/tTTXn4WTpX9CfrmipqRytCpJ27Uw3G6I7NcP2WwcmY= +github.com/mssola/user_agent v0.6.0 h1:uwPR4rtWlCHRFyyP9u2KOV0u8iQXmS7Z7feTrstQwk4= +github.com/mssola/user_agent v0.6.0/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= @@ -402,22 +440,14 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.21.1 h1:OB/euWYIExnPBohllTicTHmGTrMaqJ67nIu80j0/uEM= -github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= @@ -469,6 +499,11 @@ github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0Niuqvtf github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= +github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= +github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -525,9 +560,12 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= @@ -542,9 +580,19 @@ github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7Am github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c= +github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.7.1 h1:gm8q0UCAyaTt3MEF5wWMjVdmthm2EHAWesGSKS9tdVI= +github.com/xuri/excelize/v2 v2.7.1/go.mod h1:qc0+2j4TvAUrBw36ATtcTeC1VCM0fFdAXZOmcF4nTpY= +github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M= +github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -575,6 +623,9 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -594,6 +645,7 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -634,6 +686,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -664,7 +717,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -677,6 +729,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -699,6 +753,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -720,12 +775,9 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -750,7 +802,6 @@ golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -759,16 +810,21 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -778,6 +834,7 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -837,12 +894,12 @@ golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -945,6 +1002,9 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= @@ -954,6 +1014,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogR gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= @@ -967,12 +1028,16 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= +gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= +gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= +gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1091,6 +1156,7 @@ modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk= modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/lib/core/account/account.go b/lib/core/account/account.go index 2279141..53f12c8 100644 --- a/lib/core/account/account.go +++ b/lib/core/account/account.go @@ -8,9 +8,9 @@ import ( sysMenuService "ems.agt/features/sys_menu/service" sysRoleService "ems.agt/features/sys_role/service" "ems.agt/lib/core/cache" - "ems.agt/lib/core/conf" "ems.agt/lib/core/vo" "ems.agt/lib/dborm" + srcConfig "ems.agt/src/framework/config" ) // 登录缓存用户信息 @@ -35,7 +35,7 @@ func CacheLoginUser(user *dborm.User) { } // 是否管理员 - if conf.IsAdmin(loginUser.UserID) { + if srcConfig.IsAdmin(loginUser.UserID) { loginUser.Permissions = []string{"*:*:*"} } else { // 获取权限标识 diff --git a/lib/core/conf/conf.go b/lib/core/conf/conf.go index 506c66a..c5e2586 100644 --- a/lib/core/conf/conf.go +++ b/lib/core/conf/conf.go @@ -2,51 +2,35 @@ package conf import ( "fmt" - "time" "github.com/spf13/viper" ) +var v *viper.Viper + // 配置文件读取 func InitConfig(configFile string) { + v = viper.New() + // 设置配置文件路径 - viper.SetConfigFile(configFile) + v.SetConfigFile(configFile) // 读取配置文件 - err := viper.ReadInConfig() + err := v.ReadInConfig() if err != nil { fmt.Printf("读取配置文件失败: %v \n", err) return } - - // 记录程序开始运行的时间点 - viper.Set("runTime", time.Now()) -} - -// RunTime 程序开始运行的时间 -func RunTime() time.Time { - return viper.GetTime("runTime") } // Get 获取配置信息 // // Get("framework.name") func Get(key string) any { - return viper.Get(key) + return v.Get(key) } -// IsAdmin 用户是否为管理员 -func IsAdmin(userID string) bool { - if userID == "" { - return false - } - // 从本地配置获取user信息 - // admins := Get("user.adminList").([]any) - admins := []string{"1", "2", "3"} - for _, s := range admins { - if s == userID { - return true - } - } - return false +// AllSettings 全部配置信息 +func AllSettings() map[string]interface{} { + return v.AllSettings() } diff --git a/lib/core/utils/ctx/ctx.go b/lib/core/utils/ctx/ctx.go index f800d7a..dac758b 100644 --- a/lib/core/utils/ctx/ctx.go +++ b/lib/core/utils/ctx/ctx.go @@ -8,8 +8,11 @@ import ( "net/url" "os" "path/filepath" + "strings" "ems.agt/lib/core/vo" + commonConstants "ems.agt/src/framework/constants/common" + tokenConst "ems.agt/src/framework/constants/token" "github.com/gorilla/mux" ) @@ -101,13 +104,27 @@ func SaveUploadedFile(r *http.Request, dst string) error { /// ==== 登录用户信息, 通过中间件后预置入 +// Authorization 解析请求头 +func Authorization(r *http.Request) string { + authHeader := r.Header.Get(tokenConst.HEADER_KEY) + if authHeader == "" { + return "" + } + // 拆分 Authorization 请求头,提取 JWT 令牌部分 + arr := strings.Split(authHeader, tokenConst.HEADER_PREFIX) + if len(arr) == 2 && arr[1] == "" { + return "" + } + return arr[1] +} + // 定义自定义类型作为键 type ContextKey string // LoginUser 登录用户信息需要Authorize中间件 func LoginUser(r *http.Request) (vo.LoginUser, error) { // 上下文 - v := r.Context().Value(ContextKey("LoginUser")) + v := r.Context().Value(ContextKey(commonConstants.CTX_LOGIN_USER)) if v != nil { return v.(vo.LoginUser), nil } diff --git a/lib/global/global.go b/lib/global/global.go index 29270bc..228fdf2 100644 --- a/lib/global/global.go +++ b/lib/global/global.go @@ -26,9 +26,9 @@ const ( ) var ( - Version string - BuildTime string - GoVer string + Version string = "-" + BuildTime string = "-" + GoVer string = "-" ) var ( diff --git a/lib/midware/authorize.go b/lib/midware/authorize.go index 12d9ca9..f8f4f18 100644 --- a/lib/midware/authorize.go +++ b/lib/midware/authorize.go @@ -10,6 +10,8 @@ import ( "ems.agt/lib/core/vo" "ems.agt/lib/core/vo/result" "ems.agt/lib/dborm" + commonConstants "ems.agt/src/framework/constants/common" + tokenUtils "ems.agt/src/framework/utils/token" ) // Authorize 用户身份授权认证校验 @@ -25,30 +27,74 @@ func Authorize(options map[string][]string) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 获取请求头标识信息 + tokenStr := ctx.Authorization(r) + // 获取请求头标识信息-旧头 accessToken := r.Header.Get("AccessToken") - if accessToken == "" { - ctx.JSON(w, 401, result.CodeMsg(401, "token error 无效身份授权")) + if tokenStr == "" && accessToken != "" { + // 验证令牌 == 这里直接查数据库session + if !dborm.XormExistValidToken(accessToken, 0) { + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization valid error")) + return + } + se, err := dborm.XormUpdateSessionShakeTime(accessToken) + if err != nil { + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization shake error")) + return + } + + // 获取缓存的用户信息 + data, ok := cache.GetLocalTTL(se.AccountId) + if data == nil || !ok { + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization info error")) + return + } + loginUser := data.(vo.LoginUser) + + // 登录用户角色权限校验 + if options != nil { + var roles []string + for _, item := range loginUser.User.Roles { + roles = append(roles, item.RoleKey) + } + perms := loginUser.Permissions + verifyOk := verifyRolePermission(roles, perms, options) + if !verifyOk { + msg := fmt.Sprintf("Unauthorized access %s %s", r.Method, r.RequestURI) + ctx.JSON(w, 403, result.CodeMsg(403, msg)) + return + } + } + + // 在请求的 Context 中存储数据 + rContext := r.Context() + rContext = context.WithValue(rContext, ctx.ContextKey(commonConstants.CTX_LOGIN_USER), loginUser) + // 继续处理请求 + next.ServeHTTP(w, r.WithContext(rContext)) return } - // 验证令牌 == 这里直接查数据库session - if !dborm.XormExistValidToken(accessToken, 0) { - ctx.JSON(w, 401, result.CodeMsg(401, "valid error 无效身份授权")) + // 获取请求头标识信息 + if tokenStr == "" { + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization token error")) return } - se, err := dborm.XormUpdateSessionShakeTime(accessToken) + + // 验证令牌 + claims, err := tokenUtils.Verify(tokenStr) if err != nil { - ctx.JSON(w, 401, result.CodeMsg(401, "shake error 无效身份授权")) + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization valid error")) return } // 获取缓存的用户信息 - data, ok := cache.GetLocalTTL(se.AccountId) - if data == nil || !ok { - ctx.JSON(w, 401, result.CodeMsg(401, "info error 无效身份授权")) + loginUser := tokenUtils.LoginUser(claims) + if loginUser.UserID == "" { + ctx.JSON(w, 401, result.CodeMsg(401, "Invalid identity authorization shake error")) return } - loginUser := data.(vo.LoginUser) + + // 检查刷新有效期后存入上下文 + tokenUtils.RefreshIn(&loginUser) // 登录用户角色权限校验 if options != nil { @@ -59,7 +105,7 @@ func Authorize(options map[string][]string) func(http.Handler) http.Handler { perms := loginUser.Permissions verifyOk := verifyRolePermission(roles, perms, options) if !verifyOk { - msg := fmt.Sprintf("无权访问 %s %s", r.Method, r.RequestURI) + msg := fmt.Sprintf("Unauthorized access %s %s", r.Method, r.RequestURI) ctx.JSON(w, 403, result.CodeMsg(403, msg)) return } @@ -67,7 +113,7 @@ func Authorize(options map[string][]string) func(http.Handler) http.Handler { // 在请求的 Context 中存储数据 rContext := r.Context() - rContext = context.WithValue(rContext, ctx.ContextKey("LoginUser"), loginUser) + rContext = context.WithValue(rContext, ctx.ContextKey(commonConstants.CTX_LOGIN_USER), loginUser) // 继续处理请求 next.ServeHTTP(w, r.WithContext(rContext)) }) diff --git a/lib/midware/midhandle.go b/lib/midware/midhandle.go index 14cb958..506ee70 100644 --- a/lib/midware/midhandle.go +++ b/lib/midware/midhandle.go @@ -6,6 +6,7 @@ import ( "ems.agt/lib/log" "ems.agt/lib/services" + tokenConst "ems.agt/src/framework/constants/token" "github.com/gorilla/mux" ) @@ -22,6 +23,7 @@ func LoggerTrace(next http.Handler) http.Handler { log.Trace(" User-Agent:", r.Header.Get("User-Agent")) log.Trace(" Content-Type:", r.Header.Get("Content-Type")) log.Trace(" AccessToken:", r.Header.Get("AccessToken")) + log.Trace(" Authorization:", r.Header.Get(tokenConst.HEADER_KEY)) log.Trace("Trace End=====") //body, _ := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) // nop-close to ready r.Body !!! diff --git a/lib/mmlp/parse.go b/lib/mmlp/parse.go index d9a173a..75d4bb2 100644 --- a/lib/mmlp/parse.go +++ b/lib/mmlp/parse.go @@ -15,6 +15,7 @@ import ( "ems.agt/lib/global" "ems.agt/lib/log" "ems.agt/lib/run" + tokenConst "ems.agt/src/framework/constants/token" "github.com/go-resty/resty/v2" ) @@ -36,14 +37,15 @@ type MmlCommand struct { } type MmlVar struct { - Version string `json:"version"` - Output string `json:"output"` - MmlHome string `json:"mmlHome"` - Limit int `json:"limit"` - User string `json:"user"` - SessionToken string `josn:"sessionToken"` - HttpUri string `json:"httpUri"` - UserAgent string `json:"userAgent"` + Version string `json:"version"` + Output string `json:"output"` + MmlHome string `json:"mmlHome"` + Limit int `json:"limit"` + User string `json:"user"` + SessionToken string `josn:"sessionToken"` + Authorization string `josn:"authorization"` + HttpUri string `json:"httpUri"` + UserAgent string `json:"userAgent"` } // func init() { @@ -504,6 +506,7 @@ func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) { log.Debugf("method: Get requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}). SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}). SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -520,6 +523,7 @@ func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) { log.Debugf("method: Post requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}). SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}). SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -553,6 +557,7 @@ func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) { body := ParseInputBody(inputJson, mml) response, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}). SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}). SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -569,6 +574,7 @@ func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) { log.Debugf("method: Delete requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}). SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}). SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -584,6 +590,7 @@ func TransMml2HttpReq(omcMmlVar *MmlVar, mml *MmlCommand) (*[]byte, error) { log.Debugf("method: patch requestURI: %s", requestURI) response, err := client.R(). EnableTrace(). + SetHeaders(map[string]string{tokenConst.HEADER_KEY: omcMmlVar.Authorization}). SetHeaders(map[string]string{"accessToken": omcMmlVar.SessionToken}). SetHeaders(map[string]string{"User-Agent": omcMmlVar.UserAgent}). SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}). @@ -764,12 +771,19 @@ func ParseOutputResponse(omcMmlVar *MmlVar, outputJson *dborm.MmlOutput, respons output = *ParseErrorOutput(string(response.Body())) } else { log.Trace("mapResults:", mapResults) - errResult := mapResults["error"] - log.Trace("errResult:", errResult) - if len(errResult.(map[string]interface{})) > 0 { - errCode, _ := strconv.Atoi(fmt.Sprintf("%v", errResult.(map[string]interface{})["errorCode"])) - errorInfo := errResult.(map[string]interface{})["errorInfo"] + if v, ok := mapResults["error"]; ok { + vMap := v.(map[string]interface{}) + if len(vMap) > 0 { + errCode, _ := strconv.Atoi(fmt.Sprintf("%v", vMap["errorCode"])) + errorInfo := vMap["errorInfo"] + output = []byte(fmt.Sprintf(outputJson.ErrMsg, errCode, errorInfo)) + } + } else if v, ok := mapResults["code"]; ok { + errCode, _ := strconv.Atoi(fmt.Sprintf("%v", v)) + errorInfo := mapResults["msg"] output = []byte(fmt.Sprintf(outputJson.ErrMsg, errCode, errorInfo)) + } else { + output = []byte(fmt.Sprintf("%v", mapResults)) } } } diff --git a/lib/routes/routes.go b/lib/routes/routes.go index b0e187e..8ada599 100644 --- a/lib/routes/routes.go +++ b/lib/routes/routes.go @@ -17,6 +17,7 @@ import ( "ems.agt/features/nbi" "ems.agt/features/pm" "ems.agt/features/security" + "ems.agt/features/sm" "ems.agt/features/state" sysconfig "ems.agt/features/sys_config" sysdictdata "ems.agt/features/sys_dict_data" @@ -66,6 +67,9 @@ func init() { Register("GET", state.CustomUriLicenseInfoAll, state.GetAllLicenseInfoFromNF, nil) Register("GET", state.CustomUriLicenseInfoOne, state.GetOneLicenseInfoFromNF, nil) + Register("GET", sm.UriOMCLocalTime, sm.GetOMCLocalTime, nil) + Register("GET", sm.CustomUriOMCLocalTime, sm.GetOMCLocalTime, nil) + // 数据库直连操作权限 selectPermission := midware.Authorize(map[string][]string{ "hasRoles": {"dba"}, @@ -364,12 +368,12 @@ func NewRouter() *mux.Router { r := mux.NewRouter() // set custom handle for status 404/405 - r.NotFoundHandler = services.CustomResponseNotFound404Handler() - r.MethodNotAllowedHandler = services.CustomResponseMethodNotAllowed405Handler() + // r.NotFoundHandler = services.CustomResponseNotFound404Handler() + // r.MethodNotAllowedHandler = services.CustomResponseMethodNotAllowed405Handler() r.Use(midware.LoggerTrace) - r.Use(midware.Cors) - //r.Use(midware.OptionProcess) + // r.Use(midware.Cors) + // r.Use(midware.OptionProcess) // r.Use(midware.ArrowIPAddr) for _, router := range routers { diff --git a/restagent/config/config.go b/restagent/config/config.go index 77a543a..29aba63 100644 --- a/restagent/config/config.go +++ b/restagent/config/config.go @@ -1,12 +1,10 @@ package config import ( - "flag" "fmt" "os" "strings" - "ems.agt/lib/core/conf" "ems.agt/lib/global" "ems.agt/lib/log" @@ -288,7 +286,7 @@ func GetLogLevel() log.LogLevel { var ( DefaultUriPrefix string = "/api/rest" - UriPrefix string = "/api/rest" + UriPrefix string = "/omc/rest" //TestDataUDM []map[string]interface{} TDatas map[string]NeTestData ) @@ -312,33 +310,33 @@ func GetDefaultUserAgent() string { return "OMC-restagent/" + global.Version } -const defaultConfigFile = "./etc/restconf.yaml" +// const defaultConfigFile = "./etc/restconf.yaml" -func init() { - cfile := flag.String("c", defaultConfigFile, "config file") - pv := flag.Bool("version", false, "print version") - ph := flag.Bool("help", false, "print help") +// func init() { +// cfile := flag.String("c", defaultConfigFile, "config file") +// pv := flag.Bool("version", false, "print version") +// ph := flag.Bool("help", false, "print help") - global.BuildTime = "Wed May 31 18:24:04 CST 2023" - global.GoVer = "go version go1.15.7 linux/arm64" - flag.Parse() - if *pv { - fmt.Printf("OMC restagent version: %s\n%s\n%s\n\n", global.Version, global.BuildTime, global.GoVer) - os.Exit(0) - } - if *ph { - flag.Usage() - os.Exit(0) - } +// //global.BuildTime = "Wed May 31 18:24:04 CST 2023" +// //global.GoVer = "go version go1.15.7 linux/arm64" +// flag.Parse() +// if *pv { +// fmt.Printf("OMC restagent version: %s\n%s\n%s\n\n", global.Version, global.BuildTime, global.GoVer) +// os.Exit(0) +// } +// if *ph { +// flag.Usage() +// os.Exit(0) +// } - // 使用viper读取配置 - conf.InitConfig(*cfile) +// // 使用viper读取配置 +// conf.InitConfig(*cfile) - ReadConfig(*cfile) - if GetYamlConfig().OMC.UriPrefix != "" { - UriPrefix = GetYamlConfig().OMC.UriPrefix - } - if GetYamlConfig().TestConfig.Enabled { - ReadTestConfigYaml(GetYamlConfig().TestConfig.File) - } -} +// ReadConfig(*cfile) +// if GetYamlConfig().OMC.UriPrefix != "" { +// UriPrefix = GetYamlConfig().OMC.UriPrefix +// } +// if GetYamlConfig().TestConfig.Enabled { +// ReadTestConfigYaml(GetYamlConfig().TestConfig.File) +// } +// } diff --git a/restagent/dev.yaml b/restagent/dev.yaml index eaa2491..6d23ef0 100644 --- a/restagent/dev.yaml +++ b/restagent/dev.yaml @@ -79,7 +79,7 @@ ne: # chk2ne: true/false, if put OmcNeConfig parameters to NE omc: - uriPrefix: /api/rest/oam + uriPrefix: "/api/rest/oam" neType: OMC neId: 001 rmUID: 4400HX101 diff --git a/restagent/etc/restconf-t.yaml b/restagent/etc/restconf-t.yaml index b53e780..bafe50f 100644 --- a/restagent/etc/restconf-t.yaml +++ b/restagent/etc/restconf-t.yaml @@ -62,7 +62,7 @@ ne: # chk2ne: true/false, if put OmcNeConfig parameters to NE omc: - uriPrefix: /api/rest + uriPrefix: /api/rest/oam neType: OMC neId: 001 rmUID: 4400HX101 diff --git a/restagent/license/upf/upf_1350015_system.ini b/restagent/license/upf/upf_1350015_system.ini new file mode 100644 index 0000000..53ea81a Binary files /dev/null and b/restagent/license/upf/upf_1350015_system.ini differ diff --git a/restagent/restagent.go b/restagent/restagent.go index c8e2708..25d5503 100644 --- a/restagent/restagent.go +++ b/restagent/restagent.go @@ -4,23 +4,23 @@ import ( "crypto/tls" "crypto/x509" "fmt" - "net" "net/http" "os" "strconv" "strings" - "ems.agt/lib/core/redis" - "ems.agt/lib/dborm" - "ems.agt/lib/global" - "ems.agt/lib/log" - "ems.agt/lib/routes" - "ems.agt/features/dbrest" "ems.agt/features/fm" "ems.agt/features/lm" "ems.agt/features/pm" + "ems.agt/lib/dborm" + "ems.agt/lib/global" + "ems.agt/lib/log" + "ems.agt/lib/routes" "ems.agt/restagent/config" + "ems.agt/src" + libSession "ems.agt/src/lib_features/session" + "github.com/gin-gonic/gin" ) // const defaultConfigFile = "./etc/restconf.yaml" @@ -45,25 +45,25 @@ import ( // //fmt.Println(config.UriPrefix) // } -func listenIPv6(ipv6 string, port int) { - // - addr := &net.TCPAddr{ - IP: net.ParseIP(ipv6), - Port: port, - } +// func listenIPv6(ipv6 string, port int) { +// // +// addr := &net.TCPAddr{ +// IP: net.ParseIP(ipv6), +// Port: port, +// } - listener, err := net.ListenTCP("tcp6", addr) - if err != nil { - fmt.Println("Failed to listen:", err) - return - } +// listener, err := net.ListenTCP("tcp6", addr) +// if err != nil { +// fmt.Println("Failed to listen:", err) +// return +// } - server := &http.Server{} - err = server.Serve(listener) - if err != nil { - fmt.Println("Failed to serve:", err) - } -} +// server := &http.Server{} +// err = server.Serve(listener) +// if err != nil { +// fmt.Println("Failed to serve:", err) +// } +// } func HttpListen(addr string, router http.Handler) { err := http.ListenAndServe(addr, router) @@ -127,6 +127,10 @@ func HttpListenWebServer(addr string) { } func main() { + // src 配置中心初始加载 + src.ConfigurationInit() + app := src.AppEngine() + conf := config.GetYamlConfig() log.InitLogger(conf.Logger.File, conf.Logger.Duration, conf.Logger.Count, "omc:restagent", config.GetLogLevel()) @@ -165,10 +169,16 @@ func main() { os.Exit(4) } - // 连接redis - redis.Connect() + // 将 mux.Router 注册到 gin.Engine - router := routes.NewRouter() + // 默认路由组 + defaultUriGroup := app.Group(config.DefaultUriPrefix) + defaultUriGroup.Use(libSession.SessionHeader()) + defaultUriGroup.Any("/*any", gin.WrapH(routes.NewRouter())) + // 可配置前缀路由组 + uriGroup := app.Group(config.UriPrefix) + uriGroup.Use(libSession.SessionHeader()) + uriGroup.Any("/*any", gin.WrapH(routes.NewRouter())) // 开启监控采集 // monitor.StartMonitor(false, "") @@ -178,9 +188,9 @@ func main() { if rest.IPv4 != "" { listen := rest.IPv4 + ":" + strconv.Itoa(int(rest.Port)) if strings.ToLower(rest.Scheme) == "https" { - go HttpListenTLS(listen, rest.CertFile, rest.KeyFile, router) + go HttpListenTLS(listen, rest.CertFile, rest.KeyFile, app) } else { - go HttpListen(listen, router) + go HttpListen(listen, app) } } @@ -188,9 +198,9 @@ func main() { if rest.IPv6 != "" { listenv6 := "[" + rest.IPv6 + "]" + ":" + strconv.Itoa(int(rest.Port)) if strings.ToLower(rest.Scheme) == "https" { - go HttpListenTLS(listenv6, rest.CertFile, rest.KeyFile, router) + go HttpListenTLS(listenv6, rest.CertFile, rest.KeyFile, app) } else { - go HttpListen(listenv6, router) + go HttpListen(listenv6, app) } } } diff --git a/src/app.go b/src/app.go new file mode 100644 index 0000000..c9ef209 --- /dev/null +++ b/src/app.go @@ -0,0 +1,119 @@ +package src + +import ( + "fmt" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/errorcatch" + "ems.agt/src/framework/middleware" + "ems.agt/src/framework/middleware/security" + "ems.agt/src/modules/common" + "ems.agt/src/modules/monitor" + "ems.agt/src/modules/system" + + "github.com/gin-gonic/gin" +) + +// 路由函数句柄,交给由 http.ListenAndServe(addr, router) +func AppEngine() *gin.Engine { + app := initAppEngine() + + // 初始全局默认 + initDefeat(app) + + // 初始模块路由 + initModulesRoute(app) + + // 读取服务配置 + app.ForwardedByClientIP = config.Get("server.proxy").(bool) + addr := fmt.Sprintf(":%d", config.Get("server.port").(int)) + + // 启动服务 + fmt.Printf("\nopen http://localhost%s \n\n", addr) + return app +} + +// 运行服务程序 main.go +// +// func main() { +// src.ConfigurationInit() +// if err := src.RunServer(); err != nil { +// src.ConfigurationClose() +// } +// } +func RunServer() error { + app := initAppEngine() + + // 初始全局默认 + initDefeat(app) + + // 初始模块路由 + initModulesRoute(app) + + // 读取服务配置 + app.ForwardedByClientIP = config.Get("server.proxy").(bool) + addr := fmt.Sprintf(":%d", config.Get("server.port").(int)) + + // 启动服务 + fmt.Printf("\nopen http://localhost%s \n\n", addr) + return app.Run(addr) +} + +// 初始应用引擎 +func initAppEngine() *gin.Engine { + var app *gin.Engine + + // 禁止控制台日志输出的颜色 + gin.DisableConsoleColor() + + // 根据运行环境注册引擎 + if config.Env() == "prod" { + gin.SetMode(gin.ReleaseMode) + app = gin.New() + app.Use(gin.Recovery()) + } else { + app = gin.Default() + } + + return app +} + +// 初始全局默认 +func initDefeat(app *gin.Engine) { + // 全局中间件 + app.Use(errorcatch.ErrorCatch(), middleware.Report(), middleware.Cors(), security.Security()) + + // 静态目录-静态资源 + if v := config.Get("staticFile.default"); v != nil { + fsMap := v.(map[string]any) + prefix, dir := fsMap["prefix"], fsMap["dir"] + if prefix != nil && dir != nil { + app.StaticFS(prefix.(string), gin.Dir(dir.(string), true)) + } + } + + // 静态目录-上传资源 + if v := config.Get("staticFile.upload"); v != nil { + fsMap := v.(map[string]any) + prefix, dir := fsMap["prefix"], fsMap["dir"] + if prefix != nil && dir != nil { + app.StaticFS(prefix.(string), gin.Dir(dir.(string), true)) + } + } + + // 路由未找到时 + app.NoRoute(func(c *gin.Context) { + c.JSON(404, gin.H{ + "code": 404, + "msg": fmt.Sprintf("%s Not Found", c.Request.RequestURI), + }) + }) +} + +// 初始模块路由 +func initModulesRoute(app *gin.Engine) { + + common.Setup(app) + monitor.Setup(app) + system.Setup(app) +} diff --git a/src/configuration.go b/src/configuration.go new file mode 100644 index 0000000..fdf2712 --- /dev/null +++ b/src/configuration.go @@ -0,0 +1,35 @@ +package src + +import ( + "ems.agt/src/framework/config" + "ems.agt/src/framework/cron" + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/redis" +) + +// 配置中心初始加载 +func ConfigurationInit() { + // 初始配置参数 + config.InitConfig() + // 初始程序日志 + logger.InitLogger() + // 连接数据库实例 + datasource.Connect() + // 连接Redis实例 + redis.Connect() + // 启动调度任务实例 + cron.StartCron() +} + +// 配置中心相关配置关闭连接 +func ConfigurationClose() { + // 停止调度任务实例 + cron.StopCron() + // 关闭Redis实例 + redis.Close() + // 关闭数据库实例 + datasource.Close() + // 关闭程序日志 + logger.Close() +} diff --git a/src/framework/config/config.go b/src/framework/config/config.go new file mode 100644 index 0000000..dfe60eb --- /dev/null +++ b/src/framework/config/config.go @@ -0,0 +1,163 @@ +package config + +import ( + "bytes" + "embed" + "fmt" + "log" + "os" + "time" + + libConfig "ems.agt/src/lib_features/config" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +//go:embed config/*.yaml +var configFiles embed.FS + +// 初始化程序配置 +func InitConfig() { + initFlag() + initViper() +} + +// 指定参数绑定 +func initFlag() { + // --env prod + pflag.String("env", "prod", "Specify Run Environment Configuration local or prod") + // --c /etc/restconf.yaml + // -c /etc/restconf.yaml + pConfig := pflag.StringP("config", "c", "./etc/restconf.yaml", "Specify Configuration File") + // --version + // -V + pVersion := pflag.BoolP("version", "V", false, "Output program version") + // --help + pHelp := pflag.Bool("help", false, "Viewing Help Commands") + + pflag.Parse() + + // 参数固定输出 + if *pVersion { + buildInfo := libConfig.BuildInfo() + fmt.Println(buildInfo) + os.Exit(1) + } + if *pHelp { + pflag.Usage() + os.Exit(1) + } + + // 外层lib和features使用的配置 + libConfig.ConfigRead(*pConfig) + + viper.BindPFlags(pflag.CommandLine) +} + +// 配置文件读取 +func initViper() { + // 在当前工作目录中寻找配置 + // viper.AddConfigPath("config") + // viper.AddConfigPath("src/config") + // 如果配置文件名中没有扩展名,则需要设置Type + viper.SetConfigType("yaml") + + // 从 embed.FS 中读取默认配置文件内容 + configDefault, err := configFiles.ReadFile("config/config.default.yaml") + if err != nil { + log.Fatalf("ReadFile config default file: %s", err) + return + } + // 设置默认配置文件内容到 viper + err = viper.ReadConfig(bytes.NewReader(configDefault)) + if err != nil { + log.Fatalf("NewReader config default file: %s", err) + return + } + + // // 配置文件的名称(无扩展名) + // viper.SetConfigName("config.default") + // // 读取默认配置文件 + // if err := viper.ReadInConfig(); err != nil { + // log.Fatalf("fatal error config default file: %s", err) + // } + + env := viper.GetString("env") + if env != "local" && env != "prod" { + log.Fatalf("fatal error config env for local or prod : %s", env) + } + log.Printf("Current service environment operation configuration => %s \n", env) + + // 加载运行配置文件合并相同配置 + if env == "prod" { + // viper.SetConfigName("config.prod") + // 从 embed.FS 中读取默认配置文件内容 + configProd, err := configFiles.ReadFile("config/config.prod.yaml") + if err != nil { + log.Fatalf("ReadFile config prod file: %s", err) + return + } + // 设置默认配置文件内容到 viper + err = viper.MergeConfig(bytes.NewReader(configProd)) + if err != nil { + log.Fatalf("NewReader config prod file: %s", err) + return + } + } else { + // viper.SetConfigName("config.local") + // 从 embed.FS 中读取默认配置文件内容 + configLocal, err := configFiles.ReadFile("config/config.local.yaml") + if err != nil { + log.Fatalf("ReadFile config local file: %s", err) + return + } + // 设置默认配置文件内容到 viper + err = viper.MergeConfig(bytes.NewReader(configLocal)) + if err != nil { + log.Fatalf("NewReader config local file: %s", err) + return + } + } + // if err := viper.MergeInConfig(); err != nil { + // log.Fatalf("fatal error config MergeInConfig: %s", err) + // } + + // 合并外层lib和features使用配置 + libConfig.ConfigInMerge() + + // 记录程序开始运行的时间点 + viper.Set("runTime", time.Now()) +} + +// Env 获取运行服务环境 +// local prod +func Env() string { + return viper.GetString("env") +} + +// RunTime 程序开始运行的时间 +func RunTime() time.Time { + return viper.GetTime("runTime") +} + +// Get 获取配置信息 +// +// Get("framework.name") +func Get(key string) any { + return viper.Get(key) +} + +// IsAdmin 用户是否为管理员 +func IsAdmin(userID string) bool { + if userID == "" { + return false + } + // 从本地配置获取user信息 + admins := Get("user.adminList").([]any) + for _, s := range admins { + if s.(string) == userID { + return true + } + } + return false +} diff --git a/src/framework/config/config/config.default.yaml b/src/framework/config/config/config.default.yaml new file mode 100644 index 0000000..0a0bc1f --- /dev/null +++ b/src/framework/config/config/config.default.yaml @@ -0,0 +1,208 @@ +# 项目信息 +framework: + name: "ems_agt" + version: "1.6.2" + +# 应用服务配置 +server: + # 服务端口 + port: 3040 + # 是否开启代理 + proxy: false + +# 日志 +logger: + fileDir: "/usr/local/omc/logs" + fileName: "ems_agt.log" + level: 2 # 日志记录的等级 0:silent<1:info<2:warn<3:error + maxDay: 30 # 日志会保留 30 天 + maxSize: 10 # 调整按 10MB 大小的切割 + +# 静态文件配置, 相对项目根路径或填绝对路径 +staticFile: + # 默认资源,dir目录需要预先创建 + default: + prefix: "/static" + dir: "/usr/local/omc/static" + # 文件上传资源目录映射,与项目目录同级 + upload: + prefix: "/upload" + dir: "/usr/local/omc/upload" + +# 文件上传 +upload: + # 最大上传文件大小,默认为 10mb + fileSize: 10 + # 文件扩展名白名单 + whitelist: + # 图片 + - ".bmp" + - ".webp" + - ".gif" + - ".jpg" + - ".jpeg" + - ".png" + # word excel powerpoint + - ".doc" + - ".docx" + - ".xls" + - ".xlsx" + - ".ppt" + - ".pptx" + # 文本文件 + - ".html" + - ".htm" + - ".txt" + # pdf + - ".pdf" + # 压缩文件 + - ".zip" + - ".gz" + - ".tgz" + - ".gzip" + # 音视频格式 + - ".mp3" + - ".mp4" + - ".avi" + - ".rmvb" + +# cors 跨域 +cors: + # 设置 Access-Control-Allow-Origin 的值,【默认值】会获取请求头上的 origin + # 例如:http://mask-api.org + # 如果请求设置了 credentials,则 origin 不能设置为 * + origin: "*" + # 设置 Access-Control-Allow-Credentials,【默认值】false + credentials: true + # 设置 Access-Control-Max-Age + maxAge: 31536000 + # 允许跨域的方法,【默认值】为 GET,HEAD,PUT,POST,DELETE,PATCH + allowMethods: + - "OPTIONS" + - "HEAD" + - "GET" + - "POST" + - "PUT" + - "DELETE" + - "PATCH" + # 设置 Access-Control-Allow-Headers 的值,【默认值】会获取请求头上的 Access-Control-Request-Headers + allowHeaders: + - "X-App-Code" + - "X-App-Version" + - "Authorization" + - "Origin" + - "X-Requested-With" + - "Content-Type" + - "Content-Language" + - "Accept" + - "Range" + - "Accesstoken" + - "Operationtype" + # 设置 Access-Control-Expose-Headers 的值 + exposeHeaders: + - "X-RepeatSubmit-Rest" + +# security 安全 +security: + csrf: + enable: false + type: "referer" + # 允许调用的域名地址的,例如:http:///mask-api + refererWhiteList: + - "127.0.0.1:3030" + xframe: + enable: true + value: "SAMEORIGIN" + csp: + enable: true + hsts: + enable: false + maxAge: 31536000 + includeSubdomains: false + noopen: + enable: false + nosniff: + enable: false + xssProtection: + enable: true + value: "1; mode=block" + +# JWT 令牌配置 +jwt: + # 令牌算法 HS256 HS384 HS512 + algorithm: "HS512" + # 令牌密钥 + secret: "217a0481c7f9cfe1cb547d32ee012b0f" + # 令牌有效期(默认120分钟) + expiresIn: 120 + # 验证令牌有效期,相差不足xx分钟,自动刷新缓存 + refreshIn: 20 + +# GORM 数据源 +gorm: + dataSource: + # 默认数据库实例 + default: + type: "mysql" + host: "127.0.0.1" + port: 3306 + username: "<用户名>" + password: "<密码>" + database: "<数据库>" + logging: false + # 多个数据源时可以用这个指定默认的数据源 + defaultDataSourceName: "default" + +# Redis 缓存数据 +redis: + dataSource: + default: + port: 6379 # Redis port + host: "127.0.0.1" # Redis host + password: "<密码>" + db: 0 # Redis db_num + # 多个数据源时可以用这个指定默认的数据源 + defaultDataSourceName: "default" + +# 用户配置 +user: + # 密码 + password: + # 密码最大错误次数 + maxRetryCount: 5 + # 密码锁定时间,单位分钟(默认10分钟) + lockTime: 10 + # 管理员列表 + adminList: + - "1" + - "2" + +# char 字符验证码配置 +charCaptcha: + # 宽度 + width: 120 + # 高度 + height: 40 + # 干扰线条的数量 + noise: 4 + # 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有 + color: true + # 验证码图片背景颜色 + background: "#fafafa" + # 验证码长度 + size: 4 + # 验证码字符 + chars: "023456789abcdefghjkmnprstuvwxyz" + +# math 数值计算码配置 +mathCaptcha: + # 宽度 + width: 120 + # 高度 + height: 40 + # 干扰线条的数量 + noise: 4 + # 验证码的字符是否有颜色,默认没有,如果设定了背景,则默认有 + color: true + # 验证码图片背景颜色 + background: "#fafafa" diff --git a/src/framework/config/config/config.local.yaml b/src/framework/config/config/config.local.yaml new file mode 100644 index 0000000..5ea34a6 --- /dev/null +++ b/src/framework/config/config/config.local.yaml @@ -0,0 +1,53 @@ +# 应用服务配置 +server: + port: 3040 + +# 日志 +logger: + fileDir: "C:/usr/local/omc/logs" + level: 0 # 输出最低等级 + +# 静态文件配置, 相对项目根路径或填绝对路径 +staticFile: + default: + dir: "C:/usr/local/omc/static" + # 文件上传资源目录映射,与项目目录同级 + upload: + dir: "C:/usr/local/omc/upload" + +# security 安全 +security: + csrf: + refererWhiteList: + - "localhost:3131" + - "127.0.0.1:3131" + +# GORM 数据源 +gorm: + dataSource: + default: + type: "mysql" + host: "192.168.0.229" + port: 33066 + username: "root" + password: "1000omc@kp!" + database: "omc_db_dev" + logging: true + +# Redis 缓存数据,数据源声明全小写 +redis: + dataSource: + # OMC系统使用库 + default: + port: 6379 # Redis port + host: "192.168.0.229" # Redis host + password: "" + db: 10 # Redis db_num + # UDM网元用户库 + udmuser: + port: 6379 # Redis port + host: "192.168.0.229" + password: "" + db: 0 # Redis db_num + # 多个数据源时可以用这个指定默认的数据源 + defaultDataSourceName: "default" diff --git a/src/framework/config/config/config.prod.yaml b/src/framework/config/config/config.prod.yaml new file mode 100644 index 0000000..3323c4b --- /dev/null +++ b/src/framework/config/config/config.prod.yaml @@ -0,0 +1,32 @@ +# 应用服务配置 +server: + port: 3040 + proxy: true + +# security 安全 +security: + csrf: + # 允许调用的域名地址的,例如:http:/// + refererWhiteList: + - "127.0.0.1" + - "" + +# GORM 数据源 +gorm: + dataSource: + default: + type: "mysql" + host: "" + port: 3306 + username: "<用户名>" + password: "<密码>" + database: "<数据库>" + +# Redis 缓存数据 +redis: + dataSource: + default: + port: 6379 # Redis port + host: "" + password: "<密码>" + db: 0 # Redis db_num diff --git a/src/framework/constants/admin/admin.go b/src/framework/constants/admin/admin.go new file mode 100644 index 0000000..7ac32c5 --- /dev/null +++ b/src/framework/constants/admin/admin.go @@ -0,0 +1,12 @@ +package admin + +// 管理员常量信息 + +// 管理员-系统指定角色ID +const ROLE_ID = "1" + +// 管理员-系统指定角色KEY +const ROLE_KEY = "admin" + +// 管理员-系统指定权限 +const PERMISSION = "*:*:*" diff --git a/src/framework/constants/cachekey/cachekey.go b/src/framework/constants/cachekey/cachekey.go new file mode 100644 index 0000000..0e69445 --- /dev/null +++ b/src/framework/constants/cachekey/cachekey.go @@ -0,0 +1,24 @@ +package cachekey + +// 缓存的key常量 + +// 登录用户 +const LOGIN_TOKEN_KEY = "login_tokens:" + +// 验证码 +const CAPTCHA_CODE_KEY = "captcha_codes:" + +// 参数管理 +const SYS_CONFIG_KEY = "sys_config:" + +// 字典管理 +const SYS_DICT_KEY = "sys_dict:" + +// 防重提交 +const REPEAT_SUBMIT_KEY = "repeat_submit:" + +// 限流 +const RATE_LIMIT_KEY = "rate_limit:" + +// 登录账户密码错误次数 +const PWD_ERR_CNT_KEY = "pwd_err_cnt:" diff --git a/src/framework/constants/captcha/captcha.go b/src/framework/constants/captcha/captcha.go new file mode 100644 index 0000000..816a65e --- /dev/null +++ b/src/framework/constants/captcha/captcha.go @@ -0,0 +1,12 @@ +package captcha + +// 验证码常量信息 + +// 验证码有效期,单位秒 +const EXPIRATION = 2 * 60 + +// 验证码类型-数值计算 +const TYPE_CHAR = "char" + +// 验证码类型-字符验证 +const TYPE_MATH = "math" diff --git a/src/framework/constants/common/common.go b/src/framework/constants/common/common.go new file mode 100644 index 0000000..3786104 --- /dev/null +++ b/src/framework/constants/common/common.go @@ -0,0 +1,21 @@ +package common + +// 通用常量信息 + +// www主域 +const WWW = "www." + +// http请求 +const HTTP = "http://" + +// https请求 +const HTTPS = "https://" + +// 通用状态标识-正常/成功/是 +const STATUS_YES = "1" + +// 通用状态标识-停用/失败/否 +const STATUS_NO = "0" + +// 上下文信息-登录用户 +const CTX_LOGIN_USER = "loginuser" diff --git a/src/framework/constants/menu/menu.go b/src/framework/constants/menu/menu.go new file mode 100644 index 0000000..94913cb --- /dev/null +++ b/src/framework/constants/menu/menu.go @@ -0,0 +1,24 @@ +package menu + +// 系统菜单常量信息 + +const ( + // 组件布局类型-基础布局组件标识 + COMPONENT_LAYOUT_BASIC = "BasicLayout" + // 组件布局类型-空白布局组件标识 + COMPONENT_LAYOUT_BLANK = "BlankLayout" + // 组件布局类型-内链接布局组件标识 + COMPONENT_LAYOUT_LINK = "LinkLayout" +) + +const ( + // 菜单类型-目录 + TYPE_DIR = "D" + // 菜单类型-菜单 + TYPE_MENU = "M" + // 菜单类型-按钮 + TYPE_BUTTON = "B" +) + +// 菜单内嵌地址标识-带/前缀 +const PATH_INLINE = "/inline" diff --git a/src/framework/constants/result/result.go b/src/framework/constants/result/result.go new file mode 100644 index 0000000..dbd8630 --- /dev/null +++ b/src/framework/constants/result/result.go @@ -0,0 +1,15 @@ +package result + +// 响应结果常量信息 + +const ( + // 响应-code错误失败 + CODE_ERROR = 0 + // 响应-msg错误失败 + MSG_ERROR = "error" + + // 响应-msg正常成功 + CODE_SUCCESS = 1 + // 响应-code正常成功 + MSG_SUCCESS = "success" +) diff --git a/src/framework/constants/roledatascope/roledatascope.go b/src/framework/constants/roledatascope/roledatascope.go new file mode 100644 index 0000000..52c6350 --- /dev/null +++ b/src/framework/constants/roledatascope/roledatascope.go @@ -0,0 +1,29 @@ +package roledatascope + +// 系统角色数据范围常量 + +const ( + // 全部数据权限 + ALL = "1" + + // 自定数据权限 + CUSTOM = "2" + + // 部门数据权限 + DEPT = "3" + + // 部门及以下数据权限 + DEPT_AND_CHILD = "4" + + // 仅本人数据权限 + SELF = "5" +) + +// 系统角色数据范围映射 +var RoleDataScope = map[string]string{ + ALL: "全部数据权限", + CUSTOM: "自定数据权限", + DEPT: "部门数据权限", + DEPT_AND_CHILD: "部门及以下数据权限", + SELF: "仅本人数据权限", +} diff --git a/src/framework/constants/token/token.go b/src/framework/constants/token/token.go new file mode 100644 index 0000000..5c71b08 --- /dev/null +++ b/src/framework/constants/token/token.go @@ -0,0 +1,21 @@ +package token + +// 令牌常量信息 + +// 令牌-数据响应字段 +const RESPONSE_FIELD = "access_token" + +// 令牌-请求头标识前缀 +const HEADER_PREFIX = "Bearer " + +// 令牌-请求头标识 +const HEADER_KEY = "Authorization" + +// 令牌-JWT唯一标识字段 +const JWT_UUID = "login_key" + +// 令牌-JWT标识用户主键字段 +const JWT_KEY = "user_id" + +// 令牌-JWT标识用户登录账号字段 +const JWT_NAME = "user_name" diff --git a/src/framework/constants/uploadsubpath/uploadsubpath.go b/src/framework/constants/uploadsubpath/uploadsubpath.go new file mode 100644 index 0000000..697e371 --- /dev/null +++ b/src/framework/constants/uploadsubpath/uploadsubpath.go @@ -0,0 +1,37 @@ +package uploadsubpath + +// 文件上传-子路径类型常量 + +const ( + // 默认 + DEFAULT = "default" + + // 头像 + AVATART = "avatar" + + // 导入 + IMPORT = "import" + + // 导出 + EXPORT = "export" + + // 通用上传 + COMMON = "common" + + // 下载 + DOWNLOAD = "download" + + // 切片 + CHUNK = "chunk" +) + +// 子路径类型映射 +var UploadSubpath = map[string]string{ + DEFAULT: "默认", + AVATART: "头像", + IMPORT: "导入", + EXPORT: "导出", + COMMON: "通用上传", + DOWNLOAD: "下载", + CHUNK: "切片", +} diff --git a/src/framework/cron/cron.go b/src/framework/cron/cron.go new file mode 100644 index 0000000..ac14b05 --- /dev/null +++ b/src/framework/cron/cron.go @@ -0,0 +1,202 @@ +package cron + +import ( + "fmt" + "time" + + "github.com/robfig/cron/v3" +) + +// 定义内部调度任务实例 +var c *cron.Cron + +// 任务队列 +var queueMap map[string]Queue + +// StartCron 启动调度任务实例 +func StartCron() { + queueMap = make(map[string]Queue) + c = cron.New(cron.WithSeconds()) + c.Start() +} + +// StopCron 停止调度任务实例 +func StopCron() { + c.Stop() +} + +// CreateQueue 创建队列注册处理器 +func CreateQueue(name string, processor QueueProcessor) Queue { + queue := Queue{ + Name: name, + Processor: processor, + Job: &[]*QueueJob{}, + } + queueMap[name] = queue + return queue +} + +// GetQueue 通过名称获取队列实例 +func GetQueue(name string) Queue { + if v, ok := queueMap[name]; ok { + return v + } + return Queue{} +} + +// QueueNames 获取注册的队列名称 +func QueueNames() []string { + keys := make([]string, 0, len(queueMap)) + for k := range queueMap { + keys = append(keys, k) + } + return keys +} + +// Queue 任务队列 +type Queue struct { + Name string // 队列名 + Processor QueueProcessor + Job *[]*QueueJob +} + +// QueueProcessor 队列处理函数接口 +type QueueProcessor interface { + // Execute 实际执行函数 + Execute(data any) any +} + +// RunJob 运行任务,data是传入的数据 +func (q *Queue) RunJob(data any, opts JobOptions) int { + job := &QueueJob{ + Status: Waiting, + Data: data, + Opts: opts, + queueName: q.Name, + queueProcessor: &q.Processor, + } + + // 非重复任务立即执行 + if opts.Cron == "" { + // 获取执行的任务 + currentJob := job.GetJob(false) + if currentJob.Status == Active { + return Active + } + // 从切片 jobs 中删除指定索引位置的元素 + for i, v := range *q.Job { + if v.cid == 0 { + jobs := *q.Job + jobs = append(jobs[:i], jobs[i+1:]...) + *q.Job = jobs + break + } + } + go job.Run() + } else { + // 移除已存的任务ID + q.RemoveJob(opts.JobId) + // 添加新任务 + cid, err := c.AddJob(opts.Cron, job) + if err != nil { + newLog.Error(err, "err") + job.Status = Failed + } + job.cid = cid + } + + *q.Job = append(*q.Job, job) + newLog.Info("RunJob", job.cid, opts.JobId, job.Status) + return job.Status +} + +// RemoveJob 移除任务 +func (q *Queue) RemoveJob(jobId string) bool { + for i, v := range *q.Job { + if jobId == v.Opts.JobId { + newLog.Info("RemoveJob", v.cid, jobId, v.Status) + c.Remove(v.cid) + // 从切片 jobs 中删除指定索引位置的元素 + jobs := *q.Job + jobs = append(jobs[:i], jobs[i+1:]...) + *q.Job = jobs + return true + } + } + return false +} + +// Status 任务执行状态 +const ( + Waiting = iota + Active + Completed + Failed +) + +// JobOptions 任务参数信息 +type JobOptions struct { + JobId string // 执行任务编号 + Cron string // 重复任务cron表达式 +} + +// QueueJob 队列内部执行任务 +type QueueJob struct { + Status int // 任务执行状态 + Timestamp int64 // 执行时间 + Data any // 执行任务时传入的参数 + Opts JobOptions + + cid cron.EntryID // 执行ID + + queueName string //队列名 + queueProcessor *QueueProcessor +} + +// GetJob 获取当前执行任务 +func (job *QueueJob) GetJob(repeat bool) *QueueJob { + q := GetQueue(job.queueName) + for _, v := range *q.Job { + if repeat && v.Opts.JobId == job.Opts.JobId { + return v + } + if !repeat && v.cid == 0 { + return v + } + } + return job +} + +// Run 实现的接口函数 +func (s QueueJob) Run() { + // 检查当前任务 + job := s.GetJob(s.cid != 0) + + // Active 状态不执行 + if job.Status == Active { + return + } + + // panics 异常收集 + defer func() { + if r := recover(); r != nil { + err, ok := r.(error) + if !ok { + err = fmt.Errorf("%v", r) + } + job.Status = Failed + newLog.Error(err, "failed", job) + } + }() + + // 开始执行 + job.Status = Active + job.Timestamp = time.Now().UnixMilli() + newLog.Info("run", job.cid, job.Opts.JobId) + + // 获取队列处理器接口实现 + processor := *job.queueProcessor + result := processor.Execute(job.Data) + job.Status = Completed + newLog.Completed(result, "completed", job) +} diff --git a/src/framework/cron/cron_test.go b/src/framework/cron/cron_test.go new file mode 100644 index 0000000..cb8f777 --- /dev/null +++ b/src/framework/cron/cron_test.go @@ -0,0 +1,179 @@ +package cron + +import ( + "testing" + "time" + + "ems.agt/src/framework/logger" +) + +// 参考文章: +// https://blog.csdn.net/zjbyough/article/details/113853582 +// https://mp.weixin.qq.com/s/Ak7RBv1NuS-VBeDNo8_fww +func init() { + StartCron() +} + +// 简单示例 队列任务处理 +var NewSimple = &Simple{} + +type Simple struct{} + +func (s *Simple) Execute(data any) any { + logger.Infof("执行=> %+v ", data) + // 实现任务处理逻辑 + return data +} + +func TestSimple(t *testing.T) { + + simple := CreateQueue("simple", NewSimple) + simple.RunJob(map[string]string{ + "ok": "ok", + "data": "data", + }, JobOptions{ + JobId: "101", + }) + + simpleC := CreateQueue("simple", NewSimple) + simpleC.RunJob(map[string]string{ + "corn": "*/5 * * * * *", + "id": "102", + }, JobOptions{ + JobId: "102", + Cron: "*/5 * * * * *", + }) + + // simpleC.RunJob(map[string]string{ + // "corn": "*/15 * * * * *", + // "id": "103", + // }, JobOptions{ + // JobId: "103", + // Cron: "*/15 * * * * *", + // }) + + // simpleC.RemoveJob("102") + + select {} +} + +// Foo 队列任务处理 +var NewFooProcessor = &FooProcessor{ + progress: 0, + count: 0, +} + +type FooProcessor struct { + progress int + count int +} + +func (s *FooProcessor) Execute(data any) any { + logger.Infof("执行 %d %d => %+v ", s.count, s.progress, data) + s.count++ + + // 实现任务处理逻辑 + i := 0 + s.progress = i + for i < 10 { + // 获取任务进度 + progress := s.progress + logger.Infof("data: %v => 任务进度:%d", data, progress) + // 延迟响应 + time.Sleep(time.Second * 2) + i++ + // 改变任务进度 + s.progress = i + } + return data +} + +func TestFoo(t *testing.T) { + + foo := CreateQueue("foo", NewFooProcessor) + foo.RunJob(map[string]string{ + "data": "2", + }, JobOptions{ + JobId: "2", + }) + + fooC := CreateQueue("foo", NewFooProcessor) + fooC.RunJob(map[string]string{ + "corn": "*/5 * * * * *", + }, JobOptions{ + JobId: "3", + Cron: "*/5 * * * * *", + }) + + select {} +} + +// Bar 队列任务处理 +var NewBarProcessor = &BarProcessor{ + progress: 0, + count: 0, +} + +type BarProcessor struct { + progress int + count int +} + +func (s *BarProcessor) Execute(data any) any { + logger.Infof("执行 %d %d => %+v ", s.count, s.progress, data) + s.count++ + + // 实现任务处理逻辑 + i := 0 + s.progress = i + for i < 5 { + // 获取任务进度 + progress := s.progress + logger.Infof("data: %v => 任务进度:%d", data, progress) + // 延迟响应 + time.Sleep(time.Second * 2) + // 程序中途执行错误 + if i == 3 { + // arr := [1]int{1} + // arr[i] = 3 + // fmt.Println(arr) + // return "i = 3" + panic("程序中途执行错误") + } + i++ + // 改变任务进度 + s.progress = i + } + + return data +} + +func TestBar(t *testing.T) { + + bar := CreateQueue("bar", NewBarProcessor) + bar.RunJob(map[string]string{ + "data": "wdf", + }, JobOptions{ + JobId: "81923", + }) + + barC := CreateQueue("bar", NewBarProcessor) + barC.RunJob(map[string]string{ + "corn": "*/5 * * * * *", + }, JobOptions{ + JobId: "789", + Cron: "*/5 * * * * *", + }) + + // barDB := CreateQueue("barDB", NewBarProcessor) + // barDB.RunJob(JobData{ + // SysJob: model.SysJob{ + // JobID: "9123", + // JobName: "测试任务", + // }, + // }, JobOptions{ + // JobId: "9123", + // }) + + select {} +} diff --git a/src/framework/cron/log.go b/src/framework/cron/log.go new file mode 100644 index 0000000..0657ab4 --- /dev/null +++ b/src/framework/cron/log.go @@ -0,0 +1,112 @@ +package cron + +import ( + "encoding/json" + "time" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/repository" +) + +// 实例任务执行日志收集 +var newLog = cronlog{} + +// cronlog 任务执行日志收集 +type cronlog struct{} + +// Info 任务普通信息收集 +func (s cronlog) Info(msg string, keysAndValues ...any) { + // logger.Infof("Info msg: %v ====> kv: %v", msg, keysAndValues) + +} + +// Error 任务异常错误收集 +func (s cronlog) Error(err error, msg string, keysAndValues ...any) { + // logger.Errorf("Error: %v -> msg: %v ====> kv: %v", err, msg, keysAndValues) + // logger.Errorf("k0: %v", keysAndValues[0].(*QueueJob)) + + // 指定的错误收集 + if msg == "failed" { + // 任务对象 + job := keysAndValues[0].(*QueueJob) + + // 结果信息序列化字符串 + jsonByte, _ := json.Marshal(map[string]any{ + "name": "failed", + "message": err.Error(), + }) + jobMsg := string(jsonByte) + if len(jobMsg) > 500 { + jobMsg = jobMsg[:500] + } + + // 读取任务信息创建日志对象 + if data, ok := job.Data.(JobData); ok { + duration := time.Since(time.UnixMilli(job.Timestamp)) + sysJob := data.SysJob + if sysJob.JobID == job.Opts.JobId { + sysJobLog := model.SysJobLog{ + JobName: sysJob.JobName, + JobGroup: sysJob.JobGroup, + InvokeTarget: sysJob.InvokeTarget, + TargetParams: sysJob.TargetParams, + Status: common.STATUS_NO, + JobMsg: jobMsg, + CostTime: duration.Milliseconds(), + } + // 插入数据 + repository.NewSysJobLogImpl.InsertJobLog(sysJobLog) + } + } + } +} + +// Completed 任务完成return的结果收集 +func (s cronlog) Completed(result any, msg string, keysAndValues ...any) { + // logger.Infof("Completed: %v -> msg: %v ====> kv: %v", result, msg, keysAndValues) + // logger.Infof("k0: %v", keysAndValues[0].(*QueueJob)) + + // 指定的完成收集 + if msg == "completed" { + // 任务对象 + job := keysAndValues[0].(*QueueJob) + + // 结果信息序列化字符串 + jsonByte, _ := json.Marshal(map[string]any{ + "name": "completed", + "message": result, + }) + jobMsg := string(jsonByte) + if len(jobMsg) > 500 { + jobMsg = jobMsg[:500] + } + + // 读取任务信息创建日志对象 + if data, ok := job.Data.(JobData); ok { + duration := time.Since(time.UnixMilli(job.Timestamp)) + sysJob := data.SysJob + if sysJob.JobID == job.Opts.JobId { + sysJobLog := model.SysJobLog{ + JobName: sysJob.JobName, + JobGroup: sysJob.JobGroup, + InvokeTarget: sysJob.InvokeTarget, + TargetParams: sysJob.TargetParams, + Status: common.STATUS_YES, + JobMsg: jobMsg, + CostTime: duration.Milliseconds(), + } + // 插入数据 + repository.NewSysJobLogImpl.InsertJobLog(sysJobLog) + } + } + } +} + +// JobData 调度任务日志收集结构体,执行任务时传入的接收参数 +type JobData struct { + // 触发执行cron重复多次 + Repeat bool + // 定时任务调度表记录信息 + SysJob model.SysJob +} diff --git a/src/framework/datasource/datasource.go b/src/framework/datasource/datasource.go new file mode 100644 index 0000000..b4b6320 --- /dev/null +++ b/src/framework/datasource/datasource.go @@ -0,0 +1,161 @@ +package datasource + +import ( + "fmt" + "log" + "os" + "regexp" + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/logger" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + gormLog "gorm.io/gorm/logger" +) + +// 数据库连接实例 +var dbMap = make(map[string]*gorm.DB) + +type dialectInfo struct { + dialector gorm.Dialector + logging bool +} + +// 载入数据库连接 +func loadDialect() map[string]dialectInfo { + dialects := make(map[string]dialectInfo, 0) + + // 读取数据源配置 + datasource := config.Get("gorm.datasource").(map[string]any) + for key, value := range datasource { + item := value.(map[string]any) + // 数据库类型对应的数据库连接 + switch item["type"] { + case "mysql": + dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", + item["username"], + item["password"], + item["host"], + item["port"], + item["database"], + ) + dialects[key] = dialectInfo{ + dialector: mysql.Open(dsn), + logging: item["logging"].(bool), + } + default: + logger.Fatalf("%s: %v\n Not Load DB Config Type", key, item) + } + } + + return dialects +} + +// 载入连接日志配置 +func loadLogger() gormLog.Interface { + newLogger := gormLog.New( + log.New(os.Stdout, "[GORM] ", log.LstdFlags), // 将日志输出到控制台 + gormLog.Config{ + SlowThreshold: time.Second, // Slow SQL 阈值 + LogLevel: gormLog.Info, // 日志级别 Silent不输出任何日志 + ParameterizedQueries: false, // 参数化查询SQL 用实际值带入?的执行语句 + Colorful: false, // 彩色日志输出 + }, + ) + return newLogger +} + +// 连接数据库实例 +func Connect() { + // 遍历进行连接数据库实例 + for key, info := range loadDialect() { + opts := &gorm.Config{} + // 是否需要日志输出 + if info.logging { + opts.Logger = loadLogger() + } + // 创建连接 + db, err := gorm.Open(info.dialector, opts) + if err != nil { + logger.Fatalf("failed error db connect: %s", err) + } + // 获取底层 SQL 数据库连接 + sqlDB, err := db.DB() + if err != nil { + logger.Fatalf("failed error underlying SQL database: %v", err) + } + // 测试数据库连接 + err = sqlDB.Ping() + if err != nil { + logger.Fatalf("failed error ping database: %v", err) + } + logger.Infof("database %s connection is successful.", key) + dbMap[key] = db + } +} + +// 关闭数据库实例 +func Close() { + for _, db := range dbMap { + sqlDB, err := db.DB() + if err != nil { + continue + } + if err := sqlDB.Close(); err != nil { + logger.Errorf("fatal error db close: %s", err) + } + } +} + +// 获取默认数据源 +func DefaultDB() *gorm.DB { + source := config.Get("gorm.defaultDataSourceName").(string) + return dbMap[source] +} + +// 获取数据源 +func DB(source string) *gorm.DB { + return dbMap[source] +} + +// RawDB 原生查询语句 +func RawDB(source string, sql string, parameters []any) ([]map[string]any, error) { + // 数据源 + db := DefaultDB() + if source != "" { + db = DB(source) + } + + // 使用正则表达式替换连续的空白字符为单个空格 + fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ") + + // logger.Infof("sql=> %v", fmtSql) + // logger.Infof("parameters=> %v", parameters) + + // 查询结果 + var rows []map[string]any + res := db.Raw(fmtSql, parameters...).Scan(&rows) + if res.Error != nil { + return nil, res.Error + } + return rows, nil +} + +// ExecDB 原生执行语句 +func ExecDB(source string, sql string, parameters []any) (int64, error) { + // 数据源 + db := DefaultDB() + if source != "" { + db = DB(source) + } + // 使用正则表达式替换连续的空白字符为单个空格 + fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ") + // 执行结果 + res := db.Exec(fmtSql, parameters...) + if res.Error != nil { + return 0, res.Error + } + return res.RowsAffected, nil +} diff --git a/src/framework/errorcatch/errorcatch.go b/src/framework/errorcatch/errorcatch.go new file mode 100644 index 0000000..baa4258 --- /dev/null +++ b/src/framework/errorcatch/errorcatch.go @@ -0,0 +1,40 @@ +package errorcatch + +import ( + "fmt" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// ErrorCatch 全局异常捕获 +func ErrorCatch() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + // 在这里处理 Panic 异常,例如记录日志或返回错误信息给客户端 + if err := recover(); err != nil { + logger.Errorf("发生了 Panic 异常: %v", err) + + // 返回错误响应给客户端 + if config.Env() == "prod" { + c.JSON(500, result.ErrMsg("服务器内部错误")) + } else { + // 通过实现 error 接口的 Error() 方法自定义错误类型进行捕获 + switch v := err.(type) { + case error: + c.JSON(500, result.ErrMsg(v.Error())) + default: + c.JSON(500, result.ErrMsg(fmt.Sprint(err))) + } + } + + c.Abort() // 停止执行后续的处理函数 + } + }() + + c.Next() + } +} diff --git a/src/framework/logger/logger.go b/src/framework/logger/logger.go new file mode 100644 index 0000000..543252c --- /dev/null +++ b/src/framework/logger/logger.go @@ -0,0 +1,49 @@ +package logger + +import ( + "log" + + "github.com/spf13/viper" +) + +var logWriter *Logger + +// 初始程序日志 +func InitLogger() { + env := viper.GetString("env") + conf := viper.GetStringMap("logger") + fileDir := conf["filedir"].(string) + fileName := conf["filename"].(string) + level := conf["level"].(int) + maxDay := conf["maxday"].(int) + maxSize := conf["maxsize"].(int) + + newLog, err := NewLogger(env, fileDir, fileName, level, maxDay, maxSize) + if err != nil { + log.Fatalf("failed to initialize logger: %v", err) + } + + logWriter = newLog +} + +// 关闭程序日志写入 +func Close() { + logWriter.Close() +} + +func Infof(format string, v ...any) { + logWriter.Infof(format, v...) +} + +func Warnf(format string, v ...any) { + logWriter.Warnf(format, v...) +} + +func Errorf(format string, v ...any) { + logWriter.Errorf(format, v...) +} + +// Fatalf 抛出错误并退出程序 +func Fatalf(format string, v ...any) { + log.Fatalf(format, v...) +} diff --git a/src/framework/logger/writer.go b/src/framework/logger/writer.go new file mode 100644 index 0000000..70fca2c --- /dev/null +++ b/src/framework/logger/writer.go @@ -0,0 +1,190 @@ +package logger + +import ( + "fmt" + "io" + "log" + "os" + "path/filepath" + "strings" + "time" +) + +// 日志器对象 +type Logger struct { + env string // 运行环境 + filePath string // 文件路径 + fileName string // 文件名 + level int // 日志等级标识 + maxDay int // 保留最长天数 + maxSize int64 // 文件最大空间 + fileHandle *os.File // 文件实例 + logger *log.Logger // 日志实例 + logLevelMap map[int]string // 日志等级标识名 + logDay int // 日志当前日 +} + +const ( + LOG_LEVEL_SILENT = iota + LOG_LEVEL_INFO + LOG_LEVEL_WARN + LOG_LEVEL_ERROR +) + +// NewLogger 实例日志器对象 +func NewLogger(env, fileDir, fileName string, level, maxDay, maxSize int) (*Logger, error) { + logFilePath := filepath.Join(fileDir, fileName) + if err := os.MkdirAll(filepath.Dir(logFilePath), 0750); err != nil { + return nil, fmt.Errorf("failed to mkdir logger dir: %v", err) + } + fileHandle, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + return nil, fmt.Errorf("failed to open log file: %v", err) + } + + writer := io.Writer(fileHandle) + if env == "local" { + writer = io.MultiWriter(fileHandle, os.Stderr) + } + + logger := log.New(writer, "", log.LstdFlags|log.Lshortfile) + + logLevelMap := map[int]string{ + LOG_LEVEL_INFO: "INFO", + LOG_LEVEL_WARN: "WARN", + LOG_LEVEL_ERROR: "ERROR", + } + + stdLogger := &Logger{ + env: env, + filePath: fileDir, + fileName: fileName, + level: level, + maxDay: maxDay, + maxSize: int64(maxSize * 1024 * 1024), + fileHandle: fileHandle, + logger: logger, + logLevelMap: logLevelMap, + logDay: time.Now().Day(), + } + + go stdLogger.checkFile() + + return stdLogger, nil +} + +// checkFile 检查文件分割,自定时调用 +func (l *Logger) checkFile() { + fileInfo, err := l.fileHandle.Stat() + if err != nil { + l.logger.Printf("Failed to get log file info: %v\n", err) + return + } + + currTime := time.Now() + if l.logDay != currTime.Day() { + l.logDay = currTime.Day() + l.rotateFile(currTime.AddDate(0, 0, -1).Format("2006_01_02")) + // 移除超过保存最长天数的文件 + l.removeOldFile(currTime.AddDate(0, 0, -l.maxDay)) + } else if fileInfo.Size() >= l.maxSize { + l.rotateFile(currTime.Format("2006_01_02_150405")) + } else if time.Since(fileInfo.ModTime()).Hours() > 24 { + l.rotateFile(fileInfo.ModTime().Format("2006_01_02")) + } + + time.AfterFunc(1*time.Minute, l.checkFile) +} + +// rotateFile 检查文件大小进行分割 +func (l *Logger) rotateFile(timeFormat string) { + l.fileHandle.Close() + + newFileName := fmt.Sprintf("%s.%s", l.fileName, timeFormat) + newFilePath := filepath.Join(l.filePath, newFileName) + oldfilePath := filepath.Join(l.filePath, l.fileName) + + // 重命名 + os.Rename(oldfilePath, newFilePath) + + // 新文件句柄 + fileHandle, err := os.OpenFile(oldfilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + l.logger.Printf("Failed to open log file: %v\n", err) + return + } + + l.fileHandle = fileHandle + + // 重新设置 logger 的 writer + writer := io.Writer(l.fileHandle) + if l.env == "local" { + writer = io.MultiWriter(l.fileHandle, os.Stderr) + } + l.logger.SetOutput(writer) +} + +// RemoveOldFile 删除旧文件 +func (l *Logger) removeOldFile(oldFileDate time.Time) { + // 遍历目标文件夹中的文件 + files, err := os.ReadDir(l.filePath) + if err != nil { + l.Errorf("logger RemoveOldFile ReadDir err: %v", err.Error()) + return + } + + for _, file := range files { + idx := strings.LastIndex(file.Name(), ".log.") + if idx == -1 { + continue + } + dateStr := file.Name()[idx+5 : idx+15] + + // 解析日期字符串 + fileDate, err := time.Parse("2006_01_02", dateStr) + if err != nil { + l.Errorf("logger RemoveOldFile Parse err: %v", err.Error()) + continue + } + + // 判断文件日期是否在给定日期之前 + if fileDate.Before(oldFileDate) { + // 删除旧文件 + err := os.Remove(filepath.Join(l.filePath, file.Name())) + if err != nil { + l.Errorf("logger RemoveOldFile Remove err: %v", err.Error()) + continue + } + } + } +} + +// writeLog 写入chan +func (l *Logger) writeLog(level int, format string, args ...interface{}) { + if level < l.level { + return + } + + logMsg := fmt.Sprintf("[%s] %s\n", l.logLevelMap[level], fmt.Sprintf(format, args...)) + l.logger.Output(4, logMsg) +} + +func (l *Logger) Infof(format string, args ...interface{}) { + l.writeLog(LOG_LEVEL_INFO, format, args...) +} + +func (l *Logger) Warnf(format string, args ...interface{}) { + l.writeLog(LOG_LEVEL_WARN, format, args...) +} + +func (l *Logger) Errorf(format string, args ...interface{}) { + l.writeLog(LOG_LEVEL_ERROR, format, args...) +} + +// Close 日志关闭 +func (l *Logger) Close() { + err := l.fileHandle.Close() + if err != nil { + l.logger.Printf("Failed to close log file: %v\n", err) + } +} diff --git a/src/framework/middleware/collectlogs/operate_log.go b/src/framework/middleware/collectlogs/operate_log.go new file mode 100644 index 0000000..3c22e46 --- /dev/null +++ b/src/framework/middleware/collectlogs/operate_log.go @@ -0,0 +1,182 @@ +package collectlogs + +import ( + "encoding/json" + "fmt" + "strings" + "time" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +const ( + // 业务操作类型-其它 + BUSINESS_TYPE_OTHER = "0" + + // 业务操作类型-新增 + BUSINESS_TYPE_INSERT = "1" + + // 业务操作类型-修改 + BUSINESS_TYPE_UPDATE = "2" + + // 业务操作类型-删除 + BUSINESS_TYPE_DELETE = "3" + + // 业务操作类型-授权 + BUSINESS_TYPE_GRANT = "4" + + // 业务操作类型-导出 + BUSINESS_TYPE_EXPORT = "5" + + // 业务操作类型-导入 + BUSINESS_TYPE_IMPORT = "6" + + // 业务操作类型-强退 + BUSINESS_TYPE_FORCE = "7" + + // 业务操作类型-清空数据 + BUSINESS_TYPE_CLEAN = "8" +) + +const ( + // 操作人类别-其它 + OPERATOR_TYPE_OTHER = "0" + + // 操作人类别-后台用户 + OPERATOR_TYPE_MANAGE = "1" + + // 操作人类别-手机端用户 + OPERATOR_TYPE_MOBILE = "2" +) + +// Option 操作日志参数 +type Options struct { + Title string `json:"title"` // 标题 + BusinessType string `json:"businessType"` // 类型,默认常量 BUSINESS_TYPE_OTHER + OperatorType string `json:"operatorType"` // 操作人类别,默认常量 OPERATOR_TYPE_OTHER + IsSaveRequestData bool `json:"isSaveRequestData"` // 是否保存请求的参数 + IsSaveResponseData bool `json:"isSaveResponseData"` // 是否保存响应的参数 +} + +// OptionNew 操作日志参数默认值 +// +// 标题 "title":"--" +// +// 类型 "businessType": BUSINESS_TYPE_OTHER +// +// 注意之后JSON反序列使用:c.ShouldBindBodyWith(¶ms, binding.JSON) +func OptionNew(title, businessType string) Options { + return Options{ + Title: title, + BusinessType: businessType, + OperatorType: OPERATOR_TYPE_OTHER, + IsSaveRequestData: true, + IsSaveResponseData: true, + } +} + +// 敏感属性字段进行掩码 +var maskProperties []string = []string{ + "password", + "oldPassword", + "newPassword", + "confirmPassword", +} + +// OperateLog 访问操作日志记录 +// +// 请在用户身份授权认证校验后使用以便获取登录用户信息 +func OperateLog(options Options) gin.HandlerFunc { + return func(c *gin.Context) { + c.Set("startTime", time.Now()) + + // 函数名 + funcName := c.HandlerName() + lastDotIndex := strings.LastIndex(funcName, "/") + funcName = funcName[lastDotIndex+1:] + + // 解析ip地址 + ipaddr, location := ctx.IPAddrLocation(c) + + // 获取登录用户信息 + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, "无效身份授权")) + c.Abort() // 停止执行后续的处理函数 + return + } + + // 操作日志记录 + operLog := model.SysLogOperate{ + Title: options.Title, + BusinessType: options.BusinessType, + OperatorType: options.OperatorType, + Method: funcName, + OperURL: c.Request.RequestURI, + RequestMethod: c.Request.Method, + OperIP: ipaddr, + OperLocation: location, + OperName: loginUser.User.UserName, + DeptName: loginUser.User.Dept.DeptName, + } + + if loginUser.User.UserType == "sys" { + operLog.OperatorType = OPERATOR_TYPE_MANAGE + } + + // 是否需要保存request,参数和值 + if options.IsSaveRequestData { + params := ctx.RequestParamsMap(c) + for k, v := range params { + // 敏感属性字段进行掩码 + for _, s := range maskProperties { + if s == k { + params[k] = parse.SafeContent(v.(string)) + break + } + } + } + jsonStr, _ := json.Marshal(params) + paramsStr := string(jsonStr) + if len(paramsStr) > 2000 { + paramsStr = paramsStr[:2000] + } + operLog.OperParam = paramsStr + } + + // 调用下一个处理程序 + c.Next() + + // 响应状态 + status := c.Writer.Status() + if status == 200 { + operLog.Status = common.STATUS_YES + } else { + operLog.Status = common.STATUS_NO + } + + // 是否需要保存response,参数和值 + if options.IsSaveResponseData { + contentDisposition := c.Writer.Header().Get("Content-Disposition") + contentType := c.Writer.Header().Get("Content-Type") + content := contentType + contentDisposition + msg := fmt.Sprintf(`{"status":"%d","size":"%d","content-type":"%s"}`, status, c.Writer.Size(), content) + operLog.OperMsg = msg + } + + // 日志记录时间 + duration := time.Since(c.GetTime("startTime")) + operLog.CostTime = duration.Milliseconds() + operLog.OperTime = time.Now().UnixMilli() + + // 保存操作记录到数据库 + service.NewSysLogOperateImpl.InsertSysLogOperate(operLog) + } +} diff --git a/src/framework/middleware/cors.go b/src/framework/middleware/cors.go new file mode 100644 index 0000000..12a15a2 --- /dev/null +++ b/src/framework/middleware/cors.go @@ -0,0 +1,83 @@ +package middleware + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// Cors 跨域 +func Cors() gin.HandlerFunc { + return func(c *gin.Context) { + // 设置Vary头部 + c.Header("Vary", "Origin") + c.Header("Keep-Alive", "timeout=5") + + requestOrigin := c.GetHeader("Origin") + if requestOrigin == "" { + c.Next() + return + } + + origin := requestOrigin + if v := config.Get("cors.origin"); v != nil { + origin = v.(string) + } + c.Header("Access-Control-Allow-Origin", origin) + + if v := config.Get("cors.credentials"); v != nil && v.(bool) { + c.Header("Access-Control-Allow-Credentials", "true") + } + + // OPTIONS + if method := c.Request.Method; method == "OPTIONS" { + requestMethod := c.GetHeader("Access-Control-Request-Method") + if requestMethod == "" { + c.Next() + return + } + + // 响应最大时间值 + if v := config.Get("cors.maxAge"); v != nil && v.(int) > 10000 { + c.Header("Access-Control-Max-Age", fmt.Sprint(v)) + } + + // 允许方法 + if v := config.Get("cors.allowMethods"); v != nil { + var allowMethods = make([]string, 0) + for _, s := range v.([]any) { + allowMethods = append(allowMethods, s.(string)) + } + c.Header("Access-Control-Allow-Methods", strings.Join(allowMethods, ",")) + } else { + c.Header("Access-Control-Allow-Methods", "GET,HEAD,PUT,POST,DELETE,PATCH") + } + + // 允许请求头 + if v := config.Get("cors.allowHeaders"); v != nil { + var allowHeaders = make([]string, 0) + for _, s := range v.([]any) { + allowHeaders = append(allowHeaders, s.(string)) + } + c.Header("Access-Control-Allow-Headers", strings.Join(allowHeaders, ",")) + } + + c.AbortWithStatus(204) + return + } + + // 暴露请求头 + if v := config.Get("cors.exposeHeaders"); v != nil { + var exposeHeaders = make([]string, 0) + for _, s := range v.([]any) { + exposeHeaders = append(exposeHeaders, s.(string)) + } + c.Header("Access-Control-Expose-Headers", strings.Join(exposeHeaders, ",")) + } + + c.Next() + } +} diff --git a/src/framework/middleware/pre_authorize.go b/src/framework/middleware/pre_authorize.go new file mode 100644 index 0000000..3fd8988 --- /dev/null +++ b/src/framework/middleware/pre_authorize.go @@ -0,0 +1,180 @@ +package middleware + +import ( + "fmt" + + AdminConstants "ems.agt/src/framework/constants/admin" + commonConstants "ems.agt/src/framework/constants/common" + ctxUtils "ems.agt/src/framework/utils/ctx" + tokenUtils "ems.agt/src/framework/utils/token" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// PreAuthorize 用户身份授权认证校验 +// +// 只需含有其中角色 "hasRoles": {"xxx"}, +// +// 只需含有其中权限 "hasPerms": {"xxx"}, +// +// 同时匹配其中角色 "matchRoles": {"xxx"}, +// +// 同时匹配其中权限 "matchPerms": {"xxx"}, +func PreAuthorize(options map[string][]string) gin.HandlerFunc { + return func(c *gin.Context) { + // 获取请求头标识信息 + tokenStr := ctxUtils.Authorization(c) + if tokenStr == "" { + c.JSON(401, result.CodeMsg(401, "无效身份授权")) + c.Abort() // 停止执行后续的处理函数 + return + } + + // 验证令牌 + claims, err := tokenUtils.Verify(tokenStr) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + c.Abort() // 停止执行后续的处理函数 + return + } + + // 获取缓存的用户信息 + loginUser := tokenUtils.LoginUser(claims) + if loginUser.UserID == "" { + c.JSON(401, result.CodeMsg(401, "无效身份授权")) + c.Abort() // 停止执行后续的处理函数 + return + } + + // 检查刷新有效期后存入上下文 + tokenUtils.RefreshIn(&loginUser) + c.Set(commonConstants.CTX_LOGIN_USER, loginUser) + + // 登录用户角色权限校验 + if options != nil { + var roles []string + for _, item := range loginUser.User.Roles { + roles = append(roles, item.RoleKey) + } + perms := loginUser.Permissions + verifyOk := verifyRolePermission(roles, perms, options) + if !verifyOk { + msg := fmt.Sprintf("无权访问 %s %s", c.Request.Method, c.Request.RequestURI) + c.JSON(403, result.CodeMsg(403, msg)) + c.Abort() // 停止执行后续的处理函数 + return + } + } + + // 调用下一个处理程序 + c.Next() + } +} + +// verifyRolePermission 校验角色权限是否满足 +// +// roles 角色字符数组 +// +// perms 权限字符数组 +// +// options 参数 +func verifyRolePermission(roles, perms []string, options map[string][]string) bool { + // 直接放行 管理员角色或任意权限 + if contains(roles, AdminConstants.ROLE_KEY) || contains(perms, AdminConstants.PERMISSION) { + return true + } + opts := make([]bool, 4) + + // 只需含有其中角色 + hasRole := false + if arr, ok := options["hasRoles"]; ok && len(arr) > 0 { + hasRole = some(roles, arr) + opts[0] = true + } + + // 只需含有其中权限 + hasPerms := false + if arr, ok := options["hasPerms"]; ok && len(arr) > 0 { + hasPerms = some(perms, arr) + opts[1] = true + } + + // 同时匹配其中角色 + matchRoles := false + if arr, ok := options["matchRoles"]; ok && len(arr) > 0 { + matchRoles = every(roles, arr) + opts[2] = true + } + + // 同时匹配其中权限 + matchPerms := false + if arr, ok := options["matchPerms"]; ok && len(arr) > 0 { + matchPerms = every(perms, arr) + opts[3] = true + } + + // 同时判断 含有其中 + if opts[0] && opts[1] { + return hasRole || hasPerms + } + // 同时判断 匹配其中 + if opts[2] && opts[3] { + return matchRoles && matchPerms + } + // 同时判断 含有其中且匹配其中 + if opts[0] && opts[3] { + return hasRole && matchPerms + } + if opts[1] && opts[2] { + return hasPerms && matchRoles + } + + return hasRole || hasPerms || matchRoles || matchPerms +} + +// contains 检查字符串数组中是否包含指定的字符串 +func contains(arr []string, target string) bool { + for _, str := range arr { + if str == target { + return true + } + } + return false +} + +// some 检查字符串数组中含有其中一项 +func some(origin []string, target []string) bool { + has := false + for _, t := range target { + for _, o := range origin { + if t == o { + has = true + break + } + } + if has { + break + } + } + return has +} + +// every 检查字符串数组中同时包含所有项 +func every(origin []string, target []string) bool { + match := true + for _, t := range target { + found := false + for _, o := range origin { + if t == o { + found = true + break + } + } + if !found { + match = false + break + } + } + return match +} diff --git a/src/framework/middleware/rate_limit.go b/src/framework/middleware/rate_limit.go new file mode 100644 index 0000000..5dbf53d --- /dev/null +++ b/src/framework/middleware/rate_limit.go @@ -0,0 +1,101 @@ +package middleware + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/ip2region" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +const ( + // 默认策略全局限流 + LIMIT_GLOBAL = 1 + + // 根据请求者IP进行限流 + LIMIT_IP = 2 + + // 根据用户ID进行限流 + LIMIT_USER = 3 +) + +// LimitOption 请求限流参数 +type LimitOption struct { + Time int64 `json:"time"` // 限流时间,单位秒 + Count int64 `json:"count"` // 限流次数 + Type int64 `json:"type"` // 限流条件类型,默认LIMIT_GLOBAL +} + +// RateLimit 请求限流 +// +// 示例参数:middleware.LimitOption{ Time: 5, Count: 10, Type: middleware.LIMIT_IP } +// +// 参数表示:5秒内,最多请求10次,限制类型为 IP +// +// 使用 USER 时,请在用户身份授权认证校验后使用 +// 以便获取登录用户信息,无用户信息时默认为 GLOBAL +func RateLimit(option LimitOption) gin.HandlerFunc { + return func(c *gin.Context) { + // 初始可选参数数据 + if option.Time < 5 { + option.Time = 5 + } + if option.Count < 10 { + option.Count = 10 + } + if option.Type == 0 { + option.Type = LIMIT_GLOBAL + } + + // 获取执行函数名称 + funcName := c.HandlerName() + lastDotIndex := strings.LastIndex(funcName, "/") + funcName = funcName[lastDotIndex+1:] + // 生成限流key + var limitKey string = cachekey.RATE_LIMIT_KEY + funcName + + // 用户 + if option.Type == LIMIT_USER { + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.Err(map[string]any{ + "code": 401, + "msg": err.Error(), + })) + c.Abort() // 停止执行后续的处理函数 + return + } + limitKey = cachekey.RATE_LIMIT_KEY + loginUser.UserID + ":" + funcName + } + + // IP + if option.Type == LIMIT_IP { + clientIP := ip2region.ClientIP(c.ClientIP()) + limitKey = cachekey.RATE_LIMIT_KEY + clientIP + ":" + funcName + } + + // 在Redis查询并记录请求次数 + rateCount, _ := redis.RateLimit("", limitKey, option.Time, option.Count) + rateTime, _ := redis.GetExpire("", limitKey) + + // 设置响应头中的限流声明字段 + c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", option.Count)) // 总请求数限制 + c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", option.Count-rateCount)) // 剩余可用请求数 + c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Unix()+int64(rateTime))) // 重置时间戳 + + if rateCount >= option.Count { + c.JSON(200, result.ErrMsg("访问过于频繁,请稍候再试")) + c.Abort() // 停止执行后续的处理函数 + return + } + + // 调用下一个处理程序 + c.Next() + } +} diff --git a/src/framework/middleware/repeat/repeat.go b/src/framework/middleware/repeat/repeat.go new file mode 100644 index 0000000..0a84463 --- /dev/null +++ b/src/framework/middleware/repeat/repeat.go @@ -0,0 +1,84 @@ +package repeat + +import ( + "encoding/json" + "strconv" + "time" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/ip2region" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// repeatParam 重复提交参数的类型定义 +type repeatParam struct { + Time int64 `json:"time"` + Params string `json:"params"` +} + +// RepeatSubmit 防止表单重复提交,小于间隔时间视为重复提交 +// +// 间隔时间(单位秒) 默认:5 +// +// 注意之后JSON反序列使用:c.ShouldBindBodyWith(¶ms, binding.JSON) +func RepeatSubmit(interval int64) gin.HandlerFunc { + return func(c *gin.Context) { + if interval < 5 { + interval = 5 + } + + // 提交参数 + params := ctx.RequestParamsMap(c) + paramsJSONByte, err := json.Marshal(params) + if err != nil { + logger.Errorf("RepeatSubmit params json marshal err: %v", err) + } + paramsJSONStr := string(paramsJSONByte) + + // 唯一标识(指定key + 客户端IP + 请求地址) + clientIP := ip2region.ClientIP(c.ClientIP()) + repeatKey := cachekey.REPEAT_SUBMIT_KEY + clientIP + ":" + c.Request.RequestURI + + // 在Redis查询并记录请求次数 + repeatStr, _ := redis.Get("", repeatKey) + if repeatStr != "" { + var rp repeatParam + err := json.Unmarshal([]byte(repeatStr), &rp) + if err != nil { + logger.Errorf("RepeatSubmit repeatStr json unmarshal err: %v", err) + } + compareTime := time.Now().Unix() - rp.Time + compareParams := rp.Params == paramsJSONStr + + // 设置重复提交声明响应头(毫秒) + c.Header("X-RepeatSubmit-Rest", strconv.FormatInt(time.Now().Add(time.Duration(compareTime)*time.Second).UnixNano()/int64(time.Millisecond), 10)) + + // 小于间隔时间且参数内容一致 + if compareTime < interval && compareParams { + c.JSON(200, result.ErrMsg("不允许重复提交,请稍候再试")) + c.Abort() + return + } + } + + // 当前请求参数 + rp := repeatParam{ + Time: time.Now().Unix(), + Params: paramsJSONStr, + } + rpJSON, err := json.Marshal(rp) + if err != nil { + logger.Errorf("RepeatSubmit rp json marshal err: %v", err) + } + // 保存请求时间和参数 + redis.SetByExpire("", repeatKey, string(rpJSON), time.Duration(interval)*time.Second) + + // 调用下一个处理程序 + c.Next() + } +} diff --git a/src/framework/middleware/report.go b/src/framework/middleware/report.go new file mode 100644 index 0000000..74b3a5e --- /dev/null +++ b/src/framework/middleware/report.go @@ -0,0 +1,23 @@ +package middleware + +import ( + "time" + + "ems.agt/src/framework/logger" + + "github.com/gin-gonic/gin" +) + +// Report 请求响应日志 +func Report() gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + + // 调用下一个处理程序 + c.Next() + + // 计算请求处理时间,并打印日志 + duration := time.Since(start) + logger.Infof("%s %s report end=> %v", c.Request.Method, c.Request.RequestURI, duration) + } +} diff --git a/src/framework/middleware/security/csp.go b/src/framework/middleware/security/csp.go new file mode 100644 index 0000000..3d76c81 --- /dev/null +++ b/src/framework/middleware/security/csp.go @@ -0,0 +1,22 @@ +package security + +import ( + "ems.agt/src/framework/config" + "ems.agt/src/framework/utils/generate" + + "github.com/gin-gonic/gin" +) + +// TODO +// csp 这将帮助防止跨站脚本攻击(XSS)。 +// HTTP 响应头 Content-Security-Policy 允许站点管理者控制指定的页面加载哪些资源。 +func csp(c *gin.Context) { + enable := false + if v := config.Get("security.csp.enable"); v != nil { + enable = v.(bool) + } + + if enable { + c.Header("x-csp-nonce", generate.Code(8)) + } +} diff --git a/src/framework/middleware/security/hsts.go b/src/framework/middleware/security/hsts.go new file mode 100644 index 0000000..4feb37c --- /dev/null +++ b/src/framework/middleware/security/hsts.go @@ -0,0 +1,37 @@ +package security + +import ( + "fmt" + + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// hsts 是一个安全功能 HTTP Strict Transport Security(通常简称为 HSTS ) +// 它告诉浏览器只能通过 HTTPS 访问当前资源,而不是 HTTP。 +func hsts(c *gin.Context) { + enable := false + if v := config.Get("security.hsts.enable"); v != nil { + enable = v.(bool) + } + + maxAge := 365 * 24 * 3600 + if v := config.Get("security.hsts.maxAge"); v != nil { + maxAge = v.(int) + } + + includeSubdomains := false + if v := config.Get("security.hsts.includeSubdomains"); v != nil { + includeSubdomains = v.(bool) + } + + str := fmt.Sprintf("max-age=%d", maxAge) + if includeSubdomains { + str += "; includeSubdomains" + } + + if enable { + c.Header("strict-transport-security", str) + } +} diff --git a/src/framework/middleware/security/noopen.go b/src/framework/middleware/security/noopen.go new file mode 100644 index 0000000..e0b5a76 --- /dev/null +++ b/src/framework/middleware/security/noopen.go @@ -0,0 +1,20 @@ +package security + +import ( + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// noopen 用于指定 IE 8 以上版本的用户不打开文件而直接保存文件。 +// 在下载对话框中不显式“打开”选项。 +func noopen(c *gin.Context) { + enable := false + if v := config.Get("security.noopen.enable"); v != nil { + enable = v.(bool) + } + + if enable { + c.Header("x-download-options", "noopen") + } +} diff --git a/src/framework/middleware/security/nosniff.go b/src/framework/middleware/security/nosniff.go new file mode 100644 index 0000000..f8ca6aa --- /dev/null +++ b/src/framework/middleware/security/nosniff.go @@ -0,0 +1,26 @@ +package security + +import ( + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// nosniff 用于防止 XSS 等跨站脚本攻击 +// 如果从 script 或 stylesheet 读入的文件的 MIME 类型与指定 MIME 类型不匹配,不允许读取该文件。 +func nosniff(c *gin.Context) { + // 排除状态码范围 + status := c.Writer.Status() + if status >= 300 && status <= 308 { + return + } + + enable := false + if v := config.Get("security.nosniff.enable"); v != nil { + enable = v.(bool) + } + + if enable { + c.Header("x-content-type-options", "nosniff") + } +} diff --git a/src/framework/middleware/security/referer.go b/src/framework/middleware/security/referer.go new file mode 100644 index 0000000..b716f7f --- /dev/null +++ b/src/framework/middleware/security/referer.go @@ -0,0 +1,74 @@ +package security + +import ( + "net/url" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// referer 配置 referer 的 host 部分 +func referer(c *gin.Context) { + enable := false + if v := config.Get("security.csrf.enable"); v != nil { + enable = v.(bool) + } + + // csrf 校验类型 + okType := false + if v := config.Get("security.csrf.type"); v != nil { + vType := v.(string) + if vType == "all" || vType == "any" || vType == "referer" { + okType = true + } + } + if !okType { + return + } + + // 忽略请求方法 + method := c.Request.Method + ignoreMethods := []string{"GET", "HEAD", "OPTIONS", "TRACE"} + for _, ignore := range ignoreMethods { + if ignore == method { + return + } + } + + referer := c.GetHeader("Referer") + if referer == "" { + c.AbortWithStatusJSON(200, result.ErrMsg("无效 Referer 未知")) + return + } + + // 获取host + u, err := url.Parse(referer) + if err != nil { + c.AbortWithStatusJSON(200, result.ErrMsg("无效 Referer 未知")) + return + } + host := u.Host + + // 允许的来源白名单 + refererWhiteList := make([]string, 0) + if v := config.Get("security.csrf.refererWhiteList"); v != nil { + for _, s := range v.([]any) { + refererWhiteList = append(refererWhiteList, s.(string)) + } + } + + if enable && okType { + ok := false + for _, domain := range refererWhiteList { + if domain == host { + ok = true + } + } + if !ok { + c.AbortWithStatusJSON(200, result.ErrMsg("无效 Referer "+host)) + return + } + } +} diff --git a/src/framework/middleware/security/security.go b/src/framework/middleware/security/security.go new file mode 100644 index 0000000..d922a09 --- /dev/null +++ b/src/framework/middleware/security/security.go @@ -0,0 +1,23 @@ +package security + +import ( + "github.com/gin-gonic/gin" +) + +// Security 安全 +func Security() gin.HandlerFunc { + return func(c *gin.Context) { + // 拦截,判断是否有效Referer + referer(c) + + // 无拦截,仅仅设置响应头 + xframe(c) + csp(c) + hsts(c) + noopen(c) + nosniff(c) + xssProtection(c) + + c.Next() + } +} diff --git a/src/framework/middleware/security/xframe.go b/src/framework/middleware/security/xframe.go new file mode 100644 index 0000000..9fae200 --- /dev/null +++ b/src/framework/middleware/security/xframe.go @@ -0,0 +1,26 @@ +package security + +import ( + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// xframe 用来配置 X-Frame-Options 响应头 +// 用来给浏览器指示允许一个页面可否在 frame, iframe, embed 或者 object 中展现的标记。 +// 站点可以通过确保网站没有被嵌入到别人的站点里面,从而避免 clickjacking 攻击。 +func xframe(c *gin.Context) { + enable := false + if v := config.Get("security.xframe.enable"); v != nil { + enable = v.(bool) + } + + value := "sameorigin" + if v := config.Get("security.xframe.value"); v != nil { + value = v.(string) + } + + if enable { + c.Header("x-frame-options", value) + } +} diff --git a/src/framework/middleware/security/xss_protection.go b/src/framework/middleware/security/xss_protection.go new file mode 100644 index 0000000..88502cc --- /dev/null +++ b/src/framework/middleware/security/xss_protection.go @@ -0,0 +1,24 @@ +package security + +import ( + "ems.agt/src/framework/config" + + "github.com/gin-gonic/gin" +) + +// xssProtection 用于启用浏览器的XSS过滤功能,以防止 XSS 跨站脚本攻击。 +func xssProtection(c *gin.Context) { + enable := false + if v := config.Get("security.xssProtection.enable"); v != nil { + enable = v.(bool) + } + + value := "1; mode=block" + if v := config.Get("security.xssProtection.value"); v != nil { + value = v.(string) + } + + if enable { + c.Header("x-xss-protection", value) + } +} diff --git a/src/framework/redis/redis.go b/src/framework/redis/redis.go new file mode 100644 index 0000000..604d538 --- /dev/null +++ b/src/framework/redis/redis.go @@ -0,0 +1,358 @@ +package redis + +import ( + "context" + "fmt" + "strings" + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/logger" + + "github.com/redis/go-redis/v9" +) + +// Redis连接实例 +var rdbMap = make(map[string]*redis.Client) + +// 声明定义限流脚本命令 +var rateLimitCommand = redis.NewScript(` +local key = KEYS[1] +local time = tonumber(ARGV[1]) +local count = tonumber(ARGV[2]) +local current = redis.call('get', key); +if current and tonumber(current) >= count then + return tonumber(current); +end +current = redis.call('incr', key) +if tonumber(current) == 1 then + redis.call('expire', key, time) +end +return tonumber(current);`) + +// 连接Redis实例 +func Connect() { + ctx := context.Background() + // 读取数据源配置 + datasource := config.Get("redis.dataSource").(map[string]any) + for k, v := range datasource { + client := v.(map[string]any) + // 创建连接 + address := fmt.Sprintf("%s:%d", client["host"], client["port"]) + rdb := redis.NewClient(&redis.Options{ + Addr: address, + Password: client["password"].(string), + DB: client["db"].(int), + }) + // 测试数据库连接 + pong, err := rdb.Ping(ctx).Result() + if err != nil { + logger.Fatalf("Ping redis %s is %v", k, err) + } + logger.Infof("redis %s %s %d connection is successful.", k, pong, client["db"].(int)) + rdbMap[k] = rdb + } +} + +// 关闭Redis实例 +func Close() { + for _, rdb := range rdbMap { + if err := rdb.Close(); err != nil { + logger.Errorf("fatal error db close: %s", err) + } + } +} + +// 获取默认实例 +func DefaultRDB() *redis.Client { + source := config.Get("redis.defaultDataSourceName").(string) + return rdbMap[source] +} + +// 获取实例 +func RDB(source string) *redis.Client { + return rdbMap[source] +} + +// Info 获取redis服务信息 +func Info(source string) map[string]map[string]string { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + info, err := rdb.Info(ctx).Result() + if err != nil { + return map[string]map[string]string{} + } + infoObj := make(map[string]map[string]string) + lines := strings.Split(info, "\r\n") + label := "" + for _, line := range lines { + if strings.Contains(line, "#") { + label = strings.Fields(line)[len(strings.Fields(line))-1] + label = strings.ToLower(label) + infoObj[label] = make(map[string]string) + continue + } + kvArr := strings.Split(line, ":") + if len(kvArr) >= 2 { + key := strings.TrimSpace(kvArr[0]) + value := strings.TrimSpace(kvArr[len(kvArr)-1]) + infoObj[label][key] = value + } + } + return infoObj +} + +// KeySize 获取redis当前连接可用键Key总数信息 +func KeySize(source string) int64 { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + size, err := rdb.DBSize(ctx).Result() + if err != nil { + return 0 + } + return size +} + +// CommandStats 获取redis命令状态信息 +func CommandStats(source string) []map[string]string { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + commandstats, err := rdb.Info(ctx, "commandstats").Result() + if err != nil { + return []map[string]string{} + } + statsObjArr := make([]map[string]string, 0) + lines := strings.Split(commandstats, "\r\n") + for _, line := range lines { + if !strings.HasPrefix(line, "cmdstat_") { + continue + } + kvArr := strings.Split(line, ":") + key := kvArr[0] + valueStr := kvArr[len(kvArr)-1] + statsObj := make(map[string]string) + statsObj["name"] = key[8:] + statsObj["value"] = valueStr[6:strings.Index(valueStr, ",usec=")] + statsObjArr = append(statsObjArr, statsObj) + } + return statsObjArr +} + +// 获取键的剩余有效时间(秒) +func GetExpire(source string, key string) (float64, error) { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + ttl, err := rdb.TTL(ctx, key).Result() + if err != nil { + return 0, err + } + return ttl.Seconds(), nil +} + +// 获得缓存数据的key列表 +func GetKeys(source string, pattern string) ([]string, error) { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + // 初始化变量 + var keys []string + var cursor uint64 = 0 + ctx := context.Background() + // 循环遍历获取匹配的键 + for { + // 使用 SCAN 命令获取匹配的键 + batchKeys, nextCursor, err := rdb.Scan(ctx, cursor, pattern, 100).Result() + if err != nil { + logger.Errorf("Failed to scan keys: %v", err) + return keys, err + } + cursor = nextCursor + keys = append(keys, batchKeys...) + // 当 cursor 为 0,表示遍历完成 + if cursor == 0 { + break + } + } + return keys, nil +} + +// 批量获得缓存数据 +func GetBatch(source string, keys []string) ([]any, error) { + if len(keys) == 0 { + return []any{}, fmt.Errorf("not keys") + } + + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + // 获取缓存数据 + result, err := rdb.MGet(context.Background(), keys...).Result() + if err != nil { + logger.Errorf("Failed to get batch data: %v", err) + return []any{}, err + } + return result, nil +} + +// 获得缓存数据 +func Get(source, key string) (string, error) { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + value, err := rdb.Get(ctx, key).Result() + if err == redis.Nil || err != nil { + return "", err + } + return value, nil +} + +// 获得缓存数据Hash +func GetHash(source, key string) (map[string]string, error) { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + value, err := rdb.HGetAll(ctx, key).Result() + if err == redis.Nil || err != nil { + return map[string]string{}, err + } + return value, nil +} + +// 判断是否存在 +func Has(source string, keys ...string) (bool, error) { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + exists, err := rdb.Exists(ctx, keys...).Result() + if err != nil { + return false, err + } + return exists >= 1, nil +} + +// 设置缓存数据 +func Set(source, key string, value any) (bool, error) { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + err := rdb.Set(ctx, key, value, 0).Err() + if err != nil { + logger.Errorf("redis Set err %v", err) + return false, err + } + return true, nil +} + +// 设置缓存数据与过期时间 +func SetByExpire(source, key string, value any, expiration time.Duration) (bool, error) { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + err := rdb.Set(ctx, key, value, expiration).Err() + if err != nil { + logger.Errorf("redis SetByExpire err %v", err) + return false, err + } + return true, nil +} + +// 删除单个 +func Del(source string, key string) (bool, error) { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + err := rdb.Del(ctx, key).Err() + if err != nil { + logger.Errorf("redis Del err %v", err) + return false, err + } + return true, nil +} + +// 删除多个 +func DelKeys(source string, keys []string) (bool, error) { + if len(keys) == 0 { + return false, fmt.Errorf("no keys") + } + + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + err := rdb.Del(ctx, keys...).Err() + if err != nil { + logger.Errorf("redis DelKeys err %v", err) + return false, err + } + return true, nil +} + +// 限流查询并记录 +func RateLimit(source, limitKey string, time, count int64) (int64, error) { + // 数据源 + rdb := DefaultRDB() + if source != "" { + rdb = RDB(source) + } + + ctx := context.Background() + result, err := rateLimitCommand.Run(ctx, rdb, []string{limitKey}, time, count).Result() + if err != nil { + logger.Errorf("redis RateLimit err %v", err) + return 0, err + } + return result.(int64), err +} diff --git a/src/framework/utils/crypto/crypto.go b/src/framework/utils/crypto/crypto.go new file mode 100644 index 0000000..c242f7f --- /dev/null +++ b/src/framework/utils/crypto/crypto.go @@ -0,0 +1,20 @@ +package crypto + +import ( + "golang.org/x/crypto/bcrypt" +) + +// BcryptHash Bcrypt密码加密 +func BcryptHash(originStr string) string { + hash, err := bcrypt.GenerateFromPassword([]byte(originStr), bcrypt.DefaultCost) + if err != nil { + return "" + } + return string(hash) +} + +// BcryptCompare Bcrypt密码匹配检查 +func BcryptCompare(originStr, hashStr string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hashStr), []byte(originStr)) + return err == nil +} diff --git a/src/framework/utils/ctx/ctx.go b/src/framework/utils/ctx/ctx.go new file mode 100644 index 0000000..7de6ace --- /dev/null +++ b/src/framework/utils/ctx/ctx.go @@ -0,0 +1,199 @@ +package ctx + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/constants/roledatascope" + "ems.agt/src/framework/constants/token" + "ems.agt/src/framework/utils/ip2region" + "ems.agt/src/framework/utils/ua" + "ems.agt/src/framework/vo" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// QueryMap 查询参数转换Map +func QueryMap(c *gin.Context) map[string]any { + queryValues := c.Request.URL.Query() + queryParams := make(map[string]any) + for key, values := range queryValues { + queryParams[key] = values[0] + } + return queryParams +} + +// BodyJSONMap JSON参数转换Map +func BodyJSONMap(c *gin.Context) map[string]any { + params := make(map[string]any) + c.ShouldBindBodyWith(¶ms, binding.JSON) + return params +} + +// RequestParamsMap 请求参数转换Map +func RequestParamsMap(c *gin.Context) map[string]any { + params := make(map[string]any) + // json + if strings.HasPrefix(c.ContentType(), "application/json") { + c.ShouldBindBodyWith(¶ms, binding.JSON) + } + + // 表单 + bodyParams := c.Request.PostForm + for key, value := range bodyParams { + params[key] = value[0] + } + + // 查询 + queryParams := c.Request.URL.Query() + for key, value := range queryParams { + params[key] = value[0] + } + return params +} + +// IPAddrLocation 解析ip地址 +func IPAddrLocation(c *gin.Context) (string, string) { + ip := ip2region.ClientIP(c.ClientIP()) + location := ip2region.RealAddressByIp(ip) + return ip, location +} + +// Authorization 解析请求头 +func Authorization(c *gin.Context) string { + authHeader := c.GetHeader(token.HEADER_KEY) + if authHeader == "" { + return "" + } + // 拆分 Authorization 请求头,提取 JWT 令牌部分 + arr := strings.Split(authHeader, token.HEADER_PREFIX) + if len(arr) == 2 && arr[1] == "" { + return "" + } + return arr[1] +} + +// UaOsBrowser 解析请求用户代理信息 +func UaOsBrowser(c *gin.Context) (string, string) { + userAgent := c.GetHeader("user-agent") + uaInfo := ua.Info(userAgent) + + browser := "未知 未知" + bName, bVersion := uaInfo.Browser() + if bName != "" && bVersion != "" { + browser = bName + " " + bVersion + } + + os := "未知 未知" + bos := uaInfo.OS() + if bos != "" { + os = bos + } + return os, browser +} + +// LoginUser 登录用户信息 +func LoginUser(c *gin.Context) (vo.LoginUser, error) { + value, exists := c.Get(common.CTX_LOGIN_USER) + if exists { + return value.(vo.LoginUser), nil + } + return vo.LoginUser{}, fmt.Errorf("无效登录用户信息") +} + +// LoginUserToUserID 登录用户信息-用户ID +func LoginUserToUserID(c *gin.Context) string { + value, exists := c.Get(common.CTX_LOGIN_USER) + if exists { + loginUser := value.(vo.LoginUser) + return loginUser.UserID + } + return "" +} + +// LoginUserToUserName 登录用户信息-用户名称 +func LoginUserToUserName(c *gin.Context) string { + value, exists := c.Get(common.CTX_LOGIN_USER) + if exists { + loginUser := value.(vo.LoginUser) + return loginUser.User.UserName + } + return "" +} + +// LoginUserToDataScopeSQL 登录用户信息-角色数据范围过滤SQL字符串 +func LoginUserToDataScopeSQL(c *gin.Context, deptAlias string, userAlias string) string { + dataScopeSQL := "" + // 登录用户信息 + loginUser, err := LoginUser(c) + if err != nil { + return dataScopeSQL + } + userInfo := loginUser.User + + // 如果是管理员,则不过滤数据 + if config.IsAdmin(userInfo.UserID) { + return dataScopeSQL + } + // 无用户角色 + if len(userInfo.Roles) <= 0 { + return dataScopeSQL + } + + // 记录角色权限范围定义添加过, 非自定数据权限不需要重复拼接SQL + var scopeKeys []string + var conditions []string + for _, role := range userInfo.Roles { + dataScope := role.DataScope + + if roledatascope.ALL == dataScope { + break + } + + if roledatascope.CUSTOM != dataScope { + hasKey := false + for _, key := range scopeKeys { + if key == dataScope { + hasKey = true + break + } + } + if hasKey { + continue + } + } + + if roledatascope.CUSTOM == dataScope { + sql := fmt.Sprintf(`%s.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = '%s' )`, deptAlias, role.RoleID) + conditions = append(conditions, sql) + } + + if roledatascope.DEPT_AND_CHILD == dataScope { + sql := fmt.Sprintf(`%s.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = '%s' or find_in_set('%s' , ancestors ) )`, deptAlias, userInfo.DeptID, userInfo.DeptID) + conditions = append(conditions, sql) + } + + if roledatascope.SELF == dataScope { + // 数据权限为仅本人且没有userAlias别名不查询任何数据 + if userAlias == "" { + sql := fmt.Sprintf(`%s.dept_id = '0'`, deptAlias) + conditions = append(conditions, sql) + } else { + sql := fmt.Sprintf(`%s.user_id = '%s'`, userAlias, userInfo.UserID) + conditions = append(conditions, sql) + } + } + + // 记录角色范围 + scopeKeys = append(scopeKeys, dataScope) + } + + // 构建查询条件语句 + if len(conditions) > 0 { + dataScopeSQL = fmt.Sprintf(" AND ( %s ) ", strings.Join(conditions, " OR ")) + } + return dataScopeSQL +} diff --git a/src/framework/utils/date/date.go b/src/framework/utils/date/date.go new file mode 100644 index 0000000..c8cfa69 --- /dev/null +++ b/src/framework/utils/date/date.go @@ -0,0 +1,69 @@ +package date + +import ( + "time" + + "ems.agt/src/framework/logger" +) + +const ( + // 年 列如:2022 + YYYY = "2006" + // 年-月 列如:2022-12 + YYYY_MM = "2006-01" + // 年-月-日 列如:2022-12-30 + YYYY_MM_DD = "2006-01-02" + // 年月日时分秒 列如:20221230010159 + YYYYMMDDHHMMSS = "20060102150405" + // 年-月-日 时:分:秒 列如:2022-12-30 01:01:59 + YYYY_MM_DD_HH_MM_SS = "2006-01-02 15:04:05" +) + +// 格式时间字符串 +// +// dateStr 时间字符串 +// +// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss +func ParseStrToDate(dateStr, formatStr string) time.Time { + t, err := time.Parse(formatStr, dateStr) + if err != nil { + logger.Infof("utils ParseStrToDate err %v", err) + return time.Time{} + } + return t +} + +// 格式时间 +// +// date 可转的Date对象 +// +// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss +func ParseDateToStr(date any, formatStr string) string { + t, ok := date.(time.Time) + if !ok { + switch v := date.(type) { + case int64: + if v == 0 { + return "" + } + t = time.UnixMilli(v) + case string: + parsedTime, err := time.Parse(formatStr, v) + if err != nil { + logger.Infof("utils ParseDateToStr err %v", err) + return "" + } + t = parsedTime + default: + return "" + } + } + return t.Format(formatStr) +} + +// 格式时间成日期路径 +// +// 年/月 列如:2022/12 +func ParseDatePath(date time.Time) string { + return date.Format("2006/01") +} diff --git a/src/framework/utils/file/excel.go b/src/framework/utils/file/excel.go new file mode 100644 index 0000000..22a423d --- /dev/null +++ b/src/framework/utils/file/excel.go @@ -0,0 +1,154 @@ +package file + +import ( + "fmt" + "mime/multipart" + "os" + "path/filepath" + "time" + + "ems.agt/src/framework/constants/uploadsubpath" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + + "github.com/xuri/excelize/v2" +) + +// TransferExeclUploadFile 表格文件上传保存 +// +// file 上传文件对象 +func TransferExeclUploadFile(file *multipart.FileHeader) (string, error) { + // 上传文件检查 + err := isAllowWrite(file.Filename, []string{".xls", ".xlsx"}, file.Size) + if err != nil { + return "", err + } + // 上传资源路径 + _, dir := resourceUpload() + // 新文件名称并组装文件地址 + filePath := filepath.Join(uploadsubpath.IMPORT, date.ParseDatePath(time.Now())) + fileName := generateFileName(file.Filename) + writePathFile := filepath.Join(dir, filePath, fileName) + // 存入新文件路径 + err = transferToNewFile(file, writePathFile) + if err != nil { + return "", err + } + return filepath.ToSlash(writePathFile), nil +} + +// 表格读取数据 +// +// filePath 文件路径地址 +// +// sheetName 工作簿名称, 空字符默认Sheet1 +func ReadSheet(filePath, sheetName string) ([]map[string]string, error) { + data := make([]map[string]string, 0) + // 打开 Excel 文件 + f, err := excelize.OpenFile(filePath) + if err != nil { + return data, err + } + defer func() { + if err := f.Close(); err != nil { + logger.Errorf("工作表文件关闭失败 : %v", err) + } + }() + + // 检查工作簿是否存在 + if sheetName == "" { + sheetName = "Sheet1" + } + if visible, _ := f.GetSheetVisible(sheetName); !visible { + return data, fmt.Errorf("读取工作簿 %s 失败", sheetName) + } + + // 获取工作簿记录 + rows, err := f.GetRows(sheetName) + if err != nil { + return data, err + } + + for i, row := range rows { + // 跳过第一行 + if i == 0 { + continue + } + // 遍历每个单元格 + rowData := map[string]string{} + for columnIndex, cellValue := range row { + columnName, _ := excelize.ColumnNumberToName(columnIndex + 1) + rowData[columnName] = cellValue + } + + data = append(data, rowData) + } + return data, nil +} + +// 表格写入数据 +// +// headerCells 第一行表头标题 "A1":"?" +// +// dataCells 从第二行开始的数据 "A2":"?" +// +// fileName 文件名称 +// +// sheetName 工作簿名称, 空字符默认Sheet1 +func WriteSheet(headerCells map[string]string, dataCells []map[string]any, fileName, sheetName string) (string, error) { + f := excelize.NewFile() + defer func() { + if err := f.Close(); err != nil { + logger.Errorf("工作表文件关闭失败 : %v", err) + } + }() + + // 创建一个工作表 + if sheetName == "" { + sheetName = "Sheet1" + } + index, err := f.NewSheet(sheetName) + if err != nil { + return "", fmt.Errorf("创建工作表失败 %v", err) + } + // 设置工作簿的默认工作表 + f.SetActiveSheet(index) + + // 首个和最后key名称 + firstKey := "A" + lastKey := "B" + + // 第一行表头标题 + for key, title := range headerCells { + f.SetCellValue(sheetName, key, title) + if key[:1] > lastKey { + lastKey = key[:1] + } + } + + // 设置工作表上宽度为 20 + f.SetColWidth(sheetName, firstKey, lastKey, 20) + + // 从第二行开始的数据 + for _, cell := range dataCells { + for key, value := range cell { + f.SetCellValue(sheetName, key, value) + } + } + + // 上传资源路径 + _, dir := resourceUpload() + filePath := filepath.Join(uploadsubpath.EXPORT, date.ParseDatePath(time.Now())) + saveFilePath := filepath.Join(dir, filePath, fileName) + + // 创建文件目录 + if err := os.MkdirAll(filepath.Dir(saveFilePath), 0750); err != nil { + return "", fmt.Errorf("创建保存文件失败 %v", err) + } + + // 根据指定路径保存文件 + if err := f.SaveAs(saveFilePath); err != nil { + return "", fmt.Errorf("保存工作表失败 %v", err) + } + return saveFilePath, nil +} diff --git a/src/framework/utils/file/file.go b/src/framework/utils/file/file.go new file mode 100644 index 0000000..9a95d9c --- /dev/null +++ b/src/framework/utils/file/file.go @@ -0,0 +1,297 @@ +package file + +import ( + "errors" + "fmt" + "mime/multipart" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/uploadsubpath" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/generate" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/regular" +) + +/**最大文件名长度 */ +const DEFAULT_FILE_NAME_LENGTH = 100 + +// 文件上传路径 prefix, dir +func resourceUpload() (string, string) { + upload := config.Get("staticFile.upload").(map[string]any) + dir, err := filepath.Abs(upload["dir"].(string)) + if err != nil { + logger.Errorf("file resourceUpload err %v", err) + } + return upload["prefix"].(string), dir +} + +// 最大上传文件大小 +func uploadFileSize() int64 { + fileSize := 1 * 1024 * 1024 + size := config.Get("upload.fileSize").(int) + if size > 1 { + fileSize = size * 1024 * 1024 + } + return int64(fileSize) +} + +// 文件上传扩展名白名单 +func uploadWhiteList() []string { + arr := config.Get("upload.whitelist").([]any) + strings := make([]string, len(arr)) + for i, val := range arr { + if str, ok := val.(string); ok { + strings[i] = str + } + } + return strings +} + +// 生成文件名称 fileName_随机值.extName +// +// fileName 原始文件名称含后缀,如:logo.png +func generateFileName(fileName string) string { + fileExt := filepath.Ext(fileName) + // 替换掉后缀和特殊字符保留文件名 + newFileName := regular.Replace(fileName, fileExt, "") + newFileName = regular.Replace(newFileName, `[<>:"\\|?*]+`, "") + newFileName = strings.TrimSpace(newFileName) + return fmt.Sprintf("%s_%s%s", newFileName, generate.Code(6), fileExt) +} + +// 检查文件允许写入本地 +// +// fileName 原始文件名称含后缀,如:midway1_logo_iipc68.png +// +// allowExts 允许上传拓展类型,['.png'] +func isAllowWrite(fileName string, allowExts []string, fileSize int64) error { + // 判断上传文件名称长度 + if len(fileName) > DEFAULT_FILE_NAME_LENGTH { + return fmt.Errorf("上传文件名称长度限制最长为 %d", DEFAULT_FILE_NAME_LENGTH) + } + + // 最大上传文件大小 + maxFileSize := uploadFileSize() + if fileSize > maxFileSize { + return fmt.Errorf("最大上传文件大小 %s", parse.Bit(float64(maxFileSize))) + } + + // 判断文件拓展是否为允许的拓展类型 + fileExt := filepath.Ext(fileName) + hasExt := false + if len(allowExts) == 0 { + allowExts = uploadWhiteList() + } + for _, ext := range allowExts { + if ext == fileExt { + hasExt = true + break + } + } + if !hasExt { + return fmt.Errorf("上传文件类型不支持,仅支持以下类型:%s", strings.Join(allowExts, "、")) + } + + return nil +} + +// 检查文件允许本地读取 +// +// filePath 文件存放资源路径,URL相对地址 +func isAllowRead(filePath string) error { + // 禁止目录上跳级别 + if strings.Contains(filePath, "..") { + return fmt.Errorf("禁止目录上跳级别") + } + + // 检查允许下载的文件规则 + fileExt := filepath.Ext(filePath) + hasExt := false + for _, ext := range uploadWhiteList() { + if ext == fileExt { + hasExt = true + break + } + } + if !hasExt { + return fmt.Errorf("非法下载的文件规则:%s", fileExt) + } + + return nil +} + +// TransferUploadFile 上传资源文件转存 +// +// subPath 子路径,默认 UploadSubPath.DEFAULT +// +// allowExts 允许上传拓展类型(含“.”),如 ['.png','.jpg'] +func TransferUploadFile(file *multipart.FileHeader, subPath string, allowExts []string) (string, error) { + // 上传文件检查 + err := isAllowWrite(file.Filename, allowExts, file.Size) + if err != nil { + return "", err + } + // 上传资源路径 + prefix, dir := resourceUpload() + // 新文件名称并组装文件地址 + fileName := generateFileName(file.Filename) + filePath := filepath.Join(subPath, date.ParseDatePath(time.Now())) + writePathFile := filepath.Join(dir, filePath, fileName) + // 存入新文件路径 + err = transferToNewFile(file, writePathFile) + if err != nil { + return "", err + } + urlPath := filepath.Join(prefix, filePath, fileName) + return filepath.ToSlash(urlPath), nil +} + +// ReadUploadFileStream 上传资源文件读取 +// +// filePath 文件存放资源路径,URL相对地址 如:/upload/common/2023/06/xxx.png +// +// headerRange 断点续传范围区间,bytes=0-12131 +func ReadUploadFileStream(filePath, headerRange string) (map[string]any, error) { + // 读取文件检查 + err := isAllowRead(filePath) + if err != nil { + return map[string]any{}, err + } + // 上传资源路径 + prefix, dir := resourceUpload() + fileAsbPath := strings.Replace(filePath, prefix, dir, 1) + + // 响应结果 + result := map[string]any{ + "range": "", + "chunkSize": 0, + "fileSize": 0, + "data": nil, + } + + // 文件大小 + fileSize := getFileSize(fileAsbPath) + if fileSize <= 0 { + return result, nil + } + result["fileSize"] = fileSize + + if headerRange != "" { + partsStr := strings.Replace(headerRange, "bytes=", "", 1) + parts := strings.Split(partsStr, "-") + start, err := strconv.ParseInt(parts[0], 10, 64) + if err != nil || start > fileSize { + start = 0 + } + end, err := strconv.ParseInt(parts[1], 10, 64) + if err != nil || end > fileSize { + end = fileSize - 1 + } + if start > end { + start = end + } + + // 分片结果 + result["range"] = fmt.Sprintf("bytes %d-%d/%d", start, end, fileSize) + result["chunkSize"] = end - start + 1 + byteArr, err := getFileStream(fileAsbPath, start, end) + if err != nil { + return map[string]any{}, errors.New("读取文件失败") + } + result["data"] = byteArr + return result, nil + } + + byteArr, err := getFileStream(fileAsbPath, 0, fileSize) + if err != nil { + return map[string]any{}, errors.New("读取文件失败") + } + result["data"] = byteArr + return result, nil +} + +// TransferChunkUploadFile 上传资源切片文件转存 +// +// file 上传文件对象 +// +// index 切片文件序号 +// +// identifier 切片文件目录标识符 +func TransferChunkUploadFile(file *multipart.FileHeader, index, identifier string) (string, error) { + // 上传文件检查 + err := isAllowWrite(file.Filename, []string{}, file.Size) + if err != nil { + return "", err + } + // 上传资源路径 + prefix, dir := resourceUpload() + // 新文件名称并组装文件地址 + filePath := filepath.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier) + writePathFile := filepath.Join(dir, filePath, index) + // 存入新文件路径 + err = transferToNewFile(file, writePathFile) + if err != nil { + return "", err + } + urlPath := filepath.Join(prefix, filePath, index) + return filepath.ToSlash(urlPath), nil +} + +// 上传资源切片文件检查 +// +// identifier 切片文件目录标识符 +// +// originalFileName 原始文件名称,如logo.png +func ChunkCheckFile(identifier, originalFileName string) ([]string, error) { + // 读取文件检查 + err := isAllowWrite(originalFileName, []string{}, 0) + if err != nil { + return []string{}, err + } + // 上传资源路径 + _, dir := resourceUpload() + dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier) + readPath := path.Join(dir, dirPath) + fileList, err := getDirFileNameList(readPath) + if err != nil { + return []string{}, errors.New("读取文件失败") + } + return fileList, nil +} + +// 上传资源切片文件检查 +// +// identifier 切片文件目录标识符 +// +// originalFileName 原始文件名称,如logo.png +// +// subPath 子路径,默认 DEFAULT +func ChunkMergeFile(identifier, originalFileName, subPath string) (string, error) { + // 读取文件检查 + err := isAllowWrite(originalFileName, []string{}, 0) + if err != nil { + return "", err + } + // 上传资源路径 + prefix, dir := resourceUpload() + // 切片存放目录 + dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier) + readPath := path.Join(dir, dirPath) + // 组合存放文件路径 + fileName := generateFileName(originalFileName) + filePath := path.Join(subPath, date.ParseDatePath(time.Now())) + writePath := path.Join(dir, filePath) + err = mergeToNewFile(readPath, writePath, fileName) + if err != nil { + return "", err + } + urlPath := filepath.Join(prefix, filePath, fileName) + return filepath.ToSlash(urlPath), nil +} diff --git a/src/framework/utils/file/utils.go b/src/framework/utils/file/utils.go new file mode 100644 index 0000000..0a0f4b5 --- /dev/null +++ b/src/framework/utils/file/utils.go @@ -0,0 +1,185 @@ +package file + +import ( + "fmt" + "io" + "mime/multipart" + "os" + "path/filepath" + "sort" + "strconv" + + "ems.agt/src/framework/logger" +) + +// transferToNewFile 读取目标文件转移到新路径下 +// +// readFilePath 读取目标文件 +// +// writePath 写入路径 +// +// fileName 文件名称 +func transferToNewFile(file *multipart.FileHeader, dst string) error { + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + if err = os.MkdirAll(filepath.Dir(dst), 0750); err != nil { + return err + } + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, src) + return err +} + +// mergeToNewFile 将多个文件合并成一个文件并删除合并前的切片目录文件 +// +// dirPath 读取要合并文件的目录 +// +// writePath 写入路径 +// +// fileName 文件名称 +func mergeToNewFile(dirPath string, writePath string, fileName string) error { + // 读取目录下所有文件并排序,注意文件名称是否数值 + fileNameList, err := getDirFileNameList(dirPath) + if err != nil { + return fmt.Errorf("读取合并目标文件失败: %v", err) + } + if len(fileNameList) <= 0 { + return fmt.Errorf("读取合并目标文件失败") + } + + // 排序 + sort.Slice(fileNameList, func(i, j int) bool { + numI, _ := strconv.Atoi(fileNameList[i]) + numJ, _ := strconv.Atoi(fileNameList[j]) + return numI < numJ + }) + + // 写入到新路径文件 + newFilePath := filepath.Join(writePath, fileName) + if err = os.MkdirAll(filepath.Dir(newFilePath), 0750); err != nil { + return err + } + + // 转移完成后删除切片目录 + defer os.Remove(dirPath) + + // 打开新路径文件 + outputFile, err := os.Create(newFilePath) + if err != nil { + return fmt.Errorf("failed to create file: %v", err) + } + defer outputFile.Close() + + // 逐个读取文件后进行流拷贝数据块 + for _, fileName := range fileNameList { + chunkPath := filepath.Join(dirPath, fileName) + // 拷贝结束后删除切片 + defer os.Remove(chunkPath) + // 打开切片文件 + inputFile, err := os.Open(chunkPath) + if err != nil { + return fmt.Errorf("failed to open file: %v", err) + } + defer inputFile.Close() + // 拷贝文件流 + _, err = io.Copy(outputFile, inputFile) + if err != nil { + return fmt.Errorf("failed to copy file data: %w", err) + } + } + + return nil +} + +// getFileSize 读取文件大小 +func getFileSize(filePath string) int64 { + // 获取文件信息 + fileInfo, err := os.Stat(filePath) + if err != nil { + logger.Errorf("Failed stat %s: %v", filePath, err) + return 0 + } + // 获取文件大小(字节数) + return fileInfo.Size() +} + +// 读取文件流用于返回下载 +// +// filePath 文件路径 +// startOffset, endOffset 分片块读取区间,根据文件切片的块范围 +func getFileStream(filePath string, startOffset, endOffset int64) ([]byte, error) { + // 打开文件 + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer file.Close() + + // 获取文件的大小 + fileInfo, err := file.Stat() + if err != nil { + return nil, err + } + fileSize := fileInfo.Size() + + // 确保起始和结束偏移量在文件范围内 + if startOffset > fileSize { + startOffset = 0 + } + if endOffset >= fileSize { + endOffset = fileSize - 1 + } + + // 计算切片的大小 + chunkSize := endOffset - startOffset + 1 + + // 创建 SectionReader + reader := io.NewSectionReader(file, startOffset, chunkSize) + + // 创建一个缓冲区来存储读取的数据 + buffer := make([]byte, chunkSize) + + // 读取数据到缓冲区 + _, err = reader.Read(buffer) + if err != nil && err != io.EOF { + return nil, err + } + + return buffer, nil +} + +// 获取文件目录下所有文件名称,不含目录名称 +// +// filePath 文件路径 +func getDirFileNameList(dirPath string) ([]string, error) { + fileNames := []string{} + + dir, err := os.Open(dirPath) + if err != nil { + return fileNames, nil + } + defer dir.Close() + + fileInfos, err := dir.Readdir(-1) + if err != nil { + return fileNames, err + } + + for _, fileInfo := range fileInfos { + if fileInfo.Mode().IsRegular() { + fileNames = append(fileNames, fileInfo.Name()) + } + } + + return fileNames, nil +} diff --git a/src/framework/utils/generate/generate.go b/src/framework/utils/generate/generate.go new file mode 100644 index 0000000..f7ca157 --- /dev/null +++ b/src/framework/utils/generate/generate.go @@ -0,0 +1,43 @@ +package generate + +import ( + "math/rand" + "time" + + "ems.agt/src/framework/logger" + + gonanoid "github.com/matoous/go-nanoid/v2" +) + +// 生成随机Code +// 包含数字、小写字母 +// 不保证长度满足 +func Code(size int) string { + str, err := gonanoid.Generate("0123456789abcdefghijklmnopqrstuvwxyz", size) + if err != nil { + logger.Infof("%d : %v", size, err) + return "" + } + return str +} + +// 生成随机字符串 +// 包含数字、大小写字母、下划线、横杠 +// 不保证长度满足 +func String(size int) string { + str, err := gonanoid.New(size) + if err != nil { + logger.Infof("%d : %v", size, err) + return "" + } + return str +} + +// 随机数 纯数字0-9 +func Number(size int) int { + source := rand.NewSource(time.Now().UnixNano()) + random := rand.New(source) + min := int64(0) + max := int64(9 * int(size)) + return int(random.Int63n(max-min+1) + min) +} diff --git a/src/framework/utils/ip2region/binding.go b/src/framework/utils/ip2region/binding.go new file mode 100644 index 0000000..4782f67 --- /dev/null +++ b/src/framework/utils/ip2region/binding.go @@ -0,0 +1,238 @@ +package ip2region + +import ( + "encoding/binary" + "fmt" + "os" +) + +const ( + HeaderInfoLength = 256 + VectorIndexRows = 256 + VectorIndexCols = 256 + VectorIndexSize = 8 + SegmentIndexBlockSize = 14 +) + +// --- Index policy define + +type IndexPolicy int + +const ( + VectorIndexPolicy IndexPolicy = 1 + BTreeIndexPolicy IndexPolicy = 2 +) + +func (i IndexPolicy) String() string { + switch i { + case VectorIndexPolicy: + return "VectorIndex" + case BTreeIndexPolicy: + return "BtreeIndex" + default: + return "unknown" + } +} + +// --- Header define + +type Header struct { + // data []byte + Version uint16 + IndexPolicy IndexPolicy + CreatedAt uint32 + StartIndexPtr uint32 + EndIndexPtr uint32 +} + +func NewHeader(input []byte) (*Header, error) { + if len(input) < 16 { + return nil, fmt.Errorf("invalid input buffer") + } + + return &Header{ + Version: binary.LittleEndian.Uint16(input), + IndexPolicy: IndexPolicy(binary.LittleEndian.Uint16(input[2:])), + CreatedAt: binary.LittleEndian.Uint32(input[4:]), + StartIndexPtr: binary.LittleEndian.Uint32(input[8:]), + EndIndexPtr: binary.LittleEndian.Uint32(input[12:]), + }, nil +} + +// --- searcher implementation + +type Searcher struct { + handle *os.File + + ioCount int + + // use it only when this feature enabled. + // Preload the vector index will reduce the number of IO operations + // thus speedup the search process + vectorIndex []byte + + // content buffer. + // running with the whole xdb file cached + contentBuff []byte +} + +func baseNew(dbFile string, vIndex []byte, cBuff []byte) (*Searcher, error) { + var err error + + // content buff first + if cBuff != nil { + return &Searcher{ + vectorIndex: nil, + contentBuff: cBuff, + }, nil + } + + // open the xdb binary file + handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600) + if err != nil { + return nil, err + } + + return &Searcher{ + handle: handle, + vectorIndex: vIndex, + }, nil +} + +func NewWithFileOnly(dbFile string) (*Searcher, error) { + return baseNew(dbFile, nil, nil) +} + +func NewWithVectorIndex(dbFile string, vIndex []byte) (*Searcher, error) { + return baseNew(dbFile, vIndex, nil) +} + +func NewWithBuffer(cBuff []byte) (*Searcher, error) { + return baseNew("", nil, cBuff) +} + +func (s *Searcher) Close() { + if s.handle != nil { + err := s.handle.Close() + if err != nil { + return + } + } +} + +// GetIOCount return the global io count for the last search +func (s *Searcher) GetIOCount() int { + return s.ioCount +} + +// SearchByStr find the region for the specified ip string +func (s *Searcher) SearchByStr(str string) (string, error) { + ip, err := CheckIP(str) + if err != nil { + return "", err + } + + return s.Search(ip) +} + +// Search find the region for the specified long ip +func (s *Searcher) Search(ip uint32) (string, error) { + // reset the global ioCount + s.ioCount = 0 + + // locate the segment index block based on the vector index + var il0 = (ip >> 24) & 0xFF + var il1 = (ip >> 16) & 0xFF + var idx = il0*VectorIndexCols*VectorIndexSize + il1*VectorIndexSize + var sPtr, ePtr = uint32(0), uint32(0) + if s.vectorIndex != nil { + sPtr = binary.LittleEndian.Uint32(s.vectorIndex[idx:]) + ePtr = binary.LittleEndian.Uint32(s.vectorIndex[idx+4:]) + } else if s.contentBuff != nil { + sPtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx:]) + ePtr = binary.LittleEndian.Uint32(s.contentBuff[HeaderInfoLength+idx+4:]) + } else { + // read the vector index block + var buff = make([]byte, VectorIndexSize) + err := s.read(int64(HeaderInfoLength+idx), buff) + if err != nil { + return "", fmt.Errorf("read vector index block at %d: %w", HeaderInfoLength+idx, err) + } + + sPtr = binary.LittleEndian.Uint32(buff) + ePtr = binary.LittleEndian.Uint32(buff[4:]) + } + + // fmt.Printf("sPtr=%d, ePtr=%d", sPtr, ePtr) + + // binary search the segment index to get the region + var dataLen, dataPtr = 0, uint32(0) + var buff = make([]byte, SegmentIndexBlockSize) + var l, h = 0, int((ePtr - sPtr) / SegmentIndexBlockSize) + for l <= h { + m := (l + h) >> 1 + p := sPtr + uint32(m*SegmentIndexBlockSize) + err := s.read(int64(p), buff) + if err != nil { + return "", fmt.Errorf("read segment index at %d: %w", p, err) + } + + // decode the data step by step to reduce the unnecessary operations + sip := binary.LittleEndian.Uint32(buff) + if ip < sip { + h = m - 1 + } else { + eip := binary.LittleEndian.Uint32(buff[4:]) + if ip > eip { + l = m + 1 + } else { + dataLen = int(binary.LittleEndian.Uint16(buff[8:])) + dataPtr = binary.LittleEndian.Uint32(buff[10:]) + break + } + } + } + + //fmt.Printf("dataLen: %d, dataPtr: %d", dataLen, dataPtr) + if dataLen == 0 { + return "", nil + } + + // load and return the region data + var regionBuff = make([]byte, dataLen) + err := s.read(int64(dataPtr), regionBuff) + if err != nil { + return "", fmt.Errorf("read region at %d: %w", dataPtr, err) + } + + return string(regionBuff), nil +} + +// do the data read operation based on the setting. +// content buffer first or will read from the file. +// this operation will invoke the Seek for file based read. +func (s *Searcher) read(offset int64, buff []byte) error { + if s.contentBuff != nil { + cLen := copy(buff, s.contentBuff[offset:]) + if cLen != len(buff) { + return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff)) + } + } else { + _, err := s.handle.Seek(offset, 0) + if err != nil { + return fmt.Errorf("seek to %d: %w", offset, err) + } + + s.ioCount++ + rLen, err := s.handle.Read(buff) + if err != nil { + return fmt.Errorf("handle read: %w", err) + } + + if rLen != len(buff) { + return fmt.Errorf("incomplete read: readed bytes should be %d", len(buff)) + } + } + + return nil +} diff --git a/src/framework/utils/ip2region/ip2region.go b/src/framework/utils/ip2region/ip2region.go new file mode 100644 index 0000000..68bd0b1 --- /dev/null +++ b/src/framework/utils/ip2region/ip2region.go @@ -0,0 +1,89 @@ +package ip2region + +import ( + "embed" + "strings" + "time" + + "ems.agt/src/framework/logger" +) + +// 网络地址(内网) +const LOCAT_HOST = "127.0.0.1" + +// 全局查询对象 +var searcher *Searcher + +//go:embed ip2region.xdb +var ip2regionDB embed.FS + +func init() { + // 从 dbPath 加载整个 xdb 到内存 + buf, err := ip2regionDB.ReadFile("ip2region.xdb") + if err != nil { + logger.Fatalf("failed error load xdb from : %s\n", err) + return + } + + // 用全局的 cBuff 创建完全基于内存的查询对象。 + base, err := NewWithBuffer(buf) + if err != nil { + logger.Errorf("failed error create searcher with content: %s\n", err) + return + } + + // 赋值到全局查询对象 + searcher = base +} + +// RegionSearchByIp 查询IP所在地 +// +// 国家|区域|省份|城市|ISP +func RegionSearchByIp(ip string) (string, int, int64) { + ip = ClientIP(ip) + if ip == LOCAT_HOST { + return "0|0|0|内网IP|内网IP", 0, 0 + } + tStart := time.Now() + region, err := searcher.SearchByStr(ip) + if err != nil { + logger.Errorf("failed to SearchIP(%s): %s\n", ip, err) + return "0|0|0|0|0", 0, 0 + } + return region, 0, time.Since(tStart).Milliseconds() +} + +// RealAddressByIp 地址IP所在地 +// +// 218.4.167.70 江苏省 苏州市 +func RealAddressByIp(ip string) string { + ip = ClientIP(ip) + if ip == LOCAT_HOST { + return "内网IP" + } + region, err := searcher.SearchByStr(ip) + if err != nil { + logger.Errorf("failed to SearchIP(%s): %s\n", ip, err) + return "未知" + } + parts := strings.Split(region, "|") + province := parts[2] + city := parts[3] + if province == "0" && city != "0" { + return city + } + return province + " " + city +} + +// ClientIP 处理客户端IP地址显示iPv4 +// +// 转换 ip2region.ClientIP(c.ClientIP()) +func ClientIP(ip string) string { + if strings.HasPrefix(ip, "::ffff:") { + ip = strings.Replace(ip, "::ffff:", "", 1) + } + if ip == LOCAT_HOST || ip == "::1" { + return LOCAT_HOST + } + return ip +} diff --git a/src/framework/utils/ip2region/ip2region.xdb b/src/framework/utils/ip2region/ip2region.xdb new file mode 100644 index 0000000..c78b792 Binary files /dev/null and b/src/framework/utils/ip2region/ip2region.xdb differ diff --git a/src/framework/utils/ip2region/util.go b/src/framework/utils/ip2region/util.go new file mode 100644 index 0000000..cfef29e --- /dev/null +++ b/src/framework/utils/ip2region/util.go @@ -0,0 +1,175 @@ +package ip2region + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +var shiftIndex = []int{24, 16, 8, 0} + +func CheckIP(ip string) (uint32, error) { + var ps = strings.Split(strings.TrimSpace(ip), ".") + if len(ps) != 4 { + return 0, fmt.Errorf("invalid ip address `%s`", ip) + } + + var val = uint32(0) + for i, s := range ps { + d, err := strconv.Atoi(s) + if err != nil { + return 0, fmt.Errorf("the %dth part `%s` is not an integer", i, s) + } + + if d < 0 || d > 255 { + return 0, fmt.Errorf("the %dth part `%s` should be an integer bettween 0 and 255", i, s) + } + + val |= uint32(d) << shiftIndex[i] + } + + // convert the ip to integer + return val, nil +} + +func Long2IP(ip uint32) string { + return fmt.Sprintf("%d.%d.%d.%d", (ip>>24)&0xFF, (ip>>16)&0xFF, (ip>>8)&0xFF, ip&0xFF) +} + +func MidIP(sip uint32, eip uint32) uint32 { + return uint32((uint64(sip) + uint64(eip)) >> 1) +} + +// LoadHeader load the header info from the specified handle +func LoadHeader(handle *os.File) (*Header, error) { + _, err := handle.Seek(0, 0) + if err != nil { + return nil, fmt.Errorf("seek to the header: %w", err) + } + + var buff = make([]byte, HeaderInfoLength) + rLen, err := handle.Read(buff) + if err != nil { + return nil, err + } + + if rLen != len(buff) { + return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff)) + } + + return NewHeader(buff) +} + +// LoadHeaderFromFile load header info from the specified db file path +func LoadHeaderFromFile(dbFile string) (*Header, error) { + handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600) + if err != nil { + return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err) + } + + defer func(handle *os.File) { + _ = handle.Close() + }(handle) + + header, err := LoadHeader(handle) + if err != nil { + return nil, err + } + + return header, nil +} + +// LoadHeaderFromBuff wrap the header info from the content buffer +func LoadHeaderFromBuff(cBuff []byte) (*Header, error) { + return NewHeader(cBuff[0:256]) +} + +// LoadVectorIndex util function to load the vector index from the specified file handle +func LoadVectorIndex(handle *os.File) ([]byte, error) { + // load all the vector index block + _, err := handle.Seek(HeaderInfoLength, 0) + if err != nil { + return nil, fmt.Errorf("seek to vector index: %w", err) + } + + var buff = make([]byte, VectorIndexRows*VectorIndexCols*VectorIndexSize) + rLen, err := handle.Read(buff) + if err != nil { + return nil, err + } + + if rLen != len(buff) { + return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff)) + } + + return buff, nil +} + +// LoadVectorIndexFromFile load vector index from a specified file path +func LoadVectorIndexFromFile(dbFile string) ([]byte, error) { + handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600) + if err != nil { + return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err) + } + + defer func() { + _ = handle.Close() + }() + + vIndex, err := LoadVectorIndex(handle) + if err != nil { + return nil, err + } + + return vIndex, nil +} + +// LoadContent load the whole xdb content from the specified file handle +func LoadContent(handle *os.File) ([]byte, error) { + // get file size + fi, err := handle.Stat() + if err != nil { + return nil, fmt.Errorf("stat: %w", err) + } + + size := fi.Size() + + // seek to the head of the file + _, err = handle.Seek(0, 0) + if err != nil { + return nil, fmt.Errorf("seek to get xdb file length: %w", err) + } + + var buff = make([]byte, size) + rLen, err := handle.Read(buff) + if err != nil { + return nil, err + } + + if rLen != len(buff) { + return nil, fmt.Errorf("incomplete read: readed bytes should be %d", len(buff)) + } + + return buff, nil +} + +// LoadContentFromFile load the whole xdb content from the specified db file path +func LoadContentFromFile(dbFile string) ([]byte, error) { + handle, err := os.OpenFile(dbFile, os.O_RDONLY, 0600) + if err != nil { + return nil, fmt.Errorf("open xdb file `%s`: %w", dbFile, err) + } + + defer func() { + _ = handle.Close() + }() + + cBuff, err := LoadContent(handle) + if err != nil { + return nil, err + } + + return cBuff, nil +} + diff --git a/src/framework/utils/parse/parse.go b/src/framework/utils/parse/parse.go new file mode 100644 index 0000000..ff8145c --- /dev/null +++ b/src/framework/utils/parse/parse.go @@ -0,0 +1,167 @@ +package parse + +import ( + "fmt" + "image/color" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/robfig/cron/v3" +) + +// Number 解析数值型 +func Number(str any) int64 { + switch str := str.(type) { + case string: + if str == "" { + return 0 + } + num, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0 + } + return num + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + return reflect.ValueOf(str).Int() + case float32, float64: + return int64(reflect.ValueOf(str).Float()) + default: + return 0 + } +} + +// Boolean 解析布尔型 +func Boolean(str any) bool { + switch str := str.(type) { + case string: + if str == "" || str == "false" || str == "0" { + return false + } + // 尝试将字符串解析为数字 + if num, err := strconv.ParseFloat(str, 64); err == nil { + return num != 0 + } + return true + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + num := reflect.ValueOf(str).Int() + return num != 0 + case float32, float64: + num := reflect.ValueOf(str).Float() + return num != 0 + default: + return false + } +} + +// ConvertToCamelCase 字符串转换驼峰形式 +// +// 字符串 dict/inline/data/:dictId 结果 DictInlineDataDictId +func ConvertToCamelCase(str string) string { + if len(str) == 0 { + return str + } + reg := regexp.MustCompile(`[-_:/]\w`) + result := reg.ReplaceAllStringFunc(str, func(match string) string { + return strings.ToUpper(string(match[1])) + }) + + words := strings.Fields(result) + for i, word := range words { + str := word[1:] + str = strings.ReplaceAll(str, "/", "") + words[i] = strings.ToUpper(word[:1]) + str + } + + return strings.Join(words, "") +} + +// Bit 比特位为单位 +func Bit(bit float64) string { + var GB, MB, KB string + + if bit > float64(1<<30) { + GB = fmt.Sprintf("%0.2f", bit/(1<<30)) + } + + if bit > float64(1<<20) && bit < (1<<30) { + MB = fmt.Sprintf("%.2f", bit/(1<<20)) + } + + if bit > float64(1<<10) && bit < (1<<20) { + KB = fmt.Sprintf("%.2f", bit/(1<<10)) + } + + if GB != "" { + return GB + "GB" + } else if MB != "" { + return MB + "MB" + } else if KB != "" { + return KB + "KB" + } else { + return fmt.Sprintf("%vB", bit) + } +} + +// CronExpression 解析 Cron 表达式,返回下一次执行的时间戳(毫秒) +// +// 【*/5 * * * * ?】 6个参数 +func CronExpression(expression string) int64 { + specParser := cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor) + schedule, err := specParser.Parse(expression) + if err != nil { + return 0 + } + return schedule.Next(time.Now()).UnixMilli() +} + +// SafeContent 内容值进行安全掩码 +func SafeContent(value string) string { + if len(value) < 3 { + return strings.Repeat("*", len(value)) + } else if len(value) < 6 { + return string(value[0]) + strings.Repeat("*", len(value)-1) + } else if len(value) < 10 { + return string(value[0]) + strings.Repeat("*", len(value)-2) + string(value[len(value)-1]) + } else if len(value) < 15 { + return value[:2] + strings.Repeat("*", len(value)-4) + value[len(value)-2:] + } else { + return value[:3] + strings.Repeat("*", len(value)-6) + value[len(value)-3:] + } +} + +// RemoveDuplicates 数组内字符串去重 +func RemoveDuplicates(ids []string) []string { + uniqueIDs := make(map[string]bool) + uniqueIDSlice := make([]string, 0) + + for _, id := range ids { + _, ok := uniqueIDs[id] + if !ok && id != "" { + uniqueIDs[id] = true + uniqueIDSlice = append(uniqueIDSlice, id) + } + } + + return uniqueIDSlice +} + +// Color 解析颜色 #fafafa +func Color(colorStr string) *color.RGBA { + // 去除 # 号 + colorStr = colorStr[1:] + + // 将颜色字符串拆分为 R、G、B 分量 + r, _ := strconv.ParseInt(colorStr[0:2], 16, 0) + g, _ := strconv.ParseInt(colorStr[2:4], 16, 0) + b, _ := strconv.ParseInt(colorStr[4:6], 16, 0) + + return &color.RGBA{ + R: uint8(r), + G: uint8(g), + B: uint8(b), + A: 255, // 不透明 + } +} diff --git a/src/framework/utils/regular/regular.go b/src/framework/utils/regular/regular.go new file mode 100644 index 0000000..9feebc3 --- /dev/null +++ b/src/framework/utils/regular/regular.go @@ -0,0 +1,86 @@ +package regular + +import ( + "regexp" + + "github.com/dlclark/regexp2" +) + +// Replace 正则替换 +func Replace(originStr, pattern, repStr string) string { + regex := regexp.MustCompile(pattern) + return regex.ReplaceAllString(originStr, repStr) +} + +// 判断是否为有效用户名格式 +// +// 用户名不能以数字开头,可包含大写小写字母,数字,且不少于5位 +func ValidUsername(username string) bool { + if username == "" { + return false + } + pattern := `^[a-zA-Z][a-z0-9A-Z]{5,}` + match, err := regexp.MatchString(pattern, username) + if err != nil { + return false + } + return match +} + +// 判断是否为有效密码格式 +// +// 密码至少包含大小写字母、数字、特殊符号,且不少于6位 +func ValidPassword(password string) bool { + if password == "" { + return false + } + pattern := `^(?![A-Za-z0-9]+$)(?![a-z0-9\W]+$)(?![A-Za-z\W]+$)(?![A-Z0-9\W]+$)[a-zA-Z0-9\W]{6,}$` + re := regexp2.MustCompile(pattern, 0) + match, err := re.MatchString(password) + if err != nil { + return false + } + return match +} + +// 判断是否为有效手机号格式,1开头的11位手机号 +func ValidMobile(mobile string) bool { + if mobile == "" { + return false + } + pattern := `^1[3|4|5|6|7|8|9][0-9]\d{8}$` + match, err := regexp.MatchString(pattern, mobile) + if err != nil { + return false + } + return match +} + +// 判断是否为有效邮箱格式 +func ValidEmail(email string) bool { + if email == "" { + return false + } + pattern := `^(([^<>()\\.,;:\s@"]+(\.[^<>()\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+\.)+[a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}))$` + re := regexp2.MustCompile(pattern, 0) + match, err := re.MatchString(email) + if err != nil { + return false + } + return match +} + +// 判断是否为http(s)://开头 +// +// link 网络链接 +func ValidHttp(link string) bool { + if link == "" { + return false + } + pattern := `^http(s)?:\/\/+` + match, err := regexp.MatchString(pattern, link) + if err != nil { + return false + } + return match +} diff --git a/src/framework/utils/repo/repo.go b/src/framework/utils/repo/repo.go new file mode 100644 index 0000000..47bdf86 --- /dev/null +++ b/src/framework/utils/repo/repo.go @@ -0,0 +1,132 @@ +package repo + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + "ems.agt/src/framework/utils/parse" +) + +// PageNumSize 分页页码记录数 +func PageNumSize(pageNum, pageSize any) (int64, int64) { + // 记录起始索引 + num := parse.Number(pageNum) + if num > 5000 { + num = 5000 + } + if num < 1 { + num = 1 + } + + // 显示记录数 + size := parse.Number(pageSize) + if size > 50000 { + size = 50000 + } + if size < 0 { + size = 10 + } + return num - 1, size +} + +// SetFieldValue 判断结构体内是否存在指定字段并设置值 +func SetFieldValue(obj any, fieldName string, value any) { + // 获取结构体的反射值 + userValue := reflect.ValueOf(obj) + + // 获取字段的反射值 + fieldValue := userValue.Elem().FieldByName(fieldName) + + // 检查字段是否存在 + if fieldValue.IsValid() && fieldValue.CanSet() { + // 获取字段的类型 + fieldType := fieldValue.Type() + + // 转换传入的值类型为字段类型 + switch fieldType.Kind() { + case reflect.String: + if value == nil { + fieldValue.SetString("") + } else { + fieldValue.SetString(fmt.Sprintf("%v", value)) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + intValue, err := strconv.ParseInt(fmt.Sprintf("%v", value), 10, 64) + if err != nil { + intValue = 0 + } + fieldValue.SetInt(intValue) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + uintValue, err := strconv.ParseUint(fmt.Sprintf("%v", value), 10, 64) + if err != nil { + uintValue = 0 + } + fieldValue.SetUint(uintValue) + case reflect.Float32, reflect.Float64: + floatValue, err := strconv.ParseFloat(fmt.Sprintf("%v", value), 64) + if err != nil { + floatValue = 0 + } + fieldValue.SetFloat(floatValue) + default: + // 设置字段的值 + fieldValue.Set(reflect.ValueOf(value).Convert(fieldValue.Type())) + } + } +} + +// ConvertIdsSlice 将 []string 转换为 []any +func ConvertIdsSlice(ids []string) []any { + // 将 []string 转换为 []any + arr := make([]any, len(ids)) + for i, v := range ids { + arr[i] = v + } + return arr +} + +// 查询-参数值的占位符 +func KeyPlaceholderByQuery(sum int) string { + placeholders := make([]string, sum) + for i := 0; i < sum; i++ { + placeholders[i] = "?" + } + return strings.Join(placeholders, ",") +} + +// 插入-参数映射键值占位符 keys, placeholder, values +func KeyPlaceholderValueByInsert(params map[string]any) ([]string, string, []any) { + // 参数映射的键 + keys := make([]string, len(params)) + // 参数映射的值 + values := make([]any, len(params)) + sum := 0 + for k, v := range params { + keys[sum] = k + values[sum] = v + sum++ + } + // 参数值的占位符 + placeholders := make([]string, sum) + for i := 0; i < sum; i++ { + placeholders[i] = "?" + } + return keys, strings.Join(placeholders, ","), values +} + +// 更新-参数映射键值占位符 keys, values +func KeyValueByUpdate(params map[string]any) ([]string, []any) { + // 参数映射的键 + keys := make([]string, len(params)) + // 参数映射的值 + values := make([]any, len(params)) + sum := 0 + for k, v := range params { + keys[sum] = k + "=?" + values[sum] = v + sum++ + } + return keys, values +} diff --git a/src/framework/utils/token/token.go b/src/framework/utils/token/token.go new file mode 100644 index 0000000..6815eb2 --- /dev/null +++ b/src/framework/utils/token/token.go @@ -0,0 +1,151 @@ +package token + +import ( + "encoding/json" + "errors" + "time" + + "ems.agt/src/framework/config" + cachekeyConstants "ems.agt/src/framework/constants/cachekey" + tokenConstants "ems.agt/src/framework/constants/token" + "ems.agt/src/framework/logger" + redisCahe "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/generate" + "ems.agt/src/framework/vo" + + jwt "github.com/golang-jwt/jwt/v5" +) + +// Remove 清除登录用户信息UUID +func Remove(tokenStr string) string { + claims, err := Verify(tokenStr) + if err != nil { + logger.Errorf("token verify err %v", err) + return "" + } + // 清除缓存KEY + uuid := claims[tokenConstants.JWT_UUID].(string) + tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + uuid + hasKey, _ := redisCahe.Has("", tokenKey) + if hasKey { + redisCahe.Del("", tokenKey) + } + return claims[tokenConstants.JWT_NAME].(string) +} + +// Create 令牌生成 +func Create(loginUser *vo.LoginUser, ilobArgs ...string) string { + // 生成用户唯一tokne32位 + loginUser.UUID = generate.Code(32) + + // 设置请求用户登录客户端 + loginUser.IPAddr = ilobArgs[0] + loginUser.LoginLocation = ilobArgs[1] + loginUser.OS = ilobArgs[2] + loginUser.Browser = ilobArgs[3] + + // 设置用户令牌有效期并存入缓存 + Cache(loginUser) + + // 令牌算法 HS256 HS384 HS512 + algorithm := config.Get("jwt.algorithm").(string) + var method *jwt.SigningMethodHMAC + switch algorithm { + case "HS512": + method = jwt.SigningMethodHS512 + case "HS384": + method = jwt.SigningMethodHS384 + case "HS256": + default: + method = jwt.SigningMethodHS256 + } + // 生成令牌负荷绑定uuid标识 + jwtToken := jwt.NewWithClaims(method, jwt.MapClaims{ + tokenConstants.JWT_UUID: loginUser.UUID, + tokenConstants.JWT_KEY: loginUser.UserID, + tokenConstants.JWT_NAME: loginUser.User.UserName, + "exp": loginUser.ExpireTime, + "ait": loginUser.LoginTime, + }) + + // 生成令牌设置密钥 + secret := config.Get("jwt.secret").(string) + tokenStr, err := jwtToken.SignedString([]byte(secret)) + if err != nil { + logger.Infof("jwt sign err : %v", err) + return "" + } + return tokenStr +} + +// Cache 缓存登录用户信息 +func Cache(loginUser *vo.LoginUser) { + // 计算配置的有效期 + expTime := config.Get("jwt.expiresIn").(int) + expTimestamp := time.Duration(expTime) * time.Minute + iatTimestamp := time.Now().UnixMilli() + loginUser.LoginTime = iatTimestamp + loginUser.ExpireTime = iatTimestamp + expTimestamp.Milliseconds() + // 根据登录标识将loginUser缓存 + tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + loginUser.UUID + jsonBytes, err := json.Marshal(loginUser) + if err != nil { + return + } + redisCahe.SetByExpire("", tokenKey, string(jsonBytes), expTimestamp) +} + +// RefreshIn 验证令牌有效期,相差不足xx分钟,自动刷新缓存 +func RefreshIn(loginUser *vo.LoginUser) { + // 相差不足xx分钟,自动刷新缓存 + refreshTime := config.Get("jwt.refreshIn").(int) + refreshTimestamp := time.Duration(refreshTime) * time.Minute + // 过期时间 + expireTimestamp := loginUser.ExpireTime + currentTimestamp := time.Now().UnixMilli() + if expireTimestamp-currentTimestamp <= refreshTimestamp.Milliseconds() { + Cache(loginUser) + } +} + +// Verify 校验令牌是否有效 +func Verify(tokenString string) (jwt.MapClaims, error) { + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) { + // 判断加密算法是预期的加密算法 + if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok { + secret := config.Get("jwt.secret").(string) + return []byte(secret), nil + } + return nil, jwt.ErrSignatureInvalid + }) + if err != nil { + logger.Errorf("token String Verify : %v", err) + return nil, errors.New("无效身份授权") + } + // 如果解析负荷成功并通过签名校验 + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid { + return claims, nil + } + return nil, errors.New("token valid error") +} + +// LoginUser 缓存的登录用户信息 +func LoginUser(claims jwt.MapClaims) vo.LoginUser { + uuid := claims[tokenConstants.JWT_UUID].(string) + tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + uuid + hasKey, _ := redisCahe.Has("", tokenKey) + var loginUser vo.LoginUser + if hasKey { + loginUserStr, _ := redisCahe.Get("", tokenKey) + if loginUserStr == "" { + return loginUser + } + err := json.Unmarshal([]byte(loginUserStr), &loginUser) + if err != nil { + logger.Errorf("loginuser info json err : %v", err) + return loginUser + } + return loginUser + } + return loginUser +} diff --git a/src/framework/utils/ua/ua.go b/src/framework/utils/ua/ua.go new file mode 100644 index 0000000..1a12531 --- /dev/null +++ b/src/framework/utils/ua/ua.go @@ -0,0 +1,8 @@ +package ua + +import "github.com/mssola/user_agent" + +// 获取user-agent信息 +func Info(userAgent string) *user_agent.UserAgent { + return user_agent.New(userAgent) +} diff --git a/src/framework/vo/loginuser.go b/src/framework/vo/loginuser.go new file mode 100644 index 0000000..823d7a6 --- /dev/null +++ b/src/framework/vo/loginuser.go @@ -0,0 +1,39 @@ +package vo + +import systemModel "ems.agt/src/modules/system/model" + +// LoginUser 登录用户身份权限信息对象 +type LoginUser struct { + // UserID 用户ID + UserID string `json:"userId"` + + // DeptID 部门ID + DeptID string `json:"deptId"` + + // UUID 用户唯一标识 + UUID string `json:"uuid"` + + // LoginTime 登录时间时间戳 + LoginTime int64 `json:"loginTime"` + + // ExpireTime 过期时间时间戳 + ExpireTime int64 `json:"expireTime"` + + // IPAddr 登录IP地址 x.x.x.x + IPAddr string `json:"ipaddr"` + + // LoginLocation 登录地点 xx xx + LoginLocation string `json:"loginLocation"` + + // Browser 浏览器类型 + Browser string `json:"browser"` + + // OS 操作系统 + OS string `json:"os"` + + // Permissions 权限列表 + Permissions []string `json:"permissions"` + + // User 用户信息 + User systemModel.SysUser `json:"user"` +} diff --git a/src/framework/vo/result/result.go b/src/framework/vo/result/result.go new file mode 100644 index 0000000..e10b53c --- /dev/null +++ b/src/framework/vo/result/result.go @@ -0,0 +1,71 @@ +package result + +import ( + "ems.agt/src/framework/constants/result" +) + +// CodeMsg 响应结果 +func CodeMsg(code int, msg string) map[string]any { + args := make(map[string]any) + args["code"] = code + args["msg"] = msg + return args +} + +// 响应成功结果 map[string]any{} +func Ok(v map[string]any) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_SUCCESS + args["msg"] = result.MSG_SUCCESS + // v合并到args + for key, value := range v { + args[key] = value + } + return args +} + +// 响应成功结果信息 +func OkMsg(msg string) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_SUCCESS + args["msg"] = msg + return args +} + +// 响应成功结果数据 +func OkData(data any) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_SUCCESS + args["msg"] = result.MSG_SUCCESS + args["data"] = data + return args +} + +// 响应失败结果 map[string]any{} +func Err(v map[string]any) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_ERROR + args["msg"] = result.MSG_ERROR + // v合并到args + for key, value := range v { + args[key] = value + } + return args +} + +// 响应失败结果信息 +func ErrMsg(msg string) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_ERROR + args["msg"] = msg + return args +} + +// 响应失败结果数据 +func ErrData(data any) map[string]any { + args := make(map[string]any) + args["code"] = result.CODE_ERROR + args["msg"] = result.MSG_ERROR + args["data"] = data + return args +} diff --git a/src/framework/vo/router.go b/src/framework/vo/router.go new file mode 100644 index 0000000..a3cc408 --- /dev/null +++ b/src/framework/vo/router.go @@ -0,0 +1,17 @@ +package vo + +// Router 路由信息对象 +type Router struct { + // 路由名字 英文首字母大写 + Name string `json:"name"` + // 路由地址 + Path string `json:"path"` + // 其他元素 + Meta RouterMeta `json:"meta"` + // 组件地址 + Component string `json:"component"` + // 重定向地址 + Redirect string `json:"redirect"` + // 子路由 + Children []Router `json:"children,omitempty"` +} diff --git a/src/framework/vo/router_meta.go b/src/framework/vo/router_meta.go new file mode 100644 index 0000000..b3447e0 --- /dev/null +++ b/src/framework/vo/router_meta.go @@ -0,0 +1,17 @@ +package vo + +// RouterMeta 路由元信息对象 +type RouterMeta struct { + // 设置该菜单在侧边栏和面包屑中展示的名字 + Title string `json:"title"` + // 设置该菜单的图标 + Icon string `json:"icon"` + // 设置为true,则不会被 缓存 + Cache bool `json:"cache"` + // 内链地址(http(s)://开头), 打开目标位置 '_blank' | '_self' | '' + Target string `json:"target"` + // 在菜单中隐藏子节点 + HideChildInMenu bool `json:"hideChildInMenu"` + // 在菜单中隐藏自己和子节点 + HideInMenu bool `json:"hideInMenu"` +} diff --git a/src/framework/vo/treeselect.go b/src/framework/vo/treeselect.go new file mode 100644 index 0000000..ea86ba9 --- /dev/null +++ b/src/framework/vo/treeselect.go @@ -0,0 +1,51 @@ +package vo + +import systemModel "ems.agt/src/modules/system/model" + +// TreeSelect 树结构实体类 +type TreeSelect struct { + // ID 节点ID + ID string `json:"id"` + + // Label 节点名称 + Label string `json:"label"` + + // Children 子节点 + Children []TreeSelect `json:"children"` +} + +// SysMenuTreeSelect 使用给定的 SysMenu 对象解析为 TreeSelect 对象 +func SysMenuTreeSelect(sysMenu systemModel.SysMenu) TreeSelect { + t := TreeSelect{} + t.ID = sysMenu.MenuID + t.Label = sysMenu.MenuName + + if len(sysMenu.Children) > 0 { + for _, menu := range sysMenu.Children { + child := SysMenuTreeSelect(menu) + t.Children = append(t.Children, child) + } + } else { + t.Children = []TreeSelect{} + } + + return t +} + +// SysDeptTreeSelect 使用给定的 SysDept 对象解析为 TreeSelect 对象 +func SysDeptTreeSelect(sysDept systemModel.SysDept) TreeSelect { + t := TreeSelect{} + t.ID = sysDept.DeptID + t.Label = sysDept.DeptName + + if len(sysDept.Children) > 0 { + for _, dept := range sysDept.Children { + child := SysDeptTreeSelect(dept) + t.Children = append(t.Children, child) + } + } else { + t.Children = []TreeSelect{} + } + + return t +} diff --git a/src/lib_features/account/account.go b/src/lib_features/account/account.go new file mode 100644 index 0000000..7d4af0d --- /dev/null +++ b/src/lib_features/account/account.go @@ -0,0 +1,32 @@ +package libfeatures + +import ( + "time" + + "ems.agt/lib/dborm" + "ems.agt/lib/oauth" + libConfig "ems.agt/restagent/config" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/redis" +) + +// SessionToken 设置登录会话-兼容旧登录方式 +func SessionToken(username, sourceAddr string) bool { + token, _ := redis.Get("", "session_token") + if token != "" { + return true + } + + token = oauth.GenRandToken("omc") // Generate new token to session ID + affected, err := dborm.XormInsertSession(username, sourceAddr, token, libConfig.GetExpiresFromConfig(), libConfig.GetYamlConfig().Auth.Session) + if err != nil { + logger.Errorf("SessionToken XormInsertSession err %v", err) + } + if affected == 1 { + // 过期时间单位秒 配置1800是半小时 + expireTime := time.Duration(int64(libConfig.GetExpiresFromConfig())) * time.Second + redis.SetByExpire("", "session_token", token, expireTime) + return true + } + return false +} diff --git a/src/lib_features/config/config.go b/src/lib_features/config/config.go new file mode 100644 index 0000000..a56a757 --- /dev/null +++ b/src/lib_features/config/config.go @@ -0,0 +1,53 @@ +package libfeatures + +import ( + "fmt" + + libConf "ems.agt/lib/core/conf" + libGlobal "ems.agt/lib/global" + libConfig "ems.agt/restagent/config" + "github.com/spf13/viper" +) + +// BuildInfo 程序-V查看编译版本号信息 +func BuildInfo() string { + return fmt.Sprintf("OMC restagent version: %s\n%s\n%s\n\n", libGlobal.Version, libGlobal.BuildTime, libGlobal.GoVer) +} + +// ConfigRead 指定配置文件读取 +func ConfigRead(configFile string) { + // 外层lib和features使用的配置 + libConfig.ReadConfig(configFile) + uriPrefix := libConfig.GetYamlConfig().OMC.UriPrefix + if uriPrefix != "" { + libConfig.UriPrefix = uriPrefix + } + if libConfig.GetYamlConfig().TestConfig.Enabled { + libConfig.ReadTestConfigYaml(libConfig.GetYamlConfig().TestConfig.File) + } + // 外层lib和features使用配置 + libConf.InitConfig(configFile) +} + +// 配置文件读取进行内部参数合并 +func ConfigInMerge() { + // 合并外层lib和features使用配置 + for key, value := range libConf.AllSettings() { + // 跳过配置 + if key == "testconfig" || key == "rest" || key == "logger" { + continue + } + // 数据库配置 + if key == "database" { + item := value.(map[string]any) + defaultItem := viper.GetStringMap("gorm.datasource.default") + defaultItem["host"] = item["host"] + defaultItem["port"] = item["port"] + defaultItem["username"] = item["user"] + defaultItem["password"] = item["password"] + defaultItem["database"] = item["name"] + continue + } + viper.Set(key, value) + } +} diff --git a/src/lib_features/readme.md b/src/lib_features/readme.md new file mode 100644 index 0000000..8e91940 --- /dev/null +++ b/src/lib_features/readme.md @@ -0,0 +1,5 @@ +# 外层 lib 和 features 粘合层 + +- config.go 配置合并: restagent.yaml 文件内容,主要是数据库配置 +- account.go 登录会话生成 token +- session.go 中间件方式设置请求头 token 值 diff --git a/src/lib_features/session/session.go b/src/lib_features/session/session.go new file mode 100644 index 0000000..e7c4a11 --- /dev/null +++ b/src/lib_features/session/session.go @@ -0,0 +1,23 @@ +package session + +import ( + "ems.agt/src/framework/redis" + + "github.com/gin-gonic/gin" +) + +// SessionHeader 旧登录方式token头 +func SessionHeader() gin.HandlerFunc { + return func(c *gin.Context) { + // 读取登录生成的会话token + token, err := redis.Get("", "session_token") + if err == nil { + c.Request.Header.Set("Accesstoken", token) + } + + // Accesstoken: omc-ce4d0a86-8515-ad51-3249-4913c95f8e34 + // 调用下一个处理程序 + c.Next() + + } +} diff --git a/src/modules/common/common.go b/src/modules/common/common.go new file mode 100644 index 0000000..a809084 --- /dev/null +++ b/src/modules/common/common.go @@ -0,0 +1,85 @@ +package common + +import ( + "ems.agt/src/framework/logger" + "ems.agt/src/framework/middleware" + "ems.agt/src/modules/common/controller" + + "github.com/gin-gonic/gin" +) + +// 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> common 模块路由") + + // 路由主页 + indexGroup := router.Group("/") + indexGroup.GET("", + middleware.RateLimit(middleware.LimitOption{ + Time: 300, + Count: 10, + Type: middleware.LIMIT_IP, + }), + controller.NewIndex.Handler, + ) + + // 验证码操作处理 + indexGroup.GET("/captchaImage", + middleware.RateLimit(middleware.LimitOption{ + Time: 300, + Count: 60, + Type: middleware.LIMIT_IP, + }), + controller.NewCaptcha.Image, + ) + + // 账号身份操作处理 + { + indexGroup.POST("/login", + middleware.RateLimit(middleware.LimitOption{ + Time: 300, + Count: 10, + Type: middleware.LIMIT_IP, + }), + controller.NewAccount.Login, + ) + indexGroup.GET("/getInfo", middleware.PreAuthorize(nil), controller.NewAccount.Info) + indexGroup.GET("/getRouters", middleware.PreAuthorize(nil), controller.NewAccount.Router) + indexGroup.POST("/logout", + middleware.RateLimit(middleware.LimitOption{ + Time: 300, + Count: 5, + Type: middleware.LIMIT_IP, + }), + controller.NewAccount.Logout, + ) + } + + // 账号注册操作处理 + { + indexGroup.POST("/register", + middleware.RateLimit(middleware.LimitOption{ + Time: 300, + Count: 10, + Type: middleware.LIMIT_IP, + }), + controller.NewRegister.UserName, + ) + } + + // 通用请求 + commonGroup := router.Group("/common") + { + commonGroup.GET("/hash", middleware.PreAuthorize(nil), controller.NewCommont.Hash) + } + + // 文件操作处理 + fileGroup := router.Group("/file") + { + fileGroup.GET("/download/:filePath", middleware.PreAuthorize(nil), controller.NewFile.Download) + fileGroup.POST("/upload", middleware.PreAuthorize(nil), controller.NewFile.Upload) + fileGroup.POST("/chunkCheck", middleware.PreAuthorize(nil), controller.NewFile.ChunkCheck) + fileGroup.POST("/chunkUpload", middleware.PreAuthorize(nil), controller.NewFile.ChunkUpload) + fileGroup.POST("/chunkMerge", middleware.PreAuthorize(nil), controller.NewFile.ChunkMerge) + } +} diff --git a/src/modules/common/controller/account.go b/src/modules/common/controller/account.go new file mode 100644 index 0000000..4e137aa --- /dev/null +++ b/src/modules/common/controller/account.go @@ -0,0 +1,144 @@ +package controller + +import ( + "ems.agt/src/framework/config" + commonConstants "ems.agt/src/framework/constants/common" + tokenConstants "ems.agt/src/framework/constants/token" + ctxUtils "ems.agt/src/framework/utils/ctx" + tokenUtils "ems.agt/src/framework/utils/token" + "ems.agt/src/framework/vo/result" + libAccount "ems.agt/src/lib_features/account" + commonModel "ems.agt/src/modules/common/model" + commonService "ems.agt/src/modules/common/service" + systemService "ems.agt/src/modules/system/service" + "github.com/gin-gonic/gin" +) + +// 实例化控制层 AccountController 结构体 +var NewAccount = &AccountController{ + accountService: commonService.NewAccountImpl, + sysLogLoginService: systemService.NewSysLogLoginImpl, +} + +// 账号身份操作处理 +// +// PATH / +type AccountController struct { + // 账号身份操作服务 + accountService commonService.IAccount + // 系统登录访问 + sysLogLoginService systemService.ISysLogLogin +} + +// 系统登录 +// +// POST /login +func (s *AccountController) Login(c *gin.Context) { + var loginBody commonModel.LoginBody + if err := c.ShouldBindJSON(&loginBody); err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 当前请求信息 + ipaddr, location := ctxUtils.IPAddrLocation(c) + os, browser := ctxUtils.UaOsBrowser(c) + + // 校验验证码 + err := s.accountService.ValidateCaptcha( + loginBody.Code, + loginBody.UUID, + ) + // 根据错误信息,创建系统访问记录 + if err != nil { + msg := err.Error() + " " + loginBody.Code + s.sysLogLoginService.NewSysLogLogin( + loginBody.Username, commonConstants.STATUS_NO, msg, + ipaddr, location, os, browser, + ) + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 登录用户信息 + loginUser, err := s.accountService.LoginByUsername(loginBody.Username, loginBody.Password) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 生成令牌,创建系统访问记录 + tokenStr := tokenUtils.Create(&loginUser, ipaddr, location, os, browser) + if tokenStr == "" { + c.JSON(200, result.Err(nil)) + return + } else { + s.sysLogLoginService.NewSysLogLogin( + loginBody.Username, commonConstants.STATUS_YES, "登录成功", + ipaddr, location, os, browser, + ) + } + + // 设置登录会话-兼容旧登录方式 + libAccount.SessionToken(loginBody.Username, ipaddr) + + c.JSON(200, result.OkData(map[string]any{ + tokenConstants.RESPONSE_FIELD: tokenStr, + })) +} + +// 登录用户信息 +// +// GET /getInfo +func (s *AccountController) Info(c *gin.Context) { + loginUser, err := ctxUtils.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + return + } + + // 角色权限集合,管理员拥有所有权限 + isAdmin := config.IsAdmin(loginUser.UserID) + roles, perms := s.accountService.RoleAndMenuPerms(loginUser.UserID, isAdmin) + + c.JSON(200, result.OkData(map[string]any{ + "user": loginUser.User, + "roles": roles, + "permissions": perms, + })) +} + +// 登录用户路由信息 +// +// GET /getRouters +func (s *AccountController) Router(c *gin.Context) { + userID := ctxUtils.LoginUserToUserID(c) + + // 前端路由,管理员拥有所有 + isAdmin := config.IsAdmin(userID) + buildMenus := s.accountService.RouteMenus(userID, isAdmin) + c.JSON(200, result.OkData(buildMenus)) +} + +// 系统登出 +// +// POST /logout +func (s *AccountController) Logout(c *gin.Context) { + tokenStr := ctxUtils.Authorization(c) + if tokenStr != "" { + // 存在token时记录退出信息 + userName := tokenUtils.Remove(tokenStr) + if userName != "" { + // 当前请求信息 + ipaddr, location := ctxUtils.IPAddrLocation(c) + os, browser := ctxUtils.UaOsBrowser(c) + // 创建系统访问记录 + s.sysLogLoginService.NewSysLogLogin( + userName, commonConstants.STATUS_NO, "退出成功", + ipaddr, location, os, browser, + ) + } + } + + c.JSON(200, result.OkMsg("退出成功")) +} diff --git a/src/modules/common/controller/captcha.go b/src/modules/common/controller/captcha.go new file mode 100644 index 0000000..1a19883 --- /dev/null +++ b/src/modules/common/controller/captcha.go @@ -0,0 +1,129 @@ +package controller + +import ( + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/constants/captcha" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + systemService "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/mojocn/base64Captcha" +) + +// 实例化控制层 CaptchaController 结构体 +var NewCaptcha = &CaptchaController{ + sysConfigService: systemService.NewSysConfigImpl, +} + +// 验证码操作处理 +// +// PATH / +type CaptchaController struct { + // 参数配置服务 + sysConfigService systemService.ISysConfig +} + +// 获取验证码 +// +// GET /captchaImage +func (s *CaptchaController) Image(c *gin.Context) { + // 从数据库配置获取验证码开关 true开启,false关闭 + captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled") + captchaEnabled := parse.Boolean(captchaEnabledStr) + if !captchaEnabled { + c.JSON(200, result.Ok(map[string]any{ + "captchaEnabled": captchaEnabled, + })) + return + } + + // 生成唯一标识 + verifyKey := "" + data := map[string]any{ + "captchaEnabled": captchaEnabled, + "uuid": "", + "img": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", + } + + // 从数据库配置获取验证码类型 math 数值计算 char 字符验证 + captchaType := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaType") + if captchaType == captcha.TYPE_MATH { + math := config.Get("mathCaptcha").(map[string]any) + driverCaptcha := &base64Captcha.DriverMath{ + //Height png height in pixel. + Height: math["height"].(int), + // Width Captcha png width in pixel. + Width: math["width"].(int), + //NoiseCount text noise count. + NoiseCount: math["noise"].(int), + //ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine . + ShowLineOptions: base64Captcha.OptionShowHollowLine, + } + if math["color"].(bool) { + //BgColor captcha image background color (optional) + driverCaptcha.BgColor = parse.Color(math["background"].(string)) + } + // 验证码生成 + id, question, answer := driverCaptcha.GenerateIdQuestionAnswer() + // 验证码表达式解析输出 + item, err := driverCaptcha.DrawCaptcha(question) + if err != nil { + logger.Infof("Generate Id Question Answer %s %s : %v", captchaType, question, err) + } else { + data["uuid"] = id + data["img"] = item.EncodeB64string() + expiration := captcha.EXPIRATION * time.Second + verifyKey = cachekey.CAPTCHA_CODE_KEY + id + redis.SetByExpire("", verifyKey, answer, expiration) + } + } + if captchaType == captcha.TYPE_CHAR { + char := config.Get("charCaptcha").(map[string]any) + driverCaptcha := &base64Captcha.DriverString{ + //Height png height in pixel. + Height: char["height"].(int), + // Width Captcha png width in pixel. + Width: char["width"].(int), + //NoiseCount text noise count. + NoiseCount: char["noise"].(int), + //Length random string length. + Length: char["size"].(int), + //Source is a unicode which is the rand string from. + Source: char["chars"].(string), + //ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine . + ShowLineOptions: base64Captcha.OptionShowHollowLine, + } + if char["color"].(bool) { + //BgColor captcha image background color (optional) + driverCaptcha.BgColor = parse.Color(char["background"].(string)) + } + // 验证码生成 + id, question, answer := driverCaptcha.GenerateIdQuestionAnswer() + // 验证码表达式解析输出 + item, err := driverCaptcha.DrawCaptcha(question) + if err != nil { + logger.Infof("Generate Id Question Answer %s %s : %v", captchaType, question, err) + } else { + data["uuid"] = id + data["img"] = item.EncodeB64string() + expiration := captcha.EXPIRATION * time.Second + verifyKey = cachekey.CAPTCHA_CODE_KEY + id + redis.SetByExpire("", verifyKey, answer, expiration) + } + } + + // 本地开发下返回验证码结果,方便接口调试 + if config.Env() == "local" { + text, _ := redis.Get("", verifyKey) + data["text"] = text + c.JSON(200, result.Ok(data)) + return + } + c.JSON(200, result.Ok(data)) +} diff --git a/src/modules/common/controller/common.go b/src/modules/common/controller/common.go new file mode 100644 index 0000000..b3c2708 --- /dev/null +++ b/src/modules/common/controller/common.go @@ -0,0 +1,20 @@ +package controller + +import ( + "github.com/gin-gonic/gin" +) + +// 实例化控制层 CommontController 结构体 +var NewCommont = &CommontController{} + +// 通用请求 +// +// PATH / +type CommontController struct{} + +// 哈希加密 +// +// GET /hash +func (s *CommontController) Hash(c *gin.Context) { + c.String(200, "commont Hash") +} diff --git a/src/modules/common/controller/file.go b/src/modules/common/controller/file.go new file mode 100644 index 0000000..c1f9076 --- /dev/null +++ b/src/modules/common/controller/file.go @@ -0,0 +1,185 @@ +package controller + +import ( + "encoding/base64" + "fmt" + "net/url" + "strings" + + "ems.agt/src/framework/constants/uploadsubpath" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 FileController 结构体 +var NewFile = &FileController{} + +// 文件操作处理 +// +// PATH / +type FileController struct{} + +// 下载文件 +// +// GET /download/:filePath +func (s *FileController) Download(c *gin.Context) { + filePath := c.Param("filePath") + if len(filePath) < 8 { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // base64解析出地址 + decodedBytes, err := base64.StdEncoding.DecodeString(filePath) + if err != nil { + c.JSON(400, result.CodeMsg(400, err.Error())) + return + } + routerPath := string(decodedBytes) + // 地址文件名截取 + fileName := routerPath[strings.LastIndex(routerPath, "/")+1:] + + // 响应头 + c.Writer.Header().Set("Content-Disposition", `attachment; filename="`+url.QueryEscape(fileName)+`"`) + c.Writer.Header().Set("Accept-Ranges", "bytes") + c.Writer.Header().Set("Content-Type", "application/octet-stream") + + // 断点续传 + headerRange := c.GetHeader("Range") + resultMap, err := file.ReadUploadFileStream(routerPath, headerRange) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + if headerRange != "" { + c.Writer.Header().Set("Content-Range", fmt.Sprint(resultMap["range"])) + c.Writer.Header().Set("Content-Length", fmt.Sprint(resultMap["chunkSize"])) + c.Status(206) + } else { + c.Writer.Header().Set("Content-Length", fmt.Sprint(resultMap["fileSize"])) + c.Status(200) + + } + c.Writer.Write(resultMap["data"].([]byte)) +} + +// 上传文件 +// +// POST /upload +func (s *FileController) Upload(c *gin.Context) { + // 上传的文件 + formFile, err := c.FormFile("file") + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 子路径 + subPath := c.PostForm("subPath") + if _, ok := uploadsubpath.UploadSubpath[subPath]; !ok { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 上传文件转存 + upFilePath, err := file.TransferUploadFile(formFile, subPath, nil) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + newFileName := upFilePath[strings.LastIndex(upFilePath, "/")+1:] + c.JSON(200, result.OkData(map[string]string{ + "url": "http://" + c.Request.Host + upFilePath, + "fileName": upFilePath, + "newFileName": newFileName, + "originalFileName": formFile.Filename, + })) +} + +// 切片文件检查 +// +// POST /chunkCheck +func (s *FileController) ChunkCheck(c *gin.Context) { + var body struct { + // 唯一标识 + Identifier string `json:"identifier" binding:"required"` + // 文件名 + FileName string `json:"fileName" binding:"required"` + } + err := c.ShouldBindJSON(&body) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 读取标识目录 + chunks, err := file.ChunkCheckFile(body.Identifier, body.FileName) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(200, result.OkData(chunks)) +} + +// 切片文件合并 +// +// POST /chunkMerge +func (s *FileController) ChunkMerge(c *gin.Context) { + var body struct { + // 唯一标识 + Identifier string `json:"identifier" binding:"required"` + // 文件名 + FileName string `json:"fileName" binding:"required"` + // 子路径类型 + SubPath string `json:"subPath" binding:"required"` + } + err := c.ShouldBindJSON(&body) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + if _, ok := uploadsubpath.UploadSubpath[body.SubPath]; !ok { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 切片文件合并 + mergeFilePath, err := file.ChunkMergeFile(body.Identifier, body.FileName, body.SubPath) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + newFileName := mergeFilePath[strings.LastIndex(mergeFilePath, "/")+1:] + c.JSON(200, result.OkData(map[string]string{ + "url": "http://" + c.Request.Host + mergeFilePath, + "fileName": mergeFilePath, + "newFileName": newFileName, + "originalFileName": body.FileName, + })) +} + +// 切片文件上传 +// +// POST /chunkUpload +func (s *FileController) ChunkUpload(c *gin.Context) { + // 切片编号 + index := c.PostForm("index") + // 切片唯一标识 + identifier := c.PostForm("identifier") + // 上传的文件 + formFile, err := c.FormFile("file") + if index == "" || identifier == "" || err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 上传文件转存 + chunkFilePath, err := file.TransferChunkUploadFile(formFile, index, identifier) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(206, result.OkData(chunkFilePath)) +} diff --git a/src/modules/common/controller/index.go b/src/modules/common/controller/index.go new file mode 100644 index 0000000..e295e10 --- /dev/null +++ b/src/modules/common/controller/index.go @@ -0,0 +1,28 @@ +package controller + +import ( + "fmt" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/vo/result" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 IndexController 结构体 +var NewIndex = &IndexController{} + +// 路由主页 +// +// PATH / +type IndexController struct{} + +// 根路由 +// +// GET / +func (s *IndexController) Handler(c *gin.Context) { + name := config.Get("framework.name").(string) + version := config.Get("framework.version").(string) + str := "欢迎使用%s后台管理框架,当前版本:%s,请通过前端管理地址访问。" + c.JSON(200, result.OkMsg(fmt.Sprintf(str, name, version))) +} diff --git a/src/modules/common/controller/register.go b/src/modules/common/controller/register.go new file mode 100644 index 0000000..2b3e945 --- /dev/null +++ b/src/modules/common/controller/register.go @@ -0,0 +1,88 @@ +package controller + +import ( + "strings" + + commonConstants "ems.agt/src/framework/constants/common" + ctxUtils "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/framework/vo/result" + commonModel "ems.agt/src/modules/common/model" + commonService "ems.agt/src/modules/common/service" + systemService "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 RegisterController 结构体 +var NewRegister = &RegisterController{ + registerService: commonService.NewRegisterImpl, + sysLogLoginService: systemService.NewSysLogLoginImpl, +} + +// 账号注册操作处理 +// +// PATH / +type RegisterController struct { + // 账号注册操作服务 + registerService commonService.IRegister + // 系统登录访问 + sysLogLoginService systemService.ISysLogLogin +} + +// 账号注册 +// +// GET /captchaImage +func (s *RegisterController) UserName(c *gin.Context) { + var registerBody commonModel.RegisterBody + if err := c.ShouldBindJSON(®isterBody); err != nil { + c.JSON(400, result.ErrMsg("参数错误")) + return + } + + // 判断必传参数 + if !regular.ValidUsername(registerBody.Username) { + c.JSON(200, result.ErrMsg("账号不能以数字开头,可包含大写小写字母,数字,且不少于5位")) + return + } + if !regular.ValidPassword(registerBody.Password) { + c.JSON(200, result.ErrMsg("登录密码至少包含大小写字母、数字、特殊符号,且不少于6位")) + return + } + if registerBody.Password != registerBody.ConfirmPassword { + c.JSON(200, result.ErrMsg("用户确认输入密码不一致")) + return + } + + // 当前请求信息 + ipaddr, location := ctxUtils.IPAddrLocation(c) + os, browser := ctxUtils.UaOsBrowser(c) + + // 校验验证码 + err := s.registerService.ValidateCaptcha( + registerBody.Code, + registerBody.UUID, + ) + // 根据错误信息,创建系统访问记录 + if err != nil { + msg := err.Error() + " " + registerBody.Code + s.sysLogLoginService.NewSysLogLogin( + registerBody.Username, commonConstants.STATUS_NO, msg, + ipaddr, location, os, browser, + ) + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + infoStr := s.registerService.ByUserName(registerBody.Username, registerBody.Password, registerBody.UserType) + if !strings.HasPrefix(infoStr, "注册") { + msg := registerBody.Username + " 注册成功 " + infoStr + s.sysLogLoginService.NewSysLogLogin( + registerBody.Username, commonConstants.STATUS_NO, msg, + ipaddr, location, os, browser, + ) + c.JSON(200, result.OkMsg("注册成功")) + return + } + c.JSON(200, result.ErrMsg(infoStr)) +} diff --git a/src/modules/common/model/login_body.go b/src/modules/common/model/login_body.go new file mode 100644 index 0000000..ec37763 --- /dev/null +++ b/src/modules/common/model/login_body.go @@ -0,0 +1,16 @@ +package model + +// LoginBody 用户登录对象 +type LoginBody struct { + // Username 用户名 + Username string `json:"username" binding:"required"` + + // Password 用户密码 + Password string `json:"password" binding:"required"` + + // Code 验证码 + Code string `json:"code"` + + // UUID 验证码唯一标识 + UUID string `json:"uuid"` +} diff --git a/src/modules/common/model/register_body.go b/src/modules/common/model/register_body.go new file mode 100644 index 0000000..4bf1114 --- /dev/null +++ b/src/modules/common/model/register_body.go @@ -0,0 +1,22 @@ +package model + +// RegisterBody 用户注册对象 +type RegisterBody struct { + // Username 用户名 + Username string `json:"username" binding:"required"` + + // Password 用户密码 + Password string `json:"password" binding:"required"` + + // ConfirmPassword 用户确认密码 + ConfirmPassword string `json:"confirmPassword" binding:"required"` + + // Code 验证码 + Code string `json:"code"` + + // UUID 验证码唯一标识 + UUID string `json:"uuid"` + + // UserType 标记用户类型 + UserType string `json:"userType"` +} diff --git a/src/modules/common/service/account.go b/src/modules/common/service/account.go new file mode 100644 index 0000000..5f8c42f --- /dev/null +++ b/src/modules/common/service/account.go @@ -0,0 +1,21 @@ +package service + +import "ems.agt/src/framework/vo" + +// 账号身份操作服务 服务层接口 +type IAccount interface { + // ValidateCaptcha 校验验证码 + ValidateCaptcha(code, uuid string) error + + // LoginByUsername 登录生成token + LoginByUsername(username, password string) (vo.LoginUser, error) + + // ClearLoginRecordCache 清除错误记录次数 + ClearLoginRecordCache(username string) bool + + // RoleAndMenuPerms 角色和菜单数据权限 + RoleAndMenuPerms(userId string, isAdmin bool) ([]string, []string) + + // RouteMenus 前端路由所需要的菜单 + RouteMenus(userId string, isAdmin bool) []vo.Router +} diff --git a/src/modules/common/service/account.impl.go b/src/modules/common/service/account.impl.go new file mode 100644 index 0000000..82d0c8f --- /dev/null +++ b/src/modules/common/service/account.impl.go @@ -0,0 +1,166 @@ +package service + +import ( + "errors" + "fmt" + "time" + + "ems.agt/src/framework/config" + adminConstants "ems.agt/src/framework/constants/admin" + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/crypto" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo" + systemService "ems.agt/src/modules/system/service" +) + +// 实例化服务层 AccountImpl 结构体 +var NewAccountImpl = &AccountImpl{ + sysUserService: systemService.NewSysUserImpl, + sysConfigService: systemService.NewSysConfigImpl, + sysRoleService: systemService.NewSysRoleImpl, + sysMenuService: systemService.NewSysMenuImpl, +} + +// 账号身份操作服务 服务层处理 +type AccountImpl struct { + // 用户信息服务 + sysUserService systemService.ISysUser + // 参数配置服务 + sysConfigService systemService.ISysConfig + // 角色服务 + sysRoleService systemService.ISysRole + // 菜单服务 + sysMenuService systemService.ISysMenu +} + +// ValidateCaptcha 校验验证码 +func (s *AccountImpl) ValidateCaptcha(code, uuid string) error { + // 验证码检查,从数据库配置获取验证码开关 true开启,false关闭 + captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled") + if !parse.Boolean(captchaEnabledStr) { + return nil + } + if code == "" || uuid == "" { + return errors.New("验证码信息错误") + } + verifyKey := cachekey.CAPTCHA_CODE_KEY + uuid + captcha, _ := redis.Get("", verifyKey) + if captcha == "" { + return errors.New("验证码已失效") + } + redis.Del("", verifyKey) + if captcha != code { + return errors.New("验证码错误") + } + return nil +} + +// LoginByUsername 登录创建用户信息 +func (s *AccountImpl) LoginByUsername(username, password string) (vo.LoginUser, error) { + loginUser := vo.LoginUser{} + + // 检查密码重试次数 + retrykey, retryCount, lockTime, err := s.passwordRetryCount(username) + if err != nil { + return loginUser, err + } + + // 查询用户登录账号 + sysUser := s.sysUserService.SelectUserByUserName(username) + if sysUser.UserName != username { + return loginUser, errors.New("用户不存在或密码错误") + } + if sysUser.DelFlag == common.STATUS_YES { + return loginUser, errors.New("对不起,您的账号已被删除") + } + if sysUser.Status == common.STATUS_NO { + return loginUser, errors.New("对不起,您的账号已禁用") + } + + // 检验用户密码 + compareBool := crypto.BcryptCompare(password, sysUser.Password) + if !compareBool { + redis.SetByExpire("", retrykey, retryCount+1, lockTime) + return loginUser, errors.New("用户不存在或密码错误") + } else { + // 清除错误记录次数 + s.ClearLoginRecordCache(username) + } + + // 登录用户信息 + loginUser.UserID = sysUser.UserID + loginUser.DeptID = sysUser.DeptID + loginUser.User = sysUser + // 用户权限组标识 + isAdmin := config.IsAdmin(sysUser.UserID) + if isAdmin { + loginUser.Permissions = []string{adminConstants.PERMISSION} + } else { + perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID) + loginUser.Permissions = parse.RemoveDuplicates(perms) + } + return loginUser, nil +} + +// ClearLoginRecordCache 清除错误记录次数 +func (s *AccountImpl) ClearLoginRecordCache(username string) bool { + cacheKey := cachekey.PWD_ERR_CNT_KEY + username + hasKey, _ := redis.Has("", cacheKey) + if hasKey { + delOk, _ := redis.Del("", cacheKey) + return delOk + } + return false +} + +// passwordRetryCount 密码重试次数 +func (s *AccountImpl) passwordRetryCount(username string) (string, int64, time.Duration, error) { + // 验证登录次数和错误锁定时间 + maxRetryCount := config.Get("user.password.maxRetryCount").(int) + lockTime := config.Get("user.password.lockTime").(int) + // 验证缓存记录次数 + retrykey := cachekey.PWD_ERR_CNT_KEY + username + retryCount, err := redis.Get("", retrykey) + if retryCount == "" || err != nil { + retryCount = "0" + } + // 是否超过错误值 + if parse.Number(retryCount) >= int64(maxRetryCount) { + msg := fmt.Sprintf("密码输入错误 %d 次,帐户锁定 %d 分钟", maxRetryCount, lockTime) + return retrykey, int64(maxRetryCount), time.Duration(lockTime) * time.Minute, errors.New(msg) + } + return retrykey, int64(maxRetryCount), time.Duration(lockTime) * time.Minute, nil +} + +// RoleAndMenuPerms 角色和菜单数据权限 +func (s *AccountImpl) RoleAndMenuPerms(userId string, isAdmin bool) ([]string, []string) { + if isAdmin { + return []string{adminConstants.ROLE_KEY}, []string{adminConstants.PERMISSION} + } else { + // 角色key + roleGroup := []string{} + roles := s.sysRoleService.SelectRoleListByUserId(userId) + for _, role := range roles { + roleGroup = append(roleGroup, role.RoleKey) + } + // 菜单权限key + perms := s.sysMenuService.SelectMenuPermsByUserId(userId) + return parse.RemoveDuplicates(roleGroup), parse.RemoveDuplicates(perms) + } +} + +// RouteMenus 前端路由所需要的菜单 +func (s *AccountImpl) RouteMenus(userId string, isAdmin bool) []vo.Router { + var buildMenus []vo.Router + if isAdmin { + menus := s.sysMenuService.SelectMenuTreeByUserId("*") + buildMenus = s.sysMenuService.BuildRouteMenus(menus, "") + } else { + menus := s.sysMenuService.SelectMenuTreeByUserId(userId) + buildMenus = s.sysMenuService.BuildRouteMenus(menus, "") + } + return buildMenus +} diff --git a/src/modules/common/service/register.go b/src/modules/common/service/register.go new file mode 100644 index 0000000..6570c50 --- /dev/null +++ b/src/modules/common/service/register.go @@ -0,0 +1,10 @@ +package service + +// 账号注册操作处理 服务层接口 +type IRegister interface { + // ValidateCaptcha 校验验证码 + ValidateCaptcha(code, uuid string) error + + // ByUserName 账号注册 + ByUserName(username, password, userType string) string +} diff --git a/src/modules/common/service/register.impl.go b/src/modules/common/service/register.impl.go new file mode 100644 index 0000000..5d536fc --- /dev/null +++ b/src/modules/common/service/register.impl.go @@ -0,0 +1,100 @@ +package service + +import ( + "errors" + "fmt" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/utils/parse" + systemModel "ems.agt/src/modules/system/model" + systemService "ems.agt/src/modules/system/service" +) + +// 实例化服务层 RegisterImpl 结构体 +var NewRegisterImpl = &RegisterImpl{ + sysUserService: systemService.NewSysUserImpl, + sysConfigService: systemService.NewSysConfigImpl, + sysRoleService: systemService.NewSysRoleImpl, +} + +// 账号注册操作处理 服务层处理 +type RegisterImpl struct { + // 用户信息服务 + sysUserService systemService.ISysUser + // 参数配置服务 + sysConfigService systemService.ISysConfig + // 角色服务 + sysRoleService systemService.ISysRole +} + +// ValidateCaptcha 校验验证码 +func (s *RegisterImpl) ValidateCaptcha(code, uuid string) error { + // 验证码检查,从数据库配置获取验证码开关 true开启,false关闭 + captchaEnabledStr := s.sysConfigService.SelectConfigValueByKey("sys.account.captchaEnabled") + if !parse.Boolean(captchaEnabledStr) { + return nil + } + if code == "" || uuid == "" { + return errors.New("验证码信息错误") + } + verifyKey := cachekey.CAPTCHA_CODE_KEY + uuid + captcha, err := redis.Get("", verifyKey) + if captcha == "" || err != nil { + return errors.New("验证码已失效") + } + redis.Del("", verifyKey) + if captcha != code { + return errors.New("验证码错误") + } + return nil +} + +// ByUserName 账号注册 +func (s *RegisterImpl) ByUserName(username, password, userType string) string { + // 检查用户登录账号是否唯一 + uniqueUserName := s.sysUserService.CheckUniqueUserName(username, "") + if !uniqueUserName { + return fmt.Sprintf("注册用户【%s】失败,注册账号已存在", username) + } + + sysUser := systemModel.SysUser{ + UserName: username, + NickName: username, // 昵称使用名称账号 + Password: password, // 原始密码 + Status: common.STATUS_YES, // 账号状态激活 + DeptID: "100", // 归属部门为根节点 + CreateBy: "注册", // 创建来源 + } + // 标记用户类型 + if userType == "" { + sysUser.UserType = "sys" + } + // 新增用户的角色管理 + sysUser.RoleIDs = s.registerRoleInit(userType) + // 新增用户的岗位管理 + sysUser.PostIDs = s.registerPostInit(userType) + + insertId := s.sysUserService.InsertUser(sysUser) + if insertId != "" { + return insertId + } + return "注册失败,请联系系统管理人员" +} + +// registerRoleInit 注册初始角色 +func (s *RegisterImpl) registerRoleInit(userType string) []string { + if userType == "sys" { + return []string{} + } + return []string{} +} + +// registerPostInit 注册初始岗位 +func (s *RegisterImpl) registerPostInit(userType string) []string { + if userType == "sys" { + return []string{} + } + return []string{} +} diff --git a/src/modules/monitor/controller/sys_cache.go b/src/modules/monitor/controller/sys_cache.go new file mode 100644 index 0000000..1731287 --- /dev/null +++ b/src/modules/monitor/controller/sys_cache.go @@ -0,0 +1,149 @@ +package controller + +import ( + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/monitor/model" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SysCacheController 结构体 +var NewSysCache = &SysCacheController{} + +// 缓存监控信息 +// +// PATH /monitor/cache +type SysCacheController struct{} + +// Redis信息 +// +// GET / +func (s *SysCacheController) Info(c *gin.Context) { + c.JSON(200, result.OkData(map[string]any{ + "info": redis.Info(""), + "dbSize": redis.KeySize(""), + "commandStats": redis.CommandStats(""), + })) +} + +// 缓存名称列表 +// +// GET /getNames +func (s *SysCacheController) Names(c *gin.Context) { + caches := []model.SysCache{ + model.NewSysCacheNames("用户信息", cachekey.LOGIN_TOKEN_KEY), + model.NewSysCacheNames("配置信息", cachekey.SYS_CONFIG_KEY), + model.NewSysCacheNames("数据字典", cachekey.SYS_DICT_KEY), + model.NewSysCacheNames("验证码", cachekey.CAPTCHA_CODE_KEY), + model.NewSysCacheNames("防重提交", cachekey.REPEAT_SUBMIT_KEY), + model.NewSysCacheNames("限流处理", cachekey.RATE_LIMIT_KEY), + model.NewSysCacheNames("密码错误次数", cachekey.PWD_ERR_CNT_KEY), + } + c.JSON(200, result.OkData(caches)) +} + +// 缓存名称下键名列表 +// +// GET /getKeys/:cacheName +func (s *SysCacheController) Keys(c *gin.Context) { + cacheName := c.Param("cacheName") + if cacheName == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + caches := []model.SysCache{} + + // 遍历组装 + cacheKeys, _ := redis.GetKeys("", cacheName+":*") + for _, key := range cacheKeys { + caches = append(caches, model.NewSysCacheKeys(cacheName, key)) + } + + c.JSON(200, result.OkData(caches)) +} + +// 缓存内容 +// +// GET /getValue/:cacheName/:cacheKey +func (s *SysCacheController) Value(c *gin.Context) { + cacheName := c.Param("cacheName") + cacheKey := c.Param("cacheKey") + if cacheName == "" || cacheKey == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + cacheValue, err := redis.Get("", cacheName+":"+cacheKey) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + sysCache := model.NewSysCacheValue(cacheName, cacheKey, cacheValue) + c.JSON(200, result.OkData(sysCache)) +} + +// 删除缓存名称下键名列表 +// +// DELETE /clearCacheName/:cacheName +func (s *SysCacheController) ClearCacheName(c *gin.Context) { + cacheName := c.Param("cacheName") + if cacheName == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + cacheKeys, err := redis.GetKeys("", cacheName+":*") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + ok, _ := redis.DelKeys("", cacheKeys) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 删除缓存键名 +// +// DELETE /clearCacheKey/:cacheName/:cacheKey +func (s *SysCacheController) ClearCacheKey(c *gin.Context) { + cacheName := c.Param("cacheName") + cacheKey := c.Param("cacheKey") + if cacheName == "" || cacheKey == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + ok, _ := redis.Del("", cacheName+":"+cacheKey) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 安全清理缓存名称 +// +// DELETE /clearCacheSafe +func (s *SysCacheController) ClearCacheSafe(c *gin.Context) { + caches := []model.SysCache{ + model.NewSysCacheNames("配置信息", cachekey.SYS_CONFIG_KEY), + model.NewSysCacheNames("数据字典", cachekey.SYS_DICT_KEY), + model.NewSysCacheNames("验证码", cachekey.CAPTCHA_CODE_KEY), + model.NewSysCacheNames("防重提交", cachekey.REPEAT_SUBMIT_KEY), + model.NewSysCacheNames("限流处理", cachekey.RATE_LIMIT_KEY), + model.NewSysCacheNames("密码错误次数", cachekey.PWD_ERR_CNT_KEY), + } + for _, v := range caches { + cacheKeys, err := redis.GetKeys("", v.CacheName+":*") + if err != nil { + continue + } + redis.DelKeys("", cacheKeys) + } + c.JSON(200, result.Ok(nil)) +} diff --git a/src/modules/monitor/controller/sys_job.go b/src/modules/monitor/controller/sys_job.go new file mode 100644 index 0000000..31bed89 --- /dev/null +++ b/src/modules/monitor/controller/sys_job.go @@ -0,0 +1,338 @@ +package controller + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/service" + systemService "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysJobLogController 结构体 +var NewSysJob = &SysJobController{ + sysJobService: service.NewSysJobImpl, + sysDictDataService: systemService.NewSysDictDataImpl, +} + +// 调度任务信息 +// +// PATH /monitor/job +type SysJobController struct { + // 调度任务服务 + sysJobService service.ISysJob + // 字典数据服务 + sysDictDataService systemService.ISysDictData +} + +// 调度任务列表 +// +// GET /list +func (s *SysJobController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysJobService.SelectJobPage(querys) + c.JSON(200, result.Ok(data)) +} + +// 调度任务信息 +// +// GET /:jobId +func (s *SysJobController) Info(c *gin.Context) { + jobId := c.Param("jobId") + if jobId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + data := s.sysJobService.SelectJobById(jobId) + if data.JobID == jobId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务新增 +// +// POST / +func (s *SysJobController) Add(c *gin.Context) { + var body model.SysJob + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.JobID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查cron表达式格式 + if parse.CronExpression(body.CronExpression) == 0 { + msg := fmt.Sprintf("调度任务新增【%s】失败,Cron表达式不正确", body.JobName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查任务调用传入参数是否json格式 + if body.TargetParams != "" { + msg := fmt.Sprintf("调度任务新增【%s】失败,任务传入参数json字符串不正确", body.JobName) + if len(body.TargetParams) < 7 { + c.JSON(200, result.ErrMsg(msg)) + return + } + if !json.Valid([]byte(body.TargetParams)) { + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查属性值唯一 + uniqueJob := s.sysJobService.CheckUniqueJobName(body.JobName, body.JobGroup, "") + if !uniqueJob { + msg := fmt.Sprintf("调度任务新增【%s】失败,同任务组内有相同任务名称", body.JobName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysJobService.InsertJob(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务修改 +// +// PUT / +func (s *SysJobController) Edit(c *gin.Context) { + var body model.SysJob + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.JobID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查cron表达式格式 + if parse.CronExpression(body.CronExpression) == 0 { + msg := fmt.Sprintf("调度任务修改【%s】失败,Cron表达式不正确", body.JobName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查任务调用传入参数是否json格式 + if body.TargetParams != "" { + msg := fmt.Sprintf("调度任务修改【%s】失败,任务传入参数json字符串不正确", body.JobName) + if len(body.TargetParams) < 7 { + c.JSON(200, result.ErrMsg(msg)) + return + } + if !json.Valid([]byte(body.TargetParams)) { + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查属性值唯一 + uniqueJob := s.sysJobService.CheckUniqueJobName(body.JobName, body.JobGroup, body.JobID) + if !uniqueJob { + msg := fmt.Sprintf("调度任务修改【%s】失败,同任务组内有相同任务名称", body.JobName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysJobService.UpdateJob(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务删除 +// +// DELETE /:jobIds +func (s *SysJobController) Remove(c *gin.Context) { + jobIds := c.Param("jobIds") + if jobIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(jobIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysJobService.DeleteJobByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 调度任务修改状态 +// +// PUT /changeStatus +func (s *SysJobController) Status(c *gin.Context) { + var body struct { + // 任务ID + JobId string `json:"jobId" binding:"required"` + // 状态 + Status string `json:"status" binding:"required"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + job := s.sysJobService.SelectJobById(body.JobId) + if job.JobID != body.JobId { + c.JSON(200, result.ErrMsg("没有权限访问调度任务数据!")) + return + } + + // 与旧值相等不变更 + if job.Status == body.Status { + c.JSON(200, result.ErrMsg("变更状态与旧值相等!")) + return + } + + // 更新状态 + job.Status = body.Status + job.UpdateBy = ctx.LoginUserToUserName(c) + ok := s.sysJobService.ChangeStatus(job) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务立即执行一次 +// +// PUT /run/:jobId +func (s *SysJobController) Run(c *gin.Context) { + jobId := c.Param("jobId") + if jobId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + job := s.sysJobService.SelectJobById(jobId) + if job.JobID != jobId { + c.JSON(200, result.ErrMsg("没有权限访问调度任务数据!")) + return + } + + ok := s.sysJobService.RunQueueJob(job) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务重置刷新队列 +// +// PUT /resetQueueJob +func (s *SysJobController) ResetQueueJob(c *gin.Context) { + s.sysJobService.ResetQueueJob() + c.JSON(200, result.Ok(nil)) +} + +// 导出调度任务信息 +// +// POST /export +func (s *SysJobController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysJobService.SelectJobPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysJob) + + // 导出文件名称 + fileName := fmt.Sprintf("job_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "任务编号", + "B1": "任务名称", + "C1": "任务组名", + "D1": "调用目标", + "E1": "传入参数", + "F1": "执行表达式", + "G1": "出错策略", + "H1": "并发执行", + "I1": "任务状态", + "J1": "备注说明", + } + // 读取任务组名字典数据 + dictSysJobGroup := s.sysDictDataService.SelectDictDataByType("sys_job_group") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 任务组名 + sysJobGroup := "" + for _, v := range dictSysJobGroup { + if row.JobGroup == v.DictValue { + sysJobGroup = v.DictLabel + break + } + } + misfirePolicy := "放弃执行" + if row.MisfirePolicy == "1" { + misfirePolicy = "立即执行" + } else if row.MisfirePolicy == "2" { + misfirePolicy = "执行一次" + } + concurrent := "禁止" + if row.Concurrent == "1" { + concurrent = "允许" + } + // 状态 + statusValue := "失败" + if row.Status == "1" { + statusValue = "成功" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.JobID, + "B" + idx: row.JobName, + "C" + idx: sysJobGroup, + "D" + idx: row.InvokeTarget, + "E" + idx: row.TargetParams, + "F" + idx: row.CronExpression, + "G" + idx: misfirePolicy, + "H" + idx: concurrent, + "I" + idx: statusValue, + "J" + idx: row.Remark, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/monitor/controller/sys_job_log.go b/src/modules/monitor/controller/sys_job_log.go new file mode 100644 index 0000000..640b1fa --- /dev/null +++ b/src/modules/monitor/controller/sys_job_log.go @@ -0,0 +1,167 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/service" + systemService "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SysJobLogController 结构体 +var NewSysJobLog = &SysJobLogController{ + sysJobLogService: service.NewSysJobLogImpl, + sysDictDataService: systemService.NewSysDictDataImpl, +} + +// 调度任务日志信息 +// +// PATH /monitor/jobLog +type SysJobLogController struct { + // 调度任务日志服务 + sysJobLogService service.ISysJobLog + // 字典数据服务 + sysDictDataService systemService.ISysDictData +} + +// 调度任务日志列表 +// +// GET /list +func (s *SysJobLogController) List(c *gin.Context) { + // 查询参数转换map + querys := ctx.QueryMap(c) + list := s.sysJobLogService.SelectJobLogPage(querys) + c.JSON(200, result.Ok(list)) +} + +// 调度任务日志信息 +// +// GET /:jobLogId +func (s *SysJobLogController) Info(c *gin.Context) { + jobLogId := c.Param("jobLogId") + if jobLogId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysJobLogService.SelectJobLogById(jobLogId) + if data.JobLogID == jobLogId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务日志删除 +// +// DELETE /:jobLogIds +func (s *SysJobLogController) Remove(c *gin.Context) { + jobLogIds := c.Param("jobLogIds") + if jobLogIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 处理字符转id数组后去重 + ids := strings.Split(jobLogIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows := s.sysJobLogService.DeleteJobLogByIds(uniqueIDs) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 调度任务日志清空 +// +// DELETE /clean +func (s *SysJobLogController) Clean(c *gin.Context) { + err := s.sysJobLogService.CleanJobLog() + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(200, result.Ok(nil)) +} + +// 导出调度任务日志信息 +// +// POST /export +func (s *SysJobLogController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysJobLogService.SelectJobLogPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysJobLog) + + // 导出文件名称 + fileName := fmt.Sprintf("jobLog_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "日志序号", + "B1": "任务名称", + "C1": "任务组名", + "D1": "调用目标", + "E1": "传入参数", + "F1": "日志信息", + "G1": "执行状态", + "H1": "记录时间", + } + // 读取任务组名字典数据 + dictSysJobGroup := s.sysDictDataService.SelectDictDataByType("sys_job_group") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 任务组名 + sysJobGroup := "" + for _, v := range dictSysJobGroup { + if row.JobGroup == v.DictValue { + sysJobGroup = v.DictLabel + break + } + } + // 状态 + statusValue := "失败" + if row.Status == "1" { + statusValue = "成功" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.JobLogID, + "B" + idx: row.JobName, + "C" + idx: sysJobGroup, + "D" + idx: row.InvokeTarget, + "E" + idx: row.TargetParams, + "F" + idx: row.JobMsg, + "G" + idx: statusValue, + "H" + idx: date.ParseDateToStr(row.CreateTime, date.YYYY_MM_DD_HH_MM_SS), + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/monitor/controller/sys_user_online.go b/src/modules/monitor/controller/sys_user_online.go new file mode 100644 index 0000000..721ee67 --- /dev/null +++ b/src/modules/monitor/controller/sys_user_online.go @@ -0,0 +1,126 @@ +package controller + +import ( + "encoding/json" + "sort" + "strings" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/redis" + "ems.agt/src/framework/vo" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SysUserOnlineController 结构体 +var NewSysUserOnline = &SysUserOnlineController{ + sysUserOnlineService: service.NewSysUserOnlineImpl, +} + +// 在线用户监控 +// +// PATH /monitor/online +type SysUserOnlineController struct { + // 在线用户服务 + sysUserOnlineService service.ISysUserOnline +} + +// 在线用户列表 +// +// GET /list +func (s *SysUserOnlineController) List(c *gin.Context) { + ipaddr := c.Query("ipaddr") + userName := c.Query("userName") + + // 获取所有在线用户key + keys, _ := redis.GetKeys("", cachekey.LOGIN_TOKEN_KEY+"*") + + // 分批获取 + arr := make([]string, 0) + for i := 0; i < len(keys); i += 20 { + end := i + 20 + if end > len(keys) { + end = len(keys) + } + chunk := keys[i:end] + values, _ := redis.GetBatch("", chunk) + for _, v := range values { + arr = append(arr, v.(string)) + } + } + + // 遍历字符串信息解析组合可用对象 + userOnlines := make([]model.SysUserOnline, 0) + for _, str := range arr { + if str == "" { + continue + } + + var loginUser vo.LoginUser + err := json.Unmarshal([]byte(str), &loginUser) + if err != nil { + continue + } + + onlineUser := s.sysUserOnlineService.LoginUserToUserOnline(loginUser) + if onlineUser.TokenID != "" { + userOnlines = append(userOnlines, onlineUser) + } + } + + // 根据查询条件过滤 + filteredUserOnlines := make([]model.SysUserOnline, 0) + if ipaddr != "" && userName != "" { + for _, o := range userOnlines { + if strings.Contains(o.IPAddr, ipaddr) && strings.Contains(o.UserName, userName) { + filteredUserOnlines = append(filteredUserOnlines, o) + } + } + } else if ipaddr != "" { + for _, o := range userOnlines { + if strings.Contains(o.IPAddr, ipaddr) { + filteredUserOnlines = append(filteredUserOnlines, o) + } + } + } else if userName != "" { + for _, o := range userOnlines { + if strings.Contains(o.UserName, userName) { + filteredUserOnlines = append(filteredUserOnlines, o) + } + } + } else { + filteredUserOnlines = userOnlines + } + + // 按登录时间排序 + sort.Slice(filteredUserOnlines, func(i, j int) bool { + return filteredUserOnlines[j].LoginTime > filteredUserOnlines[i].LoginTime + }) + + c.JSON(200, result.Ok(map[string]any{ + "total": len(filteredUserOnlines), + "rows": filteredUserOnlines, + })) +} + +// 在线用户强制退出 +// +// DELETE /:tokenId +func (s *SysUserOnlineController) ForceLogout(c *gin.Context) { + tokenId := c.Param("tokenId") + if tokenId == "" || tokenId == "*" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 删除token + ok, _ := redis.Del("", cachekey.LOGIN_TOKEN_KEY+tokenId) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} diff --git a/src/modules/monitor/controller/system_info.go b/src/modules/monitor/controller/system_info.go new file mode 100644 index 0000000..13004e1 --- /dev/null +++ b/src/modules/monitor/controller/system_info.go @@ -0,0 +1,36 @@ +package controller + +import ( + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/monitor/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SystemInfoController 结构体 +var NewSystemInfo = &SystemInfoController{ + systemInfogService: service.NewSystemInfoImpl, +} + +// 服务器监控信息 +// +// PATH /monitor/system-info +type SystemInfoController struct { + // 服务器系统相关信息服务 + systemInfogService service.ISystemInfo +} + +// 服务器信息 +// +// GET / +func (s *SystemInfoController) Info(c *gin.Context) { + c.JSON(200, result.OkData(map[string]any{ + "project": s.systemInfogService.ProjectInfo(), + "cpu": s.systemInfogService.CPUInfo(), + "memory": s.systemInfogService.MemoryInfo(), + "network": s.systemInfogService.NetworkInfo(), + "time": s.systemInfogService.TimeInfo(), + "system": s.systemInfogService.SystemInfo(), + "disk": s.systemInfogService.DiskInfo(), + })) +} diff --git a/src/modules/monitor/model/sys_cache.go b/src/modules/monitor/model/sys_cache.go new file mode 100644 index 0000000..29e79bc --- /dev/null +++ b/src/modules/monitor/model/sys_cache.go @@ -0,0 +1,41 @@ +package model + +import "strings" + +// SysCache 缓存信息对象 +type SysCache struct { + CacheName string `json:"cacheName"` // 缓存名称 + CacheKey string `json:"cacheKey"` // 缓存键名 + CacheValue string `json:"cacheValue"` // 缓存内容 + Remark string `json:"remark"` // 备注 +} + +// NewSysCacheNames 创建新的缓存名称列表项实例 +func NewSysCacheNames(cacheName string, cacheKey string) SysCache { + return SysCache{ + CacheName: cacheKey[:len(cacheKey)-1], + CacheKey: "", + CacheValue: "", + Remark: cacheName, + } +} + +// NewSysCacheKeys 创建新的缓存键名列表项实例 +func NewSysCacheKeys(cacheName string, cacheKey string) SysCache { + return SysCache{ + CacheName: cacheName, + CacheKey: strings.Replace(cacheKey, cacheName+":", "", 1), + CacheValue: "", + Remark: "", + } +} + +// NewSysCacheValue 创建新的缓存键名内容项实例 +func NewSysCacheValue(cacheName string, cacheKey string, cacheValue string) SysCache { + return SysCache{ + CacheName: cacheName, + CacheKey: cacheKey, + CacheValue: cacheValue, + Remark: "", + } +} diff --git a/src/modules/monitor/model/sys_job.go b/src/modules/monitor/model/sys_job.go new file mode 100644 index 0000000..598f129 --- /dev/null +++ b/src/modules/monitor/model/sys_job.go @@ -0,0 +1,33 @@ +package model + +// SysJob 调度任务信息表 sys_job +type SysJob struct { + // 任务ID + JobID string `json:"jobId"` + // 任务名称 + JobName string `json:"jobName" binding:"required"` + // 任务组名 + JobGroup string `json:"jobGroup" binding:"required"` + // 调用目标字符串 + InvokeTarget string `json:"invokeTarget" binding:"required"` + // 调用目标传入参数 + TargetParams string `json:"targetParams"` + // cron执行表达式 + CronExpression string `json:"cronExpression" binding:"required"` + // 计划执行错误策略(1立即执行 2执行一次 3放弃执行) + MisfirePolicy string `json:"misfirePolicy"` + // 是否并发执行(0禁止 1允许) + Concurrent string `json:"concurrent"` + // 任务状态(0暂停 1正常) + Status string `json:"status"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/monitor/model/sys_job_log.go b/src/modules/monitor/model/sys_job_log.go new file mode 100644 index 0000000..c9a27bc --- /dev/null +++ b/src/modules/monitor/model/sys_job_log.go @@ -0,0 +1,23 @@ +package model + +// SysJobLog 定时任务调度日志表 sys_job_log +type SysJobLog struct { + // 日志序号 + JobLogID string `json:"jobLogId"` + // 任务名称 + JobName string `json:"jobName"` + // 任务组名 + JobGroup string `json:"jobGroup"` + // 调用目标字符串 + InvokeTarget string `json:"invokeTarget"` + // 调用目标传入参数 + TargetParams string `json:"targetParams"` + // 日志信息 + JobMsg string `json:"jobMsg"` + // 执行状态(0失败 1正常) + Status string `json:"status"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 消耗时间(毫秒) + CostTime int64 `json:"costTime"` +} diff --git a/src/modules/monitor/model/sys_user_online.go b/src/modules/monitor/model/sys_user_online.go new file mode 100644 index 0000000..2c8a1b2 --- /dev/null +++ b/src/modules/monitor/model/sys_user_online.go @@ -0,0 +1,21 @@ +package model + +// SysUserOnline 当前在线会话对象 +type SysUserOnline struct { + // 会话编号 + TokenID string `json:"tokenId"` + // 部门名称 + DeptName string `json:"deptName"` + // 用户名称 + UserName string `json:"userName"` + // 登录IP地址 + IPAddr string `json:"ipaddr"` + // 登录地址 + LoginLocation string `json:"loginLocation"` + // 浏览器类型 + Browser string `json:"browser"` + // 操作系统 + OS string `json:"os"` + // 登录时间 + LoginTime int64 `json:"loginTime"` +} diff --git a/src/modules/monitor/monitor.go b/src/modules/monitor/monitor.go new file mode 100644 index 0000000..549de18 --- /dev/null +++ b/src/modules/monitor/monitor.go @@ -0,0 +1,160 @@ +package monitor + +import ( + "ems.agt/src/framework/logger" + "ems.agt/src/framework/middleware" + "ems.agt/src/framework/middleware/collectlogs" + "ems.agt/src/framework/middleware/repeat" + "ems.agt/src/modules/monitor/controller" + "ems.agt/src/modules/monitor/processor" + "ems.agt/src/modules/monitor/service" + + "github.com/gin-gonic/gin" +) + +// Setup 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> monitor 模块路由") + + // 启动时需要的初始参数 + InitLoad() + + // 服务器服务信息 + router.GET("/monitor/system-info", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:system:info"}}), + controller.NewSystemInfo.Info, + ) + + // 缓存服务信息 + sysCacheGroup := router.Group("/monitor/cache") + { + sysCacheGroup.GET("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:info"}}), + controller.NewSysCache.Info, + ) + sysCacheGroup.GET("/getNames", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:list"}}), + controller.NewSysCache.Names, + ) + sysCacheGroup.GET("/getKeys/:cacheName", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:list"}}), + controller.NewSysCache.Keys, + ) + sysCacheGroup.GET("/getValue/:cacheName/:cacheKey", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:query"}}), + controller.NewSysCache.Value, + ) + sysCacheGroup.DELETE("/clearCacheName/:cacheName", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}), + controller.NewSysCache.ClearCacheName, + ) + sysCacheGroup.DELETE("/clearCacheKey/:cacheName/:cacheKey", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}), + controller.NewSysCache.ClearCacheKey, + ) + sysCacheGroup.DELETE("/clearCacheSafe", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}), + controller.NewSysCache.ClearCacheSafe, + ) + } + + // 调度任务日志信息 + sysJobLogGroup := router.Group("/monitor/jobLog") + { + sysJobLogGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:list"}}), + controller.NewSysJobLog.List, + ) + sysJobLogGroup.GET("/:jobLogId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:query"}}), + controller.NewSysJobLog.Info, + ) + sysJobLogGroup.DELETE("/:jobLogIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务日志信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysJobLog.Remove, + ) + sysJobLogGroup.DELETE("/clean", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务日志信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysJobLog.Clean, + ) + sysJobLogGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务日志信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysJobLog.Export, + ) + } + + // 调度任务信息 + sysJobGroup := router.Group("/monitor/job") + { + sysJobGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:list"}}), + controller.NewSysJob.List, + ) + sysJobGroup.GET("/:jobId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:query"}}), + controller.NewSysJob.Info, + ) + sysJobGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysJob.Add, + ) + sysJobGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysJob.Edit, + ) + sysJobGroup.DELETE("/:jobIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysJob.Remove, + ) + sysJobGroup.PUT("/changeStatus", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysJob.Status, + ) + sysJobGroup.PUT("/run/:jobId", + repeat.RepeatSubmit(10), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysJob.Run, + ) + sysJobGroup.PUT("/resetQueueJob", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysJob.ResetQueueJob, + ) + sysJobGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("调度任务信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysJob.Export, + ) + } + + // 在线用户监控 + sysUserOnlineGroup := router.Group("/monitor/online") + { + sysUserOnlineGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:online:list"}}), + controller.NewSysUserOnline.List, + ) + sysUserOnlineGroup.DELETE("/:tokenId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:online:forceLogout"}}), + controller.NewSysUserOnline.ForceLogout, + ) + } +} + +// InitLoad 初始参数 +func InitLoad() { + // 初始化定时任务处理 + processor.InitCronQueue() + // 启动时,初始化调度任务 + service.NewSysJobImpl.ResetQueueJob() +} diff --git a/src/modules/monitor/processor/bar/bar.go b/src/modules/monitor/processor/bar/bar.go new file mode 100644 index 0000000..ad2ace1 --- /dev/null +++ b/src/modules/monitor/processor/bar/bar.go @@ -0,0 +1,60 @@ +package bar + +import ( + "time" + + "ems.agt/src/framework/cron" + "ems.agt/src/framework/logger" +) + +var NewProcessor = &BarProcessor{ + progress: 0, + count: 0, +} + +// bar 队列任务处理 +type BarProcessor struct { + // 任务进度 + progress int + // 执行次数 + count int +} + +func (s *BarProcessor) Execute(data any) any { + logger.Infof("执行 %d 次,上次进度: %d ", s.count, s.progress) + s.count++ + + options := data.(cron.JobData) + sysJob := options.SysJob + logger.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID) + + // 实现任务处理逻辑 + i := 0 + s.progress = i + for i < 5 { + // 获取任务进度 + progress := s.progress + logger.Infof("jonId: %s => 任务进度:%d", sysJob.JobID, progress) + // 延迟响应 + time.Sleep(time.Second * 2) + // 程序中途执行错误 + if i == 3 { + // arr := [1]int{1} + // arr[i] = 3 + // fmt.Println(arr) + // return "i = 3" + panic("程序中途执行错误") + } + i++ + // 改变任务进度 + s.progress = i + } + + // 返回结果,用于记录执行结果 + return map[string]any{ + "repeat": options.Repeat, + "jobName": sysJob.JobName, + "invokeTarget": sysJob.InvokeTarget, + "targetParams": sysJob.TargetParams, + } +} diff --git a/src/modules/monitor/processor/foo/foo.go b/src/modules/monitor/processor/foo/foo.go new file mode 100644 index 0000000..ec6d675 --- /dev/null +++ b/src/modules/monitor/processor/foo/foo.go @@ -0,0 +1,52 @@ +package foo + +import ( + "time" + + "ems.agt/src/framework/cron" + "ems.agt/src/framework/logger" +) + +var NewProcessor = &FooProcessor{ + progress: 0, + count: 0, +} + +// foo 队列任务处理 +type FooProcessor struct { + // 任务进度 + progress int + // 执行次数 + count int +} + +func (s *FooProcessor) Execute(data any) any { + logger.Infof("执行 %d 次,上次进度: %d ", s.count, s.progress) + s.count++ + + options := data.(cron.JobData) + sysJob := options.SysJob + logger.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID) + + // 实现任务处理逻辑 + i := 0 + s.progress = i + for i < 20 { + // 获取任务进度 + progress := s.progress + logger.Infof("jonId: %s => 任务进度:%d", sysJob.JobID, progress) + // 延迟响应 + time.Sleep(time.Second * 2) + i++ + // 改变任务进度 + s.progress = i + } + + // 返回结果,用于记录执行结果 + return map[string]any{ + "repeat": options.Repeat, + "jobName": sysJob.JobName, + "invokeTarget": sysJob.InvokeTarget, + "targetParams": sysJob.TargetParams, + } +} diff --git a/src/modules/monitor/processor/processor.go b/src/modules/monitor/processor/processor.go new file mode 100644 index 0000000..74909d6 --- /dev/null +++ b/src/modules/monitor/processor/processor.go @@ -0,0 +1,15 @@ +package processor + +import ( + "ems.agt/src/framework/cron" + "ems.agt/src/modules/monitor/processor/bar" + "ems.agt/src/modules/monitor/processor/foo" + "ems.agt/src/modules/monitor/processor/simple" +) + +// InitCronQueue 初始定时任务队列 +func InitCronQueue() { + cron.CreateQueue("simple", simple.NewProcessor) + cron.CreateQueue("foo", foo.NewProcessor) + cron.CreateQueue("bar", bar.NewProcessor) +} diff --git a/src/modules/monitor/processor/simple/simple.go b/src/modules/monitor/processor/simple/simple.go new file mode 100644 index 0000000..d033937 --- /dev/null +++ b/src/modules/monitor/processor/simple/simple.go @@ -0,0 +1,26 @@ +package simple + +import ( + "ems.agt/src/framework/cron" + "ems.agt/src/framework/logger" +) + +var NewProcessor = &simpleProcessor{} + +// simple 队列任务处理 +type simpleProcessor struct{} + +func (s *simpleProcessor) Execute(data any) any { + options := data.(cron.JobData) + + sysJob := options.SysJob + logger.Infof("重复 %v 任务ID %s", options.Repeat, sysJob.JobID) + + // 返回结果,用于记录执行结果 + return map[string]any{ + "repeat": options.Repeat, + "jobName": sysJob.JobName, + "invokeTarget": sysJob.InvokeTarget, + "targetParams": sysJob.TargetParams, + } +} diff --git a/src/modules/monitor/repository/sys_job.go b/src/modules/monitor/repository/sys_job.go new file mode 100644 index 0000000..7fa93f8 --- /dev/null +++ b/src/modules/monitor/repository/sys_job.go @@ -0,0 +1,29 @@ +package repository + +import ( + "ems.agt/src/modules/monitor/model" +) + +// ISysJob 调度任务表 数据层接口 +type ISysJob interface { + // SelectJobPage 分页查询调度任务集合 + SelectJobPage(query map[string]any) map[string]any + + // SelectJobList 查询调度任务集合 + SelectJobList(sysJob model.SysJob) []model.SysJob + + // SelectJobByIds 通过调度ID查询调度任务信息 + SelectJobByIds(jobIds []string) []model.SysJob + + // CheckUniqueJob 校验调度任务是否唯一 + CheckUniqueJob(sysJob model.SysJob) string + + // InsertJob 新增调度任务信息 + InsertJob(sysJob model.SysJob) string + + // UpdateJob 修改调度任务信息 + UpdateJob(sysJob model.SysJob) int64 + + // DeleteJobByIds 批量删除调度任务信息 + DeleteJobByIds(jobIds []string) int64 +} diff --git a/src/modules/monitor/repository/sys_job.impl.go b/src/modules/monitor/repository/sys_job.impl.go new file mode 100644 index 0000000..12eceb2 --- /dev/null +++ b/src/modules/monitor/repository/sys_job.impl.go @@ -0,0 +1,344 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/monitor/model" +) + +// 实例化数据层 SysJobImpl 结构体 +var NewSysJobImpl = &SysJobImpl{ + selectSql: `select job_id, job_name, job_group, invoke_target, target_params, cron_expression, + misfire_policy, concurrent, status, create_by, create_time, remark from sys_job`, + + resultMap: map[string]string{ + "job_id": "JobID", + "job_name": "JobName", + "job_group": "JobGroup", + "invoke_target": "InvokeTarget", + "target_params": "TargetParams", + "cron_expression": "CronExpression", + "misfire_policy": "MisfirePolicy", + "concurrent": "Concurrent", + "status": "Status", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, +} + +// SysJobImpl 调度任务表 数据层处理 +type SysJobImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysJobImpl) convertResultRows(rows []map[string]any) []model.SysJob { + arr := make([]model.SysJob, 0) + for _, row := range rows { + sysJob := model.SysJob{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysJob, keyMapper, value) + } + } + arr = append(arr, sysJob) + } + return arr +} + +// SelectJobPage 分页查询调度任务集合 +func (r *SysJobImpl) SelectJobPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["jobName"]; ok && v != "" { + conditions = append(conditions, "job_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["jobGroup"]; ok && v != "" { + conditions = append(conditions, "job_group = ?") + params = append(params, v) + } + if v, ok := query["invokeTarget"]; ok && v != "" { + conditions = append(conditions, "invoke_target like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysJob{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_job" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectJobList 查询调度任务集合 +func (r *SysJobImpl) SelectJobList(sysJob model.SysJob) []model.SysJob { + // 查询条件拼接 + var conditions []string + var params []any + if sysJob.JobName != "" { + conditions = append(conditions, "job_name like concat(?, '%')") + params = append(params, sysJob.JobName) + } + if sysJob.JobGroup != "" { + conditions = append(conditions, "job_group = ?") + params = append(params, sysJob.JobGroup) + } + if sysJob.InvokeTarget != "" { + conditions = append(conditions, "invoke_target like concat(?, '%')") + params = append(params, sysJob.InvokeTarget) + } + if sysJob.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysJob.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysJob{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectJobByIds 通过调度ID查询调度任务信息 +func (r *SysJobImpl) SelectJobByIds(jobIds []string) []model.SysJob { + placeholder := repo.KeyPlaceholderByQuery(len(jobIds)) + querySql := r.selectSql + " where job_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(jobIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysJob{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// CheckUniqueJob 校验调度任务是否唯一 +func (r *SysJobImpl) CheckUniqueJob(sysJob model.SysJob) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysJob.JobName != "" { + conditions = append(conditions, "job_name = ?") + params = append(params, sysJob.JobName) + } + if sysJob.JobGroup != "" { + conditions = append(conditions, "job_group = ?") + params = append(params, sysJob.JobGroup) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select job_id as 'str' from sys_job " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// InsertJob 新增调度任务信息 +func (r *SysJobImpl) InsertJob(sysJob model.SysJob) string { + // 参数拼接 + params := make(map[string]any) + if sysJob.JobID != "" { + params["job_id"] = sysJob.JobID + } + if sysJob.JobName != "" { + params["job_name"] = sysJob.JobName + } + if sysJob.JobGroup != "" { + params["job_group"] = sysJob.JobGroup + } + if sysJob.InvokeTarget != "" { + params["invoke_target"] = sysJob.InvokeTarget + } + if sysJob.TargetParams != "" { + params["target_params"] = sysJob.TargetParams + } + if sysJob.CronExpression != "" { + params["cron_expression"] = sysJob.CronExpression + } + if sysJob.MisfirePolicy != "" { + params["misfire_policy"] = sysJob.MisfirePolicy + } + if sysJob.Concurrent != "" { + params["concurrent"] = sysJob.Concurrent + } + if sysJob.Status != "" { + params["status"] = sysJob.Status + } + if sysJob.Remark != "" { + params["remark"] = sysJob.Remark + } + if sysJob.CreateBy != "" { + params["create_by"] = sysJob.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_job (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateJob 修改调度任务信息 +func (r *SysJobImpl) UpdateJob(sysJob model.SysJob) int64 { + // 参数拼接 + params := make(map[string]any) + if sysJob.JobName != "" { + params["job_name"] = sysJob.JobName + } + if sysJob.JobGroup != "" { + params["job_group"] = sysJob.JobGroup + } + if sysJob.InvokeTarget != "" { + params["invoke_target"] = sysJob.InvokeTarget + } + if sysJob.TargetParams != "" { + params["target_params"] = sysJob.TargetParams + } + if sysJob.CronExpression != "" { + params["cron_expression"] = sysJob.CronExpression + } + if sysJob.MisfirePolicy != "" { + params["misfire_policy"] = sysJob.MisfirePolicy + } + if sysJob.Concurrent != "" { + params["concurrent"] = sysJob.Concurrent + } + if sysJob.Status != "" { + params["status"] = sysJob.Status + } + if sysJob.Remark != "" { + params["remark"] = sysJob.Remark + } + if sysJob.UpdateBy != "" { + params["update_by"] = sysJob.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_job set " + strings.Join(keys, ",") + " where job_id = ?" + + // 执行更新 + values = append(values, sysJob.JobID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteJobByIds 批量删除调度任务信息 +func (r *SysJobImpl) DeleteJobByIds(jobIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(jobIds)) + sql := "delete from sys_job where job_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(jobIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/monitor/repository/sys_job_log.go b/src/modules/monitor/repository/sys_job_log.go new file mode 100644 index 0000000..6579e47 --- /dev/null +++ b/src/modules/monitor/repository/sys_job_log.go @@ -0,0 +1,26 @@ +package repository + +import ( + "ems.agt/src/modules/monitor/model" +) + +// 调度任务日志表 数据层接口 +type ISysJobLog interface { + // 分页查询调度任务日志集合 + SelectJobLogPage(query map[string]any) map[string]any + + // 查询调度任务日志集合 + SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog + + // 通过调度ID查询调度任务日志信息 + SelectJobLogById(jobLogId string) model.SysJobLog + + // 新增调度任务日志信息 + InsertJobLog(sysJobLog model.SysJobLog) string + + // 批量删除调度任务日志信息 + DeleteJobLogByIds(jobLogId []string) int64 + + // 清空调度任务日志 + CleanJobLog() error +} diff --git a/src/modules/monitor/repository/sys_job_log.impl.go b/src/modules/monitor/repository/sys_job_log.impl.go new file mode 100644 index 0000000..cf506c4 --- /dev/null +++ b/src/modules/monitor/repository/sys_job_log.impl.go @@ -0,0 +1,272 @@ +package repository + +import ( + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/monitor/model" +) + +// 实例化数据层 SysJobLogImpl 结构体 +var NewSysJobLogImpl = &SysJobLogImpl{ + selectSql: `select job_log_id, job_name, job_group, invoke_target, + target_params, job_msg, status, create_time, cost_time from sys_job_log`, + + resultMap: map[string]string{ + "job_log_id": "JobLogID", + "job_name": "JobName", + "job_group": "JobGroup", + "invoke_target": "InvokeTarget", + "target_params": "TargetParams", + "job_msg": "JobMsg", + "status": "Status", + "create_time": "CreateTime", + "cost_time": "CostTime", + }, +} + +// SysJobLogImpl 调度任务日志表 数据层处理 +type SysJobLogImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysJobLogImpl) convertResultRows(rows []map[string]any) []model.SysJobLog { + arr := make([]model.SysJobLog, 0) + for _, row := range rows { + sysJobLog := model.SysJobLog{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysJobLog, keyMapper, value) + } + } + arr = append(arr, sysJobLog) + } + return arr +} + +// 分页查询调度任务日志集合 +func (r *SysJobLogImpl) SelectJobLogPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["jobName"]; ok && v != "" { + conditions = append(conditions, "job_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["jobGroup"]; ok && v != "" { + conditions = append(conditions, "job_group = ?") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + if v, ok := query["invokeTarget"]; ok && v != "" { + conditions = append(conditions, "invoke_target like concat(?, '%')") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "create_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "create_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysJobLog{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_job_log" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by job_log_id desc limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// 查询调度任务日志集合 +func (r *SysJobLogImpl) SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog { + // 查询条件拼接 + var conditions []string + var params []any + if sysJobLog.JobName != "" { + conditions = append(conditions, "job_name like concat(?, '%')") + params = append(params, sysJobLog.JobName) + } + if sysJobLog.JobGroup != "" { + conditions = append(conditions, "job_group = ?") + params = append(params, sysJobLog.JobGroup) + } + if sysJobLog.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysJobLog.Status) + } + if sysJobLog.InvokeTarget != "" { + conditions = append(conditions, "invoke_target like concat(?, '%')") + params = append(params, sysJobLog.InvokeTarget) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysJobLog{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// 通过调度ID查询调度任务日志信息 +func (r *SysJobLogImpl) SelectJobLogById(jobLogId string) model.SysJobLog { + querySql := r.selectSql + " where job_log_id = ?" + results, err := datasource.RawDB("", querySql, []any{jobLogId}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysJobLog{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysJobLog{} +} + +// 新增调度任务日志信息 +func (r *SysJobLogImpl) InsertJobLog(sysJobLog model.SysJobLog) string { + // 参数拼接 + params := make(map[string]any) + params["create_time"] = time.Now().UnixMilli() + if sysJobLog.JobLogID != "" { + params["job_log_id"] = sysJobLog.JobLogID + } + if sysJobLog.JobName != "" { + params["job_name"] = sysJobLog.JobName + } + if sysJobLog.JobGroup != "" { + params["job_group"] = sysJobLog.JobGroup + } + if sysJobLog.InvokeTarget != "" { + params["invoke_target"] = sysJobLog.InvokeTarget + } + if sysJobLog.TargetParams != "" { + params["target_params"] = sysJobLog.TargetParams + } + if sysJobLog.JobMsg != "" { + params["job_msg"] = sysJobLog.JobMsg + } + if sysJobLog.Status != "" { + params["status"] = sysJobLog.Status + } + if sysJobLog.CostTime > 0 { + params["cost_time"] = sysJobLog.CostTime + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_job_log (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// 批量删除调度任务日志信息 +func (r *SysJobLogImpl) DeleteJobLogByIds(jobLogIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(jobLogIds)) + sql := "delete from sys_job_log where job_log_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(jobLogIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// 清空调度任务日志 +func (r *SysJobLogImpl) CleanJobLog() error { + sql := "truncate table sys_job_log" + _, err := datasource.ExecDB("", sql, []any{}) + return err +} diff --git a/src/modules/monitor/service/sys_job.go b/src/modules/monitor/service/sys_job.go new file mode 100644 index 0000000..9e9a5ca --- /dev/null +++ b/src/modules/monitor/service/sys_job.go @@ -0,0 +1,38 @@ +package service + +import ( + "ems.agt/src/modules/monitor/model" +) + +// ISysJob 调度任务信息 服务层接口 +type ISysJob interface { + // SelectJobPage 分页查询调度任务集合 + SelectJobPage(query map[string]any) map[string]any + + // SelectJobList 查询调度任务集合 + SelectJobList(sysJob model.SysJob) []model.SysJob + + // SelectJobById 通过调度ID查询调度任务信息 + SelectJobById(jobId string) model.SysJob + + // CheckUniqueJobName 校验调度任务名称和组是否唯一 + CheckUniqueJobName(jobName, jobGroup, jobId string) bool + + // InsertJob 新增调度任务信息 + InsertJob(sysJob model.SysJob) string + + // UpdateJob 修改调度任务信息 + UpdateJob(sysJob model.SysJob) int64 + + // DeleteJobByIds 批量删除调度任务信息 + DeleteJobByIds(jobIds []string) (int64, error) + + // ChangeStatus 任务调度状态修改 + ChangeStatus(sysJob model.SysJob) bool + + // RunQueueJob 立即运行一次调度任务 + RunQueueJob(sysJob model.SysJob) bool + + // ResetQueueJob 重置初始调度任务 + ResetQueueJob() +} diff --git a/src/modules/monitor/service/sys_job.impl.go b/src/modules/monitor/service/sys_job.impl.go new file mode 100644 index 0000000..a6fd24e --- /dev/null +++ b/src/modules/monitor/service/sys_job.impl.go @@ -0,0 +1,190 @@ +package service + +import ( + "errors" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/cron" + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/repository" +) + +// 实例化服务层 SysJobImpl 结构体 +var NewSysJobImpl = &SysJobImpl{ + sysJobRepository: repository.NewSysJobImpl, +} + +// SysJobImpl 调度任务 服务层处理 +type SysJobImpl struct { + // 调度任务数据信息 + sysJobRepository repository.ISysJob +} + +// SelectJobPage 分页查询调度任务集合 +func (r *SysJobImpl) SelectJobPage(query map[string]any) map[string]any { + return r.sysJobRepository.SelectJobPage(query) +} + +// SelectJobList 查询调度任务集合 +func (r *SysJobImpl) SelectJobList(sysJob model.SysJob) []model.SysJob { + return r.sysJobRepository.SelectJobList(sysJob) +} + +// SelectJobById 通过调度ID查询调度任务信息 +func (r *SysJobImpl) SelectJobById(jobId string) model.SysJob { + if jobId == "" { + return model.SysJob{} + } + jobs := r.sysJobRepository.SelectJobByIds([]string{jobId}) + if len(jobs) > 0 { + return jobs[0] + } + return model.SysJob{} +} + +// CheckUniqueJobName 校验调度任务名称和组是否唯一 +func (r *SysJobImpl) CheckUniqueJobName(jobName, jobGroup, jobId string) bool { + uniqueId := r.sysJobRepository.CheckUniqueJob(model.SysJob{ + JobName: jobName, + JobGroup: jobGroup, + }) + if uniqueId == jobId { + return true + } + return uniqueId == "" +} + +// InsertJob 新增调度任务信息 +func (r *SysJobImpl) InsertJob(sysJob model.SysJob) string { + insertId := r.sysJobRepository.InsertJob(sysJob) + if insertId == "" && sysJob.Status == common.STATUS_YES { + sysJob.JobID = insertId + r.insertQueueJob(sysJob, true) + } + return insertId +} + +// UpdateJob 修改调度任务信息 +func (r *SysJobImpl) UpdateJob(sysJob model.SysJob) int64 { + rows := r.sysJobRepository.UpdateJob(sysJob) + if rows > 0 { + //状态正常添加队列任务 + if sysJob.Status == common.STATUS_YES { + r.insertQueueJob(sysJob, true) + } + // 状态禁用删除队列任务 + if sysJob.Status == common.STATUS_NO { + r.deleteQueueJob(sysJob) + } + } + return rows +} + +// DeleteJobByIds 批量删除调度任务信息 +func (r *SysJobImpl) DeleteJobByIds(jobIds []string) (int64, error) { + // 检查是否存在 + jobs := r.sysJobRepository.SelectJobByIds(jobIds) + if len(jobs) <= 0 { + return 0, errors.New("没有权限访问调度任务数据!") + } + if len(jobs) == len(jobIds) { + // 清除任务 + for _, job := range jobs { + r.deleteQueueJob(job) + } + rows := r.sysJobRepository.DeleteJobByIds(jobIds) + return rows, nil + } + return 0, errors.New("删除调度任务信息失败!") +} + +// ChangeStatus 任务调度状态修改 +func (r *SysJobImpl) ChangeStatus(sysJob model.SysJob) bool { + // 更新状态 + newSysJob := model.SysJob{ + JobID: sysJob.JobID, + Status: sysJob.Status, + UpdateBy: sysJob.UpdateBy, + } + rows := r.sysJobRepository.UpdateJob(newSysJob) + if rows > 0 { + //状态正常添加队列任务 + if sysJob.Status == common.STATUS_YES { + r.insertQueueJob(sysJob, true) + } + // 状态禁用删除队列任务 + if sysJob.Status == common.STATUS_NO { + r.deleteQueueJob(sysJob) + } + return true + } + return false +} + +// ResetQueueJob 重置初始调度任务 +func (r *SysJobImpl) ResetQueueJob() { + // 获取注册的队列名称 + queueNames := cron.QueueNames() + if len(queueNames) == 0 { + return + } + // 查询系统中定义状态为正常启用的任务 + sysJobs := r.sysJobRepository.SelectJobList(model.SysJob{ + Status: common.STATUS_YES, + }) + for _, sysJob := range sysJobs { + for _, name := range queueNames { + if name == sysJob.InvokeTarget { + r.insertQueueJob(sysJob, true) + } + } + } +} + +// RunQueueJob 立即运行一次调度任务 +func (r *SysJobImpl) RunQueueJob(sysJob model.SysJob) bool { + return r.insertQueueJob(sysJob, false) +} + +// insertQueueJob 添加调度任务 +func (r *SysJobImpl) insertQueueJob(sysJob model.SysJob, repeat bool) bool { + // 获取队列 Processor + queue := cron.GetQueue(sysJob.InvokeTarget) + if queue.Name != sysJob.InvokeTarget { + return false + } + + // 给执行任务数据参数 + options := cron.JobData{ + Repeat: repeat, + SysJob: sysJob, + } + + // 不是重复任务的情况,立即执行一次 + if !repeat { + // 执行单次任务 + status := queue.RunJob(options, cron.JobOptions{ + JobId: sysJob.JobID, + }) + // 执行中或等待中的都返回正常 + return status == cron.Active || status == cron.Waiting + } + + // 执行重复任务 + queue.RunJob(options, cron.JobOptions{ + JobId: sysJob.JobID, + Cron: sysJob.CronExpression, + }) + + return true +} + +// deleteQueueJob 删除调度任务 +func (r *SysJobImpl) deleteQueueJob(sysJob model.SysJob) bool { + // 获取队列 Processor + queue := cron.GetQueue(sysJob.InvokeTarget) + if queue.Name != sysJob.InvokeTarget { + return false + } + return queue.RemoveJob(sysJob.JobID) +} diff --git a/src/modules/monitor/service/sys_job_log.go b/src/modules/monitor/service/sys_job_log.go new file mode 100644 index 0000000..ea4c1e8 --- /dev/null +++ b/src/modules/monitor/service/sys_job_log.go @@ -0,0 +1,23 @@ +package service + +import ( + "ems.agt/src/modules/monitor/model" +) + +// ISysJobLog 调度任务日志 服务层接口 +type ISysJobLog interface { + // SelectJobLogPage 分页查询调度任务日志集合 + SelectJobLogPage(query map[string]any) map[string]any + + // SelectJobLogList 查询调度任务日志集合 + SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog + + // SelectJobLogById 通过调度ID查询调度任务日志信息 + SelectJobLogById(jobLogId string) model.SysJobLog + + // DeleteJobLogByIds 批量删除调度任务日志信息 + DeleteJobLogByIds(jobLogIds []string) int64 + + // CleanJobLog 清空调度任务日志 + CleanJobLog() error +} diff --git a/src/modules/monitor/service/sys_job_log.impl.go b/src/modules/monitor/service/sys_job_log.impl.go new file mode 100644 index 0000000..369845b --- /dev/null +++ b/src/modules/monitor/service/sys_job_log.impl.go @@ -0,0 +1,42 @@ +package service + +import ( + "ems.agt/src/modules/monitor/model" + "ems.agt/src/modules/monitor/repository" +) + +// 实例化服务层 SysJobLogImpl 结构体 +var NewSysJobLogImpl = &SysJobLogImpl{ + sysJobLogRepository: repository.NewSysJobLogImpl, +} + +// SysJobLogImpl 调度任务日志 服务层处理 +type SysJobLogImpl struct { + // 调度任务日志数据信息 + sysJobLogRepository repository.ISysJobLog +} + +// SelectJobLogPage 分页查询调度任务日志集合 +func (s *SysJobLogImpl) SelectJobLogPage(query map[string]any) map[string]any { + return s.sysJobLogRepository.SelectJobLogPage(query) +} + +// SelectJobLogList 查询调度任务日志集合 +func (s *SysJobLogImpl) SelectJobLogList(sysJobLog model.SysJobLog) []model.SysJobLog { + return s.sysJobLogRepository.SelectJobLogList(sysJobLog) +} + +// SelectJobLogById 通过调度ID查询调度任务日志信息 +func (s *SysJobLogImpl) SelectJobLogById(jobLogId string) model.SysJobLog { + return s.sysJobLogRepository.SelectJobLogById(jobLogId) +} + +// DeleteJobLogByIds 批量删除调度任务日志信息 +func (s *SysJobLogImpl) DeleteJobLogByIds(jobLogIds []string) int64 { + return s.sysJobLogRepository.DeleteJobLogByIds(jobLogIds) +} + +// CleanJobLog 清空调度任务日志 +func (s *SysJobLogImpl) CleanJobLog() error { + return s.sysJobLogRepository.CleanJobLog() +} diff --git a/src/modules/monitor/service/sys_user_online.go b/src/modules/monitor/service/sys_user_online.go new file mode 100644 index 0000000..b74f348 --- /dev/null +++ b/src/modules/monitor/service/sys_user_online.go @@ -0,0 +1,12 @@ +package service + +import ( + "ems.agt/src/framework/vo" + "ems.agt/src/modules/monitor/model" +) + +// ISysUserOnline 在线用户 服务层接口 +type ISysUserOnline interface { + // LoginUserToUserOnline 设置在线用户信息 + LoginUserToUserOnline(loginUser vo.LoginUser) model.SysUserOnline +} diff --git a/src/modules/monitor/service/sys_user_online.impl.go b/src/modules/monitor/service/sys_user_online.impl.go new file mode 100644 index 0000000..dbfe427 --- /dev/null +++ b/src/modules/monitor/service/sys_user_online.impl.go @@ -0,0 +1,33 @@ +package service + +import ( + "ems.agt/src/framework/vo" + "ems.agt/src/modules/monitor/model" +) + +// 实例化服务层 SysUserOnlineImpl 结构体 +var NewSysUserOnlineImpl = &SysUserOnlineImpl{} + +// SysUserOnlineImpl 在线用户 服务层处理 +type SysUserOnlineImpl struct{} + +// LoginUserToUserOnline 设置在线用户信息 +func (r *SysUserOnlineImpl) LoginUserToUserOnline(loginUser vo.LoginUser) model.SysUserOnline { + if loginUser.UserID == "" { + return model.SysUserOnline{} + } + + sysUserOnline := model.SysUserOnline{ + TokenID: loginUser.UUID, + UserName: loginUser.User.UserName, + IPAddr: loginUser.IPAddr, + LoginLocation: loginUser.LoginLocation, + Browser: loginUser.Browser, + OS: loginUser.OS, + LoginTime: loginUser.LoginTime, + } + if loginUser.User.DeptID != "" { + sysUserOnline.DeptName = loginUser.User.Dept.DeptName + } + return sysUserOnline +} diff --git a/src/modules/monitor/service/system_info.go b/src/modules/monitor/service/system_info.go new file mode 100644 index 0000000..e2be65f --- /dev/null +++ b/src/modules/monitor/service/system_info.go @@ -0,0 +1,25 @@ +package service + +// ISystemInfo 服务器系统相关信息 服务层接口 +type ISystemInfo interface { + // ProjectInfo 程序项目信息 + ProjectInfo() map[string]any + + // SystemInfo 系统信息 + SystemInfo() map[string]any + + // TimeInfo 系统时间信息 + TimeInfo() map[string]string + + // MemoryInfo 内存信息 + MemoryInfo() map[string]any + + // CPUInfo CPU信息 + CPUInfo() map[string]any + + // NetworkInfo 网络信息 + NetworkInfo() map[string]string + + // DiskInfo 磁盘信息 + DiskInfo() []map[string]string +} diff --git a/src/modules/monitor/service/system_info.impl.go b/src/modules/monitor/service/system_info.impl.go new file mode 100644 index 0000000..c6f3f37 --- /dev/null +++ b/src/modules/monitor/service/system_info.impl.go @@ -0,0 +1,236 @@ +package service + +import ( + "bufio" + "fmt" + "os" + "runtime" + "strings" + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/utils/parse" + + "github.com/shirou/gopsutil/v3/cpu" + "github.com/shirou/gopsutil/v3/disk" + "github.com/shirou/gopsutil/v3/host" + "github.com/shirou/gopsutil/v3/mem" + "github.com/shirou/gopsutil/v3/net" +) + +// 实例化服务层 SystemInfoImpl 结构体 +var NewSystemInfoImpl = &SystemInfoImpl{} + +// SystemInfoImpl 服务器系统相关信息 服务层处理 +type SystemInfoImpl struct{} + +// ProjectInfo 程序项目信息 +func (s *SystemInfoImpl) ProjectInfo() map[string]any { + // 获取工作目录 + appDir, err := os.Getwd() + if err != nil { + appDir = "" + } + // 项目依赖 + dependencies := s.dependencies() + return map[string]any{ + "appDir": appDir, + "env": config.Env(), + "name": config.Get("framework.name"), + "version": config.Get("framework.version"), + "dependencies": dependencies, + } +} + +// dependencies 读取mod内项目包依赖 +func (s *SystemInfoImpl) dependencies() map[string]string { + var pkgs = make(map[string]string) + + // 打开 go.mod 文件 + file, err := os.Open("go.mod") + if err != nil { + return pkgs + } + defer file.Close() + + // 使用 bufio.Scanner 逐行读取文件内容 + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + line = strings.TrimSpace(line) + + // 行不为空,不以module\require开头,不带有 // indirect 注释,则解析包名和版本 + prefixLine := strings.HasPrefix(line, "module") || strings.HasPrefix(line, "require") || strings.HasPrefix(line, "go ") + suffixLine := strings.HasSuffix(line, ")") || strings.HasSuffix(line, "// indirect") + if line == "" || prefixLine || suffixLine { + continue + } + + modInfo := strings.Split(line, " ") + if len(modInfo) >= 2 { + moduleName := strings.TrimSpace(modInfo[0]) + version := strings.TrimSpace(modInfo[1]) + pkgs[moduleName] = version + } + } + + if err := scanner.Err(); err != nil { + pkgs["scanner-err"] = err.Error() + } + return pkgs +} + +// SystemInfo 系统信息 +func (s *SystemInfoImpl) SystemInfo() map[string]any { + info, err := host.Info() + if err != nil { + info.Platform = err.Error() + } + // 用户目录 + homeDir, err := os.UserHomeDir() + if err != nil { + homeDir = "" + } + cmd, err := os.Executable() + if err != nil { + cmd = "" + } + return map[string]any{ + "platform": info.Platform, + "go": runtime.Version(), + "processId": os.Getpid(), + "arch": info.KernelArch, + "uname": runtime.GOARCH, + "release": info.OS, + "hostname": info.Hostname, + "homeDir": homeDir, + "cmd": cmd, + "execCommand": strings.Join(os.Args, " "), + } +} + +// TimeInfo 系统时间信息 +func (s *SystemInfoImpl) TimeInfo() map[string]string { + // 获取当前时间 + current := time.Now().Format("2006-01-02 15:04:05") + // 获取程序运行时间 + uptime := time.Since(config.RunTime()).String() + // 获取时区 + timezone := time.Now().Format("-0700 MST") + // 获取时区名称 + timezoneName := time.Now().Format("MST") + + return map[string]string{ + "current": current, + "uptime": uptime, + "timezone": timezone, + "timezoneName": timezoneName, + } +} + +// MemoryInfo 内存信息 +func (s *SystemInfoImpl) MemoryInfo() map[string]any { + memInfo, err := mem.VirtualMemory() + if err != nil { + memInfo.UsedPercent = 0 + memInfo.Available = 0 + memInfo.Total = 0 + } + + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + + return map[string]any{ + "usage": fmt.Sprintf("%.2f", memInfo.UsedPercent), // 内存利用率 + "freemem": parse.Bit(float64(memInfo.Available)), // 可用内存大小(GB) + "totalmem": parse.Bit(float64(memInfo.Total)), // 总内存大小(GB) + "rss": parse.Bit(float64(memStats.Sys)), // 常驻内存大小(RSS) + "heapTotal": parse.Bit(float64(memStats.HeapSys)), // 堆总大小 + "heapUsed": parse.Bit(float64(memStats.HeapAlloc)), // 堆已使用大小 + "external": parse.Bit(float64(memStats.Sys - memStats.HeapSys)), // 外部内存大小(非堆) + } +} + +// CPUInfo CPU信息 +func (s *SystemInfoImpl) CPUInfo() map[string]any { + var core int32 = 0 + var speed string = "未知" + var model string = "未知" + cpuInfo, err := cpu.Info() + if err == nil { + core = cpuInfo[0].Cores + speed = fmt.Sprintf("%.0fMHz", cpuInfo[0].Mhz) + model = strings.TrimSpace(cpuInfo[0].ModelName) + } + + useds := []string{} + cpuPercent, err := cpu.Percent(0, true) + if err == nil { + for _, v := range cpuPercent { + useds = append(useds, fmt.Sprintf("%.2f", v)) + } + } + + return map[string]any{ + "model": model, + "speed": speed, + "core": core, + "coreUsed": useds, + } +} + +// NetworkInfo 网络信息 +func (s *SystemInfoImpl) NetworkInfo() map[string]string { + ipAddrs := make(map[string]string) + interfaces, err := net.Interfaces() + if err == nil { + for _, iface := range interfaces { + name := iface.Name + if name[len(name)-1] == '0' { + name = name[0 : len(name)-1] + name = strings.Trim(name, "") + } + // ignore localhost + if name == "lo" { + continue + } + var addrs []string + for _, v := range iface.Addrs { + prefix := strings.Split(v.Addr, "/")[0] + if strings.Contains(prefix, "::") { + addrs = append(addrs, fmt.Sprintf("IPv6 %s", prefix)) + } + if strings.Contains(prefix, ".") { + addrs = append(addrs, fmt.Sprintf("IPv4 %s", prefix)) + } + } + ipAddrs[name] = strings.Join(addrs, " / ") + } + } + return ipAddrs +} + +// DiskInfo 磁盘信息 +func (s *SystemInfoImpl) DiskInfo() []map[string]string { + disks := make([]map[string]string, 0) + + partitions, err := disk.Partitions(false) + if err != nil { + return disks + } + + for _, partition := range partitions { + usage, err := disk.Usage(partition.Mountpoint) + if err != nil { + continue + } + disks = append(disks, map[string]string{ + "size": parse.Bit(float64(usage.Total)), + "used": parse.Bit(float64(usage.Used)), + "avail": parse.Bit(float64(usage.Free)), + "pcent": fmt.Sprintf("%.1f%%", usage.UsedPercent), + "target": partition.Device, + }) + } + return disks +} diff --git a/src/modules/system/controller/sys_config.go b/src/modules/system/controller/sys_config.go new file mode 100644 index 0000000..7350365 --- /dev/null +++ b/src/modules/system/controller/sys_config.go @@ -0,0 +1,220 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysConfigController 结构体 +var NewSysConfig = &SysConfigController{ + sysConfigService: service.NewSysConfigImpl, +} + +// 参数配置信息 +// +// PATH /system/config +type SysConfigController struct { + // 参数配置服务 + sysConfigService service.ISysConfig +} + +// 参数配置列表 +// +// GET /list +func (s *SysConfigController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysConfigService.SelectConfigPage(querys) + c.JSON(200, result.Ok(data)) +} + +// 参数配置信息 +// +// GET /:configId +func (s *SysConfigController) Info(c *gin.Context) { + configId := c.Param("configId") + if configId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysConfigService.SelectConfigById(configId) + if data.ConfigID == configId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 参数配置新增 +// +// POST / +func (s *SysConfigController) Add(c *gin.Context) { + var body model.SysConfig + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.ConfigID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查属性值唯一 + uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, "") + if !uniqueConfigKey { + msg := fmt.Sprintf("参数配置新增【%s】失败,参数键名已存在", body.ConfigKey) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysConfigService.InsertConfig(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 参数配置修改 +// +// PUT / +func (s *SysConfigController) Edit(c *gin.Context) { + var body model.SysConfig + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.ConfigID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查属性值唯一 + uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, body.ConfigID) + if !uniqueConfigKey { + msg := fmt.Sprintf("参数配置修改【%s】失败,参数键名已存在", body.ConfigKey) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查是否存在 + config := s.sysConfigService.SelectConfigById(body.ConfigID) + if config.ConfigID != body.ConfigID { + c.JSON(200, result.ErrMsg("没有权限访问参数配置数据!")) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysConfigService.UpdateConfig(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 参数配置删除 +// +// DELETE /:configIds +func (s *SysConfigController) Remove(c *gin.Context) { + configIds := c.Param("configIds") + if configIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(configIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysConfigService.DeleteConfigByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 参数配置刷新缓存 +// +// PUT /refreshCache +func (s *SysConfigController) RefreshCache(c *gin.Context) { + s.sysConfigService.ResetConfigCache() + c.JSON(200, result.Ok(nil)) +} + +// 参数配置根据参数键名 +// +// GET /configKey/:configKey +func (s *SysConfigController) ConfigKey(c *gin.Context) { + configKey := c.Param("configKey") + if configKey == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + key := s.sysConfigService.SelectConfigValueByKey(configKey) + if key != "" { + c.JSON(200, result.OkData(key)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 导出参数配置信息 +// +// POST /export +func (s *SysConfigController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysConfigService.SelectConfigPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysConfig) + + // 导出文件名称 + fileName := fmt.Sprintf("config_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "参数编号", + "B1": "参数名称", + "C1": "参数键名", + "D1": "参数键值", + "E1": "系统内置", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + typeValue := "否" + if row.ConfigType == "Y" { + typeValue = "是" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.ConfigID, + "B" + idx: row.ConfigName, + "C" + idx: row.ConfigKey, + "D" + idx: row.ConfigValue, + "E" + idx: typeValue, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_dept.go b/src/modules/system/controller/sys_dept.go new file mode 100644 index 0000000..046cce9 --- /dev/null +++ b/src/modules/system/controller/sys_dept.go @@ -0,0 +1,309 @@ +package controller + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysDeptController 结构体 +var NewSysDept = &SysDeptController{ + sysDeptService: service.NewSysDeptImpl, +} + +// 部门信息 +// +// PATH /system/dept +type SysDeptController struct { + // 部门服务 + sysDeptService service.ISysDept +} + +// 部门列表 +// +// GET /list +func (s *SysDeptController) List(c *gin.Context) { + var querys struct { + // 部门ID + DeptID string `json:"deptId"` + // 父部门ID + ParentID string `json:"parentId" ` + // 部门名称 + DeptName string `json:"deptName" ` + // 部门状态(0正常 1停用) + Status string `json:"status"` + } + err := c.ShouldBindQuery(&querys) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + SysDeptController := model.SysDept{ + DeptID: querys.DeptID, + ParentID: querys.ParentID, + DeptName: querys.DeptName, + Status: querys.Status, + } + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysDeptService.SelectDeptList(SysDeptController, dataScopeSQL) + c.JSON(200, result.OkData(data)) +} + +// 部门信息 +// +// GET /:deptId +func (s *SysDeptController) Info(c *gin.Context) { + deptId := c.Param("deptId") + if deptId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysDeptService.SelectDeptById(deptId) + if data.DeptID == deptId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 部门新增 +// +// POST / +func (s *SysDeptController) Add(c *gin.Context) { + var body model.SysDept + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DeptID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 父级ID不为0是要检查 + if body.ParentID != "0" { + deptParent := s.sysDeptService.SelectDeptById(body.ParentID) + if deptParent.DeptID != body.ParentID { + c.JSON(200, result.ErrMsg("没有权限访问部门数据!")) + return + } + if deptParent.Status == common.STATUS_NO { + msg := fmt.Sprintf("上级部门【%s】停用,不允许新增", deptParent.DeptName) + c.JSON(200, result.ErrMsg(msg)) + return + } + if deptParent.DelFlag == common.STATUS_YES { + msg := fmt.Sprintf("上级部门【%s】已删除,不允许新增", deptParent.DeptName) + c.JSON(200, result.ErrMsg(msg)) + return + } + body.Ancestors = deptParent.Ancestors + "," + body.ParentID + } else { + body.Ancestors = "0" + } + + // 检查同级下名称唯一 + uniqueDeptName := s.sysDeptService.CheckUniqueDeptName(body.DeptName, body.ParentID, "") + if !uniqueDeptName { + msg := fmt.Sprintf("部门新增【%s】失败,部门名称已存在", body.DeptName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysDeptService.InsertDept(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 部门修改 +// +// PUT / +func (s *SysDeptController) Edit(c *gin.Context) { + var body model.SysDept + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DeptID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 上级部门不能选自己 + if body.DeptID == body.ParentID { + msg := fmt.Sprintf("部门修改【%s】失败,上级部门不能是自己", body.DeptName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查数据是否存在 + deptInfo := s.sysDeptService.SelectDeptById(body.DeptID) + if deptInfo.DeptID != body.DeptID { + c.JSON(200, result.ErrMsg("没有权限访问部门数据!")) + return + } + // 父级ID不为0是要检查 + if body.ParentID != "0" { + deptParent := s.sysDeptService.SelectDeptById(body.ParentID) + if deptParent.DeptID != body.ParentID { + c.JSON(200, result.ErrMsg("没有权限访问部门数据!")) + return + } + } + + // 检查同级下名称唯一 + uniqueDeptName := s.sysDeptService.CheckUniqueDeptName(body.DeptName, body.ParentID, body.DeptID) + if !uniqueDeptName { + msg := fmt.Sprintf("部门修改【%s】失败,部门名称已存在", body.DeptName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 上级停用需要检查下级是否有在使用 + if body.Status == common.STATUS_NO { + hasChild := s.sysDeptService.HasChildByDeptId(body.DeptID) + if hasChild > 0 { + msg := fmt.Sprintf("该部门包含未停用的子部门数量:%d", hasChild) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysDeptService.UpdateDept(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 部门删除 +// +// DELETE /:deptId +func (s *SysDeptController) Remove(c *gin.Context) { + deptId := c.Param("deptId") + if deptId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查数据是否存在 + dept := s.sysDeptService.SelectDeptById(deptId) + if dept.DeptID != deptId { + c.JSON(200, result.ErrMsg("没有权限访问部门数据!")) + return + } + + // 检查是否存在子部门 + hasChild := s.sysDeptService.HasChildByDeptId(deptId) + if hasChild > 0 { + msg := fmt.Sprintf("不允许删除,存在子部门数:%d", hasChild) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查是否分配给用户 + existUser := s.sysDeptService.CheckDeptExistUser(deptId) + if existUser > 0 { + msg := fmt.Sprintf("不允许删除,部门已分配给用户数:%d", existUser) + c.JSON(200, result.ErrMsg(msg)) + return + } + + rows := s.sysDeptService.DeleteDeptById(deptId) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 部门列表(排除节点) +// +// GET /list/exclude/:deptId +func (s *SysDeptController) ExcludeChild(c *gin.Context) { + deptId := c.Param("deptId") + if deptId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysDeptService.SelectDeptList(model.SysDept{}, dataScopeSQL) + + // 过滤排除节点 + filtered := make([]model.SysDept, 0) + for _, dept := range data { + hasAncestor := false + ancestorList := strings.Split(dept.Ancestors, ",") + for _, ancestor := range ancestorList { + if ancestor == deptId { + hasAncestor = true + break + } + } + if !(dept.DeptID == deptId || hasAncestor) { + filtered = append(filtered, dept) + } + } + c.JSON(200, result.OkData(filtered)) +} + +// 部门树结构列表 +// +// GET /treeSelect +func (s *SysDeptController) TreeSelect(c *gin.Context) { + var querys struct { + // 部门ID + DeptID string `json:"deptId"` + // 父部门ID + ParentID string `json:"parentId" ` + // 部门名称 + DeptName string `json:"deptName" ` + // 部门状态(0正常 1停用) + Status string `json:"status"` + } + err := c.ShouldBindQuery(&querys) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + SysDeptController := model.SysDept{ + DeptID: querys.DeptID, + ParentID: querys.ParentID, + DeptName: querys.DeptName, + Status: querys.Status, + } + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysDeptService.SelectDeptTreeSelect(SysDeptController, dataScopeSQL) + c.JSON(200, result.OkData(data)) +} + +// 部门树结构列表(指定角色) +// +// GET /roleDeptTreeSelect/:roleId +func (s *SysDeptController) RoleDeptTreeSelect(c *gin.Context) { + roleId := c.Param("roleId") + if roleId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + deptTreeSelect := s.sysDeptService.SelectDeptTreeSelect(model.SysDept{}, dataScopeSQL) + checkedKeys := s.sysDeptService.SelectDeptListByRoleId(roleId) + c.JSON(200, result.OkData(map[string]any{ + "depts": deptTreeSelect, + "checkedKeys": checkedKeys, + })) +} diff --git a/src/modules/system/controller/sys_dict_data.go b/src/modules/system/controller/sys_dict_data.go new file mode 100644 index 0000000..8c1509a --- /dev/null +++ b/src/modules/system/controller/sys_dict_data.go @@ -0,0 +1,244 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysDictDataController 结构体 +var NewSysDictData = &SysDictDataController{ + sysDictDataService: service.NewSysDictDataImpl, + sysDictTypeService: service.NewSysDictTypeImpl, +} + +// 字典类型对应的字典数据信息 +// +// PATH /system/dict/data +type SysDictDataController struct { + // 字典数据服务 + sysDictDataService service.ISysDictData + // 字典类型服务 + sysDictTypeService service.ISysDictType +} + +// 字典数据列表 +// +// GET /list +func (s *SysDictDataController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysDictDataService.SelectDictDataPage(querys) + c.JSON(200, result.Ok(data)) +} + +// 字典数据详情 +// +// GET /:dictCode +func (s *SysDictDataController) Info(c *gin.Context) { + dictCode := c.Param("dictCode") + if dictCode == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysDictDataService.SelectDictDataByCode(dictCode) + if data.DictCode == dictCode { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典数据新增 +// +// POST / +func (s *SysDictDataController) Add(c *gin.Context) { + var body model.SysDictData + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DictCode != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查字典类型是否存在 + sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType) + if sysDictType.DictType != body.DictType { + c.JSON(200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典标签唯一 + uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, "") + if !uniqueDictLabel { + msg := fmt.Sprintf("数据新增【%s】失败,该字典类型下标签名已存在", body.DictLabel) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查字典键值唯一 + uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, "") + if !uniqueDictValue { + msg := fmt.Sprintf("数据新增【%s】失败,该字典类型下标签值已存在", body.DictValue) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysDictDataService.InsertDictData(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典类型修改 +// +// PUT / +func (s *SysDictDataController) Edit(c *gin.Context) { + var body model.SysDictData + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DictCode == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查字典类型是否存在 + sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType) + if sysDictType.DictType != body.DictType { + c.JSON(200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典编码是否存在 + SysDictDataController := s.sysDictDataService.SelectDictDataByCode(body.DictCode) + if SysDictDataController.DictCode != body.DictCode { + c.JSON(200, result.ErrMsg("没有权限访问字典编码数据!")) + return + } + + // 检查字典标签唯一 + uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, body.DictCode) + if !uniqueDictLabel { + msg := fmt.Sprintf("数据修改【%s】失败,该字典类型下标签名已存在", body.DictLabel) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查字典键值唯一 + uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, body.DictCode) + if !uniqueDictValue { + msg := fmt.Sprintf("数据修改【%s】失败,该字典类型下标签值已存在", body.DictValue) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysDictDataService.UpdateDictData(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典数据删除 +// +// DELETE /:dictCodes +func (s *SysDictDataController) Remove(c *gin.Context) { + dictCodes := c.Param("dictCodes") + if dictCodes == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(dictCodes, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysDictDataService.DeleteDictDataByCodes(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 字典数据列表(指定字典类型) +// +// GET /type/:dictType +func (s *SysDictDataController) DictType(c *gin.Context) { + dictType := c.Param("dictType") + if dictType == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + data := s.sysDictDataService.SelectDictDataByType(dictType) + c.JSON(200, result.OkData(data)) +} + +// 字典数据列表导出 +// +// POST /export +func (s *SysDictDataController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysDictDataService.SelectDictDataPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysDictData) + + // 导出文件名称 + fileName := fmt.Sprintf("dict_data_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "字典编码", + "B1": "字典排序", + "C1": "字典标签", + "D1": "字典键值", + "E1": "字典类型", + "F1": "状态", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + statusValue := "停用" + if row.Status == "1" { + statusValue = "正常" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.DictCode, + "B" + idx: row.DictSort, + "C" + idx: row.DictLabel, + "D" + idx: row.DictValue, + "E" + idx: row.DictType, + "F" + idx: statusValue, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_dict_type.go b/src/modules/system/controller/sys_dict_type.go new file mode 100644 index 0000000..5969778 --- /dev/null +++ b/src/modules/system/controller/sys_dict_type.go @@ -0,0 +1,242 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysDictTypeController 结构体 +var NewSysDictType = &SysDictTypeController{ + sysDictTypeService: service.NewSysDictTypeImpl, +} + +// 字典类型信息 +// +// PATH /system/dict/type +type SysDictTypeController struct { + // 字典类型服务 + sysDictTypeService service.ISysDictType +} + +// 字典类型列表 +// +// GET /list +func (s *SysDictTypeController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysDictTypeService.SelectDictTypePage(querys) + c.JSON(200, result.Ok(data)) +} + +// 字典类型信息 +// +// GET /:dictId +func (s *SysDictTypeController) Info(c *gin.Context) { + dictId := c.Param("dictId") + if dictId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysDictTypeService.SelectDictTypeByID(dictId) + if data.DictID == dictId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典类型新增 +// +// POST / +func (s *SysDictTypeController) Add(c *gin.Context) { + var body model.SysDictType + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DictID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查字典名称唯一 + uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, "") + if !uniqueDictName { + msg := fmt.Sprintf("字典新增【%s】失败,字典名称已存在", body.DictName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查字典类型唯一 + uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, "") + if !uniqueDictType { + msg := fmt.Sprintf("字典新增【%s】失败,字典类型已存在", body.DictType) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysDictTypeService.InsertDictType(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典类型修改 +// +// PUT / +func (s *SysDictTypeController) Edit(c *gin.Context) { + var body model.SysDictType + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.DictID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查数据是否存在 + dictInfo := s.sysDictTypeService.SelectDictTypeByID(body.DictID) + if dictInfo.DictID != body.DictID { + c.JSON(200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典名称唯一 + uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, body.DictID) + if !uniqueDictName { + msg := fmt.Sprintf("字典修改【%s】失败,字典名称已存在", body.DictName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查字典类型唯一 + uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, body.DictID) + if !uniqueDictType { + msg := fmt.Sprintf("字典修改【%s】失败,字典类型已存在", body.DictType) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysDictTypeService.UpdateDictType(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 字典类型删除 +// +// DELETE /:dictIds +func (s *SysDictTypeController) Remove(c *gin.Context) { + dictIds := c.Param("dictIds") + if dictIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(dictIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysDictTypeService.DeleteDictTypeByIDs(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 字典类型刷新缓存 +// +// PUT /refreshCache +func (s *SysDictTypeController) RefreshCache(c *gin.Context) { + s.sysDictTypeService.ResetDictCache() + c.JSON(200, result.Ok(nil)) +} + +// 字典类型选择框列表 +// +// GET /getDictOptionselect +func (s *SysDictTypeController) DictOptionselect(c *gin.Context) { + data := s.sysDictTypeService.SelectDictTypeList(model.SysDictType{ + Status: common.STATUS_YES, + }) + + type labelValue struct { + Label string `json:"label"` + Value string `json:"value"` + } + + // 数据组 + arr := []labelValue{} + for _, v := range data { + arr = append(arr, labelValue{ + Label: v.DictName, + Value: v.DictType, + }) + } + c.JSON(200, result.OkData(arr)) +} + +// 字典类型列表导出 +// +// POST /export +func (s *SysDictTypeController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysDictTypeService.SelectDictTypePage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysDictType) + + // 导出文件名称 + fileName := fmt.Sprintf("dict_type_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "字典主键", + "B1": "字典名称", + "C1": "字典类型", + "D1": "状态", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + statusValue := "停用" + if row.Status == "1" { + statusValue = "正常" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.DictID, + "B" + idx: row.DictName, + "C" + idx: row.DictType, + "D" + idx: statusValue, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_log_login.go b/src/modules/system/controller/sys_log_login.go new file mode 100644 index 0000000..c3976d0 --- /dev/null +++ b/src/modules/system/controller/sys_log_login.go @@ -0,0 +1,158 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + commonService "ems.agt/src/modules/common/service" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SysLogLoginController 结构体 +var NewSysLogLogin = &SysLogLoginController{ + sysLogLoginService: service.NewSysLogLoginImpl, + accountService: commonService.NewAccountImpl, +} + +// 系统登录日志信息 +// +// PATH /system/log/login +type SysLogLoginController struct { + // 系统登录日志服务 + sysLogLoginService service.ISysLogLogin + // 账号身份操作服务 + accountService commonService.IAccount +} + +// 系统登录日志列表 +// +// GET /list +func (s *SysLogLoginController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysLogLoginService.SelectSysLogLoginPage(querys) + c.JSON(200, result.Ok(data)) +} + +// 系统登录日志删除 +// +// DELETE /:infoIds +func (s *SysLogLoginController) Remove(c *gin.Context) { + infoIds := c.Param("infoIds") + if infoIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 处理字符转id数组后去重 + ids := strings.Split(infoIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows := s.sysLogLoginService.DeleteSysLogLoginByIds(uniqueIDs) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 系统登录日志清空 +// +// DELETE /clean +func (s *SysLogLoginController) Clean(c *gin.Context) { + err := s.sysLogLoginService.CleanSysLogLogin() + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(200, result.Ok(nil)) +} + +// 系统登录日志账户解锁 +// +// PUT /unlock/:userName +func (s *SysLogLoginController) Unlock(c *gin.Context) { + userName := c.Param("userName") + if userName == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + ok := s.accountService.ClearLoginRecordCache(userName) + if ok { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 导出系统登录日志信息 +// +// POST /export +func (s *SysLogLoginController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysLogLoginService.SelectSysLogLoginPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysLogLogin) + + // 导出文件名称 + fileName := fmt.Sprintf("sys_log_login_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "序号", + "B1": "用户账号", + "C1": "登录状态", + "D1": "登录地址", + "E1": "登录地点", + "F1": "浏览器", + "G1": "操作系统", + "H1": "提示消息", + "I1": "访问时间", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 状态 + statusValue := "失败" + if row.Status == "1" { + statusValue = "成功" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.LoginID, + "B" + idx: row.UserName, + "C" + idx: statusValue, + "D" + idx: row.IPAddr, + "E" + idx: row.LoginLocation, + "F" + idx: row.Browser, + "G" + idx: row.OS, + "H" + idx: row.Msg, + "I" + idx: date.ParseDateToStr(row.LoginTime, date.YYYY_MM_DD_HH_MM_SS), + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_log_operate.go b/src/modules/system/controller/sys_log_operate.go new file mode 100644 index 0000000..1620f03 --- /dev/null +++ b/src/modules/system/controller/sys_log_operate.go @@ -0,0 +1,155 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// 实例化控制层 SysLogOperateController 结构体 +var NewSysLogOperate = &SysLogOperateController{ + SysLogOperateService: service.NewSysLogOperateImpl, +} + +// 操作日志记录信息 +// +// PATH /system/log/operate +type SysLogOperateController struct { + // 操作日志服务 + SysLogOperateService service.ISysLogOperate +} + +// 操作日志列表 +// +// GET /list +func (s *SysLogOperateController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.SysLogOperateService.SelectSysLogOperatePage(querys) + c.JSON(200, result.Ok(data)) +} + +// 操作日志删除 +// +// DELETE /:operIds +func (s *SysLogOperateController) Remove(c *gin.Context) { + operIds := c.Param("operIds") + if operIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 处理字符转id数组后去重 + ids := strings.Split(operIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows := s.SysLogOperateService.DeleteSysLogOperateByIds(uniqueIDs) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 操作日志清空 +// +// DELETE /clean +func (s *SysLogOperateController) Clean(c *gin.Context) { + err := s.SysLogOperateService.CleanSysLogOperate() + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + c.JSON(200, result.Ok(nil)) +} + +// 导出操作日志 +// +// POST /export +func (s *SysLogOperateController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.SysLogOperateService.SelectSysLogOperatePage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysLogOperate) + + // 导出文件名称 + fileName := fmt.Sprintf("sys_log_operate_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "操作序号", + "B1": "操作模块", + "C1": "业务类型", + "D1": "请求方法", + "E1": "请求方式", + "F1": "操作类别", + "G1": "操作人员", + "H1": "部门名称", + "I1": "请求地址", + "J1": "操作地址", + "K1": "操作地点", + "L1": "请求参数", + "M1": "操作消息", + "N1": "状态", + "O1": "消耗时间(毫秒)", + "P1": "操作时间", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 业务类型 + businessType := "" + // 操作类别 + OperatorType := "" + // 状态 + statusValue := "失败" + if row.Status == "1" { + statusValue = "成功" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.OperID, + "B" + idx: row.Title, + "C" + idx: businessType, + "D" + idx: row.Method, + "E" + idx: row.RequestMethod, + "F" + idx: OperatorType, + "G" + idx: row.OperName, + "H" + idx: row.DeptName, + "I" + idx: row.OperURL, + "J" + idx: row.OperIP, + "K" + idx: row.OperLocation, + "L" + idx: row.OperParam, + "M" + idx: row.OperMsg, + "N" + idx: statusValue, + "O" + idx: row.CostTime, + "P" + idx: date.ParseDateToStr(row.OperTime, date.YYYY_MM_DD_HH_MM_SS), + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_menu.go b/src/modules/system/controller/sys_menu.go new file mode 100644 index 0000000..6e75ee7 --- /dev/null +++ b/src/modules/system/controller/sys_menu.go @@ -0,0 +1,287 @@ +package controller + +import ( + "fmt" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/constants/menu" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysMenuController 结构体 +var NewSysMenu = &SysMenuController{ + sysMenuService: service.NewSysMenuImpl, +} + +// 菜单信息 +// +// PATH /system/menu +type SysMenuController struct { + // 菜单服务 + sysMenuService service.ISysMenu +} + +// 菜单列表 +// +// GET /list +func (s *SysMenuController) List(c *gin.Context) { + query := model.SysMenu{} + if v, ok := c.GetQuery("menuName"); ok { + query.MenuName = v + } + if v, ok := c.GetQuery("status"); ok { + query.Status = v + } + + userId := ctx.LoginUserToUserID(c) + if config.IsAdmin(userId) { + userId = "*" + } + data := s.sysMenuService.SelectMenuList(query, userId) + c.JSON(200, result.OkData(data)) +} + +// 菜单信息 +// +// GET /:menuId +func (s *SysMenuController) Info(c *gin.Context) { + menuId := c.Param("menuId") + if menuId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysMenuService.SelectMenuById(menuId) + if data.MenuID == menuId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 菜单新增 +// +// POST / +func (s *SysMenuController) Add(c *gin.Context) { + var body model.SysMenu + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.MenuID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 目录和菜单检查地址唯一 + if menu.TYPE_DIR == body.MenuType || menu.TYPE_MENU == body.MenuType { + uniqueNenuPath := s.sysMenuService.CheckUniqueMenuPath(body.Path, "") + if !uniqueNenuPath { + msg := fmt.Sprintf("菜单新增【%s】失败,菜单路由地址已存在", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查名称唯一 + uniqueNenuName := s.sysMenuService.CheckUniqueMenuName(body.MenuName, body.ParentID, "") + if !uniqueNenuName { + msg := fmt.Sprintf("菜单新增【%s】失败,菜单名称已存在", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 外链菜单需要符合网站http(s)开头 + if body.IsFrame == common.STATUS_NO && !regular.ValidHttp(body.Path) { + msg := fmt.Sprintf("菜单新增【%s】失败,非内部地址必须以http(s)://开头", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysMenuService.InsertMenu(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 菜单修改 +// +// PUT / +func (s *SysMenuController) Edit(c *gin.Context) { + var body model.SysMenu + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.MenuID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 上级菜单不能选自己 + if body.MenuID == body.ParentID { + msg := fmt.Sprintf("菜单修改【%s】失败,上级菜单不能选择自己", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查数据是否存在 + menuInfo := s.sysMenuService.SelectMenuById(body.MenuID) + if menuInfo.MenuID != body.MenuID { + c.JSON(200, result.ErrMsg("没有权限访问菜单数据")) + return + } + // 父级ID不为0是要检查 + if body.ParentID != "0" { + menuParent := s.sysMenuService.SelectMenuById(body.ParentID) + if menuParent.MenuID != body.ParentID { + c.JSON(200, result.ErrMsg("没有权限访问菜单数据")) + return + } + // 禁用菜单时检查父菜单是否使用 + if body.Status == common.STATUS_YES && menuParent.Status == common.STATUS_NO { + c.JSON(200, result.ErrMsg("上级菜单未启用!")) + return + } + } + + // 目录和菜单检查地址唯一 + if menu.TYPE_DIR == body.MenuType || menu.TYPE_MENU == body.MenuType { + uniqueNenuPath := s.sysMenuService.CheckUniqueMenuPath(body.Path, body.MenuID) + if !uniqueNenuPath { + msg := fmt.Sprintf("菜单修改【%s】失败,菜单路由地址已存在", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查名称唯一 + uniqueNenuName := s.sysMenuService.CheckUniqueMenuName(body.MenuName, body.ParentID, body.MenuID) + if !uniqueNenuName { + msg := fmt.Sprintf("菜单修改【%s】失败,菜单名称已存在", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 外链菜单需要符合网站http(s)开头 + if body.IsFrame == common.STATUS_NO && !regular.ValidHttp(body.Path) { + msg := fmt.Sprintf("菜单修改【%s】失败,非内部地址必须以http(s)://开头", body.MenuName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 禁用菜单时检查子菜单是否使用 + if body.Status == common.STATUS_NO { + hasStatus := s.sysMenuService.HasChildByMenuIdAndStatus(body.MenuID, common.STATUS_YES) + if hasStatus > 0 { + msg := fmt.Sprintf("不允许禁用,存在使用子菜单数:%d", hasStatus) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysMenuService.UpdateMenu(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 菜单删除 +// +// DELETE /:menuId +func (s *SysMenuController) Remove(c *gin.Context) { + menuId := c.Param("menuId") + if menuId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查数据是否存在 + menu := s.sysMenuService.SelectMenuById(menuId) + if menu.MenuID != menuId { + c.JSON(200, result.ErrMsg("没有权限访问菜单数据!")) + return + } + + // 检查是否存在子菜单 + hasChild := s.sysMenuService.HasChildByMenuIdAndStatus(menuId, "") + if hasChild > 0 { + msg := fmt.Sprintf("不允许删除,存在子菜单数:%d", hasChild) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查是否分配给角色 + existRole := s.sysMenuService.CheckMenuExistRole(menuId) + if existRole > 0 { + msg := fmt.Sprintf("不允许删除,菜单已分配给角色数:%d", existRole) + c.JSON(200, result.ErrMsg(msg)) + return + } + + rows := s.sysMenuService.DeleteMenuById(menuId) + if rows > 0 { + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 菜单树结构列表 +// +// GET /treeSelect +func (s *SysMenuController) TreeSelect(c *gin.Context) { + query := model.SysMenu{} + if v, ok := c.GetQuery("menuName"); ok { + query.MenuName = v + } + if v, ok := c.GetQuery("status"); ok { + query.Status = v + } + + userId := ctx.LoginUserToUserID(c) + if config.IsAdmin(userId) { + userId = "*" + } + data := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId) + c.JSON(200, result.OkData(data)) + +} + +// 菜单树结构列表(指定角色) +// +// GET /roleMenuTreeSelect/:roleId +func (s *SysMenuController) RoleMenuTreeSelect(c *gin.Context) { + roleId := c.Param("roleId") + if roleId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + query := model.SysMenu{} + if v, ok := c.GetQuery("menuName"); ok { + query.MenuName = v + } + if v, ok := c.GetQuery("status"); ok { + query.Status = v + } + + userId := ctx.LoginUserToUserID(c) + if config.IsAdmin(userId) { + userId = "*" + } + menuTreeSelect := s.sysMenuService.SelectMenuTreeSelectByUserId(query, userId) + checkedKeys := s.sysMenuService.SelectMenuListByRoleId(roleId) + c.JSON(200, result.OkData(map[string]any{ + "menus": menuTreeSelect, + "checkedKeys": checkedKeys, + })) +} diff --git a/src/modules/system/controller/sys_notice.go b/src/modules/system/controller/sys_notice.go new file mode 100644 index 0000000..29e1861 --- /dev/null +++ b/src/modules/system/controller/sys_notice.go @@ -0,0 +1,126 @@ +package controller + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysNoticeController 结构体 +var NewSysNotice = &SysNoticeController{ + sysNoticeService: service.NewSysNoticeImpl, +} + +// 通知公告信息 +// +// PATH /system/notice +type SysNoticeController struct { + // 公告服务 + sysNoticeService service.ISysNotice +} + +// 通知公告列表 +// +// GET /list +func (s *SysNoticeController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysNoticeService.SelectNoticePage(querys) + c.JSON(200, result.Ok(data)) +} + +// 通知公告信息 +// +// GET /:noticeId +func (s *SysNoticeController) Info(c *gin.Context) { + noticeId := c.Param("noticeId") + if noticeId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysNoticeService.SelectNoticeById(noticeId) + if data.NoticeID == noticeId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 通知公告新增 +// +// POST / +func (s *SysNoticeController) Add(c *gin.Context) { + var body model.SysNotice + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.NoticeID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysNoticeService.InsertNotice(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 通知公告修改 +// +// PUT / +func (s *SysNoticeController) Edit(c *gin.Context) { + var body model.SysNotice + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.NoticeID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + notice := s.sysNoticeService.SelectNoticeById(body.NoticeID) + if notice.NoticeID != body.NoticeID { + c.JSON(200, result.ErrMsg("没有权限访问公告信息数据!")) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysNoticeService.UpdateNotice(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 通知公告删除 +// +// DELETE /:noticeIds +func (s *SysNoticeController) Remove(c *gin.Context) { + noticeIds := c.Param("noticeIds") + if noticeIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(noticeIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysNoticeService.DeleteNoticeByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} diff --git a/src/modules/system/controller/sys_post.go b/src/modules/system/controller/sys_post.go new file mode 100644 index 0000000..e38eca3 --- /dev/null +++ b/src/modules/system/controller/sys_post.go @@ -0,0 +1,211 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysPostController 结构体 +var NewSysPost = &SysPostController{ + sysPostService: service.NewSysPostImpl, +} + +// 岗位信息 +// +// PATH /system/post +type SysPostController struct { + // 岗位服务 + sysPostService service.ISysPost +} + +// 岗位列表 +// +// GET /list +func (s *SysPostController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + data := s.sysPostService.SelectPostPage(querys) + c.JSON(200, result.Ok(data)) +} + +// 岗位信息 +// +// GET /:postId +func (s *SysPostController) Info(c *gin.Context) { + postId := c.Param("postId") + if postId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysPostService.SelectPostById(postId) + if data.PostID == postId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 岗位新增 +// +// POST / +func (s *SysPostController) Add(c *gin.Context) { + var body model.SysPost + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.PostID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查名称唯一 + uniqueuPostName := s.sysPostService.CheckUniquePostName(body.PostName, "") + if !uniqueuPostName { + msg := fmt.Sprintf("岗位新增【%s】失败,岗位名称已存在", body.PostName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查编码属性值唯一 + uniquePostCode := s.sysPostService.CheckUniquePostCode(body.PostCode, "") + if !uniquePostCode { + msg := fmt.Sprintf("岗位新增【%s】失败,岗位编码已存在", body.PostCode) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysPostService.InsertPost(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 岗位修改 +// +// PUT / +func (s *SysPostController) Edit(c *gin.Context) { + var body model.SysPost + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.PostID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + post := s.sysPostService.SelectPostById(body.PostID) + if post.PostID != body.PostID { + c.JSON(200, result.ErrMsg("没有权限访问岗位数据!")) + return + } + + // 检查名称唯一 + uniqueuPostName := s.sysPostService.CheckUniquePostName(body.PostName, body.PostID) + if !uniqueuPostName { + msg := fmt.Sprintf("岗位修改【%s】失败,岗位名称已存在", body.PostName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查编码属性值唯一 + uniquePostCode := s.sysPostService.CheckUniquePostCode(body.PostCode, body.PostID) + if !uniquePostCode { + msg := fmt.Sprintf("岗位修改【%s】失败,岗位编码已存在", body.PostCode) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysPostService.UpdatePost(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 岗位删除 +// +// DELETE /:postIds +func (s *SysPostController) Remove(c *gin.Context) { + postIds := c.Param("postIds") + if postIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(postIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysPostService.DeletePostByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 导出岗位信息 +// +// POST /export +func (s *SysPostController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + data := s.sysPostService.SelectPostPage(querys) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysPost) + + // 导出文件名称 + fileName := fmt.Sprintf("post_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "岗位编号", + "B1": "岗位编码", + "C1": "岗位名称", + "D1": "岗位排序", + "E1": "状态", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + statusValue := "停用" + if row.Status == "1" { + statusValue = "正常" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.PostID, + "B" + idx: row.PostCode, + "C" + idx: row.PostName, + "D" + idx: row.PostSort, + "E" + idx: statusValue, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_profile.go b/src/modules/system/controller/sys_profile.go new file mode 100644 index 0000000..e0f5999 --- /dev/null +++ b/src/modules/system/controller/sys_profile.go @@ -0,0 +1,282 @@ +package controller + +import ( + "fmt" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/admin" + "ems.agt/src/framework/constants/uploadsubpath" + "ems.agt/src/framework/utils/crypto" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/framework/utils/token" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysProfileController 结构体 +var NewSysProfile = &SysProfileController{ + sysUserService: service.NewSysUserImpl, + sysRoleService: service.NewSysRoleImpl, + sysPostService: service.NewSysPostImpl, + sysMenuService: service.NewSysMenuImpl, +} + +// 个人信息 +// +// PATH /system/user/profile +type SysProfileController struct { + // 用户服务 + sysUserService service.ISysUser + // 角色服务 + sysRoleService service.ISysRole + // 岗位服务 + sysPostService service.ISysPost + // 菜单服务 + sysMenuService service.ISysMenu +} + +// 个人信息 +// +// GET / +func (s *SysProfileController) Info(c *gin.Context) { + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + return + } + + // 查询用户所属角色组 + roleGroup := []string{} + roles := s.sysRoleService.SelectRoleListByUserId(loginUser.UserID) + for _, role := range roles { + roleGroup = append(roleGroup, role.RoleName) + } + isAdmin := config.IsAdmin(loginUser.UserID) + if isAdmin { + roleGroup = append(roleGroup, "管理员") + } + + // 查询用户所属岗位组 + postGroup := []string{} + posts := s.sysPostService.SelectPostListByUserId(loginUser.UserID) + for _, post := range posts { + postGroup = append(postGroup, post.PostName) + } + + c.JSON(200, result.OkData(map[string]any{ + "user": loginUser.User, + "roleGroup": parse.RemoveDuplicates(roleGroup), + "postGroup": parse.RemoveDuplicates(postGroup), + })) +} + +// 个人信息修改 +// +// PUT / +func (s *SysProfileController) UpdateProfile(c *gin.Context) { + var body struct { + // 昵称 + NickName string `json:"nickName" binding:"required"` + // 性别 + Sex string `json:"sex" binding:"required"` + // 手机号 + PhoneNumber string `json:"phonenumber"` + // 邮箱 + Email string `json:"email"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.Sex == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 登录用户信息 + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + return + } + userId := loginUser.UserID + userName := loginUser.User.UserName + + // 检查手机号码格式并判断是否唯一 + if body.PhoneNumber != "" { + if regular.ValidMobile(body.PhoneNumber) { + uniquePhone := s.sysUserService.CheckUniquePhone(body.PhoneNumber, userId) + if !uniquePhone { + msg := fmt.Sprintf("修改用户【%s】失败,手机号码已存在", userName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("修改用户【%s】失败,手机号码格式错误", userName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + body.PhoneNumber = "nil" + } + + // 检查邮箱格式并判断是否唯一 + if body.Email != "" { + if regular.ValidEmail(body.Email) { + uniqueEmail := s.sysUserService.CheckUniqueEmail(body.Email, userId) + if !uniqueEmail { + msg := fmt.Sprintf("修改用户【%s】失败,邮箱已存在", userName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("修改用户【%s】失败,邮箱格式错误", userName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + body.Email = "nil" + } + + // 用户基本资料 + sysUser := model.SysUser{ + UserID: userId, + UpdateBy: userName, + NickName: body.NickName, + PhoneNumber: body.PhoneNumber, + Email: body.Email, + Sex: body.Sex, + } + rows := s.sysUserService.UpdateUser(sysUser) + if rows > 0 { + // 更新缓存用户信息 + loginUser.User = s.sysUserService.SelectUserByUserName(userName) + // 用户权限组标识 + isAdmin := config.IsAdmin(sysUser.UserID) + if isAdmin { + loginUser.Permissions = []string{admin.PERMISSION} + } else { + perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID) + loginUser.Permissions = parse.RemoveDuplicates(perms) + } + // 刷新令牌信息 + token.Cache(&loginUser) + + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.ErrMsg("上传图片异常")) +} + +// 个人重置密码 +// +// PUT /updatePwd +func (s *SysProfileController) UpdatePwd(c *gin.Context) { + var body struct { + // 旧密码 + OldPassword string `json:"oldPassword" binding:"required"` + // 新密码 + NewPassword string `json:"newPassword" binding:"required"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 登录用户信息 + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + return + } + userId := loginUser.UserID + userName := loginUser.User.UserName + + // 查询当前登录用户信息得到密码值 + user := s.sysUserService.SelectUserById(userId) + if user.UserID != userId { + c.JSON(200, result.ErrMsg("没有权限访问用户数据!")) + return + } + + // 检查匹配用户密码 + oldCompare := crypto.BcryptCompare(body.OldPassword, user.Password) + if !oldCompare { + c.JSON(200, result.ErrMsg("修改密码失败,旧密码错误")) + return + } + newCompare := crypto.BcryptCompare(body.NewPassword, user.Password) + if newCompare { + c.JSON(200, result.ErrMsg("新密码不能与旧密码相同")) + return + } + + // 修改新密码 + sysUser := model.SysUser{ + UserID: userId, + UpdateBy: userName, + Password: body.NewPassword, + } + rows := s.sysUserService.UpdateUser(sysUser) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 个人头像上传 +// +// POST /avatar +func (s *SysProfileController) Avatar(c *gin.Context) { + formFile, err := c.FormFile("file") + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 上传文件转存 + filePath, err := file.TransferUploadFile(formFile, uploadsubpath.AVATART, []string{".jpg", ".jpeg", ".png"}) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 登录用户信息 + loginUser, err := ctx.LoginUser(c) + if err != nil { + c.JSON(401, result.CodeMsg(401, err.Error())) + return + } + + // 更新头像地址 + sysUser := model.SysUser{ + UserID: loginUser.UserID, + UpdateBy: loginUser.User.UserName, + Avatar: filePath, + } + rows := s.sysUserService.UpdateUser(sysUser) + if rows > 0 { + // 更新缓存用户信息 + loginUser.User = s.sysUserService.SelectUserByUserName(loginUser.User.UserName) + // 用户权限组标识 + isAdmin := config.IsAdmin(sysUser.UserID) + if isAdmin { + loginUser.Permissions = []string{admin.PERMISSION} + } else { + perms := s.sysMenuService.SelectMenuPermsByUserId(sysUser.UserID) + loginUser.Permissions = parse.RemoveDuplicates(perms) + } + // 刷新令牌信息 + token.Cache(&loginUser) + + c.JSON(200, result.OkData(filePath)) + return + } + c.JSON(200, result.Err(nil)) +} diff --git a/src/modules/system/controller/sys_role.go b/src/modules/system/controller/sys_role.go new file mode 100644 index 0000000..94535a9 --- /dev/null +++ b/src/modules/system/controller/sys_role.go @@ -0,0 +1,408 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/constants/admin" + "ems.agt/src/framework/constants/roledatascope" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysRoleController 结构体 +var NewSysRole = &SysRoleController{ + sysRoleService: service.NewSysRoleImpl, + sysUserService: service.NewSysUserImpl, +} + +// 角色信息 +// +// PATH /system/role +type SysRoleController struct { + // 角色服务 + sysRoleService service.ISysRole + // 用户服务 + sysUserService service.ISysUser +} + +// 角色列表 +// +// GET /list +func (s *SysRoleController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysRoleService.SelectRolePage(querys, dataScopeSQL) + c.JSON(200, result.Ok(data)) +} + +// 角色信息详情 +// +// GET /:roleId +func (s *SysRoleController) Info(c *gin.Context) { + roleId := c.Param("roleId") + if roleId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysRoleService.SelectRoleById(roleId) + if data.RoleID == roleId { + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 角色信息新增 +// +// POST / +func (s *SysRoleController) Add(c *gin.Context) { + var body model.SysRole + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.RoleID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 判断角色名称是否唯一 + uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, "") + if !uniqueRoleName { + msg := fmt.Sprintf("角色新增【%s】失败,角色名称已存在", body.RoleName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 判断角色键值是否唯一 + uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, "") + if !uniqueRoleKey { + msg := fmt.Sprintf("角色新增【%s】失败,角色键值已存在", body.RoleName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysRoleService.InsertRole(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 角色信息修改 +// +// PUT / +func (s *SysRoleController) Edit(c *gin.Context) { + var body model.SysRole + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.RoleID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否管理员角色 + if body.RoleID == admin.ROLE_ID { + c.JSON(200, result.ErrMsg("不允许操作管理员角色")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(body.RoleID) + if role.RoleID != body.RoleID { + c.JSON(200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + // 判断角色名称是否唯一 + uniqueRoleName := s.sysRoleService.CheckUniqueRoleName(body.RoleName, body.RoleID) + if !uniqueRoleName { + msg := fmt.Sprintf("角色修改【%s】失败,角色名称已存在", body.RoleName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 判断角色键值是否唯一 + uniqueRoleKey := s.sysRoleService.CheckUniqueRoleKey(body.RoleKey, body.RoleID) + if !uniqueRoleKey { + msg := fmt.Sprintf("角色修改【%s】失败,角色键值已存在", body.RoleName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysRoleService.UpdateRole(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 角色信息删除 +// +// DELETE /:roleIds +func (s *SysRoleController) Remove(c *gin.Context) { + roleIds := c.Param("roleIds") + if roleIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(roleIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + // 检查是否管理员角色 + for _, id := range uniqueIDs { + if id == admin.ROLE_ID { + c.JSON(200, result.ErrMsg("不允许操作管理员角色")) + return + } + } + rows, err := s.sysRoleService.DeleteRoleByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 角色状态变更 +// +// PUT /changeStatus +func (s *SysRoleController) Status(c *gin.Context) { + var body struct { + // 角色ID + RoleID string `json:"roleId" binding:"required"` + // 状态 + Status string `json:"status" binding:"required"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否管理员角色 + if body.RoleID == admin.ROLE_ID { + c.JSON(200, result.ErrMsg("不允许操作管理员角色")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(body.RoleID) + if role.RoleID != body.RoleID { + c.JSON(200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + // 与旧值相等不变更 + if role.Status == body.Status { + c.JSON(200, result.ErrMsg("变更状态与旧值相等!")) + return + } + + // 更新状态不刷新缓存 + userName := ctx.LoginUserToUserName(c) + SysRoleController := model.SysRole{ + RoleID: body.RoleID, + Status: body.Status, + UpdateBy: userName, + } + rows := s.sysRoleService.UpdateRole(SysRoleController) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 角色数据权限修改 +// +// PUT /dataScope +func (s *SysRoleController) DataScope(c *gin.Context) { + var body struct { + // 角色ID + RoleID string `json:"roleId"` + // 部门组(数据权限) + DeptIds []string `json:"deptIds"` + // 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限) + DataScope string `json:"dataScope"` + // 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示) + DeptCheckStrictly string `json:"deptCheckStrictly"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否管理员角色 + if body.RoleID == admin.ROLE_ID { + c.JSON(200, result.ErrMsg("不允许操作管理员角色")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(body.RoleID) + if role.RoleID != body.RoleID { + c.JSON(200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + // 更新数据权限 + userName := ctx.LoginUserToUserName(c) + SysRoleController := model.SysRole{ + RoleID: body.RoleID, + DeptIds: body.DeptIds, + DataScope: body.DataScope, + DeptCheckStrictly: body.DeptCheckStrictly, + UpdateBy: userName, + } + rows := s.sysRoleService.AuthDataScope(SysRoleController) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 角色分配用户列表 +// +// GET /authUser/allocatedList +func (s *SysRoleController) AuthUserAllocatedList(c *gin.Context) { + querys := ctx.QueryMap(c) + roleId, ok := querys["roleId"] + if !ok || roleId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(roleId.(string)) + if role.RoleID != roleId { + c.JSON(200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "u") + data := s.sysUserService.SelectAllocatedPage(querys, dataScopeSQL) + c.JSON(200, result.Ok(data)) +} + +// 角色分配选择授权 +// +// PUT /authUser/checked +func (s *SysRoleController) AuthUserChecked(c *gin.Context) { + var body struct { + // 角色ID + RoleID string `json:"roleId" binding:"required"` + // 用户ID组 + UserIDs string `json:"userIds" binding:"required"` + // 选择操作 添加true 取消false + Checked bool `json:"checked"` + } + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 处理字符转id数组后去重 + ids := strings.Split(body.UserIDs, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + + // 检查是否存在 + role := s.sysRoleService.SelectRoleById(body.RoleID) + if role.RoleID != body.RoleID { + c.JSON(200, result.ErrMsg("没有权限访问角色数据!")) + return + } + + var rows int64 + if body.Checked { + rows = s.sysRoleService.InsertAuthUsers(body.RoleID, uniqueIDs) + } else { + rows = s.sysRoleService.DeleteAuthUsers(body.RoleID, uniqueIDs) + } + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 导出角色信息 +// +// POST /export +func (s *SysRoleController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysRoleService.SelectRolePage(querys, dataScopeSQL) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysRole) + + // 导出文件名称 + fileName := fmt.Sprintf("role_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "角色序号", + "B1": "角色名称", + "C1": "角色权限", + "D1": "角色排序", + "E1": "数据范围", + "F1": "角色状态", + } + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 数据范围 + dataScope := "空" + if v, ok := roledatascope.RoleDataScope[row.DataScope]; ok { + dataScope = v + } + // 角色状态 + statusValue := "停用" + if row.Status == "1" { + statusValue = "正常" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.RoleID, + "B" + idx: row.RoleName, + "C" + idx: row.RoleKey, + "D" + idx: row.RoleSort, + "E" + idx: dataScope, + "F" + idx: statusValue, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} diff --git a/src/modules/system/controller/sys_user.go b/src/modules/system/controller/sys_user.go new file mode 100644 index 0000000..220cd2d --- /dev/null +++ b/src/modules/system/controller/sys_user.go @@ -0,0 +1,481 @@ +package controller + +import ( + "fmt" + "strconv" + "strings" + "time" + + "ems.agt/src/framework/config" + "ems.agt/src/framework/constants/admin" + "ems.agt/src/framework/utils/ctx" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/file" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/framework/vo/result" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysUserController 结构体 +var NewSysUser = &SysUserController{ + sysUserService: service.NewSysUserImpl, + sysRoleService: service.NewSysRoleImpl, + sysPostService: service.NewSysPostImpl, + sysDictDataService: service.NewSysDictDataImpl, +} + +// 用户信息 +// +// PATH /system/user +type SysUserController struct { + // 用户服务 + sysUserService service.ISysUser + // 角色服务 + sysRoleService service.ISysRole + // 岗位服务 + sysPostService service.ISysPost + // 字典数据服务 + sysDictDataService service.ISysDictData +} + +// 用户信息列表 +// +// GET /list +func (s *SysUserController) List(c *gin.Context) { + querys := ctx.QueryMap(c) + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "u") + data := s.sysUserService.SelectUserPage(querys, dataScopeSQL) + c.JSON(200, result.Ok(data)) +} + +// 用户信息详情 +// +// GET /:userId +func (s *SysUserController) Info(c *gin.Context) { + userId := c.Param("userId") + if userId == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 查询系统角色列表 + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + roles := s.sysRoleService.SelectRoleList(model.SysRole{}, dataScopeSQL) + + // 不是系统指定管理员需要排除其角色 + if !config.IsAdmin(userId) { + rolesFilter := make([]model.SysRole, 0) + for _, r := range roles { + if r.RoleID != admin.ROLE_ID { + rolesFilter = append(rolesFilter, r) + } + } + roles = rolesFilter + } + + // 查询系统岗位列表 + posts := s.sysPostService.SelectPostList(model.SysPost{}) + + // 新增用户时,用户ID为0 + if userId == "0" { + c.JSON(200, result.OkData(map[string]any{ + "user": map[string]any{}, + "roleIds": []string{}, + "postIds": []string{}, + "roles": roles, + "posts": posts, + })) + return + } + + // 检查用户是否存在 + user := s.sysUserService.SelectUserById(userId) + if user.UserID != userId { + c.JSON(200, result.ErrMsg("没有权限访问用户数据!")) + return + } + + // 角色ID组 + roleIds := make([]string, 0) + for _, r := range user.Roles { + roleIds = append(roleIds, r.RoleID) + } + + // 岗位ID组 + postIds := make([]string, 0) + userPosts := s.sysPostService.SelectPostListByUserId(userId) + for _, p := range userPosts { + postIds = append(postIds, p.PostID) + } + + c.JSON(200, result.OkData(map[string]any{ + "user": user, + "roleIds": roleIds, + "postIds": postIds, + "roles": roles, + "posts": posts, + })) +} + +// 用户信息新增 +// +// POST / +func (s *SysUserController) Add(c *gin.Context) { + var body model.SysUser + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.UserID != "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查用户登录账号是否唯一 + uniqueUserName := s.sysUserService.CheckUniqueUserName(body.UserName, "") + if !uniqueUserName { + msg := fmt.Sprintf("新增用户【%s】失败,登录账号已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查手机号码格式并判断是否唯一 + if body.PhoneNumber != "" { + if regular.ValidMobile(body.PhoneNumber) { + uniquePhone := s.sysUserService.CheckUniquePhone(body.PhoneNumber, "") + if !uniquePhone { + msg := fmt.Sprintf("新增用户【%s】失败,手机号码已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("新增用户【%s】失败,手机号码格式错误", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查邮箱格式并判断是否唯一 + if body.Email != "" { + if regular.ValidEmail(body.Email) { + uniqueEmail := s.sysUserService.CheckUniqueEmail(body.Email, "") + if !uniqueEmail { + msg := fmt.Sprintf("新增用户【%s】失败,邮箱已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("新增用户【%s】失败,邮箱格式错误", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysUserService.InsertUser(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 用户信息修改 +// +// POST / +func (s *SysUserController) Edit(c *gin.Context) { + var body model.SysUser + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.UserID == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否管理员用户 + if config.IsAdmin(body.UserID) { + c.JSON(200, result.ErrMsg("不允许操作管理员用户")) + return + } + + user := s.sysUserService.SelectUserById(body.UserID) + if user.UserID != body.UserID { + c.JSON(200, result.ErrMsg("没有权限访问用户数据!")) + return + } + + // 检查用户登录账号是否唯一 + uniqueUserName := s.sysUserService.CheckUniqueUserName(body.UserName, body.UserID) + if !uniqueUserName { + msg := fmt.Sprintf("修改用户【%s】失败,登录账号已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查手机号码格式并判断是否唯一 + if body.PhoneNumber != "" { + if regular.ValidMobile(body.PhoneNumber) { + uniquePhone := s.sysUserService.CheckUniquePhone(body.PhoneNumber, body.UserID) + if !uniquePhone { + msg := fmt.Sprintf("修改用户【%s】失败,手机号码已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("修改用户【%s】失败,手机号码格式错误", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 检查邮箱格式并判断是否唯一 + if body.Email != "" { + if regular.ValidEmail(body.Email) { + uniqueEmail := s.sysUserService.CheckUniqueEmail(body.Email, body.UserID) + if !uniqueEmail { + msg := fmt.Sprintf("修改用户【%s】失败,邮箱已存在", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } else { + msg := fmt.Sprintf("修改用户【%s】失败,邮箱格式错误", body.UserName) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + body.UserName = "" // 忽略修改登录用户名称 + body.Password = "" // 忽略修改密码 + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysUserService.UpdateUserAndRolePost(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 用户信息删除 +// +// DELETE /:userIds +func (s *SysUserController) Remove(c *gin.Context) { + userIds := c.Param("userIds") + if userIds == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(userIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + c.JSON(200, result.Err(nil)) + return + } + rows, err := s.sysUserService.DeleteUserByIds(uniqueIDs) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + c.JSON(200, result.OkMsg(msg)) +} + +// 用户重置密码 +// +// PUT /resetPwd +func (s *SysUserController) ResetPwd(c *gin.Context) { + var body struct { + UserID string `json:"userId" binding:"required"` + Password string `json:"password" binding:"required"` + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否管理员用户 + if config.IsAdmin(body.UserID) { + c.JSON(200, result.ErrMsg("不允许操作管理员用户")) + return + } + + user := s.sysUserService.SelectUserById(body.UserID) + if user.UserID != body.UserID { + c.JSON(200, result.ErrMsg("没有权限访问用户数据!")) + return + } + if !regular.ValidPassword(body.Password) { + c.JSON(200, result.ErrMsg("登录密码至少包含大小写字母、数字、特殊符号,且不少于6位")) + return + } + + userName := ctx.LoginUserToUserName(c) + SysUserController := model.SysUser{ + UserID: body.UserID, + Password: body.Password, + UpdateBy: userName, + } + rows := s.sysUserService.UpdateUser(SysUserController) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 用户状态修改 +// +// PUT /changeStatus +func (s *SysUserController) Status(c *gin.Context) { + var body struct { + UserID string `json:"userId" binding:"required"` + Status string `json:"status" binding:"required"` + } + if err := c.ShouldBindBodyWith(&body, binding.JSON); err != nil { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 检查是否存在 + user := s.sysUserService.SelectUserById(body.UserID) + if user.UserID != body.UserID { + c.JSON(200, result.ErrMsg("没有权限访问用户数据!")) + return + } + + // 与旧值相等不变更 + if user.Status == body.Status { + c.JSON(200, result.ErrMsg("变更状态与旧值相等!")) + return + } + + userName := ctx.LoginUserToUserName(c) + SysUserController := model.SysUser{ + UserID: body.UserID, + Status: body.Status, + UpdateBy: userName, + } + rows := s.sysUserService.UpdateUser(SysUserController) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 用户信息列表导出 +// +// POST /export +func (s *SysUserController) Export(c *gin.Context) { + // 查询结果,根据查询条件结果,单页最大值限制 + querys := ctx.BodyJSONMap(c) + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "u") + data := s.sysUserService.SelectUserPage(querys, dataScopeSQL) + if data["total"].(int64) == 0 { + c.JSON(200, result.ErrMsg("导出数据记录为空")) + return + } + rows := data["rows"].([]model.SysUser) + + // 导出文件名称 + fileName := fmt.Sprintf("user_export_%d_%d.xlsx", len(rows), time.Now().UnixMilli()) + // 第一行表头标题 + headerCells := map[string]string{ + "A1": "用户序号", + "B1": "登录名称", + "C1": "用户名称", + "D1": "用户邮箱", + "E1": "手机号码", + "F1": "用户性别", + "G1": "帐号状态", + "H1": "最后登录IP", + "I1": "最后登录时间", + "J1": "部门名称", + "K1": "部门负责人", + } + // 读取用户性别字典数据 + dictSysUserSex := s.sysDictDataService.SelectDictDataByType("sys_user_sex") + // 从第二行开始的数据 + dataCells := make([]map[string]any, 0) + for i, row := range rows { + idx := strconv.Itoa(i + 2) + // 用户性别 + sysUserSex := "未知" + for _, v := range dictSysUserSex { + if row.Sex == v.DictValue { + sysUserSex = v.DictLabel + break + } + } + // 帐号状态 + statusValue := "停用" + if row.Status == "1" { + statusValue = "正常" + } + dataCells = append(dataCells, map[string]any{ + "A" + idx: row.UserID, + "B" + idx: row.UserName, + "C" + idx: row.NickName, + "D" + idx: row.Email, + "E" + idx: row.PhoneNumber, + "F" + idx: sysUserSex, + "G" + idx: statusValue, + "H" + idx: row.LoginIP, + "I" + idx: date.ParseDateToStr(row.LoginDate, date.YYYY_MM_DD_HH_MM_SS), + "J" + idx: row.Dept.DeptName, + "K" + idx: row.Dept.Leader, + }) + } + + // 导出数据表格 + saveFilePath, err := file.WriteSheet(headerCells, dataCells, fileName, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + c.FileAttachment(saveFilePath, fileName) +} + +// 用户信息列表导入模板下载 +// +// GET /importTemplate +func (s *SysUserController) Template(c *gin.Context) { + fileName := fmt.Sprintf("user_import_template_%d.xlsx", time.Now().UnixMilli()) + asserPath := "assets/template/excel/user_import_template.xlsx" + c.FileAttachment(asserPath, fileName) +} + +// 用户信息列表导入 +// +// POST /importData +func (s *SysUserController) ImportData(c *gin.Context) { + // 允许进行更新 + updateSupport := c.PostForm("updateSupport") + // 上传的文件 + formFile, err := c.FormFile("file") + if err != nil || updateSupport == "" { + c.JSON(400, result.CodeMsg(400, "参数错误")) + return + } + + // 保存表格文件 + filePath, err := file.TransferExeclUploadFile(formFile) + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 读取表格数据 + rows, err := file.ReadSheet(filePath, "") + if err != nil { + c.JSON(200, result.ErrMsg(err.Error())) + return + } + + // 获取操作人名称 + operName := ctx.LoginUserToUserName(c) + isUpdateSupport := parse.Boolean(updateSupport) + message := s.sysUserService.ImportUser(rows, isUpdateSupport, operName) + c.JSON(200, result.OkMsg(message)) +} diff --git a/src/modules/system/model/sys_config.go b/src/modules/system/model/sys_config.go new file mode 100644 index 0000000..8949c67 --- /dev/null +++ b/src/modules/system/model/sys_config.go @@ -0,0 +1,25 @@ +package model + +// 参数配置对象 sys_config +type SysConfig struct { + // 参数主键 + ConfigID string `json:"configId"` + // 参数名称 + ConfigName string `json:"configName" binding:"required"` + // 参数键名 + ConfigKey string `json:"configKey" binding:"required"` + // 参数键值 + ConfigValue string `json:"configValue" binding:"required"` + // 系统内置(Y是 N否) + ConfigType string `json:"configType"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/system/model/sys_dept.go b/src/modules/system/model/sys_dept.go new file mode 100644 index 0000000..b7a08f1 --- /dev/null +++ b/src/modules/system/model/sys_dept.go @@ -0,0 +1,41 @@ +package model + +// SysDept 部门对象 sys_dept +type SysDept struct { + // 部门ID + DeptID string `json:"deptId"` + // 父部门ID + ParentID string `json:"parentId" binding:"required"` + // 祖级列表 + Ancestors string `json:"ancestors"` + // 部门名称 + DeptName string `json:"deptName" binding:"required"` + // 显示顺序 + OrderNum int `json:"orderNum"` + // 负责人 + Leader string `json:"leader"` + // 联系电话 + Phone string `json:"phone"` + // 邮箱 + Email string `json:"email"` + // 部门状态(0正常 1停用) + Status string `json:"status"` + // 删除标志(0代表存在 1代表删除) + DelFlag string `json:"delFlag"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + + // ====== 非数据库字段属性 ====== + + // 子部门列表 + Children []SysDept `json:"children,omitempty"` + + // 父部门名称 + ParentName string `json:"parentName,omitempty"` +} diff --git a/src/modules/system/model/sys_dict_data.go b/src/modules/system/model/sys_dict_data.go new file mode 100644 index 0000000..7c8cd06 --- /dev/null +++ b/src/modules/system/model/sys_dict_data.go @@ -0,0 +1,31 @@ +package model + +// SysDictData 字典数据对象 sys_dict_data +type SysDictData struct { + // 字典编码 + DictCode string `json:"dictCode"` + // 字典排序 + DictSort int `json:"dictSort"` + // 字典标签 + DictLabel string `json:"dictLabel" binding:"required"` + // 字典键值 + DictValue string `json:"dictValue" binding:"required"` + // 字典类型 + DictType string `json:"dictType" binding:"required"` + // 样式属性(样式扩展) + TagClass string `json:"tagClass"` + // 标签类型(预设颜色) + TagType string `json:"tagType"` + // 状态(0停用 1正常) + Status string `json:"status"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/system/model/sys_dict_type.go b/src/modules/system/model/sys_dict_type.go new file mode 100644 index 0000000..0ad0b1a --- /dev/null +++ b/src/modules/system/model/sys_dict_type.go @@ -0,0 +1,23 @@ +package model + +// SysDictType 字典类型对象 sys_dict_type +type SysDictType struct { + // 字典主键 + DictID string `json:"dictId"` + // 字典名称 + DictName string `json:"dictName" binding:"required"` + // 字典类型 + DictType string `json:"dictType" binding:"required"` + // 状态(0停用 1正常) + Status string `json:"status"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/system/model/sys_log_login.go b/src/modules/system/model/sys_log_login.go new file mode 100644 index 0000000..8548f8d --- /dev/null +++ b/src/modules/system/model/sys_log_login.go @@ -0,0 +1,23 @@ +package model + +// SysLogLogin 系统登录日志表 sys_log_login +type SysLogLogin struct { + // 登录ID + LoginID string `json:"loginId"` + // 用户账号 + UserName string `json:"userName"` + // 登录IP地址 + IPAddr string `json:"ipaddr"` + // 登录地点 + LoginLocation string `json:"loginLocation"` + // 浏览器类型 + Browser string `json:"browser"` + // 操作系统 + OS string `json:"os"` + // 登录状态(0失败 1成功) + Status string `json:"status"` + // 提示消息 + Msg string `json:"msg"` + // 访问时间 + LoginTime int64 `json:"loginTime"` +} diff --git a/src/modules/system/model/sys_log_operate.go b/src/modules/system/model/sys_log_operate.go new file mode 100644 index 0000000..3a0b604 --- /dev/null +++ b/src/modules/system/model/sys_log_operate.go @@ -0,0 +1,37 @@ +package model + +// SysLogOperate 系统操作日志表 sys_log_operate +type SysLogOperate struct { + // 日志主键 + OperID string `json:"operId"` + // 模块标题 + Title string `json:"title"` + // 业务类型(0其它 1新增 2修改 3删除 4授权 5导出 6导入 7强退 8清空数据) + BusinessType string `json:"businessType"` + // 方法名称 + Method string `json:"method"` + // 请求方式 + RequestMethod string `json:"requestMethod"` + // 操作人员类别(0其它 1后台用户 2手机端用户) + OperatorType string `json:"operatorType"` + // 操作人员 + OperName string `json:"operName"` + // 部门名称 + DeptName string `json:"deptName"` + // 请求URL + OperURL string `json:"operUrl"` + // 主机地址 + OperIP string `json:"operIp"` + // 操作地点 + OperLocation string `json:"operLocation"` + // 请求参数 + OperParam string `json:"operParam"` + // 操作消息 + OperMsg string `json:"operMsg"` + // 操作状态(0异常 1正常) + Status string `json:"status"` + // 操作时间 + OperTime int64 `json:"operTime"` + // 消耗时间(毫秒) + CostTime int64 `json:"costTime"` +} diff --git a/src/modules/system/model/sys_menu.go b/src/modules/system/model/sys_menu.go new file mode 100644 index 0000000..397e30e --- /dev/null +++ b/src/modules/system/model/sys_menu.go @@ -0,0 +1,46 @@ +package model + +// SysMenu 菜单权限对象 sys_menu +type SysMenu struct { + // 菜单ID + MenuID string `json:"menuId"` + // 菜单名称 + MenuName string `json:"menuName" binding:"required"` + // 父菜单ID 默认0 + ParentID string `json:"parentId" binding:"required"` + // 显示顺序 + MenuSort int `json:"menuSort"` + // 路由地址 + Path string `json:"path"` + // 组件路径 + Component string `json:"component"` + // 是否内部跳转(0否 1是) + IsFrame string `json:"isFrame"` + // 是否缓存(0不缓存 1缓存) + IsCache string `json:"isCache"` + // 菜单类型(D目录 M菜单 B按钮) + MenuType string `json:"menuType" binding:"required"` + // 是否显示(0隐藏 1显示) + Visible string `json:"visible"` + // 菜单状态(0停用 1正常) + Status string `json:"status"` + // 权限标识 + Perms string `json:"perms"` + // 菜单图标(#无图标) + Icon string `json:"icon"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` + + // ====== 非数据库字段属性 ====== + + // 子菜单 + Children []SysMenu `json:"children,omitempty"` +} diff --git a/src/modules/system/model/sys_notice.go b/src/modules/system/model/sys_notice.go new file mode 100644 index 0000000..dc33097 --- /dev/null +++ b/src/modules/system/model/sys_notice.go @@ -0,0 +1,27 @@ +package model + +// SysNotice 通知公告对象 sys_notice +type SysNotice struct { + // 公告ID + NoticeID string `json:"noticeId"` + // 公告标题 + NoticeTitle string `json:"noticeTitle" binding:"required"` + // 公告类型(1通知 2公告) + NoticeType string `json:"noticeType" binding:"required"` + // 公告内容 + NoticeContent string `json:"noticeContent" binding:"required"` + // 公告状态(0关闭 1正常) + Status string `json:"status"` + // 删除标志(0代表存在 1代表删除) + DelFlag string `json:"delFlag"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/system/model/sys_post.go b/src/modules/system/model/sys_post.go new file mode 100644 index 0000000..7b65197 --- /dev/null +++ b/src/modules/system/model/sys_post.go @@ -0,0 +1,25 @@ +package model + +// SysPost 岗位对象 sys_post +type SysPost struct { + // 岗位ID + PostID string `json:"postId"` + // 岗位编码 + PostCode string `json:"postCode" binding:"required"` + // 岗位名称 + PostName string `json:"postName" binding:"required"` + // 显示顺序 + PostSort int `json:"postSort"` + // 状态(0停用 1正常) + Status string `json:"status"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` +} diff --git a/src/modules/system/model/sys_role.go b/src/modules/system/model/sys_role.go new file mode 100644 index 0000000..c4a8d9e --- /dev/null +++ b/src/modules/system/model/sys_role.go @@ -0,0 +1,40 @@ +package model + +// SysRole 角色对象 sys_role +type SysRole struct { + // 角色ID + RoleID string `json:"roleId"` + // 角色名称 + RoleName string `json:"roleName" binding:"required"` + // 角色键值 + RoleKey string `json:"roleKey" binding:"required"` + // 显示顺序 + RoleSort int `json:"roleSort"` + // 数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限 5:仅本人数据权限) + DataScope string `json:"dataScope"` + // 菜单树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示) + MenuCheckStrictly string `json:"menuCheckStrictly"` + // 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示) + DeptCheckStrictly string `json:"deptCheckStrictly"` + // 角色状态(0停用 1正常) + Status string `json:"status"` + // 删除标志(0代表存在 1代表删除) + DelFlag string `json:"delFlag"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` + + // ====== 非数据库字段属性 ====== + + // 菜单组 + MenuIds []string `json:"menuIds,omitempty"` + // 部门组(数据权限) + DeptIds []string `json:"deptIds,omitempty"` +} diff --git a/src/modules/system/model/sys_role_dept.go b/src/modules/system/model/sys_role_dept.go new file mode 100644 index 0000000..29f9840 --- /dev/null +++ b/src/modules/system/model/sys_role_dept.go @@ -0,0 +1,15 @@ +package model + +// SysRoleDept 角色和部门关联对象 sys_role_dept +type SysRoleDept struct { + RoleID string `json:"roleId"` // 角色ID + DeptID string `json:"deptId"` // 部门ID +} + +// NewSysRoleDept 创建角色和部门关联对象的构造函数 +func NewSysRoleDept(roleID string, deptID string) SysRoleDept { + return SysRoleDept{ + RoleID: roleID, + DeptID: deptID, + } +} diff --git a/src/modules/system/model/sys_role_menu.go b/src/modules/system/model/sys_role_menu.go new file mode 100644 index 0000000..3d5f74d --- /dev/null +++ b/src/modules/system/model/sys_role_menu.go @@ -0,0 +1,15 @@ +package model + +// SysRoleMenu 角色和菜单关联对象 sys_role_menu +type SysRoleMenu struct { + RoleID string `json:"roleId"` // 角色ID + MenuID string `json:"menuId"` // 菜单ID +} + +// NewSysRoleMenu 创建角色和菜单关联对象的构造函数 +func NewSysRoleMenu(roleID string, menuID string) SysRoleMenu { + return SysRoleMenu{ + RoleID: roleID, + MenuID: menuID, + } +} diff --git a/src/modules/system/model/sys_user.go b/src/modules/system/model/sys_user.go new file mode 100644 index 0000000..c580784 --- /dev/null +++ b/src/modules/system/model/sys_user.go @@ -0,0 +1,56 @@ +package model + +// SysUser 用户对象 sys_user +type SysUser struct { + // 用户ID + UserID string `json:"userId"` + // 部门ID + DeptID string `json:"deptId"` + // 用户账号 + UserName string `json:"userName" binding:"required"` + // 用户昵称 + NickName string `json:"nickName" binding:"required"` + // 用户类型(sys系统用户) + UserType string `json:"userType"` + // 用户邮箱 + Email string `json:"email"` + // 手机号码 + PhoneNumber string `json:"phonenumber"` + // 用户性别(0未知 1男 2女) + Sex string `json:"sex"` + // 头像地址 + Avatar string `json:"avatar"` + // 密码 + Password string `json:"-"` + // 帐号状态(0停用 1正常) + Status string `json:"status"` + // 删除标志(0代表存在 1代表删除) + DelFlag string `json:"delFlag"` + // 最后登录IP + LoginIP string `json:"loginIp"` + // 最后登录时间 + LoginDate int64 `json:"loginDate"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + // 备注 + Remark string `json:"remark"` + + // ====== 非数据库字段属性 ====== + + // 部门对象 + Dept SysDept `json:"dept,omitempty" binding:"structonly"` + // 角色对象组 + Roles []SysRole `json:"roles"` + // 角色ID + RoleID string `json:"roleId,omitempty"` + // 角色组 + RoleIDs []string `json:"roleIds,omitempty"` + // 岗位组 + PostIDs []string `json:"postIds,omitempty"` +} diff --git a/src/modules/system/model/sys_user_post.go b/src/modules/system/model/sys_user_post.go new file mode 100644 index 0000000..557cd2e --- /dev/null +++ b/src/modules/system/model/sys_user_post.go @@ -0,0 +1,15 @@ +package model + +// SysUserPost 用户和岗位关联对象 sys_user_post +type SysUserPost struct { + UserID string `json:"userId"` // 用户ID + PostID string `json:"postId"` // 岗位ID +} + +// NewSysUserPost 创建用户和岗位关联对象的构造函数 +func NewSysUserPost(userID string, postID string) SysUserPost { + return SysUserPost{ + UserID: userID, + PostID: postID, + } +} diff --git a/src/modules/system/model/sys_user_role.go b/src/modules/system/model/sys_user_role.go new file mode 100644 index 0000000..e2c44a7 --- /dev/null +++ b/src/modules/system/model/sys_user_role.go @@ -0,0 +1,15 @@ +package model + +// SysUserRole 用户和角色关联对象 sys_user_role +type SysUserRole struct { + UserID string `json:"userId"` // 用户ID + RoleID string `json:"roleId"` // 角色ID +} + +// NewSysUserRole 创建用户和角色关联对象的构造函数 +func NewSysUserRole(userID string, roleID string) SysUserRole { + return SysUserRole{ + UserID: userID, + RoleID: roleID, + } +} diff --git a/src/modules/system/repository/sys_config.go b/src/modules/system/repository/sys_config.go new file mode 100644 index 0000000..ea7cb2b --- /dev/null +++ b/src/modules/system/repository/sys_config.go @@ -0,0 +1,30 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysConfig 参数配置表 数据层接口 +type ISysConfig interface { + // SelectDictDataPage 分页查询参数配置列表数据 + SelectConfigPage(query map[string]any) map[string]any + + // SelectConfigList 查询参数配置列表 + SelectConfigList(sysConfig model.SysConfig) []model.SysConfig + + // SelectConfigValueByKey 通过参数键名查询参数键值 + SelectConfigValueByKey(configKey string) string + + // SelectConfigByIds 通过配置ID查询参数配置信息 + SelectConfigByIds(configIds []string) []model.SysConfig + + // CheckUniqueConfig 校验配置参数是否唯一 + CheckUniqueConfig(sysConfig model.SysConfig) string + + // InsertConfig 新增参数配置 + InsertConfig(sysConfig model.SysConfig) string + + // UpdateConfig 修改参数配置 + UpdateConfig(sysConfig model.SysConfig) int64 + + // DeleteConfigByIds 批量删除参数配置信息 + DeleteConfigByIds(configIds []string) int64 +} diff --git a/src/modules/system/repository/sys_config.impl.go b/src/modules/system/repository/sys_config.impl.go new file mode 100644 index 0000000..fa5543e --- /dev/null +++ b/src/modules/system/repository/sys_config.impl.go @@ -0,0 +1,339 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysConfigImpl 结构体 +var NewSysConfigImpl = &SysConfigImpl{ + selectSql: `select + config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark + from sys_config`, + + resultMap: map[string]string{ + "config_id": "ConfigID", + "config_name": "ConfigName", + "config_key": "ConfigKey", + "config_value": "ConfigValue", + "config_type": "ConfigType", + "remark": "Remark", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + }, +} + +// SysConfigImpl 参数配置表 数据层处理 +type SysConfigImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysConfigImpl) convertResultRows(rows []map[string]any) []model.SysConfig { + arr := make([]model.SysConfig, 0) + for _, row := range rows { + sysConfig := model.SysConfig{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysConfig, keyMapper, value) + } + } + arr = append(arr, sysConfig) + } + return arr +} + +// SelectDictDataPage 分页查询参数配置列表数据 +func (r *SysConfigImpl) SelectConfigPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["configName"]; ok && v != "" { + conditions = append(conditions, "config_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["configType"]; ok && v != "" { + conditions = append(conditions, "config_type = ?") + params = append(params, v) + } + if v, ok := query["configKey"]; ok && v != "" { + conditions = append(conditions, "config_key like concat(?, '%')") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "create_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "create_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysConfig{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_config" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectConfigList 查询参数配置列表 +func (r *SysConfigImpl) SelectConfigList(sysConfig model.SysConfig) []model.SysConfig { + // 查询条件拼接 + var conditions []string + var params []any + if sysConfig.ConfigName != "" { + conditions = append(conditions, "config_name like concat(?, '%')") + params = append(params, sysConfig.ConfigName) + } + if sysConfig.ConfigType != "" { + conditions = append(conditions, "config_type = ?") + params = append(params, sysConfig.ConfigType) + } + if sysConfig.ConfigKey != "" { + conditions = append(conditions, "config_key like concat(?, '%')") + params = append(params, sysConfig.ConfigKey) + } + if sysConfig.CreateTime > 0 { + conditions = append(conditions, "create_time >= ?") + params = append(params, sysConfig.CreateTime) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysConfig{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectConfigValueByKey 通过参数键名查询参数键值 +func (r *SysConfigImpl) SelectConfigValueByKey(configKey string) string { + querySql := "select config_value as 'str' from sys_config where config_key = ?" + results, err := datasource.RawDB("", querySql, []any{configKey}) + if err != nil { + logger.Errorf("query err => %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// SelectConfigByIds 通过配置ID查询参数配置信息 +func (r *SysConfigImpl) SelectConfigByIds(configIds []string) []model.SysConfig { + placeholder := repo.KeyPlaceholderByQuery(len(configIds)) + querySql := r.selectSql + " where config_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(configIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysConfig{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// CheckUniqueConfig 校验配置参数是否唯一 +func (r *SysConfigImpl) CheckUniqueConfig(sysConfig model.SysConfig) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysConfig.ConfigKey != "" { + conditions = append(conditions, "config_key = ?") + params = append(params, sysConfig.ConfigKey) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select config_id as 'str' from sys_config " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// InsertConfig 新增参数配置 +func (r *SysConfigImpl) InsertConfig(sysConfig model.SysConfig) string { + // 参数拼接 + params := make(map[string]any) + if sysConfig.ConfigName != "" { + params["config_name"] = sysConfig.ConfigName + } + if sysConfig.ConfigKey != "" { + params["config_key"] = sysConfig.ConfigKey + } + if sysConfig.ConfigValue != "" { + params["config_value"] = sysConfig.ConfigValue + } + if sysConfig.ConfigType != "" { + params["config_type"] = sysConfig.ConfigType + } + if sysConfig.Remark != "" { + params["remark"] = sysConfig.Remark + } + if sysConfig.CreateBy != "" { + params["create_by"] = sysConfig.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_config (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateConfig 修改参数配置 +func (r *SysConfigImpl) UpdateConfig(sysConfig model.SysConfig) int64 { + // 参数拼接 + params := make(map[string]any) + if sysConfig.ConfigName != "" { + params["config_name"] = sysConfig.ConfigName + } + if sysConfig.ConfigKey != "" { + params["config_key"] = sysConfig.ConfigKey + } + if sysConfig.ConfigValue != "" { + params["config_value"] = sysConfig.ConfigValue + } + if sysConfig.ConfigType != "" { + params["config_type"] = sysConfig.ConfigType + } + if sysConfig.Remark != "" { + params["remark"] = sysConfig.Remark + } + if sysConfig.UpdateBy != "" { + params["update_by"] = sysConfig.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_config set " + strings.Join(keys, ",") + " where config_id = ?" + + // 执行更新 + values = append(values, sysConfig.ConfigID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteConfigByIds 批量删除参数配置信息 +func (r *SysConfigImpl) DeleteConfigByIds(configIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(configIds)) + sql := "delete from sys_config where config_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(configIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_dept.go b/src/modules/system/repository/sys_dept.go new file mode 100644 index 0000000..bc64d8f --- /dev/null +++ b/src/modules/system/repository/sys_dept.go @@ -0,0 +1,42 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysDept 部门表 数据层接口 +type ISysDept interface { + // SelectDeptList 查询部门管理数据 + SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept + + // SelectDeptListByRoleId 根据角色ID查询部门树信息 + SelectDeptListByRoleId(roleId string, deptCheckStrictly bool) []string + + // SelectDeptById 根据部门ID查询信息 + SelectDeptById(deptId string) model.SysDept + + // SelectChildrenDeptById 根据ID查询所有子部门 + SelectChildrenDeptById(deptId string) []model.SysDept + + // HasChildByDeptId 是否存在子节点 + HasChildByDeptId(deptId string) int64 + + // CheckDeptExistUser 查询部门是否存在用户 + CheckDeptExistUser(deptId string) int64 + + // CheckUniqueDept 校验部门是否唯一 + CheckUniqueDept(sysDept model.SysDept) string + + // InsertDept 新增部门信息 + InsertDept(sysDept model.SysDept) string + + // UpdateDept 修改部门信息 + UpdateDept(sysDept model.SysDept) int64 + + // UpdateDeptStatusNormal 修改所在部门正常状态 + UpdateDeptStatusNormal(deptIds []string) int64 + + // UpdateDeptChildren 修改子元素关系 + UpdateDeptChildren(sysDepts []model.SysDept) int64 + + // DeleteDeptById 删除部门管理信息 + DeleteDeptById(deptId string) int64 +} diff --git a/src/modules/system/repository/sys_dept.impl.go b/src/modules/system/repository/sys_dept.impl.go new file mode 100644 index 0000000..e5698bc --- /dev/null +++ b/src/modules/system/repository/sys_dept.impl.go @@ -0,0 +1,393 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysDeptImpl 结构体 +var NewSysDeptImpl = &SysDeptImpl{ + selectSql: `select + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time + from sys_dept d`, + + resultMap: map[string]string{ + "dept_id": "DeptID", + "parent_id": "ParentID", + "ancestors": "Ancestors", + "dept_name": "DeptName", + "order_num": "OrderNum", + "leader": "Leader", + "phone": "Phone", + "email": "Email", + "status": "Status", + "del_flag": "DelFlag", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "parent_name": "ParentName", + }, +} + +// SysDeptImpl 部门表 数据层处理 +type SysDeptImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysDeptImpl) convertResultRows(rows []map[string]any) []model.SysDept { + arr := make([]model.SysDept, 0) + for _, row := range rows { + sysDept := model.SysDept{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysDept, keyMapper, value) + } + } + arr = append(arr, sysDept) + } + return arr +} + +// SelectDeptList 查询部门管理数据 +func (r *SysDeptImpl) SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept { + // 查询条件拼接 + var conditions []string + var params []any + if sysDept.DeptID != "" { + conditions = append(conditions, "dept_id = ?") + params = append(params, sysDept.DeptID) + } + if sysDept.ParentID != "" { + conditions = append(conditions, "parent_id = ?") + params = append(params, sysDept.ParentID) + } + if sysDept.DeptName != "" { + conditions = append(conditions, "dept_name like concat(?, '%')") + params = append(params, sysDept.DeptName) + } + if sysDept.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysDept.Status) + } + + // 构建查询条件语句 + whereSql := " where d.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by d.parent_id, d.order_num asc " + querySql := r.selectSql + whereSql + dataScopeSQL + orderSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDept{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDeptListByRoleId 根据角色ID查询部门树信息 +func (r *SysDeptImpl) SelectDeptListByRoleId(roleId string, deptCheckStrictly bool) []string { + querySql := `select d.dept_id as 'str' from sys_dept d + left join sys_role_dept rd on d.dept_id = rd.dept_id + where rd.role_id = ? ` + var params []any + params = append(params, roleId) + // 展开 + if deptCheckStrictly { + querySql += ` and d.dept_id not in + (select d.parent_id from sys_dept d + inner join sys_role_dept rd on d.dept_id = rd.dept_id + and rd.role_id = ?) ` + params = append(params, roleId) + } + querySql += "order by d.parent_id, d.order_num" + + // 查询结果 + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []string{} + } + + if len(results) > 0 { + ids := make([]string, 0) + for _, v := range results { + ids = append(ids, fmt.Sprintf("%v", v["str"])) + } + return ids + } + return []string{} +} + +// SelectDeptById 根据部门ID查询信息 +func (r *SysDeptImpl) SelectDeptById(deptId string) model.SysDept { + querySql := `select d.dept_id, d.parent_id, d.ancestors, + d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, + (select dept_name from sys_dept where dept_id = d.parent_id) parent_name + from sys_dept d where d.dept_id = ?` + results, err := datasource.RawDB("", querySql, []any{deptId}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysDept{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysDept{} +} + +// SelectChildrenDeptById 根据ID查询所有子部门 +func (r *SysDeptImpl) SelectChildrenDeptById(deptId string) []model.SysDept { + querySql := r.selectSql + " where find_in_set(?, d.ancestors)" + results, err := datasource.RawDB("", querySql, []any{deptId}) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDept{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// HasChildByDeptId 是否存在子节点 +func (r *SysDeptImpl) HasChildByDeptId(deptId string) int64 { + querySql := "select count(1) as 'total' from sys_dept where status = '1' and parent_id = ? limit 1" + results, err := datasource.RawDB("", querySql, []any{deptId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// CheckDeptExistUser 查询部门是否存在用户 +func (r *SysDeptImpl) CheckDeptExistUser(deptId string) int64 { + querySql := "select count(1) as 'total' from sys_user where dept_id = ? and del_flag = '0'" + results, err := datasource.RawDB("", querySql, []any{deptId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// CheckUniqueDept 校验部门是否唯一 +func (r *SysDeptImpl) CheckUniqueDept(sysDept model.SysDept) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysDept.DeptName != "" { + conditions = append(conditions, "dept_name = ?") + params = append(params, sysDept.DeptName) + } + if sysDept.ParentID != "" { + conditions = append(conditions, "parent_id = ?") + params = append(params, sysDept.ParentID) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + if whereSql == "" { + return "" + } + + // 查询数据 + querySql := "select dept_id as 'str' from sys_dept " + whereSql + " and del_flag = '0' limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// InsertDept 新增部门信息 +func (r *SysDeptImpl) InsertDept(sysDept model.SysDept) string { + // 参数拼接 + params := make(map[string]any) + if sysDept.DeptID != "" { + params["dept_id"] = sysDept.DeptID + } + if sysDept.ParentID != "" { + params["parent_id"] = sysDept.ParentID + } + if sysDept.DeptName != "" { + params["dept_name"] = sysDept.DeptName + } + if sysDept.Ancestors != "" { + params["ancestors"] = sysDept.Ancestors + } + if sysDept.OrderNum > 0 { + params["order_num"] = sysDept.OrderNum + } + if sysDept.Leader != "" { + params["leader"] = sysDept.Leader + } + if sysDept.Phone != "" { + params["phone"] = sysDept.Phone + } + if sysDept.Email != "" { + params["email"] = sysDept.Email + } + if sysDept.Status != "" { + params["status"] = sysDept.Status + } + if sysDept.CreateBy != "" { + params["create_by"] = sysDept.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_dept (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateDept 修改部门信息 +func (r *SysDeptImpl) UpdateDept(sysDept model.SysDept) int64 { + // 参数拼接 + params := make(map[string]any) + if sysDept.ParentID != "" { + params["parent_id"] = sysDept.ParentID + } + if sysDept.DeptName != "" { + params["dept_name"] = sysDept.DeptName + } + if sysDept.Ancestors != "" { + params["ancestors"] = sysDept.Ancestors + } + if sysDept.OrderNum > 0 { + params["order_num"] = sysDept.OrderNum + } + if sysDept.Leader != "" { + params["leader"] = sysDept.Leader + } + if sysDept.Phone != "" { + params["phone"] = sysDept.Phone + } + if sysDept.Email != "" { + params["email"] = sysDept.Email + } + if sysDept.Status != "" { + params["status"] = sysDept.Status + } + if sysDept.UpdateBy != "" { + params["update_by"] = sysDept.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_dept set " + strings.Join(keys, ",") + " where dept_id = ?" + + // 执行更新 + values = append(values, sysDept.DeptID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// UpdateDeptStatusNormal 修改所在部门正常状态 +func (r *SysDeptImpl) UpdateDeptStatusNormal(deptIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(deptIds)) + sql := "update sys_dept set status = '1' where dept_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(deptIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("update err => %v", err) + return 0 + } + return results +} + +// UpdateDeptChildren 修改子元素关系 +func (r *SysDeptImpl) UpdateDeptChildren(sysDepts []model.SysDept) int64 { + // 无参数 + if len(sysDepts) == 0 { + return 0 + } + + // 更新条件拼接 + var conditions []string + var params []any + for _, dept := range sysDepts { + caseSql := fmt.Sprintf("WHEN dept_id = '%s' THEN '%s'", dept.DeptID, dept.Ancestors) + conditions = append(conditions, caseSql) + params = append(params, dept.DeptID) + } + + cases := strings.Join(conditions, " ") + placeholders := repo.KeyPlaceholderByQuery(len(params)) + sql := "update sys_dept set ancestors = CASE " + cases + " END where dept_id in (" + placeholders + ")" + results, err := datasource.ExecDB("", sql, params) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteDeptById 删除部门管理信息 +func (r *SysDeptImpl) DeleteDeptById(deptId string) int64 { + sql := "update sys_dept set status = '0', del_flag = '1' where dept_id = ?" + results, err := datasource.ExecDB("", sql, []any{deptId}) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_dict_data.go b/src/modules/system/repository/sys_dict_data.go new file mode 100644 index 0000000..050d697 --- /dev/null +++ b/src/modules/system/repository/sys_dict_data.go @@ -0,0 +1,33 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysDictData 字典类型数据表 数据层接口 +type ISysDictData interface { + // SelectDictDataPage 根据条件分页查询字典数据 + SelectDictDataPage(query map[string]any) map[string]any + + // SelectDictDataList 根据条件查询字典数据 + SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData + + // SelectDictDataByCodes 根据字典数据编码查询信息 + SelectDictDataByCodes(dictCodes []string) []model.SysDictData + + // CountDictDataByType 查询字典数据 + CountDictDataByType(dictType string) int64 + + // CheckUniqueDictData 校验字典数据是否唯一 + CheckUniqueDictData(sysDictData model.SysDictData) string + + // DeleteDictDataByCodes 批量删除字典数据信息 + DeleteDictDataByCodes(dictCodes []string) int64 + + // InsertDictData 新增字典数据信息 + InsertDictData(sysDictData model.SysDictData) string + + // UpdateDictData 修改字典数据信息 + UpdateDictData(sysDictData model.SysDictData) int64 + + // UpdateDictDataType 同步修改字典类型 + UpdateDictDataType(oldDictType string, newDictType string) int64 +} diff --git a/src/modules/system/repository/sys_dict_data.impl.go b/src/modules/system/repository/sys_dict_data.impl.go new file mode 100644 index 0000000..0664cb0 --- /dev/null +++ b/src/modules/system/repository/sys_dict_data.impl.go @@ -0,0 +1,368 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysDictDataImpl 结构体 +var NewSysDictDataImpl = &SysDictDataImpl{ + selectSql: `select + dict_code, dict_sort, dict_label, dict_value, dict_type, tag_class, tag_type, status, create_by, create_time, remark + from sys_dict_data`, + + resultMap: map[string]string{ + "dict_code": "DictCode", + "dict_sort": "DictSort", + "dict_label": "DictLabel", + "dict_value": "DictValue", + "dict_type": "DictType", + "tag_class": "TagClass", + "tag_type": "TagType", + "status": "Status", + "remark": "Remark", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + }, +} + +// SysDictDataImpl 字典类型数据表 数据层处理 +type SysDictDataImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysDictDataImpl) convertResultRows(rows []map[string]any) []model.SysDictData { + arr := make([]model.SysDictData, 0) + for _, row := range rows { + sysDictData := model.SysDictData{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysDictData, keyMapper, value) + } + } + arr = append(arr, sysDictData) + } + return arr +} + +// SelectDictDataPage 根据条件分页查询字典数据 +func (r *SysDictDataImpl) SelectDictDataPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["dictType"]; ok && v != "" { + conditions = append(conditions, "dict_type = ?") + params = append(params, v) + } + if v, ok := query["dictLabel"]; ok && v != "" { + conditions = append(conditions, "dict_label like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysDictData{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_dict_data" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by dict_sort asc limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectDictDataList 根据条件查询字典数据 +func (r *SysDictDataImpl) SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData { + // 查询条件拼接 + var conditions []string + var params []any + if sysDictData.DictLabel != "" { + conditions = append(conditions, "dict_label like concat(?, '%')") + params = append(params, sysDictData.DictLabel) + } + if sysDictData.DictType != "" { + conditions = append(conditions, "dict_type = ?") + params = append(params, sysDictData.DictType) + } + if sysDictData.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysDictData.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by dict_sort asc " + querySql := r.selectSql + whereSql + orderSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDictData{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDictDataByCodes 根据字典数据编码查询信息 +func (r *SysDictDataImpl) SelectDictDataByCodes(dictCodes []string) []model.SysDictData { + placeholder := repo.KeyPlaceholderByQuery(len(dictCodes)) + querySql := r.selectSql + " where dict_code in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(dictCodes) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDictData{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// CountDictDataByType 查询字典数据 +func (r *SysDictDataImpl) CountDictDataByType(dictType string) int64 { + querySql := "select count(1) as 'total' from sys_dict_data where dict_type = ?" + results, err := datasource.RawDB("", querySql, []any{dictType}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// CheckUniqueDictData 校验字典数据是否唯一 +func (r *SysDictDataImpl) CheckUniqueDictData(sysDictData model.SysDictData) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysDictData.DictType != "" { + conditions = append(conditions, "dict_type = ?") + params = append(params, sysDictData.DictType) + } + if sysDictData.DictLabel != "" { + conditions = append(conditions, "dict_label = ?") + params = append(params, sysDictData.DictLabel) + } + if sysDictData.DictValue != "" { + conditions = append(conditions, "dict_value = ?") + params = append(params, sysDictData.DictValue) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select dict_code as 'str' from sys_dict_data " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// DeleteDictDataByCodes 批量删除字典数据信息 +func (r *SysDictDataImpl) DeleteDictDataByCodes(dictCodes []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(dictCodes)) + sql := "delete from sys_dict_data where dict_code in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(dictCodes) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// InsertDictData 新增字典数据信息 +func (r *SysDictDataImpl) InsertDictData(sysDictData model.SysDictData) string { + // 参数拼接 + params := make(map[string]any) + if sysDictData.DictSort > 0 { + params["dict_sort"] = sysDictData.DictSort + } + if sysDictData.DictLabel != "" { + params["dict_label"] = sysDictData.DictLabel + } + if sysDictData.DictValue != "" { + params["dict_value"] = sysDictData.DictValue + } + if sysDictData.DictType != "" { + params["dict_type"] = sysDictData.DictType + } + if sysDictData.TagClass != "" { + params["tag_class"] = sysDictData.TagClass + } + if sysDictData.TagType != "" { + params["tag_type"] = sysDictData.TagType + } + if sysDictData.Status != "" { + params["status"] = sysDictData.Status + } + if sysDictData.Remark != "" { + params["remark"] = sysDictData.Remark + } + if sysDictData.CreateBy != "" { + params["create_by"] = sysDictData.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_dict_data (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateDictData 修改字典数据信息 +func (r *SysDictDataImpl) UpdateDictData(sysDictData model.SysDictData) int64 { + // 参数拼接 + params := make(map[string]any) + if sysDictData.DictSort > 0 { + params["dict_sort"] = sysDictData.DictSort + } + if sysDictData.DictLabel != "" { + params["dict_label"] = sysDictData.DictLabel + } + if sysDictData.DictValue != "" { + params["dict_value"] = sysDictData.DictValue + } + if sysDictData.DictType != "" { + params["dict_type"] = sysDictData.DictType + } + if sysDictData.TagClass != "" { + params["tag_class"] = sysDictData.TagClass + } + if sysDictData.TagType != "" { + params["tag_type"] = sysDictData.TagType + } + if sysDictData.Status != "" { + params["status"] = sysDictData.Status + } + if sysDictData.Remark != "" { + params["remark"] = sysDictData.Remark + } + if sysDictData.UpdateBy != "" { + params["update_by"] = sysDictData.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_dict_data set " + strings.Join(keys, ",") + " where dict_code = ?" + + // 执行更新 + values = append(values, sysDictData.DictCode) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// UpdateDictDataType 同步修改字典类型 +func (r *SysDictDataImpl) UpdateDictDataType(oldDictType string, newDictType string) int64 { + // 参数拼接 + params := make([]any, 0) + if oldDictType == "" || newDictType == "" { + return 0 + } + params = append(params, newDictType) + params = append(params, oldDictType) + + // 构建执行语句 + sql := "update sys_dict_data set dict_type = ? where dict_type = ?" + + // 执行更新 + rows, err := datasource.ExecDB("", sql, params) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} diff --git a/src/modules/system/repository/sys_dict_type.go b/src/modules/system/repository/sys_dict_type.go new file mode 100644 index 0000000..8a4f4ec --- /dev/null +++ b/src/modules/system/repository/sys_dict_type.go @@ -0,0 +1,30 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysDictType 字典类型表 数据层接口 +type ISysDictType interface { + // SelectDictTypePage 根据条件分页查询字典类型 + SelectDictTypePage(query map[string]any) map[string]any + + // SelectDictTypeList 根据条件查询字典类型 + SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType + + // SelectDictTypeByIDs 根据字典类型ID查询信息 + SelectDictTypeByIDs(dictIDs []string) []model.SysDictType + + // SelectDictTypeByType 根据字典类型查询信息 + SelectDictTypeByType(dictType string) model.SysDictType + + // CheckUniqueDictType 校验字典类型是否唯一 + CheckUniqueDictType(sysDictType model.SysDictType) string + + // InsertDictType 新增字典类型信息 + InsertDictType(sysDictType model.SysDictType) string + + // UpdateDictType 修改字典类型信息 + UpdateDictType(sysDictType model.SysDictType) int64 + + // DeleteDictTypeByIDs 批量删除字典类型信息 + DeleteDictTypeByIDs(dictIDs []string) int64 +} diff --git a/src/modules/system/repository/sys_dict_type.impl.go b/src/modules/system/repository/sys_dict_type.impl.go new file mode 100644 index 0000000..e34ab7b --- /dev/null +++ b/src/modules/system/repository/sys_dict_type.impl.go @@ -0,0 +1,334 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysDictTypeImpl 结构体 +var NewSysDictTypeImpl = &SysDictTypeImpl{ + selectSql: `select + dict_id, dict_name, dict_type, status, create_by, create_time, remark + from sys_dict_type`, + + resultMap: map[string]string{ + "dict_id": "DictID", + "dict_name": "DictName", + "dict_type": "DictType", + "remark": "Remark", + "status": "Status", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + }, +} + +// SysDictTypeImpl 字典类型表 数据层处理 +type SysDictTypeImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysDictTypeImpl) convertResultRows(rows []map[string]any) []model.SysDictType { + arr := make([]model.SysDictType, 0) + for _, row := range rows { + sysDictType := model.SysDictType{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysDictType, keyMapper, value) + } + } + arr = append(arr, sysDictType) + } + return arr +} + +// SelectDictTypePage 根据条件分页查询字典类型 +func (r *SysDictTypeImpl) SelectDictTypePage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["dictName"]; ok && v != "" { + conditions = append(conditions, "dict_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["dictType"]; ok && v != "" { + conditions = append(conditions, "dict_type like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "create_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "create_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysDictType{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_dict_type" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectDictTypeList 根据条件查询字典类型 +func (r *SysDictTypeImpl) SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType { + // 查询条件拼接 + var conditions []string + var params []any + if sysDictType.DictName != "" { + conditions = append(conditions, "dict_name like concat(?, '%')") + params = append(params, sysDictType.DictName) + } + if sysDictType.DictType != "" { + conditions = append(conditions, "dict_type like concat(?, '%')") + params = append(params, sysDictType.DictType) + } + if sysDictType.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysDictType.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDictType{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDictTypeByIDs 根据字典类型ID查询信息 +func (r *SysDictTypeImpl) SelectDictTypeByIDs(dictIDs []string) []model.SysDictType { + placeholder := repo.KeyPlaceholderByQuery(len(dictIDs)) + querySql := r.selectSql + " where dict_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(dictIDs) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysDictType{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDictTypeByType 根据字典类型查询信息 +func (r *SysDictTypeImpl) SelectDictTypeByType(dictType string) model.SysDictType { + querySql := r.selectSql + " where dict_type = ?" + results, err := datasource.RawDB("", querySql, []any{dictType}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysDictType{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysDictType{} +} + +// CheckUniqueDictType 校验字典是否唯一 +func (r *SysDictTypeImpl) CheckUniqueDictType(sysDictType model.SysDictType) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysDictType.DictName != "" { + conditions = append(conditions, "dict_name = ?") + params = append(params, sysDictType.DictName) + } + if sysDictType.DictType != "" { + conditions = append(conditions, "dict_type = ?") + params = append(params, sysDictType.DictType) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select dict_id as 'str' from sys_dict_type " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// InsertDictType 新增字典类型信息 +func (r *SysDictTypeImpl) InsertDictType(sysDictType model.SysDictType) string { + // 参数拼接 + params := make(map[string]any) + if sysDictType.DictName != "" { + params["dict_name"] = sysDictType.DictName + } + if sysDictType.DictType != "" { + params["dict_type"] = sysDictType.DictType + } + if sysDictType.Status != "" { + params["status"] = sysDictType.Status + } + if sysDictType.Remark != "" { + params["remark"] = sysDictType.Remark + } + if sysDictType.CreateBy != "" { + params["create_by"] = sysDictType.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_dict_type (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateDictType 修改字典类型信息 +func (r *SysDictTypeImpl) UpdateDictType(sysDictType model.SysDictType) int64 { + // 参数拼接 + params := make(map[string]any) + if sysDictType.DictName != "" { + params["dict_name"] = sysDictType.DictName + } + if sysDictType.DictType != "" { + params["dict_type"] = sysDictType.DictType + } + if sysDictType.Status != "" { + params["status"] = sysDictType.Status + } + if sysDictType.Remark != "" { + params["remark"] = sysDictType.Remark + } + if sysDictType.UpdateBy != "" { + params["update_by"] = sysDictType.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_dict_type set " + strings.Join(keys, ",") + " where dict_id = ?" + + // 执行更新 + values = append(values, sysDictType.DictID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteDictTypeByIDs 批量删除字典类型信息 +func (r *SysDictTypeImpl) DeleteDictTypeByIDs(dictIDs []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(dictIDs)) + sql := "delete from sys_dict_type where dict_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(dictIDs) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_log_login.go b/src/modules/system/repository/sys_log_login.go new file mode 100644 index 0000000..662e9fd --- /dev/null +++ b/src/modules/system/repository/sys_log_login.go @@ -0,0 +1,21 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysLogLogin 系统登录日志表 数据层接口 +type ISysLogLogin interface { + // SelectSysLogLoginPage 分页查询系统登录日志集合 + SelectSysLogLoginPage(query map[string]any) map[string]any + + // SelectSysLogLoginList 查询系统登录日志集合 + SelectSysLogLoginList(sysLogLogin model.SysLogLogin) []model.SysLogLogin + + // InsertSysLogLogin 新增系统登录日志 + InsertSysLogLogin(sysLogLogin model.SysLogLogin) string + + // DeleteSysLogLoginByIds 批量删除系统登录日志 + DeleteSysLogLoginByIds(loginIds []string) int64 + + // CleanSysLogLogin 清空系统登录日志 + CleanSysLogLogin() error +} diff --git a/src/modules/system/repository/sys_log_login.impl.go b/src/modules/system/repository/sys_log_login.impl.go new file mode 100644 index 0000000..601bc5b --- /dev/null +++ b/src/modules/system/repository/sys_log_login.impl.go @@ -0,0 +1,245 @@ +package repository + +import ( + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysLogLoginImpl 结构体 +var NewSysLogLoginImpl = &SysLogLoginImpl{ + selectSql: `select login_id, user_name, ipaddr, login_location, + browser, os, status, msg, login_time from sys_log_login`, + + resultMap: map[string]string{ + "login_id": "LoginID", + "user_name": "UserName", + "status": "Status", + "ipaddr": "IPAddr", + "login_location": "LoginLocation", + "browser": "Browser", + "os": "OS", + "msg": "Msg", + "login_time": "LoginTime", + }, +} + +// SysLogLoginImpl 系统登录访问表 数据层处理 +type SysLogLoginImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysLogLoginImpl) convertResultRows(rows []map[string]any) []model.SysLogLogin { + arr := make([]model.SysLogLogin, 0) + for _, row := range rows { + SysLogLogin := model.SysLogLogin{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&SysLogLogin, keyMapper, value) + } + } + arr = append(arr, SysLogLogin) + } + return arr +} + +// SelectSysLogLoginPage 分页查询系统登录日志集合 +func (r *SysLogLoginImpl) SelectSysLogLoginPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["ipaddr"]; ok && v != "" { + conditions = append(conditions, "ipaddr like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["userName"]; ok && v != "" { + conditions = append(conditions, "user_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "login_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "login_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysLogLogin{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_log_login" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by login_id desc limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectSysLogLoginList 查询系统登录日志集合 +func (r *SysLogLoginImpl) SelectSysLogLoginList(SysLogLogin model.SysLogLogin) []model.SysLogLogin { + // 查询条件拼接 + var conditions []string + var params []any + if SysLogLogin.IPAddr != "" { + conditions = append(conditions, "title like concat(?, '%')") + params = append(params, SysLogLogin.IPAddr) + } + if SysLogLogin.UserName != "" { + conditions = append(conditions, "user_name like concat(?, '%')") + params = append(params, SysLogLogin.UserName) + } + if SysLogLogin.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, SysLogLogin.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysLogLogin{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// InsertSysLogLogin 新增系统登录日志 +func (r *SysLogLoginImpl) InsertSysLogLogin(SysLogLogin model.SysLogLogin) string { + // 参数拼接 + params := make(map[string]any) + params["login_time"] = time.Now().UnixMilli() + if SysLogLogin.UserName != "" { + params["user_name"] = SysLogLogin.UserName + } + if SysLogLogin.Status != "" { + params["status"] = SysLogLogin.Status + } + if SysLogLogin.IPAddr != "" { + params["ipaddr"] = SysLogLogin.IPAddr + } + if SysLogLogin.LoginLocation != "" { + params["login_location"] = SysLogLogin.LoginLocation + } + if SysLogLogin.Browser != "" { + params["browser"] = SysLogLogin.Browser + } + if SysLogLogin.OS != "" { + params["os"] = SysLogLogin.OS + } + if SysLogLogin.Msg != "" { + params["msg"] = SysLogLogin.Msg + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_log_login (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// DeleteSysLogLoginByIds 批量删除系统登录日志 +func (r *SysLogLoginImpl) DeleteSysLogLoginByIds(loginIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(loginIds)) + sql := "delete from sys_log_login where login_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(loginIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// CleanSysLogLogin 清空系统登录日志 +func (r *SysLogLoginImpl) CleanSysLogLogin() error { + sql := "truncate table sys_log_login" + _, err := datasource.ExecDB("", sql, []any{}) + return err +} diff --git a/src/modules/system/repository/sys_log_operate.go b/src/modules/system/repository/sys_log_operate.go new file mode 100644 index 0000000..e3f7be2 --- /dev/null +++ b/src/modules/system/repository/sys_log_operate.go @@ -0,0 +1,24 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysLogOperate 操作日志表 数据层接口 +type ISysLogOperate interface { + // SelectSysLogOperatePage 分页查询系统操作日志集合 + SelectSysLogOperatePage(query map[string]any) map[string]any + + // SelectSysLogOperateList 查询系统操作日志集合 + SelectSysLogOperateList(sysLogOperate model.SysLogOperate) []model.SysLogOperate + + // SelectSysLogOperateById 查询操作日志详细 + SelectSysLogOperateById(operId string) model.SysLogOperate + + // InsertSysLogOperate 新增操作日志 + InsertSysLogOperate(sysLogOperate model.SysLogOperate) string + + // DeleteSysLogOperateByIds 批量删除系统操作日志 + DeleteSysLogOperateByIds(operIds []string) int64 + + // CleanSysLogOperate 清空操作日志 + CleanSysLogOperate() error +} diff --git a/src/modules/system/repository/sys_log_operate.impl.go b/src/modules/system/repository/sys_log_operate.impl.go new file mode 100644 index 0000000..10d1f3b --- /dev/null +++ b/src/modules/system/repository/sys_log_operate.impl.go @@ -0,0 +1,299 @@ +package repository + +import ( + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysLogOperateImpl 结构体 +var NewSysLogOperateImpl = &SysLogOperateImpl{ + selectSql: `select + oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, + oper_url, oper_ip, oper_location, oper_param, oper_msg, status, oper_time, cost_time + from sys_log_operate`, + + resultMap: map[string]string{ + "oper_id": "OperID", + "title": "Title", + "business_type": "BusinessType", + "method": "Method", + "request_method": "RequestMethod", + "operator_type": "OperatorType", + "oper_name": "OperName", + "dept_name": "DeptName", + "oper_url": "OperURL", + "oper_ip": "OperIP", + "oper_location": "OperLocation", + "oper_param": "OperParam", + "oper_msg": "OperMsg", + "status": "Status", + "oper_time": "OperTime", + "cost_time": "CostTime", + }, +} + +// SysLogOperateImpl 操作日志表 数据层处理 +type SysLogOperateImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysLogOperateImpl) convertResultRows(rows []map[string]any) []model.SysLogOperate { + arr := make([]model.SysLogOperate, 0) + for _, row := range rows { + SysLogOperate := model.SysLogOperate{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&SysLogOperate, keyMapper, value) + } + } + arr = append(arr, SysLogOperate) + } + return arr +} + +// SelectSysLogOperatePage 分页查询系统操作日志集合 +func (r *SysLogOperateImpl) SelectSysLogOperatePage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["title"]; ok && v != "" { + conditions = append(conditions, "title like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["businessType"]; ok && v != "" { + conditions = append(conditions, "business_type = ?") + params = append(params, v) + } + if v, ok := query["operName"]; ok && v != "" { + conditions = append(conditions, "oper_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "oper_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "oper_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysLogOperate{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_log_operate" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by oper_id desc limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectSysLogOperateList 查询系统操作日志集合 +func (r *SysLogOperateImpl) SelectSysLogOperateList(SysLogOperate model.SysLogOperate) []model.SysLogOperate { + // 查询条件拼接 + var conditions []string + var params []any + if SysLogOperate.Title != "" { + conditions = append(conditions, "title like concat(?, '%')") + params = append(params, SysLogOperate.Title) + } + if SysLogOperate.BusinessType != "" { + conditions = append(conditions, "business_type = ?") + params = append(params, SysLogOperate.BusinessType) + } + if SysLogOperate.OperName != "" { + conditions = append(conditions, "oper_name like concat(?, '%')") + params = append(params, SysLogOperate.OperName) + } + if SysLogOperate.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, SysLogOperate.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysLogOperate{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectSysLogOperateById 查询操作日志详细 +func (r *SysLogOperateImpl) SelectSysLogOperateById(operId string) model.SysLogOperate { + querySql := r.selectSql + " where oper_id = ?" + results, err := datasource.RawDB("", querySql, []any{operId}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysLogOperate{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysLogOperate{} +} + +// InsertSysLogOperate 新增操作日志 +func (r *SysLogOperateImpl) InsertSysLogOperate(SysLogOperate model.SysLogOperate) string { + // 参数拼接 + params := make(map[string]any) + params["oper_time"] = time.Now().UnixMilli() + if SysLogOperate.Title != "" { + params["title"] = SysLogOperate.Title + } + if SysLogOperate.BusinessType != "" { + params["business_type"] = SysLogOperate.BusinessType + } + if SysLogOperate.Method != "" { + params["method"] = SysLogOperate.Method + } + if SysLogOperate.RequestMethod != "" { + params["request_method"] = SysLogOperate.RequestMethod + } + if SysLogOperate.OperatorType != "" { + params["operator_type"] = SysLogOperate.OperatorType + } + if SysLogOperate.DeptName != "" { + params["dept_name"] = SysLogOperate.DeptName + } + if SysLogOperate.OperName != "" { + params["oper_name"] = SysLogOperate.OperName + } + if SysLogOperate.OperURL != "" { + params["oper_url"] = SysLogOperate.OperURL + } + if SysLogOperate.OperIP != "" { + params["oper_ip"] = SysLogOperate.OperIP + } + if SysLogOperate.OperLocation != "" { + params["oper_location"] = SysLogOperate.OperLocation + } + if SysLogOperate.OperParam != "" { + params["oper_param"] = SysLogOperate.OperParam + } + if SysLogOperate.OperMsg != "" { + params["oper_msg"] = SysLogOperate.OperMsg + } + if SysLogOperate.Status != "" { + params["status"] = SysLogOperate.Status + } + if SysLogOperate.CostTime > 0 { + params["cost_time"] = SysLogOperate.CostTime + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_log_operate (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// DeleteSysLogOperateByIds 批量删除系统操作日志 +func (r *SysLogOperateImpl) DeleteSysLogOperateByIds(operIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(operIds)) + sql := "delete from sys_log_operate where oper_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(operIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// CleanSysLogOperate 清空操作日志 +func (r *SysLogOperateImpl) CleanSysLogOperate() error { + sql := "truncate table sys_log_operate" + _, err := datasource.ExecDB("", sql, []any{}) + return err +} diff --git a/src/modules/system/repository/sys_menu.go b/src/modules/system/repository/sys_menu.go new file mode 100644 index 0000000..6b5f4ea --- /dev/null +++ b/src/modules/system/repository/sys_menu.go @@ -0,0 +1,36 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysMenu 菜单表 数据层接口 +type ISysMenu interface { + // SelectMenuList 查询系统菜单列表 + SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu + + // SelectMenuPermsByUserId 根据用户ID查询权限 + SelectMenuPermsByUserId(userId string) []string + + // SelectMenuTreeByUserId 根据用户ID查询菜单 + SelectMenuTreeByUserId(userId string) []model.SysMenu + + // SelectMenuListByRoleId 根据角色ID查询菜单树信息 + SelectMenuListByRoleId(roleId string, menuCheckStrictly bool) []string + + // SelectMenuByIds 根据菜单ID查询信息 + SelectMenuByIds(menuIds []string) []model.SysMenu + + // HasChildByMenuIdAndStatus 存在菜单子节点数量与状态 + HasChildByMenuIdAndStatus(menuId, status string) int64 + + // InsertMenu 新增菜单信息 + InsertMenu(sysMenu model.SysMenu) string + + // UpdateMenu 修改菜单信息 + UpdateMenu(sysMenu model.SysMenu) int64 + + // DeleteMenuById 删除菜单管理信息 + DeleteMenuById(menuId string) int64 + + // CheckUniqueMenu 校验菜单是否唯一 + CheckUniqueMenu(sysMenu model.SysMenu) string +} diff --git a/src/modules/system/repository/sys_menu.impl.go b/src/modules/system/repository/sys_menu.impl.go new file mode 100644 index 0000000..4127c90 --- /dev/null +++ b/src/modules/system/repository/sys_menu.impl.go @@ -0,0 +1,476 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/constants/menu" + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysMenuImpl 结构体 +var NewSysMenuImpl = &SysMenuImpl{ + selectSql: `select + m.menu_id, m.menu_name, m.parent_id, m.menu_sort, m.path, m.component, m.is_frame, m.is_cache, m.menu_type, m.visible, m.status, ifnull(m.perms,'') as perms, m.icon, m.create_time, m.remark + from sys_menu m`, + + selectSqlByUser: `select distinct + m.menu_id, m.menu_name, m.parent_id, m.menu_sort, m.path, m.component, m.is_frame, m.is_cache, m.menu_type, m.visible, m.status, ifnull(m.perms,'') as perms, m.icon, m.create_time, m.remark + from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + left join sys_user_role ur on rm.role_id = ur.role_id + left join sys_role ro on ur.role_id = ro.role_id`, + + resultMap: map[string]string{ + "menu_id": "MenuID", + "menu_name": "MenuName", + "parent_name": "ParentName", + "parent_id": "ParentID", + "path": "Path", + "menu_sort": "MenuSort", + "component": "Component", + "is_frame": "IsFrame", + "is_cache": "IsCache", + "menu_type": "MenuType", + "visible": "Visible", + "status": "Status", + "perms": "Perms", + "icon": "Icon", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, +} + +// SysMenuImpl 菜单表 数据层处理 +type SysMenuImpl struct { + // 查询视图对象SQL + selectSql string + // 查询视图用户对象SQL + selectSqlByUser string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysMenuImpl) convertResultRows(rows []map[string]any) []model.SysMenu { + arr := make([]model.SysMenu, 0) + for _, row := range rows { + sysMenu := model.SysMenu{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysMenu, keyMapper, value) + } + } + arr = append(arr, sysMenu) + } + return arr +} + +// SelectMenuList 查询系统菜单列表 +func (r *SysMenuImpl) SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu { + // 查询条件拼接 + var conditions []string + var params []any + if sysMenu.MenuName != "" { + conditions = append(conditions, "m.menu_name like concat(?, '%')") + params = append(params, sysMenu.MenuName) + } + if sysMenu.Visible != "" { + conditions = append(conditions, "m.visible = ?") + params = append(params, sysMenu.Visible) + } + if sysMenu.Status != "" { + conditions = append(conditions, "m.status = ?") + params = append(params, sysMenu.Status) + } + + fromSql := r.selectSql + + // 个人菜单 + if userId != "*" { + fromSql = r.selectSqlByUser + conditions = append(conditions, "ur.user_id = ?") + params = append(params, userId) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by m.parent_id, m.menu_sort" + querySql := fromSql + whereSql + orderSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysMenu{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectMenuPermsByUserId 根据用户ID查询权限 +func (r *SysMenuImpl) SelectMenuPermsByUserId(userId string) []string { + querySql := `select distinct m.perms as 'str' from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + left join sys_user_role ur on rm.role_id = ur.role_id + left join sys_role r on r.role_id = ur.role_id + where m.status = '1' and m.perms != '' and r.status = '1' and ur.user_id = ? ` + + // 查询结果 + results, err := datasource.RawDB("", querySql, []any{userId}) + if err != nil { + logger.Errorf("query err => %v", err) + return []string{} + } + + // 读取结果 + rows := make([]string, 0) + for _, m := range results { + rows = append(rows, fmt.Sprintf("%v", m["str"])) + } + return rows +} + +// SelectMenuTreeByUserId 根据用户ID查询菜单 +func (r *SysMenuImpl) SelectMenuTreeByUserId(userId string) []model.SysMenu { + var params []any + var querySql string + + if userId == "*" { + // 管理员全部菜单 + querySql = r.selectSql + ` where + m.menu_type in (?,?) and m.status = '1' + order by m.parent_id, m.menu_sort` + params = append(params, menu.TYPE_DIR) + params = append(params, menu.TYPE_MENU) + } else { + // 用户ID权限 + querySql = r.selectSqlByUser + ` where + m.menu_type in (?, ?) and m.status = '1' + and ur.user_id = ? and ro.status = '1' + order by m.parent_id, m.menu_sort` + params = append(params, menu.TYPE_DIR) + params = append(params, menu.TYPE_MENU) + params = append(params, userId) + } + + // 查询结果 + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysMenu{} + } + + return r.convertResultRows(results) +} + +// SelectMenuListByRoleId 根据角色ID查询菜单树信息 +func (r *SysMenuImpl) SelectMenuListByRoleId(roleId string, menuCheckStrictly bool) []string { + querySql := `select m.menu_id as 'str' from sys_menu m + left join sys_role_menu rm on m.menu_id = rm.menu_id + where rm.role_id = ? ` + var params []any + params = append(params, roleId) + // 展开 + if menuCheckStrictly { + querySql += ` and m.menu_id not in + (select m.parent_id from sys_menu m + inner join sys_role_menu rm on m.menu_id = rm.menu_id + and rm.role_id = ?) ` + params = append(params, roleId) + } + + // 查询结果 + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []string{} + } + + if len(results) > 0 { + ids := make([]string, 0) + for _, v := range results { + ids = append(ids, fmt.Sprintf("%v", v["str"])) + } + return ids + } + return []string{} +} + +// SelectMenuByIds 根据菜单ID查询信息 +func (r *SysMenuImpl) SelectMenuByIds(menuIds []string) []model.SysMenu { + placeholder := repo.KeyPlaceholderByQuery(len(menuIds)) + querySql := r.selectSql + " where m.menu_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(menuIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysMenu{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// HasChildByMenuIdAndStatus 存在菜单子节点数量与状态 +func (r *SysMenuImpl) HasChildByMenuIdAndStatus(menuId, status string) int64 { + querySql := "select count(1) as 'total' from sys_menu where parent_id = ?" + params := []any{menuId} + + // 菜单状态 + if status != "" { + querySql += " and status = ? and menu_type in (?, ?) " + params = append(params, status) + params = append(params, menu.TYPE_DIR) + params = append(params, menu.TYPE_MENU) + } + + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// InsertMenu 新增菜单信息 +func (r *SysMenuImpl) InsertMenu(sysMenu model.SysMenu) string { + // 参数拼接 + params := make(map[string]any) + if sysMenu.MenuID != "" { + params["menu_id"] = sysMenu.MenuID + } + if sysMenu.ParentID != "" { + params["parent_id"] = sysMenu.ParentID + } + if sysMenu.MenuName != "" { + params["menu_name"] = sysMenu.MenuName + } + if sysMenu.MenuSort > 0 { + params["menu_sort"] = sysMenu.MenuSort + } + if sysMenu.Path != "" { + params["path"] = sysMenu.Path + } + if sysMenu.Component != "" { + params["component"] = sysMenu.Component + } + if sysMenu.IsFrame != "" { + params["is_frame"] = sysMenu.IsFrame + } + if sysMenu.IsCache != "" { + params["is_cache"] = sysMenu.IsCache + } + if sysMenu.MenuType != "" { + params["menu_type"] = sysMenu.MenuType + } + if sysMenu.Visible != "" { + params["visible"] = sysMenu.Visible + } + if sysMenu.Status != "" { + params["status"] = sysMenu.Status + } + if sysMenu.Perms != "" { + params["perms"] = sysMenu.Perms + } + if sysMenu.Icon != "" { + params["icon"] = sysMenu.Icon + } else { + params["icon"] = "#" + } + if sysMenu.Remark != "" { + params["remark"] = sysMenu.Remark + } + if sysMenu.CreateBy != "" { + params["create_by"] = sysMenu.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 根据菜单类型重置参数 + if sysMenu.MenuType == menu.TYPE_BUTTON { + params["component"] = "" + params["path"] = "" + params["icon"] = "#" + params["is_cache"] = "1" + params["is_frame"] = "1" + params["visible"] = "1" + params["status"] = "1" + } + if sysMenu.MenuType == menu.TYPE_DIR { + params["component"] = "" + params["perms"] = "" + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_menu (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateMenu 修改菜单信息 +func (r *SysMenuImpl) UpdateMenu(sysMenu model.SysMenu) int64 { + // 参数拼接 + params := make(map[string]any) + if sysMenu.MenuID != "" { + params["menu_id"] = sysMenu.MenuID + } + if sysMenu.ParentID != "" { + params["parent_id"] = sysMenu.ParentID + } + if sysMenu.MenuName != "" { + params["menu_name"] = sysMenu.MenuName + } + if sysMenu.MenuSort > 0 { + params["menu_sort"] = sysMenu.MenuSort + } + if sysMenu.Path != "" { + params["path"] = sysMenu.Path + } + if sysMenu.Component != "" { + params["component"] = sysMenu.Component + } + if sysMenu.IsFrame != "" { + params["is_frame"] = sysMenu.IsFrame + } + if sysMenu.IsCache != "" { + params["is_cache"] = sysMenu.IsCache + } + if sysMenu.MenuType != "" { + params["menu_type"] = sysMenu.MenuType + } + if sysMenu.Visible != "" { + params["visible"] = sysMenu.Visible + } + if sysMenu.Status != "" { + params["status"] = sysMenu.Status + } + if sysMenu.Perms != "" { + params["perms"] = sysMenu.Perms + } + if sysMenu.Icon != "" { + params["icon"] = sysMenu.Icon + } else { + params["icon"] = "#" + } + if sysMenu.Remark != "" { + params["remark"] = sysMenu.Remark + } + if sysMenu.UpdateBy != "" { + params["update_by"] = sysMenu.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 根据菜单类型重置参数 + if sysMenu.MenuType == menu.TYPE_BUTTON { + params["component"] = "" + params["path"] = "" + params["icon"] = "#" + params["is_cache"] = "1" + params["is_frame"] = "1" + params["visible"] = "1" + params["status"] = "1" + } + if sysMenu.MenuType == menu.TYPE_DIR { + params["component"] = "" + params["perms"] = "" + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_menu set " + strings.Join(keys, ",") + " where menu_id = ?" + + // 执行更新 + values = append(values, sysMenu.MenuID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteMenuById 删除菜单管理信息 +func (r *SysMenuImpl) DeleteMenuById(menuId string) int64 { + sql := "delete from sys_menu where menu_id = ?" + results, err := datasource.ExecDB("", sql, []any{menuId}) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// CheckUniqueMenu 校验菜单是否唯一 +func (r *SysMenuImpl) CheckUniqueMenu(sysMenu model.SysMenu) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysMenu.MenuName != "" { + conditions = append(conditions, "menu_name = ?") + params = append(params, sysMenu.MenuName) + } + if sysMenu.ParentID != "" { + conditions = append(conditions, "parent_id = ?") + params = append(params, sysMenu.ParentID) + } + if sysMenu.Path != "" { + conditions = append(conditions, "path = ?") + params = append(params, sysMenu.Path) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + if whereSql == "" { + return "" + } + + // 查询数据 + querySql := "select menu_id as 'str' from sys_menu " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} diff --git a/src/modules/system/repository/sys_notice.go b/src/modules/system/repository/sys_notice.go new file mode 100644 index 0000000..cacb311 --- /dev/null +++ b/src/modules/system/repository/sys_notice.go @@ -0,0 +1,24 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysNotice 通知公告表 数据层接口 +type ISysNotice interface { + // SelectNoticePage 分页查询公告列表 + SelectNoticePage(query map[string]any) map[string]any + + // SelectNoticeList 查询公告列表 + SelectNoticeList(sysNotice model.SysNotice) []model.SysNotice + + // SelectNoticeById 查询公告信息 + SelectNoticeByIds(noticeIds []string) []model.SysNotice + + // InsertNotice 新增公告 + InsertNotice(sysNotice model.SysNotice) string + + // UpdateNotice 修改公告 + UpdateNotice(sysNotice model.SysNotice) int64 + + // DeleteNoticeByIds 批量删除公告信息 + DeleteNoticeByIds(noticeIds []string) int64 +} diff --git a/src/modules/system/repository/sys_notice.impl.go b/src/modules/system/repository/sys_notice.impl.go new file mode 100644 index 0000000..f565ddb --- /dev/null +++ b/src/modules/system/repository/sys_notice.impl.go @@ -0,0 +1,297 @@ +package repository + +import ( + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysNoticeImpl 结构体 +var NewSysNoticeImpl = &SysNoticeImpl{ + selectSql: `select + notice_id, notice_title, notice_type, notice_content, status, del_flag, + create_by, create_time, update_by, update_time, remark from sys_notice`, + + resultMap: map[string]string{ + "notice_id": "NoticeID", + "notice_title": "NoticeTitle", + "notice_type": "NoticeType", + "notice_content": "NoticeContent", + "status": "Status", + "del_flag": "DelFlag", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, +} + +// SysNoticeImpl 通知公告表 数据层处理 +type SysNoticeImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysNoticeImpl) convertResultRows(rows []map[string]any) []model.SysNotice { + arr := make([]model.SysNotice, 0) + for _, row := range rows { + sysNotice := model.SysNotice{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysNotice, keyMapper, value) + } + } + arr = append(arr, sysNotice) + } + return arr +} + +// SelectNoticePage 分页查询公告列表 +func (r *SysNoticeImpl) SelectNoticePage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["noticeTitle"]; ok && v != "" { + conditions = append(conditions, "notice_title like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["noticeType"]; ok && v != "" { + conditions = append(conditions, "notice_type = ?") + params = append(params, v) + } + if v, ok := query["createBy"]; ok && v != "" { + conditions = append(conditions, "create_by like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "create_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "create_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + + // 构建查询条件语句 + whereSql := " where del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysNotice{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_notice" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectNoticeList 查询公告列表 +func (r *SysNoticeImpl) SelectNoticeList(sysNotice model.SysNotice) []model.SysNotice { + // 查询条件拼接 + var conditions []string + var params []any + if sysNotice.NoticeTitle != "" { + conditions = append(conditions, "notice_title like concat(?, '%')") + params = append(params, sysNotice.NoticeTitle) + } + if sysNotice.NoticeType != "" { + conditions = append(conditions, "notice_type = ?") + params = append(params, sysNotice.NoticeType) + } + if sysNotice.CreateBy != "" { + conditions = append(conditions, "create_by like concat(?, '%')") + params = append(params, sysNotice.CreateBy) + } + if sysNotice.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysNotice.Status) + } + + // 构建查询条件语句 + whereSql := " where del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := r.selectSql + whereSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysNotice{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectNoticeByIds 查询公告信息 +func (r *SysNoticeImpl) SelectNoticeByIds(noticeIds []string) []model.SysNotice { + placeholder := repo.KeyPlaceholderByQuery(len(noticeIds)) + querySql := r.selectSql + " where notice_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(noticeIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysNotice{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// InsertNotice 新增公告 +func (r *SysNoticeImpl) InsertNotice(sysNotice model.SysNotice) string { + // 参数拼接 + params := make(map[string]any) + if sysNotice.NoticeTitle != "" { + params["notice_title"] = sysNotice.NoticeTitle + } + if sysNotice.NoticeType != "" { + params["notice_type"] = sysNotice.NoticeType + } + if sysNotice.NoticeContent != "" { + params["notice_content"] = sysNotice.NoticeContent + } + if sysNotice.Status != "" { + params["status"] = sysNotice.Status + } + if sysNotice.Remark != "" { + params["remark"] = sysNotice.Remark + } + if sysNotice.CreateBy != "" { + params["create_by"] = sysNotice.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_notice (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateNotice 修改公告 +func (r *SysNoticeImpl) UpdateNotice(sysNotice model.SysNotice) int64 { + // 参数拼接 + params := make(map[string]any) + if sysNotice.NoticeTitle != "" { + params["notice_title"] = sysNotice.NoticeTitle + } + if sysNotice.NoticeType != "" { + params["notice_type"] = sysNotice.NoticeType + } + if sysNotice.NoticeContent != "" { + params["notice_content"] = sysNotice.NoticeContent + } + if sysNotice.Status != "" { + params["status"] = sysNotice.Status + } + if sysNotice.Remark != "" { + params["remark"] = sysNotice.Remark + } + if sysNotice.UpdateBy != "" { + params["update_by"] = sysNotice.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_notice set " + strings.Join(keys, ",") + " where notice_id = ?" + + // 执行更新 + values = append(values, sysNotice.NoticeID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteNoticeByIds 批量删除公告信息 +func (r *SysNoticeImpl) DeleteNoticeByIds(noticeIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(noticeIds)) + sql := "update sys_notice set del_flag = '1' where notice_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(noticeIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("update err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_post.go b/src/modules/system/repository/sys_post.go new file mode 100644 index 0000000..98bf3e4 --- /dev/null +++ b/src/modules/system/repository/sys_post.go @@ -0,0 +1,30 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysPost 岗位表 数据层接口 +type ISysPost interface { + // SelectPostPage 查询岗位分页数据集合 + SelectPostPage(query map[string]any) map[string]any + + // SelectPostList 查询岗位数据集合 + SelectPostList(sysPost model.SysPost) []model.SysPost + + // SelectPostByIds 通过岗位ID查询岗位信息 + SelectPostByIds(postIds []string) []model.SysPost + + // SelectPostListByUserId 根据用户ID获取岗位选择框列表 + SelectPostListByUserId(userId string) []model.SysPost + + // DeletePostByIds 批量删除岗位信息 + DeletePostByIds(postIds []string) int64 + + // UpdatePost 修改岗位信息 + UpdatePost(sysPost model.SysPost) int64 + + // InsertPost 新增岗位信息 + InsertPost(sysPost model.SysPost) string + + // CheckUniquePost 校验岗位唯一 + CheckUniquePost(sysPost model.SysPost) string +} diff --git a/src/modules/system/repository/sys_post.impl.go b/src/modules/system/repository/sys_post.impl.go new file mode 100644 index 0000000..d1dcda5 --- /dev/null +++ b/src/modules/system/repository/sys_post.impl.go @@ -0,0 +1,323 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysPostImpl 结构体 +var NewSysPostImpl = &SysPostImpl{ + selectSql: `select + post_id, post_code, post_name, post_sort, status, create_by, create_time, remark + from sys_post`, + + resultMap: map[string]string{ + "post_id": "PostID", + "post_code": "PostCode", + "post_name": "PostName", + "post_sort": "PostSort", + "status": "Status", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, +} + +// SysPostImpl 岗位表 数据层处理 +type SysPostImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysPostImpl) convertResultRows(rows []map[string]any) []model.SysPost { + arr := make([]model.SysPost, 0) + for _, row := range rows { + sysPost := model.SysPost{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysPost, keyMapper, value) + } + } + arr = append(arr, sysPost) + } + return arr +} + +// SelectPostPage 查询岗位分页数据集合 +func (r *SysPostImpl) SelectPostPage(query map[string]any) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["postCode"]; ok && v != "" { + conditions = append(conditions, "post_code like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["postName"]; ok && v != "" { + conditions = append(conditions, "post_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "status = ?") + params = append(params, v) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysPost{}, + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_post" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by post_sort limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectPostList 查询岗位数据集合 +func (r *SysPostImpl) SelectPostList(sysPost model.SysPost) []model.SysPost { + // 查询条件拼接 + var conditions []string + var params []any + if sysPost.PostCode != "" { + conditions = append(conditions, "post_code like concat(?, '%')") + params = append(params, sysPost.PostCode) + } + if sysPost.PostName != "" { + conditions = append(conditions, "post_name like concat(?, '%')") + params = append(params, sysPost.PostName) + } + if sysPost.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysPost.Status) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by post_sort" + querySql := r.selectSql + whereSql + orderSql + rows, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysPost{} + } + return r.convertResultRows(rows) +} + +// SelectPostByIds 通过岗位ID查询岗位信息 +func (r *SysPostImpl) SelectPostByIds(postIds []string) []model.SysPost { + placeholder := repo.KeyPlaceholderByQuery(len(postIds)) + querySql := r.selectSql + " where post_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(postIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysPost{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// SelectPostListByUserId 根据用户ID获取岗位选择框列表 +func (r *SysPostImpl) SelectPostListByUserId(userId string) []model.SysPost { + // 查询数据 + querySql := `select distinct + p.post_id, p.post_name, p.post_code + from sys_post p + left join sys_user_post up on up.post_id = p.post_id + left join sys_user u on u.user_id = up.user_id + where u.user_id = ? order by p.post_id` + rows, err := datasource.RawDB("", querySql, []any{userId}) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysPost{} + } + return r.convertResultRows(rows) +} + +// DeletePostByIds 批量删除岗位信息 +func (r *SysPostImpl) DeletePostByIds(postIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(postIds)) + sql := "delete from sys_post where post_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(postIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// UpdatePost 修改岗位信息 +func (r *SysPostImpl) UpdatePost(sysPost model.SysPost) int64 { + // 参数拼接 + params := make(map[string]any) + if sysPost.PostCode != "" { + params["post_code"] = sysPost.PostCode + } + if sysPost.PostName != "" { + params["post_name"] = sysPost.PostName + } + if sysPost.PostSort > 0 { + params["post_sort"] = sysPost.PostSort + } + if sysPost.Status != "" { + params["status"] = sysPost.Status + } + if sysPost.Remark != "" { + params["remark"] = sysPost.Remark + } + if sysPost.UpdateBy != "" { + params["update_by"] = sysPost.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_post set " + strings.Join(keys, ",") + " where post_id = ?" + + // 执行更新 + values = append(values, sysPost.PostID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// InsertPost 新增岗位信息 +func (r *SysPostImpl) InsertPost(sysPost model.SysPost) string { + // 参数拼接 + params := make(map[string]any) + if sysPost.PostID != "" { + params["post_id"] = sysPost.PostID + } + if sysPost.PostCode != "" { + params["post_code"] = sysPost.PostCode + } + if sysPost.PostName != "" { + params["post_name"] = sysPost.PostName + } + if sysPost.PostSort > 0 { + params["post_sort"] = sysPost.PostSort + } + if sysPost.Status != "" { + params["status"] = sysPost.Status + } + if sysPost.Remark != "" { + params["remark"] = sysPost.Remark + } + if sysPost.CreateBy != "" { + params["create_by"] = sysPost.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_post (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// CheckUniquePost 校验岗位唯一 +func (r *SysPostImpl) CheckUniquePost(sysPost model.SysPost) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysPost.PostName != "" { + conditions = append(conditions, "post_name= ?") + params = append(params, sysPost.PostName) + } + if sysPost.PostCode != "" { + conditions = append(conditions, "post_code = ?") + params = append(params, sysPost.PostCode) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select post_id as 'str' from sys_post " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} diff --git a/src/modules/system/repository/sys_role.go b/src/modules/system/repository/sys_role.go new file mode 100644 index 0000000..cf259b5 --- /dev/null +++ b/src/modules/system/repository/sys_role.go @@ -0,0 +1,30 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysRole 角色表 数据层接口 +type ISysRole interface { + // SelectRolePage 根据条件分页查询角色数据 + SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectRoleList 根据条件查询角色数据 + SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole + + // SelectRoleListByUserId 根据用户ID获取角色选择框列表 + SelectRoleListByUserId(userId string) []model.SysRole + + // SelectRoleByIds 通过角色ID查询角色 + SelectRoleByIds(roleIds []string) []model.SysRole + + // UpdateRole 修改角色信息 + UpdateRole(sysRole model.SysRole) int64 + + // InsertRole 新增角色信息 + InsertRole(sysRole model.SysRole) string + + // DeleteRoleByIds 批量删除角色信息 + DeleteRoleByIds(roleIds []string) int64 + + // CheckUniqueRole 校验角色是否唯一 + CheckUniqueRole(sysRole model.SysRole) string +} diff --git a/src/modules/system/repository/sys_role.impl.go b/src/modules/system/repository/sys_role.impl.go new file mode 100644 index 0000000..ee4a2ae --- /dev/null +++ b/src/modules/system/repository/sys_role.impl.go @@ -0,0 +1,382 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysRoleImpl 结构体 +var NewSysRoleImpl = &SysRoleImpl{ + selectSql: `select distinct + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, + r.dept_check_strictly, r.status, r.del_flag, r.create_time, r.remark + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id`, + + resultMap: map[string]string{ + "role_id": "RoleID", + "role_name": "RoleName", + "role_key": "RoleKey", + "role_sort": "RoleSort", + "data_scope": "DataScope", + "menu_check_strictly": "MenuCheckStrictly", + "dept_check_strictly": "DeptCheckStrictly", + "status": "Status", + "del_flag": "DelFlag", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, +} + +// SysRoleImpl 角色表 数据层处理 +type SysRoleImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysRoleImpl) convertResultRows(rows []map[string]any) []model.SysRole { + arr := make([]model.SysRole, 0) + for _, row := range rows { + sysRole := model.SysRole{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysRole, keyMapper, value) + } + } + arr = append(arr, sysRole) + } + return arr +} + +// SelectRolePage 根据条件分页查询角色数据 +func (r *SysRoleImpl) SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["roleId"]; ok && v != "" { + conditions = append(conditions, "r.role_id = ?") + params = append(params, v) + } + if v, ok := query["roleName"]; ok && v != "" { + conditions = append(conditions, "r.role_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["roleKey"]; ok && v != "" { + conditions = append(conditions, "r.role_key like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "r.status = ?") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "r.create_time >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "r.create_time <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + if v, ok := query["deptId"]; ok && v != "" { + conditions = append(conditions, `(u.dept_id = ? or u.dept_id in ( + select t.dept_id from sys_dept t where find_in_set(?, ancestors) + ))`) + params = append(params, v) + params = append(params, v) + } + + // 构建查询条件语句 + whereSql := " where r.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysRole{}, + } + + // 查询数量 长度为0直接返回 + totalSql := `select count(distinct r.role_id) as 'total' from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id` + totalRows, err := datasource.RawDB("", totalSql+whereSql+dataScopeSQL, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " order by r.role_sort asc limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := r.selectSql + whereSql + dataScopeSQL + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectRoleList 根据条件查询角色数据 +func (r *SysRoleImpl) SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole { + // 查询条件拼接 + var conditions []string + var params []any + if sysRole.RoleID != "" { + conditions = append(conditions, "r.role_id = ?") + params = append(params, sysRole.RoleID) + } + if sysRole.RoleKey != "" { + conditions = append(conditions, "r.role_key like concat(?, '%')") + params = append(params, sysRole.RoleKey) + } + if sysRole.RoleName != "" { + conditions = append(conditions, "r.role_name like concat(?, '%')") + params = append(params, sysRole.RoleName) + } + if sysRole.Status != "" { + conditions = append(conditions, "r.status = ?") + params = append(params, sysRole.Status) + } + + // 构建查询条件语句 + whereSql := " where r.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by r.role_sort" + querySql := r.selectSql + whereSql + dataScopeSQL + orderSql + rows, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysRole{} + } + return r.convertResultRows(rows) +} + +// SelectRoleListByUserId 根据用户ID获取角色选择框列表 +func (r *SysRoleImpl) SelectRoleListByUserId(userId string) []model.SysRole { + querySql := r.selectSql + " where r.del_flag = '0' and ur.user_id = ?" + results, err := datasource.RawDB("", querySql, []any{userId}) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysRole{} + } + return r.convertResultRows(results) +} + +// SelectRoleByIds 通过角色ID查询角色 +func (r *SysRoleImpl) SelectRoleByIds(roleIds []string) []model.SysRole { + placeholder := repo.KeyPlaceholderByQuery(len(roleIds)) + querySql := r.selectSql + " where r.role_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(roleIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysRole{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// UpdateRole 修改角色信息 +func (r *SysRoleImpl) UpdateRole(sysRole model.SysRole) int64 { + // 参数拼接 + params := make(map[string]any) + if sysRole.RoleName != "" { + params["role_name"] = sysRole.RoleName + } + if sysRole.RoleKey != "" { + params["role_key"] = sysRole.RoleKey + } + if sysRole.RoleSort > 0 { + params["role_sort"] = sysRole.RoleSort + } + if sysRole.DataScope != "" { + params["data_scope"] = sysRole.DataScope + } + if sysRole.MenuCheckStrictly != "" { + params["menu_check_strictly"] = sysRole.MenuCheckStrictly + } + if sysRole.DeptCheckStrictly != "" { + params["dept_check_strictly"] = sysRole.DeptCheckStrictly + } + if sysRole.Status != "" { + params["status"] = sysRole.Status + } + if sysRole.Remark != "" { + params["remark"] = sysRole.Remark + } + if sysRole.UpdateBy != "" { + params["update_by"] = sysRole.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_role set " + strings.Join(keys, ",") + " where role_id = ?" + + // 执行更新 + values = append(values, sysRole.RoleID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// InsertRole 新增角色信息 +func (r *SysRoleImpl) InsertRole(sysRole model.SysRole) string { + // 参数拼接 + params := make(map[string]any) + if sysRole.RoleID != "" { + params["role_id"] = sysRole.RoleID + } + if sysRole.RoleName != "" { + params["role_name"] = sysRole.RoleName + } + if sysRole.RoleKey != "" { + params["role_key"] = sysRole.RoleKey + } + if sysRole.RoleSort > 0 { + params["role_sort"] = sysRole.RoleSort + } + if sysRole.DataScope != "" { + params["data_scope"] = sysRole.DataScope + } + if sysRole.MenuCheckStrictly != "" { + params["menu_check_strictly"] = sysRole.MenuCheckStrictly + } + if sysRole.DeptCheckStrictly != "" { + params["dept_check_strictly"] = sysRole.DeptCheckStrictly + } + if sysRole.Status != "" { + params["status"] = sysRole.Status + } + if sysRole.Remark != "" { + params["remark"] = sysRole.Remark + } + if sysRole.CreateBy != "" { + params["create_by"] = sysRole.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_role (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// DeleteRoleByIds 批量删除角色信息 +func (r *SysRoleImpl) DeleteRoleByIds(roleIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(roleIds)) + sql := "update sys_role set del_flag = '1' where role_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(roleIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// CheckUniqueRole 校验角色是否唯一 +func (r *SysRoleImpl) CheckUniqueRole(sysRole model.SysRole) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysRole.RoleName != "" { + conditions = append(conditions, "r.role_name = ?") + params = append(params, sysRole.RoleName) + } + if sysRole.RoleKey != "" { + conditions = append(conditions, "r.role_key = ?") + params = append(params, sysRole.RoleKey) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select role_id as 'str' from sys_role r " + whereSql + " and r.del_flag = '0' limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} diff --git a/src/modules/system/repository/sys_role_dept.go b/src/modules/system/repository/sys_role_dept.go new file mode 100644 index 0000000..28c034f --- /dev/null +++ b/src/modules/system/repository/sys_role_dept.go @@ -0,0 +1,15 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysRoleDept 角色与部门关联表 数据层接口 +type ISysRoleDept interface { + // DeleteRoleDept 批量删除角色部门关联信息 + DeleteRoleDept(roleIds []string) int64 + + // DeleteDeptRole 批量删除部门角色关联信息 + DeleteDeptRole(deptIds []string) int64 + + // BatchRoleDept 批量新增角色部门信息 + BatchRoleDept(sysRoleDepts []model.SysRoleDept) int64 +} diff --git a/src/modules/system/repository/sys_role_dept.impl.go b/src/modules/system/repository/sys_role_dept.impl.go new file mode 100644 index 0000000..c6e3ce9 --- /dev/null +++ b/src/modules/system/repository/sys_role_dept.impl.go @@ -0,0 +1,58 @@ +package repository + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysRoleDeptImpl 结构体 +var NewSysRoleDeptImpl = &SysRoleDeptImpl{} + +// SysRoleDeptImpl 角色与部门关联表 数据层处理 +type SysRoleDeptImpl struct{} + +// DeleteRoleDept 批量删除角色部门关联信息 +func (r *SysRoleDeptImpl) DeleteRoleDept(roleIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(roleIds)) + sql := "delete from sys_role_dept where role_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(roleIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteDeptRole 批量删除部门角色关联信息 +func (r *SysRoleDeptImpl) DeleteDeptRole(deptIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(deptIds)) + sql := "delete from sys_role_dept where dept_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(deptIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// BatchRoleDept 批量新增角色部门信息 +func (r *SysRoleDeptImpl) BatchRoleDept(sysRoleDepts []model.SysRoleDept) int64 { + keyValues := make([]string, 0) + for _, item := range sysRoleDepts { + keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.RoleID, item.DeptID)) + } + sql := "insert into sys_role_dept(role_id, dept_id) values " + strings.Join(keyValues, ",") + results, err := datasource.ExecDB("", sql, nil) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_role_menu.go b/src/modules/system/repository/sys_role_menu.go new file mode 100644 index 0000000..5a2def7 --- /dev/null +++ b/src/modules/system/repository/sys_role_menu.go @@ -0,0 +1,18 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysRoleMenu 角色与菜单关联表 数据层接口 +type ISysRoleMenu interface { + // CheckMenuExistRole 查询菜单分配给角色使用数量 + CheckMenuExistRole(menuId string) int64 + + // DeleteRoleMenu 批量删除角色和菜单关联 + DeleteRoleMenu(roleIds []string) int64 + + // DeleteMenuRole 批量删除菜单和角色关联 + DeleteMenuRole(menuIds []string) int64 + + // BatchRoleMenu 批量新增角色菜单信息 + BatchRoleMenu(sysRoleMenus []model.SysRoleMenu) int64 +} diff --git a/src/modules/system/repository/sys_role_menu.impl.go b/src/modules/system/repository/sys_role_menu.impl.go new file mode 100644 index 0000000..7c7ecf1 --- /dev/null +++ b/src/modules/system/repository/sys_role_menu.impl.go @@ -0,0 +1,73 @@ +package repository + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysRoleMenuImpl 结构体 +var NewSysRoleMenuImpl = &SysRoleMenuImpl{} + +// SysRoleMenuImpl 角色与菜单关联表 数据层处理 +type SysRoleMenuImpl struct{} + +// CheckMenuExistRole 查询菜单分配给角色使用数量 +func (r *SysRoleMenuImpl) CheckMenuExistRole(menuId string) int64 { + querySql := "select count(1) as 'total' from sys_role_menu where menu_id = ?" + results, err := datasource.RawDB("", querySql, []any{menuId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// DeleteRoleMenu 批量删除角色和菜单关联 +func (r *SysRoleMenuImpl) DeleteRoleMenu(roleIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(roleIds)) + sql := "delete from sys_role_menu where role_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(roleIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteMenuRole 批量删除菜单和角色关联 +func (r *SysRoleMenuImpl) DeleteMenuRole(menuIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(menuIds)) + sql := "delete from sys_role_menu where menu_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(menuIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// BatchRoleMenu 批量新增角色菜单信息 +func (r *SysRoleMenuImpl) BatchRoleMenu(sysRoleMenus []model.SysRoleMenu) int64 { + keyValues := make([]string, 0) + for _, item := range sysRoleMenus { + keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.RoleID, item.MenuID)) + } + sql := "insert into sys_role_menu(role_id, menu_id) values " + strings.Join(keyValues, ",") + results, err := datasource.ExecDB("", sql, nil) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_user.go b/src/modules/system/repository/sys_user.go new file mode 100644 index 0000000..b79ab0e --- /dev/null +++ b/src/modules/system/repository/sys_user.go @@ -0,0 +1,33 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysUser 用户表 数据层接口 +type ISysUser interface { + // SelectUserPage 根据条件分页查询用户列表 + SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectAllocatedPage 根据条件分页查询分配用户角色列表 + SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectUserList 根据条件查询用户列表 + SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser + + // SelectUserByIds 通过用户ID查询用户 + SelectUserByIds(userIds []string) []model.SysUser + + // SelectUserByUserName 通过用户登录账号查询用户 + SelectUserByUserName(userName string) model.SysUser + + // InsertUser 新增用户信息 + InsertUser(sysUser model.SysUser) string + + // UpdateUser 修改用户信息 + UpdateUser(sysUser model.SysUser) int64 + + // DeleteUserByIds 批量删除用户信息 + DeleteUserByIds(userIds []string) int64 + + // CheckUniqueUser 校验用户信息是否唯一 + CheckUniqueUser(sysUser model.SysUser) string +} diff --git a/src/modules/system/repository/sys_user.impl.go b/src/modules/system/repository/sys_user.impl.go new file mode 100644 index 0000000..7116632 --- /dev/null +++ b/src/modules/system/repository/sys_user.impl.go @@ -0,0 +1,580 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/crypto" + "ems.agt/src/framework/utils/date" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysUserImpl 结构体 +var NewSysUserImpl = &SysUserImpl{ + selectSql: `select + u.user_id, u.dept_id, u.user_name, u.nick_name, u.user_type, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id`, + + sysUserMap: map[string]string{ + "user_id": "UserID", + "dept_id": "DeptID", + "user_name": "UserName", + "nick_name": "NickName", + "user_type": "UserType", + "email": "Email", + "phonenumber": "PhoneNumber", + "sex": "Sex", + "avatar": "Avatar", + "password": "Password", + "status": "Status", + "del_flag": "DelFlag", + "login_ip": "LoginIP", + "login_date": "LoginDate", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "remark": "Remark", + }, + + sysDeptMap: map[string]string{ + "dept_id": "DeptID", + "parent_id": "ParentID", + "dept_name": "DeptName", + "ancestors": "Ancestors", + "order_num": "OrderNum", + "leader": "Leader", + "dept_status": "Status", + }, + + sysRoleMap: map[string]string{ + "role_id": "RoleID", + "role_name": "RoleName", + "role_key": "RoleKey", + "role_sort": "RoleSort", + "data_scope": "DataScope", + "role_status": "Status", + }, +} + +// SysUserImpl 用户表 数据层处理 +type SysUserImpl struct { + // 查询视图对象SQL + selectSql string + // 用户信息实体映射 + sysUserMap map[string]string + // 用户部门实体映射 一对一 + sysDeptMap map[string]string + // 用户角色实体映射 一对多 + sysRoleMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysUserImpl) convertResultRows(rows []map[string]any) []model.SysUser { + arr := make([]model.SysUser, 0) + + for _, row := range rows { + sysUser := model.SysUser{} + sysDept := model.SysDept{} + sysRole := model.SysRole{} + sysUser.Roles = []model.SysRole{} + + for key, value := range row { + if keyMapper, ok := r.sysUserMap[key]; ok { + repo.SetFieldValue(&sysUser, keyMapper, value) + } + if keyMapper, ok := r.sysDeptMap[key]; ok { + repo.SetFieldValue(&sysDept, keyMapper, value) + } + if keyMapper, ok := r.sysRoleMap[key]; ok { + repo.SetFieldValue(&sysRole, keyMapper, value) + } + } + + sysUser.Dept = sysDept + if sysRole.RoleKey != "" { + sysUser.Roles = append(sysUser.Roles, sysRole) + } + + one := true + for i, a := range arr { + if a.UserID == sysUser.UserID { + arrUser := &arr[i] + arrUser.Roles = append(arrUser.Roles, sysUser.Roles...) + one = false + break + } + } + if one { + arr = append(arr, sysUser) + } + } + + return arr +} + +// SelectUserPage 根据条件分页查询用户列表 +func (r *SysUserImpl) SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any { + selectUserSql := `select + u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id` + selectUserTotalSql := `select count(distinct u.user_id) as 'total' + from sys_user u left join sys_dept d on u.dept_id = d.dept_id` + + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["userId"]; ok && v != "" { + conditions = append(conditions, "u.user_id = ?") + params = append(params, v) + } + if v, ok := query["userName"]; ok && v != "" { + conditions = append(conditions, "u.user_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "u.status = ?") + params = append(params, v) + } + if v, ok := query["phonenumber"]; ok && v != "" { + conditions = append(conditions, "u.phonenumber like concat(?, '%')") + params = append(params, v) + } + beginTime, ok := query["beginTime"] + if !ok { + beginTime, ok = query["params[beginTime]"] + } + if ok && beginTime != "" { + conditions = append(conditions, "u.login_date >= ?") + beginDate := date.ParseStrToDate(beginTime.(string), date.YYYY_MM_DD) + params = append(params, beginDate.UnixMilli()) + } + endTime, ok := query["endTime"] + if !ok { + endTime, ok = query["params[endTime]"] + } + if ok && endTime != "" { + conditions = append(conditions, "u.login_date <= ?") + endDate := date.ParseStrToDate(endTime.(string), date.YYYY_MM_DD) + params = append(params, endDate.UnixMilli()) + } + if v, ok := query["deptId"]; ok && v != "" { + conditions = append(conditions, "(u.dept_id = ? or u.dept_id in ( select t.dept_id from sys_dept t where find_in_set(?, ancestors) ))") + params = append(params, v) + params = append(params, v) + } + + // 构建查询条件语句 + whereSql := " where u.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysUser{}, + } + + // 查询数量 长度为0直接返回 + totalSql := selectUserTotalSql + whereSql + dataScopeSQL + totalRows, err := datasource.RawDB("", totalSql, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := selectUserSql + whereSql + dataScopeSQL + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return result + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectAllocatedPage 根据条件分页查询分配用户角色列表 +func (r *SysUserImpl) SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any { + // 查询条件拼接 + var conditions []string + var params []any + if v, ok := query["userName"]; ok && v != "" { + conditions = append(conditions, "u.user_name like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["phonenumber"]; ok && v != "" { + conditions = append(conditions, "u.phonenumber like concat(?, '%')") + params = append(params, v) + } + if v, ok := query["status"]; ok && v != "" { + conditions = append(conditions, "u.status = ?") + params = append(params, v) + } + // 分配角色用户 + if allocated, ok := query["allocated"]; ok && allocated != "" { + if roleId, ok := query["roleId"]; ok && roleId != "" { + if parse.Boolean(allocated) { + conditions = append(conditions, "r.role_id = ?") + params = append(params, roleId) + } else { + conditions = append(conditions, `(r.role_id != ? or r.role_id IS NULL) + and u.user_id not in ( + select u.user_id from sys_user u + inner join sys_user_role ur on u.user_id = ur.user_id + and ur.role_id = ? + )`) + params = append(params, roleId) + params = append(params, roleId) + } + + } + } + + // 构建查询条件语句 + whereSql := " where u.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询结果 + result := map[string]any{ + "total": 0, + "rows": []model.SysUser{}, + } + + // 查询数量 长度为0直接返回 + totalSql := `select count(distinct u.user_id) as 'total' from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id` + totalRows, err := datasource.RawDB("", totalSql+whereSql+dataScopeSQL, params) + if err != nil { + logger.Errorf("total err => %v", err) + return result + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return result + } else { + result["total"] = total + } + + // 分页 + pageNum, pageSize := repo.PageNumSize(query["pageNum"], query["pageSize"]) + pageSql := " limit ?,? " + params = append(params, pageNum*pageSize) + params = append(params, pageSize) + + // 查询数据 + querySql := `select distinct + u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, + u.phonenumber, u.status, u.create_time, d.dept_name + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id` + querySql = querySql + whereSql + dataScopeSQL + pageSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + } + + // 转换实体 + result["rows"] = r.convertResultRows(results) + return result +} + +// SelectUserList 根据条件查询用户列表 +func (r *SysUserImpl) SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser { + selectUserSql := `select + u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id` + + // 查询条件拼接 + var conditions []string + var params []any + if sysUser.UserID != "" { + conditions = append(conditions, "u.user_id = ?") + params = append(params, sysUser.UserID) + } + if sysUser.UserName != "" { + conditions = append(conditions, "u.user_name like concat(?, '%')") + params = append(params, sysUser.UserName) + } + if sysUser.Status != "" { + conditions = append(conditions, "u.status = ?") + params = append(params, sysUser.Status) + } + if sysUser.PhoneNumber != "" { + conditions = append(conditions, "u.phonenumber like concat(?, '%')") + params = append(params, sysUser.PhoneNumber) + } + + // 构建查询条件语句 + whereSql := " where u.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询数据 + querySql := selectUserSql + whereSql + dataScopeSQL + rows, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysUser{} + } + return r.convertResultRows(rows) +} + +// SelectUserByIds 通过用户ID查询用户 +func (r *SysUserImpl) SelectUserByIds(userIds []string) []model.SysUser { + placeholder := repo.KeyPlaceholderByQuery(len(userIds)) + querySql := r.selectSql + " where u.del_flag = '0' and u.user_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(userIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysUser{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// SelectUserByUserName 通过用户登录账号查询用户 +func (r *SysUserImpl) SelectUserByUserName(userName string) model.SysUser { + querySql := r.selectSql + " where u.del_flag = '0' and u.user_name = ?" + results, err := datasource.RawDB("", querySql, []any{userName}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysUser{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysUser{} +} + +// InsertUser 新增用户信息 +func (r *SysUserImpl) InsertUser(sysUser model.SysUser) string { + // 参数拼接 + params := make(map[string]any) + if sysUser.UserID != "" { + params["user_id"] = sysUser.UserID + } + if sysUser.DeptID != "" { + params["dept_id"] = sysUser.DeptID + } + if sysUser.UserName != "" { + params["user_name"] = sysUser.UserName + } + if sysUser.NickName != "" { + params["nick_name"] = sysUser.NickName + } + if sysUser.UserType != "" { + params["user_type"] = sysUser.UserType + } + if sysUser.Avatar != "" { + params["avatar"] = sysUser.Avatar + } + if sysUser.Email != "" { + params["email"] = sysUser.Email + } + if sysUser.PhoneNumber != "" { + params["phonenumber"] = sysUser.PhoneNumber + } + if sysUser.Sex != "" { + params["sex"] = sysUser.Sex + } + if sysUser.Password != "" { + password := crypto.BcryptHash(sysUser.Password) + params["password"] = password + } + if sysUser.Status != "" { + params["status"] = sysUser.Status + } + if sysUser.Remark != "" { + params["remark"] = sysUser.Remark + } + if sysUser.CreateBy != "" { + params["create_by"] = sysUser.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_user (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateUser 修改用户信息 +func (r *SysUserImpl) UpdateUser(sysUser model.SysUser) int64 { + // 参数拼接 + params := make(map[string]any) + if sysUser.DeptID != "" { + params["dept_id"] = sysUser.DeptID + } + if sysUser.UserName != "" { + params["user_name"] = sysUser.UserName + } + if sysUser.NickName != "" { + params["nick_name"] = sysUser.NickName + } + if sysUser.UserType != "" { + params["user_type"] = sysUser.UserType + } + if sysUser.Avatar != "" { + params["avatar"] = sysUser.Avatar + } + if sysUser.Email != "" { + if sysUser.Email == "nil" { + params["email"] = "" + } else { + params["email"] = sysUser.Email + } + } + if sysUser.PhoneNumber != "" { + if sysUser.PhoneNumber == "nil" { + params["phonenumber"] = "" + } else { + params["phonenumber"] = sysUser.PhoneNumber + } + } + if sysUser.Sex != "" { + params["sex"] = sysUser.Sex + } + if sysUser.Password != "" { + password := crypto.BcryptHash(sysUser.Password) + params["password"] = password + } + if sysUser.Status != "" { + params["status"] = sysUser.Status + } + if sysUser.Remark != "" { + params["remark"] = sysUser.Remark + } + if sysUser.UpdateBy != "" { + params["update_by"] = sysUser.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + if sysUser.LoginIP != "" { + params["login_ip"] = sysUser.LoginIP + } + if sysUser.LoginDate > 0 { + params["login_date"] = sysUser.LoginDate + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_user set " + strings.Join(keys, ",") + " where user_id = ?" + + // 执行更新 + values = append(values, sysUser.UserID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteUserByIds 批量删除用户信息 +func (r *SysUserImpl) DeleteUserByIds(userIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(userIds)) + sql := "update sys_user set del_flag = '1' where user_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(userIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("update err => %v", err) + return 0 + } + return results +} + +// CheckUniqueUser 校验用户信息是否唯一 +func (r *SysUserImpl) CheckUniqueUser(sysUser model.SysUser) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysUser.UserName != "" { + conditions = append(conditions, "user_name = ?") + params = append(params, sysUser.UserName) + } + if sysUser.PhoneNumber != "" { + conditions = append(conditions, "phonenumber = ?") + params = append(params, sysUser.PhoneNumber) + } + if sysUser.Email != "" { + conditions = append(conditions, "email = ?") + params = append(params, sysUser.Email) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } else { + return "" + } + + // 查询数据 + querySql := "select user_id as 'str' from sys_user " + whereSql + " limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} diff --git a/src/modules/system/repository/sys_user_post.go b/src/modules/system/repository/sys_user_post.go new file mode 100644 index 0000000..2d2060b --- /dev/null +++ b/src/modules/system/repository/sys_user_post.go @@ -0,0 +1,15 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysUserPost 用户与岗位关联表 数据层接口 +type ISysUserPost interface { + // CountUserPostByPostId 通过岗位ID查询岗位使用数量 + CountUserPostByPostId(postId string) int64 + + // BatchUserPost 批量新增用户岗位信息 + BatchUserPost(sysUserPosts []model.SysUserPost) int64 + + // DeleteUserPost 批量删除用户和岗位关联 + DeleteUserPost(userIds []string) int64 +} diff --git a/src/modules/system/repository/sys_user_post.impl.go b/src/modules/system/repository/sys_user_post.impl.go new file mode 100644 index 0000000..cd6b937 --- /dev/null +++ b/src/modules/system/repository/sys_user_post.impl.go @@ -0,0 +1,60 @@ +package repository + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysUserPostImpl 结构体 +var NewSysUserPostImpl = &SysUserPostImpl{} + +// SysUserPostImpl 用户与岗位关联表 数据层处理 +type SysUserPostImpl struct{} + +// CountUserPostByPostId 通过岗位ID查询岗位使用数量 +func (r *SysUserPostImpl) CountUserPostByPostId(postId string) int64 { + querySql := "select count(1) as total from sys_user_role where role_id = ?" + results, err := datasource.RawDB("", querySql, []any{postId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// DeleteUserPost 批量删除用户和岗位关联 +func (r *SysUserPostImpl) DeleteUserPost(userIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(userIds)) + sql := "delete from sys_user_post where user_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(userIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// BatchUserPost 批量新增用户岗位信息 +func (r *SysUserPostImpl) BatchUserPost(sysUserPosts []model.SysUserPost) int64 { + keyValues := make([]string, 0) + for _, item := range sysUserPosts { + keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.UserID, item.PostID)) + } + sql := "insert into sys_user_post(user_id, post_id) values " + strings.Join(keyValues, ",") + results, err := datasource.ExecDB("", sql, nil) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_user_role.go b/src/modules/system/repository/sys_user_role.go new file mode 100644 index 0000000..b6af57c --- /dev/null +++ b/src/modules/system/repository/sys_user_role.go @@ -0,0 +1,18 @@ +package repository + +import "ems.agt/src/modules/system/model" + +// ISysUserRole 用户与角色关联表 数据层接口 +type ISysUserRole interface { + // CountUserRoleByRoleId 通过角色ID查询角色使用数量 + CountUserRoleByRoleId(roleId string) int64 + + // BatchUserRole 批量新增用户角色信息 + BatchUserRole(sysUserRoles []model.SysUserRole) int64 + + // DeleteUserRole 批量删除用户和角色关联 + DeleteUserRole(userIds []string) int64 + + // DeleteUserRoleByRoleId 批量取消授权用户角色 + DeleteUserRoleByRoleId(roleId string, userIds []string) int64 +} diff --git a/src/modules/system/repository/sys_user_role.impl.go b/src/modules/system/repository/sys_user_role.impl.go new file mode 100644 index 0000000..f44a219 --- /dev/null +++ b/src/modules/system/repository/sys_user_role.impl.go @@ -0,0 +1,74 @@ +package repository + +import ( + "fmt" + "strings" + + "ems.agt/src/framework/datasource" + "ems.agt/src/framework/logger" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/repo" + "ems.agt/src/modules/system/model" +) + +// 实例化数据层 SysUserRoleImpl 结构体 +var NewSysUserRoleImpl = &SysUserRoleImpl{} + +// SysUserRoleImpl 用户与角色关联表 数据层处理 +type SysUserRoleImpl struct{} + +// CountUserRoleByRoleId 通过角色ID查询角色使用数量 +func (r *SysUserRoleImpl) CountUserRoleByRoleId(roleId string) int64 { + querySql := "select count(1) as total from sys_user_role where role_id = ?" + results, err := datasource.RawDB("", querySql, []any{roleId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// BatchUserRole 批量新增用户角色信息 +func (r *SysUserRoleImpl) BatchUserRole(sysUserRoles []model.SysUserRole) int64 { + keyValues := make([]string, 0) + for _, item := range sysUserRoles { + keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.UserID, item.RoleID)) + } + sql := "insert into sys_user_role(user_id, role_id) values " + strings.Join(keyValues, ",") + results, err := datasource.ExecDB("", sql, nil) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteUserRole 批量删除用户和角色关联 +func (r *SysUserRoleImpl) DeleteUserRole(userIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(userIds)) + sql := "delete from sys_user_role where user_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(userIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteUserRoleByRoleId 批量取消授权用户角色 +func (r *SysUserRoleImpl) DeleteUserRoleByRoleId(roleId string, userIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(userIds)) + sql := "delete from sys_user_role where role_id= ? and user_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(userIds) + parameters = append([]any{roleId}, parameters...) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/service/sys_config.go b/src/modules/system/service/sys_config.go new file mode 100644 index 0000000..4ec80db --- /dev/null +++ b/src/modules/system/service/sys_config.go @@ -0,0 +1,30 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysConfig 参数配置 服务层接口 +type ISysConfig interface { + // SelectDictDataPage 分页查询参数配置列表数据 + SelectConfigPage(query map[string]any) map[string]any + + // SelectConfigValueByKey 通过参数键名查询参数键值 + SelectConfigValueByKey(configKey string) string + + // SelectConfigById 通过配置ID查询参数配置信息 + SelectConfigById(configId string) model.SysConfig + + // CheckUniqueConfigKey 校验参数键名是否唯一 + CheckUniqueConfigKey(configKey, configId string) bool + + // InsertConfig 新增参数配置 + InsertConfig(sysConfig model.SysConfig) string + + // UpdateConfig 修改参数配置 + UpdateConfig(sysConfig model.SysConfig) int64 + + // DeleteConfigByIds 批量删除参数配置信息 + DeleteConfigByIds(configIds []string) (int64, error) + + // ResetConfigCache 重置参数缓存数据 + ResetConfigCache() +} diff --git a/src/modules/system/service/sys_config.impl.go b/src/modules/system/service/sys_config.impl.go new file mode 100644 index 0000000..c15eea0 --- /dev/null +++ b/src/modules/system/service/sys_config.impl.go @@ -0,0 +1,157 @@ +package service + +import ( + "errors" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/redis" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysConfigImpl 结构体 +var NewSysConfigImpl = &SysConfigImpl{ + sysConfigRepository: repository.NewSysConfigImpl, +} + +// SysConfigImpl 参数配置 服务层处理 +type SysConfigImpl struct { + // 参数配置表 + sysConfigRepository repository.ISysConfig +} + +// SelectDictDataPage 分页查询参数配置列表数据 +func (r *SysConfigImpl) SelectConfigPage(query map[string]any) map[string]any { + return r.sysConfigRepository.SelectConfigPage(query) +} + +// SelectConfigList 查询参数配置列表 +func (r *SysConfigImpl) SelectConfigList(sysConfig model.SysConfig) []model.SysConfig { + return r.sysConfigRepository.SelectConfigList(sysConfig) +} + +// SelectConfigValueByKey 通过参数键名查询参数键值 +func (r *SysConfigImpl) SelectConfigValueByKey(configKey string) string { + cacheKey := r.getCacheKey(configKey) + // 从缓存中读取 + cacheValue, err := redis.Get("", cacheKey) + if cacheValue != "" || err != nil { + return cacheValue + } + // 无缓存时读取数据放入缓存中 + configValue := r.sysConfigRepository.SelectConfigValueByKey(configKey) + if configValue != "" { + redis.Set("", cacheKey, configValue) + return configValue + } + return "" +} + +// SelectConfigById 通过配置ID查询参数配置信息 +func (r *SysConfigImpl) SelectConfigById(configId string) model.SysConfig { + if configId == "" { + return model.SysConfig{} + } + configs := r.sysConfigRepository.SelectConfigByIds([]string{configId}) + if len(configs) > 0 { + return configs[0] + } + return model.SysConfig{} +} + +// CheckUniqueConfigKey 校验参数键名是否唯一 +func (r *SysConfigImpl) CheckUniqueConfigKey(configKey, configId string) bool { + uniqueId := r.sysConfigRepository.CheckUniqueConfig(model.SysConfig{ + ConfigKey: configKey, + }) + if uniqueId == configId { + return true + } + return uniqueId == "" +} + +// InsertConfig 新增参数配置 +func (r *SysConfigImpl) InsertConfig(sysConfig model.SysConfig) string { + configId := r.sysConfigRepository.InsertConfig(sysConfig) + if configId != "" { + r.loadingConfigCache(sysConfig.ConfigKey) + } + return configId +} + +// UpdateConfig 修改参数配置 +func (r *SysConfigImpl) UpdateConfig(sysConfig model.SysConfig) int64 { + rows := r.sysConfigRepository.UpdateConfig(sysConfig) + if rows > 0 { + r.loadingConfigCache(sysConfig.ConfigKey) + } + return rows +} + +// DeleteConfigByIds 批量删除参数配置信息 +func (r *SysConfigImpl) DeleteConfigByIds(configIds []string) (int64, error) { + // 检查是否存在 + configs := r.sysConfigRepository.SelectConfigByIds(configIds) + if len(configs) <= 0 { + return 0, errors.New("没有权限访问参数配置数据!") + } + for _, config := range configs { + // 检查是否为内置参数 + if config.ConfigType == "Y" { + return 0, errors.New(config.ConfigID + " 配置参数属于内置参数,禁止删除!") + } + // 清除缓存 + r.clearConfigCache(config.ConfigKey) + } + if len(configs) == len(configIds) { + rows := r.sysConfigRepository.DeleteConfigByIds(configIds) + return rows, nil + } + return 0, errors.New("删除参数配置信息失败!") +} + +// ResetConfigCache 重置参数缓存数据 +func (r *SysConfigImpl) ResetConfigCache() { + r.clearConfigCache("*") + r.loadingConfigCache("*") +} + +// getCacheKey 组装缓存key +func (r *SysConfigImpl) getCacheKey(configKey string) string { + return cachekey.SYS_CONFIG_KEY + configKey +} + +// loadingConfigCache 加载参数缓存数据 +func (r *SysConfigImpl) loadingConfigCache(configKey string) { + // 查询全部参数 + if configKey == "*" { + sysConfigs := r.SelectConfigList(model.SysConfig{}) + for _, v := range sysConfigs { + key := r.getCacheKey(v.ConfigKey) + redis.Del("", key) + redis.Set("", key, v.ConfigValue) + } + return + } + // 指定参数 + if configKey != "" { + cacheValue := r.sysConfigRepository.SelectConfigValueByKey(configKey) + if cacheValue != "" { + key := r.getCacheKey(configKey) + redis.Del("", key) + redis.Set("", key, cacheValue) + } + return + } +} + +// clearConfigCache 清空参数缓存数据 +func (r *SysConfigImpl) clearConfigCache(configKey string) bool { + key := r.getCacheKey(configKey) + keys, err := redis.GetKeys("", key) + if err != nil { + return false + } + delOk, _ := redis.DelKeys("", keys) + return delOk +} diff --git a/src/modules/system/service/sys_dept.go b/src/modules/system/service/sys_dept.go new file mode 100644 index 0000000..db995fc --- /dev/null +++ b/src/modules/system/service/sys_dept.go @@ -0,0 +1,39 @@ +package service + +import ( + "ems.agt/src/framework/vo" + "ems.agt/src/modules/system/model" +) + +// ISysDept 部门管理 服务层接口 +type ISysDept interface { + // SelectDeptList 查询部门管理数据 + SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept + + // SelectDeptListByRoleId 根据角色ID查询部门树信息 + SelectDeptListByRoleId(roleId string) []string + + // SelectDeptById 根据部门ID查询信息 + SelectDeptById(deptId string) model.SysDept + + // HasChildByDeptId 是否存在子节点 + HasChildByDeptId(deptId string) int64 + + // CheckDeptExistUser 查询部门是否存在用户 + CheckDeptExistUser(deptId string) int64 + + // CheckUniqueDeptName 校验同级部门名称是否唯一 + CheckUniqueDeptName(deptName, parentId, deptId string) bool + + // InsertDept 新增部门信息 + InsertDept(sysDept model.SysDept) string + + // UpdateDept 修改部门信息 + UpdateDept(sysDept model.SysDept) int64 + + // DeleteDeptById 删除部门管理信息 + DeleteDeptById(deptId string) int64 + + // SelectDeptTreeSelect 查询部门树结构信息 + SelectDeptTreeSelect(sysDept model.SysDept, dataScopeSQL string) []vo.TreeSelect +} diff --git a/src/modules/system/service/sys_dept.impl.go b/src/modules/system/service/sys_dept.impl.go new file mode 100644 index 0000000..c718df6 --- /dev/null +++ b/src/modules/system/service/sys_dept.impl.go @@ -0,0 +1,202 @@ +package service + +import ( + "strings" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/vo" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysDeptImpl 结构体 +var NewSysDeptImpl = &SysDeptImpl{ + sysDeptRepository: repository.NewSysDeptImpl, + sysRoleRepository: repository.NewSysRoleImpl, + sysRoleDeptRepository: repository.NewSysRoleDeptImpl, +} + +// SysDeptImpl 部门表 服务层处理 +type SysDeptImpl struct { + // 部门服务 + sysDeptRepository repository.ISysDept + // 角色服务 + sysRoleRepository repository.ISysRole + // 角色与部门关联服务 + sysRoleDeptRepository repository.ISysRoleDept +} + +// SelectDeptList 查询部门管理数据 +func (r *SysDeptImpl) SelectDeptList(sysDept model.SysDept, dataScopeSQL string) []model.SysDept { + return r.sysDeptRepository.SelectDeptList(sysDept, dataScopeSQL) +} + +// SelectDeptListByRoleId 根据角色ID查询部门树信息 TODO +func (r *SysDeptImpl) SelectDeptListByRoleId(roleId string) []string { + roles := r.sysRoleRepository.SelectRoleByIds([]string{roleId}) + if len(roles) > 0 { + role := roles[0] + if role.RoleID == roleId { + return r.sysDeptRepository.SelectDeptListByRoleId( + role.RoleID, + role.DeptCheckStrictly == "1", + ) + } + } + return []string{} +} + +// SelectDeptById 根据部门ID查询信息 +func (r *SysDeptImpl) SelectDeptById(deptId string) model.SysDept { + return r.sysDeptRepository.SelectDeptById(deptId) +} + +// HasChildByDeptId 是否存在子节点 +func (r *SysDeptImpl) HasChildByDeptId(deptId string) int64 { + return r.sysDeptRepository.HasChildByDeptId(deptId) +} + +// CheckDeptExistUser 查询部门是否存在用户 +func (r *SysDeptImpl) CheckDeptExistUser(deptId string) int64 { + return r.sysDeptRepository.CheckDeptExistUser(deptId) +} + +// CheckUniqueDeptName 校验同级部门名称是否唯一 +func (r *SysDeptImpl) CheckUniqueDeptName(deptName, parentId, deptId string) bool { + uniqueId := r.sysDeptRepository.CheckUniqueDept(model.SysDept{ + DeptName: deptName, + ParentID: parentId, + }) + if uniqueId == deptId { + return true + } + return uniqueId == "" +} + +// InsertDept 新增部门信息 +func (r *SysDeptImpl) InsertDept(sysDept model.SysDept) string { + return r.sysDeptRepository.InsertDept(sysDept) +} + +// UpdateDept 修改部门信息 +func (r *SysDeptImpl) UpdateDept(sysDept model.SysDept) int64 { + parentDept := r.sysDeptRepository.SelectDeptById(sysDept.ParentID) + dept := r.sysDeptRepository.SelectDeptById(sysDept.DeptID) + // 上级与当前部门祖级列表更新 + if parentDept.DeptID == sysDept.ParentID && dept.DeptID == sysDept.DeptID { + newAncestors := parentDept.Ancestors + "," + parentDept.DeptID + oldAncestors := dept.Ancestors + // 祖级列表不一致时更新 + if newAncestors != oldAncestors { + sysDept.Ancestors = newAncestors + r.updateDeptChildren(sysDept.DeptID, newAncestors, oldAncestors) + } + } + // 如果该部门是启用状态,则启用该部门的所有上级部门 + if sysDept.Status == common.STATUS_YES && parentDept.Status == common.STATUS_NO { + r.updateDeptStatusNormal(sysDept.Ancestors) + } + return r.sysDeptRepository.UpdateDept(sysDept) +} + +// updateDeptStatusNormal 修改所在部门正常状态 +func (r *SysDeptImpl) updateDeptStatusNormal(ancestors string) int64 { + if ancestors == "" || ancestors == "0" { + return 0 + } + deptIds := strings.Split(ancestors, ",") + return r.sysDeptRepository.UpdateDeptStatusNormal(deptIds) +} + +// updateDeptChildren 修改子元素关系 +func (r *SysDeptImpl) updateDeptChildren(deptId, newAncestors, oldAncestors string) int64 { + childrens := r.sysDeptRepository.SelectChildrenDeptById(deptId) + if len(childrens) == 0 { + return 0 + } + // 替换父ID + for i := range childrens { + child := &childrens[i] + ancestors := strings.Replace(child.Ancestors, oldAncestors, newAncestors, -1) + child.Ancestors = ancestors + } + return r.sysDeptRepository.UpdateDeptChildren(childrens) +} + +// DeleteDeptById 删除部门管理信息 +func (r *SysDeptImpl) DeleteDeptById(deptId string) int64 { + // 删除角色与部门关联 + r.sysRoleDeptRepository.DeleteDeptRole([]string{deptId}) + return r.sysDeptRepository.DeleteDeptById(deptId) +} + +// SelectDeptTreeSelect 查询部门树结构信息 +func (r *SysDeptImpl) SelectDeptTreeSelect(sysDept model.SysDept, dataScopeSQL string) []vo.TreeSelect { + sysDepts := r.sysDeptRepository.SelectDeptList(sysDept, dataScopeSQL) + depts := r.parseDataToTree(sysDepts) + tree := make([]vo.TreeSelect, 0) + for _, dept := range depts { + tree = append(tree, vo.SysDeptTreeSelect(dept)) + } + return tree +} + +// parseDataToTree 将数据解析为树结构,构建前端所需要下拉树结构 +func (r *SysDeptImpl) parseDataToTree(sysDepts []model.SysDept) []model.SysDept { + // 节点分组 + nodesMap := make(map[string][]model.SysDept) + // 节点id + treeIds := []string{} + // 树节点 + tree := []model.SysDept{} + + for _, item := range sysDepts { + parentID := item.ParentID + // 分组 + mapItem, ok := nodesMap[parentID] + if !ok { + mapItem = []model.SysDept{} + } + mapItem = append(mapItem, item) + nodesMap[parentID] = mapItem + // 记录节点ID + treeIds = append(treeIds, item.DeptID) + } + + for key, value := range nodesMap { + // 选择不是节点ID的作为树节点 + found := false + for _, id := range treeIds { + if id == key { + found = true + break + } + } + if !found { + tree = append(tree, value...) + } + } + + for i, node := range tree { + iN := r.parseDataToTreeComponet(node, &nodesMap) + tree[i] = iN + } + + return tree +} + +// parseDataToTreeComponet 递归函数处理子节点 +func (r *SysDeptImpl) parseDataToTreeComponet(node model.SysDept, nodesMap *map[string][]model.SysDept) model.SysDept { + id := node.DeptID + children, ok := (*nodesMap)[id] + if ok { + node.Children = children + } + if len(node.Children) > 0 { + for i, child := range node.Children { + icN := r.parseDataToTreeComponet(child, nodesMap) + node.Children[i] = icN + } + } + return node +} diff --git a/src/modules/system/service/sys_dict_data.go b/src/modules/system/service/sys_dict_data.go new file mode 100644 index 0000000..802a7f7 --- /dev/null +++ b/src/modules/system/service/sys_dict_data.go @@ -0,0 +1,33 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysDictData 字典类型数据 服务层接口 +type ISysDictData interface { + // SelectDictDataPage 根据条件分页查询字典数据 + SelectDictDataPage(query map[string]any) map[string]any + + // SelectDictDataList 根据条件查询字典数据 + SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData + + // SelectDictDataByCode 根据字典数据编码查询信息 + SelectDictDataByCode(dictCode string) model.SysDictData + + // SelectDictDataByType 根据字典类型查询信息 + SelectDictDataByType(dictType string) []model.SysDictData + + // CheckUniqueDictLabel 校验字典标签是否唯一 + CheckUniqueDictLabel(dictType, dictLabel, dictCode string) bool + + // CheckUniqueDictValue 校验字典键值是否唯一 + CheckUniqueDictValue(dictType, dictValue, dictCode string) bool + + // DeleteDictDataByCodes 批量删除字典数据信息 + DeleteDictDataByCodes(dictCodes []string) (int64, error) + + // InsertDictData 新增字典数据信息 + InsertDictData(sysDictData model.SysDictData) string + + // UpdateDictData 修改字典数据信息 + UpdateDictData(sysDictData model.SysDictData) int64 +} diff --git a/src/modules/system/service/sys_dict_data.impl.go b/src/modules/system/service/sys_dict_data.impl.go new file mode 100644 index 0000000..dff2961 --- /dev/null +++ b/src/modules/system/service/sys_dict_data.impl.go @@ -0,0 +1,110 @@ +package service + +import ( + "errors" + + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysDictDataImpl 结构体 +var NewSysDictDataImpl = &SysDictDataImpl{ + sysDictDataRepository: repository.NewSysDictDataImpl, + sysDictTypeService: NewSysDictTypeImpl, +} + +// SysDictDataImpl 字典类型数据 服务层处理 +type SysDictDataImpl struct { + // 字典数据服务 + sysDictDataRepository repository.ISysDictData + // 字典类型服务 + sysDictTypeService ISysDictType +} + +// SelectDictDataPage 根据条件分页查询字典数据 +func (r *SysDictDataImpl) SelectDictDataPage(query map[string]any) map[string]any { + return r.sysDictDataRepository.SelectDictDataPage(query) +} + +// SelectDictDataList 根据条件查询字典数据 +func (r *SysDictDataImpl) SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData { + return r.sysDictDataRepository.SelectDictDataList(sysDictData) +} + +// SelectDictDataByCode 根据字典数据编码查询信息 +func (r *SysDictDataImpl) SelectDictDataByCode(dictCode string) model.SysDictData { + if dictCode == "" { + return model.SysDictData{} + } + dictCodes := r.sysDictDataRepository.SelectDictDataByCodes([]string{dictCode}) + if len(dictCodes) > 0 { + return dictCodes[0] + } + return model.SysDictData{} +} + +// SelectDictDataByType 根据字典类型查询信息 +func (r *SysDictDataImpl) SelectDictDataByType(dictType string) []model.SysDictData { + return r.sysDictTypeService.DictDataCache(dictType) +} + +// CheckUniqueDictLabel 校验字典标签是否唯一 +func (r *SysDictDataImpl) CheckUniqueDictLabel(dictType, dictLabel, dictCode string) bool { + uniqueId := r.sysDictDataRepository.CheckUniqueDictData(model.SysDictData{ + DictType: dictType, + DictLabel: dictLabel, + }) + if uniqueId == dictCode { + return true + } + return uniqueId == "" +} + +// CheckUniqueDictValue 校验字典键值是否唯一 +func (r *SysDictDataImpl) CheckUniqueDictValue(dictType, dictValue, dictCode string) bool { + uniqueId := r.sysDictDataRepository.CheckUniqueDictData(model.SysDictData{ + DictType: dictType, + DictValue: dictValue, + }) + if uniqueId == dictCode { + return true + } + return uniqueId == "" +} + +// DeleteDictDataByCodes 批量删除字典数据信息 +func (r *SysDictDataImpl) DeleteDictDataByCodes(dictCodes []string) (int64, error) { + // 检查是否存在 + dictDatas := r.sysDictDataRepository.SelectDictDataByCodes(dictCodes) + if len(dictDatas) <= 0 { + return 0, errors.New("没有权限访问字典编码数据!") + } + if len(dictDatas) == len(dictCodes) { + for _, v := range dictDatas { + // 刷新缓存 + r.sysDictTypeService.ClearDictCache(v.DictType) + r.sysDictTypeService.LoadingDictCache(v.DictType) + } + rows := r.sysDictDataRepository.DeleteDictDataByCodes(dictCodes) + return rows, nil + } + return 0, errors.New("删除字典数据信息失败!") +} + +// InsertDictData 新增字典数据信息 +func (r *SysDictDataImpl) InsertDictData(sysDictData model.SysDictData) string { + insertId := r.sysDictDataRepository.InsertDictData(sysDictData) + if insertId != "" { + r.sysDictTypeService.LoadingDictCache(sysDictData.DictType) + } + return insertId +} + +// UpdateDictData 修改字典数据信息 +func (r *SysDictDataImpl) UpdateDictData(sysDictData model.SysDictData) int64 { + rows := r.sysDictDataRepository.UpdateDictData(sysDictData) + if rows > 0 { + r.sysDictTypeService.LoadingDictCache(sysDictData.DictType) + } + return rows +} diff --git a/src/modules/system/service/sys_dict_type.go b/src/modules/system/service/sys_dict_type.go new file mode 100644 index 0000000..a2a1446 --- /dev/null +++ b/src/modules/system/service/sys_dict_type.go @@ -0,0 +1,45 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysDictType 字典类型 服务层接口 +type ISysDictType interface { + // SelectDictTypePage 根据条件分页查询字典类型 + SelectDictTypePage(query map[string]any) map[string]any + + // SelectDictTypeList 根据条件查询字典类型 + SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType + + // SelectDictTypeByID 根据字典类型ID查询信息 + SelectDictTypeByID(dictID string) model.SysDictType + + // SelectDictTypeByType 根据字典类型查询信息 + SelectDictTypeByType(dictType string) model.SysDictType + + // CheckUniqueDictName 校验字典名称是否唯一 + CheckUniqueDictName(dictName, dictID string) bool + + // CheckUniqueDictType 校验字典类型是否唯一 + CheckUniqueDictType(dictType, dictID string) bool + + // InsertDictType 新增字典类型信息 + InsertDictType(sysDictType model.SysDictType) string + + // UpdateDictType 修改字典类型信息 + UpdateDictType(sysDictType model.SysDictType) int64 + + // DeleteDictTypeByIDs 批量删除字典类型信息 + DeleteDictTypeByIDs(dictIDs []string) (int64, error) + + // ResetDictCache 重置字典缓存数据 + ResetDictCache() + + // 加载字典缓存数据 + LoadingDictCache(dictType string) + + // 清空字典缓存数据 + ClearDictCache(dictType string) bool + + // DictDataCache 获取字典数据缓存数据 + DictDataCache(dictType string) []model.SysDictData +} diff --git a/src/modules/system/service/sys_dict_type.impl.go b/src/modules/system/service/sys_dict_type.impl.go new file mode 100644 index 0000000..41cce72 --- /dev/null +++ b/src/modules/system/service/sys_dict_type.impl.go @@ -0,0 +1,211 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + + "ems.agt/src/framework/constants/cachekey" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/redis" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysDictTypeImpl 结构体 +var NewSysDictTypeImpl = &SysDictTypeImpl{ + sysDictTypeRepository: repository.NewSysDictTypeImpl, + sysDictDataRepository: repository.NewSysDictDataImpl, +} + +// SysDictTypeImpl 字典类型 服务层处理 +type SysDictTypeImpl struct { + // 字典类型服务 + sysDictTypeRepository repository.ISysDictType + // 字典数据服务 + sysDictDataRepository repository.ISysDictData +} + +// SelectDictTypePage 根据条件分页查询字典类型 +func (r *SysDictTypeImpl) SelectDictTypePage(query map[string]any) map[string]any { + return r.sysDictTypeRepository.SelectDictTypePage(query) +} + +// SelectDictTypeList 根据条件查询字典类型 +func (r *SysDictTypeImpl) SelectDictTypeList(sysDictType model.SysDictType) []model.SysDictType { + return r.sysDictTypeRepository.SelectDictTypeList(sysDictType) +} + +// SelectDictTypeByID 根据字典类型ID查询信息 +func (r *SysDictTypeImpl) SelectDictTypeByID(dictID string) model.SysDictType { + if dictID == "" { + return model.SysDictType{} + } + dictTypes := r.sysDictTypeRepository.SelectDictTypeByIDs([]string{dictID}) + if len(dictTypes) > 0 { + return dictTypes[0] + } + return model.SysDictType{} +} + +// SelectDictTypeByType 根据字典类型查询信息 +func (r *SysDictTypeImpl) SelectDictTypeByType(dictType string) model.SysDictType { + return r.sysDictTypeRepository.SelectDictTypeByType(dictType) +} + +// CheckUniqueDictName 校验字典名称是否唯一 +func (r *SysDictTypeImpl) CheckUniqueDictName(dictName, dictID string) bool { + uniqueId := r.sysDictTypeRepository.CheckUniqueDictType(model.SysDictType{ + DictName: dictName, + }) + if uniqueId == dictID { + return true + } + return uniqueId == "" +} + +// CheckUniqueDictType 校验字典类型是否唯一 +func (r *SysDictTypeImpl) CheckUniqueDictType(dictType, dictID string) bool { + uniqueId := r.sysDictTypeRepository.CheckUniqueDictType(model.SysDictType{ + DictType: dictType, + }) + if uniqueId == dictID { + return true + } + return uniqueId == "" +} + +// InsertDictType 新增字典类型信息 +func (r *SysDictTypeImpl) InsertDictType(sysDictType model.SysDictType) string { + insertId := r.sysDictTypeRepository.InsertDictType(sysDictType) + if insertId != "" { + r.LoadingDictCache(sysDictType.DictType) + } + return insertId +} + +// UpdateDictType 修改字典类型信息 +func (r *SysDictTypeImpl) UpdateDictType(sysDictType model.SysDictType) int64 { + data := r.sysDictTypeRepository.SelectDictTypeByIDs([]string{sysDictType.DictID}) + if len(data) == 0 { + return 0 + } + // 修改字典类型key时同步更新其字典数据的类型key + oldDictType := data[0].DictType + rows := r.sysDictTypeRepository.UpdateDictType(sysDictType) + if rows > 0 && oldDictType != "" && oldDictType != sysDictType.DictType { + r.sysDictDataRepository.UpdateDictDataType(oldDictType, sysDictType.DictType) + } + // 刷新缓存 + r.ClearDictCache(oldDictType) + r.LoadingDictCache(sysDictType.DictType) + return rows +} + +// DeleteDictTypeByIDs 批量删除字典类型信息 +func (r *SysDictTypeImpl) DeleteDictTypeByIDs(dictIDs []string) (int64, error) { + // 检查是否存在 + dictTypes := r.sysDictTypeRepository.SelectDictTypeByIDs(dictIDs) + if len(dictTypes) <= 0 { + return 0, errors.New("没有权限访问字典类型数据!") + } + for _, v := range dictTypes { + // 字典类型下级含有数据 + useCount := r.sysDictDataRepository.CountDictDataByType(v.DictType) + if useCount > 0 { + msg := fmt.Sprintf("【%s】存在字典数据,不能删除", v.DictName) + return 0, errors.New(msg) + } + // 清除缓存 + r.ClearDictCache(v.DictType) + } + if len(dictTypes) == len(dictIDs) { + rows := r.sysDictTypeRepository.DeleteDictTypeByIDs(dictIDs) + return rows, nil + } + return 0, errors.New("删除字典数据信息失败!") +} + +// ResetDictCache 重置字典缓存数据 +func (r *SysDictTypeImpl) ResetDictCache() { + r.ClearDictCache("*") + r.LoadingDictCache("") +} + +// getCacheKey 组装缓存key +func (r *SysDictTypeImpl) getDictCache(dictType string) string { + return cachekey.SYS_DICT_KEY + dictType +} + +// LoadingDictCache 加载字典缓存数据 +func (r *SysDictTypeImpl) LoadingDictCache(dictType string) { + sysDictData := model.SysDictData{ + Status: common.STATUS_YES, + } + + // 指定字典类型 + if dictType != "" { + sysDictData.DictType = dictType + // 删除缓存 + key := r.getDictCache(dictType) + redis.Del("", key) + } + + sysDictDataList := r.sysDictDataRepository.SelectDictDataList(sysDictData) + if len(sysDictDataList) == 0 { + return + } + + // 将字典数据按类型分组 + m := make(map[string][]model.SysDictData, 0) + for _, v := range sysDictDataList { + key := v.DictType + if item, ok := m[key]; ok { + m[key] = append(item, v) + } else { + m[key] = []model.SysDictData{v} + } + } + + // 放入缓存 + for k, v := range m { + key := r.getDictCache(k) + values, _ := json.Marshal(v) + redis.Set("", key, string(values)) + } +} + +// ClearDictCache 清空字典缓存数据 +func (r *SysDictTypeImpl) ClearDictCache(dictType string) bool { + key := r.getDictCache(dictType) + keys, err := redis.GetKeys("", key) + if err != nil { + return false + } + delOk, _ := redis.DelKeys("", keys) + return delOk +} + +// DictDataCache 获取字典数据缓存数据 +func (r *SysDictTypeImpl) DictDataCache(dictType string) []model.SysDictData { + data := []model.SysDictData{} + key := r.getDictCache(dictType) + jsonStr, _ := redis.Get("", key) + if len(jsonStr) > 7 { + err := json.Unmarshal([]byte(jsonStr), &data) + if err != nil { + data = []model.SysDictData{} + } + } else { + data = r.sysDictDataRepository.SelectDictDataList(model.SysDictData{ + Status: common.STATUS_YES, + DictType: dictType, + }) + if len(data) > 0 { + redis.Del("", key) + values, _ := json.Marshal(data) + redis.Set("", key, string(values)) + } + } + return data +} diff --git a/src/modules/system/service/sys_log_login.go b/src/modules/system/service/sys_log_login.go new file mode 100644 index 0000000..f2be50d --- /dev/null +++ b/src/modules/system/service/sys_log_login.go @@ -0,0 +1,24 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysLogLogin 系统登录日志 服务层接口 +type ISysLogLogin interface { + // SelectSysLogLoginPage 分页查询系统登录日志集合 + SelectSysLogLoginPage(query map[string]any) map[string]any + + // SelectSysLogLoginList 查询系统登录日志集合 + SelectSysLogLoginList(sysLogLogin model.SysLogLogin) []model.SysLogLogin + + // InsertSysLogLogin 新增系统登录日志 + InsertSysLogLogin(sysLogLogin model.SysLogLogin) string + + // DeleteSysLogLoginByIds 批量删除系统登录日志 + DeleteSysLogLoginByIds(loginIds []string) int64 + + // CleanSysLogLogin 清空系统登录日志 + CleanSysLogLogin() error + + // NewSysLogLogin 生成系统登录日志 + NewSysLogLogin(userName, status, msg string, ilobArgs ...string) string +} diff --git a/src/modules/system/service/sys_log_login.impl.go b/src/modules/system/service/sys_log_login.impl.go new file mode 100644 index 0000000..4ede16e --- /dev/null +++ b/src/modules/system/service/sys_log_login.impl.go @@ -0,0 +1,56 @@ +package service + +import ( + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysLogLoginImpl 结构体 +var NewSysLogLoginImpl = &SysLogLoginImpl{ + sysLogLoginService: repository.NewSysLogLoginImpl, +} + +// SysLogLoginImpl 系统登录访问 服务层处理 +type SysLogLoginImpl struct { + // 系统登录访问信息 + sysLogLoginService repository.ISysLogLogin +} + +// SelectSysLogLoginPage 分页查询系统登录日志集合 +func (s *SysLogLoginImpl) SelectSysLogLoginPage(query map[string]any) map[string]any { + return s.sysLogLoginService.SelectSysLogLoginPage(query) +} + +// SelectSysLogLoginList 查询系统登录日志集合 +func (s *SysLogLoginImpl) SelectSysLogLoginList(sysSysLogLogin model.SysLogLogin) []model.SysLogLogin { + return s.sysLogLoginService.SelectSysLogLoginList(sysSysLogLogin) +} + +// InsertSysLogLogin 新增系统登录日志 +func (s *SysLogLoginImpl) InsertSysLogLogin(sysSysLogLogin model.SysLogLogin) string { + return s.sysLogLoginService.InsertSysLogLogin(sysSysLogLogin) +} + +// DeleteSysLogLoginByIds 批量删除系统登录日志 +func (s *SysLogLoginImpl) DeleteSysLogLoginByIds(loginIds []string) int64 { + return s.sysLogLoginService.DeleteSysLogLoginByIds(loginIds) +} + +// CleanSysLogLogin 清空系统登录日志 +func (s *SysLogLoginImpl) CleanSysLogLogin() error { + return s.sysLogLoginService.CleanSysLogLogin() +} + +// NewSysLogLogin 生成系统登录日志 +func (s *SysLogLoginImpl) NewSysLogLogin(userName, status, msg string, ilobArgs ...string) string { + sysSysLogLogin := model.SysLogLogin{ + IPAddr: ilobArgs[0], + LoginLocation: ilobArgs[1], + OS: ilobArgs[2], + Browser: ilobArgs[3], + UserName: userName, + Status: status, + Msg: msg, + } + return s.InsertSysLogLogin(sysSysLogLogin) +} diff --git a/src/modules/system/service/sys_log_operate.go b/src/modules/system/service/sys_log_operate.go new file mode 100644 index 0000000..5441119 --- /dev/null +++ b/src/modules/system/service/sys_log_operate.go @@ -0,0 +1,24 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysLogOperate 操作日志表 服务层接口 +type ISysLogOperate interface { + // SelectSysLogOperatePage 分页查询系统操作日志集合 + SelectSysLogOperatePage(query map[string]any) map[string]any + + // SelectSysLogOperateList 查询系统操作日志集合 + SelectSysLogOperateList(sysLogOperate model.SysLogOperate) []model.SysLogOperate + + // SelectSysLogOperateById 查询操作日志详细 + SelectSysLogOperateById(operId string) model.SysLogOperate + + // InsertSysLogOperate 新增操作日志 + InsertSysLogOperate(sysLogOperate model.SysLogOperate) string + + // DeleteSysLogOperateByIds 批量删除系统操作日志 + DeleteSysLogOperateByIds(operIds []string) int64 + + // CleanSysLogOperate 清空操作日志 + CleanSysLogOperate() error +} diff --git a/src/modules/system/service/sys_log_operate.impl.go b/src/modules/system/service/sys_log_operate.impl.go new file mode 100644 index 0000000..6c40be5 --- /dev/null +++ b/src/modules/system/service/sys_log_operate.impl.go @@ -0,0 +1,47 @@ +package service + +import ( + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysLogOperateImpl 结构体 +var NewSysLogOperateImpl = &SysLogOperateImpl{ + SysLogOperateService: repository.NewSysLogOperateImpl, +} + +// SysLogOperateImpl 操作日志表 服务层处理 +type SysLogOperateImpl struct { + // 操作日志信息 + SysLogOperateService repository.ISysLogOperate +} + +// SelectSysLogOperatePage 分页查询系统操作日志集合 +func (r *SysLogOperateImpl) SelectSysLogOperatePage(query map[string]any) map[string]any { + return r.SysLogOperateService.SelectSysLogOperatePage(query) +} + +// SelectSysLogOperateList 查询系统操作日志集合 +func (r *SysLogOperateImpl) SelectSysLogOperateList(SysLogOperate model.SysLogOperate) []model.SysLogOperate { + return r.SysLogOperateService.SelectSysLogOperateList(SysLogOperate) +} + +// SelectSysLogOperateById 查询操作日志详细 +func (r *SysLogOperateImpl) SelectSysLogOperateById(operId string) model.SysLogOperate { + return r.SysLogOperateService.SelectSysLogOperateById(operId) +} + +// InsertSysLogOperate 新增操作日志 +func (r *SysLogOperateImpl) InsertSysLogOperate(SysLogOperate model.SysLogOperate) string { + return r.SysLogOperateService.InsertSysLogOperate(SysLogOperate) +} + +// DeleteSysLogOperateByIds 批量删除系统操作日志 +func (r *SysLogOperateImpl) DeleteSysLogOperateByIds(operIds []string) int64 { + return r.SysLogOperateService.DeleteSysLogOperateByIds(operIds) +} + +// CleanSysLogOperate 清空操作日志 +func (r *SysLogOperateImpl) CleanSysLogOperate() error { + return r.SysLogOperateService.CleanSysLogOperate() +} diff --git a/src/modules/system/service/sys_menu.go b/src/modules/system/service/sys_menu.go new file mode 100644 index 0000000..10374be --- /dev/null +++ b/src/modules/system/service/sys_menu.go @@ -0,0 +1,51 @@ +package service + +import ( + "ems.agt/src/framework/vo" + "ems.agt/src/modules/system/model" +) + +// ISysMenu 菜单 服务层接口 +type ISysMenu interface { + // SelectMenuList 查询系统菜单列表 + SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu + + // SelectMenuPermsByUserId 根据用户ID查询权限 + SelectMenuPermsByUserId(userId string) []string + + // SelectMenuPermsByUserId 根据用户ID查询权限 + SelectMenuTreeByUserId(userId string) []model.SysMenu + + // SelectMenuTreeSelectByUserId 查询菜单树结构信息 + SelectMenuTreeSelectByUserId(sysMenu model.SysMenu, userId string) []vo.TreeSelect + + // SelectMenuListByRoleId 根据角色ID查询菜单树信息 + SelectMenuListByRoleId(roleId string) []string + + // SelectMenuById 根据菜单ID查询信息 + SelectMenuById(menuId string) model.SysMenu + + // HasChildByMenuIdAndStatus 存在菜单子节点数量与状态 + HasChildByMenuIdAndStatus(menuId, status string) int64 + + // CheckMenuExistRole 查询菜单分配角色数量 + CheckMenuExistRole(menuId string) int64 + + // InsertMenu 新增菜单信息 + InsertMenu(sysMenu model.SysMenu) string + + // UpdateMenu 修改菜单信息 + UpdateMenu(sysMenu model.SysMenu) int64 + + // DeleteMenuById 删除菜单管理信息 + DeleteMenuById(menuId string) int64 + + // CheckUniqueMenuName 校验菜单名称是否唯一 + CheckUniqueMenuName(menuName, parentId, menuId string) bool + + // CheckUniqueMenuPath 校验路由地址是否唯一(针对目录和菜单) + CheckUniqueMenuPath(path, menuId string) bool + + // BuildRouteMenus 构建前端路由所需要的菜单 + BuildRouteMenus(sysMenus []model.SysMenu, prefix string) []vo.Router +} diff --git a/src/modules/system/service/sys_menu.impl.go b/src/modules/system/service/sys_menu.impl.go new file mode 100644 index 0000000..1dd1d83 --- /dev/null +++ b/src/modules/system/service/sys_menu.impl.go @@ -0,0 +1,380 @@ +package service + +import ( + "encoding/base64" + "strings" + + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/constants/menu" + "ems.agt/src/framework/utils/parse" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/framework/vo" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysMenuImpl 结构体 +var NewSysMenuImpl = &SysMenuImpl{ + sysMenuRepository: repository.NewSysMenuImpl, + sysRoleMenuRepository: repository.NewSysRoleMenuImpl, + sysRoleRepository: repository.NewSysRoleImpl, +} + +// SysMenuImpl 菜单 服务层处理 +type SysMenuImpl struct { + // 菜单服务 + sysMenuRepository repository.ISysMenu + // 角色与菜单关联服务 + sysRoleMenuRepository repository.ISysRoleMenu + // 角色服务 + sysRoleRepository repository.ISysRole +} + +// SelectMenuList 查询系统菜单列表 +func (r *SysMenuImpl) SelectMenuList(sysMenu model.SysMenu, userId string) []model.SysMenu { + return r.sysMenuRepository.SelectMenuList(sysMenu, userId) +} + +// SelectMenuPermsByUserId 根据用户ID查询权限 +func (r *SysMenuImpl) SelectMenuPermsByUserId(userId string) []string { + return r.sysMenuRepository.SelectMenuPermsByUserId(userId) +} + +// SelectMenuTreeByUserId 根据用户ID查询菜单 +func (r *SysMenuImpl) SelectMenuTreeByUserId(userId string) []model.SysMenu { + sysMenus := r.sysMenuRepository.SelectMenuTreeByUserId(userId) + return r.parseDataToTree(sysMenus) +} + +// SelectMenuTreeSelectByUserId 根据用户ID查询菜单树结构信息 +func (r *SysMenuImpl) SelectMenuTreeSelectByUserId(sysMenu model.SysMenu, userId string) []vo.TreeSelect { + sysMenus := r.sysMenuRepository.SelectMenuList(sysMenu, userId) + menus := r.parseDataToTree(sysMenus) + tree := make([]vo.TreeSelect, 0) + for _, menu := range menus { + tree = append(tree, vo.SysMenuTreeSelect(menu)) + } + return tree +} + +// SelectMenuListByRoleId 根据角色ID查询菜单树信息 TODO +func (r *SysMenuImpl) SelectMenuListByRoleId(roleId string) []string { + roles := r.sysRoleRepository.SelectRoleByIds([]string{roleId}) + if len(roles) > 0 { + role := roles[0] + if role.RoleID == roleId { + return r.sysMenuRepository.SelectMenuListByRoleId( + role.RoleID, + role.MenuCheckStrictly == "1", + ) + } + } + return []string{} +} + +// SelectMenuById 根据菜单ID查询信息 +func (r *SysMenuImpl) SelectMenuById(menuId string) model.SysMenu { + if menuId == "" { + return model.SysMenu{} + } + menus := r.sysMenuRepository.SelectMenuByIds([]string{menuId}) + if len(menus) > 0 { + return menus[0] + } + return model.SysMenu{} +} + +// HasChildByMenuIdAndStatus 存在菜单子节点数量与状态 +func (r *SysMenuImpl) HasChildByMenuIdAndStatus(menuId, status string) int64 { + return r.sysMenuRepository.HasChildByMenuIdAndStatus(menuId, status) +} + +// CheckMenuExistRole 查询菜单是否存在角色 +func (r *SysMenuImpl) CheckMenuExistRole(menuId string) int64 { + return r.sysRoleMenuRepository.CheckMenuExistRole(menuId) +} + +// InsertMenu 新增菜单信息 +func (r *SysMenuImpl) InsertMenu(sysMenu model.SysMenu) string { + return r.sysMenuRepository.InsertMenu(sysMenu) +} + +// UpdateMenu 修改菜单信息 +func (r *SysMenuImpl) UpdateMenu(sysMenu model.SysMenu) int64 { + return r.sysMenuRepository.UpdateMenu(sysMenu) +} + +// DeleteMenuById 删除菜单管理信息 +func (r *SysMenuImpl) DeleteMenuById(menuId string) int64 { + // 删除菜单与角色关联 + r.sysRoleMenuRepository.DeleteMenuRole([]string{menuId}) + return r.sysMenuRepository.DeleteMenuById(menuId) +} + +// CheckUniqueMenuName 校验菜单名称是否唯一 +func (r *SysMenuImpl) CheckUniqueMenuName(menuName, parentId, menuId string) bool { + uniqueId := r.sysMenuRepository.CheckUniqueMenu(model.SysMenu{ + MenuName: menuName, + ParentID: parentId, + }) + if uniqueId == menuId { + return true + } + return uniqueId == "" +} + +// CheckUniqueMenuPath 校验路由地址是否唯一(针对目录和菜单) +func (r *SysMenuImpl) CheckUniqueMenuPath(path, menuId string) bool { + uniqueId := r.sysMenuRepository.CheckUniqueMenu(model.SysMenu{ + Path: path, + }) + if uniqueId == menuId { + return true + } + return uniqueId == "" +} + +// BuildRouteMenus 构建前端路由所需要的菜单 +func (r *SysMenuImpl) BuildRouteMenus(sysMenus []model.SysMenu, prefix string) []vo.Router { + routers := []vo.Router{} + for _, item := range sysMenus { + router := vo.Router{} + router.Name = r.getRouteName(item) + router.Path = r.getRouterPath(item) + router.Component = r.getComponent(item) + router.Meta = r.getRouteMeta(item) + + // 子项菜单 目录类型 非路径链接 + cMenus := item.Children + if len(cMenus) > 0 && item.MenuType == menu.TYPE_DIR && !regular.ValidHttp(item.Path) { + // 获取重定向地址 + redirectPrefix, redirectPath := r.getRouteRedirect( + cMenus, + router.Path, + prefix, + ) + router.Redirect = redirectPath + // 子菜单进入递归 + router.Children = r.BuildRouteMenus(cMenus, redirectPrefix) + } else if item.ParentID == "0" && item.IsFrame == common.STATUS_YES && item.MenuType == menu.TYPE_MENU { + // 父菜单 内部跳转 菜单类型 + menuPath := "/" + item.MenuID + childPath := menuPath + r.getRouterPath(item) + children := vo.Router{ + Name: r.getRouteName(item), + Path: childPath, + Component: item.Component, + Meta: r.getRouteMeta(item), + } + router.Meta.HideChildInMenu = true + router.Children = append(router.Children, children) + router.Name = item.MenuID + router.Path = menuPath + router.Redirect = childPath + router.Component = menu.COMPONENT_LAYOUT_BASIC + } else if item.ParentID == "0" && item.IsFrame == common.STATUS_YES && regular.ValidHttp(item.Path) { + // 父菜单 内部跳转 路径链接 + menuPath := "/" + item.MenuID + childPath := menuPath + r.getRouterPath(item) + children := vo.Router{ + Name: r.getRouteName(item), + Path: childPath, + Component: menu.COMPONENT_LAYOUT_LINK, + Meta: r.getRouteMeta(item), + } + router.Meta.HideChildInMenu = true + router.Children = append(router.Children, children) + router.Name = item.MenuID + router.Path = menuPath + router.Redirect = childPath + router.Component = menu.COMPONENT_LAYOUT_BASIC + } + + routers = append(routers, router) + } + return routers +} + +// getRouteName 获取路由名称 路径英文首字母大写 +func (r *SysMenuImpl) getRouteName(sysMenu model.SysMenu) string { + routerName := parse.ConvertToCamelCase(sysMenu.Path) + // 路径链接 + if regular.ValidHttp(sysMenu.Path) { + routerName = routerName[:5] + "Link" + } + // 拼上菜单ID防止name重名 + return routerName + "_" + sysMenu.MenuID +} + +// getRouterPath 获取路由地址 +func (r *SysMenuImpl) getRouterPath(sysMenu model.SysMenu) string { + routerPath := sysMenu.Path + + // 显式路径 + if routerPath == "" || strings.HasPrefix(routerPath, "/") { + return routerPath + } + + // 路径链接 内部跳转 + if regular.ValidHttp(routerPath) && sysMenu.IsFrame == common.STATUS_YES { + routerPath = regular.Replace(routerPath, `/^http(s)?:\/\/+/`, "") + routerPath = base64.StdEncoding.EncodeToString([]byte(routerPath)) + } + + // 父菜单 内部跳转 + if sysMenu.ParentID == "0" && sysMenu.IsFrame == common.STATUS_YES { + routerPath = "/" + routerPath + } + + return routerPath +} + +// getComponent 获取组件信息 +func (r *SysMenuImpl) getComponent(sysMenu model.SysMenu) string { + // 内部跳转 路径链接 + if sysMenu.IsFrame == common.STATUS_YES && regular.ValidHttp(sysMenu.Path) { + return menu.COMPONENT_LAYOUT_LINK + } + + // 非父菜单 目录类型 + if sysMenu.ParentID != "0" && sysMenu.MenuType == menu.TYPE_DIR { + return menu.COMPONENT_LAYOUT_BLANK + } + + // 组件路径 内部跳转 菜单类型 + if sysMenu.Component != "" && sysMenu.IsFrame == common.STATUS_YES && sysMenu.MenuType == menu.TYPE_MENU { + // 父菜单套外层布局 + if sysMenu.ParentID == "0" { + return menu.COMPONENT_LAYOUT_BASIC + } + return sysMenu.Component + } + + return menu.COMPONENT_LAYOUT_BASIC +} + +// getRouteMeta 获取路由元信息 +func (r *SysMenuImpl) getRouteMeta(sysMenu model.SysMenu) vo.RouterMeta { + meta := vo.RouterMeta{} + if sysMenu.Icon == "#" { + meta.Icon = "" + } else { + meta.Icon = sysMenu.Icon + } + meta.Title = sysMenu.MenuName + meta.HideChildInMenu = false + meta.HideInMenu = sysMenu.Visible == common.STATUS_NO + meta.Cache = sysMenu.IsCache == common.STATUS_YES + meta.Target = "" + + // 路径链接 非内部跳转 + if regular.ValidHttp(sysMenu.Path) && sysMenu.IsFrame == common.STATUS_NO { + meta.Target = "_blank" + } + + return meta +} + +// getRouteRedirect 获取路由重定向地址(针对目录) +// +// cMenus 子菜单数组 +// routerPath 当前菜单路径 +// prefix 菜单重定向路径前缀 +func (r *SysMenuImpl) getRouteRedirect(cMenus []model.SysMenu, routerPath string, prefix string) (string, string) { + redirectPath := "" + + // 重定向为首个显示并启用的子菜单 + var firstChild *model.SysMenu + for _, item := range cMenus { + if item.IsFrame == common.STATUS_YES && item.Visible == common.STATUS_YES { + firstChild = &item + break + } + } + + // 检查内嵌隐藏菜单是否可做重定向 + if firstChild == nil { + for _, item := range cMenus { + if item.IsFrame == common.STATUS_YES && item.Visible == common.STATUS_NO && strings.Contains(item.Path, menu.PATH_INLINE) { + firstChild = &item + break + } + } + } + + if firstChild != nil { + firstChildPath := r.getRouterPath(*firstChild) + if strings.HasPrefix(firstChildPath, "/") { + redirectPath = firstChildPath + } else { + // 拼接追加路径 + if !strings.HasPrefix(routerPath, "/") { + prefix += "/" + } + prefix = prefix + routerPath + redirectPath = prefix + "/" + firstChildPath + } + } + + return prefix, redirectPath +} + +// parseDataToTree 将数据解析为树结构,构建前端所需要下拉树结构 +func (r *SysMenuImpl) parseDataToTree(sysMenus []model.SysMenu) []model.SysMenu { + // 节点分组 + nodesMap := make(map[string][]model.SysMenu) + // 节点id + treeIds := []string{} + // 树节点 + tree := []model.SysMenu{} + + for _, item := range sysMenus { + parentID := item.ParentID + // 分组 + mapItem, ok := nodesMap[parentID] + if !ok { + mapItem = []model.SysMenu{} + } + mapItem = append(mapItem, item) + nodesMap[parentID] = mapItem + // 记录节点ID + treeIds = append(treeIds, item.MenuID) + } + + for key, value := range nodesMap { + // 选择不是节点ID的作为树节点 + found := false + for _, id := range treeIds { + if id == key { + found = true + break + } + } + if !found { + tree = append(tree, value...) + } + } + + for i, node := range tree { + iN := r.parseDataToTreeComponet(node, &nodesMap) + tree[i] = iN + } + + return tree +} + +// parseDataToTreeComponet 递归函数处理子节点 +func (r *SysMenuImpl) parseDataToTreeComponet(node model.SysMenu, nodesMap *map[string][]model.SysMenu) model.SysMenu { + id := node.MenuID + children, ok := (*nodesMap)[id] + if ok { + node.Children = children + } + if len(node.Children) > 0 { + for i, child := range node.Children { + icN := r.parseDataToTreeComponet(child, nodesMap) + node.Children[i] = icN + } + } + return node +} diff --git a/src/modules/system/service/sys_notice.go b/src/modules/system/service/sys_notice.go new file mode 100644 index 0000000..78244f0 --- /dev/null +++ b/src/modules/system/service/sys_notice.go @@ -0,0 +1,24 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysNotice 公告 服务层接口 +type ISysNotice interface { + // SelectNoticePage 分页查询公告列表 + SelectNoticePage(query map[string]any) map[string]any + + // SelectNoticeList 查询公告列表 + SelectNoticeList(sysNotice model.SysNotice) []model.SysNotice + + // SelectNoticeById 查询公告信息 + SelectNoticeById(noticeId string) model.SysNotice + + // InsertNotice 新增公告 + InsertNotice(sysNotice model.SysNotice) string + + // UpdateNotice 修改公告 + UpdateNotice(sysNotice model.SysNotice) int64 + + // DeleteNoticeByIds 批量删除公告信息 + DeleteNoticeByIds(noticeIds []string) (int64, error) +} diff --git a/src/modules/system/service/sys_notice.impl.go b/src/modules/system/service/sys_notice.impl.go new file mode 100644 index 0000000..55eb34f --- /dev/null +++ b/src/modules/system/service/sys_notice.impl.go @@ -0,0 +1,71 @@ +package service + +import ( + "errors" + + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysNoticeImpl 结构体 +var NewSysNoticeImpl = &SysNoticeImpl{ + sysNoticeRepository: repository.NewSysNoticeImpl, +} + +// SysNoticeImpl 公告 服务层处理 +type SysNoticeImpl struct { + // 公告服务 + sysNoticeRepository repository.ISysNotice +} + +// SelectNoticePage 分页查询公告列表 +func (r *SysNoticeImpl) SelectNoticePage(query map[string]any) map[string]any { + return r.sysNoticeRepository.SelectNoticePage(query) +} + +// SelectNoticeList 查询公告列表 +func (r *SysNoticeImpl) SelectNoticeList(sysNotice model.SysNotice) []model.SysNotice { + return r.sysNoticeRepository.SelectNoticeList(sysNotice) +} + +// SelectNoticeById 查询公告信息 +func (r *SysNoticeImpl) SelectNoticeById(noticeId string) model.SysNotice { + if noticeId == "" { + return model.SysNotice{} + } + configs := r.sysNoticeRepository.SelectNoticeByIds([]string{noticeId}) + if len(configs) > 0 { + return configs[0] + } + return model.SysNotice{} +} + +// InsertNotice 新增公告 +func (r *SysNoticeImpl) InsertNotice(sysNotice model.SysNotice) string { + return r.sysNoticeRepository.InsertNotice(sysNotice) +} + +// UpdateNotice 修改公告 +func (r *SysNoticeImpl) UpdateNotice(sysNotice model.SysNotice) int64 { + return r.sysNoticeRepository.UpdateNotice(sysNotice) +} + +// DeleteNoticeByIds 批量删除公告信息 +func (r *SysNoticeImpl) DeleteNoticeByIds(noticeIds []string) (int64, error) { + // 检查是否存在 + notices := r.sysNoticeRepository.SelectNoticeByIds(noticeIds) + if len(notices) <= 0 { + return 0, errors.New("没有权限访问公告信息数据!") + } + for _, notice := range notices { + // 检查是否为已删除 + if notice.DelFlag == "1" { + return 0, errors.New(notice.NoticeID + " 公告信息已经删除!") + } + } + if len(notices) == len(noticeIds) { + rows := r.sysNoticeRepository.DeleteNoticeByIds(noticeIds) + return rows, nil + } + return 0, errors.New("删除公告信息失败!") +} diff --git a/src/modules/system/service/sys_post.go b/src/modules/system/service/sys_post.go new file mode 100644 index 0000000..5903384 --- /dev/null +++ b/src/modules/system/service/sys_post.go @@ -0,0 +1,33 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysPost 岗位信息 服务层接口 +type ISysPost interface { + // SelectPostPage 查询岗位分页数据集合 + SelectPostPage(query map[string]any) map[string]any + + // SelectPostList 查询岗位数据集合 + SelectPostList(sysPost model.SysPost) []model.SysPost + + // SelectPostById 通过岗位ID查询岗位信息 + SelectPostById(postId string) model.SysPost + + // SelectPostListByUserId 根据用户ID获取岗位选择框列表 + SelectPostListByUserId(userId string) []model.SysPost + + // DeletePostByIds 批量删除岗位信息 + DeletePostByIds(postIds []string) (int64, error) + + // UpdatePost 修改岗位信息 + UpdatePost(sysPost model.SysPost) int64 + + // InsertPost 新增岗位信息 + InsertPost(sysPost model.SysPost) string + + // CheckUniquePostName 校验岗位名称 + CheckUniquePostName(postName, postId string) bool + + // CheckUniquePostCode 校验岗位编码 + CheckUniquePostCode(postCode, postId string) bool +} diff --git a/src/modules/system/service/sys_post.impl.go b/src/modules/system/service/sys_post.impl.go new file mode 100644 index 0000000..ccaed52 --- /dev/null +++ b/src/modules/system/service/sys_post.impl.go @@ -0,0 +1,103 @@ +package service + +import ( + "errors" + "fmt" + + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysPostImpl 结构体 +var NewSysPostImpl = &SysPostImpl{ + sysPostRepository: repository.NewSysPostImpl, + sysUserPostRepository: repository.NewSysUserPostImpl, +} + +// SysPostImpl 岗位表 服务层处理 +type SysPostImpl struct { + // 岗位服务 + sysPostRepository repository.ISysPost + // 用户与岗位关联服务 + sysUserPostRepository repository.ISysUserPost +} + +// SelectPostPage 查询岗位分页数据集合 +func (r *SysPostImpl) SelectPostPage(query map[string]any) map[string]any { + return r.sysPostRepository.SelectPostPage(query) +} + +// SelectPostList 查询岗位数据集合 +func (r *SysPostImpl) SelectPostList(sysPost model.SysPost) []model.SysPost { + return r.sysPostRepository.SelectPostList(sysPost) +} + +// SelectPostById 通过岗位ID查询岗位信息 +func (r *SysPostImpl) SelectPostById(postId string) model.SysPost { + if postId == "" { + return model.SysPost{} + } + posts := r.sysPostRepository.SelectPostByIds([]string{postId}) + if len(posts) > 0 { + return posts[0] + } + return model.SysPost{} +} + +// SelectPostListByUserId 根据用户ID获取岗位选择框列表 +func (r *SysPostImpl) SelectPostListByUserId(userId string) []model.SysPost { + return r.sysPostRepository.SelectPostListByUserId(userId) +} + +// DeletePostByIds 批量删除岗位信息 +func (r *SysPostImpl) DeletePostByIds(postIds []string) (int64, error) { + // 检查是否存在 + posts := r.sysPostRepository.SelectPostByIds(postIds) + if len(posts) <= 0 { + return 0, errors.New("没有权限访问岗位数据!") + } + for _, post := range posts { + useCount := r.sysUserPostRepository.CountUserPostByPostId(post.PostID) + if useCount > 0 { + msg := fmt.Sprintf("【%s】已分配给用户,不能删除", post.PostName) + return 0, errors.New(msg) + } + } + if len(posts) == len(postIds) { + rows := r.sysPostRepository.DeletePostByIds(postIds) + return rows, nil + } + return 0, errors.New("删除岗位信息失败!") +} + +// UpdatePost 修改岗位信息 +func (r *SysPostImpl) UpdatePost(sysPost model.SysPost) int64 { + return r.sysPostRepository.UpdatePost(sysPost) +} + +// InsertPost 新增岗位信息 +func (r *SysPostImpl) InsertPost(sysPost model.SysPost) string { + return r.sysPostRepository.InsertPost(sysPost) +} + +// CheckUniquePostName 校验岗位名称 +func (r *SysPostImpl) CheckUniquePostName(postName, postId string) bool { + uniqueId := r.sysPostRepository.CheckUniquePost(model.SysPost{ + PostName: postName, + }) + if uniqueId == postId { + return true + } + return uniqueId == "" +} + +// CheckUniquePostCode 校验岗位编码 +func (r *SysPostImpl) CheckUniquePostCode(postCode, postId string) bool { + uniqueId := r.sysPostRepository.CheckUniquePost(model.SysPost{ + PostCode: postCode, + }) + if uniqueId == postId { + return true + } + return uniqueId == "" +} diff --git a/src/modules/system/service/sys_role.go b/src/modules/system/service/sys_role.go new file mode 100644 index 0000000..cc4d301 --- /dev/null +++ b/src/modules/system/service/sys_role.go @@ -0,0 +1,42 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysRole 角色 服务层接口 +type ISysRole interface { + // SelectRolePage 根据条件分页查询角色数据 + SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectRoleList 根据条件查询角色数据 + SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole + + // SelectRoleListByUserId 根据用户ID获取角色选择框列表 + SelectRoleListByUserId(userId string) []model.SysRole + + // SelectRoleById 通过角色ID查询角色 + SelectRoleById(roleId string) model.SysRole + + // UpdateRole 修改角色信息 + UpdateRole(sysRole model.SysRole) int64 + + // InsertRole 新增角色信息 + InsertRole(sysRole model.SysRole) string + + // DeleteRoleByIds 批量删除角色信息 + DeleteRoleByIds(roleIds []string) (int64, error) + + // CheckUniqueRoleName 校验角色名称是否唯一 + CheckUniqueRoleName(roleName, roleId string) bool + + // CheckUniqueRoleKey 校验角色权限是否唯一 + CheckUniqueRoleKey(roleKey, roleId string) bool + + // AuthDataScope 修改数据权限信息 + AuthDataScope(sysRole model.SysRole) int64 + + // DeleteAuthUsers 批量取消授权用户角色 + DeleteAuthUsers(roleId string, userIds []string) int64 + + // InsertAuthUsers 批量新增授权用户角色 + InsertAuthUsers(roleId string, userIds []string) int64 +} diff --git a/src/modules/system/service/sys_role.impl.go b/src/modules/system/service/sys_role.impl.go new file mode 100644 index 0000000..5e093d1 --- /dev/null +++ b/src/modules/system/service/sys_role.impl.go @@ -0,0 +1,189 @@ +package service + +import ( + "errors" + "fmt" + + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysRoleImpl 结构体 +var NewSysRoleImpl = &SysRoleImpl{ + sysRoleRepository: repository.NewSysRoleImpl, + sysUserRoleRepository: repository.NewSysUserRoleImpl, + sysRoleDeptRepository: repository.NewSysRoleDeptImpl, + sysRoleMenuRepository: repository.NewSysRoleMenuImpl, +} + +// SysRoleImpl 角色 服务层处理 +type SysRoleImpl struct { + // 角色服务 + sysRoleRepository repository.ISysRole + // 用户与角色关联服务 + sysUserRoleRepository repository.ISysUserRole + // 角色与部门关联服务 + sysRoleDeptRepository repository.ISysRoleDept + // 角色与菜单关联服务 + sysRoleMenuRepository repository.ISysRoleMenu +} + +// SelectRolePage 根据条件分页查询角色数据 +func (r *SysRoleImpl) SelectRolePage(query map[string]any, dataScopeSQL string) map[string]any { + return r.sysRoleRepository.SelectRolePage(query, dataScopeSQL) +} + +// SelectRoleList 根据条件查询角色数据 +func (r *SysRoleImpl) SelectRoleList(sysRole model.SysRole, dataScopeSQL string) []model.SysRole { + return r.sysRoleRepository.SelectRoleList(sysRole, dataScopeSQL) +} + +// SelectRoleListByUserId 根据用户ID获取角色选择框列表 +func (r *SysRoleImpl) SelectRoleListByUserId(userId string) []model.SysRole { + return r.sysRoleRepository.SelectRoleListByUserId(userId) +} + +// SelectRoleById 通过角色ID查询角色 +func (r *SysRoleImpl) SelectRoleById(roleId string) model.SysRole { + if roleId == "" { + return model.SysRole{} + } + posts := r.sysRoleRepository.SelectRoleByIds([]string{roleId}) + if len(posts) > 0 { + return posts[0] + } + return model.SysRole{} +} + +// UpdateRole 修改角色信息 +func (r *SysRoleImpl) UpdateRole(sysRole model.SysRole) int64 { + rows := r.sysRoleRepository.UpdateRole(sysRole) + if rows > 0 { + // 删除角色与菜单关联 + r.sysRoleMenuRepository.DeleteRoleMenu([]string{sysRole.RoleID}) + if len(sysRole.MenuIds) > 0 { + r.insertRoleMenu(sysRole.RoleID, sysRole.MenuIds) + } + } + return rows +} + +// InsertRole 新增角色信息 +func (r *SysRoleImpl) InsertRole(sysRole model.SysRole) string { + insertId := r.sysRoleRepository.InsertRole(sysRole) + if insertId != "" && len(sysRole.MenuIds) > 0 { + r.insertRoleMenu(insertId, sysRole.MenuIds) + } + return insertId +} + +// insertRoleMenu 新增角色菜单信息 +func (r *SysRoleImpl) insertRoleMenu(roleId string, menuIds []string) int64 { + if roleId == "" || len(menuIds) <= 0 { + return 0 + } + + sysRoleMenus := []model.SysRoleMenu{} + for _, menuId := range menuIds { + if menuId == "" { + continue + } + sysRoleMenus = append(sysRoleMenus, model.NewSysRoleMenu(roleId, menuId)) + } + + return r.sysRoleMenuRepository.BatchRoleMenu(sysRoleMenus) +} + +// DeleteRoleByIds 批量删除角色信息 +func (r *SysRoleImpl) DeleteRoleByIds(roleIds []string) (int64, error) { + // 检查是否存在 + roles := r.sysRoleRepository.SelectRoleByIds(roleIds) + if len(roles) <= 0 { + return 0, errors.New("没有权限访问角色数据!") + } + for _, role := range roles { + // 检查是否为已删除 + if role.DelFlag == "1" { + return 0, errors.New(role.RoleID + " 角色信息已经删除!") + } + // 检查分配用户 + userCount := r.sysUserRoleRepository.CountUserRoleByRoleId(role.RoleID) + if userCount > 0 { + msg := fmt.Sprintf("【%s】已分配给用户,不能删除", role.RoleName) + return 0, errors.New(msg) + } + } + if len(roles) == len(roleIds) { + // 删除角色与菜单关联 + r.sysRoleMenuRepository.DeleteRoleMenu(roleIds) + // 删除角色与部门关联 + r.sysRoleDeptRepository.DeleteRoleDept(roleIds) + rows := r.sysRoleRepository.DeleteRoleByIds(roleIds) + return rows, nil + } + return 0, errors.New("删除角色信息失败!") +} + +// CheckUniqueRoleName 校验角色名称是否唯一 +func (r *SysRoleImpl) CheckUniqueRoleName(roleName, roleId string) bool { + uniqueId := r.sysRoleRepository.CheckUniqueRole(model.SysRole{ + RoleName: roleName, + }) + if uniqueId == roleId { + return true + } + return uniqueId == "" +} + +// CheckUniqueRoleKey 校验角色权限是否唯一 +func (r *SysRoleImpl) CheckUniqueRoleKey(roleKey, roleId string) bool { + uniqueId := r.sysRoleRepository.CheckUniqueRole(model.SysRole{ + RoleKey: roleKey, + }) + if uniqueId == roleId { + return true + } + return uniqueId == "" +} + +// AuthDataScope 修改数据权限信息 +func (r *SysRoleImpl) AuthDataScope(sysRole model.SysRole) int64 { + // 修改角色信息 + rows := r.sysRoleRepository.UpdateRole(sysRole) + // 删除角色与部门关联 + r.sysRoleDeptRepository.DeleteRoleDept([]string{sysRole.RoleID}) + // 新增角色和部门信息 + if sysRole.DataScope == "2" && len(sysRole.DeptIds) > 0 { + sysRoleDepts := []model.SysRoleDept{} + for _, deptId := range sysRole.DeptIds { + if deptId == "" { + continue + } + sysRoleDepts = append(sysRoleDepts, model.NewSysRoleDept(sysRole.RoleID, deptId)) + } + rows += r.sysRoleDeptRepository.BatchRoleDept(sysRoleDepts) + } + return rows +} + +// DeleteAuthUsers 批量取消授权用户角色 +func (r *SysRoleImpl) DeleteAuthUsers(roleId string, userIds []string) int64 { + return r.sysUserRoleRepository.DeleteUserRoleByRoleId(roleId, userIds) +} + +// InsertAuthUsers 批量新增授权用户角色 +func (r *SysRoleImpl) InsertAuthUsers(roleId string, userIds []string) int64 { + if roleId == "" || len(userIds) <= 0 { + return 0 + } + + sysUserRoles := []model.SysUserRole{} + for _, userId := range userIds { + if userId == "" { + continue + } + sysUserRoles = append(sysUserRoles, model.NewSysUserRole(userId, roleId)) + } + + return r.sysUserRoleRepository.BatchUserRole(sysUserRoles) +} diff --git a/src/modules/system/service/sys_user.go b/src/modules/system/service/sys_user.go new file mode 100644 index 0000000..23fd5fb --- /dev/null +++ b/src/modules/system/service/sys_user.go @@ -0,0 +1,45 @@ +package service + +import "ems.agt/src/modules/system/model" + +// ISysUser 用户 服务层接口 +type ISysUser interface { + // SelectUserPage 根据条件分页查询用户列表 + SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectUserList 根据条件查询用户列表 + SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser + + // SelectAllocatedPage 根据条件分页查询分配用户角色列表 + SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any + + // SelectUserByUserName 通过用户名查询用户 + SelectUserByUserName(userName string) model.SysUser + + // SelectUserById 通过用户ID查询用户 + SelectUserById(userId string) model.SysUser + + // InsertUser 新增用户信息 + InsertUser(sysUser model.SysUser) string + + // UpdateUser 修改用户信息 + UpdateUser(sysUser model.SysUser) int64 + + // UpdateUserAndRolePost 修改用户信息同时更新角色和岗位 + UpdateUserAndRolePost(sysUser model.SysUser) int64 + + // DeleteUserByIds 批量删除用户信息 + DeleteUserByIds(userIds []string) (int64, error) + + // CheckUniqueUserName 校验用户名称是否唯一 + CheckUniqueUserName(userName, userId string) bool + + // CheckUniquePhone 校验手机号码是否唯一 + CheckUniquePhone(phonenumber, userId string) bool + + // CheckUniqueEmail 校验email是否唯一 + CheckUniqueEmail(email, userId string) bool + + // ImportUser 导入用户数据 + ImportUser(rows []map[string]string, isUpdateSupport bool, operName string) string +} diff --git a/src/modules/system/service/sys_user.impl.go b/src/modules/system/service/sys_user.impl.go new file mode 100644 index 0000000..9f2cfa4 --- /dev/null +++ b/src/modules/system/service/sys_user.impl.go @@ -0,0 +1,322 @@ +package service + +import ( + "errors" + "fmt" + "strings" + + "ems.agt/src/framework/constants/admin" + "ems.agt/src/framework/constants/common" + "ems.agt/src/framework/utils/regular" + "ems.agt/src/modules/system/model" + "ems.agt/src/modules/system/repository" +) + +// 实例化服务层 SysUserImpl 结构体 +var NewSysUserImpl = &SysUserImpl{ + sysUserRepository: repository.NewSysUserImpl, + sysUserRoleRepository: repository.NewSysUserRoleImpl, + sysUserPostRepository: repository.NewSysUserPostImpl, + sysDictDataService: NewSysDictDataImpl, + sysConfigService: NewSysConfigImpl, +} + +// SysUserImpl 用户 服务层处理 +type SysUserImpl struct { + // 用户服务 + sysUserRepository repository.ISysUser + // 用户与角色服务 + sysUserRoleRepository repository.ISysUserRole + // 用户与岗位服务 + sysUserPostRepository repository.ISysUserPost + // 字典数据服务 + sysDictDataService ISysDictData + // 参数配置服务 + sysConfigService ISysConfig +} + +// SelectUserPage 根据条件分页查询用户列表 +func (r *SysUserImpl) SelectUserPage(query map[string]any, dataScopeSQL string) map[string]any { + return r.sysUserRepository.SelectUserPage(query, dataScopeSQL) +} + +// SelectUserList 根据条件查询用户列表 +func (r *SysUserImpl) SelectUserList(sysUser model.SysUser, dataScopeSQL string) []model.SysUser { + return []model.SysUser{} +} + +// SelectAllocatedPage 根据条件分页查询分配用户角色列表 +func (r *SysUserImpl) SelectAllocatedPage(query map[string]any, dataScopeSQL string) map[string]any { + return r.sysUserRepository.SelectAllocatedPage(query, dataScopeSQL) +} + +// SelectUserByUserName 通过用户名查询用户 +func (r *SysUserImpl) SelectUserByUserName(userName string) model.SysUser { + return r.sysUserRepository.SelectUserByUserName(userName) +} + +// SelectUserById 通过用户ID查询用户 +func (r *SysUserImpl) SelectUserById(userId string) model.SysUser { + if userId == "" { + return model.SysUser{} + } + users := r.sysUserRepository.SelectUserByIds([]string{userId}) + if len(users) > 0 { + return users[0] + } + return model.SysUser{} +} + +// InsertUser 新增用户信息 +func (r *SysUserImpl) InsertUser(sysUser model.SysUser) string { + // 新增用户信息 + insertId := r.sysUserRepository.InsertUser(sysUser) + if insertId != "" { + // 新增用户角色信息 + r.insertUserRole(insertId, sysUser.RoleIDs) + // 新增用户岗位信息 + r.insertUserPost(insertId, sysUser.PostIDs) + } + return insertId +} + +// insertUserRole 新增用户角色信息 +func (r *SysUserImpl) insertUserRole(userId string, roleIds []string) int64 { + if userId == "" || len(roleIds) <= 0 { + return 0 + } + + sysUserRoles := []model.SysUserRole{} + for _, roleId := range roleIds { + // 管理员角色禁止操作,只能通过配置指定用户ID分配 + if roleId == "" || roleId == admin.ROLE_ID { + continue + } + sysUserRoles = append(sysUserRoles, model.NewSysUserRole(userId, roleId)) + } + + return r.sysUserRoleRepository.BatchUserRole(sysUserRoles) +} + +// insertUserPost 新增用户岗位信息 +func (r *SysUserImpl) insertUserPost(userId string, postIds []string) int64 { + if userId == "" || len(postIds) <= 0 { + return 0 + } + + sysUserPosts := []model.SysUserPost{} + for _, postId := range postIds { + if postId == "" { + continue + } + sysUserPosts = append(sysUserPosts, model.NewSysUserPost(userId, postId)) + } + + return r.sysUserPostRepository.BatchUserPost(sysUserPosts) +} + +// UpdateUser 修改用户信息 +func (r *SysUserImpl) UpdateUser(sysUser model.SysUser) int64 { + return r.sysUserRepository.UpdateUser(sysUser) +} + +// UpdateUserAndRolePost 修改用户信息同时更新角色和岗位 +func (r *SysUserImpl) UpdateUserAndRolePost(sysUser model.SysUser) int64 { + // 删除用户与角色关联 + r.sysUserRoleRepository.DeleteUserRole([]string{sysUser.UserID}) + // 新增用户角色信息 + r.insertUserRole(sysUser.UserID, sysUser.RoleIDs) + // 删除用户与岗位关联 + r.sysUserPostRepository.DeleteUserPost([]string{sysUser.UserID}) + // 新增用户岗位信息 + r.insertUserPost(sysUser.UserID, sysUser.PostIDs) + return r.sysUserRepository.UpdateUser(sysUser) +} + +// DeleteUserByIds 批量删除用户信息 +func (r *SysUserImpl) DeleteUserByIds(userIds []string) (int64, error) { + // 检查是否存在 + users := r.sysUserRepository.SelectUserByIds(userIds) + if len(users) <= 0 { + return 0, errors.New("没有权限访问用户数据!") + } + if len(users) == len(userIds) { + // 删除用户与角色关联 + r.sysUserRoleRepository.DeleteUserRole(userIds) + // 删除用户与岗位关联 + r.sysUserPostRepository.DeleteUserPost(userIds) + // ... 注意其他userId进行关联的表 + // 删除用户 + rows := r.sysUserRepository.DeleteUserByIds(userIds) + return rows, nil + } + return 0, errors.New("删除用户信息失败!") +} + +// CheckUniqueUserName 校验用户名称是否唯一 +func (r *SysUserImpl) CheckUniqueUserName(userName, userId string) bool { + uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{ + UserName: userName, + }) + if uniqueId == userId { + return true + } + return uniqueId == "" +} + +// CheckUniquePhone 校验手机号码是否唯一 +func (r *SysUserImpl) CheckUniquePhone(phonenumber, userId string) bool { + uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{ + PhoneNumber: phonenumber, + }) + if uniqueId == userId { + return true + } + return uniqueId == "" +} + +// CheckUniqueEmail 校验email是否唯一 +func (r *SysUserImpl) CheckUniqueEmail(email, userId string) bool { + uniqueId := r.sysUserRepository.CheckUniqueUser(model.SysUser{ + Email: email, + }) + if uniqueId == userId { + return true + } + return uniqueId == "" +} + +// ImportUser 导入用户数据 +func (r *SysUserImpl) ImportUser(rows []map[string]string, isUpdateSupport bool, operName string) string { + // 读取默认初始密码 + initPassword := r.sysConfigService.SelectConfigValueByKey("sys.user.initPassword") + // 读取用户性别字典数据 + dictSysUserSex := r.sysDictDataService.SelectDictDataByType("sys_user_sex") + + // 导入记录 + successNum := 0 + failureNum := 0 + successMsgArr := []string{} + failureMsgArr := []string{} + mustItemArr := []string{"C", "D"} + for _, row := range rows { + // 检查必填列 + ownItem := true + for _, item := range mustItemArr { + if v, ok := row[item]; !ok || v == "" { + ownItem = false + break + } + } + if !ownItem { + mustItemArrStr := strings.Join(mustItemArr, "、") + failureNum++ + failureMsgArr = append(failureMsgArr, fmt.Sprintf("表格中必填列表项,%s}", mustItemArrStr)) + continue + } + + // 用户性别转值 + sysUserSex := "0" + for _, v := range dictSysUserSex { + if row["G"] == v.DictLabel { + sysUserSex = v.DictValue + break + } + } + sysUserStatus := common.STATUS_NO + if row["H"] == "正常" { + sysUserStatus = common.STATUS_YES + } + + // 构建用户实体信息 + newSysUser := model.SysUser{ + UserType: "sys", + Password: initPassword, + DeptID: row["B"], + UserName: row["C"], + NickName: row["D"], + PhoneNumber: row["F"], + Email: row["E"], + Status: sysUserStatus, + Sex: sysUserSex, + } + + // 检查手机号码格式并判断是否唯一 + if newSysUser.PhoneNumber != "" { + if regular.ValidMobile(newSysUser.PhoneNumber) { + uniquePhone := r.CheckUniquePhone(newSysUser.PhoneNumber, "") + if !uniquePhone { + msg := fmt.Sprintf("序号:%s 手机号码 %s 已存在", row["A"], row["F"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + continue + } + } else { + msg := fmt.Sprintf("序号:%s 手机号码 %s 格式错误", row["A"], row["F"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + continue + } + } + + // 检查邮箱格式并判断是否唯一 + if newSysUser.Email != "" { + if regular.ValidEmail(newSysUser.Email) { + uniqueEmail := r.CheckUniqueEmail(newSysUser.Email, "") + if !uniqueEmail { + msg := fmt.Sprintf("序号:%s 用户邮箱 %s 已存在", row["A"], row["E"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + continue + } + } else { + msg := fmt.Sprintf("序号:%s 用户邮箱 %s 格式错误", row["A"], row["E"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + continue + } + } + + // 验证是否存在这个用户 + userInfo := r.sysUserRepository.SelectUserByUserName(newSysUser.UserName) + if userInfo.UserName != newSysUser.UserName { + newSysUser.CreateBy = operName + insertId := r.InsertUser(newSysUser) + if insertId != "" { + msg := fmt.Sprintf("序号:%s 登录名称 %s 导入成功", row["A"], row["C"]) + successNum++ + successMsgArr = append(successMsgArr, msg) + } else { + msg := fmt.Sprintf("序号:%s 登录名称 %s 导入失败", row["A"], row["E"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + } + continue + } + + // 如果用户已存在 同时 是否更新支持 + if userInfo.UserName == newSysUser.UserName && isUpdateSupport { + newSysUser.UserID = userInfo.UserID + newSysUser.UpdateBy = operName + rows := r.UpdateUser(newSysUser) + if rows > 0 { + msg := fmt.Sprintf("序号:%s 登录名称 %s 更新成功", row["A"], row["C"]) + successNum++ + successMsgArr = append(successMsgArr, msg) + } else { + msg := fmt.Sprintf("序号:%s 登录名称 %s 更新失败", row["A"], row["E"]) + failureNum++ + failureMsgArr = append(failureMsgArr, msg) + } + continue + } + } + + if failureNum > 0 { + failureMsgArr = append([]string{fmt.Sprintf("很抱歉,导入失败!共 %d 条数据格式不正确,错误如下:", failureNum)}, failureMsgArr...) + return strings.Join(failureMsgArr, "
") + } + + successMsgArr = append([]string{fmt.Sprintf("恭喜您,数据已全部导入成功!共 %d 条,数据如下:", successNum)}, successMsgArr...) + return strings.Join(successMsgArr, "
") +} diff --git a/src/modules/system/system.go b/src/modules/system/system.go new file mode 100644 index 0000000..07d226c --- /dev/null +++ b/src/modules/system/system.go @@ -0,0 +1,466 @@ +package system + +import ( + "ems.agt/src/framework/logger" + "ems.agt/src/framework/middleware" + "ems.agt/src/framework/middleware/collectlogs" + "ems.agt/src/framework/middleware/repeat" + "ems.agt/src/modules/system/controller" + "ems.agt/src/modules/system/service" + + "github.com/gin-gonic/gin" +) + +// Setup 模块路由注册 +func Setup(router *gin.Engine) { + logger.Infof("开始加载 ====> system 模块路由") + + // 启动时需要的初始参数 + InitLoad() + + // 参数配置信息 + sysConfigGroup := router.Group("/system/config") + { + sysConfigGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:list"}}), + controller.NewSysConfig.List, + ) + sysConfigGroup.GET("/:configId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:query"}}), + controller.NewSysConfig.Info, + ) + sysConfigGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysConfig.Add, + ) + sysConfigGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysConfig.Edit, + ) + sysConfigGroup.DELETE("/:configIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysConfig.Remove, + ) + sysConfigGroup.PUT("/refreshCache", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysConfig.RefreshCache, + ) + sysConfigGroup.GET("/configKey/:configKey", controller.NewSysConfig.ConfigKey) + sysConfigGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:config:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysConfig.Export, + ) + } + + // 部门信息 + sysDeptGroup := router.Group("/system/dept") + { + sysDeptGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:list"}}), + controller.NewSysDept.List, + ) + sysDeptGroup.GET("/:deptId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:query"}}), + controller.NewSysDept.Info, + ) + sysDeptGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("部门信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysDept.Add, + ) + sysDeptGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("部门信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysDept.Edit, + ) + sysDeptGroup.DELETE("/:deptId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("部门信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysDept.Remove, + ) + sysDeptGroup.GET("/list/exclude/:deptId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:list"}}), + controller.NewSysDept.ExcludeChild, + ) + sysDeptGroup.GET("/treeSelect", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:list", "system:user:list"}}), + controller.NewSysDept.TreeSelect, + ) + sysDeptGroup.GET("/roleDeptTreeSelect/:roleId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dept:query"}}), + controller.NewSysDept.RoleDeptTreeSelect, + ) + } + + // 字典数据信息 + sysDictDataGroup := router.Group("/system/dict/data") + { + sysDictDataGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:list"}}), + controller.NewSysDictData.List, + ) + sysDictDataGroup.GET("/:dictCode", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}), + controller.NewSysDictData.Info, + ) + sysDictDataGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典数据信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysDictData.Add, + ) + sysDictDataGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典数据信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysDictData.Edit, + ) + sysDictDataGroup.DELETE("/:dictCodes", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典数据信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysDictData.Remove, + ) + sysDictDataGroup.GET("/type/:dictType", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}), + controller.NewSysDictData.DictType, + ) + sysDictDataGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysDictData.Export, + ) + } + + // 字典类型信息 + sysDictTypeGroup := router.Group("/system/dict/type") + { + sysDictTypeGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:list"}}), + controller.NewSysDictType.List, + ) + sysDictTypeGroup.GET("/:dictId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}), + controller.NewSysDictType.Info, + ) + sysDictTypeGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysDictType.Add, + ) + sysDictTypeGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysDictType.Edit, + ) + sysDictTypeGroup.DELETE("/:dictIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysDictType.Remove, + ) + sysDictTypeGroup.PUT("/refreshCache", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysDictType.RefreshCache, + ) + sysDictTypeGroup.GET("/getDictOptionselect", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:query"}}), + controller.NewSysDictType.DictOptionselect, + ) + sysDictTypeGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:dict:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("字典类型信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysDictType.Export, + ) + } + + // 菜单信息 + sysMenuGroup := router.Group("/system/menu") + { + sysMenuGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:list"}}), + controller.NewSysMenu.List, + ) + sysMenuGroup.GET("/:menuId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:query"}}), + controller.NewSysMenu.Info, + ) + sysMenuGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("菜单信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysMenu.Add, + ) + sysMenuGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("菜单信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysMenu.Edit, + ) + sysMenuGroup.DELETE("/:menuId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("菜单信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysMenu.Remove, + ) + sysMenuGroup.GET("/treeSelect", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:list"}}), + controller.NewSysMenu.TreeSelect, + ) + sysMenuGroup.GET("/roleMenuTreeSelect/:roleId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:menu:list"}}), + controller.NewSysMenu.RoleMenuTreeSelect, + ) + } + + // 通知公告信息 + sysNoticeGroup := router.Group("/system/notice") + { + sysNoticeGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:notice:list"}}), + controller.NewSysNotice.List, + ) + sysNoticeGroup.GET("/:noticeId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:notice:query"}}), + controller.NewSysNotice.Info, + ) + sysNoticeGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:notice:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysNotice.Add, + ) + sysNoticeGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:notice:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysNotice.Edit, + ) + sysNoticeGroup.DELETE("/:noticeIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:notice:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("参数配置信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysNotice.Remove, + ) + } + + // 岗位信息 + sysPostGroup := router.Group("/system/post") + { + sysPostGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:list"}}), + controller.NewSysPost.List, + ) + sysPostGroup.GET("/:postId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:query"}}), + controller.NewSysPost.Info, + ) + sysPostGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("岗位信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysPost.Add, + ) + sysPostGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("岗位信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysPost.Edit, + ) + sysPostGroup.DELETE("/:postIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("岗位信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysPost.Remove, + ) + sysPostGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:post:export"}}), + controller.NewSysPost.Export, + ) + } + + // 个人信息 + sysProfileGroup := router.Group("/system/user/profile") + { + sysProfileGroup.GET("", + middleware.PreAuthorize(nil), + controller.NewSysProfile.Info, + ) + sysProfileGroup.PUT("", + middleware.PreAuthorize(nil), + controller.NewSysProfile.UpdateProfile, + ) + sysProfileGroup.PUT("/updatePwd", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("个人信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysProfile.UpdatePwd, + ) + sysProfileGroup.POST("/avatar", + middleware.PreAuthorize(nil), + collectlogs.OperateLog(collectlogs.OptionNew("用户头像", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysProfile.Avatar, + ) + } + + // 角色信息 + sysRoleGroup := router.Group("/system/role") + { + sysRoleGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:list"}}), + controller.NewSysRole.List, + ) + sysRoleGroup.GET("/:roleId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:query"}}), + controller.NewSysRole.Info, + ) + sysRoleGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysRole.Add, + ) + sysRoleGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysRole.Edit, + ) + sysRoleGroup.DELETE("/:roleIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysRole.Remove, + ) + sysRoleGroup.PUT("/changeStatus", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:role:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysRole.Status, + ) + sysRoleGroup.PUT("/dataScope", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysRole.DataScope, + ) + sysRoleGroup.GET("/authUser/allocatedList", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:list"}}), + controller.NewSysRole.AuthUserAllocatedList, + ) + sysRoleGroup.PUT("/authUser/checked", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_GRANT)), + controller.NewSysRole.AuthUserChecked, + ) + sysRoleGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("角色信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysRole.Export, + ) + } + + // 用户信息 + sysUserGroup := router.Group("/system/user") + { + sysUserGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:list"}}), + controller.NewSysUser.List, + ) + sysUserGroup.GET("/:userId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:query"}}), + controller.NewSysUser.Info, + ) + sysUserGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysUser.Add, + ) + sysUserGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysUser.Edit, + ) + sysUserGroup.DELETE("/:userIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysUser.Remove, + ) + sysUserGroup.PUT("/resetPwd", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:resetPwd"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysUser.ResetPwd, + ) + sysUserGroup.PUT("/changeStatus", + repeat.RepeatSubmit(5), + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysUser.Status, + ) + sysUserGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysUser.Export, + ) + sysUserGroup.GET("/importTemplate", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:import"}}), + controller.NewSysUser.Template, + ) + sysUserGroup.POST("/importData", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:user:import"}}), + collectlogs.OperateLog(collectlogs.OptionNew("用户信息", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysUser.ImportData, + ) + } + + // 操作日志记录信息 + sysOperLogGroup := router.Group("/system/log/operate") + { + sysOperLogGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:list"}}), + controller.NewSysLogOperate.List, + ) + sysOperLogGroup.DELETE("/:operIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("操作日志", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysLogOperate.Remove, + ) + sysOperLogGroup.DELETE("/clean", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("操作日志", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysLogOperate.Clean, + ) + sysOperLogGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:operate:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("操作日志", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysLogOperate.Export, + ) + } + + // 系统登录日志信息 + sysLogininforGroup := router.Group("/system/log/login") + { + sysLogininforGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:list"}}), + controller.NewSysLogLogin.List, + ) + sysLogininforGroup.DELETE("/:loginIds", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("系统登录信息", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysLogLogin.Remove, + ) + sysLogininforGroup.DELETE("/clean", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("系统登录信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysLogLogin.Clean, + ) + sysLogininforGroup.PUT("/unlock/:userName", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:unlock"}}), + collectlogs.OperateLog(collectlogs.OptionNew("系统登录信息", collectlogs.BUSINESS_TYPE_CLEAN)), + controller.NewSysLogLogin.Unlock, + ) + sysLogininforGroup.POST("/export", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:log:login:export"}}), + collectlogs.OperateLog(collectlogs.OptionNew("系统登录信息", collectlogs.BUSINESS_TYPE_EXPORT)), + controller.NewSysLogLogin.Export, + ) + } +} + +// InitLoad 初始参数 +func InitLoad() { + // 启动时,刷新缓存-参数配置 + service.NewSysConfigImpl.ResetConfigCache() + // 启动时,刷新缓存-字典类型数据 + service.NewSysDictTypeImpl.ResetDictCache() +}