From 8edc9f4cc33fac03978aa8d301faa3c4b0b82e63 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Tue, 5 Sep 2023 19:03:51 +0800 Subject: [PATCH 1/7] =?UTF-8?q?add=20=E7=BC=93=E5=AD=98=E7=9A=84key?= =?UTF-8?q?=E5=B8=B8=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/constants/cachekey/cachekey.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 lib/core/constants/cachekey/cachekey.go diff --git a/lib/core/constants/cachekey/cachekey.go b/lib/core/constants/cachekey/cachekey.go new file mode 100644 index 00000000..0e69445f --- /dev/null +++ b/lib/core/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:" From 2ab01037d8855a5833426697f2a5f4681db23366 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Tue, 5 Sep 2023 19:04:08 +0800 Subject: [PATCH 2/7] =?UTF-8?q?add=20=E8=8E=B7=E5=8F=96=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E5=89=8D=E7=BC=80=E7=9A=84=E6=89=80=E6=9C=89=E9=94=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/cache/lcoal.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/core/cache/lcoal.go b/lib/core/cache/lcoal.go index 62f776a4..06b5628f 100644 --- a/lib/core/cache/lcoal.go +++ b/lib/core/cache/lcoal.go @@ -1,6 +1,7 @@ package cache import ( + "strings" "time" "github.com/patrickmn/go-cache" @@ -24,6 +25,17 @@ func DeleteLocal(key string) { cNoExpiration.Delete(key) } +// 获取指定前缀的所有键 +func GetLocalKeys(prefix string) []string { + var keys []string + for key := range cNoExpiration.Items() { + if strings.HasPrefix(key, prefix) { + keys = append(keys, key) + } + } + return keys +} + // 创建一个全局的过期缓存对象 var cTTL = cache.New(6*time.Hour, 12*time.Hour) From ffe4545386064a413d2b53e6b63eacf6e8070d28 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Tue, 5 Sep 2023 19:04:38 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E5=8F=82=E6=95=B0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E6=8E=A5=E5=8F=A3=E6=B7=BB=E5=8A=A0=E5=88=B0?= =?UTF-8?q?=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/sys_config/api_sys_config.go | 230 +++++++++++++ features/sys_config/model/sys_config.go | 25 ++ .../sys_config/service/repo_sys_config.go | 321 ++++++++++++++++++ .../sys_config/service/service_sys_config.go | 155 +++++++++ lib/routes/routes.go | 6 + 5 files changed, 737 insertions(+) create mode 100644 features/sys_config/api_sys_config.go create mode 100644 features/sys_config/model/sys_config.go create mode 100644 features/sys_config/service/repo_sys_config.go create mode 100644 features/sys_config/service/service_sys_config.go diff --git a/features/sys_config/api_sys_config.go b/features/sys_config/api_sys_config.go new file mode 100644 index 00000000..37019f94 --- /dev/null +++ b/features/sys_config/api_sys_config.go @@ -0,0 +1,230 @@ +package sysconfig + +import ( + "fmt" + "net/http" + "strings" + + "ems.agt/features/sys_config/model" + "ems.agt/features/sys_config/service" + "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" +) + +// 参数配置信息接口添加到路由 +func Routers() []services.RouterItem { + // 实例化控制层 SysConfigApi 结构体 + var apis = &SysConfigApi{ + sysConfigService: service.NewServiceSysConfig, + } + + rs := [...]services.RouterItem{ + { + Method: "GET", + Pattern: "/configs", + Handler: apis.List, + Middleware: midware.Authorize(nil), + }, + { + Method: "GET", + Pattern: "/config/{configId}", + Handler: apis.Info, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/config", + Handler: apis.Add, + Middleware: midware.Authorize(nil), + }, + { + Method: "PUT", + Pattern: "/config", + Handler: apis.Edit, + Middleware: midware.Authorize(nil), + }, + { + Method: "DELETE", + Pattern: "/config/{configIds}", + Handler: apis.Remove, + Middleware: midware.Authorize(nil), + }, + { + Method: "PUT", + Pattern: "/config/refreshCache", + Handler: apis.RefreshCache, + Middleware: midware.Authorize(nil), + }, + { + Method: "GET", + Pattern: "/config/configKey/{configKey}", + Handler: apis.ConfigKey, + Middleware: midware.Authorize(nil), + }, + // 添加更多的 Router 对象... + } + + // 生成两组前缀路由 + rsPrefix := []services.RouterItem{} + for _, v := range rs { + path := "/configManage/{apiVersion}" + v.Pattern + // 固定前缀 + v.Pattern = config.DefaultUriPrefix + path + rsPrefix = append(rsPrefix, v) + // 可配置 + v.Pattern = config.UriPrefix + path + rsPrefix = append(rsPrefix, v) + } + return rsPrefix +} + +// 参数配置信息 +// +// PATH /configManage +type SysConfigApi struct { + // 参数配置服务 + sysConfigService *service.ServiceSysConfig +} + +// 参数配置列表 +// +// GET /list +func (s *SysConfigApi) List(w http.ResponseWriter, r *http.Request) { + querys := ctx.QueryMap(r) + data := s.sysConfigService.SelectConfigPage(querys) + ctx.JSON(w, 200, result.Ok(data)) +} + +// 参数配置信息 +// +// GET /:configId +func (s *SysConfigApi) Info(w http.ResponseWriter, r *http.Request) { + configId := ctx.Param(r, "configId") + if configId == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysConfigService.SelectConfigById(configId) + if data.ConfigID == configId { + ctx.JSON(w, 200, result.OkData(data)) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} + +// 参数配置新增 +// +// POST / +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, "参数错误")) + return + } + + // 检查属性值唯一 + uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, "") + if !uniqueConfigKey { + msg := fmt.Sprintf("参数配置新增【%s】失败,参数键名已存在", body.ConfigKey) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(r) + insertId := s.sysConfigService.InsertConfig(body) + if insertId != "" { + ctx.JSON(w, 200, result.Ok(nil)) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} + +// 参数配置修改 +// +// PUT / +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, "参数错误")) + return + } + + // 检查属性值唯一 + uniqueConfigKey := s.sysConfigService.CheckUniqueConfigKey(body.ConfigKey, body.ConfigID) + if !uniqueConfigKey { + msg := fmt.Sprintf("参数配置修改【%s】失败,参数键名已存在", body.ConfigKey) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 检查是否存在 + config := s.sysConfigService.SelectConfigById(body.ConfigID) + if config.ConfigID != body.ConfigID { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问参数配置数据!")) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(r) + rows := s.sysConfigService.UpdateConfig(body) + if rows > 0 { + ctx.JSON(w, 200, result.Ok(nil)) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} + +// 参数配置删除 +// +// DELETE /:configIds +func (s *SysConfigApi) Remove(w http.ResponseWriter, r *http.Request) { + configIds := ctx.Param(r, "configIds") + if configIds == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(configIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + ctx.JSON(w, 200, result.Err(nil)) + return + } + rows, err := s.sysConfigService.DeleteConfigByIds(uniqueIDs) + if err != nil { + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + ctx.JSON(w, 200, result.OkMsg(msg)) +} + +// 参数配置刷新缓存 +// +// PUT /refreshCache +func (s *SysConfigApi) RefreshCache(w http.ResponseWriter, r *http.Request) { + s.sysConfigService.ResetConfigCache() + ctx.JSON(w, 200, result.Ok(nil)) +} + +// 参数配置根据参数键名 +// +// GET /configKey/:configKey +func (s *SysConfigApi) ConfigKey(w http.ResponseWriter, r *http.Request) { + configKey := ctx.Param(r, "configKey") + if configKey == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + key := s.sysConfigService.SelectConfigValueByKey(configKey) + if key != "" { + ctx.JSON(w, 200, result.OkData(key)) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} diff --git a/features/sys_config/model/sys_config.go b/features/sys_config/model/sys_config.go new file mode 100644 index 00000000..8949c67b --- /dev/null +++ b/features/sys_config/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/features/sys_config/service/repo_sys_config.go b/features/sys_config/service/repo_sys_config.go new file mode 100644 index 00000000..95c4cb1f --- /dev/null +++ b/features/sys_config/service/repo_sys_config.go @@ -0,0 +1,321 @@ +package service + +import ( + "fmt" + "strings" + "time" + + "ems.agt/features/sys_config/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/date" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/log" +) + +// 实例化数据层 RepoSysConfig 结构体 +var NewRepoSysConfig = &RepoSysConfig{ + 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", + }, +} + +// RepoSysConfig 参数配置表 数据层处理 +type RepoSysConfig struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *RepoSysConfig) 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 { + datasource.SetFieldValue(&sysConfig, keyMapper, value) + } + } + arr = append(arr, sysConfig) + } + return arr +} + +// SelectDictDataPage 分页查询参数配置列表数据 +func (r *RepoSysConfig) 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 ") + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_config" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + log.Errorf("total err => %v", err) + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return map[string]any{ + "total": total, + "rows": []model.SysConfig{}, + } + } + + // 分页 + pageNum, pageSize := datasource.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 { + log.Errorf("query err => %v", err) + } + + // 转换实体 + rows := r.convertResultRows(results) + return map[string]any{ + "total": total, + "rows": rows, + } +} + +// SelectConfigList 查询参数配置列表 +func (r *RepoSysConfig) 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 { + log.Errorf("query err => %v", err) + return []model.SysConfig{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectConfigValueByKey 通过参数键名查询参数键值 +func (r *RepoSysConfig) 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 { + log.Errorf("query err => %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// SelectConfigByIds 通过配置ID查询参数配置信息 +func (r *RepoSysConfig) SelectConfigByIds(configIds []string) []model.SysConfig { + placeholder := datasource.KeyPlaceholderByQuery(len(configIds)) + querySql := r.selectSql + " where config_id in (" + placeholder + ")" + parameters := datasource.ConvertIdsSlice(configIds) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + log.Errorf("query err => %v", err) + return []model.SysConfig{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// CheckUniqueConfig 校验配置参数是否唯一 +func (r *RepoSysConfig) 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 { + log.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// InsertConfig 新增参数配置 +func (r *RepoSysConfig) 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 := datasource.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_config (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + // 执行插入 + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + log.Errorf("insert row : %v", err.Error()) + return "" + } + + return fmt.Sprint(rows) +} + +// UpdateConfig 修改参数配置 +func (r *RepoSysConfig) 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 := datasource.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 { + log.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// DeleteConfigByIds 批量删除参数配置信息 +func (r *RepoSysConfig) DeleteConfigByIds(configIds []string) int64 { + placeholder := datasource.KeyPlaceholderByQuery(len(configIds)) + sql := "delete from sys_config where config_id in (" + placeholder + ")" + parameters := datasource.ConvertIdsSlice(configIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/features/sys_config/service/service_sys_config.go b/features/sys_config/service/service_sys_config.go new file mode 100644 index 00000000..ed0bf3c7 --- /dev/null +++ b/features/sys_config/service/service_sys_config.go @@ -0,0 +1,155 @@ +package service + +import ( + "errors" + + "ems.agt/features/sys_config/model" + "ems.agt/lib/core/cache" + "ems.agt/lib/core/constants/cachekey" +) + +// 实例化服务层 ServiceSysConfig 结构体 +var NewServiceSysConfig = &ServiceSysConfig{ + sysConfigRepository: NewRepoSysConfig, +} + +// ServiceSysConfig 参数配置 服务层处理 +type ServiceSysConfig struct { + // 参数配置表 + sysConfigRepository *RepoSysConfig +} + +// SelectDictDataPage 分页查询参数配置列表数据 +func (r *ServiceSysConfig) SelectConfigPage(query map[string]any) map[string]any { + return r.sysConfigRepository.SelectConfigPage(query) +} + +// SelectConfigList 查询参数配置列表 +func (r *ServiceSysConfig) SelectConfigList(sysConfig model.SysConfig) []model.SysConfig { + return r.sysConfigRepository.SelectConfigList(sysConfig) +} + +// SelectConfigValueByKey 通过参数键名查询参数键值 +func (r *ServiceSysConfig) SelectConfigValueByKey(configKey string) string { + cacheKey := r.getCacheKey(configKey) + // 从缓存中读取 + cacheValue, ok := cache.GetLocal(cacheKey) + if cacheValue != nil && ok { + return cacheValue.(string) + } + // 无缓存时读取数据放入缓存中 + configValue := r.sysConfigRepository.SelectConfigValueByKey(configKey) + if configValue != "" { + cache.SetLocal(cacheKey, configValue) + return configValue + } + return "" +} + +// SelectConfigById 通过配置ID查询参数配置信息 +func (r *ServiceSysConfig) 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 *ServiceSysConfig) CheckUniqueConfigKey(configKey, configId string) bool { + uniqueId := r.sysConfigRepository.CheckUniqueConfig(model.SysConfig{ + ConfigKey: configKey, + }) + if uniqueId == configId { + return true + } + return uniqueId == "" +} + +// InsertConfig 新增参数配置 +func (r *ServiceSysConfig) InsertConfig(sysConfig model.SysConfig) string { + configId := r.sysConfigRepository.InsertConfig(sysConfig) + if configId != "" { + r.loadingConfigCache(sysConfig.ConfigKey) + } + return configId +} + +// UpdateConfig 修改参数配置 +func (r *ServiceSysConfig) UpdateConfig(sysConfig model.SysConfig) int64 { + rows := r.sysConfigRepository.UpdateConfig(sysConfig) + if rows > 0 { + r.loadingConfigCache(sysConfig.ConfigKey) + } + return rows +} + +// DeleteConfigByIds 批量删除参数配置信息 +func (r *ServiceSysConfig) 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 *ServiceSysConfig) ResetConfigCache() { + r.clearConfigCache("*") + r.loadingConfigCache("*") +} + +// getCacheKey 组装缓存key +func (r *ServiceSysConfig) getCacheKey(configKey string) string { + return cachekey.SYS_CONFIG_KEY + configKey +} + +// loadingConfigCache 加载参数缓存数据 +func (r *ServiceSysConfig) loadingConfigCache(configKey string) { + // 查询全部参数 + if configKey == "*" { + sysConfigs := r.SelectConfigList(model.SysConfig{}) + for _, v := range sysConfigs { + key := r.getCacheKey(v.ConfigKey) + cache.DeleteLocal(key) + cache.SetLocal(key, v.ConfigValue) + } + return + } + // 指定参数 + if configKey != "" { + cacheValue := r.sysConfigRepository.SelectConfigValueByKey(configKey) + if cacheValue != "" { + key := r.getCacheKey(configKey) + cache.DeleteLocal(key) + cache.SetLocal(key, cacheValue) + } + return + } +} + +// clearConfigCache 清空参数缓存数据 +func (r *ServiceSysConfig) clearConfigCache(configKey string) bool { + key := r.getCacheKey(configKey) + keys := cache.GetLocalKeys(key) + for _, v := range keys { + cache.DeleteLocal(v) + } + return len(keys) > 0 +} diff --git a/lib/routes/routes.go b/lib/routes/routes.go index c6d40eb2..3477e892 100644 --- a/lib/routes/routes.go +++ b/lib/routes/routes.go @@ -18,6 +18,7 @@ import ( "ems.agt/features/pm" "ems.agt/features/security" "ems.agt/features/state" + sysconfig "ems.agt/features/sys_config" sysmenu "ems.agt/features/sys_menu" sysrole "ems.agt/features/sys_role" sysuser "ems.agt/features/sys_user" @@ -287,6 +288,11 @@ func init() { Register("GET", security.UriRouters, security.Routers, midware.Authorize(nil)) Register("GET", security.CustomUriRouters, security.Routers, midware.Authorize(nil)) + // 参数配置信息接口添加到路由 + for _, v := range sysconfig.Routers() { + Register(v.Method, v.Pattern, v.Handler, v.Middleware) + } + // 菜单接口添加到路由 for _, v := range sysmenu.Routers() { Register(v.Method, v.Pattern, v.Handler, v.Middleware) From d782595d8c9f721618d20b9f5b149fcd3354e3ca Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Tue, 5 Sep 2023 19:25:39 +0800 Subject: [PATCH 4/7] =?UTF-8?q?fix=E6=95=B0=E6=8D=AE=E6=8F=92=E5=85=A5?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=8E=B7=E5=8F=96=E8=BF=94=E5=9B=9Eid?= =?UTF-8?q?=E5=92=8C=E5=BD=B1=E5=93=8D=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/sys_menu/service/repo_sys_menu.go | 25 ++++++++++--- features/sys_role/service/repo_sys_role.go | 35 +++++++++--------- features/sys_role_menu/repo_sys_role_menu.go | 23 +++++++++--- features/sys_user/service/repo_sys_user.go | 37 +++++++++++--------- features/sys_user_role/repo_sys_user_role.go | 21 +++++++++-- lib/core/datasource/datasource.go | 8 ++--- 6 files changed, 100 insertions(+), 49 deletions(-) diff --git a/features/sys_menu/service/repo_sys_menu.go b/features/sys_menu/service/repo_sys_menu.go index 49b6497b..af809a19 100644 --- a/features/sys_menu/service/repo_sys_menu.go +++ b/features/sys_menu/service/repo_sys_menu.go @@ -317,13 +317,18 @@ func (r *RepoSysMenu) InsertMenu(sysMenu model.SysMenu) string { sql := "insert into sys_menu (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" // 执行插入 - rows, err := datasource.ExecDB("", sql, values) + results, err := datasource.ExecDB("", sql, values) if err != nil { log.Errorf("insert row : %v", err.Error()) return "" } - return fmt.Sprint(rows) + insertId, err := results.LastInsertId() + if err != nil { + log.Errorf("insert row : %v", err.Error()) + return "" + } + return fmt.Sprint(insertId) } // UpdateMenu 修改菜单信息 @@ -400,12 +405,17 @@ func (r *RepoSysMenu) UpdateMenu(sysMenu model.SysMenu) int64 { // 执行更新 values = append(values, sysMenu.MenuID) - rows, err := datasource.ExecDB("", sql, values) + results, err := datasource.ExecDB("", sql, values) if err != nil { log.Errorf("update row : %v", err.Error()) return 0 } - return rows + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("update err => %v", err) + return 0 + } + return affected } // DeleteMenuById 删除菜单管理信息 @@ -416,7 +426,12 @@ func (r *RepoSysMenu) DeleteMenuById(menuId string) int64 { log.Errorf("delete err => %v", err) return 0 } - return results + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected } // CheckUniqueMenu 校验菜单是否唯一 diff --git a/features/sys_role/service/repo_sys_role.go b/features/sys_role/service/repo_sys_role.go index 00354a36..7f61e8a5 100644 --- a/features/sys_role/service/repo_sys_role.go +++ b/features/sys_role/service/repo_sys_role.go @@ -244,12 +244,17 @@ func (r *RepoSysRole) UpdateRole(sysRole model.SysRole) int64 { // 执行更新 values = append(values, sysRole.RoleID) - rows, err := datasource.ExecDB("", sql, values) + results, err := datasource.ExecDB("", sql, values) if err != nil { log.Errorf("update row : %v", err.Error()) return 0 } - return rows + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("update err => %v", err) + return 0 + } + return affected } // InsertRole 新增角色信息 @@ -290,24 +295,17 @@ func (r *RepoSysRole) InsertRole(sysRole model.SysRole) string { sql := "insert into sys_role (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" // 执行插入 - rows, err := datasource.ExecDB("", sql, values) + results, err := datasource.ExecDB("", sql, values) if err != nil { log.Errorf("insert row : %v", err.Error()) return "" } - //取插入ID - if rows > 0 { - results, err := datasource.RawDB("", "SELECT MAX(role_id) AS str FROM sys_role;", nil) - if err != nil { - log.Errorf("query err %v", err) - return "" - } - if len(results) > 0 { - return fmt.Sprintf("%v", results[0]["str"]) - } + insertId, err := results.LastInsertId() + if err != nil { + log.Errorf("insert row : %v", err.Error()) + return "" } - - return "" + return fmt.Sprint(insertId) } // DeleteRoleByIds 批量删除角色信息 @@ -320,7 +318,12 @@ func (r *RepoSysRole) DeleteRoleByIds(roleIds []string) int64 { log.Errorf("delete err => %v", err) return 0 } - return results + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected } // CheckUniqueRole 校验角色是否唯一 diff --git a/features/sys_role_menu/repo_sys_role_menu.go b/features/sys_role_menu/repo_sys_role_menu.go index 24d96f2f..02dc8264 100644 --- a/features/sys_role_menu/repo_sys_role_menu.go +++ b/features/sys_role_menu/repo_sys_role_menu.go @@ -39,7 +39,12 @@ func (r *RepoSysRoleMenu) DeleteRoleMenu(roleIds []string) int64 { log.Errorf("delete err => %v", err) return 0 } - return results + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected } // DeleteMenuRole 批量删除菜单和角色关联 @@ -52,7 +57,12 @@ func (r *RepoSysRoleMenu) DeleteMenuRole(menuIds []string) int64 { log.Errorf("delete err => %v", err) return 0 } - return results + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected } // BatchRoleMenu 批量新增角色菜单信息 @@ -64,8 +74,13 @@ func (r *RepoSysRoleMenu) BatchRoleMenu(sysRoleMenus []SysRoleMenu) int64 { sql := "insert into sys_role_menu(role_id, menu_id) values " + strings.Join(keyValues, ",") results, err := datasource.ExecDB("", sql, nil) if err != nil { - log.Errorf("delete err => %v", err) + log.Errorf("insert err => %v", err) return 0 } - return results + insertId, err := results.LastInsertId() + if err != nil { + log.Errorf("insert row : %v", err.Error()) + return 0 + } + return insertId } diff --git a/features/sys_user/service/repo_sys_user.go b/features/sys_user/service/repo_sys_user.go index 2d2cc67e..622b7e68 100644 --- a/features/sys_user/service/repo_sys_user.go +++ b/features/sys_user/service/repo_sys_user.go @@ -436,24 +436,17 @@ func (r *RepoSysUser) InsertUser(sysUser sysUserModel.SysUser) string { sql := "insert into user (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" // 执行插入 - rows, err := datasource.ExecDB("", sql, values) + results, err := datasource.ExecDB("", sql, values) if err != nil { log.Errorf("insert row : %v", err.Error()) return "" } - //取插入ID - if rows > 0 { - results, err := datasource.RawDB("", "SELECT MAX(id) AS str FROM user;", nil) - if err != nil { - log.Errorf("query err %v", err) - return "" - } - if len(results) > 0 { - return fmt.Sprintf("%v", results[0]["str"]) - } + insertId, err := results.LastInsertId() + if err != nil { + log.Errorf("insert row : %v", err.Error()) + return "" } - - return "" + return fmt.Sprint(insertId) } // UpdateUser 修改用户信息 @@ -519,12 +512,17 @@ func (r *RepoSysUser) UpdateUser(sysUser sysUserModel.SysUser) int64 { // 执行更新 values = append(values, sysUser.Id) - rows, err := datasource.ExecDB("", sql, values) + results, err := datasource.ExecDB("", sql, values) if err != nil { log.Errorf("update row : %v", err.Error()) return 0 } - return rows + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("update err => %v", err) + return 0 + } + return affected } // DeleteUserByIds 批量删除用户信息 @@ -534,10 +532,15 @@ func (r *RepoSysUser) DeleteUserByIds(userIds []string) int64 { parameters := datasource.ConvertIdsSlice(userIds) results, err := datasource.ExecDB("", sql, parameters) if err != nil { - log.Errorf("update err => %v", err) + log.Errorf("delete err => %v", err) return 0 } - return results + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected } // CheckUniqueUser 校验用户信息是否唯一 diff --git a/features/sys_user_role/repo_sys_user_role.go b/features/sys_user_role/repo_sys_user_role.go index c7237646..97585a18 100644 --- a/features/sys_user_role/repo_sys_user_role.go +++ b/features/sys_user_role/repo_sys_user_role.go @@ -41,7 +41,12 @@ func (r *RepoSysUserRole) BatchUserRole(sysUserRoles []SysUserRole) int64 { log.Errorf("delete err => %v", err) return 0 } - return results + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected } // DeleteUserRole 批量删除用户和角色关联 @@ -54,7 +59,12 @@ func (r *RepoSysUserRole) DeleteUserRole(userIds []string) int64 { log.Errorf("delete err => %v", err) return 0 } - return results + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected } // DeleteUserRoleByRoleId 批量取消授权用户角色 @@ -68,5 +78,10 @@ func (r *RepoSysUserRole) DeleteUserRoleByRoleId(roleId string, userIds []string log.Errorf("delete err => %v", err) return 0 } - return results + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected } diff --git a/lib/core/datasource/datasource.go b/lib/core/datasource/datasource.go index a36e2ba1..cde0a71b 100644 --- a/lib/core/datasource/datasource.go +++ b/lib/core/datasource/datasource.go @@ -1,6 +1,7 @@ package datasource import ( + "database/sql" "regexp" "ems.agt/lib/dborm" @@ -33,7 +34,7 @@ func RawDB(source string, sql string, parameters []any) ([]map[string]any, error } // ExecDB 原生执行语句 -func ExecDB(source string, sql string, parameters []any) (int64, error) { +func ExecDB(source string, sql string, parameters []any) (sql.Result, error) { // 数据源 db := DefaultDB() @@ -42,8 +43,7 @@ func ExecDB(source string, sql string, parameters []any) (int64, error) { // 执行结果 res, err := db.Exec(append([]any{fmtSql}, parameters...)...) if err != nil { - return 0, err + return nil, err } - affected, err := res.RowsAffected() - return affected, err + return res, err } From 7c07d9b8032a4d00bb7069be0c1a2011178c9387 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Wed, 6 Sep 2023 10:46:28 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E5=B0=86=E7=BC=93=E5=AD=98key=E5=90=AB*?= =?UTF-8?q?=E5=8F=B7=E7=9A=84=E6=88=AA=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/cache/lcoal.go | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/core/cache/lcoal.go b/lib/core/cache/lcoal.go index 06b5628f..f6f12af0 100644 --- a/lib/core/cache/lcoal.go +++ b/lib/core/cache/lcoal.go @@ -27,6 +27,7 @@ func DeleteLocal(key string) { // 获取指定前缀的所有键 func GetLocalKeys(prefix string) []string { + prefix = strings.TrimSuffix(prefix, "*") var keys []string for key := range cNoExpiration.Items() { if strings.HasPrefix(key, prefix) { From 3f35f2bc96405e1f742722a4f4167aad9d3cb28d Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Wed, 6 Sep 2023 10:46:50 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E5=8F=82=E6=95=B0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=8F=92=E5=85=A5=E7=BB=93=E6=9E=9C=E8=BF=94=E5=9B=9E=E8=AE=B0?= =?UTF-8?q?=E5=BD=95ID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sys_config/service/repo_sys_config.go | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/features/sys_config/service/repo_sys_config.go b/features/sys_config/service/repo_sys_config.go index 95c4cb1f..488d7c9a 100644 --- a/features/sys_config/service/repo_sys_config.go +++ b/features/sys_config/service/repo_sys_config.go @@ -260,13 +260,18 @@ func (r *RepoSysConfig) InsertConfig(sysConfig model.SysConfig) string { sql := "insert into sys_config (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" // 执行插入 - rows, err := datasource.ExecDB("", sql, values) + results, err := datasource.ExecDB("", sql, values) if err != nil { log.Errorf("insert row : %v", err.Error()) return "" } - return fmt.Sprint(rows) + insertId, err := results.LastInsertId() + if err != nil { + log.Errorf("insert row : %v", err.Error()) + return "" + } + return fmt.Sprint(insertId) } // UpdateConfig 修改参数配置 @@ -299,12 +304,17 @@ func (r *RepoSysConfig) UpdateConfig(sysConfig model.SysConfig) int64 { // 执行更新 values = append(values, sysConfig.ConfigID) - rows, err := datasource.ExecDB("", sql, values) + results, err := datasource.ExecDB("", sql, values) if err != nil { log.Errorf("update row : %v", err.Error()) return 0 } - return rows + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("update err => %v", err) + return 0 + } + return affected } // DeleteConfigByIds 批量删除参数配置信息 @@ -317,5 +327,10 @@ func (r *RepoSysConfig) DeleteConfigByIds(configIds []string) int64 { log.Errorf("delete err => %v", err) return 0 } - return results + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected } From 5079a1b7821efbacd465cac8792f7d2b6f493fb0 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Wed, 6 Sep 2023 10:47:16 +0800 Subject: [PATCH 7/7] =?UTF-8?q?add=20=E5=AD=97=E5=85=B8=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- features/sys_dict_data/api_sys_dict_data.go | 247 ++++++++++++ features/sys_dict_data/model/sys_dict_data.go | 31 ++ .../sys_dict_data/repo/repo_sys_dict_data.go | 369 ++++++++++++++++++ .../service/service_sys_dict_data.go | 111 ++++++ features/sys_dict_type/api_sys_dict_type.go | 253 ++++++++++++ features/sys_dict_type/model/sys_dict_type.go | 23 ++ .../sys_dict_type/repo/repo_sys_dict_type.go | 330 ++++++++++++++++ .../service/service_sys_dict_type.go | 211 ++++++++++ lib/routes/routes.go | 12 + 9 files changed, 1587 insertions(+) create mode 100644 features/sys_dict_data/api_sys_dict_data.go create mode 100644 features/sys_dict_data/model/sys_dict_data.go create mode 100644 features/sys_dict_data/repo/repo_sys_dict_data.go create mode 100644 features/sys_dict_data/service/service_sys_dict_data.go create mode 100644 features/sys_dict_type/api_sys_dict_type.go create mode 100644 features/sys_dict_type/model/sys_dict_type.go create mode 100644 features/sys_dict_type/repo/repo_sys_dict_type.go create mode 100644 features/sys_dict_type/service/service_sys_dict_type.go diff --git a/features/sys_dict_data/api_sys_dict_data.go b/features/sys_dict_data/api_sys_dict_data.go new file mode 100644 index 00000000..05dbb081 --- /dev/null +++ b/features/sys_dict_data/api_sys_dict_data.go @@ -0,0 +1,247 @@ +package sysdictdata + +import ( + "fmt" + "net/http" + "strings" + + "ems.agt/features/sys_dict_data/model" + sysDictDataService "ems.agt/features/sys_dict_data/service" + sysDictTypeService "ems.agt/features/sys_dict_type/service" + "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" +) + +// 字典类型对应的字典数据信息接口添加到路由 +func Routers() []services.RouterItem { + // 实例化控制层 SysDictDataApi 结构体 + var apis = &SysDictDataApi{ + sysDictDataService: sysDictDataService.NewServiceSysDictData, + sysDictTypeService: sysDictTypeService.NewServiceSysDictType, + } + + rs := [...]services.RouterItem{ + { + Method: "GET", + Pattern: "/dictDatas", + Handler: apis.List, + Middleware: midware.Authorize(nil), + }, + { + Method: "GET", + Pattern: "/dictData/{dictCode}", + Handler: apis.Info, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/dictData", + Handler: apis.Add, + Middleware: midware.Authorize(nil), + }, + { + Method: "PUT", + Pattern: "/dictData", + Handler: apis.Edit, + Middleware: midware.Authorize(nil), + }, + { + Method: "DELETE", + Pattern: "/dictData/{dictCodes}", + Handler: apis.Remove, + Middleware: midware.Authorize(nil), + }, + { + Method: "GET", + Pattern: "/dictData/type/{dictType}", + Handler: apis.DictType, + Middleware: midware.Authorize(nil), + }, + // 添加更多的 Router 对象... + } + + // 生成两组前缀路由 + rsPrefix := []services.RouterItem{} + for _, v := range rs { + path := "/dictDataManage/{apiVersion}" + v.Pattern + // 固定前缀 + v.Pattern = config.DefaultUriPrefix + path + rsPrefix = append(rsPrefix, v) + // 可配置 + v.Pattern = config.UriPrefix + path + rsPrefix = append(rsPrefix, v) + } + return rsPrefix +} + +// 字典类型对应的字典数据信息 +// +// PATH /dictDataManage +type SysDictDataApi struct { + // 字典数据服务 + sysDictDataService *sysDictDataService.ServiceSysDictData + // 字典类型服务 + sysDictTypeService *sysDictTypeService.ServiceSysDictType +} + +// 字典数据列表 +// +// GET /list +func (s *SysDictDataApi) List(w http.ResponseWriter, r *http.Request) { + querys := ctx.QueryMap(r) + data := s.sysDictDataService.SelectDictDataPage(querys) + ctx.JSON(w, 200, result.Ok(data)) +} + +// 字典数据详情 +// +// GET /:dictCode +func (s *SysDictDataApi) Info(w http.ResponseWriter, r *http.Request) { + dictCode := ctx.Param(r, "dictCode") + if dictCode == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysDictDataService.SelectDictDataByCode(dictCode) + if data.DictCode == dictCode { + ctx.JSON(w, 200, result.OkData(data)) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} + +// 字典数据新增 +// +// POST / +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, "参数错误")) + return + } + + // 检查字典类型是否存在 + sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType) + if sysDictType.DictType != body.DictType { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典标签唯一 + uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, "") + if !uniqueDictLabel { + msg := fmt.Sprintf("数据新增【%s】失败,该字典类型下标签名已存在", body.DictLabel) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 检查字典键值唯一 + uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, "") + if !uniqueDictValue { + msg := fmt.Sprintf("数据新增【%s】失败,该字典类型下标签值已存在", body.DictValue) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(r) + insertId := s.sysDictDataService.InsertDictData(body) + if insertId != "" { + ctx.JSON(w, 200, result.Ok(nil)) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} + +// 字典类型修改 +// +// PUT / +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, "参数错误")) + return + } + + // 检查字典类型是否存在 + sysDictType := s.sysDictTypeService.SelectDictTypeByType(body.DictType) + if sysDictType.DictType != body.DictType { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典编码是否存在 + SysDictDataApi := s.sysDictDataService.SelectDictDataByCode(body.DictCode) + if SysDictDataApi.DictCode != body.DictCode { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典编码数据!")) + return + } + + // 检查字典标签唯一 + uniqueDictLabel := s.sysDictDataService.CheckUniqueDictLabel(body.DictType, body.DictLabel, body.DictCode) + if !uniqueDictLabel { + msg := fmt.Sprintf("数据修改【%s】失败,该字典类型下标签名已存在", body.DictLabel) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 检查字典键值唯一 + uniqueDictValue := s.sysDictDataService.CheckUniqueDictValue(body.DictType, body.DictValue, body.DictCode) + if !uniqueDictValue { + msg := fmt.Sprintf("数据修改【%s】失败,该字典类型下标签值已存在", body.DictValue) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(r) + rows := s.sysDictDataService.UpdateDictData(body) + if rows > 0 { + ctx.JSON(w, 200, result.Ok(nil)) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} + +// 字典数据删除 +// +// DELETE /:dictCodes +func (s *SysDictDataApi) Remove(w http.ResponseWriter, r *http.Request) { + dictCodes := ctx.Param(r, "dictCodes") + if dictCodes == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(dictCodes, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + ctx.JSON(w, 200, result.Err(nil)) + return + } + rows, err := s.sysDictDataService.DeleteDictDataByCodes(uniqueIDs) + if err != nil { + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + ctx.JSON(w, 200, result.OkMsg(msg)) +} + +// 字典数据列表(指定字典类型) +// +// GET /type/:dictType +func (s *SysDictDataApi) DictType(w http.ResponseWriter, r *http.Request) { + dictType := ctx.Param(r, "dictType") + if dictType == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + + data := s.sysDictDataService.SelectDictDataByType(dictType) + ctx.JSON(w, 200, result.OkData(data)) +} diff --git a/features/sys_dict_data/model/sys_dict_data.go b/features/sys_dict_data/model/sys_dict_data.go new file mode 100644 index 00000000..7c8cd06a --- /dev/null +++ b/features/sys_dict_data/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/features/sys_dict_data/repo/repo_sys_dict_data.go b/features/sys_dict_data/repo/repo_sys_dict_data.go new file mode 100644 index 00000000..cd6268a6 --- /dev/null +++ b/features/sys_dict_data/repo/repo_sys_dict_data.go @@ -0,0 +1,369 @@ +package repo + +import ( + "fmt" + "strings" + "time" + + "ems.agt/features/sys_dict_data/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/log" +) + +// 实例化数据层 RepoSysDictData 结构体 +var NewRepoSysDictData = &RepoSysDictData{ + 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", + }, +} + +// RepoSysDictData 字典类型数据表 数据层处理 +type RepoSysDictData struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *RepoSysDictData) 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 { + datasource.SetFieldValue(&sysDictData, keyMapper, value) + } + } + arr = append(arr, sysDictData) + } + return arr +} + +// SelectDictDataPage 根据条件分页查询字典数据 +func (r *RepoSysDictData) 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 ") + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_dict_data" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + log.Errorf("total err => %v", err) + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return map[string]any{ + "total": total, + "rows": []model.SysDictData{}, + } + } + + // 分页 + pageNum, pageSize := datasource.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 { + log.Errorf("query err => %v", err) + } + + // 转换实体 + rows := r.convertResultRows(results) + return map[string]any{ + "total": total, + "rows": rows, + } +} + +// SelectDictDataList 根据条件查询字典数据 +func (r *RepoSysDictData) 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 { + log.Errorf("query err => %v", err) + return []model.SysDictData{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDictDataByCodes 根据字典数据编码查询信息 +func (r *RepoSysDictData) SelectDictDataByCodes(dictCodes []string) []model.SysDictData { + placeholder := datasource.KeyPlaceholderByQuery(len(dictCodes)) + querySql := r.selectSql + " where dict_code in (" + placeholder + ")" + parameters := datasource.ConvertIdsSlice(dictCodes) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + log.Errorf("query err => %v", err) + return []model.SysDictData{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// CountDictDataByType 查询字典数据 +func (r *RepoSysDictData) 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 { + log.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// CheckUniqueDictData 校验字典数据是否唯一 +func (r *RepoSysDictData) 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 { + log.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// DeleteDictDataByCodes 批量删除字典数据信息 +func (r *RepoSysDictData) DeleteDictDataByCodes(dictCodes []string) int64 { + placeholder := datasource.KeyPlaceholderByQuery(len(dictCodes)) + sql := "delete from sys_dict_data where dict_code in (" + placeholder + ")" + parameters := datasource.ConvertIdsSlice(dictCodes) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected +} + +// InsertDictData 新增字典数据信息 +func (r *RepoSysDictData) 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 := datasource.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_dict_data (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + // 执行插入 + results, err := datasource.ExecDB("", sql, values) + if err != nil { + log.Errorf("insert row : %v", err.Error()) + return "" + } + insertId, err := results.LastInsertId() + if err != nil { + log.Errorf("insert row : %v", err.Error()) + return "" + } + return fmt.Sprint(insertId) +} + +// UpdateDictData 修改字典数据信息 +func (r *RepoSysDictData) 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 := datasource.KeyValueByUpdate(params) + sql := "update sys_dict_data set " + strings.Join(keys, ",") + " where dict_code = ?" + + // 执行更新 + values = append(values, sysDictData.DictCode) + results, err := datasource.ExecDB("", sql, values) + if err != nil { + log.Errorf("update row : %v", err.Error()) + return 0 + } + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("update err => %v", err) + return 0 + } + return affected +} + +// UpdateDictDataType 同步修改字典类型 +func (r *RepoSysDictData) 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 = ?" + + // 执行更新 + results, err := datasource.ExecDB("", sql, params) + if err != nil { + log.Errorf("update row : %v", err.Error()) + return 0 + } + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("update err => %v", err) + return 0 + } + return affected +} diff --git a/features/sys_dict_data/service/service_sys_dict_data.go b/features/sys_dict_data/service/service_sys_dict_data.go new file mode 100644 index 00000000..624105e2 --- /dev/null +++ b/features/sys_dict_data/service/service_sys_dict_data.go @@ -0,0 +1,111 @@ +package service + +import ( + "errors" + + "ems.agt/features/sys_dict_data/model" + "ems.agt/features/sys_dict_data/repo" + sysDictTypeService "ems.agt/features/sys_dict_type/service" +) + +// 实例化服务层 ServiceSysDictData 结构体 +var NewServiceSysDictData = &ServiceSysDictData{ + sysDictDataRepository: *repo.NewRepoSysDictData, + sysDictTypeService: *sysDictTypeService.NewServiceSysDictType, +} + +// ServiceSysDictData 字典类型数据 服务层处理 +type ServiceSysDictData struct { + // 字典数据服务 + sysDictDataRepository repo.RepoSysDictData + // 字典类型服务 + sysDictTypeService sysDictTypeService.ServiceSysDictType +} + +// SelectDictDataPage 根据条件分页查询字典数据 +func (r *ServiceSysDictData) SelectDictDataPage(query map[string]any) map[string]any { + return r.sysDictDataRepository.SelectDictDataPage(query) +} + +// SelectDictDataList 根据条件查询字典数据 +func (r *ServiceSysDictData) SelectDictDataList(sysDictData model.SysDictData) []model.SysDictData { + return r.sysDictDataRepository.SelectDictDataList(sysDictData) +} + +// SelectDictDataByCode 根据字典数据编码查询信息 +func (r *ServiceSysDictData) 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 *ServiceSysDictData) SelectDictDataByType(dictType string) []model.SysDictData { + return r.sysDictTypeService.DictDataCache(dictType) +} + +// CheckUniqueDictLabel 校验字典标签是否唯一 +func (r *ServiceSysDictData) 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 *ServiceSysDictData) 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 *ServiceSysDictData) 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 *ServiceSysDictData) InsertDictData(sysDictData model.SysDictData) string { + insertId := r.sysDictDataRepository.InsertDictData(sysDictData) + if insertId != "" { + r.sysDictTypeService.LoadingDictCache(sysDictData.DictType) + } + return insertId +} + +// UpdateDictData 修改字典数据信息 +func (r *ServiceSysDictData) UpdateDictData(sysDictData model.SysDictData) int64 { + rows := r.sysDictDataRepository.UpdateDictData(sysDictData) + if rows > 0 { + r.sysDictTypeService.LoadingDictCache(sysDictData.DictType) + } + return rows +} diff --git a/features/sys_dict_type/api_sys_dict_type.go b/features/sys_dict_type/api_sys_dict_type.go new file mode 100644 index 00000000..480ff01c --- /dev/null +++ b/features/sys_dict_type/api_sys_dict_type.go @@ -0,0 +1,253 @@ +package sysdicttype + +import ( + "fmt" + "net/http" + "strings" + + "ems.agt/features/sys_dict_type/model" + sysDictTypeService "ems.agt/features/sys_dict_type/service" + "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" +) + +// 字典类型信息接口添加到路由 +func Routers() []services.RouterItem { + // 实例化控制层 SysDictTypeApi 结构体 + var apis = &SysDictTypeApi{ + sysDictTypeService: *sysDictTypeService.NewServiceSysDictType, + } + + rs := [...]services.RouterItem{ + { + Method: "GET", + Pattern: "/dictTypes", + Handler: apis.List, + Middleware: midware.Authorize(nil), + }, + { + Method: "GET", + Pattern: "/dictType/{dictId}", + Handler: apis.Info, + Middleware: midware.Authorize(nil), + }, + { + Method: "POST", + Pattern: "/dictType", + Handler: apis.Add, + Middleware: midware.Authorize(nil), + }, + { + Method: "PUT", + Pattern: "/dictType", + Handler: apis.Edit, + Middleware: midware.Authorize(nil), + }, + { + Method: "DELETE", + Pattern: "/dictType/{dictIds}", + Handler: apis.Remove, + Middleware: midware.Authorize(nil), + }, + { + Method: "PUT", + Pattern: "/dictType/refreshCache", + Handler: apis.RefreshCache, + Middleware: midware.Authorize(nil), + }, + { + Method: "GET", + Pattern: "/dictTypes/optionselect", + Handler: apis.DictOptionselect, + Middleware: midware.Authorize(nil), + }, + // 添加更多的 Router 对象... + } + + // 生成两组前缀路由 + rsPrefix := []services.RouterItem{} + for _, v := range rs { + path := "/dictTypegManage/{apiVersion}" + v.Pattern + // 固定前缀 + v.Pattern = config.DefaultUriPrefix + path + rsPrefix = append(rsPrefix, v) + // 可配置 + v.Pattern = config.UriPrefix + path + rsPrefix = append(rsPrefix, v) + } + return rsPrefix +} + +// 字典类型信息 +// +// PATH /dictTypegManage +type SysDictTypeApi struct { + // 字典类型服务 + sysDictTypeService sysDictTypeService.ServiceSysDictType +} + +// 字典类型列表 +// +// GET /list +func (s *SysDictTypeApi) List(w http.ResponseWriter, r *http.Request) { + querys := ctx.QueryMap(r) + data := s.sysDictTypeService.SelectDictTypePage(querys) + ctx.JSON(w, 200, result.Ok(data)) +} + +// 字典类型信息 +// +// GET /:dictId +func (s *SysDictTypeApi) Info(w http.ResponseWriter, r *http.Request) { + dictId := ctx.Param(r, "dictId") + if dictId == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + data := s.sysDictTypeService.SelectDictTypeByID(dictId) + if data.DictID == dictId { + ctx.JSON(w, 200, result.OkData(data)) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} + +// 字典类型新增 +// +// POST / +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, "参数错误")) + return + } + + // 检查字典名称唯一 + uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, "") + if !uniqueDictName { + msg := fmt.Sprintf("字典新增【%s】失败,字典名称已存在", body.DictName) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 检查字典类型唯一 + uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, "") + if !uniqueDictType { + msg := fmt.Sprintf("字典新增【%s】失败,字典类型已存在", body.DictType) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(r) + insertId := s.sysDictTypeService.InsertDictType(body) + if insertId != "" { + ctx.JSON(w, 200, result.Ok(nil)) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} + +// 字典类型修改 +// +// PUT / +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, "参数错误")) + return + } + + // 检查数据是否存在 + dictInfo := s.sysDictTypeService.SelectDictTypeByID(body.DictID) + if dictInfo.DictID != body.DictID { + ctx.JSON(w, 200, result.ErrMsg("没有权限访问字典类型数据!")) + return + } + + // 检查字典名称唯一 + uniqueDictName := s.sysDictTypeService.CheckUniqueDictName(body.DictName, body.DictID) + if !uniqueDictName { + msg := fmt.Sprintf("字典修改【%s】失败,字典名称已存在", body.DictName) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + // 检查字典类型唯一 + uniqueDictType := s.sysDictTypeService.CheckUniqueDictType(body.DictType, body.DictID) + if !uniqueDictType { + msg := fmt.Sprintf("字典修改【%s】失败,字典类型已存在", body.DictType) + ctx.JSON(w, 200, result.ErrMsg(msg)) + return + } + + body.UpdateBy = ctx.LoginUserToUserName(r) + rows := s.sysDictTypeService.UpdateDictType(body) + if rows > 0 { + ctx.JSON(w, 200, result.Ok(nil)) + return + } + ctx.JSON(w, 200, result.Err(nil)) +} + +// 字典类型删除 +// +// DELETE /:dictIds +func (s *SysDictTypeApi) Remove(w http.ResponseWriter, r *http.Request) { + dictIds := ctx.Param(r, "dictIds") + if dictIds == "" { + ctx.JSON(w, 400, result.CodeMsg(400, "参数错误")) + return + } + // 处理字符转id数组后去重 + ids := strings.Split(dictIds, ",") + uniqueIDs := parse.RemoveDuplicates(ids) + if len(uniqueIDs) <= 0 { + ctx.JSON(w, 200, result.Err(nil)) + return + } + rows, err := s.sysDictTypeService.DeleteDictTypeByIDs(uniqueIDs) + if err != nil { + ctx.JSON(w, 200, result.ErrMsg(err.Error())) + return + } + msg := fmt.Sprintf("删除成功:%d", rows) + ctx.JSON(w, 200, result.OkMsg(msg)) +} + +// 字典类型刷新缓存 +// +// PUT /refreshCache +func (s *SysDictTypeApi) RefreshCache(w http.ResponseWriter, r *http.Request) { + s.sysDictTypeService.ResetDictCache() + ctx.JSON(w, 200, result.Ok(nil)) +} + +// 字典类型选择框列表 +// +// GET /getDictOptionselect +func (s *SysDictTypeApi) DictOptionselect(w http.ResponseWriter, r *http.Request) { + data := s.sysDictTypeService.SelectDictTypeList(model.SysDictType{ + Status: "1", + }) + + 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, + }) + } + ctx.JSON(w, 200, result.OkData(arr)) +} diff --git a/features/sys_dict_type/model/sys_dict_type.go b/features/sys_dict_type/model/sys_dict_type.go new file mode 100644 index 00000000..0ad0b1ab --- /dev/null +++ b/features/sys_dict_type/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/features/sys_dict_type/repo/repo_sys_dict_type.go b/features/sys_dict_type/repo/repo_sys_dict_type.go new file mode 100644 index 00000000..874dac11 --- /dev/null +++ b/features/sys_dict_type/repo/repo_sys_dict_type.go @@ -0,0 +1,330 @@ +package repo + +import ( + "fmt" + "strings" + "time" + + "ems.agt/features/sys_dict_type/model" + "ems.agt/lib/core/datasource" + "ems.agt/lib/core/utils/date" + "ems.agt/lib/core/utils/parse" + "ems.agt/lib/log" +) + +// 实例化数据层 RepoSysDictType 结构体 +var NewRepoSysDictType = &RepoSysDictType{ + 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", + }, +} + +// RepoSysDictType 字典类型表 数据层处理 +type RepoSysDictType struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *RepoSysDictType) 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 { + datasource.SetFieldValue(&sysDictType, keyMapper, value) + } + } + arr = append(arr, sysDictType) + } + return arr +} + +// SelectDictTypePage 根据条件分页查询字典类型 +func (r *RepoSysDictType) 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 ") + } + + // 查询数量 长度为0直接返回 + totalSql := "select count(1) as 'total' from sys_dict_type" + totalRows, err := datasource.RawDB("", totalSql+whereSql, params) + if err != nil { + log.Errorf("total err => %v", err) + } + total := parse.Number(totalRows[0]["total"]) + if total == 0 { + return map[string]any{ + "total": total, + "rows": []model.SysDictType{}, + } + } + + // 分页 + pageNum, pageSize := datasource.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 { + log.Errorf("query err => %v", err) + } + + // 转换实体 + rows := r.convertResultRows(results) + return map[string]any{ + "total": total, + "rows": rows, + } +} + +// SelectDictTypeList 根据条件查询字典类型 +func (r *RepoSysDictType) 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 { + log.Errorf("query err => %v", err) + return []model.SysDictType{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDictTypeByIDs 根据字典类型ID查询信息 +func (r *RepoSysDictType) SelectDictTypeByIDs(dictIDs []string) []model.SysDictType { + placeholder := datasource.KeyPlaceholderByQuery(len(dictIDs)) + querySql := r.selectSql + " where dict_id in (" + placeholder + ")" + parameters := datasource.ConvertIdsSlice(dictIDs) + results, err := datasource.RawDB("", querySql, parameters) + if err != nil { + log.Errorf("query err => %v", err) + return []model.SysDictType{} + } + // 转换实体 + return r.convertResultRows(results) +} + +// SelectDictTypeByType 根据字典类型查询信息 +func (r *RepoSysDictType) SelectDictTypeByType(dictType string) model.SysDictType { + querySql := r.selectSql + " where dict_type = ?" + results, err := datasource.RawDB("", querySql, []any{dictType}) + if err != nil { + log.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 *RepoSysDictType) 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 { + log.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprintf("%v", results[0]["str"]) + } + return "" +} + +// InsertDictType 新增字典类型信息 +func (r *RepoSysDictType) 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 := datasource.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_dict_type (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + // 执行插入 + results, err := datasource.ExecDB("", sql, values) + if err != nil { + log.Errorf("insert row : %v", err.Error()) + return "" + } + insertId, err := results.LastInsertId() + if err != nil { + log.Errorf("insert row : %v", err.Error()) + return "" + } + return fmt.Sprint(insertId) +} + +// UpdateDictType 修改字典类型信息 +func (r *RepoSysDictType) 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 := datasource.KeyValueByUpdate(params) + sql := "update sys_dict_type set " + strings.Join(keys, ",") + " where dict_id = ?" + + // 执行更新 + values = append(values, sysDictType.DictID) + results, err := datasource.ExecDB("", sql, values) + if err != nil { + log.Errorf("update row : %v", err.Error()) + return 0 + } + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("update err => %v", err) + return 0 + } + return affected +} + +// DeleteDictTypeByIDs 批量删除字典类型信息 +func (r *RepoSysDictType) DeleteDictTypeByIDs(dictIDs []string) int64 { + placeholder := datasource.KeyPlaceholderByQuery(len(dictIDs)) + sql := "delete from sys_dict_type where dict_id in (" + placeholder + ")" + parameters := datasource.ConvertIdsSlice(dictIDs) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + affected, err := results.RowsAffected() + if err != nil { + log.Errorf("delete err => %v", err) + return 0 + } + return affected +} diff --git a/features/sys_dict_type/service/service_sys_dict_type.go b/features/sys_dict_type/service/service_sys_dict_type.go new file mode 100644 index 00000000..8cbb16b9 --- /dev/null +++ b/features/sys_dict_type/service/service_sys_dict_type.go @@ -0,0 +1,211 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + + sysDictDataModel "ems.agt/features/sys_dict_data/model" + sysDictDataRepo "ems.agt/features/sys_dict_data/repo" + sysDictTypeModel "ems.agt/features/sys_dict_type/model" + "ems.agt/features/sys_dict_type/repo" + "ems.agt/lib/core/cache" + "ems.agt/lib/core/constants/cachekey" +) + +// 实例化服务层 ServiceSysDictType 结构体 +var NewServiceSysDictType = &ServiceSysDictType{ + sysDictTypeRepository: *repo.NewRepoSysDictType, + sysDictDataRepository: *sysDictDataRepo.NewRepoSysDictData, +} + +// ServiceSysDictType 字典类型 服务层处理 +type ServiceSysDictType struct { + // 字典类型服务 + sysDictTypeRepository repo.RepoSysDictType + // 字典数据服务 + sysDictDataRepository sysDictDataRepo.RepoSysDictData +} + +// SelectDictTypePage 根据条件分页查询字典类型 +func (r *ServiceSysDictType) SelectDictTypePage(query map[string]any) map[string]any { + return r.sysDictTypeRepository.SelectDictTypePage(query) +} + +// SelectDictTypeList 根据条件查询字典类型 +func (r *ServiceSysDictType) SelectDictTypeList(sysDictType sysDictTypeModel.SysDictType) []sysDictTypeModel.SysDictType { + return r.sysDictTypeRepository.SelectDictTypeList(sysDictType) +} + +// SelectDictTypeByID 根据字典类型ID查询信息 +func (r *ServiceSysDictType) SelectDictTypeByID(dictID string) sysDictTypeModel.SysDictType { + if dictID == "" { + return sysDictTypeModel.SysDictType{} + } + dictTypes := r.sysDictTypeRepository.SelectDictTypeByIDs([]string{dictID}) + if len(dictTypes) > 0 { + return dictTypes[0] + } + return sysDictTypeModel.SysDictType{} +} + +// SelectDictTypeByType 根据字典类型查询信息 +func (r *ServiceSysDictType) SelectDictTypeByType(dictType string) sysDictTypeModel.SysDictType { + return r.sysDictTypeRepository.SelectDictTypeByType(dictType) +} + +// CheckUniqueDictName 校验字典名称是否唯一 +func (r *ServiceSysDictType) CheckUniqueDictName(dictName, dictID string) bool { + uniqueId := r.sysDictTypeRepository.CheckUniqueDictType(sysDictTypeModel.SysDictType{ + DictName: dictName, + }) + if uniqueId == dictID { + return true + } + return uniqueId == "" +} + +// CheckUniqueDictType 校验字典类型是否唯一 +func (r *ServiceSysDictType) CheckUniqueDictType(dictType, dictID string) bool { + uniqueId := r.sysDictTypeRepository.CheckUniqueDictType(sysDictTypeModel.SysDictType{ + DictType: dictType, + }) + if uniqueId == dictID { + return true + } + return uniqueId == "" +} + +// InsertDictType 新增字典类型信息 +func (r *ServiceSysDictType) InsertDictType(sysDictType sysDictTypeModel.SysDictType) string { + insertId := r.sysDictTypeRepository.InsertDictType(sysDictType) + if insertId != "" { + r.LoadingDictCache(sysDictType.DictType) + } + return insertId +} + +// UpdateDictType 修改字典类型信息 +func (r *ServiceSysDictType) UpdateDictType(sysDictType sysDictTypeModel.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 *ServiceSysDictType) 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 *ServiceSysDictType) ResetDictCache() { + r.ClearDictCache("*") + r.LoadingDictCache("") +} + +// getCacheKey 组装缓存key +func (r *ServiceSysDictType) getDictCache(dictType string) string { + return cachekey.SYS_DICT_KEY + dictType +} + +// LoadingDictCache 加载字典缓存数据 +func (r *ServiceSysDictType) LoadingDictCache(dictType string) { + sysDictData := sysDictDataModel.SysDictData{ + Status: "1", + } + + // 指定字典类型 + if dictType != "" { + sysDictData.DictType = dictType + // 删除缓存 + key := r.getDictCache(dictType) + cache.DeleteLocal(key) + } + + sysDictDataList := r.sysDictDataRepository.SelectDictDataList(sysDictData) + if len(sysDictDataList) == 0 { + return + } + + // 将字典数据按类型分组 + m := make(map[string][]sysDictDataModel.SysDictData, 0) + for _, v := range sysDictDataList { + key := v.DictType + if item, ok := m[key]; ok { + m[key] = append(item, v) + } else { + m[key] = []sysDictDataModel.SysDictData{v} + } + } + + // 放入缓存 + for k, v := range m { + key := r.getDictCache(k) + values, _ := json.Marshal(v) + cache.SetLocal(key, string(values)) + } +} + +// ClearDictCache 清空字典缓存数据 +func (r *ServiceSysDictType) ClearDictCache(dictType string) bool { + key := r.getDictCache(dictType) + keys := cache.GetLocalKeys(key) + for _, v := range keys { + cache.DeleteLocal(v) + } + return len(keys) > 0 +} + +// DictDataCache 获取字典数据缓存数据 +func (r *ServiceSysDictType) DictDataCache(dictType string) []sysDictDataModel.SysDictData { + data := []sysDictDataModel.SysDictData{} + key := r.getDictCache(dictType) + jsonAny, ok := cache.GetLocal(key) + if jsonAny != nil && ok { + err := json.Unmarshal([]byte(jsonAny.(string)), &data) + if err != nil { + data = []sysDictDataModel.SysDictData{} + } + } else { + data = r.sysDictDataRepository.SelectDictDataList(sysDictDataModel.SysDictData{ + Status: "1", + DictType: dictType, + }) + if len(data) > 0 { + cache.DeleteLocal(key) + values, _ := json.Marshal(data) + cache.SetLocal(key, string(values)) + } + } + return data +} diff --git a/lib/routes/routes.go b/lib/routes/routes.go index 3477e892..2bcdb3e1 100644 --- a/lib/routes/routes.go +++ b/lib/routes/routes.go @@ -19,6 +19,8 @@ import ( "ems.agt/features/security" "ems.agt/features/state" sysconfig "ems.agt/features/sys_config" + sysdictdata "ems.agt/features/sys_dict_data" + sysdicttype "ems.agt/features/sys_dict_type" sysmenu "ems.agt/features/sys_menu" sysrole "ems.agt/features/sys_role" sysuser "ems.agt/features/sys_user" @@ -293,6 +295,16 @@ func init() { Register(v.Method, v.Pattern, v.Handler, v.Middleware) } + // 字典类型信息接口添加到路由 + for _, v := range sysdicttype.Routers() { + Register(v.Method, v.Pattern, v.Handler, v.Middleware) + } + + // 字典类型对应的字典数据信息接口添加到路由 + for _, v := range sysdictdata.Routers() { + Register(v.Method, v.Pattern, v.Handler, v.Middleware) + } + // 菜单接口添加到路由 for _, v := range sysmenu.Routers() { Register(v.Method, v.Pattern, v.Handler, v.Middleware)