diff --git a/features/ue/ims_user/service.go b/features/ue/ims_user/service.go new file mode 100644 index 00000000..62f5406d --- /dev/null +++ b/features/ue/ims_user/service.go @@ -0,0 +1,209 @@ +package ims_user + +import ( + "fmt" + "strconv" + "strings" + + "be.ems/src/framework/database/redis" + neService "be.ems/src/modules/network_element/service" +) + +// 实例化服务层 Service 结构体 +var NewVoLTEService = &Service{ + volteRepository: NewVoLTERepository, +} + +// VoLTE用户信息 服务层处理 +type Service struct { + volteRepository *Repository // VoLTE用户信息数据信息 +} + +// dataByRedis UDM签约用户 db:0 中 volte:* +func (r *Service) dataByRedis(imsi, neId string) []VoLTEUser { + arr := []VoLTEUser{} + key := fmt.Sprintf("volte:%s", imsi) + source := fmt.Sprintf("UDM_%s", neId) + + // 网元主机的Redis客户端 + redisClient, err := neService.NewNeInfo.NeRunRedisClient("UDM", neId) + if err != nil { + return arr + } + defer func() { + redisClient.Close() + redis.ConnectPush(source, nil) + }() + redis.ConnectPush(source, redisClient.Client) + + udmsdArr, err := redis.GetKeys(source, key) + if err != nil { + return arr + } + mkv, err := redis.GetHashBatch(source, udmsdArr) + if err != nil { + return arr + } + + for k, m := range mkv { + var imsi, msisdn string + KeyParts := strings.Split(k, ":") + switch len(KeyParts) { + case 0, 1: + // 处理单个部分的情况 + continue + case 2: + // 处理两个部分的情况 + imsi = KeyParts[1] + msisdn = "-" + case 3: + // 处理三个部分的情况 + imsi = KeyParts[1] + msisdn = KeyParts[2] + default: + // 处理更多部分的情况 + imsi = KeyParts[1] + msisdn = KeyParts[2] + } + + var vni string = "-" + impiParts := strings.Split(m["impi"], "@") + if len(impiParts) > 1 { + vni = impiParts[1] // 输出: ims.mnc001.mcc110.3gppnetwork.org + } + a := VoLTEUser{ + NeId: neId, + IMSI: imsi, // volte:360000100000130:8612300000130 + MSISDN: msisdn, // 8612300000130 + Tag: m["tag"], // volte = tag + VNI: vni, // ims.mnc001.mcc110.3gppnetwork.org + } + arr = append(arr, a) + } + return arr +} + +// ResetData 重置鉴权用户数据,清空数据库重新同步Redis数据 +func (r *Service) ResetData(neId string) int64 { + subArr := r.dataByRedis("*", neId) + // 数据清空后添加 + go r.volteRepository.ClearAndInsert(neId, subArr) + return int64(len(subArr)) +} + +// ParseInfo 解析单个用户imsi签约信息 data从命令MML得到的结果 +func (r *Service) ParseInfo(imsi, neId string, data map[string]string) VoLTEUser { + u := r.volteRepository.SelectByIMSIAndNeID(imsi, neId) + + msisdn := data["msisdn"] + if imsMsisdnLen := strings.Index(msisdn, ","); imsMsisdnLen != -1 { + msisdn = msisdn[:imsMsisdnLen] + } + + // 用于更新 + u.NeId = neId + u.IMSI = imsi + u.MSISDN = msisdn + u.Tag = data["volte_tag"] + u.VNI = data["VNI"] + return u +} + +// SelectPage 分页查询数据库 +func (r *Service) SelectPage(query map[string]any) map[string]any { + return r.volteRepository.SelectPage(query) +} + +// SelectList 查询数据库 +func (r *Service) SelectList(u VoLTEUser) []VoLTEUser { + return r.volteRepository.SelectList(u) +} + +// Insert 从数据中读取后删除imsi再存入数据库 +// imsi长度15,ki长度32,opc长度0或者32 +func (r *Service) Insert(neId string, u VoLTEUser) int64 { + uArr := r.dataByRedis(u.IMSI, neId) + if len(uArr) > 0 { + r.volteRepository.Delete(u.IMSI, neId) + return r.volteRepository.Inserts(uArr) + } + return 0 +} + +// InsertData 导入文件数据 dataType目前两种:txt/csv +func (r *Service) InsertData(neId, dataType string, data any) int64 { + // imsi截取前缀,重新获取部分数据 + prefixes := make(map[string]struct{}) + + if dataType == "csv" { + for _, v := range data.([]map[string]string) { + imsi := v["imsi"] + if len(imsi) < 6 { + continue + } + prefix := imsi[:len(imsi)-4] + prefixes[prefix] = struct{}{} + } + } + if dataType == "txt" { + for _, v := range data.([][]string) { + imsi := v[0] + if len(imsi) < 6 { + continue + } + prefix := imsi[:len(imsi)-4] + prefixes[prefix] = struct{}{} + } + } + + // 根据前缀重新加载插入 + var num int64 = 0 + for prefix := range prefixes { + // keys volte:4600001000004* + arr := r.dataByRedis(prefix+"*", neId) + if len(arr) > 0 { + r.volteRepository.DeletePrefixByIMSI(prefix, neId) + num += r.volteRepository.Inserts(arr) + } + } + return num +} + +// Delete 删除单个不重新加载 +func (r *Service) Delete(neId, imsi string) int64 { + return r.volteRepository.Delete(imsi, neId) +} + +// LoadData 重新加载从imsi开始num的数据 +func (r *Service) LoadData(neId, imsi, num string) { + startIMSI, _ := strconv.ParseInt(imsi, 10, 64) + subNum, _ := strconv.ParseInt(num, 10, 64) + var i int64 + for i = 0; i < subNum; i++ { + keyIMSI := fmt.Sprintf("%015d", startIMSI+i) + // 删除原数据 + r.volteRepository.Delete(keyIMSI, neId) + arr := r.dataByRedis(keyIMSI, neId) + if len(arr) < 1 { + continue + } + r.volteRepository.Inserts(arr) + } +} + +// ParseCommandParams 解析数据组成命令参数 msisdn=xx,xx=xx,... +func (r *Service) ParseCommandParams(item VoLTEUser) string { + var conditions []string + if item.MSISDN != "" { + conditions = append(conditions, fmt.Sprintf("msisdn=%s", item.MSISDN)) + } + + if item.Tag != "" { + conditions = append(conditions, fmt.Sprintf("volte=%s", item.Tag)) + } + if item.VNI != "" { + conditions = append(conditions, fmt.Sprintf("vni=%s", item.VNI)) + } + + return strings.Join(conditions, ",") +} diff --git a/features/ue/service/ims_user.go b/features/ue/service/ims_user.go new file mode 100644 index 00000000..c4c9a76c --- /dev/null +++ b/features/ue/service/ims_user.go @@ -0,0 +1,234 @@ +package service + +import ( + "fmt" + "strconv" + "strings" + + "be.ems/features/ue/model" + "be.ems/features/ue/repository" + "be.ems/src/framework/database/redis" + neService "be.ems/src/modules/network_element/service" +) + +// 实例化服务层 IMSUserService 结构体 +var NewIMSUserService = &IMSUserService{ + imsUserRepository: repository.NewIMSUserRepository, +} + +// VoLTE用户信息 服务层处理 +type IMSUserService struct { + imsUserRepository *repository.IMSUserRepository // VoLTE用户信息数据信息 +} + +// dataByRedis UDM签约用户 db:0 中 volte:* +func (r *IMSUserService) dataByRedis(imsi, neId string) []model.IMSUser { + arr := []model.IMSUser{} + key := fmt.Sprintf("volte:%s", imsi) + source := fmt.Sprintf("UDM_%s", neId) + + // 网元主机的Redis客户端 + redisClient, err := neService.NewNeInfo.NeRunRedisClient("UDM", neId) + if err != nil { + return arr + } + defer func() { + redisClient.Close() + redis.ConnectPush(source, nil) + }() + redis.ConnectPush(source, redisClient.Client) + + udmsdArr, err := redis.GetKeys(source, key) + if err != nil { + return arr + } + mkv, err := redis.GetHashBatch(source, udmsdArr) + if err != nil { + return arr + } + + for k, m := range mkv { + var imsi, msisdn string + KeyParts := strings.Split(k, ":") + switch len(KeyParts) { + case 0, 1: + // 处理单个部分的情况 + continue + case 2: + // 处理两个部分的情况 + imsi = KeyParts[1] + msisdn = "-" + case 3: + // 处理三个部分的情况 + imsi = KeyParts[1] + msisdn = KeyParts[2] + default: + // 处理更多部分的情况 + imsi = KeyParts[1] + msisdn = KeyParts[2] + } + + var vni string = "-" + impiParts := strings.Split(m["impi"], "@") + if len(impiParts) > 1 { + vni = impiParts[1] // 输出: ims.mnc001.mcc110.3gppnetwork.org + } + a := model.IMSUser{ + NeId: neId, + IMSI: imsi, // volte:360000100000130:8612300000130 + MSISDN: msisdn, // 8612300000130 + Tag: model.ParseCallTag(m["tag"]), // volte = tag + VNI: vni, // ims.mnc001.mcc110.3gppnetwork.org + } + arr = append(arr, a) + } + return arr +} + +// ResetData 重置鉴权用户数据,清空数据库重新同步Redis数据 +func (r *IMSUserService) ResetData(neId string) int64 { + subArr := r.dataByRedis("*", neId) + // 数据清空后添加 + go r.imsUserRepository.ClearAndInsert(neId, subArr) + return int64(len(subArr)) +} + +// ParseInfo 解析单个用户imsi签约信息 data从命令MML得到的结果 +func (r *IMSUserService) ParseInfo(imsi, neId string, data map[string]string) model.IMSUser { + u := r.imsUserRepository.SelectByIMSIAndNeID(imsi, neId) + + msisdn := data["msisdn"] + if imsMsisdnLen := strings.Index(msisdn, ","); imsMsisdnLen != -1 { + msisdn = msisdn[:imsMsisdnLen] + } + var vni string = "-" + impiParts := strings.Split(data["impi"], "@") + if len(impiParts) > 1 { + vni = impiParts[1] // 输出: ims.mnc001.mcc110.3gppnetwork.org + } + + // 用于更新 + u.NeId = neId + u.IMSI = imsi + u.MSISDN = msisdn + u.Tag = model.ParseCallTag(data["volte_tag"]) + u.VNI = vni + return u +} + +// SelectPage 分页查询数据库 +func (r *IMSUserService) SelectPage(query map[string]any) map[string]any { + return r.imsUserRepository.SelectPage(query) +} + +// SelectList 查询数据库 +func (r *IMSUserService) SelectList(u model.IMSUser) []model.IMSUser { + return r.imsUserRepository.SelectList(u) +} + +// Insert 从数据中读取后删除imsi再存入数据库 +// imsi长度15,ki长度32,opc长度0或者32 +func (r *IMSUserService) Insert(neId string, u model.IMSUser) int64 { + uArr := r.dataByRedis(u.IMSI+":*", neId) + if len(uArr) > 0 { + r.imsUserRepository.Delete(u.IMSI, neId) + return r.imsUserRepository.Inserts(uArr) + } + return 0 +} + +// InsertData 导入文件数据 dataType目前两种:txt/csv +func (r *IMSUserService) InsertData(neId, dataType string, data any) int64 { + // imsi截取前缀,重新获取部分数据 + prefixes := make(map[string]struct{}) + + if dataType == "csv" { + for _, v := range data.([]map[string]string) { + imsi := v["imsi"] + if len(imsi) < 6 { + continue + } + prefix := imsi[:len(imsi)-4] + prefixes[prefix] = struct{}{} + } + } + if dataType == "txt" { + for _, v := range data.([][]string) { + imsi := v[0] + if len(imsi) < 6 { + continue + } + prefix := imsi[:len(imsi)-4] + prefixes[prefix] = struct{}{} + } + } + + // 根据前缀重新加载插入 + var num int64 = 0 + for prefix := range prefixes { + // keys volte:4600001000004* + arr := r.dataByRedis(prefix+"*", neId) + if len(arr) > 0 { + r.imsUserRepository.DeletePrefixByIMSI(prefix, neId) + num += r.imsUserRepository.Inserts(arr) + } + } + return num +} + +// Delete 删除单个不重新加载 +func (r *IMSUserService) Delete(neId, imsi string) int64 { + return r.imsUserRepository.Delete(imsi, neId) +} + +// LoadData 重新加载从imsi开始num的数据 +func (r *IMSUserService) LoadData(neId, imsi, num string) { + startIMSI, _ := strconv.ParseInt(imsi, 10, 64) + subNum, _ := strconv.ParseInt(num, 10, 64) + var i int64 + for i = 0; i < subNum; i++ { + var keyIMSI string + if len(imsi) == model.IMSI_MAX_LENGTH { + keyIMSI = fmt.Sprintf("%015d", startIMSI+i) + } else { + // 处理不满15位的IMSI, tag=TAG_VoIP + keyIMSI = fmt.Sprintf("%d", startIMSI+i) + } + + // 删除原数据 + r.imsUserRepository.Delete(keyIMSI, neId) + arr := r.dataByRedis(keyIMSI+":*", neId) + if len(arr) < 1 { + continue + } + r.imsUserRepository.Inserts(arr) + } +} + +// ParseCommandParams 解析数据组成命令参数 msisdn=xx,xx=xx,... +func (r *IMSUserService) ParseCommandParams(item model.IMSUser) string { + var conditions []string + if item.MSISDN != "" { + conditions = append(conditions, fmt.Sprintf("msisdn=%s", item.MSISDN)) + } + + if item.Tag != model.ParseCallTag("") { + conditions = append(conditions, fmt.Sprintf("volte=%d", item.Tag)) + } + if item.VNI != "" { + conditions = append(conditions, fmt.Sprintf("vni=%s", item.VNI)) + } + + return strings.Join(conditions, ",") +} + +// ResetDataWithResult 重置鉴权用户数据,清空数据库重新同步Redis数据 +// 通过 channel 返回 ClearAndInsert 的执行结果 +func (r *IMSUserService) ResetDataWithResult(neId string) chan int64 { + arr := r.dataByRedis("*", neId) + resultCh := make(chan int64, 1) + go func() { + resultCh <- r.imsUserRepository.ClearAndInsert(neId, arr) + }() + return resultCh +} diff --git a/features/ue/service/voip_auth.go b/features/ue/service/voip_auth.go new file mode 100644 index 00000000..2ffb2a76 --- /dev/null +++ b/features/ue/service/voip_auth.go @@ -0,0 +1,186 @@ +package service + +import ( + "fmt" + "strconv" + "strings" + + "be.ems/features/ue/model" + "be.ems/features/ue/repository" + "be.ems/src/framework/database/redis" + neService "be.ems/src/modules/network_element/service" +) + +// 实例化服务层 VoIPAuthService 结构体 +var NewVoIPAuthService = &VoIPAuthService{ + voipAuthRepository: repository.NewVoIPAuthRepository, +} + +// VoLTE用户信息 服务层处理 +type VoIPAuthService struct { + voipAuthRepository *repository.VoIPAuthRepository // VoLTE用户信息数据信息 +} + +// dataByRedis VoIP鉴权数据 db:0 中 voip:* +func (r *VoIPAuthService) dataByRedis(userName, neId string) []model.VoIPAuth { + arr := []model.VoIPAuth{} + key := fmt.Sprintf("voip:%s", userName) + source := fmt.Sprintf("UDM_%s", neId) + + // 网元主机的Redis客户端 + redisClient, err := neService.NewNeInfo.NeRunRedisClient("UDM", neId) + if err != nil { + return arr + } + defer func() { + redisClient.Close() + redis.ConnectPush(source, nil) + }() + redis.ConnectPush(source, redisClient.Client) + + udmsdArr, err := redis.GetKeys(source, key) + if err != nil { + return arr + } + mkv, err := redis.GetHashBatch(source, udmsdArr) + if err != nil { + return arr + } + + for k, m := range mkv { + var userName string + KeyParts := strings.Split(k, ":") + if len(KeyParts) > 1 { + userName = KeyParts[1] + } + + a := model.VoIPAuth{ + NeId: neId, + UserName: userName, // userName + Password: m["password"], // + } + arr = append(arr, a) + } + return arr +} + +// ResetData 重置鉴权用户数据,清空数据库重新同步Redis数据 +func (r *VoIPAuthService) ResetData(neId string) int64 { + subArr := r.dataByRedis("*", neId) + // 数据清空后添加 + go r.voipAuthRepository.ClearAndInsert(neId, subArr) + return int64(len(subArr)) +} + +// ParseInfo 解析单个用户userName信息 data从命令MML得到的结果 +func (r *VoIPAuthService) ParseInfo(userName, neId string, data map[string]string) model.VoIPAuth { + u := r.voipAuthRepository.SelectByUserNameAndNeID(userName, neId) + password := data["password"] + + // 用于更新 + u.NeId = neId + u.UserName = userName + u.Password = password + return u +} + +// SelectPage 分页查询数据库 +func (r *VoIPAuthService) SelectPage(query map[string]any) map[string]any { + return r.voipAuthRepository.SelectPage(query) +} + +// SelectList 查询数据库 +func (r *VoIPAuthService) SelectList(u model.VoIPAuth) []model.VoIPAuth { + return r.voipAuthRepository.SelectList(u) +} + +// Insert 从数据中读取后删除userName再存入数据库 +func (r *VoIPAuthService) Insert(neId string, u model.VoIPAuth) int64 { + uArr := r.dataByRedis(u.UserName, neId) + if len(uArr) > 0 { + r.voipAuthRepository.Delete(u.UserName, neId) + return r.voipAuthRepository.Inserts(uArr) + } + return 0 +} + +// InsertData 导入文件数据 dataType目前两种:txt/csv +func (r *VoIPAuthService) InsertData(neId, dataType string, data any) int64 { + userNames := make(map[string]struct{}) + + if dataType == "csv" { + for _, v := range data.([]map[string]string) { + userName := v["userName"] + // if len(userName) < 6 { + // continue + // } + // prefix := userName[:len(userName)-4] + userNames[userName] = struct{}{} + } + } + if dataType == "txt" { + for _, v := range data.([][]string) { + userName := v[0] + // if len(userName) < 6 { + // continue + // } + // prefix := userName[:len(userName)-4] + userNames[userName] = struct{}{} + } + } + + // 根据前缀重新加载插入 + var num int64 = 0 + for userName := range userNames { + // keys voip:11111 + arr := r.dataByRedis(userName, neId) + if len(arr) > 0 { + r.voipAuthRepository.DeleteByUserName(userName, neId) + num += r.voipAuthRepository.Inserts(arr) + } + } + return num +} + +// Delete 删除单个不重新加载 +func (r *VoIPAuthService) Delete(neId, userName string) int64 { + return r.voipAuthRepository.Delete(userName, neId) +} + +// LoadData 重新加载从userName开始num的数据 +func (r *VoIPAuthService) LoadData(neId, userName, num string) { + startUserName, _ := strconv.ParseInt(userName, 10, 64) + subNum, _ := strconv.ParseInt(num, 10, 64) + var i int64 + for i = 0; i < subNum; i++ { + keyUserName := fmt.Sprintf("%d", startUserName+i) + // 删除原数据 + r.voipAuthRepository.Delete(keyUserName, neId) + arr := r.dataByRedis(keyUserName, neId) + if len(arr) < 1 { + continue + } + r.voipAuthRepository.Inserts(arr) + } +} + +// ParseCommandParams 解析数据组成命令参数 msisdn=xx,xx=xx,... +func (r *VoIPAuthService) ParseCommandParams(item model.VoIPAuth) string { + var conditions []string + if item.Password != "" { + conditions = append(conditions, fmt.Sprintf("password=%s", item.Password)) + } + + return strings.Join(conditions, ",") +} + +// ResetDataWithResult 重置鉴权用户数据,清空数据库重新同步Redis数据 +// 通过 channel 返回 ClearAndInsert 的执行结果 +func (r *VoIPAuthService) ResetDataWithResult(neId string) chan int64 { + arr := r.dataByRedis("*", neId) + resultCh := make(chan int64, 1) + go func() { + resultCh <- r.voipAuthRepository.ClearAndInsert(neId, arr) + }() + return resultCh +} diff --git a/src/configuration.go b/src/configuration.go index d24f7b28..8d7dc74f 100644 --- a/src/configuration.go +++ b/src/configuration.go @@ -5,9 +5,9 @@ import ( "be.ems/src/framework/config" "be.ems/src/framework/cron" - "be.ems/src/framework/datasource" + "be.ems/src/framework/database/db" + "be.ems/src/framework/database/redis" "be.ems/src/framework/logger" - "be.ems/src/framework/redis" ) //go:embed config/*.yaml @@ -23,7 +23,7 @@ func ConfigurationInit() { // 初始程序日志 logger.InitLogger() // 连接数据库实例 - datasource.Connect() + db.Connect() // 连接Redis实例 redis.Connect() // 启动调度任务实例 @@ -37,7 +37,7 @@ func ConfigurationClose() { // 关闭Redis实例 redis.Close() // 关闭数据库实例 - datasource.Close() + db.Close() // 关闭程序日志 logger.Close() } diff --git a/src/framework/database/db/db.go b/src/framework/database/db/db.go new file mode 100644 index 00000000..85f14746 --- /dev/null +++ b/src/framework/database/db/db.go @@ -0,0 +1,207 @@ +package db + +import ( + "fmt" + "log" + "os" + "regexp" + "time" + + "gorm.io/driver/mysql" + "gorm.io/gorm" + gormLog "gorm.io/gorm/logger" + + "be.ems/src/framework/config" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/parse" +) + +// 数据库连接实例 +var dbMap = make(map[string]*gorm.DB) + +type dialectInfo struct { + dialectic gorm.Dialector + logging bool +} + +// 载入数据库连接 +func loadDialect() map[string]dialectInfo { + dialects := make(map[string]dialectInfo) + + // 读取数据源配置 + 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{ + dialectic: mysql.Open(dsn), + logging: parse.Boolean(item["logging"]), + } + default: + logger.Warnf("%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 +} + +// Connect 连接数据库实例 +func Connect() { + // 遍历进行连接数据库实例 + for key, info := range loadDialect() { + opts := &gorm.Config{ + Logger: gormLog.Discard, + } + // 是否需要日志输出 + if info.logging { + opts.Logger = loadLogger() + } + // 创建连接 + db, err := gorm.Open(info.dialectic, opts) + if err != nil { + logger.Errorf("failed error db connect: %s", err) + continue + } + // 获取底层 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) + } + // SetMaxIdleConns 用于设置连接池中空闲连接的最大数量。 + sqlDB.SetMaxIdleConns(10) + // SetMaxOpenConns 设置打开数据库连接的最大数量。 + sqlDB.SetMaxOpenConns(100) + // SetConnMaxLifetime 设置了连接可复用的最大时间。 + sqlDB.SetConnMaxLifetime(time.Hour) + logger.Infof("database %s connection is successful.", key) + dbMap[key] = db + } +} + +// Close 关闭数据库实例 +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) + } + } +} + +// DB 获取数据源 +// +// source-数据源 +func DB(source string) *gorm.DB { + // 不指定时获取默认实例 + if source == "" { + source = config.Get("gorm.defaultDataSourceName").(string) + } + db := dbMap[source] + if db == nil { + logger.Errorf("not database source: %s", source) + return nil + } + return db +} + +// Names 获取数据源名称列表 +func Names() []string { + var names []string + for key := range dbMap { + names = append(names, key) + } + return names +} + +// RawDB 原生语句查询 +// +// source-数据源 +// sql-预编译的SQL语句 +// parameters-预编译的SQL语句参数 +func RawDB(source string, sql string, parameters []any) ([]map[string]any, error) { + var rows []map[string]any + // 数据源 + db := DB(source) + if db == nil { + return rows, fmt.Errorf("not database source") + } + // 使用正则表达式替换连续的空白字符为单个空格 + fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ") + // 查询结果 + res := db.Raw(fmtSql, parameters...).Scan(&rows) + if res.Error != nil { + return nil, res.Error + } + return rows, nil +} + +// ExecDB 原生语句执行 +// +// source-数据源 +// sql-预编译的SQL语句 +// parameters-预编译的SQL语句参数 +func ExecDB(source string, sql string, parameters []any) (int64, error) { + // 数据源 + db := DB(source) + if db == nil { + return 0, fmt.Errorf("not database source") + } + // 使用正则表达式替换连续的空白字符为单个空格 + fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ") + // 执行结果 + res := db.Exec(fmtSql, parameters...) + if res.Error != nil { + return 0, res.Error + } + return res.RowsAffected, nil +} + +// PageNumSize 分页页码记录数 +// +// pageNum-页码 +// pageSize-记录数 +func PageNumSize(pageNum, pageSize any) (int, int) { + // 记录起始索引 + num := parse.Number(pageNum) + if num < 1 { + num = 1 + } + + // 显示记录数 + size := parse.Number(pageSize) + if size < 0 { + size = 10 + } + return int(num - 1), int(size) +} diff --git a/src/framework/database/db/expand.go b/src/framework/database/db/expand.go new file mode 100644 index 00000000..7212e4ca --- /dev/null +++ b/src/framework/database/db/expand.go @@ -0,0 +1,104 @@ +package db + +import ( + "bufio" + "log" + "os" + "path/filepath" + "strings" + + "gorm.io/gorm" + + "be.ems/src/framework/config" +) + +// ImportSQL 导入SQL +func ImportSQL() { + sqlPath := config.Get("sqlPath").(string) + if sqlPath == "" { + return + } + sqlSource := config.Get("sqlSource").(string) + if sqlSource == "" { + sqlSource = config.Get("database.defaultDataSourceName").(string) + } + + // 数据源 + db := DB(sqlSource) + if db == nil { + log.Fatalln("not database source") + return + } + + // 获取路径信息 + fileInfo, err := os.Stat(sqlPath) + if err != nil { + log.Fatalln(err.Error()) + return + } + + // 处理目录或文件 + if fileInfo.IsDir() { + // 处理目录 + files, err := os.ReadDir(sqlPath) + if err != nil { + log.Fatalln(err.Error()) + return + } + + for _, file := range files { + if file.IsDir() { + continue + } + if !strings.HasSuffix(file.Name(), ".sql") { + continue + } + processSQLFile(db, filepath.Join(sqlPath, file.Name())) + } + } else { + // 处理单个文件 + processSQLFile(db, sqlPath) + } + + log.Println("process success") + os.Exit(0) +} + +// 处理单个SQL文件的通用函数 +func processSQLFile(db *gorm.DB, filePath string) { + file, err := os.Open(filePath) + if err != nil { + log.Fatalln(err.Error()) + return + } + defer file.Close() + + // 逐行读取 SQL 文件 + scanner := bufio.NewScanner(file) + var sqlBuilder strings.Builder + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + // 跳过注释和空行 + if strings.HasPrefix(line, "--") || strings.TrimSpace(line) == "" { + continue + } + // 跳过配置语句 + if strings.HasPrefix(line, "/*!") { + continue + } + + sqlBuilder.WriteString(line + "\n") + + // 当遇到分号时,执行 SQL 语句 + if strings.HasSuffix(line, ";") { + // 执行 SQL 语句 + if err := db.Exec(sqlBuilder.String()).Error; err != nil { + log.Fatalln(err.Error()) + return + } + + sqlBuilder.Reset() + continue + } + } +} diff --git a/src/framework/redis/conn.go b/src/framework/database/redis/conn.go similarity index 100% rename from src/framework/redis/conn.go rename to src/framework/database/redis/conn.go diff --git a/src/framework/redis/expand.go b/src/framework/database/redis/expand.go similarity index 100% rename from src/framework/redis/expand.go rename to src/framework/database/redis/expand.go diff --git a/src/framework/redis/redis.go b/src/framework/database/redis/redis.go similarity index 59% rename from src/framework/redis/redis.go rename to src/framework/database/redis/redis.go index 2438ec4d..077075bf 100644 --- a/src/framework/redis/redis.go +++ b/src/framework/database/redis/redis.go @@ -2,35 +2,21 @@ package redis import ( "context" + "errors" "fmt" "strings" "time" + "github.com/redis/go-redis/v9" + "be.ems/src/framework/config" "be.ems/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实例 +// Connect 连接Redis实例 func Connect() { ctx := context.Background() // 读取数据源配置 @@ -47,29 +33,23 @@ func Connect() { // 测试数据库连接 pong, err := rdb.Ping(ctx).Result() if err != nil { - logger.Fatalf("Ping redis %s is %v", k, err) + logger.Fatalf("failed error redis connect: %s is %v", k, err) } - logger.Infof("redis %s %s %d connection is successful.", k, pong, client["db"].(int)) + logger.Infof("redis %s %d %s connection is successful.", k, client["db"].(int), pong) rdbMap[k] = rdb } } -// 关闭Redis实例 +// Close 关闭Redis实例 func Close() { for _, rdb := range rdbMap { if err := rdb.Close(); err != nil { - logger.Errorf("fatal error db close: %s", err) + logger.Errorf("redis db close: %s", err) } } } -// 获取默认实例 -func DefaultRDB() *redis.Client { - source := config.Get("redis.defaultDataSourceName").(string) - return rdbMap[source] -} - -// 获取实例 +// RDB 获取实例 func RDB(source string) *redis.Client { // 不指定时获取默认实例 if source == "" { @@ -80,18 +60,19 @@ func RDB(source string) *redis.Client { // Info 获取redis服务信息 func Info(source string) map[string]map[string]string { + infoObj := make(map[string]map[string]string) // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return infoObj } ctx := context.Background() info, err := rdb.Info(ctx).Result() if err != nil { - return map[string]map[string]string{} + return infoObj } - infoObj := make(map[string]map[string]string) + lines := strings.Split(info, "\r\n") label := "" for _, line := range lines { @@ -114,9 +95,9 @@ func Info(source string) map[string]map[string]string { // KeySize 获取redis当前连接可用键Key总数信息 func KeySize(source string) int64 { // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return 0 } ctx := context.Background() @@ -129,18 +110,19 @@ func KeySize(source string) int64 { // CommandStats 获取redis命令状态信息 func CommandStats(source string) []map[string]string { + statsObjArr := make([]map[string]string, 0) // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return statsObjArr } ctx := context.Background() commandstats, err := rdb.Info(ctx, "commandstats").Result() if err != nil { - return []map[string]string{} + return statsObjArr } - statsObjArr := make([]map[string]string, 0) + lines := strings.Split(commandstats, "\r\n") for _, line := range lines { if !strings.HasPrefix(line, "cmdstat_") { @@ -157,12 +139,12 @@ func CommandStats(source string) []map[string]string { return statsObjArr } -// 获取键的剩余有效时间(秒) -func GetExpire(source string, key string) (float64, error) { +// GetExpire 获取键的剩余有效时间(秒) +func GetExpire(source string, key string) (int64, error) { // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return 0, fmt.Errorf("redis not client") } ctx := context.Background() @@ -170,168 +152,195 @@ func GetExpire(source string, key string) (float64, error) { if err != nil { return 0, err } - return ttl.Seconds(), nil + return int64(ttl.Seconds()), nil } -// 获得缓存数据的key列表 -func GetKeys(source string, match string) ([]string, error) { +// GetKeys 获得缓存数据的key列表 +func GetKeys(source string, pattern string) ([]string, error) { + keys := make([]string, 0) // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return keys, fmt.Errorf("redis not client") } - keys := make([]string, 0) + // 游标 + var cursor uint64 = 0 + var count int64 = 100 ctx := context.Background() - iter := rdb.Scan(ctx, 0, match, 1000).Iterator() - if err := iter.Err(); err != nil { - logger.Errorf("Failed to scan keys: %v", err) - return keys, err - } - for iter.Next(ctx) { - keys = append(keys, iter.Val()) + // 循环遍历获取匹配的键 + for { + // 使用 SCAN 命令获取匹配的键 + batchKeys, nextCursor, err := rdb.Scan(ctx, cursor, pattern, count).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 } -// 批量获得缓存数据 +// GetBatch 批量获得缓存数据 func GetBatch(source string, keys []string) ([]any, error) { + result := make([]any, 0) if len(keys) == 0 { - return []any{}, fmt.Errorf("not keys") + return result, fmt.Errorf("not keys") } // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return result, fmt.Errorf("redis not client") } // 获取缓存数据 - result, err := rdb.MGet(context.Background(), keys...).Result() - if err != nil { - logger.Errorf("Failed to get batch data: %v", err) - return []any{}, err + v, err := rdb.MGet(context.Background(), keys...).Result() + if err != nil || errors.Is(err, redis.Nil) { + logger.Errorf("failed to get batch data: %v", err) + return result, err } - return result, nil + return v, nil } -// 获得缓存数据 +// Get 获得缓存数据 func Get(source, key string) (string, error) { // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return "", fmt.Errorf("redis not client") } ctx := context.Background() - value, err := rdb.Get(ctx, key).Result() - if err == redis.Nil || err != nil { + v, err := rdb.Get(ctx, key).Result() + if errors.Is(err, redis.Nil) { + return "", fmt.Errorf("no keys") + } + if err != nil { return "", err } - return value, nil + return v, nil } -// 判断是否存在 -func Has(source string, keys ...string) (bool, error) { +// Has 判断是否存在 +func Has(source string, keys ...string) (int64, error) { // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return 0, fmt.Errorf("redis not client") } ctx := context.Background() exists, err := rdb.Exists(ctx, keys...).Result() if err != nil { - return false, err + return 0, err } - return exists >= 1, nil + return exists, nil } -// 设置缓存数据 -func Set(source, key string, value any) (bool, error) { +// Set 设置缓存数据 +func Set(source, key string, value any) error { // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return fmt.Errorf("redis not client") } 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 err } - return true, nil + return nil } -// 设置缓存数据与过期时间 -func SetByExpire(source, key string, value any, expiration time.Duration) (bool, error) { +// SetByExpire 设置缓存数据与过期时间 +func SetByExpire(source, key string, value any, expiration time.Duration) error { // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return fmt.Errorf("redis not client") } 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 err } - return true, nil + return nil } -// 删除单个 -func Del(source string, key string) (bool, error) { +// Del 删除单个 +func Del(source string, key string) error { // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return fmt.Errorf("redis not client") } ctx := context.Background() - err := rdb.Del(ctx, key).Err() - if err != nil { + if err := rdb.Del(ctx, key).Err(); err != nil { logger.Errorf("redis Del err %v", err) - return false, err + return err } - return true, nil + return nil } -// 删除多个 -func DelKeys(source string, keys []string) (bool, error) { +// DelKeys 删除多个 +func DelKeys(source string, keys []string) error { if len(keys) == 0 { - return false, fmt.Errorf("no keys") + return fmt.Errorf("no keys") } // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return fmt.Errorf("redis not client") } ctx := context.Background() - err := rdb.Del(ctx, keys...).Err() - if err != nil { + if err := rdb.Del(ctx, keys...).Err(); err != nil { logger.Errorf("redis DelKeys err %v", err) - return false, err + return err } - return true, nil + return nil } -// 限流查询并记录 +// RateLimit 限流查询并记录 func RateLimit(source, limitKey string, time, count int64) (int64, error) { // 数据源 - rdb := DefaultRDB() - if source != "" { - rdb = RDB(source) + rdb := RDB(source) + if rdb == nil { + return 0, fmt.Errorf("redis not client") } ctx := context.Background() result, err := rateLimitCommand.Run(ctx, rdb, []string{limitKey}, time, count).Result() if err != nil { - logger.Errorf("redis RateLimit err %v", err) + logger.Errorf("redis lua script err %v", err) return 0, err } return result.(int64), err } + +// 声明定义限流脚本命令 +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);`) diff --git a/src/framework/datasource/datasource.go b/src/framework/datasource/datasource.go index 0bebed8d..88533dde 100644 --- a/src/framework/datasource/datasource.go +++ b/src/framework/datasource/datasource.go @@ -1,127 +1,22 @@ package datasource import ( - "fmt" - "log" - "os" "regexp" - "time" - "be.ems/src/framework/config" - "be.ems/src/framework/logger" + "be.ems/src/framework/database/db" "be.ems/src/framework/utils/parse" - "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] + return db.DB("") } // 获取数据源 func DB(source string) *gorm.DB { - if source == "" { - source = config.Get("gorm.defaultDataSourceName").(string) - } - return dbMap[source] + return db.DB(source) } // RawDB 原生查询语句 diff --git a/src/framework/middleware/rate_limit.go b/src/framework/middleware/rate_limit.go index 368028d6..a007007c 100644 --- a/src/framework/middleware/rate_limit.go +++ b/src/framework/middleware/rate_limit.go @@ -6,8 +6,8 @@ import ( "time" "be.ems/src/framework/constants/cachekey" + "be.ems/src/framework/database/redis" "be.ems/src/framework/i18n" - "be.ems/src/framework/redis" "be.ems/src/framework/utils/ctx" "be.ems/src/framework/utils/ip2region" "be.ems/src/framework/vo/result" diff --git a/src/framework/middleware/repeat/repeat.go b/src/framework/middleware/repeat/repeat.go index 1d08c594..de689fab 100644 --- a/src/framework/middleware/repeat/repeat.go +++ b/src/framework/middleware/repeat/repeat.go @@ -6,8 +6,8 @@ import ( "time" "be.ems/src/framework/constants/cachekey" + "be.ems/src/framework/database/redis" "be.ems/src/framework/logger" - "be.ems/src/framework/redis" "be.ems/src/framework/utils/ctx" "be.ems/src/framework/utils/ip2region" "be.ems/src/framework/vo/result" diff --git a/src/framework/reqctx/auth.go b/src/framework/reqctx/auth.go new file mode 100644 index 00000000..3d55a874 --- /dev/null +++ b/src/framework/reqctx/auth.go @@ -0,0 +1,160 @@ +package reqctx + +import ( + "fmt" + "strings" + + "github.com/gin-gonic/gin" + + "be.ems/src/framework/config" + "be.ems/src/framework/constants" + "be.ems/src/framework/vo" +) + +// LoginUser 登录用户信息 +func LoginUser(c *gin.Context) (vo.LoginUser, error) { + value, exists := c.Get(constants.CTX_LOGIN_USER) + if exists && value != nil { + return value.(vo.LoginUser), nil + } + return vo.LoginUser{}, fmt.Errorf("invalid login user information") +} + +// LoginUserToUserID 登录用户信息-用户ID +func LoginUserToUserID(c *gin.Context) string { + info, err := LoginUser(c) + if err != nil { + return "" + } + return info.UserID +} + +// LoginUserToUserName 登录用户信息-用户名称 +func LoginUserToUserName(c *gin.Context) string { + info, err := LoginUser(c) + if err != nil { + return "" + } + return info.User.UserName +} + +// LoginUserByContainRoles 登录用户信息-包含角色KEY +func LoginUserByContainRoles(c *gin.Context, target string) bool { + info, err := LoginUser(c) + if err != nil { + return false + } + if config.IsAdmin(info.UserID) { + return true + } + roles := info.User.Roles + for _, item := range roles { + if item.RoleKey == target { + return true + } + } + return false +} + +// LoginUserByContainPerms 登录用户信息-包含权限标识 +func LoginUserByContainPerms(c *gin.Context, target string) bool { + loginUser, err := LoginUser(c) + if err != nil { + return false + } + if config.IsAdmin(loginUser.UserID) { + return true + } + perms := loginUser.Permissions + for _, str := range perms { + if str == target { + return true + } + } + return false +} + +// LoginUserToDataScopeSQL 登录用户信息-角色数据范围过滤SQL字符串 +func LoginUserToDataScopeSQL(c *gin.Context, deptAlias string, userAlias string) string { + dataScopeSQL := "" + // 登录用户信息 + info, err := LoginUser(c) + if err != nil { + return dataScopeSQL + } + userInfo := info.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 constants.ROLE_SCOPE_ALL == dataScope { + break + } + + if constants.ROLE_SCOPE_CUSTOM != dataScope { + hasKey := false + for _, key := range scopeKeys { + if key == dataScope { + hasKey = true + break + } + } + if hasKey { + continue + } + } + + if constants.ROLE_SCOPE_CUSTOM == dataScope { + sql := fmt.Sprintf(`%s.dept_id IN + ( SELECT dept_id FROM sys_role_dept WHERE role_id = %s ) + AND %s.dept_id NOT IN + ( + SELECT d.parent_id FROM sys_dept d + INNER JOIN sys_role_dept rd ON rd.dept_id = d.dept_id + AND rd.role_id = %s + )`, deptAlias, role.RoleID, deptAlias, role.RoleID) + conditions = append(conditions, sql) + } + + if constants.ROLE_SCOPE_DEPT == dataScope { + sql := fmt.Sprintf("%s.dept_id = %s", deptAlias, userInfo.DeptID) + conditions = append(conditions, sql) + } + + if constants.ROLE_SCOPE_DEPT_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 constants.ROLE_SCOPE_SELF == dataScope { + if userAlias == "" { + sql := fmt.Sprintf("%s.dept_id = %s", deptAlias, userInfo.DeptID) + 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(" ( %s ) ", strings.Join(conditions, " OR ")) + } + return dataScopeSQL +} diff --git a/src/framework/reqctx/context.go b/src/framework/reqctx/context.go new file mode 100644 index 00000000..dc597025 --- /dev/null +++ b/src/framework/reqctx/context.go @@ -0,0 +1,106 @@ +package reqctx + +import ( + "strings" + + "github.com/gin-gonic/gin" + "golang.org/x/text/language" + + "be.ems/src/framework/constants" +) + +// QueryMap Query参数转换Map +func QueryMap(c *gin.Context) map[string]string { + queryValues := c.Request.URL.Query() + queryParams := make(map[string]string, len(queryValues)) + 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, 0) + c.ShouldBindBodyWithJSON(¶ms) + return params +} + +// RequestParamsMap 请求参数转换Map +func RequestParamsMap(c *gin.Context) map[string]any { + params := make(map[string]any, 0) + // json + if strings.HasPrefix(c.ContentType(), "application/json") { + c.ShouldBindBodyWithJSON(¶ms) + } + + // 表单 + formParams := c.Request.PostForm + for key, value := range formParams { + if _, ok := params[key]; !ok { + params[key] = value[0] + } + } + + // 查询 + queryParams := c.Request.URL.Query() + for key, value := range queryParams { + if _, ok := params[key]; !ok { + params[key] = value[0] + } + } + return params +} + +// Authorization 解析请求头 +func Authorization(c *gin.Context) string { + // Query请求查询 + if authQuery, ok := c.GetQuery(constants.ACCESS_TOKEN); ok && authQuery != "" { + return authQuery + } + // Header请求头 + if authHeader := c.GetHeader(constants.ACCESS_TOKEN); authHeader != "" { + return authHeader + } + + // Query请求查询 + if authQuery, ok := c.GetQuery(constants.ACCESS_TOKEN_QUERY); ok && authQuery != "" { + return authQuery + } + // Header请求头 + authHeader := c.GetHeader(constants.HEADER_KEY) + if authHeader == "" { + return "" + } + // 拆分 Authorization 请求头,提取 JWT 令牌部分 + arr := strings.SplitN(authHeader, constants.HEADER_PREFIX, 2) + if len(arr) < 2 { + return "" + } + return arr[1] +} + +// AcceptLanguage 解析客户端接收语言 zh:中文 en: 英文 +func AcceptLanguage(c *gin.Context) string { + preferredLanguage := language.English + + // Query请求查询 + if v, ok := c.GetQuery("language"); ok && v != "" { + tags, _, _ := language.ParseAcceptLanguage(v) + if len(tags) > 0 { + preferredLanguage = tags[0] + } + } + // Header请求头 + if v := c.GetHeader("Accept-Language"); v != "" { + tags, _, _ := language.ParseAcceptLanguage(v) + if len(tags) > 0 { + preferredLanguage = tags[0] + } + } + + // 只取前缀 + lang := preferredLanguage.String() + arr := strings.Split(lang, "-") + return arr[0] +} diff --git a/src/framework/reqctx/param.go b/src/framework/reqctx/param.go new file mode 100644 index 00000000..2a671eee --- /dev/null +++ b/src/framework/reqctx/param.go @@ -0,0 +1,35 @@ +package reqctx + +import ( + "github.com/gin-gonic/gin" + + "be.ems/src/framework/utils/ip2region" + "be.ems/src/framework/utils/ua" +) + +// IPAddrLocation 解析ip地址 +func IPAddrLocation(c *gin.Context) (string, string) { + ip := ip2region.ClientIP(c.ClientIP()) + location := "-" //ip2region.RealAddressByIp(ip) + return ip, location +} + +// UaOsBrowser 解析请求用户代理信息 +func UaOsBrowser(c *gin.Context) (string, string) { + userAgent := c.GetHeader("user-agent") + uaInfo := ua.Info(userAgent) + + browser := "-" + if bName, bVersion := uaInfo.Browser(); bName != "" { + browser = bName + if bVersion != "" { + browser = bName + " " + bVersion + } + } + + os := "-" + if bos := uaInfo.OS(); bos != "" { + os = bos + } + return os, browser +} diff --git a/src/framework/resp/api.go b/src/framework/resp/api.go new file mode 100644 index 00000000..eff18f28 --- /dev/null +++ b/src/framework/resp/api.go @@ -0,0 +1,74 @@ +package resp + +const ( + // CODE_ERROR 响应-code错误失败 + CODE_ERROR = 0 + // MSG_ERROR 响应-msg错误失败 + MSG_ERROR = "error" + + // CODE_SUCCESS 响应-msg正常成功 + CODE_SUCCESS = 1 + // MSG_SUCCCESS 响应-code正常成功 + MSG_SUCCCESS = "success" + + // 响应-code加密数据 + CODE_ENCRYPT = 2 + // 响应-msg加密数据 + MSG_ENCRYPT = "encrypt" +) + +// Resp 响应结构体 +type Resp struct { + Code int `json:"code"` // 响应状态码 + Msg string `json:"msg"` // 响应信息 + Data any `json:"data,omitempty"` // 响应数据 +} + +// CodeMsg 响应结果 +func CodeMsg(code int, msg string) Resp { + return Resp{Code: code, Msg: msg} +} + +// Ok 响应成功结果 +func Ok(v map[string]any) map[string]any { + args := make(map[string]any) + args["code"] = CODE_SUCCESS + args["msg"] = MSG_SUCCCESS + // v合并到args + for key, value := range v { + args[key] = value + } + return args +} + +// OkMsg 响应成功结果信息 +func OkMsg(msg string) Resp { + return Resp{Code: CODE_SUCCESS, Msg: msg} +} + +// OkData 响应成功结果数据 +func OkData(data any) Resp { + return Resp{Code: CODE_SUCCESS, Msg: MSG_SUCCCESS, Data: data} +} + +// Err 响应失败结果 map[string]any{} +func Err(v map[string]any) map[string]any { + args := make(map[string]any) + args["code"] = CODE_ERROR + args["msg"] = MSG_ERROR + // v合并到args + for key, value := range v { + args[key] = value + } + return args +} + +// ErrMsg 响应失败结果信息 +func ErrMsg(msg string) Resp { + return Resp{Code: CODE_ERROR, Msg: msg} +} + +// ErrData 响应失败结果数据 +func ErrData(data any) Resp { + return Resp{Code: CODE_ERROR, Msg: MSG_ERROR, Data: data} +} diff --git a/src/framework/resp/error.go b/src/framework/resp/error.go new file mode 100644 index 00000000..3df849f3 --- /dev/null +++ b/src/framework/resp/error.go @@ -0,0 +1,23 @@ +package resp + +import ( + "fmt" + "strings" + + "github.com/go-playground/validator/v10" +) + +// FormatBindError 格式化Gin ShouldBindWith绑定错误 +// +// binding:"required" 验证失败返回: field=id type=string tag=required value= +func FormatBindError(err error) string { + if errs, ok := err.(validator.ValidationErrors); ok { + var errMsgs []string + for _, e := range errs { + str := fmt.Sprintf("[field=%s, type=%s, tag=%s, param=%s, value=%v]", e.Field(), e.Type().Name(), e.Tag(), e.Param(), e.Value()) + errMsgs = append(errMsgs, str) + } + return strings.Join(errMsgs, ", ") + } + return err.Error() +} diff --git a/src/framework/utils/parse/parse.go b/src/framework/utils/parse/parse.go index a51e313f..d174e741 100644 --- a/src/framework/utils/parse/parse.go +++ b/src/framework/utils/parse/parse.go @@ -91,31 +91,16 @@ func ConvertToCamelCase(str string) string { return strings.Join(words, "") } -// Bit 比特位为单位 +// Bit 比特位为单位 1023.00 B --> 1.00 KB 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) + units := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} + for i := 0; i < len(units); i++ { + if bit < 1024 || i == len(units)-1 { + return fmt.Sprintf("%.2f %s", bit, units[i]) + } + bit /= 1024 } + return "" } // CronExpression 解析 Cron 表达式,返回下一次执行的时间戳(毫秒) @@ -146,11 +131,11 @@ func SafeContent(value string) string { } // RemoveDuplicates 数组内字符串去重 -func RemoveDuplicates(ids []string) []string { +func RemoveDuplicates(arr []string) []string { uniqueIDs := make(map[string]bool) uniqueIDSlice := make([]string, 0) - for _, id := range ids { + for _, id := range arr { _, ok := uniqueIDs[id] if !ok && id != "" { uniqueIDs[id] = true @@ -161,6 +146,29 @@ func RemoveDuplicates(ids []string) []string { return uniqueIDSlice } +// RemoveDuplicatesToArray 数组内字符串分隔去重转为字符数组 +func RemoveDuplicatesToArray(keyStr, sep string) []string { + arr := make([]string, 0) + if keyStr == "" { + return arr + } + if strings.Contains(keyStr, sep) { + // 处理字符转数组后去重 + strArr := strings.Split(keyStr, sep) + uniqueKeys := make(map[string]bool) + for _, str := range strArr { + _, ok := uniqueKeys[str] + if !ok && str != "" { + uniqueKeys[str] = true + arr = append(arr, str) + } + } + } else { + arr = append(arr, keyStr) + } + return arr +} + // Color 解析颜色 #fafafa func Color(colorStr string) *color.RGBA { // 去除 # 号 diff --git a/src/framework/utils/token/token.go b/src/framework/utils/token/token.go index b07879d6..3e9fb465 100644 --- a/src/framework/utils/token/token.go +++ b/src/framework/utils/token/token.go @@ -8,8 +8,8 @@ import ( "be.ems/src/framework/config" cachekeyConstants "be.ems/src/framework/constants/cachekey" tokenConstants "be.ems/src/framework/constants/token" + redisCahe "be.ems/src/framework/database/redis" "be.ems/src/framework/logger" - redisCahe "be.ems/src/framework/redis" "be.ems/src/framework/utils/generate" "be.ems/src/framework/utils/machine" "be.ems/src/framework/vo" @@ -28,7 +28,7 @@ func Remove(tokenStr string) string { uuid := claims[tokenConstants.JWT_UUID].(string) tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + uuid hasKey, _ := redisCahe.Has("", tokenKey) - if hasKey { + if hasKey > 0 { redisCahe.Del("", tokenKey) } return claims[tokenConstants.JWT_NAME].(string) @@ -141,7 +141,7 @@ func LoginUser(claims jwt.MapClaims) vo.LoginUser { tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + uuid hasKey, _ := redisCahe.Has("", tokenKey) var loginUser vo.LoginUser - if hasKey { + if hasKey > 0 { loginUserStr, _ := redisCahe.Get("", tokenKey) if loginUserStr == "" { return loginUser diff --git a/src/modules/common/controller/captcha.go b/src/modules/common/controller/captcha.go index c39a6430..cb5adcfd 100644 --- a/src/modules/common/controller/captcha.go +++ b/src/modules/common/controller/captcha.go @@ -6,8 +6,8 @@ import ( "be.ems/src/framework/config" "be.ems/src/framework/constants/cachekey" "be.ems/src/framework/constants/captcha" + "be.ems/src/framework/database/redis" "be.ems/src/framework/logger" - "be.ems/src/framework/redis" "be.ems/src/framework/utils/parse" "be.ems/src/framework/vo/result" systemService "be.ems/src/modules/system/service" diff --git a/src/modules/common/service/account.go b/src/modules/common/service/account.go index dce06a02..6dc4ef38 100644 --- a/src/modules/common/service/account.go +++ b/src/modules/common/service/account.go @@ -8,7 +8,7 @@ import ( adminConstants "be.ems/src/framework/constants/admin" "be.ems/src/framework/constants/cachekey" "be.ems/src/framework/constants/common" - "be.ems/src/framework/redis" + "be.ems/src/framework/database/redis" "be.ems/src/framework/utils/crypto" "be.ems/src/framework/utils/parse" "be.ems/src/framework/vo" @@ -131,9 +131,9 @@ func (s *Account) UpdateLoginDateAndIP(loginUser *vo.LoginUser) bool { func (s *Account) ClearLoginRecordCache(username string) bool { cacheKey := cachekey.PWD_ERR_CNT_KEY + username hasKey, _ := redis.Has("", cacheKey) - if hasKey { - delOk, _ := redis.Del("", cacheKey) - return delOk + if hasKey > 0 { + err := redis.Del("", cacheKey) + return err == nil } return false } diff --git a/src/modules/common/service/register.impl.go b/src/modules/common/service/register.impl.go index 5f43d0ac..3c203fe5 100644 --- a/src/modules/common/service/register.impl.go +++ b/src/modules/common/service/register.impl.go @@ -5,7 +5,7 @@ import ( "be.ems/src/framework/constants/cachekey" "be.ems/src/framework/constants/common" - "be.ems/src/framework/redis" + "be.ems/src/framework/database/redis" "be.ems/src/framework/utils/parse" systemModel "be.ems/src/modules/system/model" systemService "be.ems/src/modules/system/service" diff --git a/src/modules/monitor/controller/sys_cache.go b/src/modules/monitor/controller/sys_cache.go index 6743d0cb..937a3353 100644 --- a/src/modules/monitor/controller/sys_cache.go +++ b/src/modules/monitor/controller/sys_cache.go @@ -2,8 +2,8 @@ package controller import ( "be.ems/src/framework/constants/cachekey" + "be.ems/src/framework/database/redis" "be.ems/src/framework/i18n" - "be.ems/src/framework/redis" "be.ems/src/framework/utils/ctx" "be.ems/src/framework/vo/result" "be.ems/src/modules/monitor/model" @@ -116,8 +116,8 @@ func (s *SysCacheController) ClearCacheName(c *gin.Context) { c.JSON(200, result.ErrMsg(err.Error())) return } - ok, _ := redis.DelKeys("", cacheKeys) - if ok { + err = redis.DelKeys("", cacheKeys) + if err == nil { c.JSON(200, result.Ok(nil)) return } @@ -136,8 +136,8 @@ func (s *SysCacheController) ClearCacheKey(c *gin.Context) { return } - ok, _ := redis.Del("", cacheName+":"+cacheKey) - if ok { + err := redis.Del("", cacheName+":"+cacheKey) + if err == nil { c.JSON(200, result.Ok(nil)) return } diff --git a/src/modules/monitor/controller/sys_user_online.go b/src/modules/monitor/controller/sys_user_online.go index 09b0edec..e49b12ba 100644 --- a/src/modules/monitor/controller/sys_user_online.go +++ b/src/modules/monitor/controller/sys_user_online.go @@ -6,8 +6,8 @@ import ( "strings" "be.ems/src/framework/constants/cachekey" + "be.ems/src/framework/database/redis" "be.ems/src/framework/i18n" - "be.ems/src/framework/redis" "be.ems/src/framework/utils/ctx" "be.ems/src/framework/vo" "be.ems/src/framework/vo/result" @@ -130,8 +130,8 @@ func (s *SysUserOnlineController) ForceLogout(c *gin.Context) { } // 删除token - ok, _ := redis.Del("", cachekey.LOGIN_TOKEN_KEY+tokenId) - if ok { + err := redis.Del("", cachekey.LOGIN_TOKEN_KEY+tokenId) + if err == nil { c.JSON(200, result.Ok(nil)) return } diff --git a/src/modules/network_data/service/all_perf_kpi.go b/src/modules/network_data/service/all_perf_kpi.go index aadeaf50..36dc3d35 100644 --- a/src/modules/network_data/service/all_perf_kpi.go +++ b/src/modules/network_data/service/all_perf_kpi.go @@ -6,7 +6,7 @@ import ( "time" "be.ems/src/framework/constants/cachekey" - "be.ems/src/framework/redis" + "be.ems/src/framework/database/redis" "be.ems/src/framework/utils/parse" "be.ems/src/modules/network_data/model" "be.ems/src/modules/network_data/repository" diff --git a/src/modules/network_data/service/udm_auth.go b/src/modules/network_data/service/udm_auth.go index 67b4e296..399ef64e 100644 --- a/src/modules/network_data/service/udm_auth.go +++ b/src/modules/network_data/service/udm_auth.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "be.ems/src/framework/redis" + "be.ems/src/framework/database/redis" "be.ems/src/modules/network_data/model" "be.ems/src/modules/network_data/repository" neService "be.ems/src/modules/network_element/service" diff --git a/src/modules/network_data/service/udm_sub.go b/src/modules/network_data/service/udm_sub.go index 5ff990fe..bb05f309 100644 --- a/src/modules/network_data/service/udm_sub.go +++ b/src/modules/network_data/service/udm_sub.go @@ -5,7 +5,7 @@ import ( "strconv" "strings" - "be.ems/src/framework/redis" + "be.ems/src/framework/database/redis" "be.ems/src/modules/network_data/model" "be.ems/src/modules/network_data/repository" neService "be.ems/src/modules/network_element/service" diff --git a/src/modules/network_element/controller/ne_host.go b/src/modules/network_element/controller/ne_host.go index 64fd3069..6688d262 100644 --- a/src/modules/network_element/controller/ne_host.go +++ b/src/modules/network_element/controller/ne_host.go @@ -3,8 +3,8 @@ package controller import ( "strings" + "be.ems/src/framework/database/redis" "be.ems/src/framework/i18n" - "be.ems/src/framework/redis" "be.ems/src/framework/telnet" "be.ems/src/framework/utils/ctx" "be.ems/src/framework/utils/parse" diff --git a/src/modules/network_element/fetch_link/ne_trace.go b/src/modules/network_element/fetch_link/ne_trace.go index 387633fa..b31117ae 100644 --- a/src/modules/network_element/fetch_link/ne_trace.go +++ b/src/modules/network_element/fetch_link/ne_trace.go @@ -10,31 +10,10 @@ import ( "be.ems/src/modules/network_element/model" ) -// NeTraceInfo 网元跟踪任务信息 -func NeTraceInfo(neInfo model.NeInfo, traceId string) (map[string]any, error) { - // 跟踪任务信息 - neUrl := fmt.Sprintf("http://%s:%d/api/rest/traceManagement/v1/subscriptions?id=%s", neInfo.IP, neInfo.Port, traceId) - resBytes, err := fetch.Get(neUrl, nil, 30_000) - if err != nil { - logger.Warnf("NeTraceInfo Get \"%s\"", neUrl) - logger.Errorf("NeTraceInfo %s", err.Error()) - return nil, fmt.Errorf("NeService Trace Info API Error") - } - - // 序列化结果 - var resData map[string]any - err = json.Unmarshal(resBytes, &resData) - if err != nil { - logger.Errorf("NeTraceInfo Unmarshal %s", err.Error()) - return nil, err - } - return resData, nil -} - // NeTraceAdd 网元跟踪任务新增 -func NeTraceAdd(neInfo model.NeInfo, data map[string]any) (map[string]any, error) { +func NeTraceAdd(neInfo model.NeInfo, data any) (map[string]any, error) { // 跟踪任务创建 - neUrl := fmt.Sprintf("http://%s:%d/api/rest/traceManagement/v1/subscriptions", neInfo.IP, neInfo.Port) + neUrl := fmt.Sprintf("http://%s:%d/api/rest/traceManagement/v2/%s/subscriptions", neInfo.IP, neInfo.Port, neInfo.NeType) resBytes, err := fetch.PostJSON(neUrl, data, nil) var resData map[string]any if err != nil { @@ -60,48 +39,17 @@ func NeTraceAdd(neInfo model.NeInfo, data map[string]any) (map[string]any, error return resData, nil } -// NeTraceEdit 网元跟踪任务编辑 -func NeTraceEdit(neInfo model.NeInfo, data map[string]any) (map[string]any, error) { - // 网元参数配置新增(array) - neUrl := fmt.Sprintf("http://%s:%d/api/rest/traceManagement/v1/subscriptions", neInfo.IP, neInfo.Port) - resBytes, err := fetch.PutJSON(neUrl, data, nil) - var resData map[string]any - if err != nil { - errStr := err.Error() - logger.Warnf("NeTraceEdit PUT \"%s\"", neUrl) - if strings.HasPrefix(errStr, "201") || strings.HasPrefix(errStr, "204") { - return resData, nil - } - logger.Errorf("NeTraceEdit %s", errStr) - return nil, fmt.Errorf("NeService Trace Edit API Error") - } - - // 200 成功无数据时 - if len(resBytes) == 0 { - return resData, nil - } - - // 序列化结果 - err = json.Unmarshal(resBytes, &resData) - if err != nil { - logger.Errorf("NeTraceEdit Unmarshal %s", err.Error()) - return nil, err - } - return resData, nil -} - // NeTraceDelete 网元跟踪任务删除 func NeTraceDelete(neInfo model.NeInfo, traceId string) (map[string]any, error) { - // 网元参数配置删除(array) - neUrl := fmt.Sprintf("http://%s:%d/api/rest/traceManagement/v1/subscriptions?id=%s", neInfo.IP, neInfo.Port, traceId) + neUrl := fmt.Sprintf("http://%s:%d/api/rest/traceManagement/v2/%s/subscriptions?id=%s", neInfo.IP, neInfo.Port, neInfo.NeType, traceId) resBytes, err := fetch.Delete(neUrl, nil) var resData map[string]any if err != nil { errStr := err.Error() - logger.Warnf("NeTraceDelete Delete \"%s\"", neUrl) if strings.HasPrefix(errStr, "201") || strings.HasPrefix(errStr, "204") { return resData, nil } + logger.Warnf("NeTraceDelete Delete \"%s\"", neUrl) logger.Errorf("NeTraceDelete %s", errStr) return nil, fmt.Errorf("NeService Trace Delete API Error") } diff --git a/src/modules/network_element/service/ne_config.go b/src/modules/network_element/service/ne_config.go index 7981d0e1..2a4d7755 100644 --- a/src/modules/network_element/service/ne_config.go +++ b/src/modules/network_element/service/ne_config.go @@ -6,7 +6,7 @@ import ( "strings" "be.ems/src/framework/constants/cachekey" - "be.ems/src/framework/redis" + "be.ems/src/framework/database/redis" "be.ems/src/modules/network_element/model" "be.ems/src/modules/network_element/repository" ) @@ -81,8 +81,8 @@ func (r *NeConfig) ClearNeCacheByNeType(neType string) bool { if err != nil { return false } - delOk, _ := redis.DelKeys("", keys) - return delOk + err = redis.DelKeys("", keys) + return err == nil } // SelectNeConfigByNeType 查询网元类型参数配置 diff --git a/src/modules/system/service/sys_config.impl.go b/src/modules/system/service/sys_config.impl.go index d983102e..c7c6d6c9 100644 --- a/src/modules/system/service/sys_config.impl.go +++ b/src/modules/system/service/sys_config.impl.go @@ -4,7 +4,7 @@ import ( "fmt" "be.ems/src/framework/constants/cachekey" - "be.ems/src/framework/redis" + "be.ems/src/framework/database/redis" "be.ems/src/modules/system/model" "be.ems/src/modules/system/repository" ) @@ -154,8 +154,8 @@ func (r *SysConfigImpl) clearConfigCache(configKey string) bool { if err != nil { return false } - delOk, _ := redis.DelKeys("", keys) - return delOk + err = redis.DelKeys("", keys) + return err == nil } // SelectConfigByKey 查询配置信息BY键 diff --git a/src/modules/system/service/sys_dict_type.go b/src/modules/system/service/sys_dict_type.go index 4008b327..b8c8e7b9 100644 --- a/src/modules/system/service/sys_dict_type.go +++ b/src/modules/system/service/sys_dict_type.go @@ -6,7 +6,7 @@ import ( "be.ems/src/framework/constants/cachekey" "be.ems/src/framework/constants/common" - "be.ems/src/framework/redis" + "be.ems/src/framework/database/redis" "be.ems/src/modules/system/model" "be.ems/src/modules/system/repository" ) @@ -183,8 +183,8 @@ func (r *SysDictType) ClearDictCache(dictType string) bool { if err != nil { return false } - delOk, _ := redis.DelKeys("", keys) - return delOk + err = redis.DelKeys("", keys) + return err == nil } // DictDataCache 获取字典数据缓存数据