feat: 数据库引用变更

This commit is contained in:
TsMask
2025-04-22 14:30:05 +08:00
parent af4d2b70cc
commit 55b6aa348b
32 changed files with 1546 additions and 348 deletions

View File

@@ -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长度15ki长度32opc长度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, ",")
}

View File

@@ -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长度15ki长度32opc长度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
}

View File

@@ -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
}

View File

@@ -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()
}

View File

@@ -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)
}

View File

@@ -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
}
}
}

View File

@@ -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);`)

View File

@@ -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 原生查询语句

View File

@@ -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"

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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(&params)
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(&params)
}
// 表单
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]
}

View File

@@ -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
}

74
src/framework/resp/api.go Normal file
View File

@@ -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}
}

View File

@@ -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()
}

View File

@@ -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 {
// 去除 # 号

View File

@@ -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

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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")
}

View File

@@ -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 查询网元类型参数配置

View File

@@ -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键

View File

@@ -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 获取字典数据缓存数据