refactor: 删除冗余常量文件并整合常量定义
This commit is contained in:
@@ -3,21 +3,21 @@
|
|||||||
# duration: rotation time with xx hours, example: 1/12/24 hours
|
# duration: rotation time with xx hours, example: 1/12/24 hours
|
||||||
# count: rotation count of log, default is 30 rotation
|
# count: rotation count of log, default is 30 rotation
|
||||||
logger:
|
logger:
|
||||||
file: /usr/local/omc/log/restagent.log
|
file: /usr/local/omc/log/restagent.log
|
||||||
level: warn
|
level: warn
|
||||||
duration: 24
|
duration: 24
|
||||||
count: 90
|
count: 90
|
||||||
|
|
||||||
# rest agent listen ipv4/v6 and port, support multiple routines
|
# rest agent listen ipv4/v6 and port, support multiple routines
|
||||||
# ip: 0.0.0.0 or ::0, support IPv4/v6
|
# ip: 0.0.0.0 or ::0, support IPv4/v6
|
||||||
# clientAuthType: 0:NoClientCert (default), 1:RequestClientCert, 2:RequireAnyClientCert,
|
# clientAuthType: 0:NoClientCert (default), 1:RequestClientCert, 2:RequireAnyClientCert,
|
||||||
# 3:VerifyClientCertIfGiven, 4:RequireAndVerifyClientCerts
|
# 3:VerifyClientCertIfGiven, 4:RequireAndVerifyClientCerts
|
||||||
rest:
|
rest:
|
||||||
- ipv4: 0.0.0.0
|
- ipv4: 0.0.0.0
|
||||||
ipv6:
|
ipv6:
|
||||||
port: 33030
|
port: 33030
|
||||||
- ipv4: 0.0.0.0
|
- ipv4: 0.0.0.0
|
||||||
ipv6:
|
ipv6:
|
||||||
port: 33443
|
port: 33443
|
||||||
schema: https
|
schema: https
|
||||||
clientAuthType: 0
|
clientAuthType: 0
|
||||||
@@ -28,7 +28,7 @@ rest:
|
|||||||
webServer:
|
webServer:
|
||||||
enabled: true
|
enabled: true
|
||||||
rootDir: /usr/local/omc/htdocs/front
|
rootDir: /usr/local/omc/htdocs/front
|
||||||
listen:
|
listen:
|
||||||
- addr: :80
|
- addr: :80
|
||||||
schema: http
|
schema: http
|
||||||
- addr: :443
|
- addr: :443
|
||||||
@@ -38,15 +38,25 @@ webServer:
|
|||||||
certFile: /usr/local/omc/etc/certs/omc-server.crt
|
certFile: /usr/local/omc/etc/certs/omc-server.crt
|
||||||
keyFile: /usr/local/omc/etc/certs/omc-server.key
|
keyFile: /usr/local/omc/etc/certs/omc-server.key
|
||||||
|
|
||||||
|
# data sources
|
||||||
database:
|
database:
|
||||||
type: mysql
|
dataSource:
|
||||||
user: root
|
# Default database instance
|
||||||
password: 1000omc@kp!
|
default:
|
||||||
host: 127.0.0.1
|
type: "mysql"
|
||||||
port: 33066
|
host: "127.0.0.1"
|
||||||
name: omc_db
|
port: 33066
|
||||||
connParam: charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&interpolateParams=True
|
username: "root"
|
||||||
backup: /usr/local/omc/database
|
password: "1000omc@kp!"
|
||||||
|
database: "omc_db"
|
||||||
|
logging: false
|
||||||
|
# Built-in lightweight database
|
||||||
|
lite:
|
||||||
|
type: "sqlite"
|
||||||
|
database: "/usr/local/omc/database/omc_db.sqlite"
|
||||||
|
logging: false
|
||||||
|
# used to specify the default data source for multiple data resourece
|
||||||
|
defaultDataSourceName: "default"
|
||||||
|
|
||||||
# Redis data cache
|
# Redis data cache
|
||||||
redis:
|
redis:
|
||||||
@@ -85,9 +95,9 @@ ne:
|
|||||||
scpdir: /tmp
|
scpdir: /tmp
|
||||||
licensedir: /usr/local/etc/{neType}/license
|
licensedir: /usr/local/etc/{neType}/license
|
||||||
# backup etc list of IMS, does not contain spaces
|
# backup etc list of IMS, does not contain spaces
|
||||||
etcListIMS: '{*.yaml,mmtel,vars.cfg}'
|
etcListIMS: "{*.yaml,mmtel,vars.cfg}"
|
||||||
etcListDefault: '{*.yaml,*.conf,*.cfg}'
|
etcListDefault: "{*.yaml,*.conf,*.cfg}"
|
||||||
# true/false to overwrite config file when dpkg ne software
|
# true/false to overwrite config file when dpkg ne software
|
||||||
dpkgOverwrite: false
|
dpkgOverwrite: false
|
||||||
# dpkg timeout (second)
|
# dpkg timeout (second)
|
||||||
dpkgTimeout: 180
|
dpkgTimeout: 180
|
||||||
@@ -115,7 +125,7 @@ omc:
|
|||||||
frontTraceDir: /usr/local/omc/htdocs/front/trace
|
frontTraceDir: /usr/local/omc/htdocs/front/trace
|
||||||
software: /usr/local/omc/software
|
software: /usr/local/omc/software
|
||||||
license: /usr/local/omc/license
|
license: /usr/local/omc/license
|
||||||
gtpUri: gtp:192.168.2.119:32152
|
gtpUri: gtp:192.168.2.119:32152
|
||||||
checkContentType: false
|
checkContentType: false
|
||||||
testMode: false
|
testMode: false
|
||||||
rbacMode: true
|
rbacMode: true
|
||||||
@@ -126,21 +136,21 @@ omc:
|
|||||||
# Forward interface:
|
# Forward interface:
|
||||||
# TLS Skip verify: true/false
|
# TLS Skip verify: true/false
|
||||||
# email/sms
|
# email/sms
|
||||||
# smProxy: sms(Short Message Service)/smsc(SMS Centre)
|
# smProxy: sms(Short Message Service)/smsc(SMS Centre)
|
||||||
# dataCoding: 0:GSM7BIT, 1:ASCII, 2:BINARY8BIT1, 3:LATIN1,
|
# dataCoding: 0:GSM7BIT, 1:ASCII, 2:BINARY8BIT1, 3:LATIN1,
|
||||||
# 4:BINARY8BIT2, 6:CYRILLIC, 7:HEBREW, 8:UCS2
|
# 4:BINARY8BIT2, 6:CYRILLIC, 7:HEBREW, 8:UCS2
|
||||||
alarm:
|
alarm:
|
||||||
alarmEmailForward:
|
alarmEmailForward:
|
||||||
enable: true
|
enable: true
|
||||||
emailList:
|
emailList:
|
||||||
smtp: mail.smtp.com
|
smtp: mail.smtp.com
|
||||||
port: 25
|
port: 25
|
||||||
user: smtpext@smtp.com
|
user: smtpext@smtp.com
|
||||||
password: "1000smtp@omc!"
|
password: "1000smtp@omc!"
|
||||||
tlsSkipVerify: true
|
tlsSkipVerify: true
|
||||||
alarmSMSForward:
|
alarmSMSForward:
|
||||||
enable: true
|
enable: true
|
||||||
mobileList:
|
mobileList:
|
||||||
smscAddr: "192.168.13.114:2775"
|
smscAddr: "192.168.13.114:2775"
|
||||||
systemID: "omc"
|
systemID: "omc"
|
||||||
password: "omc123"
|
password: "omc123"
|
||||||
@@ -154,7 +164,7 @@ alarm:
|
|||||||
signName: xxx SMSC
|
signName: xxx SMSC
|
||||||
templateCode: 1000
|
templateCode: 1000
|
||||||
smProxy: smsc
|
smProxy: smsc
|
||||||
|
|
||||||
# User authorized information
|
# User authorized information
|
||||||
# crypt: mysql/md5/bcrypt
|
# crypt: mysql/md5/bcrypt
|
||||||
# token: true/false to check accessToken
|
# token: true/false to check accessToken
|
||||||
@@ -169,7 +179,7 @@ auth:
|
|||||||
publicKey: /usr/local/omc/etc/certs/omc
|
publicKey: /usr/local/omc/etc/certs/omc
|
||||||
privateKey: /usr/local/omc/etc/certs/omc
|
privateKey: /usr/local/omc/etc/certs/omc
|
||||||
|
|
||||||
# Parameter for limit number
|
# Parameter for limit number
|
||||||
# rmuid_maxnum: the max number of rmUID, default: 50
|
# rmuid_maxnum: the max number of rmUID, default: 50
|
||||||
# alarmid_maxnum: the max number of AlarmID, default: 50
|
# alarmid_maxnum: the max number of AlarmID, default: 50
|
||||||
# pmid_maxnum: the max number of pmID, default: 50
|
# pmid_maxnum: the max number of pmID, default: 50
|
||||||
@@ -186,4 +196,4 @@ params:
|
|||||||
|
|
||||||
testConfig:
|
testConfig:
|
||||||
enabled: false
|
enabled: false
|
||||||
file: /usr/local/omc/etc/testconfig.yaml
|
file: /usr/local/omc/etc/testconfig.yaml
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ import (
|
|||||||
libGlobal "be.ems/lib/global"
|
libGlobal "be.ems/lib/global"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cfg *viper.Viper
|
var conf *viper.Viper
|
||||||
|
|
||||||
// 初始化程序配置
|
// 初始化程序配置
|
||||||
func InitConfig(configDir, assetsDir embed.FS) {
|
func InitConfig(configDir *embed.FS) {
|
||||||
cfg = viper.New()
|
conf = viper.New()
|
||||||
initFlag()
|
initFlag()
|
||||||
initViper(configDir, assetsDir)
|
initViper(configDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 指定参数绑定
|
// 指定参数绑定
|
||||||
@@ -50,63 +50,50 @@ func initFlag() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.BindPFlags(pflag.CommandLine)
|
conf.BindPFlags(pflag.CommandLine)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置文件读取
|
// 配置文件读取
|
||||||
func initViper(configDir, assetsDir embed.FS) {
|
func initViper(configDir *embed.FS) {
|
||||||
// 如果配置文件名中没有扩展名,则需要设置Type
|
// 如果配置文件名中没有扩展名,则需要设置Type
|
||||||
cfg.SetConfigType("yaml")
|
conf.SetConfigType("yaml")
|
||||||
|
// 读取默认配置文件
|
||||||
// 从 embed.FS 中读取默认配置文件内容
|
configDefaultByte, err := configDir.ReadFile("config/config.default.yaml")
|
||||||
configDefault, err := configDir.ReadFile("config/config.default.yaml")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ReadFile config default file: %s", err)
|
log.Fatalf("config default file read error: %s", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 设置默认配置文件内容到 viper
|
if err = conf.ReadConfig(bytes.NewReader(configDefaultByte)); err != nil {
|
||||||
err = cfg.ReadConfig(bytes.NewReader(configDefault))
|
log.Fatalf("config default file read error: %s", err)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("NewReader config default file: %s", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载运行环境配置
|
// 当期服务环境运行配置 => local
|
||||||
env := cfg.GetString("env")
|
env := conf.GetString("env")
|
||||||
if env != "local" && env != "prod" {
|
log.Printf("current service environment operation configuration => %s \n", env)
|
||||||
log.Fatalf("fatal error config env for local or prod : %s", env)
|
|
||||||
}
|
|
||||||
log.Printf("Current service environment operation configuration => %s \n", env)
|
|
||||||
|
|
||||||
// 加载运行配置文件合并相同配置
|
// 加载运行配置文件合并相同配置
|
||||||
envPath := "config/config.prod.yaml"
|
envConfigPath := fmt.Sprintf("config/config.%s.yaml", env)
|
||||||
if env == "local" {
|
configEnvByte, err := configDir.ReadFile(envConfigPath)
|
||||||
envPath = "config/config.local.yaml"
|
|
||||||
}
|
|
||||||
// 从 embed.FS 中读取默认配置文件内容
|
|
||||||
configEnv, err := configDir.ReadFile(envPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ReadFile config local file: %s", err)
|
log.Fatalf("config env %s file read error: %s", env, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// 设置默认配置文件内容到 viper
|
if err = conf.MergeConfig(bytes.NewReader(configEnvByte)); err != nil {
|
||||||
err = cfg.MergeConfig(bytes.NewReader(configEnv))
|
log.Fatalf("config env %s file read error: %s", env, err)
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("NewReader config local file: %s", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 合并外部使用配置
|
// 外部文件配置
|
||||||
configFile := cfg.GetString("config")
|
externalConfig := conf.GetString("config")
|
||||||
if configFile != "" {
|
if externalConfig != "" {
|
||||||
configInMerge(configFile)
|
// readExternalConfig(externalConfig)
|
||||||
|
// 处理旧配置,存在相同的配置项处理
|
||||||
|
configInMerge(externalConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录程序开始运行的时间点
|
// 记录程序开始运行的时间点
|
||||||
cfg.Set("runTime", time.Now())
|
conf.Set("runTime", time.Now())
|
||||||
|
|
||||||
// 设置程序内全局资源访问
|
|
||||||
cfg.Set("AssetsDir", assetsDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 配置文件读取进行内部参数合并
|
// 配置文件读取进行内部参数合并
|
||||||
@@ -137,54 +124,62 @@ func configInMerge(configFile string) {
|
|||||||
if key == "testconfig" || key == "logger" {
|
if key == "testconfig" || key == "logger" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// 数据库配置
|
conf.Set(key, value)
|
||||||
if key == "database" {
|
|
||||||
item := value.(map[string]any)
|
|
||||||
defaultItem := cfg.GetStringMap("gorm.datasource.default")
|
|
||||||
defaultItem["type"] = item["type"]
|
|
||||||
defaultItem["host"] = item["host"]
|
|
||||||
defaultItem["port"] = item["port"]
|
|
||||||
defaultItem["username"] = item["user"]
|
|
||||||
defaultItem["password"] = item["password"]
|
|
||||||
defaultItem["database"] = item["name"]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cfg.Set(key, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Env 获取运行服务环境
|
// Env 获取运行服务环境
|
||||||
// local prod
|
// local prod
|
||||||
func Env() string {
|
func Env() string {
|
||||||
return cfg.GetString("env")
|
return conf.GetString("env")
|
||||||
}
|
}
|
||||||
|
|
||||||
// RunTime 程序开始运行的时间
|
// RunTime 程序开始运行的时间
|
||||||
func RunTime() time.Time {
|
func RunTime() time.Time {
|
||||||
return cfg.GetTime("runTime")
|
return conf.GetTime("runTime")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get 获取配置信息
|
// Get 获取配置信息
|
||||||
//
|
//
|
||||||
// Get("server.proxy")
|
// Get("server.proxy")
|
||||||
func Get(key string) any {
|
func Get(key string) any {
|
||||||
return cfg.Get(key)
|
return conf.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAssetsDirFS 访问程序内全局资源访问
|
// GetAssetsDirFS 访问程序内全局资源访问
|
||||||
func GetAssetsDirFS() embed.FS {
|
func GetAssetsDirFS() *embed.FS {
|
||||||
return cfg.Get("AssetsDir").(embed.FS)
|
return conf.Get("AssetsDir").(*embed.FS)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAdmin 用户是否为管理员
|
// SetAssetsDirFS 设置程序内全局资源访问
|
||||||
func IsAdmin(userID string) bool {
|
func SetAssetsDirFS(assetsDir *embed.FS) {
|
||||||
if userID == "" {
|
conf.Set("AssetsDir", assetsDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readExternalConfig 读取外部文件配置
|
||||||
|
func readExternalConfig(configPaht string) {
|
||||||
|
f, err := os.Open(configPaht)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("config external file read error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if err = conf.MergeConfig(f); err != nil {
|
||||||
|
log.Fatalf("config external file read error: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSystemUser 用户是否为系统管理员
|
||||||
|
func IsSystemUser(userId int64) bool {
|
||||||
|
if userId <= 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
// 从本地配置获取user信息
|
// 从配置中获取系统管理员ID列表
|
||||||
admins := Get("user.adminList").([]any)
|
arr := Get("user.system").([]any)
|
||||||
for _, s := range admins {
|
for _, v := range arr {
|
||||||
if fmt.Sprint(s) == userID {
|
if fmt.Sprint(v) == fmt.Sprint(userId) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
package admin
|
|
||||||
|
|
||||||
// 系统管理员常量信息
|
|
||||||
|
|
||||||
// 系统管理员-系统指定角色ID
|
|
||||||
const ROLE_ID = "1"
|
|
||||||
|
|
||||||
// 系统管理员-系统指定角色KEY
|
|
||||||
const ROLE_KEY = "system"
|
|
||||||
|
|
||||||
// 系统管理员-系统指定权限
|
|
||||||
const PERMISSION = "*:*:*"
|
|
||||||
25
src/framework/constants/cache_key.go
Normal file
25
src/framework/constants/cache_key.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
// 缓存的key常量
|
||||||
|
const (
|
||||||
|
// CACHE_LOGIN_TOKEN 登录用户
|
||||||
|
CACHE_LOGIN_TOKEN = "login_tokens"
|
||||||
|
// CACHE_CAPTCHA_CODE 验证码
|
||||||
|
CACHE_CAPTCHA_CODE = "captcha_codes"
|
||||||
|
// CACHE_SYS_CONFIG 参数管理
|
||||||
|
CACHE_SYS_CONFIG = "sys_config"
|
||||||
|
// CACHE_SYS_DICT 字典管理
|
||||||
|
CACHE_SYS_DICT = "sys_dict"
|
||||||
|
// CACHE_REPEAT_SUBMIT 防重提交
|
||||||
|
CACHE_REPEAT_SUBMIT = "repeat_submit"
|
||||||
|
// CACHE_RATE_LIMIT 限流
|
||||||
|
CACHE_RATE_LIMIT = "rate_limit"
|
||||||
|
// CACHE_PWD_ERR_COUNT 登录账户密码错误次数
|
||||||
|
CACHE_PWD_ERR_COUNT = "pwd_err_count"
|
||||||
|
// CACHE_I18N 多语言
|
||||||
|
CACHE_I18N = "i18n"
|
||||||
|
// CACHE_NE_INFO 网元信息管理
|
||||||
|
CACHE_NE_INFO = "ne_info"
|
||||||
|
// CACHE_NE_DATA 网元数据管理
|
||||||
|
CACHE_NE_DATA = "ne_data"
|
||||||
|
)
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package cachekey
|
|
||||||
|
|
||||||
// 缓存的key常量
|
|
||||||
|
|
||||||
// 登录用户
|
|
||||||
const LOGIN_TOKEN_KEY = "login_tokens:"
|
|
||||||
|
|
||||||
// 验证码
|
|
||||||
const CAPTCHA_CODE_KEY = "captcha_codes:"
|
|
||||||
|
|
||||||
// 参数管理
|
|
||||||
const SYS_CONFIG_KEY = "sys_config:"
|
|
||||||
|
|
||||||
// 字典管理
|
|
||||||
const SYS_DICT_KEY = "sys_dict:"
|
|
||||||
|
|
||||||
// 防重提交
|
|
||||||
const REPEAT_SUBMIT_KEY = "repeat_submit:"
|
|
||||||
|
|
||||||
// 限流
|
|
||||||
const RATE_LIMIT_KEY = "rate_limit:"
|
|
||||||
|
|
||||||
// 登录账户密码错误次数
|
|
||||||
const PWD_ERR_CNT_KEY = "pwd_err_cnt:"
|
|
||||||
|
|
||||||
// 网元信息管理
|
|
||||||
const NE_KEY = "ne_info:"
|
|
||||||
|
|
||||||
// 网元数据管理
|
|
||||||
const NE_DATA_KEY = "ne_data:"
|
|
||||||
11
src/framework/constants/captcha.go
Normal file
11
src/framework/constants/captcha.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
// 验证码常量信息
|
||||||
|
const (
|
||||||
|
// CAPTCHA_EXPIRATION 验证码有效期,单位秒
|
||||||
|
CAPTCHA_EXPIRATION = 2 * 60
|
||||||
|
// CAPTCHA_TYPE_CHAR 验证码类型-数值计算
|
||||||
|
CAPTCHA_TYPE_CHAR = "char"
|
||||||
|
// CAPTCHA_TYPE_MATH 验证码类型-字符验证
|
||||||
|
CAPTCHA_TYPE_MATH = "math"
|
||||||
|
)
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package captcha
|
|
||||||
|
|
||||||
// 验证码常量信息
|
|
||||||
|
|
||||||
// 验证码有效期,单位秒
|
|
||||||
const EXPIRATION = 2 * 60
|
|
||||||
|
|
||||||
// 验证码类型-数值计算
|
|
||||||
const TYPE_CHAR = "char"
|
|
||||||
|
|
||||||
// 验证码类型-字符验证
|
|
||||||
const TYPE_MATH = "math"
|
|
||||||
14
src/framework/constants/common.go
Normal file
14
src/framework/constants/common.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
const (
|
||||||
|
// STATUS_YES 通用状态标识-正常/成功/是
|
||||||
|
STATUS_YES = "1"
|
||||||
|
// STATUS_NO 通用状态标识-停用/失败/否
|
||||||
|
STATUS_NO = "0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CTX_LOGIN_USER 上下文信息-登录用户
|
||||||
|
const CTX_LOGIN_USER = "ctx:login_user"
|
||||||
|
|
||||||
|
// 启动-引导系统初始
|
||||||
|
const LAUNCH_BOOTLOADER = "bootloader"
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
// 通用常量信息
|
|
||||||
|
|
||||||
// www主域
|
|
||||||
const WWW = "www."
|
|
||||||
|
|
||||||
// http请求
|
|
||||||
const HTTP = "http://"
|
|
||||||
|
|
||||||
// https请求
|
|
||||||
const HTTPS = "https://"
|
|
||||||
|
|
||||||
// 通用状态标识-正常/成功/是
|
|
||||||
const STATUS_YES = "1"
|
|
||||||
|
|
||||||
// 通用状态标识-停用/失败/否
|
|
||||||
const STATUS_NO = "0"
|
|
||||||
|
|
||||||
// 上下文信息-登录用户
|
|
||||||
const CTX_LOGIN_USER = "loginuser"
|
|
||||||
|
|
||||||
// 启动-引导系统初始
|
|
||||||
const LAUNCH_BOOTLOADER = "bootloader"
|
|
||||||
21
src/framework/constants/menu.go
Normal file
21
src/framework/constants/menu.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
// 系统菜单常量信息
|
||||||
|
const (
|
||||||
|
// MENU_COMPONENT_LAYOUT_BASIC 组件布局类型-基础布局组件标识
|
||||||
|
MENU_COMPONENT_LAYOUT_BASIC = "BasicLayout"
|
||||||
|
// MENU_COMPONENT_LAYOUT_BLANK 组件布局类型-空白布局组件标识
|
||||||
|
MENU_COMPONENT_LAYOUT_BLANK = "BlankLayout"
|
||||||
|
// MENU_COMPONENT_LAYOUT_LINK 组件布局类型-内链接布局组件标识
|
||||||
|
MENU_COMPONENT_LAYOUT_LINK = "LinkLayout"
|
||||||
|
|
||||||
|
// MENU_TYPE_DIR 菜单类型-目录
|
||||||
|
MENU_TYPE_DIR = "D"
|
||||||
|
// MENU_TYPE_MENU 菜单类型-菜单
|
||||||
|
MENU_TYPE_MENU = "M"
|
||||||
|
// MENU_TYPE_BUTTON 菜单类型-按钮
|
||||||
|
MENU_TYPE_BUTTON = "B"
|
||||||
|
|
||||||
|
// MENU_PATH_INLINE 菜单内嵌地址标识-带/前缀
|
||||||
|
MENU_PATH_INLINE = "/inline"
|
||||||
|
)
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package menu
|
|
||||||
|
|
||||||
// 系统菜单常量信息
|
|
||||||
|
|
||||||
const (
|
|
||||||
// 组件布局类型-基础布局组件标识
|
|
||||||
COMPONENT_LAYOUT_BASIC = "BasicLayout"
|
|
||||||
// 组件布局类型-空白布局组件标识
|
|
||||||
COMPONENT_LAYOUT_BLANK = "BlankLayout"
|
|
||||||
// 组件布局类型-内链接布局组件标识
|
|
||||||
COMPONENT_LAYOUT_LINK = "LinkLayout"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// 菜单类型-目录
|
|
||||||
TYPE_DIR = "D"
|
|
||||||
// 菜单类型-菜单
|
|
||||||
TYPE_MENU = "M"
|
|
||||||
// 菜单类型-按钮
|
|
||||||
TYPE_BUTTON = "B"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 菜单内嵌地址标识-带/前缀
|
|
||||||
const PATH_INLINE = "/inline"
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package result
|
|
||||||
|
|
||||||
// 响应结果常量信息
|
|
||||||
|
|
||||||
const (
|
|
||||||
// 响应-code错误失败
|
|
||||||
CODE_ERROR = 0
|
|
||||||
// 响应-msg错误失败
|
|
||||||
MSG_ERROR = "error"
|
|
||||||
|
|
||||||
// 响应-code正常成功
|
|
||||||
CODE_SUCCESS = 1
|
|
||||||
// 响应-msg正常成功
|
|
||||||
MSG_SUCCESS = "success"
|
|
||||||
|
|
||||||
// 响应-code加密数据
|
|
||||||
CODE_ENCRYPT = 2
|
|
||||||
// 响应-msg加密数据
|
|
||||||
MSG_ENCRYPT = "encrypt"
|
|
||||||
)
|
|
||||||
24
src/framework/constants/role_data_scope.go
Normal file
24
src/framework/constants/role_data_scope.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
// 系统角色数据范围常量
|
||||||
|
const (
|
||||||
|
// ROLE_SCOPE_ALL 全部数据权限
|
||||||
|
ROLE_SCOPE_ALL = "1"
|
||||||
|
// ROLE_SCOPE_CUSTOM 自定数据权限
|
||||||
|
ROLE_SCOPE_CUSTOM = "2"
|
||||||
|
// ROLE_SCOPE_DEPT 部门数据权限
|
||||||
|
ROLE_SCOPE_DEPT = "3"
|
||||||
|
// ROLE_SCOPE_DEPT_CHILD 部门及以下数据权限
|
||||||
|
ROLE_SCOPE_DEPT_CHILD = "4"
|
||||||
|
// ROLE_SCOPE_SELF 仅本人数据权限
|
||||||
|
ROLE_SCOPE_SELF = "5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ROLE_SCOPE_DATA 系统角色数据范围映射
|
||||||
|
var ROLE_SCOPE_DATA = map[string]string{
|
||||||
|
ROLE_SCOPE_ALL: "全部数据权限",
|
||||||
|
ROLE_SCOPE_CUSTOM: "自定数据权限",
|
||||||
|
ROLE_SCOPE_DEPT: "部门数据权限",
|
||||||
|
ROLE_SCOPE_DEPT_CHILD: "部门及以下数据权限",
|
||||||
|
ROLE_SCOPE_SELF: "仅本人数据权限",
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
package roledatascope
|
|
||||||
|
|
||||||
// 系统角色数据范围常量
|
|
||||||
|
|
||||||
const (
|
|
||||||
// 全部数据权限
|
|
||||||
ALL = "1"
|
|
||||||
|
|
||||||
// 自定数据权限
|
|
||||||
CUSTOM = "2"
|
|
||||||
|
|
||||||
// 部门数据权限
|
|
||||||
DEPT = "3"
|
|
||||||
|
|
||||||
// 部门及以下数据权限
|
|
||||||
DEPT_AND_CHILD = "4"
|
|
||||||
|
|
||||||
// 仅本人数据权限
|
|
||||||
SELF = "5"
|
|
||||||
)
|
|
||||||
12
src/framework/constants/system.go
Normal file
12
src/framework/constants/system.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
// 系统常量信息
|
||||||
|
|
||||||
|
// SYS_ROLE_SYSTEM_ID 系统管理员-系统指定角色ID
|
||||||
|
const SYS_ROLE_SYSTEM_ID = 1
|
||||||
|
|
||||||
|
// SYS_ROLE_SYSTEM_KEY 系统管理员-系统指定角色KEY
|
||||||
|
const SYS_ROLE_SYSTEM_KEY = "system"
|
||||||
|
|
||||||
|
// SYS_PERMISSION_SYSTEM 系统管理员-系统指定权限
|
||||||
|
const SYS_PERMISSION_SYSTEM = "*:*:*"
|
||||||
24
src/framework/constants/token.go
Normal file
24
src/framework/constants/token.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
// 令牌常量信息
|
||||||
|
|
||||||
|
// HEADER_PREFIX 令牌-请求头标识前缀
|
||||||
|
const HEADER_PREFIX = "Bearer "
|
||||||
|
|
||||||
|
// HEADER_KEY 令牌-请求头标识
|
||||||
|
const HEADER_KEY = "Authorization"
|
||||||
|
|
||||||
|
// JWT_UUID 令牌-JWT唯一标识字段
|
||||||
|
const JWT_UUID = "uuid"
|
||||||
|
|
||||||
|
// JWT_USER_ID 令牌-JWT标识用户主键字段
|
||||||
|
const JWT_USER_ID = "user_id"
|
||||||
|
|
||||||
|
// JWT_USER_NAME 令牌-JWT标识用户登录账号字段
|
||||||
|
const JWT_USER_NAME = "user_name"
|
||||||
|
|
||||||
|
// NMS北向使用-数据响应字段和请求头授权
|
||||||
|
const ACCESS_TOKEN = "accessToken"
|
||||||
|
|
||||||
|
// WS请求使用-数据响应字段和请求头授权
|
||||||
|
const ACCESS_TOKEN_QUERY = "access_token"
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package token
|
|
||||||
|
|
||||||
// 令牌常量信息
|
|
||||||
|
|
||||||
// 令牌-数据响应字段
|
|
||||||
const RESPONSE_FIELD = "access_token"
|
|
||||||
|
|
||||||
// 令牌-请求头标识前缀
|
|
||||||
const HEADER_PREFIX = "Bearer "
|
|
||||||
|
|
||||||
// 令牌-请求头标识
|
|
||||||
const HEADER_KEY = "Authorization"
|
|
||||||
|
|
||||||
// 令牌-JWT唯一标识字段
|
|
||||||
const JWT_UUID = "login_key"
|
|
||||||
|
|
||||||
// 令牌-JWT标识用户主键字段
|
|
||||||
const JWT_KEY = "user_id"
|
|
||||||
|
|
||||||
// 令牌-JWT标识用户登录账号字段
|
|
||||||
const JWT_NAME = "user_name"
|
|
||||||
|
|
||||||
// NMS北向使用-数据响应字段和请求头授权
|
|
||||||
const ACCESS_TOKEN = "accessToken"
|
|
||||||
30
src/framework/constants/upload_sub_path.go
Normal file
30
src/framework/constants/upload_sub_path.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package constants
|
||||||
|
|
||||||
|
// 文件上传-子路径类型常量
|
||||||
|
const (
|
||||||
|
// UPLOAD_DEFAULT 默认
|
||||||
|
UPLOAD_DEFAULT = "default"
|
||||||
|
// UPLOAD_AVATAR 头像
|
||||||
|
UPLOAD_AVATAR = "avatar"
|
||||||
|
// UPLOAD_IMPORT 导入
|
||||||
|
UPLOAD_IMPORT = "import"
|
||||||
|
// UPLOAD_EXPORT 导出
|
||||||
|
UPLOAD_EXPORT = "export"
|
||||||
|
// UPLOAD_COMMON 通用上传
|
||||||
|
UPLOAD_COMMON = "common"
|
||||||
|
// UPLOAD_DOWNLOAD 下载
|
||||||
|
UPLOAD_DOWNLOAD = "download"
|
||||||
|
// UPLOAD_CHUNK 切片
|
||||||
|
UPLOAD_CHUNK = "chunk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UPLOAD_SUB_PATH 子路径类型映射
|
||||||
|
var UPLOAD_SUB_PATH = map[string]string{
|
||||||
|
UPLOAD_DEFAULT: "默认",
|
||||||
|
UPLOAD_AVATAR: "头像",
|
||||||
|
UPLOAD_IMPORT: "导入",
|
||||||
|
UPLOAD_EXPORT: "导出",
|
||||||
|
UPLOAD_COMMON: "通用上传",
|
||||||
|
UPLOAD_DOWNLOAD: "下载",
|
||||||
|
UPLOAD_CHUNK: "切片",
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package uploadsubpath
|
|
||||||
|
|
||||||
// 文件上传-子路径类型常量
|
|
||||||
|
|
||||||
const (
|
|
||||||
// 默认
|
|
||||||
DEFAULT = "default"
|
|
||||||
|
|
||||||
// 头像
|
|
||||||
AVATART = "avatar"
|
|
||||||
|
|
||||||
// 导入
|
|
||||||
IMPORT = "import"
|
|
||||||
|
|
||||||
// 导出
|
|
||||||
EXPORT = "export"
|
|
||||||
|
|
||||||
// 通用上传
|
|
||||||
COMMON = "common"
|
|
||||||
|
|
||||||
// 下载
|
|
||||||
DOWNLOAD = "download"
|
|
||||||
|
|
||||||
// 切片
|
|
||||||
CHUNK = "chunk"
|
|
||||||
|
|
||||||
// 软件包
|
|
||||||
SOFTWARE = "software"
|
|
||||||
|
|
||||||
// 授权文件
|
|
||||||
LICENSE = "license"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 子路径类型映射
|
|
||||||
var UploadSubpath = map[string]string{
|
|
||||||
DEFAULT: "默认",
|
|
||||||
AVATART: "头像",
|
|
||||||
IMPORT: "导入",
|
|
||||||
EXPORT: "导出",
|
|
||||||
COMMON: "通用上传",
|
|
||||||
DOWNLOAD: "下载",
|
|
||||||
CHUNK: "切片",
|
|
||||||
SOFTWARE: "软件包",
|
|
||||||
LICENSE: "授权文件",
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"be.ems/src/framework/constants/common"
|
"be.ems/src/framework/constants"
|
||||||
"be.ems/src/modules/monitor/model"
|
"be.ems/src/modules/monitor/model"
|
||||||
"be.ems/src/modules/monitor/repository"
|
"be.ems/src/modules/monitor/repository"
|
||||||
)
|
)
|
||||||
@@ -39,7 +39,7 @@ func (s cronlog) Error(err error, msg string, keysAndValues ...any) {
|
|||||||
Data: data,
|
Data: data,
|
||||||
Result: err.Error(),
|
Result: err.Error(),
|
||||||
}
|
}
|
||||||
jobLog.SaveLog(common.STATUS_NO)
|
jobLog.SaveLog(constants.STATUS_NO)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,7 +62,7 @@ func (s cronlog) Completed(result any, msg string, keysAndValues ...any) {
|
|||||||
Data: data,
|
Data: data,
|
||||||
Result: result,
|
Result: result,
|
||||||
}
|
}
|
||||||
jobLog.SaveLog(common.STATUS_YES)
|
jobLog.SaveLog(constants.STATUS_YES)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -80,13 +80,13 @@ func (jl *jobLogData) SaveLog(status string) {
|
|||||||
sysJob := jl.Data.SysJob
|
sysJob := jl.Data.SysJob
|
||||||
|
|
||||||
// 任务日志不需要记录
|
// 任务日志不需要记录
|
||||||
if sysJob.SaveLog == "" || sysJob.SaveLog == common.STATUS_NO {
|
if sysJob.SaveLog == "" || sysJob.SaveLog == constants.STATUS_NO {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 结果信息key的Name
|
// 结果信息key的Name
|
||||||
resultNmae := "failed"
|
resultNmae := "failed"
|
||||||
if status == common.STATUS_YES {
|
if status == constants.STATUS_YES {
|
||||||
resultNmae = "completed"
|
resultNmae = "completed"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +108,12 @@ func (jl *jobLogData) SaveLog(status string) {
|
|||||||
JobGroup: sysJob.JobGroup,
|
JobGroup: sysJob.JobGroup,
|
||||||
InvokeTarget: sysJob.InvokeTarget,
|
InvokeTarget: sysJob.InvokeTarget,
|
||||||
TargetParams: sysJob.TargetParams,
|
TargetParams: sysJob.TargetParams,
|
||||||
Status: status,
|
StatusFlag: status,
|
||||||
JobMsg: jobMsg,
|
JobMsg: jobMsg,
|
||||||
CostTime: duration.Milliseconds(),
|
CostTime: duration.Milliseconds(),
|
||||||
}
|
}
|
||||||
// 插入数据
|
// 插入数据
|
||||||
repository.NewSysJobLogImpl.InsertJobLog(sysJobLog)
|
repository.NewSysJobLog.Insert(sysJobLog)
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobData 调度任务日志收集结构体,执行任务时传入的接收参数
|
// JobData 调度任务日志收集结构体,执行任务时传入的接收参数
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package datasource
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -7,33 +7,40 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"be.ems/src/framework/config"
|
"github.com/glebarez/sqlite"
|
||||||
"be.ems/src/framework/logger"
|
|
||||||
"be.ems/src/framework/utils/parse"
|
|
||||||
|
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
gormLog "gorm.io/gorm/logger"
|
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)
|
var dbMap = make(map[string]*gorm.DB)
|
||||||
|
|
||||||
type dialectInfo struct {
|
type dialectInfo struct {
|
||||||
dialector gorm.Dialector
|
dialectic gorm.Dialector
|
||||||
logging bool
|
logging bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// 载入数据库连接
|
// 载入数据库连接
|
||||||
func loadDialect() map[string]dialectInfo {
|
func loadDialect() map[string]dialectInfo {
|
||||||
dialects := make(map[string]dialectInfo, 0)
|
dialects := make(map[string]dialectInfo)
|
||||||
|
|
||||||
// 读取数据源配置
|
// 读取数据源配置
|
||||||
datasource := config.Get("gorm.datasource").(map[string]any)
|
datasource := config.Get("database.datasource").(map[string]any)
|
||||||
for key, value := range datasource {
|
for key, value := range datasource {
|
||||||
item := value.(map[string]any)
|
item := value.(map[string]any)
|
||||||
// 数据库类型对应的数据库连接
|
// 数据库类型对应的数据库连接
|
||||||
switch item["type"] {
|
switch item["type"] {
|
||||||
|
case "sqlite":
|
||||||
|
dsn := fmt.Sprint(item["database"])
|
||||||
|
dialects[key] = dialectInfo{
|
||||||
|
dialectic: sqlite.Open(dsn),
|
||||||
|
logging: item["logging"].(bool),
|
||||||
|
}
|
||||||
case "mysql":
|
case "mysql":
|
||||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||||
item["username"],
|
item["username"],
|
||||||
@@ -43,11 +50,11 @@ func loadDialect() map[string]dialectInfo {
|
|||||||
item["database"],
|
item["database"],
|
||||||
)
|
)
|
||||||
dialects[key] = dialectInfo{
|
dialects[key] = dialectInfo{
|
||||||
dialector: mysql.Open(dsn),
|
dialectic: mysql.Open(dsn),
|
||||||
logging: item["logging"].(bool),
|
logging: item["logging"].(bool),
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
logger.Fatalf("%s: %v\n Not Load DB Config Type", key, item)
|
logger.Warnf("%s: %v\n Not Load DB Config Type", key, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +75,7 @@ func loadLogger() gormLog.Interface {
|
|||||||
return newLogger
|
return newLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
// 连接数据库实例
|
// Connect 连接数据库实例
|
||||||
func Connect() {
|
func Connect() {
|
||||||
// 遍历进行连接数据库实例
|
// 遍历进行连接数据库实例
|
||||||
for key, info := range loadDialect() {
|
for key, info := range loadDialect() {
|
||||||
@@ -78,7 +85,7 @@ func Connect() {
|
|||||||
opts.Logger = loadLogger()
|
opts.Logger = loadLogger()
|
||||||
}
|
}
|
||||||
// 创建连接
|
// 创建连接
|
||||||
db, err := gorm.Open(info.dialector, opts)
|
db, err := gorm.Open(info.dialectic, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("failed error db connect: %s", err)
|
logger.Fatalf("failed error db connect: %s", err)
|
||||||
}
|
}
|
||||||
@@ -92,12 +99,18 @@ func Connect() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("failed error ping database: %v", err)
|
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)
|
logger.Infof("database %s connection is successful.", key)
|
||||||
dbMap[key] = db
|
dbMap[key] = db
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭数据库实例
|
// Close 关闭数据库实例
|
||||||
func Close() {
|
func Close() {
|
||||||
for _, db := range dbMap {
|
for _, db := range dbMap {
|
||||||
sqlDB, err := db.DB()
|
sqlDB, err := db.DB()
|
||||||
@@ -110,36 +123,41 @@ func Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取默认数据源
|
// DB 获取数据源
|
||||||
func DefaultDB() *gorm.DB {
|
//
|
||||||
source := config.Get("gorm.defaultDataSourceName").(string)
|
// source-数据源
|
||||||
return dbMap[source]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取数据源
|
|
||||||
func DB(source string) *gorm.DB {
|
func DB(source string) *gorm.DB {
|
||||||
|
// 不指定时获取默认实例
|
||||||
if source == "" {
|
if source == "" {
|
||||||
source = config.Get("gorm.defaultDataSourceName").(string)
|
source = config.Get("gorm.defaultDataSourceName").(string)
|
||||||
}
|
}
|
||||||
return dbMap[source]
|
return dbMap[source]
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawDB 原生查询语句
|
// Names 获取数据源名称列表
|
||||||
func RawDB(source string, sql string, parameters []any) ([]map[string]any, error) {
|
func Names() []string {
|
||||||
// 数据源
|
var names []string
|
||||||
db := DefaultDB()
|
for key := range dbMap {
|
||||||
if source != "" {
|
names = append(names, key)
|
||||||
db = DB(source)
|
|
||||||
}
|
}
|
||||||
|
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, " ")
|
fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ")
|
||||||
|
|
||||||
// logger.Infof("sql=> %v", fmtSql)
|
|
||||||
// logger.Infof("parameters=> %v", parameters)
|
|
||||||
|
|
||||||
// 查询结果
|
// 查询结果
|
||||||
var rows []map[string]any
|
|
||||||
res := db.Raw(fmtSql, parameters...).Scan(&rows)
|
res := db.Raw(fmtSql, parameters...).Scan(&rows)
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
return nil, res.Error
|
return nil, res.Error
|
||||||
@@ -147,12 +165,16 @@ func RawDB(source string, sql string, parameters []any) ([]map[string]any, error
|
|||||||
return rows, nil
|
return rows, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecDB 原生执行语句
|
// ExecDB 原生语句执行
|
||||||
|
//
|
||||||
|
// source-数据源
|
||||||
|
// sql-预编译的SQL语句
|
||||||
|
// parameters-预编译的SQL语句参数
|
||||||
func ExecDB(source string, sql string, parameters []any) (int64, error) {
|
func ExecDB(source string, sql string, parameters []any) (int64, error) {
|
||||||
// 数据源
|
// 数据源
|
||||||
db := DefaultDB()
|
db := DB(source)
|
||||||
if source != "" {
|
if db == nil {
|
||||||
db = DB(source)
|
return 0, fmt.Errorf("not database source")
|
||||||
}
|
}
|
||||||
// 使用正则表达式替换连续的空白字符为单个空格
|
// 使用正则表达式替换连续的空白字符为单个空格
|
||||||
fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ")
|
fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ")
|
||||||
@@ -165,6 +187,9 @@ func ExecDB(source string, sql string, parameters []any) (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PageNumSize 分页页码记录数
|
// PageNumSize 分页页码记录数
|
||||||
|
//
|
||||||
|
// pageNum-页码
|
||||||
|
// pageSize-记录数
|
||||||
func PageNumSize(pageNum, pageSize any) (int, int) {
|
func PageNumSize(pageNum, pageSize any) (int, int) {
|
||||||
// 记录起始索引
|
// 记录起始索引
|
||||||
num := parse.Number(pageNum)
|
num := parse.Number(pageNum)
|
||||||
139
src/framework/database/redis/expand.go
Normal file
139
src/framework/database/redis/expand.go
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"be.ems/src/framework/logger"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 连接Redis实例
|
||||||
|
func ConnectPush(source string, rdb *redis.Client) {
|
||||||
|
if rdb == nil {
|
||||||
|
delete(rdbMap, source)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rdbMap[source] = rdb
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量获得缓存数据 [key]result
|
||||||
|
func GetHashBatch(source string, keys []string) (map[string]map[string]string, error) {
|
||||||
|
result := make(map[string]map[string]string, 0)
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return result, fmt.Errorf("not keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return result, fmt.Errorf("redis not client")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建一个有限的并发控制信号通道
|
||||||
|
sem := make(chan struct{}, 10)
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
var mt sync.Mutex
|
||||||
|
batchSize := 1000
|
||||||
|
total := len(keys)
|
||||||
|
if total < batchSize {
|
||||||
|
batchSize = total
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < total; i += batchSize {
|
||||||
|
wg.Add(1)
|
||||||
|
go func(start int) {
|
||||||
|
ctx := context.Background()
|
||||||
|
// 并发控制,限制同时执行的 Goroutine 数量
|
||||||
|
sem <- struct{}{}
|
||||||
|
defer func() {
|
||||||
|
<-sem
|
||||||
|
ctx.Done()
|
||||||
|
wg.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 检查索引是否越界
|
||||||
|
end := start + batchSize
|
||||||
|
if end > total {
|
||||||
|
end = total
|
||||||
|
}
|
||||||
|
pipe := rdb.Pipeline()
|
||||||
|
for _, key := range keys[start:end] {
|
||||||
|
pipe.HGetAll(ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmds, err := pipe.Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Failed to get hash batch exec err: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将结果添加到 result map 并发访问
|
||||||
|
mt.Lock()
|
||||||
|
defer mt.Unlock()
|
||||||
|
|
||||||
|
// 处理命令结果
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
if cmd.Err() != nil {
|
||||||
|
logger.Errorf("Failed to get hash batch cmds err: %v", cmd.Err())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// 将结果转换为 *redis.StringStringMapCmd 类型
|
||||||
|
rcmd, ok := cmd.(*redis.MapStringStringCmd)
|
||||||
|
if !ok {
|
||||||
|
logger.Errorf("Failed to get hash batch type err: %v", cmd.Err())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
key := "-"
|
||||||
|
args := rcmd.Args()
|
||||||
|
if len(args) > 0 {
|
||||||
|
key = fmt.Sprint(args[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
result[key] = rcmd.Val()
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHash 获得缓存数据
|
||||||
|
func GetHash(source, key, field string) (string, error) {
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return "", fmt.Errorf("redis not client")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
v, err := rdb.HGet(ctx, key, field).Result()
|
||||||
|
if errors.Is(err, redis.Nil) {
|
||||||
|
return "", fmt.Errorf("no key field")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHash 设置缓存数据
|
||||||
|
func SetHash(source, key string, value map[string]any) error {
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return fmt.Errorf("redis not client")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
err := rdb.HSet(ctx, key, value).Err()
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("redis HSet err %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
346
src/framework/database/redis/redis.go
Normal file
346
src/framework/database/redis/redis.go
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
package redis
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
|
||||||
|
"be.ems/src/framework/config"
|
||||||
|
"be.ems/src/framework/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redis连接实例
|
||||||
|
var rdbMap = make(map[string]*redis.Client)
|
||||||
|
|
||||||
|
// Connect 连接Redis实例
|
||||||
|
func Connect() {
|
||||||
|
ctx := context.Background()
|
||||||
|
// 读取数据源配置
|
||||||
|
datasource := config.Get("redis.dataSource").(map[string]any)
|
||||||
|
for k, v := range datasource {
|
||||||
|
client := v.(map[string]any)
|
||||||
|
// 创建连接
|
||||||
|
address := fmt.Sprintf("%s:%d", client["host"], client["port"])
|
||||||
|
rdb := redis.NewClient(&redis.Options{
|
||||||
|
Addr: address,
|
||||||
|
Password: client["password"].(string),
|
||||||
|
DB: client["db"].(int),
|
||||||
|
})
|
||||||
|
// 测试数据库连接
|
||||||
|
pong, err := rdb.Ping(ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
logger.Fatalf("Ping redis %s is %v", k, err)
|
||||||
|
}
|
||||||
|
logger.Infof("redis %s %d %s connection is successful.", k, client["db"].(int), pong)
|
||||||
|
rdbMap[k] = rdb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭Redis实例
|
||||||
|
func Close() {
|
||||||
|
for _, rdb := range rdbMap {
|
||||||
|
if err := rdb.Close(); err != nil {
|
||||||
|
logger.Errorf("redis db close: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RDB 获取实例
|
||||||
|
func RDB(source string) *redis.Client {
|
||||||
|
// 不指定时获取默认实例
|
||||||
|
if source == "" {
|
||||||
|
source = config.Get("redis.defaultDataSourceName").(string)
|
||||||
|
}
|
||||||
|
return rdbMap[source]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info 获取redis服务信息
|
||||||
|
func Info(source string) map[string]map[string]string {
|
||||||
|
infoObj := make(map[string]map[string]string)
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return infoObj
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
info, err := rdb.Info(ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
return infoObj
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(info, "\r\n")
|
||||||
|
label := ""
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.Contains(line, "#") {
|
||||||
|
label = strings.Fields(line)[len(strings.Fields(line))-1]
|
||||||
|
label = strings.ToLower(label)
|
||||||
|
infoObj[label] = make(map[string]string)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kvArr := strings.Split(line, ":")
|
||||||
|
if len(kvArr) >= 2 {
|
||||||
|
key := strings.TrimSpace(kvArr[0])
|
||||||
|
value := strings.TrimSpace(kvArr[len(kvArr)-1])
|
||||||
|
infoObj[label][key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return infoObj
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeySize 获取redis当前连接可用键Key总数信息
|
||||||
|
func KeySize(source string) int64 {
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
size, err := rdb.DBSize(ctx).Result()
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommandStats 获取redis命令状态信息
|
||||||
|
func CommandStats(source string) []map[string]string {
|
||||||
|
statsObjArr := make([]map[string]string, 0)
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return statsObjArr
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
commandstats, err := rdb.Info(ctx, "commandstats").Result()
|
||||||
|
if err != nil {
|
||||||
|
return statsObjArr
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(commandstats, "\r\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if !strings.HasPrefix(line, "cmdstat_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
kvArr := strings.Split(line, ":")
|
||||||
|
key := kvArr[0]
|
||||||
|
valueStr := kvArr[len(kvArr)-1]
|
||||||
|
statsObj := make(map[string]string)
|
||||||
|
statsObj["name"] = key[8:]
|
||||||
|
statsObj["value"] = valueStr[6:strings.Index(valueStr, ",usec=")]
|
||||||
|
statsObjArr = append(statsObjArr, statsObj)
|
||||||
|
}
|
||||||
|
return statsObjArr
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExpire 获取键的剩余有效时间(秒)
|
||||||
|
func GetExpire(source string, key string) (int64, error) {
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return 0, fmt.Errorf("redis not client")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
ttl, err := rdb.TTL(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int64(ttl.Seconds()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetKeys 获得缓存数据的key列表
|
||||||
|
func GetKeys(source string, pattern string) ([]string, error) {
|
||||||
|
keys := make([]string, 0)
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return keys, fmt.Errorf("redis not client")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 游标
|
||||||
|
var cursor uint64 = 0
|
||||||
|
var count int64 = 100
|
||||||
|
ctx := context.Background()
|
||||||
|
// 循环遍历获取匹配的键
|
||||||
|
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 result, fmt.Errorf("not keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return result, fmt.Errorf("redis not client")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取缓存数据
|
||||||
|
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 v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get 获得缓存数据
|
||||||
|
func Get(source, key string) (string, error) {
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return "", fmt.Errorf("redis not client")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
v, err := rdb.Get(ctx, key).Result()
|
||||||
|
if errors.Is(err, redis.Nil) {
|
||||||
|
return "", fmt.Errorf("no keys")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Has 判断是否存在
|
||||||
|
func Has(source string, keys ...string) (int64, error) {
|
||||||
|
// 数据源
|
||||||
|
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 0, err
|
||||||
|
}
|
||||||
|
return exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set 设置缓存数据
|
||||||
|
func Set(source, key string, value any) error {
|
||||||
|
// 数据源
|
||||||
|
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 err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetByExpire 设置缓存数据与过期时间
|
||||||
|
func SetByExpire(source, key string, value any, expiration time.Duration) error {
|
||||||
|
// 数据源
|
||||||
|
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 err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Del 删除单个
|
||||||
|
func Del(source string, key string) error {
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return fmt.Errorf("redis not client")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := rdb.Del(ctx, key).Err(); err != nil {
|
||||||
|
logger.Errorf("redis Del err %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DelKeys 删除多个
|
||||||
|
func DelKeys(source string, keys []string) error {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return fmt.Errorf("no keys")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据源
|
||||||
|
rdb := RDB(source)
|
||||||
|
if rdb == nil {
|
||||||
|
return fmt.Errorf("redis not client")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := rdb.Del(ctx, keys...).Err(); err != nil {
|
||||||
|
logger.Errorf("redis DelKeys err %v", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RateLimit 限流查询并记录
|
||||||
|
func RateLimit(source, limitKey string, time, count int64) (int64, error) {
|
||||||
|
// 数据源
|
||||||
|
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 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);`)
|
||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
"be.ems/src/framework/config"
|
"be.ems/src/framework/config"
|
||||||
"be.ems/src/framework/logger"
|
"be.ems/src/framework/logger"
|
||||||
"be.ems/src/framework/vo/result"
|
"be.ems/src/framework/resp"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -20,14 +20,14 @@ func ErrorCatch() gin.HandlerFunc {
|
|||||||
|
|
||||||
// 返回错误响应给客户端
|
// 返回错误响应给客户端
|
||||||
if config.Env() == "prod" {
|
if config.Env() == "prod" {
|
||||||
c.JSON(500, result.CodeMsg(500, "Internal Server Errors"))
|
c.JSON(500, resp.CodeMsg(500, "Internal Server Errors"))
|
||||||
} else {
|
} else {
|
||||||
// 通过实现 error 接口的 Error() 方法自定义错误类型进行捕获
|
// 通过实现 error 接口的 Error() 方法自定义错误类型进行捕获
|
||||||
switch v := err.(type) {
|
switch v := err.(type) {
|
||||||
case error:
|
case error:
|
||||||
c.JSON(500, result.CodeMsg(500, v.Error()))
|
c.JSON(500, resp.CodeMsg(500, v.Error()))
|
||||||
default:
|
default:
|
||||||
c.JSON(500, result.CodeMsg(500, fmt.Sprint(err)))
|
c.JSON(500, resp.CodeMsg(500, fmt.Sprint(err)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,110 +5,54 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
systemService "be.ems/src/modules/system/service"
|
"be.ems/src/framework/constants"
|
||||||
|
"be.ems/src/framework/database/redis"
|
||||||
)
|
)
|
||||||
|
|
||||||
// localeItem 国际化数据项
|
|
||||||
type localeItem struct {
|
|
||||||
Key string `json:"key"`
|
|
||||||
Value string `json:"value"`
|
|
||||||
Code string `json:"code"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// localeMap 国际化数据组
|
|
||||||
var localeMap = make(map[string][]localeItem)
|
|
||||||
|
|
||||||
// ClearLocaleData 清空国际化数据
|
|
||||||
func ClearLocaleData() {
|
|
||||||
localeMap = make(map[string][]localeItem)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadLocaleData 加载国际化数据
|
|
||||||
func LoadLocaleData(language string) []localeItem {
|
|
||||||
dictType := fmt.Sprintf("i18n_%s", language)
|
|
||||||
dictTypeList := systemService.NewSysDictType.DictDataCache(dictType)
|
|
||||||
localeData := []localeItem{}
|
|
||||||
for _, v := range dictTypeList {
|
|
||||||
localeData = append(localeData, localeItem{
|
|
||||||
Key: v.DictLabel,
|
|
||||||
Value: v.DictValue,
|
|
||||||
Code: v.DictCode,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
localeMap[language] = localeData
|
|
||||||
return localeData
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateKeyValue 更新键对应的值
|
|
||||||
func UpdateKeyValue(language, key, value string) bool {
|
|
||||||
arr, ok := localeMap[language]
|
|
||||||
if !ok || len(arr) == 0 {
|
|
||||||
arr = LoadLocaleData(language)
|
|
||||||
}
|
|
||||||
|
|
||||||
code := ""
|
|
||||||
if key == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, v := range arr {
|
|
||||||
if v.Key == key {
|
|
||||||
code = v.Code
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新字典数据
|
|
||||||
sysDictDataService := systemService.NewSysDictData
|
|
||||||
item := sysDictDataService.SelectDictDataByCode(code)
|
|
||||||
if item.DictCode == code && item.DictLabel == key {
|
|
||||||
item.DictValue = value
|
|
||||||
row := sysDictDataService.UpdateDictData(item)
|
|
||||||
if row > 0 {
|
|
||||||
delete(localeMap, language)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// TFindKeyPrefix 翻译值查找键 值前缀匹配
|
// TFindKeyPrefix 翻译值查找键 值前缀匹配
|
||||||
func TFindKeyPrefix(language, keyPrefix, value string) string {
|
func TFindKeyPrefix(language, keyPrefix, value string) string {
|
||||||
key := value
|
key := value
|
||||||
if value == "" {
|
if key == "" {
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
arr, ok := localeMap[language]
|
|
||||||
if !ok || len(arr) == 0 {
|
langKey := constants.CACHE_I18N + ":" + keyPrefix + "*"
|
||||||
arr = LoadLocaleData(language)
|
prefixKeys, err := redis.GetKeys("", langKey)
|
||||||
|
if err != nil {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
mkv, err := redis.GetHashBatch("", prefixKeys)
|
||||||
|
if err != nil {
|
||||||
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range arr {
|
for k, m := range mkv {
|
||||||
if strings.HasPrefix(v.Key, keyPrefix) && strings.HasPrefix(v.Value, value) {
|
// 跳过-号数据 i18n:menu.system.menu
|
||||||
key = v.Key
|
|
||||||
break
|
if v, ok := m[language]; ok {
|
||||||
|
if strings.HasPrefix(v, value) {
|
||||||
|
key = k[len(constants.CACHE_I18N)+1:]
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
// TKey 翻译键
|
// TKey 翻译键
|
||||||
|
// language: zh-中文 en-英文
|
||||||
func TKey(language, key string) string {
|
func TKey(language, key string) string {
|
||||||
value := key
|
value := key
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
arr, ok := localeMap[language]
|
|
||||||
if !ok || len(arr) == 0 {
|
|
||||||
arr = LoadLocaleData(language)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range arr {
|
langKey := constants.CACHE_I18N + ":" + key
|
||||||
if v.Key == key {
|
output, err := redis.GetHash("", langKey, language)
|
||||||
value = v.Value
|
if err != nil {
|
||||||
break
|
return value
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return value
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
// TTemplate 翻译模板字符串
|
// TTemplate 翻译模板字符串
|
||||||
|
|||||||
@@ -1,39 +1,36 @@
|
|||||||
package ip2region
|
package ip2region
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"be.ems/src/framework/logger"
|
||||||
|
|
||||||
"embed"
|
"embed"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"be.ems/src/framework/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 网络地址(内网)
|
// LocalHost 网络地址(内网)
|
||||||
const LOCAT_HOST = "127.0.0.1"
|
const LocalHost = "127.0.0.1"
|
||||||
|
|
||||||
// 全局查询对象
|
// 全局查询对象
|
||||||
var searcher *Searcher
|
var searcher *Searcher
|
||||||
|
|
||||||
//go:embed ip2region.xdb
|
// InitSearcher 初始化查询对象
|
||||||
var ip2regionDB embed.FS
|
func InitSearcher(assetsDir *embed.FS) {
|
||||||
|
if searcher != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
// 从 embed.FS 中读取内嵌文件
|
||||||
// 从 dbPath 加载整个 xdb 到内存
|
fileBuff, err := assetsDir.ReadFile("src/assets/ip2region.xdb")
|
||||||
buf, err := ip2regionDB.ReadFile("ip2region.xdb")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Fatalf("failed error load xdb from : %s\n", err)
|
logger.Fatalf("failed error load xdb from : %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// 用全局的 fileBuff 创建完全基于内存的查询对象。
|
||||||
// 用全局的 cBuff 创建完全基于内存的查询对象。
|
if searcher, err = NewWithBuffer(fileBuff); err != nil {
|
||||||
base, err := NewWithBuffer(buf)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("failed error create searcher with content: %s\n", err)
|
logger.Errorf("failed error create searcher with content: %s\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 赋值到全局查询对象
|
|
||||||
searcher = base
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegionSearchByIp 查询IP所在地
|
// RegionSearchByIp 查询IP所在地
|
||||||
@@ -41,14 +38,13 @@ func init() {
|
|||||||
// 国家|区域|省份|城市|ISP
|
// 国家|区域|省份|城市|ISP
|
||||||
func RegionSearchByIp(ip string) (string, int, int64) {
|
func RegionSearchByIp(ip string) (string, int, int64) {
|
||||||
ip = ClientIP(ip)
|
ip = ClientIP(ip)
|
||||||
if ip == LOCAT_HOST {
|
if ip == LocalHost {
|
||||||
// "0|0|0|内网IP|内网IP"
|
|
||||||
return "0|0|0|app.common.noIPregion|app.common.noIPregion", 0, 0
|
return "0|0|0|app.common.noIPregion|app.common.noIPregion", 0, 0
|
||||||
}
|
}
|
||||||
tStart := time.Now()
|
tStart := time.Now()
|
||||||
region, err := searcher.SearchByStr(ip)
|
region, err := searcher.SearchByStr(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("failed to SearchIP(%s): %s\n", ip, err)
|
logger.Errorf("failed to RegionSearchByIp(%s): %s\n", ip, err)
|
||||||
return "0|0|0|0|0", 0, 0
|
return "0|0|0|0|0", 0, 0
|
||||||
}
|
}
|
||||||
return region, 0, time.Since(tStart).Milliseconds()
|
return region, 0, time.Since(tStart).Milliseconds()
|
||||||
@@ -59,21 +55,21 @@ func RegionSearchByIp(ip string) (string, int, int64) {
|
|||||||
// 218.4.167.70 江苏省 苏州市
|
// 218.4.167.70 江苏省 苏州市
|
||||||
func RealAddressByIp(ip string) string {
|
func RealAddressByIp(ip string) string {
|
||||||
ip = ClientIP(ip)
|
ip = ClientIP(ip)
|
||||||
if ip == LOCAT_HOST {
|
if ip == LocalHost {
|
||||||
return "app.common.noIPregion" // 内网IP
|
return "app.common.noIPregion"
|
||||||
}
|
}
|
||||||
region, err := searcher.SearchByStr(ip)
|
region, err := searcher.SearchByStr(ip)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("failed to SearchIP(%s): %s\n", ip, err)
|
logger.Errorf("failed to RealAddressByIp(%s): %s\n", ip, err)
|
||||||
return "app.common.unknown" // 未知
|
return "app.common.unknown"
|
||||||
}
|
}
|
||||||
parts := strings.Split(region, "|")
|
parts := strings.Split(region, "|")
|
||||||
province := parts[2]
|
province := parts[2]
|
||||||
city := parts[3]
|
city := parts[3]
|
||||||
|
if province == "0" && city == "0" {
|
||||||
|
return "app.common.unknown"
|
||||||
|
}
|
||||||
if province == "0" && city != "0" {
|
if province == "0" && city != "0" {
|
||||||
if city == "内网IP" {
|
|
||||||
return "app.common.noIPregion" // 内网IP
|
|
||||||
}
|
|
||||||
return city
|
return city
|
||||||
}
|
}
|
||||||
return province + " " + city
|
return province + " " + city
|
||||||
@@ -86,8 +82,8 @@ func ClientIP(ip string) string {
|
|||||||
if strings.HasPrefix(ip, "::ffff:") {
|
if strings.HasPrefix(ip, "::ffff:") {
|
||||||
ip = strings.Replace(ip, "::ffff:", "", 1)
|
ip = strings.Replace(ip, "::ffff:", "", 1)
|
||||||
}
|
}
|
||||||
if ip == LOCAT_HOST || ip == "::1" {
|
if ip == LocalHost || ip == "::1" {
|
||||||
return LOCAT_HOST
|
return LocalHost
|
||||||
}
|
}
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
@@ -172,4 +172,3 @@ func LoadContentFromFile(dbFile string) ([]byte, error) {
|
|||||||
|
|
||||||
return cBuff, nil
|
return cBuff, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7,12 +7,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"be.ems/src/framework/constants/common"
|
"be.ems/src/framework/constants"
|
||||||
tokenConstants "be.ems/src/framework/constants/token"
|
|
||||||
"be.ems/src/framework/i18n"
|
"be.ems/src/framework/i18n"
|
||||||
"be.ems/src/framework/utils/ctx"
|
"be.ems/src/framework/reqctx"
|
||||||
|
"be.ems/src/framework/resp"
|
||||||
"be.ems/src/framework/utils/parse"
|
"be.ems/src/framework/utils/parse"
|
||||||
"be.ems/src/framework/vo/result"
|
|
||||||
"be.ems/src/modules/system/model"
|
"be.ems/src/modules/system/model"
|
||||||
"be.ems/src/modules/system/service"
|
"be.ems/src/modules/system/service"
|
||||||
|
|
||||||
@@ -91,7 +90,7 @@ func OptionNew(title, businessType string) Options {
|
|||||||
func OperateLog(options Options) gin.HandlerFunc {
|
func OperateLog(options Options) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
c.Set("startTime", time.Now())
|
c.Set("startTime", time.Now())
|
||||||
language := ctx.AcceptLanguage(c)
|
language := reqctx.AcceptLanguage(c)
|
||||||
|
|
||||||
// 函数名
|
// 函数名
|
||||||
funcName := c.HandlerName()
|
funcName := c.HandlerName()
|
||||||
@@ -99,37 +98,31 @@ func OperateLog(options Options) gin.HandlerFunc {
|
|||||||
funcName = funcName[lastDotIndex+1:]
|
funcName = funcName[lastDotIndex+1:]
|
||||||
|
|
||||||
// 解析ip地址
|
// 解析ip地址
|
||||||
ipaddr, location := ctx.IPAddrLocation(c)
|
ipaddr, location := reqctx.IPAddrLocation(c)
|
||||||
|
|
||||||
// 获取登录用户信息
|
// 获取登录用户信息
|
||||||
loginUser, err := ctx.LoginUser(c)
|
loginUser, err := reqctx.LoginUser(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(401, result.CodeMsg(401, i18n.TKey(language, err.Error())))
|
c.JSON(401, resp.CodeMsg(401, i18n.TKey(language, err.Error())))
|
||||||
c.Abort() // 停止执行后续的处理函数
|
c.Abort() // 停止执行后续的处理函数
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 操作日志记录
|
// 操作日志记录
|
||||||
operLog := model.SysLogOperate{
|
operLog := model.SysLogOperate{
|
||||||
Title: options.Title,
|
Title: options.Title,
|
||||||
BusinessType: options.BusinessType,
|
BusinessType: options.BusinessType,
|
||||||
OperatorType: options.OperatorType,
|
OperaMethod: funcName,
|
||||||
Method: funcName,
|
OperaUrl: c.Request.URL.Path,
|
||||||
OperURL: c.Request.URL.Path,
|
OperaUrlMethod: c.Request.Method,
|
||||||
RequestMethod: c.Request.Method,
|
OperaIp: ipaddr,
|
||||||
OperIP: ipaddr,
|
OperaLocation: location,
|
||||||
OperLocation: location,
|
OperaBy: loginUser.User.UserName,
|
||||||
OperName: loginUser.User.UserName,
|
|
||||||
DeptName: loginUser.User.Dept.DeptName,
|
|
||||||
}
|
|
||||||
|
|
||||||
if loginUser.User.UserType == "sys" {
|
|
||||||
operLog.OperatorType = OPERATOR_TYPE_MANAGE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否需要保存request,参数和值
|
// 是否需要保存request,参数和值
|
||||||
if options.IsSaveRequestData {
|
if options.IsSaveRequestData {
|
||||||
params := ctx.RequestParamsMap(c)
|
params := reqctx.RequestParamsMap(c)
|
||||||
// 敏感属性字段进行掩码
|
// 敏感属性字段进行掩码
|
||||||
processSensitiveFields(params)
|
processSensitiveFields(params)
|
||||||
jsonStr, _ := json.Marshal(params)
|
jsonStr, _ := json.Marshal(params)
|
||||||
@@ -137,7 +130,7 @@ func OperateLog(options Options) gin.HandlerFunc {
|
|||||||
if len(paramsStr) > 2000 {
|
if len(paramsStr) > 2000 {
|
||||||
paramsStr = paramsStr[:2000]
|
paramsStr = paramsStr[:2000]
|
||||||
}
|
}
|
||||||
operLog.OperParam = paramsStr
|
operLog.OperaParam = paramsStr
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用下一个处理程序
|
// 调用下一个处理程序
|
||||||
@@ -146,9 +139,9 @@ func OperateLog(options Options) gin.HandlerFunc {
|
|||||||
// 响应状态
|
// 响应状态
|
||||||
status := c.Writer.Status()
|
status := c.Writer.Status()
|
||||||
if status == 200 {
|
if status == 200 {
|
||||||
operLog.Status = common.STATUS_YES
|
operLog.StatusFlag = constants.STATUS_YES
|
||||||
} else {
|
} else {
|
||||||
operLog.Status = common.STATUS_NO
|
operLog.StatusFlag = constants.STATUS_NO
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否需要保存response,参数和值
|
// 是否需要保存response,参数和值
|
||||||
@@ -157,16 +150,16 @@ func OperateLog(options Options) gin.HandlerFunc {
|
|||||||
contentType := c.Writer.Header().Get("Content-Type")
|
contentType := c.Writer.Header().Get("Content-Type")
|
||||||
content := contentType + contentDisposition
|
content := contentType + contentDisposition
|
||||||
msg := fmt.Sprintf(`{"status":"%d","size":"%d","content-type":"%s"}`, status, c.Writer.Size(), content)
|
msg := fmt.Sprintf(`{"status":"%d","size":"%d","content-type":"%s"}`, status, c.Writer.Size(), content)
|
||||||
operLog.OperMsg = msg
|
operLog.OperaMsg = msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// 日志记录时间
|
// 日志记录时间
|
||||||
duration := time.Since(c.GetTime("startTime"))
|
duration := time.Since(c.GetTime("startTime"))
|
||||||
operLog.CostTime = duration.Milliseconds()
|
operLog.CostTime = duration.Milliseconds()
|
||||||
operLog.OperTime = time.Now().UnixMilli()
|
operLog.OperaTime = time.Now().UnixMilli()
|
||||||
|
|
||||||
// 保存操作记录到数据库
|
// 保存操作记录到数据库
|
||||||
service.NewSysLogOperateImpl.InsertSysLogOperate(operLog)
|
service.NewSysLogOperate.Insert(operLog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,8 +172,8 @@ var maskProperties []string = []string{
|
|||||||
"oldPassword",
|
"oldPassword",
|
||||||
"newPassword",
|
"newPassword",
|
||||||
"confirmPassword",
|
"confirmPassword",
|
||||||
tokenConstants.RESPONSE_FIELD,
|
constants.ACCESS_TOKEN,
|
||||||
tokenConstants.ACCESS_TOKEN,
|
constants.ACCESS_TOKEN_QUERY,
|
||||||
}
|
}
|
||||||
|
|
||||||
// processSensitiveFields 处理敏感属性字段
|
// processSensitiveFields 处理敏感属性字段
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"be.ems/src/framework/config"
|
"be.ems/src/framework/config"
|
||||||
constResult "be.ems/src/framework/constants/result"
|
|
||||||
"be.ems/src/framework/logger"
|
"be.ems/src/framework/logger"
|
||||||
|
"be.ems/src/framework/resp"
|
||||||
"be.ems/src/framework/utils/crypto"
|
"be.ems/src/framework/utils/crypto"
|
||||||
"be.ems/src/framework/utils/parse"
|
"be.ems/src/framework/utils/parse"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,10 +54,7 @@ func CryptoApi(requestDecrypt, responseEncrypt bool) gin.HandlerFunc {
|
|||||||
|
|
||||||
// 是否存在data字段数据
|
// 是否存在data字段数据
|
||||||
if contentDe == "" {
|
if contentDe == "" {
|
||||||
c.JSON(400, map[string]any{
|
c.JSON(400, resp.ErrMsg("decrypt not found field data"))
|
||||||
"code": constResult.CODE_ERROR,
|
|
||||||
"msg": "decrypt not found field data",
|
|
||||||
})
|
|
||||||
c.Abort() // 停止执行后续的处理函数
|
c.Abort() // 停止执行后续的处理函数
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -66,10 +64,7 @@ func CryptoApi(requestDecrypt, responseEncrypt bool) gin.HandlerFunc {
|
|||||||
dataBodyStr, err := crypto.AESDecryptBase64(contentDe, apiKey)
|
dataBodyStr, err := crypto.AESDecryptBase64(contentDe, apiKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("CryptoApi decrypt err => %v", err)
|
logger.Errorf("CryptoApi decrypt err => %v", err)
|
||||||
c.JSON(400, map[string]any{
|
c.JSON(400, resp.ErrMsg("decrypted data could not be parsed"))
|
||||||
"code": constResult.CODE_ERROR,
|
|
||||||
"msg": "decrypted data could not be parsed",
|
|
||||||
})
|
|
||||||
c.Abort() // 停止执行后续的处理函数
|
c.Abort() // 停止执行后续的处理函数
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -110,19 +105,19 @@ func CryptoApi(requestDecrypt, responseEncrypt bool) gin.HandlerFunc {
|
|||||||
codeV, codeOk := resBody["code"]
|
codeV, codeOk := resBody["code"]
|
||||||
dataV, dataOk := resBody["data"]
|
dataV, dataOk := resBody["data"]
|
||||||
if codeOk && dataOk {
|
if codeOk && dataOk {
|
||||||
if parse.Number(codeV) == constResult.CODE_SUCCESS {
|
if parse.Number(codeV) == resp.CODE_SUCCESS {
|
||||||
byteBodyData, _ := json.Marshal(dataV)
|
byteBodyData, _ := json.Marshal(dataV)
|
||||||
// 加密-原数据头加入标记16位长度iv终止符
|
// 加密-原数据头加入标记16位长度iv终止符
|
||||||
apiKey := config.Get("aes.apiKey").(string)
|
apiKey := config.Get("aes.apiKey").(string)
|
||||||
contentEn, err := crypto.AESEncryptBase64("=:)"+string(byteBodyData), apiKey)
|
contentEn, err := crypto.AESEncryptBase64("=:)"+string(byteBodyData), apiKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("CryptoApi encrypt err => %v", err)
|
logger.Errorf("CryptoApi encrypt err => %v", err)
|
||||||
rbw.ReplaceWrite([]byte(fmt.Sprintf(`{"code":"%d","msg":"encrypt err"}`, constResult.CODE_ERROR)))
|
rbw.ReplaceWrite([]byte(fmt.Sprintf(`{"code":"%d","msg":"encrypt err"}`, resp.CODE_ERROR)))
|
||||||
} else {
|
} else {
|
||||||
// 响应加密
|
// 响应加密
|
||||||
byteBody, _ := json.Marshal(map[string]any{
|
byteBody, _ := json.Marshal(map[string]any{
|
||||||
"code": constResult.CODE_ENCRYPT,
|
"code": resp.CODE_ENCRYPT,
|
||||||
"msg": constResult.MSG_ENCRYPT,
|
"msg": resp.MSG_ENCRYPT,
|
||||||
"data": contentEn,
|
"data": contentEn,
|
||||||
})
|
})
|
||||||
rbw.ReplaceWrite(byteBody)
|
rbw.ReplaceWrite(byteBody)
|
||||||
|
|||||||
195
src/framework/middleware/operate_log.go
Normal file
195
src/framework/middleware/operate_log.go
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"be.ems/src/framework/constants"
|
||||||
|
"be.ems/src/framework/reqctx"
|
||||||
|
"be.ems/src/framework/resp"
|
||||||
|
"be.ems/src/framework/utils/parse"
|
||||||
|
"be.ems/src/modules/system/model"
|
||||||
|
"be.ems/src/modules/system/service"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// BUSINESS_TYPE_OTHER 业务操作类型-其它
|
||||||
|
BUSINESS_TYPE_OTHER = "0"
|
||||||
|
|
||||||
|
// BUSINESS_TYPE_INSERT 业务操作类型-新增
|
||||||
|
BUSINESS_TYPE_INSERT = "1"
|
||||||
|
|
||||||
|
// BUSINESS_TYPE_UPDATE 业务操作类型-修改
|
||||||
|
BUSINESS_TYPE_UPDATE = "2"
|
||||||
|
|
||||||
|
// BUSINESS_TYPE_DELETE 业务操作类型-删除
|
||||||
|
BUSINESS_TYPE_DELETE = "3"
|
||||||
|
|
||||||
|
// BUSINESS_TYPE_GRANT 业务操作类型-授权
|
||||||
|
BUSINESS_TYPE_GRANT = "4"
|
||||||
|
|
||||||
|
// BUSINESS_TYPE_EXPORT 业务操作类型-导出
|
||||||
|
BUSINESS_TYPE_EXPORT = "5"
|
||||||
|
|
||||||
|
// BUSINESS_TYPE_IMPORT 业务操作类型-导入
|
||||||
|
BUSINESS_TYPE_IMPORT = "6"
|
||||||
|
|
||||||
|
// BUSINESS_TYPE_FORCE 业务操作类型-强退
|
||||||
|
BUSINESS_TYPE_FORCE = "7"
|
||||||
|
|
||||||
|
// BUSINESS_TYPE_CLEAN 业务操作类型-清空数据
|
||||||
|
BUSINESS_TYPE_CLEAN = "8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Options Option 操作日志参数
|
||||||
|
type Options struct {
|
||||||
|
Title string `json:"title"` // 标题
|
||||||
|
BusinessType string `json:"businessType"` // 类型,默认常量 BUSINESS_TYPE_OTHER
|
||||||
|
IsSaveRequestData bool `json:"isSaveRequestData"` // 是否保存请求的参数
|
||||||
|
IsSaveResponseData bool `json:"isSaveResponseData"` // 是否保存响应的参数
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionNew 操作日志参数默认值
|
||||||
|
//
|
||||||
|
// 标题 "title":"--"
|
||||||
|
//
|
||||||
|
// 类型 "businessType": BUSINESS_TYPE_OTHER
|
||||||
|
//
|
||||||
|
// 注意之后JSON反序列使用:c.ShouldBindBodyWithJSON(¶ms)
|
||||||
|
func OptionNew(title, businessType string) Options {
|
||||||
|
return Options{
|
||||||
|
Title: title,
|
||||||
|
BusinessType: businessType,
|
||||||
|
IsSaveRequestData: true,
|
||||||
|
IsSaveResponseData: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OperateLog 访问操作日志记录
|
||||||
|
//
|
||||||
|
// 请在用户身份授权认证校验后使用以便获取登录用户信息
|
||||||
|
func OperateLog(options Options) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Set("startTime", time.Now())
|
||||||
|
|
||||||
|
// 函数名
|
||||||
|
funcName := c.HandlerName()
|
||||||
|
lastDotIndex := strings.LastIndex(funcName, "/")
|
||||||
|
funcName = funcName[lastDotIndex+1:]
|
||||||
|
|
||||||
|
// 解析ip地址
|
||||||
|
ipaddr, location := reqctx.IPAddrLocation(c)
|
||||||
|
|
||||||
|
// 获取登录用户信息
|
||||||
|
loginUser, err := reqctx.LoginUser(c)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, resp.CodeMsg(401, "无效身份授权"))
|
||||||
|
c.Abort() // 停止执行后续的处理函数
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 操作日志记录
|
||||||
|
operaLog := model.SysLogOperate{
|
||||||
|
Title: options.Title,
|
||||||
|
BusinessType: options.BusinessType,
|
||||||
|
OperaMethod: funcName,
|
||||||
|
OperaUrl: c.Request.RequestURI,
|
||||||
|
OperaUrlMethod: c.Request.Method,
|
||||||
|
OperaIp: ipaddr,
|
||||||
|
OperaLocation: location,
|
||||||
|
OperaBy: loginUser.User.UserName,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否需要保存request,参数和值
|
||||||
|
if options.IsSaveRequestData {
|
||||||
|
params := reqctx.RequestParamsMap(c)
|
||||||
|
// 敏感属性字段进行掩码
|
||||||
|
processSensitiveFields(params)
|
||||||
|
jsonStr, _ := json.Marshal(params)
|
||||||
|
paramsStr := string(jsonStr)
|
||||||
|
if len(paramsStr) > 2000 {
|
||||||
|
paramsStr = paramsStr[:2000]
|
||||||
|
}
|
||||||
|
operaLog.OperaParam = paramsStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用下一个处理程序
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
// 响应状态
|
||||||
|
status := c.Writer.Status()
|
||||||
|
if status == 200 {
|
||||||
|
operaLog.StatusFlag = constants.STATUS_YES
|
||||||
|
} else {
|
||||||
|
operaLog.StatusFlag = constants.STATUS_NO
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否需要保存response,参数和值
|
||||||
|
if options.IsSaveResponseData {
|
||||||
|
contentDisposition := c.Writer.Header().Get("Content-Disposition")
|
||||||
|
contentType := c.Writer.Header().Get("Content-Type")
|
||||||
|
content := contentType + contentDisposition
|
||||||
|
msg := fmt.Sprintf(`{"status":"%d","size":"%d","content-type":"%s"}`, status, c.Writer.Size(), content)
|
||||||
|
operaLog.OperaMsg = msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日志记录时间
|
||||||
|
duration := time.Since(c.GetTime("startTime"))
|
||||||
|
operaLog.CostTime = duration.Milliseconds()
|
||||||
|
operaLog.OperaTime = time.Now().UnixMilli()
|
||||||
|
|
||||||
|
// 保存操作记录到数据库
|
||||||
|
service.NewSysLogOperate.Insert(operaLog)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 敏感属性字段进行掩码
|
||||||
|
var maskProperties = []string{
|
||||||
|
"password",
|
||||||
|
"oldPassword",
|
||||||
|
"newPassword",
|
||||||
|
"confirmPassword",
|
||||||
|
}
|
||||||
|
|
||||||
|
// processSensitiveFields 处理敏感属性字段
|
||||||
|
func processSensitiveFields(obj interface{}) {
|
||||||
|
val := reflect.ValueOf(obj)
|
||||||
|
|
||||||
|
switch val.Kind() {
|
||||||
|
case reflect.Map:
|
||||||
|
for _, key := range val.MapKeys() {
|
||||||
|
value := val.MapIndex(key)
|
||||||
|
keyStr := key.Interface().(string)
|
||||||
|
|
||||||
|
// 遍历是否敏感属性
|
||||||
|
hasMaskKey := false
|
||||||
|
for _, v := range maskProperties {
|
||||||
|
if v == keyStr {
|
||||||
|
hasMaskKey = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasMaskKey {
|
||||||
|
valueStr := value.Interface().(string)
|
||||||
|
if len(valueStr) > 100 {
|
||||||
|
valueStr = valueStr[0:100]
|
||||||
|
}
|
||||||
|
val.SetMapIndex(key, reflect.ValueOf(parse.SafeContent(valueStr)))
|
||||||
|
} else {
|
||||||
|
processSensitiveFields(value.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
for i := 0; i < val.Len(); i++ {
|
||||||
|
processSensitiveFields(val.Index(i).Interface())
|
||||||
|
}
|
||||||
|
// default:
|
||||||
|
// logger.Infof("processSensitiveFields unhandled case %v", val.Kind())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"be.ems/src/framework/config"
|
"be.ems/src/framework/config"
|
||||||
AdminConstants "be.ems/src/framework/constants/admin"
|
"be.ems/src/framework/constants"
|
||||||
commonConstants "be.ems/src/framework/constants/common"
|
|
||||||
"be.ems/src/framework/i18n"
|
"be.ems/src/framework/i18n"
|
||||||
ctxUtils "be.ems/src/framework/utils/ctx"
|
"be.ems/src/framework/reqctx"
|
||||||
tokenUtils "be.ems/src/framework/utils/token"
|
"be.ems/src/framework/resp"
|
||||||
"be.ems/src/framework/vo/result"
|
"be.ems/src/framework/token"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -44,17 +43,17 @@ func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
|||||||
enable = v.(bool)
|
enable = v.(bool)
|
||||||
}
|
}
|
||||||
if !enable {
|
if !enable {
|
||||||
loginUser, _ := ctxUtils.LoginUser(c)
|
loginUser, _ := reqctx.LoginUser(c)
|
||||||
loginUser.UserID = "2"
|
loginUser.UserId = 2
|
||||||
loginUser.User.UserID = "2"
|
loginUser.User.UserId = 2
|
||||||
loginUser.User.UserName = "admin"
|
loginUser.User.UserName = "admin"
|
||||||
loginUser.User.NickName = "admin"
|
loginUser.User.NickName = "admin"
|
||||||
c.Set(commonConstants.CTX_LOGIN_USER, loginUser)
|
c.Set(constants.CTX_LOGIN_USER, loginUser)
|
||||||
c.Next()
|
c.Next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
language := ctxUtils.AcceptLanguage(c)
|
language := reqctx.AcceptLanguage(c)
|
||||||
|
|
||||||
requestURI := c.Request.RequestURI
|
requestURI := c.Request.RequestURI
|
||||||
|
|
||||||
@@ -72,32 +71,32 @@ func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 获取请求头标识信息
|
// 获取请求头标识信息
|
||||||
tokenStr := ctxUtils.Authorization(c)
|
tokenStr := reqctx.Authorization(c)
|
||||||
if tokenStr == "" {
|
if tokenStr == "" {
|
||||||
c.JSON(401, result.CodeMsg(401, i18n.TKey(language, "app.common.err401")))
|
c.JSON(401, resp.CodeMsg(401, i18n.TKey(language, "app.common.err401")))
|
||||||
c.Abort() // 停止执行后续的处理函数
|
c.Abort() // 停止执行后续的处理函数
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证令牌
|
// 验证令牌
|
||||||
claims, err := tokenUtils.Verify(tokenStr)
|
claims, err := token.Verify(tokenStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(401, result.CodeMsg(401, err.Error()))
|
c.JSON(401, resp.CodeMsg(401, err.Error()))
|
||||||
c.Abort() // 停止执行后续的处理函数
|
c.Abort() // 停止执行后续的处理函数
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取缓存的用户信息
|
// 获取缓存的用户信息
|
||||||
loginUser := tokenUtils.LoginUser(claims)
|
loginUser := token.Info(claims)
|
||||||
if loginUser.UserID == "" {
|
if loginUser.UserId <= 0 {
|
||||||
c.JSON(401, result.CodeMsg(401, i18n.TKey(language, "app.common.err401")))
|
c.JSON(401, resp.CodeMsg(401, i18n.TKey(language, "app.common.err401")))
|
||||||
c.Abort() // 停止执行后续的处理函数
|
c.Abort() // 停止执行后续的处理函数
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查刷新有效期后存入上下文
|
// 检查刷新有效期后存入上下文
|
||||||
tokenUtils.RefreshIn(&loginUser)
|
token.RefreshIn(&loginUser)
|
||||||
c.Set(commonConstants.CTX_LOGIN_USER, loginUser)
|
c.Set(constants.CTX_LOGIN_USER, loginUser)
|
||||||
|
|
||||||
// 登录用户角色权限校验
|
// 登录用户角色权限校验
|
||||||
if options != nil {
|
if options != nil {
|
||||||
@@ -109,7 +108,7 @@ func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
|||||||
verifyOk := verifyRolePermission(roles, perms, options)
|
verifyOk := verifyRolePermission(roles, perms, options)
|
||||||
if !verifyOk {
|
if !verifyOk {
|
||||||
msg := i18n.TTemplate(language, "app.common.err403", map[string]any{"method": c.Request.Method, "requestURI": requestURI})
|
msg := i18n.TTemplate(language, "app.common.err403", map[string]any{"method": c.Request.Method, "requestURI": requestURI})
|
||||||
c.JSON(403, result.CodeMsg(403, msg))
|
c.JSON(403, resp.CodeMsg(403, msg))
|
||||||
c.Abort() // 停止执行后续的处理函数
|
c.Abort() // 停止执行后续的处理函数
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -129,7 +128,7 @@ func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
|||||||
// options 参数
|
// options 参数
|
||||||
func verifyRolePermission(roles, perms []string, options map[string][]string) bool {
|
func verifyRolePermission(roles, perms []string, options map[string][]string) bool {
|
||||||
// 直接放行 管理员角色或任意权限
|
// 直接放行 管理员角色或任意权限
|
||||||
if contains(roles, AdminConstants.ROLE_KEY) || contains(perms, AdminConstants.PERMISSION) {
|
if contains(roles, constants.SYS_ROLE_SYSTEM_KEY) || contains(perms, constants.SYS_PERMISSION_SYSTEM) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
opts := make([]bool, 4)
|
opts := make([]bool, 4)
|
||||||
|
|||||||
@@ -5,31 +5,30 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"be.ems/src/framework/constants/cachekey"
|
|
||||||
"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"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"be.ems/src/framework/constants"
|
||||||
|
"be.ems/src/framework/database/redis"
|
||||||
|
"be.ems/src/framework/ip2region"
|
||||||
|
"be.ems/src/framework/reqctx"
|
||||||
|
"be.ems/src/framework/resp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// 默认策略全局限流
|
// LIMIT_GLOBAL 默认策略全局限流
|
||||||
LIMIT_GLOBAL = 1
|
LIMIT_GLOBAL = 1
|
||||||
|
|
||||||
// 根据请求者IP进行限流
|
// LIMIT_IP 根据请求者IP进行限流
|
||||||
LIMIT_IP = 2
|
LIMIT_IP = 2
|
||||||
|
|
||||||
// 根据用户ID进行限流
|
// LIMIT_USER 根据用户ID进行限流
|
||||||
LIMIT_USER = 3
|
LIMIT_USER = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
// LimitOption 请求限流参数
|
// LimitOption 请求限流参数
|
||||||
type LimitOption struct {
|
type LimitOption struct {
|
||||||
Time int64 `json:"time"` // 限流时间,单位秒
|
Time int64 `json:"time"` // 限流时间,单位秒 5
|
||||||
Count int64 `json:"count"` // 限流次数
|
Count int64 `json:"count"` // 限流次数,单位次 10
|
||||||
Type int64 `json:"type"` // 限流条件类型,默认LIMIT_GLOBAL
|
Type int64 `json:"type"` // 限流条件类型,默认LIMIT_GLOBAL
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +42,6 @@ type LimitOption struct {
|
|||||||
// 以便获取登录用户信息,无用户信息时默认为 GLOBAL
|
// 以便获取登录用户信息,无用户信息时默认为 GLOBAL
|
||||||
func RateLimit(option LimitOption) gin.HandlerFunc {
|
func RateLimit(option LimitOption) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
language := ctx.AcceptLanguage(c)
|
|
||||||
|
|
||||||
// 初始可选参数数据
|
// 初始可选参数数据
|
||||||
if option.Time < 5 {
|
if option.Time < 5 {
|
||||||
option.Time = 5
|
option.Time = 5
|
||||||
@@ -61,40 +58,46 @@ func RateLimit(option LimitOption) gin.HandlerFunc {
|
|||||||
lastDotIndex := strings.LastIndex(funcName, "/")
|
lastDotIndex := strings.LastIndex(funcName, "/")
|
||||||
funcName = funcName[lastDotIndex+1:]
|
funcName = funcName[lastDotIndex+1:]
|
||||||
// 生成限流key
|
// 生成限流key
|
||||||
var limitKey string = cachekey.RATE_LIMIT_KEY + funcName
|
limitKey := constants.CACHE_RATE_LIMIT + ":" + funcName
|
||||||
|
|
||||||
// 用户
|
// 用户
|
||||||
if option.Type == LIMIT_USER {
|
if option.Type == LIMIT_USER {
|
||||||
loginUser, err := ctx.LoginUser(c)
|
loginUser, err := reqctx.LoginUser(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(401, result.Err(map[string]any{
|
c.JSON(401, resp.CodeMsg(40003, err.Error()))
|
||||||
"code": 401,
|
|
||||||
"msg": i18n.TKey(language, err.Error()),
|
|
||||||
}))
|
|
||||||
c.Abort() // 停止执行后续的处理函数
|
c.Abort() // 停止执行后续的处理函数
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limitKey = cachekey.RATE_LIMIT_KEY + loginUser.UserID + ":" + funcName
|
limitKey = fmt.Sprintf("%s:%d:%s", constants.CACHE_RATE_LIMIT, loginUser.UserId, funcName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP
|
// IP
|
||||||
if option.Type == LIMIT_IP {
|
if option.Type == LIMIT_IP {
|
||||||
clientIP := ip2region.ClientIP(c.ClientIP())
|
clientIP := ip2region.ClientIP(c.ClientIP())
|
||||||
limitKey = cachekey.RATE_LIMIT_KEY + clientIP + ":" + funcName
|
limitKey = constants.CACHE_RATE_LIMIT + ":" + clientIP + ":" + funcName
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在Redis查询并记录请求次数
|
// 在Redis查询并记录请求次数
|
||||||
rateCount, _ := redis.RateLimit("", limitKey, option.Time, option.Count)
|
rateCount, err := redis.RateLimit("", limitKey, option.Time, option.Count)
|
||||||
rateTime, _ := redis.GetExpire("", limitKey)
|
if err != nil {
|
||||||
|
c.JSON(200, resp.CodeMsg(4013, "访问过于频繁,请稍候再试"))
|
||||||
|
c.Abort() // 停止执行后续的处理函数
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rateTime, err := redis.GetExpire("", limitKey)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(200, resp.CodeMsg(4013, "访问过于频繁,请稍候再试"))
|
||||||
|
c.Abort() // 停止执行后续的处理函数
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 设置响应头中的限流声明字段
|
// 设置响应头中的限流声明字段
|
||||||
c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", option.Count)) // 总请求数限制
|
c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", option.Count)) // 总请求数限制
|
||||||
c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", option.Count-rateCount)) // 剩余可用请求数
|
c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", option.Count-rateCount)) // 剩余可用请求数
|
||||||
c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Unix()+int64(rateTime))) // 重置时间戳
|
c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Unix()+rateTime)) // 重置时间戳
|
||||||
|
|
||||||
if rateCount >= option.Count {
|
if rateCount >= option.Count {
|
||||||
// 访问过于频繁,请稍候再试
|
c.JSON(200, resp.CodeMsg(4013, "访问过于频繁,请稍候再试"))
|
||||||
c.JSON(200, i18n.TKey(language, "app.common.rateLimitTip"))
|
|
||||||
c.Abort() // 停止执行后续的处理函数
|
c.Abort() // 停止执行后续的处理函数
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"be.ems/src/framework/constants/cachekey"
|
"be.ems/src/framework/constants"
|
||||||
|
"be.ems/src/framework/database/redis"
|
||||||
|
"be.ems/src/framework/ip2region"
|
||||||
"be.ems/src/framework/logger"
|
"be.ems/src/framework/logger"
|
||||||
"be.ems/src/framework/redis"
|
"be.ems/src/framework/reqctx"
|
||||||
"be.ems/src/framework/utils/ctx"
|
"be.ems/src/framework/resp"
|
||||||
"be.ems/src/framework/utils/ip2region"
|
|
||||||
"be.ems/src/framework/vo/result"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -33,7 +33,7 @@ func RepeatSubmit(interval int64) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 提交参数
|
// 提交参数
|
||||||
params := ctx.RequestParamsMap(c)
|
params := reqctx.RequestParamsMap(c)
|
||||||
paramsJSONByte, err := json.Marshal(params)
|
paramsJSONByte, err := json.Marshal(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("RepeatSubmit params json marshal err: %v", err)
|
logger.Errorf("RepeatSubmit params json marshal err: %v", err)
|
||||||
@@ -42,7 +42,7 @@ func RepeatSubmit(interval int64) gin.HandlerFunc {
|
|||||||
|
|
||||||
// 唯一标识(指定key + 客户端IP + 请求地址)
|
// 唯一标识(指定key + 客户端IP + 请求地址)
|
||||||
clientIP := ip2region.ClientIP(c.ClientIP())
|
clientIP := ip2region.ClientIP(c.ClientIP())
|
||||||
repeatKey := cachekey.REPEAT_SUBMIT_KEY + clientIP + ":" + c.Request.RequestURI
|
repeatKey := constants.CACHE_REPEAT_SUBMIT + clientIP + ":" + c.Request.RequestURI
|
||||||
|
|
||||||
// 在Redis查询并记录请求次数
|
// 在Redis查询并记录请求次数
|
||||||
repeatStr, _ := redis.Get("", repeatKey)
|
repeatStr, _ := redis.Get("", repeatKey)
|
||||||
@@ -61,7 +61,7 @@ func RepeatSubmit(interval int64) gin.HandlerFunc {
|
|||||||
// 小于间隔时间且参数内容一致
|
// 小于间隔时间且参数内容一致
|
||||||
if compareTime < interval && compareParams {
|
if compareTime < interval && compareParams {
|
||||||
// 不允许重复提交,请稍候再试
|
// 不允许重复提交,请稍候再试
|
||||||
c.JSON(200, result.ErrMsg("Duplicate submissions are not allowed. Please try again later"))
|
c.JSON(200, resp.ErrMsg("Duplicate submissions are not allowed. Please try again later"))
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
84
src/framework/middleware/repeat_submit.go
Normal file
84
src/framework/middleware/repeat_submit.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"be.ems/src/framework/constants"
|
||||||
|
"be.ems/src/framework/database/redis"
|
||||||
|
"be.ems/src/framework/ip2region"
|
||||||
|
"be.ems/src/framework/logger"
|
||||||
|
"be.ems/src/framework/reqctx"
|
||||||
|
"be.ems/src/framework/resp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// repeatParam 重复提交参数的类型定义
|
||||||
|
type repeatParam struct {
|
||||||
|
Time int64 `json:"time"`
|
||||||
|
Params string `json:"params"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RepeatSubmit 防止表单重复提交,小于间隔时间视为重复提交
|
||||||
|
//
|
||||||
|
// 间隔时间(单位秒) 默认:5
|
||||||
|
//
|
||||||
|
// 注意之后JSON反序列使用:c.ShouldBindBodyWithJSON(¶ms)
|
||||||
|
func RepeatSubmit(interval int64) gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
if interval < 5 {
|
||||||
|
interval = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交参数
|
||||||
|
params := reqctx.RequestParamsMap(c)
|
||||||
|
paramsJSONByte, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("RepeatSubmit params json marshal err: %v", err)
|
||||||
|
}
|
||||||
|
paramsJSONStr := string(paramsJSONByte)
|
||||||
|
|
||||||
|
// 唯一标识(指定key + 客户端IP + 请求地址)
|
||||||
|
clientIP := ip2region.ClientIP(c.ClientIP())
|
||||||
|
repeatKey := constants.CACHE_REPEAT_SUBMIT + ":" + clientIP + ":" + c.Request.RequestURI
|
||||||
|
|
||||||
|
// 在Redis查询并记录请求次数
|
||||||
|
repeatStr, _ := redis.Get("", repeatKey)
|
||||||
|
if repeatStr != "" {
|
||||||
|
var rp repeatParam
|
||||||
|
err := json.Unmarshal([]byte(repeatStr), &rp)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("RepeatSubmit repeatStr json unmarshal err: %v", err)
|
||||||
|
}
|
||||||
|
compareTime := time.Now().Unix() - rp.Time
|
||||||
|
compareParams := rp.Params == paramsJSONStr
|
||||||
|
|
||||||
|
// 设置重复提交声明响应头(毫秒)
|
||||||
|
c.Header("X-RepeatSubmit-Rest", strconv.FormatInt(time.Now().Add(time.Duration(compareTime)*time.Second).UnixNano()/int64(time.Millisecond), 10))
|
||||||
|
|
||||||
|
// 小于间隔时间且参数内容一致
|
||||||
|
if compareTime < interval && compareParams {
|
||||||
|
c.JSON(200, resp.ErrMsg("不允许重复提交,请稍候再试"))
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当前请求参数
|
||||||
|
rp := repeatParam{
|
||||||
|
Time: time.Now().Unix(),
|
||||||
|
Params: paramsJSONStr,
|
||||||
|
}
|
||||||
|
rpJSON, err := json.Marshal(rp)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("RepeatSubmit rp json marshal err: %v", err)
|
||||||
|
}
|
||||||
|
// 保存请求时间和参数
|
||||||
|
_ = redis.SetByExpire("", repeatKey, string(rpJSON), time.Duration(interval)*time.Second)
|
||||||
|
|
||||||
|
// 调用下一个处理程序
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"be.ems/src/framework/logger"
|
||||||
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"be.ems/src/framework/logger"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,10 +19,7 @@ func Report() gin.HandlerFunc {
|
|||||||
|
|
||||||
// 计算请求处理时间,并打印日志
|
// 计算请求处理时间,并打印日志
|
||||||
duration := time.Since(start)
|
duration := time.Since(start)
|
||||||
// logger.Infof("%s %s report end=> %v", c.Request.Method, c.Request.RequestURI, duration)
|
numGoroutines := runtime.NumGoroutine()
|
||||||
// 获取当前活跃的goroutine数量
|
logger.Infof("\n访问接口: %s %s\n总耗时: %v\n当前活跃的Goroutine数量: %d", c.Request.Method, c.Request.RequestURI, duration, numGoroutines)
|
||||||
num := runtime.NumGoroutine()
|
|
||||||
// logger.Infof("当前活跃的goroutine数量 %d\n", num)
|
|
||||||
logger.Infof("\n访问接口 %s %s\n总耗时 %v\n当前活跃的goroutine数量 %d\n", c.Request.Method, c.Request.RequestURI, duration, num)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"be.ems/src/framework/config"
|
"be.ems/src/framework/config"
|
||||||
"be.ems/src/framework/vo/result"
|
"be.ems/src/framework/resp"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
@@ -43,7 +43,7 @@ func referer(c *gin.Context) {
|
|||||||
referer := c.GetHeader("Referer")
|
referer := c.GetHeader("Referer")
|
||||||
if referer == "" {
|
if referer == "" {
|
||||||
// 无效 Referer 未知
|
// 无效 Referer 未知
|
||||||
c.AbortWithStatusJSON(200, result.ErrMsg("Invalid referer unknown"))
|
c.AbortWithStatusJSON(200, resp.ErrMsg("Invalid referer unknown"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ func referer(c *gin.Context) {
|
|||||||
u, err := url.Parse(referer)
|
u, err := url.Parse(referer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 无效 Referer 未知
|
// 无效 Referer 未知
|
||||||
c.AbortWithStatusJSON(200, result.ErrMsg("Invalid referer unknown"))
|
c.AbortWithStatusJSON(200, resp.ErrMsg("Invalid referer unknown"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
host := u.Host
|
host := u.Host
|
||||||
@@ -73,7 +73,7 @@ func referer(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
// 无效 Referer
|
// 无效 Referer
|
||||||
c.AbortWithStatusJSON(200, result.ErrMsg("Invalid referer "+host))
|
c.AbortWithStatusJSON(200, resp.ErrMsg("Invalid referer "+host))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,442 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"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实例
|
|
||||||
func ConnectPush(source string, rdb *redis.Client) {
|
|
||||||
if rdb == nil {
|
|
||||||
delete(rdbMap, source)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
rdbMap[source] = rdb
|
|
||||||
}
|
|
||||||
|
|
||||||
// 连接Redis实例
|
|
||||||
func Connect() {
|
|
||||||
ctx := context.Background()
|
|
||||||
// 读取数据源配置
|
|
||||||
datasource := config.Get("redis.dataSource").(map[string]any)
|
|
||||||
for k, v := range datasource {
|
|
||||||
client := v.(map[string]any)
|
|
||||||
// 创建连接
|
|
||||||
address := fmt.Sprintf("%s:%d", client["host"], client["port"])
|
|
||||||
rdb := redis.NewClient(&redis.Options{
|
|
||||||
Addr: address,
|
|
||||||
Password: client["password"].(string),
|
|
||||||
DB: client["db"].(int),
|
|
||||||
})
|
|
||||||
// 测试数据库连接
|
|
||||||
pong, err := rdb.Ping(ctx).Result()
|
|
||||||
if err != nil {
|
|
||||||
logger.Fatalf("Ping redis %s is %v", k, err)
|
|
||||||
}
|
|
||||||
logger.Infof("redis %s %s %d connection is successful.", k, pong, client["db"].(int))
|
|
||||||
rdbMap[k] = rdb
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭Redis实例
|
|
||||||
func Close() {
|
|
||||||
for _, rdb := range rdbMap {
|
|
||||||
if err := rdb.Close(); err != nil {
|
|
||||||
logger.Errorf("fatal error db close: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取默认实例
|
|
||||||
func DefaultRDB() *redis.Client {
|
|
||||||
source := config.Get("redis.defaultDataSourceName").(string)
|
|
||||||
return rdbMap[source]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取实例
|
|
||||||
func RDB(source string) *redis.Client {
|
|
||||||
return rdbMap[source]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info 获取redis服务信息
|
|
||||||
func Info(source string) map[string]map[string]string {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
info, err := rdb.Info(ctx).Result()
|
|
||||||
if err != nil {
|
|
||||||
return map[string]map[string]string{}
|
|
||||||
}
|
|
||||||
infoObj := make(map[string]map[string]string)
|
|
||||||
lines := strings.Split(info, "\r\n")
|
|
||||||
label := ""
|
|
||||||
for _, line := range lines {
|
|
||||||
if strings.Contains(line, "#") {
|
|
||||||
label = strings.Fields(line)[len(strings.Fields(line))-1]
|
|
||||||
label = strings.ToLower(label)
|
|
||||||
infoObj[label] = make(map[string]string)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
kvArr := strings.Split(line, ":")
|
|
||||||
if len(kvArr) >= 2 {
|
|
||||||
key := strings.TrimSpace(kvArr[0])
|
|
||||||
value := strings.TrimSpace(kvArr[len(kvArr)-1])
|
|
||||||
infoObj[label][key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return infoObj
|
|
||||||
}
|
|
||||||
|
|
||||||
// KeySize 获取redis当前连接可用键Key总数信息
|
|
||||||
func KeySize(source string) int64 {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
size, err := rdb.DBSize(ctx).Result()
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return size
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommandStats 获取redis命令状态信息
|
|
||||||
func CommandStats(source string) []map[string]string {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
commandstats, err := rdb.Info(ctx, "commandstats").Result()
|
|
||||||
if err != nil {
|
|
||||||
return []map[string]string{}
|
|
||||||
}
|
|
||||||
statsObjArr := make([]map[string]string, 0)
|
|
||||||
lines := strings.Split(commandstats, "\r\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
if !strings.HasPrefix(line, "cmdstat_") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
kvArr := strings.Split(line, ":")
|
|
||||||
key := kvArr[0]
|
|
||||||
valueStr := kvArr[len(kvArr)-1]
|
|
||||||
statsObj := make(map[string]string)
|
|
||||||
statsObj["name"] = key[8:]
|
|
||||||
statsObj["value"] = valueStr[6:strings.Index(valueStr, ",usec=")]
|
|
||||||
statsObjArr = append(statsObjArr, statsObj)
|
|
||||||
}
|
|
||||||
return statsObjArr
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取键的剩余有效时间(秒)
|
|
||||||
func GetExpire(source string, key string) (float64, error) {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ttl, err := rdb.TTL(ctx, key).Result()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return ttl.Seconds(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获得缓存数据的key列表
|
|
||||||
func GetKeys(source string, match string) ([]string, error) {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := make([]string, 0)
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量获得缓存数据
|
|
||||||
func GetBatch(source string, keys []string) ([]any, error) {
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return []any{}, fmt.Errorf("not keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取缓存数据
|
|
||||||
result, err := rdb.MGet(context.Background(), keys...).Result()
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Failed to get batch data: %v", err)
|
|
||||||
return []any{}, err
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获得缓存数据
|
|
||||||
func Get(source, key string) (string, error) {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
value, err := rdb.Get(ctx, key).Result()
|
|
||||||
if err == redis.Nil || err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获得缓存数据Hash
|
|
||||||
func GetHash(source, key string) (map[string]string, error) {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
value, err := rdb.HGetAll(ctx, key).Result()
|
|
||||||
if err == redis.Nil || err != nil {
|
|
||||||
return map[string]string{}, err
|
|
||||||
}
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量获得缓存数据 [key]result
|
|
||||||
func GetHashBatch(source string, keys []string) (map[string]map[string]string, error) {
|
|
||||||
result := make(map[string]map[string]string, 0)
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return result, fmt.Errorf("not keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建一个有限的并发控制信号通道
|
|
||||||
sem := make(chan struct{}, 10)
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
var mt sync.Mutex
|
|
||||||
batchSize := 1000
|
|
||||||
total := len(keys)
|
|
||||||
if total < batchSize {
|
|
||||||
batchSize = total
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < total; i += batchSize {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(start int) {
|
|
||||||
ctx := context.Background()
|
|
||||||
// 并发控制,限制同时执行的 Goroutine 数量
|
|
||||||
sem <- struct{}{}
|
|
||||||
defer func() {
|
|
||||||
<-sem
|
|
||||||
ctx.Done()
|
|
||||||
wg.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
// 检查索引是否越界
|
|
||||||
end := start + batchSize
|
|
||||||
if end > total {
|
|
||||||
end = total
|
|
||||||
}
|
|
||||||
pipe := rdb.Pipeline()
|
|
||||||
for _, key := range keys[start:end] {
|
|
||||||
pipe.HGetAll(ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds, err := pipe.Exec(ctx)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("Failed to get hash batch exec err: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将结果添加到 result map 并发访问
|
|
||||||
mt.Lock()
|
|
||||||
defer mt.Unlock()
|
|
||||||
|
|
||||||
// 处理命令结果
|
|
||||||
for _, cmd := range cmds {
|
|
||||||
if cmd.Err() != nil {
|
|
||||||
logger.Errorf("Failed to get hash batch cmds err: %v", cmd.Err())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// 将结果转换为 *redis.StringStringMapCmd 类型
|
|
||||||
rcmd, ok := cmd.(*redis.MapStringStringCmd)
|
|
||||||
if !ok {
|
|
||||||
logger.Errorf("Failed to get hash batch type err: %v", cmd.Err())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
key := "-"
|
|
||||||
args := rcmd.Args()
|
|
||||||
if len(args) > 0 {
|
|
||||||
key = fmt.Sprint(args[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
result[key] = rcmd.Val()
|
|
||||||
}
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 判断是否存在
|
|
||||||
func Has(source string, keys ...string) (bool, error) {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
exists, err := rdb.Exists(ctx, keys...).Result()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return exists >= 1, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置缓存数据
|
|
||||||
func Set(source, key string, value any) (bool, error) {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err := rdb.Set(ctx, key, value, 0).Err()
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("redis Set err %v", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置缓存数据与过期时间
|
|
||||||
func SetByExpire(source, key string, value any, expiration time.Duration) (bool, error) {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err := rdb.Set(ctx, key, value, expiration).Err()
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("redis SetByExpire err %v", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除单个
|
|
||||||
func Del(source string, key string) (bool, error) {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err := rdb.Del(ctx, key).Err()
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("redis Del err %v", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除多个
|
|
||||||
func DelKeys(source string, keys []string) (bool, error) {
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return false, fmt.Errorf("no keys")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
err := rdb.Del(ctx, keys...).Err()
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("redis DelKeys err %v", err)
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 限流查询并记录
|
|
||||||
func RateLimit(source, limitKey string, time, count int64) (int64, error) {
|
|
||||||
// 数据源
|
|
||||||
rdb := DefaultRDB()
|
|
||||||
if source != "" {
|
|
||||||
rdb = RDB(source)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
result, err := rateLimitCommand.Run(ctx, rdb, []string{limitKey}, time, count).Result()
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("redis RateLimit err %v", err)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return result.(int64), err
|
|
||||||
}
|
|
||||||
160
src/framework/reqctx/auth.go
Normal file
160
src/framework/reqctx/auth.go
Normal 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/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoginUser 登录用户信息
|
||||||
|
func LoginUser(c *gin.Context) (token.TokenInfo, error) {
|
||||||
|
value, exists := c.Get(constants.CTX_LOGIN_USER)
|
||||||
|
if exists && value != nil {
|
||||||
|
return value.(token.TokenInfo), nil
|
||||||
|
}
|
||||||
|
return token.TokenInfo{}, fmt.Errorf("invalid login user information")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoginUserToUserID 登录用户信息-用户ID
|
||||||
|
func LoginUserToUserID(c *gin.Context) int64 {
|
||||||
|
info, err := LoginUser(c)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
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.IsSystemUser(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.IsSystemUser(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.IsSystemUser(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 = %d )
|
||||||
|
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 = %d
|
||||||
|
)`, deptAlias, role.RoleId, deptAlias, role.RoleId)
|
||||||
|
conditions = append(conditions, sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.ROLE_SCOPE_DEPT == dataScope {
|
||||||
|
sql := fmt.Sprintf("%s.dept_id = %d", 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 = %d OR find_in_set(%d, ancestors ) )", deptAlias, userInfo.DeptId, userInfo.DeptId)
|
||||||
|
conditions = append(conditions, sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.ROLE_SCOPE_SELF == dataScope {
|
||||||
|
if userAlias == "" {
|
||||||
|
sql := fmt.Sprintf("%s.dept_id = %d", deptAlias, userInfo.DeptId)
|
||||||
|
conditions = append(conditions, sql)
|
||||||
|
} else {
|
||||||
|
sql := fmt.Sprintf("%s.user_id = %d", 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
|
||||||
|
}
|
||||||
106
src/framework/reqctx/context.go
Normal file
106
src/framework/reqctx/context.go
Normal 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(¶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]
|
||||||
|
}
|
||||||
35
src/framework/reqctx/param.go
Normal file
35
src/framework/reqctx/param.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package reqctx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"be.ems/src/framework/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
|
||||||
|
}
|
||||||
@@ -1,7 +1,20 @@
|
|||||||
package result
|
package resp
|
||||||
|
|
||||||
import (
|
const (
|
||||||
constResult "be.ems/src/framework/constants/result"
|
// 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CodeMsg 响应结果
|
// CodeMsg 响应结果
|
||||||
@@ -12,11 +25,11 @@ func CodeMsg(code int, msg string) map[string]any {
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应成功结果 map[string]any{}
|
// Ok 响应成功结果
|
||||||
func Ok(v map[string]any) map[string]any {
|
func Ok(v map[string]any) map[string]any {
|
||||||
args := make(map[string]any)
|
args := make(map[string]any)
|
||||||
args["code"] = constResult.CODE_SUCCESS
|
args["code"] = CODE_SUCCESS
|
||||||
args["msg"] = constResult.MSG_SUCCESS
|
args["msg"] = MSG_SUCCCESS
|
||||||
// v合并到args
|
// v合并到args
|
||||||
for key, value := range v {
|
for key, value := range v {
|
||||||
args[key] = value
|
args[key] = value
|
||||||
@@ -24,28 +37,28 @@ func Ok(v map[string]any) map[string]any {
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应成功结果信息
|
// OkMsg 响应成功结果信息
|
||||||
func OkMsg(msg string) map[string]any {
|
func OkMsg(msg string) map[string]any {
|
||||||
args := make(map[string]any)
|
args := make(map[string]any)
|
||||||
args["code"] = constResult.CODE_SUCCESS
|
args["code"] = CODE_SUCCESS
|
||||||
args["msg"] = msg
|
args["msg"] = msg
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应成功结果数据
|
// OkData 响应成功结果数据
|
||||||
func OkData(data any) map[string]any {
|
func OkData(data any) map[string]any {
|
||||||
args := make(map[string]any)
|
args := make(map[string]any)
|
||||||
args["code"] = constResult.CODE_SUCCESS
|
args["code"] = CODE_SUCCESS
|
||||||
args["msg"] = constResult.MSG_SUCCESS
|
args["msg"] = MSG_SUCCCESS
|
||||||
args["data"] = data
|
args["data"] = data
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应失败结果 map[string]any{}
|
// Err 响应失败结果 map[string]any{}
|
||||||
func Err(v map[string]any) map[string]any {
|
func Err(v map[string]any) map[string]any {
|
||||||
args := make(map[string]any)
|
args := make(map[string]any)
|
||||||
args["code"] = constResult.CODE_ERROR
|
args["code"] = CODE_ERROR
|
||||||
args["msg"] = constResult.MSG_ERROR
|
args["msg"] = MSG_ERROR
|
||||||
// v合并到args
|
// v合并到args
|
||||||
for key, value := range v {
|
for key, value := range v {
|
||||||
args[key] = value
|
args[key] = value
|
||||||
@@ -53,19 +66,19 @@ func Err(v map[string]any) map[string]any {
|
|||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应失败结果信息
|
// ErrMsg 响应失败结果信息
|
||||||
func ErrMsg(msg string) map[string]any {
|
func ErrMsg(msg string) map[string]any {
|
||||||
args := make(map[string]any)
|
args := make(map[string]any)
|
||||||
args["code"] = constResult.CODE_ERROR
|
args["code"] = CODE_ERROR
|
||||||
args["msg"] = msg
|
args["msg"] = msg
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
|
|
||||||
// 响应失败结果数据
|
// ErrData 响应失败结果数据
|
||||||
func ErrData(data any) map[string]any {
|
func ErrData(data any) map[string]any {
|
||||||
args := make(map[string]any)
|
args := make(map[string]any)
|
||||||
args["code"] = constResult.CODE_ERROR
|
args["code"] = CODE_ERROR
|
||||||
args["msg"] = constResult.MSG_ERROR
|
args["msg"] = MSG_ERROR
|
||||||
args["data"] = data
|
args["data"] = data
|
||||||
return args
|
return args
|
||||||
}
|
}
|
||||||
23
src/framework/resp/error.go
Normal file
23
src/framework/resp/error.go
Normal 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()
|
||||||
|
}
|
||||||
152
src/framework/token/token.go
Normal file
152
src/framework/token/token.go
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
|
||||||
|
"be.ems/src/framework/config"
|
||||||
|
"be.ems/src/framework/constants"
|
||||||
|
"be.ems/src/framework/database/redis"
|
||||||
|
"be.ems/src/framework/logger"
|
||||||
|
"be.ems/src/framework/utils/generate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Remove 清除登录用户信息UUID
|
||||||
|
func Remove(token string) string {
|
||||||
|
claims, err := Verify(token)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("token verify err %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
// 清除缓存KEY
|
||||||
|
uuid := claims[constants.JWT_UUID].(string)
|
||||||
|
tokenKey := constants.CACHE_LOGIN_TOKEN + ":" + uuid
|
||||||
|
hasKey, err := redis.Has("", tokenKey)
|
||||||
|
if hasKey > 0 && err == nil {
|
||||||
|
_ = redis.Del("", tokenKey)
|
||||||
|
}
|
||||||
|
return claims[constants.JWT_USER_NAME].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create 令牌生成
|
||||||
|
func Create(tokenInfo *TokenInfo, ilobArr [4]string) string {
|
||||||
|
// 生成用户唯一token 32位
|
||||||
|
tokenInfo.UUID = generate.Code(32)
|
||||||
|
tokenInfo.LoginTime = time.Now().UnixMilli()
|
||||||
|
|
||||||
|
// 设置请求用户登录客户端
|
||||||
|
tokenInfo.LoginIp = ilobArr[0]
|
||||||
|
tokenInfo.LoginLocation = ilobArr[1]
|
||||||
|
tokenInfo.OS = ilobArr[2]
|
||||||
|
tokenInfo.Browser = ilobArr[3]
|
||||||
|
|
||||||
|
// 设置新登录IP和登录时间
|
||||||
|
tokenInfo.User.LoginIp = tokenInfo.LoginIp
|
||||||
|
tokenInfo.User.LoginTime = tokenInfo.LoginTime
|
||||||
|
|
||||||
|
// 设置用户令牌有效期并存入缓存
|
||||||
|
Cache(tokenInfo)
|
||||||
|
|
||||||
|
// 令牌算法 HS256 HS384 HS512
|
||||||
|
algorithm := config.Get("jwt.algorithm").(string)
|
||||||
|
var method *jwt.SigningMethodHMAC
|
||||||
|
switch algorithm {
|
||||||
|
case "HS512":
|
||||||
|
method = jwt.SigningMethodHS512
|
||||||
|
case "HS384":
|
||||||
|
method = jwt.SigningMethodHS384
|
||||||
|
case "HS256":
|
||||||
|
default:
|
||||||
|
method = jwt.SigningMethodHS256
|
||||||
|
}
|
||||||
|
// 生成令牌负荷绑定uuid标识
|
||||||
|
jwtToken := jwt.NewWithClaims(method, jwt.MapClaims{
|
||||||
|
constants.JWT_UUID: tokenInfo.UUID,
|
||||||
|
constants.JWT_USER_ID: tokenInfo.UserId,
|
||||||
|
constants.JWT_USER_NAME: tokenInfo.User.UserName,
|
||||||
|
"ait": tokenInfo.LoginTime,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 生成令牌设置密钥
|
||||||
|
secret := config.Get("jwt.secret").(string)
|
||||||
|
tokenStr, err := jwtToken.SignedString([]byte(secret))
|
||||||
|
if err != nil {
|
||||||
|
logger.Infof("jwt sign err : %v", err)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return tokenStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache 缓存登录用户信息
|
||||||
|
func Cache(tokenInfo *TokenInfo) {
|
||||||
|
// 计算配置的有效期
|
||||||
|
expTime := config.Get("jwt.expiresIn").(int)
|
||||||
|
expTimestamp := time.Duration(expTime) * time.Minute
|
||||||
|
iatTimestamp := time.Now().UnixMilli()
|
||||||
|
tokenInfo.LoginTime = iatTimestamp
|
||||||
|
tokenInfo.ExpireTime = iatTimestamp + expTimestamp.Milliseconds()
|
||||||
|
tokenInfo.User.Password = ""
|
||||||
|
// 登录信息标识缓存
|
||||||
|
tokenKey := constants.CACHE_LOGIN_TOKEN + ":" + tokenInfo.UUID
|
||||||
|
jsonBytes, err := json.Marshal(tokenInfo)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = redis.SetByExpire("", tokenKey, string(jsonBytes), expTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshIn 验证令牌有效期,相差不足xx分钟,自动刷新缓存
|
||||||
|
func RefreshIn(loginUser *TokenInfo) {
|
||||||
|
// 相差不足xx分钟,自动刷新缓存
|
||||||
|
refreshTime := config.Get("jwt.refreshIn").(int)
|
||||||
|
refreshTimestamp := time.Duration(refreshTime) * time.Minute
|
||||||
|
// 过期时间
|
||||||
|
expireTimestamp := loginUser.ExpireTime
|
||||||
|
currentTimestamp := time.Now().UnixMilli()
|
||||||
|
if expireTimestamp-currentTimestamp <= refreshTimestamp.Milliseconds() {
|
||||||
|
Cache(loginUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify 校验令牌是否有效
|
||||||
|
func Verify(token string) (jwt.MapClaims, error) {
|
||||||
|
jwtToken, err := jwt.Parse(token, func(jToken *jwt.Token) (any, error) {
|
||||||
|
// 判断加密算法是预期的加密算法
|
||||||
|
if _, ok := jToken.Method.(*jwt.SigningMethodHMAC); ok {
|
||||||
|
secret := config.Get("jwt.secret").(string)
|
||||||
|
return []byte(secret), nil
|
||||||
|
}
|
||||||
|
return nil, jwt.ErrSignatureInvalid
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("Token Verify Err: %v", err)
|
||||||
|
return nil, fmt.Errorf("token invalid")
|
||||||
|
}
|
||||||
|
// 如果解析负荷成功并通过签名校验
|
||||||
|
if claims, ok := jwtToken.Claims.(jwt.MapClaims); ok && jwtToken.Valid {
|
||||||
|
return claims, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("token valid error")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info 缓存的登录用户信息
|
||||||
|
func Info(claims jwt.MapClaims) TokenInfo {
|
||||||
|
tokenInfo := TokenInfo{}
|
||||||
|
uuid := claims[constants.JWT_UUID].(string)
|
||||||
|
tokenKey := constants.CACHE_LOGIN_TOKEN + ":" + uuid
|
||||||
|
hasKey, err := redis.Has("", tokenKey)
|
||||||
|
if hasKey > 0 && err == nil {
|
||||||
|
infoStr, err := redis.Get("", tokenKey)
|
||||||
|
if infoStr == "" || err != nil {
|
||||||
|
return tokenInfo
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(infoStr), &tokenInfo); err != nil {
|
||||||
|
logger.Errorf("info json err : %v", err)
|
||||||
|
return tokenInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tokenInfo
|
||||||
|
}
|
||||||
18
src/framework/token/token_info.go
Normal file
18
src/framework/token/token_info.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import systemModel "be.ems/src/modules/system/model"
|
||||||
|
|
||||||
|
// TokenInfo 令牌信息对象
|
||||||
|
type TokenInfo struct {
|
||||||
|
UUID string `json:"uuid"` // 用户唯一标识
|
||||||
|
UserId int64 `json:"userId"` // 用户ID
|
||||||
|
DeptId int64 `json:"deptId"` // 部门ID
|
||||||
|
LoginTime int64 `json:"loginTime"` // 登录时间时间戳
|
||||||
|
ExpireTime int64 `json:"expireTime"` // 过期时间时间戳
|
||||||
|
LoginIp string `json:"loginIp"` // 登录IP地址 x.x.x.x
|
||||||
|
LoginLocation string `json:"loginLocation"` // 登录地点 xx xx
|
||||||
|
Browser string `json:"browser"` // 浏览器类型
|
||||||
|
OS string `json:"os"` // 操作系统
|
||||||
|
Permissions []string `json:"permissions"` // 权限列表
|
||||||
|
User systemModel.SysUser `json:"user"` // 用户信息
|
||||||
|
}
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
package ctx
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"be.ems/src/framework/config"
|
|
||||||
"be.ems/src/framework/constants/common"
|
|
||||||
"be.ems/src/framework/constants/roledatascope"
|
|
||||||
"be.ems/src/framework/constants/token"
|
|
||||||
"be.ems/src/framework/utils/ip2region"
|
|
||||||
"be.ems/src/framework/utils/ua"
|
|
||||||
"be.ems/src/framework/vo"
|
|
||||||
"golang.org/x/text/language"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// QueryMapString 查询参数转换Map
|
|
||||||
func QueryMapString(c *gin.Context) map[string]string {
|
|
||||||
queryValues := c.Request.URL.Query()
|
|
||||||
queryParams := make(map[string]string)
|
|
||||||
for key, values := range queryValues {
|
|
||||||
queryParams[key] = values[0]
|
|
||||||
}
|
|
||||||
return queryParams
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryMap 查询参数转换Map
|
|
||||||
func QueryMap(c *gin.Context) map[string]any {
|
|
||||||
queryValues := c.Request.URL.Query()
|
|
||||||
queryParams := make(map[string]any)
|
|
||||||
for key, values := range queryValues {
|
|
||||||
queryParams[key] = values[0]
|
|
||||||
}
|
|
||||||
return queryParams
|
|
||||||
}
|
|
||||||
|
|
||||||
// BodyJSONMap JSON参数转换Map
|
|
||||||
func BodyJSONMap(c *gin.Context) map[string]any {
|
|
||||||
params := make(map[string]any)
|
|
||||||
c.ShouldBindBodyWithJSON(¶ms)
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestParamsMap 请求参数转换Map
|
|
||||||
func RequestParamsMap(c *gin.Context) map[string]any {
|
|
||||||
params := make(map[string]any)
|
|
||||||
// json
|
|
||||||
if strings.HasPrefix(c.ContentType(), "application/json") {
|
|
||||||
c.ShouldBindBodyWithJSON(¶ms)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 表单
|
|
||||||
bodyParams := c.Request.PostForm
|
|
||||||
for key, value := range bodyParams {
|
|
||||||
params[key] = value[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询
|
|
||||||
queryParams := c.Request.URL.Query()
|
|
||||||
for key, value := range queryParams {
|
|
||||||
params[key] = value[0]
|
|
||||||
}
|
|
||||||
return params
|
|
||||||
}
|
|
||||||
|
|
||||||
// IPAddrLocation 解析ip地址
|
|
||||||
func IPAddrLocation(c *gin.Context) (string, string) {
|
|
||||||
ip := ip2region.ClientIP(c.ClientIP())
|
|
||||||
location := ip2region.RealAddressByIp(ip)
|
|
||||||
return ip, location
|
|
||||||
}
|
|
||||||
|
|
||||||
// Authorization 解析请求头
|
|
||||||
func Authorization(c *gin.Context) string {
|
|
||||||
// Query请求查询
|
|
||||||
if authQuery, ok := c.GetQuery(token.ACCESS_TOKEN); ok && authQuery != "" {
|
|
||||||
return authQuery
|
|
||||||
}
|
|
||||||
// Header请求头
|
|
||||||
if authHeader := c.GetHeader(token.ACCESS_TOKEN); authHeader != "" {
|
|
||||||
return authHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query请求查询
|
|
||||||
if authQuery, ok := c.GetQuery(token.RESPONSE_FIELD); ok && authQuery != "" {
|
|
||||||
return authQuery
|
|
||||||
}
|
|
||||||
// Header请求头
|
|
||||||
authHeader := c.GetHeader(token.HEADER_KEY)
|
|
||||||
if authHeader == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// 拆分 Authorization 请求头,提取 JWT 令牌部分
|
|
||||||
arr := strings.SplitN(authHeader, token.HEADER_PREFIX, 2)
|
|
||||||
if len(arr) < 2 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return arr[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
// UaOsBrowser 解析请求用户代理信息
|
|
||||||
func UaOsBrowser(c *gin.Context) (string, string) {
|
|
||||||
userAgent := c.GetHeader("user-agent")
|
|
||||||
uaInfo := ua.Info(userAgent)
|
|
||||||
|
|
||||||
browser := "app.common.noUaOsBrowser"
|
|
||||||
bName, bVersion := uaInfo.Browser()
|
|
||||||
if bName != "" && bVersion != "" {
|
|
||||||
browser = bName + " " + bVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
os := "app.common.noUaOsBrowser"
|
|
||||||
bos := uaInfo.OS()
|
|
||||||
if bos != "" {
|
|
||||||
os = bos
|
|
||||||
}
|
|
||||||
return os, browser
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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]
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginUser 登录用户信息
|
|
||||||
func LoginUser(c *gin.Context) (vo.LoginUser, error) {
|
|
||||||
value, exists := c.Get(common.CTX_LOGIN_USER)
|
|
||||||
if exists {
|
|
||||||
return value.(vo.LoginUser), nil
|
|
||||||
}
|
|
||||||
// 登录用户信息无效
|
|
||||||
return vo.LoginUser{}, fmt.Errorf("app.common.noLoginUser")
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginUserToUserID 登录用户信息-用户ID
|
|
||||||
func LoginUserToUserID(c *gin.Context) string {
|
|
||||||
value, exists := c.Get(common.CTX_LOGIN_USER)
|
|
||||||
if exists {
|
|
||||||
loginUser := value.(vo.LoginUser)
|
|
||||||
return loginUser.UserID
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginUserToUserName 登录用户信息-用户名称
|
|
||||||
func LoginUserToUserName(c *gin.Context) string {
|
|
||||||
value, exists := c.Get(common.CTX_LOGIN_USER)
|
|
||||||
if exists {
|
|
||||||
loginUser := value.(vo.LoginUser)
|
|
||||||
return loginUser.User.UserName
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginUserToDataScopeSQL 登录用户信息-角色数据范围过滤SQL字符串
|
|
||||||
func LoginUserToDataScopeSQL(c *gin.Context, deptAlias string, userAlias string) string {
|
|
||||||
dataScopeSQL := ""
|
|
||||||
// 登录用户信息
|
|
||||||
loginUser, err := LoginUser(c)
|
|
||||||
if err != nil {
|
|
||||||
return dataScopeSQL
|
|
||||||
}
|
|
||||||
userInfo := loginUser.User
|
|
||||||
|
|
||||||
// 如果是管理员,则不过滤数据
|
|
||||||
if config.IsAdmin(userInfo.UserID) {
|
|
||||||
return dataScopeSQL
|
|
||||||
}
|
|
||||||
// 无用户角色
|
|
||||||
if len(userInfo.Roles) <= 0 {
|
|
||||||
return dataScopeSQL
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录角色权限范围定义添加过, 非自定数据权限不需要重复拼接SQL
|
|
||||||
var scopeKeys []string
|
|
||||||
var conditions []string
|
|
||||||
for _, role := range userInfo.Roles {
|
|
||||||
dataScope := role.DataScope
|
|
||||||
|
|
||||||
if roledatascope.ALL == dataScope {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if roledatascope.CUSTOM != dataScope {
|
|
||||||
hasKey := false
|
|
||||||
for _, key := range scopeKeys {
|
|
||||||
if key == dataScope {
|
|
||||||
hasKey = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if hasKey {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if roledatascope.CUSTOM == dataScope {
|
|
||||||
sql := fmt.Sprintf(`%s.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = '%s' )`, deptAlias, role.RoleID)
|
|
||||||
conditions = append(conditions, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
if roledatascope.DEPT == dataScope {
|
|
||||||
sql := fmt.Sprintf(`%s.dept_id = '%s'`, deptAlias, userInfo.DeptID)
|
|
||||||
conditions = append(conditions, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
if roledatascope.DEPT_AND_CHILD == dataScope {
|
|
||||||
sql := fmt.Sprintf(`%s.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = '%s' or find_in_set('%s' , ancestors ) )`, deptAlias, userInfo.DeptID, userInfo.DeptID)
|
|
||||||
conditions = append(conditions, sql)
|
|
||||||
}
|
|
||||||
|
|
||||||
if roledatascope.SELF == dataScope {
|
|
||||||
// 数据权限为仅本人且没有userAlias别名不查询任何数据
|
|
||||||
if userAlias == "" {
|
|
||||||
sql := fmt.Sprintf(`%s.parent_id = '0'`, deptAlias)
|
|
||||||
conditions = append(conditions, sql)
|
|
||||||
} else {
|
|
||||||
sql := fmt.Sprintf(`%s.user_id = '%s'`, userAlias, userInfo.UserID)
|
|
||||||
conditions = append(conditions, sql)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录角色范围
|
|
||||||
scopeKeys = append(scopeKeys, dataScope)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建查询条件语句
|
|
||||||
if len(conditions) > 0 {
|
|
||||||
dataScopeSQL = fmt.Sprintf(" AND ( %s ) ", strings.Join(conditions, " OR "))
|
|
||||||
}
|
|
||||||
return dataScopeSQL
|
|
||||||
}
|
|
||||||
@@ -27,6 +27,12 @@ const (
|
|||||||
//
|
//
|
||||||
// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss
|
// formatStr 时间格式 默认YYYY-MM-DD HH:mm:ss
|
||||||
func ParseStrToDate(dateStr, formatStr string) time.Time {
|
func ParseStrToDate(dateStr, formatStr string) time.Time {
|
||||||
|
if dateStr == "" || dateStr == "<nil>" {
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
if formatStr == "" {
|
||||||
|
formatStr = YYYY_MM_DD_HH_MM_SS
|
||||||
|
}
|
||||||
t, err := time.Parse(formatStr, dateStr)
|
t, err := time.Parse(formatStr, dateStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("utils ParseStrToDate err %v", err)
|
logger.Infof("utils ParseStrToDate err %v", err)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"be.ems/src/framework/constants/uploadsubpath"
|
"be.ems/src/framework/constants"
|
||||||
"be.ems/src/framework/logger"
|
"be.ems/src/framework/logger"
|
||||||
"be.ems/src/framework/utils/date"
|
"be.ems/src/framework/utils/date"
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@ func TransferExeclUploadFile(file *multipart.FileHeader) (string, error) {
|
|||||||
// 上传资源路径
|
// 上传资源路径
|
||||||
_, dir := resourceUpload()
|
_, dir := resourceUpload()
|
||||||
// 新文件名称并组装文件地址
|
// 新文件名称并组装文件地址
|
||||||
filePath := filepath.Join(uploadsubpath.IMPORT, date.ParseDatePath(time.Now()))
|
filePath := filepath.Join(constants.UPLOAD_IMPORT, date.ParseDatePath(time.Now()))
|
||||||
fileName := generateFileName(file.Filename)
|
fileName := generateFileName(file.Filename)
|
||||||
writePathFile := filepath.Join(dir, filePath, fileName)
|
writePathFile := filepath.Join(dir, filePath, fileName)
|
||||||
// 存入新文件路径
|
// 存入新文件路径
|
||||||
@@ -138,7 +138,7 @@ func WriteSheet(headerCells map[string]string, dataCells []map[string]any, fileN
|
|||||||
|
|
||||||
// 上传资源路径
|
// 上传资源路径
|
||||||
_, dir := resourceUpload()
|
_, dir := resourceUpload()
|
||||||
filePath := filepath.Join(uploadsubpath.EXPORT, date.ParseDatePath(time.Now()))
|
filePath := filepath.Join(constants.UPLOAD_EXPORT, date.ParseDatePath(time.Now()))
|
||||||
saveFilePath := filepath.Join(dir, filePath, fileName)
|
saveFilePath := filepath.Join(dir, filePath, fileName)
|
||||||
|
|
||||||
// 创建文件目录
|
// 创建文件目录
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"be.ems/src/framework/config"
|
"be.ems/src/framework/config"
|
||||||
"be.ems/src/framework/constants/uploadsubpath"
|
"be.ems/src/framework/constants"
|
||||||
"be.ems/src/framework/logger"
|
"be.ems/src/framework/logger"
|
||||||
"be.ems/src/framework/utils/date"
|
"be.ems/src/framework/utils/date"
|
||||||
"be.ems/src/framework/utils/generate"
|
"be.ems/src/framework/utils/generate"
|
||||||
@@ -237,7 +237,7 @@ func TransferChunkUploadFile(file *multipart.FileHeader, index, identifier strin
|
|||||||
// 上传资源路径
|
// 上传资源路径
|
||||||
prefix, dir := resourceUpload()
|
prefix, dir := resourceUpload()
|
||||||
// 新文件名称并组装文件地址
|
// 新文件名称并组装文件地址
|
||||||
filePath := filepath.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
|
filePath := filepath.Join(constants.UPLOAD_CHUNK, date.ParseDatePath(time.Now()), identifier)
|
||||||
writePathFile := filepath.Join(dir, filePath, index)
|
writePathFile := filepath.Join(dir, filePath, index)
|
||||||
// 存入新文件路径
|
// 存入新文件路径
|
||||||
err = transferToNewFile(file, writePathFile)
|
err = transferToNewFile(file, writePathFile)
|
||||||
@@ -261,7 +261,7 @@ func ChunkCheckFile(identifier, originalFileName string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
// 上传资源路径
|
// 上传资源路径
|
||||||
_, dir := resourceUpload()
|
_, dir := resourceUpload()
|
||||||
dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
|
dirPath := path.Join(constants.UPLOAD_CHUNK, date.ParseDatePath(time.Now()), identifier)
|
||||||
readPath := path.Join(dir, dirPath)
|
readPath := path.Join(dir, dirPath)
|
||||||
fileList, err := getDirFileNameList(readPath)
|
fileList, err := getDirFileNameList(readPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -286,7 +286,7 @@ func ChunkMergeFile(identifier, originalFileName, subPath string) (string, error
|
|||||||
// 上传资源路径
|
// 上传资源路径
|
||||||
prefix, dir := resourceUpload()
|
prefix, dir := resourceUpload()
|
||||||
// 切片存放目录
|
// 切片存放目录
|
||||||
dirPath := path.Join(uploadsubpath.CHUNK, date.ParseDatePath(time.Now()), identifier)
|
dirPath := path.Join(constants.UPLOAD_CHUNK, date.ParseDatePath(time.Now()), identifier)
|
||||||
readPath := path.Join(dir, dirPath)
|
readPath := path.Join(dir, dirPath)
|
||||||
// 组合存放文件路径
|
// 组合存放文件路径
|
||||||
fileName := generateFileName(originalFileName)
|
fileName := generateFileName(originalFileName)
|
||||||
@@ -305,7 +305,7 @@ func ChunkMergeFile(identifier, originalFileName, subPath string) (string, error
|
|||||||
// filePath 上传得到的文件路径 /upload....
|
// filePath 上传得到的文件路径 /upload....
|
||||||
// dst 新文件路径 /a/xx.pdf
|
// dst 新文件路径 /a/xx.pdf
|
||||||
func CopyUploadFile(filePath, dst string) error {
|
func CopyUploadFile(filePath, dst string) error {
|
||||||
srcPath := ParseUploadFilePath(filePath)
|
srcPath := ParseUploadFileAbsPath(filePath)
|
||||||
src, err := os.Open(srcPath)
|
src, err := os.Open(srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -346,10 +346,10 @@ func ParseUploadFileDir(subPath string) string {
|
|||||||
return filepath.Join(dir, filePath)
|
return filepath.Join(dir, filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseUploadFilePath 上传资源本地绝对资源路径
|
// ParseUploadFileAbsPath 上传资源本地绝对资源路径
|
||||||
//
|
//
|
||||||
// filePath 上传文件路径
|
// filePath 上传文件路径
|
||||||
func ParseUploadFilePath(filePath string) string {
|
func ParseUploadFileAbsPath(filePath string) string {
|
||||||
prefix, dir := resourceUpload()
|
prefix, dir := resourceUpload()
|
||||||
absPath := strings.Replace(filePath, prefix, dir, 1)
|
absPath := strings.Replace(filePath, prefix, dir, 1)
|
||||||
return filepath.ToSlash(absPath)
|
return filepath.ToSlash(absPath)
|
||||||
|
|||||||
Binary file not shown.
@@ -9,7 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"be.ems/src/framework/config"
|
"be.ems/src/framework/config"
|
||||||
"be.ems/src/framework/constants/common"
|
"be.ems/src/framework/constants"
|
||||||
"be.ems/src/framework/logger"
|
"be.ems/src/framework/logger"
|
||||||
"be.ems/src/framework/utils/cmd"
|
"be.ems/src/framework/utils/cmd"
|
||||||
"be.ems/src/framework/utils/crypto"
|
"be.ems/src/framework/utils/crypto"
|
||||||
@@ -106,8 +106,8 @@ func Launch() {
|
|||||||
"code": Code, // 机器码
|
"code": Code, // 机器码
|
||||||
"useTime": time.Now().UnixMilli(), // 首次使用时间
|
"useTime": time.Now().UnixMilli(), // 首次使用时间
|
||||||
|
|
||||||
common.LAUNCH_BOOTLOADER: true, // 启动引导
|
constants.LAUNCH_BOOTLOADER: true, // 启动引导
|
||||||
common.LAUNCH_BOOTLOADER + "Time": 0, // 引导完成时间
|
constants.LAUNCH_BOOTLOADER + "Time": 0, // 引导完成时间
|
||||||
}
|
}
|
||||||
codeFileWrite(LaunchInfo)
|
codeFileWrite(LaunchInfo)
|
||||||
} else {
|
} else {
|
||||||
@@ -151,8 +151,8 @@ func SetLaunchInfo(info map[string]any) error {
|
|||||||
// Bootloader 启动引导标记
|
// Bootloader 启动引导标记
|
||||||
func Bootloader(flag bool) error {
|
func Bootloader(flag bool) error {
|
||||||
return SetLaunchInfo(map[string]any{
|
return SetLaunchInfo(map[string]any{
|
||||||
common.LAUNCH_BOOTLOADER: flag, // 启动引导 true开 false关
|
constants.LAUNCH_BOOTLOADER: flag, // 启动引导 true开 false关
|
||||||
common.LAUNCH_BOOTLOADER + "Time": time.Now().UnixMilli(), // 引导完成时间
|
constants.LAUNCH_BOOTLOADER + "Time": time.Now().UnixMilli(), // 引导完成时间
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,31 +91,16 @@ func ConvertToCamelCase(str string) string {
|
|||||||
return strings.Join(words, "")
|
return strings.Join(words, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bit 比特位为单位
|
// Bit 比特位为单位 1023.00 B --> 1.00 KB
|
||||||
func Bit(bit float64) string {
|
func Bit(bit float64) string {
|
||||||
var GB, MB, KB string
|
units := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||||
|
for i := 0; i < len(units); i++ {
|
||||||
if bit > float64(1<<30) {
|
if bit < 1024 || i == len(units)-1 {
|
||||||
GB = fmt.Sprintf("%0.2f", bit/(1<<30))
|
return fmt.Sprintf("%.2f %s", bit, units[i])
|
||||||
}
|
}
|
||||||
|
bit /= 1024
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// CronExpression 解析 Cron 表达式,返回下一次执行的时间戳(毫秒)
|
// CronExpression 解析 Cron 表达式,返回下一次执行的时间戳(毫秒)
|
||||||
@@ -146,11 +131,11 @@ func SafeContent(value string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RemoveDuplicates 数组内字符串去重
|
// RemoveDuplicates 数组内字符串去重
|
||||||
func RemoveDuplicates(ids []string) []string {
|
func RemoveDuplicates(arr []string) []string {
|
||||||
uniqueIDs := make(map[string]bool)
|
uniqueIDs := make(map[string]bool)
|
||||||
uniqueIDSlice := make([]string, 0)
|
uniqueIDSlice := make([]string, 0)
|
||||||
|
|
||||||
for _, id := range ids {
|
for _, id := range arr {
|
||||||
_, ok := uniqueIDs[id]
|
_, ok := uniqueIDs[id]
|
||||||
if !ok && id != "" {
|
if !ok && id != "" {
|
||||||
uniqueIDs[id] = true
|
uniqueIDs[id] = true
|
||||||
@@ -161,6 +146,29 @@ func RemoveDuplicates(ids []string) []string {
|
|||||||
return uniqueIDSlice
|
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
|
// Color 解析颜色 #fafafa
|
||||||
func Color(colorStr string) *color.RGBA {
|
func Color(colorStr string) *color.RGBA {
|
||||||
// 去除 # 号
|
// 去除 # 号
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
package repo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"be.ems/src/framework/utils/parse"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PageNumSize 分页页码记录数
|
|
||||||
func PageNumSize(pageNum, pageSize any) (int64, int64) {
|
|
||||||
// 记录起始索引
|
|
||||||
num := parse.Number(pageNum)
|
|
||||||
if num < 1 {
|
|
||||||
num = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 显示记录数
|
|
||||||
size := parse.Number(pageSize)
|
|
||||||
if size < 1 {
|
|
||||||
size = 10
|
|
||||||
}
|
|
||||||
return num - 1, size
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFieldValue 判断结构体内是否存在指定字段并设置值
|
|
||||||
func SetFieldValue(obj any, fieldName string, value any) {
|
|
||||||
// 获取结构体的反射值
|
|
||||||
userValue := reflect.ValueOf(obj)
|
|
||||||
|
|
||||||
// 获取字段的反射值
|
|
||||||
fieldValue := userValue.Elem().FieldByName(fieldName)
|
|
||||||
|
|
||||||
// 检查字段是否存在
|
|
||||||
if fieldValue.IsValid() && fieldValue.CanSet() {
|
|
||||||
// 获取字段的类型
|
|
||||||
fieldType := fieldValue.Type()
|
|
||||||
|
|
||||||
// 转换传入的值类型为字段类型
|
|
||||||
switch fieldType.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
if value == nil {
|
|
||||||
fieldValue.SetString("")
|
|
||||||
} else {
|
|
||||||
fieldValue.SetString(fmt.Sprintf("%v", value))
|
|
||||||
}
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
intValue, err := strconv.ParseInt(fmt.Sprintf("%v", value), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
intValue = 0
|
|
||||||
}
|
|
||||||
fieldValue.SetInt(intValue)
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
uintValue, err := strconv.ParseUint(fmt.Sprintf("%v", value), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
uintValue = 0
|
|
||||||
}
|
|
||||||
fieldValue.SetUint(uintValue)
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
floatValue, err := strconv.ParseFloat(fmt.Sprintf("%v", value), 64)
|
|
||||||
if err != nil {
|
|
||||||
floatValue = 0
|
|
||||||
}
|
|
||||||
fieldValue.SetFloat(floatValue)
|
|
||||||
case reflect.Struct:
|
|
||||||
fmt.Printf("%s time resolution %s %v \n", fieldName, fieldValue.Type(), value)
|
|
||||||
if fieldValue.Type() == reflect.TypeOf(time.Time{}) && value != nil {
|
|
||||||
// 解析 value 并转换为 time.Time 类型
|
|
||||||
parsedTime, err := time.Parse("2006-01-02 15:04:05 +0800 CST", fmt.Sprintf("%v", value))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Time resolution error:", err)
|
|
||||||
} else {
|
|
||||||
// 设置字段的值
|
|
||||||
fieldValue.Set(reflect.ValueOf(parsedTime))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
// 设置字段的值
|
|
||||||
fieldValue.Set(reflect.ValueOf(value).Convert(fieldValue.Type()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConvertIdsSlice 将 []string 转换为 []any
|
|
||||||
func ConvertIdsSlice(ids []string) []any {
|
|
||||||
// 将 []string 转换为 []any
|
|
||||||
arr := make([]any, len(ids))
|
|
||||||
for i, v := range ids {
|
|
||||||
arr[i] = v
|
|
||||||
}
|
|
||||||
return arr
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询-参数值的占位符
|
|
||||||
func KeyPlaceholderByQuery(sum int) string {
|
|
||||||
placeholders := make([]string, sum)
|
|
||||||
for i := 0; i < sum; i++ {
|
|
||||||
placeholders[i] = "?"
|
|
||||||
}
|
|
||||||
return strings.Join(placeholders, ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 插入-参数映射键值占位符 keys, placeholder, values
|
|
||||||
func KeyPlaceholderValueByInsert(params map[string]any) ([]string, string, []any) {
|
|
||||||
// 参数映射的键
|
|
||||||
keys := make([]string, len(params))
|
|
||||||
// 参数映射的值
|
|
||||||
values := make([]any, len(params))
|
|
||||||
sum := 0
|
|
||||||
for k, v := range params {
|
|
||||||
keys[sum] = k
|
|
||||||
values[sum] = v
|
|
||||||
sum++
|
|
||||||
}
|
|
||||||
// 参数值的占位符
|
|
||||||
placeholders := make([]string, sum)
|
|
||||||
for i := 0; i < sum; i++ {
|
|
||||||
placeholders[i] = "?"
|
|
||||||
}
|
|
||||||
return keys, strings.Join(placeholders, ","), values
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新-参数映射键值占位符 keys, values
|
|
||||||
func KeyValueByUpdate(params map[string]any) ([]string, []any) {
|
|
||||||
// 参数映射的键
|
|
||||||
keys := make([]string, len(params))
|
|
||||||
// 参数映射的值
|
|
||||||
values := make([]any, len(params))
|
|
||||||
sum := 0
|
|
||||||
for k, v := range params {
|
|
||||||
keys[sum] = k + "=?"
|
|
||||||
values[sum] = v
|
|
||||||
sum++
|
|
||||||
}
|
|
||||||
return keys, values
|
|
||||||
}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
package token
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"be.ems/src/framework/config"
|
|
||||||
cachekeyConstants "be.ems/src/framework/constants/cachekey"
|
|
||||||
tokenConstants "be.ems/src/framework/constants/token"
|
|
||||||
"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"
|
|
||||||
|
|
||||||
jwt "github.com/golang-jwt/jwt/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Remove 清除登录用户信息UUID
|
|
||||||
func Remove(tokenStr string) string {
|
|
||||||
claims, err := Verify(tokenStr)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("token verify err %v", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
// 清除缓存KEY
|
|
||||||
uuid := claims[tokenConstants.JWT_UUID].(string)
|
|
||||||
tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + uuid
|
|
||||||
hasKey, _ := redisCahe.Has("", tokenKey)
|
|
||||||
if hasKey {
|
|
||||||
redisCahe.Del("", tokenKey)
|
|
||||||
}
|
|
||||||
return claims[tokenConstants.JWT_NAME].(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create 令牌生成
|
|
||||||
func Create(loginUser *vo.LoginUser, ilobArgs ...string) string {
|
|
||||||
// 生成用户唯一tokne16位
|
|
||||||
loginUser.UUID = generate.Code(16)
|
|
||||||
|
|
||||||
// 设置请求用户登录客户端
|
|
||||||
loginUser.IPAddr = ilobArgs[0]
|
|
||||||
loginUser.LoginLocation = ilobArgs[1]
|
|
||||||
loginUser.OS = ilobArgs[2]
|
|
||||||
loginUser.Browser = ilobArgs[3]
|
|
||||||
|
|
||||||
// 设置用户令牌有效期并存入缓存
|
|
||||||
Cache(loginUser)
|
|
||||||
|
|
||||||
// 设置登录IP和登录时间
|
|
||||||
loginUser.User.LoginIP = loginUser.IPAddr
|
|
||||||
loginUser.User.LoginDate = loginUser.LoginTime
|
|
||||||
|
|
||||||
// 令牌算法 HS256 HS384 HS512
|
|
||||||
algorithm := config.Get("jwt.algorithm").(string)
|
|
||||||
var method *jwt.SigningMethodHMAC
|
|
||||||
switch algorithm {
|
|
||||||
case "HS512":
|
|
||||||
method = jwt.SigningMethodHS512
|
|
||||||
case "HS384":
|
|
||||||
method = jwt.SigningMethodHS384
|
|
||||||
case "HS256":
|
|
||||||
default:
|
|
||||||
method = jwt.SigningMethodHS256
|
|
||||||
}
|
|
||||||
// 生成令牌负荷绑定uuid标识
|
|
||||||
jwtToken := jwt.NewWithClaims(method, jwt.MapClaims{
|
|
||||||
tokenConstants.JWT_UUID: loginUser.UUID,
|
|
||||||
tokenConstants.JWT_KEY: loginUser.UserID,
|
|
||||||
tokenConstants.JWT_NAME: loginUser.User.UserName,
|
|
||||||
"exp": loginUser.ExpireTime,
|
|
||||||
"ait": loginUser.LoginTime,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 生成令牌设置密钥
|
|
||||||
secret := config.Get("jwt.secret").(string)
|
|
||||||
tokenStr, err := jwtToken.SignedString([]byte(machine.Code + "@" + secret))
|
|
||||||
if err != nil {
|
|
||||||
logger.Infof("jwt sign err : %v", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return tokenStr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cache 缓存登录用户信息
|
|
||||||
func Cache(loginUser *vo.LoginUser) {
|
|
||||||
// 计算配置的有效期
|
|
||||||
expTime := config.Get("jwt.expiresIn").(int)
|
|
||||||
expTimestamp := time.Duration(expTime) * time.Minute
|
|
||||||
iatTimestamp := time.Now().UnixMilli()
|
|
||||||
loginUser.LoginTime = iatTimestamp
|
|
||||||
loginUser.ExpireTime = iatTimestamp + expTimestamp.Milliseconds()
|
|
||||||
// 根据登录标识将loginUser缓存
|
|
||||||
tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + loginUser.UUID
|
|
||||||
jsonBytes, err := json.Marshal(loginUser)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
redisCahe.SetByExpire("", tokenKey, string(jsonBytes), expTimestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RefreshIn 验证令牌有效期,相差不足xx分钟,自动刷新缓存
|
|
||||||
func RefreshIn(loginUser *vo.LoginUser) {
|
|
||||||
// 相差不足xx分钟,自动刷新缓存
|
|
||||||
refreshTime := config.Get("jwt.refreshIn").(int)
|
|
||||||
refreshTimestamp := time.Duration(refreshTime) * time.Minute
|
|
||||||
// 过期时间
|
|
||||||
expireTimestamp := loginUser.ExpireTime
|
|
||||||
currentTimestamp := time.Now().UnixMilli()
|
|
||||||
if expireTimestamp-currentTimestamp <= refreshTimestamp.Milliseconds() {
|
|
||||||
Cache(loginUser)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify 校验令牌是否有效
|
|
||||||
func Verify(tokenString string) (jwt.MapClaims, error) {
|
|
||||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
|
|
||||||
// 判断加密算法是预期的加密算法
|
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
|
|
||||||
secret := config.Get("jwt.secret").(string)
|
|
||||||
return []byte(machine.Code + "@" + secret), nil
|
|
||||||
}
|
|
||||||
return nil, jwt.ErrSignatureInvalid
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("token String Verify : %v", err)
|
|
||||||
// 无效身份授权
|
|
||||||
return nil, fmt.Errorf("invalid identity authorization")
|
|
||||||
}
|
|
||||||
// 如果解析负荷成功并通过签名校验
|
|
||||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
|
||||||
return claims, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("token valid error")
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoginUser 缓存的登录用户信息
|
|
||||||
func LoginUser(claims jwt.MapClaims) vo.LoginUser {
|
|
||||||
uuid := claims[tokenConstants.JWT_UUID].(string)
|
|
||||||
tokenKey := cachekeyConstants.LOGIN_TOKEN_KEY + uuid
|
|
||||||
hasKey, _ := redisCahe.Has("", tokenKey)
|
|
||||||
var loginUser vo.LoginUser
|
|
||||||
if hasKey {
|
|
||||||
loginUserStr, _ := redisCahe.Get("", tokenKey)
|
|
||||||
if loginUserStr == "" {
|
|
||||||
return loginUser
|
|
||||||
}
|
|
||||||
err := json.Unmarshal([]byte(loginUserStr), &loginUser)
|
|
||||||
if err != nil {
|
|
||||||
logger.Errorf("loginuser info json err : %v", err)
|
|
||||||
return loginUser
|
|
||||||
}
|
|
||||||
return loginUser
|
|
||||||
}
|
|
||||||
return loginUser
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package vo
|
|
||||||
|
|
||||||
import systemModel "be.ems/src/modules/system/model"
|
|
||||||
|
|
||||||
// LoginUser 登录用户身份权限信息对象
|
|
||||||
type LoginUser struct {
|
|
||||||
// UserID 用户ID
|
|
||||||
UserID string `json:"userId"`
|
|
||||||
|
|
||||||
// DeptID 部门ID
|
|
||||||
DeptID string `json:"deptId"`
|
|
||||||
|
|
||||||
// UUID 用户唯一标识
|
|
||||||
UUID string `json:"uuid"`
|
|
||||||
|
|
||||||
// LoginTime 登录时间时间戳
|
|
||||||
LoginTime int64 `json:"loginTime"`
|
|
||||||
|
|
||||||
// ExpireTime 过期时间时间戳
|
|
||||||
ExpireTime int64 `json:"expireTime"`
|
|
||||||
|
|
||||||
// IPAddr 登录IP地址 x.x.x.x
|
|
||||||
IPAddr string `json:"ipaddr"`
|
|
||||||
|
|
||||||
// LoginLocation 登录地点 xx xx
|
|
||||||
LoginLocation string `json:"loginLocation"`
|
|
||||||
|
|
||||||
// Browser 浏览器类型
|
|
||||||
Browser string `json:"browser"`
|
|
||||||
|
|
||||||
// OS 操作系统
|
|
||||||
OS string `json:"os"`
|
|
||||||
|
|
||||||
// Permissions 权限列表
|
|
||||||
Permissions []string `json:"permissions"`
|
|
||||||
|
|
||||||
// User 用户信息
|
|
||||||
User systemModel.SysUser `json:"user"`
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package vo
|
|
||||||
|
|
||||||
// Router 路由信息对象
|
|
||||||
type Router struct {
|
|
||||||
// 路由名字 英文首字母大写
|
|
||||||
Name string `json:"name"`
|
|
||||||
// 路由地址
|
|
||||||
Path string `json:"path"`
|
|
||||||
// 其他元素
|
|
||||||
Meta RouterMeta `json:"meta"`
|
|
||||||
// 组件地址
|
|
||||||
Component string `json:"component"`
|
|
||||||
// 重定向地址
|
|
||||||
Redirect string `json:"redirect"`
|
|
||||||
// 子路由
|
|
||||||
Children []Router `json:"children,omitempty"`
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package vo
|
|
||||||
|
|
||||||
// RouterMeta 路由元信息对象
|
|
||||||
type RouterMeta struct {
|
|
||||||
// 设置该菜单在侧边栏和面包屑中展示的名字
|
|
||||||
Title string `json:"title"`
|
|
||||||
// 设置该菜单的图标
|
|
||||||
Icon string `json:"icon"`
|
|
||||||
// 设置为true,则不会被 <keep-alive>缓存
|
|
||||||
Cache bool `json:"cache"`
|
|
||||||
// 内链地址(http(s)://开头), 打开目标位置 '_blank' | '_self' | ''
|
|
||||||
Target string `json:"target"`
|
|
||||||
// 在菜单中隐藏子节点
|
|
||||||
HideChildInMenu bool `json:"hideChildInMenu"`
|
|
||||||
// 在菜单中隐藏自己和子节点
|
|
||||||
HideInMenu bool `json:"hideInMenu"`
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package vo
|
|
||||||
|
|
||||||
import systemModel "be.ems/src/modules/system/model"
|
|
||||||
|
|
||||||
// TreeSelect 树结构实体类
|
|
||||||
type TreeSelect struct {
|
|
||||||
// ID 节点ID
|
|
||||||
ID string `json:"id"`
|
|
||||||
|
|
||||||
// Label 节点名称
|
|
||||||
Label string `json:"label"`
|
|
||||||
|
|
||||||
// Children 子节点
|
|
||||||
Children []TreeSelect `json:"children"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SysMenuTreeSelect 使用给定的 SysMenu 对象解析为 TreeSelect 对象
|
|
||||||
func SysMenuTreeSelect(sysMenu systemModel.SysMenu) TreeSelect {
|
|
||||||
t := TreeSelect{}
|
|
||||||
t.ID = sysMenu.MenuID
|
|
||||||
t.Label = sysMenu.MenuName
|
|
||||||
|
|
||||||
if len(sysMenu.Children) > 0 {
|
|
||||||
for _, menu := range sysMenu.Children {
|
|
||||||
child := SysMenuTreeSelect(menu)
|
|
||||||
t.Children = append(t.Children, child)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Children = []TreeSelect{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// SysDeptTreeSelect 使用给定的 SysDept 对象解析为 TreeSelect 对象
|
|
||||||
func SysDeptTreeSelect(sysDept systemModel.SysDept) TreeSelect {
|
|
||||||
t := TreeSelect{}
|
|
||||||
t.ID = sysDept.DeptID
|
|
||||||
t.Label = sysDept.DeptName
|
|
||||||
|
|
||||||
if len(sysDept.Children) > 0 {
|
|
||||||
for _, dept := range sysDept.Children {
|
|
||||||
child := SysDeptTreeSelect(dept)
|
|
||||||
t.Children = append(t.Children, child)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Children = []TreeSelect{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user