refactor: 删除冗余常量文件并整合常量定义
This commit is contained in:
@@ -3,21 +3,21 @@
|
||||
# duration: rotation time with xx hours, example: 1/12/24 hours
|
||||
# count: rotation count of log, default is 30 rotation
|
||||
logger:
|
||||
file: /usr/local/omc/log/restagent.log
|
||||
file: /usr/local/omc/log/restagent.log
|
||||
level: warn
|
||||
duration: 24
|
||||
count: 90
|
||||
count: 90
|
||||
|
||||
# rest agent listen ipv4/v6 and port, support multiple routines
|
||||
# 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
|
||||
rest:
|
||||
- ipv4: 0.0.0.0
|
||||
ipv6:
|
||||
ipv6:
|
||||
port: 33030
|
||||
- ipv4: 0.0.0.0
|
||||
ipv6:
|
||||
ipv6:
|
||||
port: 33443
|
||||
schema: https
|
||||
clientAuthType: 0
|
||||
@@ -28,7 +28,7 @@ rest:
|
||||
webServer:
|
||||
enabled: true
|
||||
rootDir: /usr/local/omc/htdocs/front
|
||||
listen:
|
||||
listen:
|
||||
- addr: :80
|
||||
schema: http
|
||||
- addr: :443
|
||||
@@ -38,15 +38,25 @@ webServer:
|
||||
certFile: /usr/local/omc/etc/certs/omc-server.crt
|
||||
keyFile: /usr/local/omc/etc/certs/omc-server.key
|
||||
|
||||
# data sources
|
||||
database:
|
||||
type: mysql
|
||||
user: root
|
||||
password: 1000omc@kp!
|
||||
host: 127.0.0.1
|
||||
port: 33066
|
||||
name: omc_db
|
||||
connParam: charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=True&interpolateParams=True
|
||||
backup: /usr/local/omc/database
|
||||
dataSource:
|
||||
# Default database instance
|
||||
default:
|
||||
type: "mysql"
|
||||
host: "127.0.0.1"
|
||||
port: 33066
|
||||
username: "root"
|
||||
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:
|
||||
@@ -85,9 +95,9 @@ ne:
|
||||
scpdir: /tmp
|
||||
licensedir: /usr/local/etc/{neType}/license
|
||||
# backup etc list of IMS, does not contain spaces
|
||||
etcListIMS: '{*.yaml,mmtel,vars.cfg}'
|
||||
etcListDefault: '{*.yaml,*.conf,*.cfg}'
|
||||
# true/false to overwrite config file when dpkg ne software
|
||||
etcListIMS: "{*.yaml,mmtel,vars.cfg}"
|
||||
etcListDefault: "{*.yaml,*.conf,*.cfg}"
|
||||
# true/false to overwrite config file when dpkg ne software
|
||||
dpkgOverwrite: false
|
||||
# dpkg timeout (second)
|
||||
dpkgTimeout: 180
|
||||
@@ -115,7 +125,7 @@ omc:
|
||||
frontTraceDir: /usr/local/omc/htdocs/front/trace
|
||||
software: /usr/local/omc/software
|
||||
license: /usr/local/omc/license
|
||||
gtpUri: gtp:192.168.2.119:32152
|
||||
gtpUri: gtp:192.168.2.119:32152
|
||||
checkContentType: false
|
||||
testMode: false
|
||||
rbacMode: true
|
||||
@@ -126,21 +136,21 @@ omc:
|
||||
# Forward interface:
|
||||
# TLS Skip verify: true/false
|
||||
# email/sms
|
||||
# smProxy: sms(Short Message Service)/smsc(SMS Centre)
|
||||
# dataCoding: 0:GSM7BIT, 1:ASCII, 2:BINARY8BIT1, 3:LATIN1,
|
||||
# smProxy: sms(Short Message Service)/smsc(SMS Centre)
|
||||
# dataCoding: 0:GSM7BIT, 1:ASCII, 2:BINARY8BIT1, 3:LATIN1,
|
||||
# 4:BINARY8BIT2, 6:CYRILLIC, 7:HEBREW, 8:UCS2
|
||||
alarm:
|
||||
alarm:
|
||||
alarmEmailForward:
|
||||
enable: true
|
||||
emailList:
|
||||
emailList:
|
||||
smtp: mail.smtp.com
|
||||
port: 25
|
||||
user: smtpext@smtp.com
|
||||
password: "1000smtp@omc!"
|
||||
tlsSkipVerify: true
|
||||
tlsSkipVerify: true
|
||||
alarmSMSForward:
|
||||
enable: true
|
||||
mobileList:
|
||||
mobileList:
|
||||
smscAddr: "192.168.13.114:2775"
|
||||
systemID: "omc"
|
||||
password: "omc123"
|
||||
@@ -154,7 +164,7 @@ alarm:
|
||||
signName: xxx SMSC
|
||||
templateCode: 1000
|
||||
smProxy: smsc
|
||||
|
||||
|
||||
# User authorized information
|
||||
# crypt: mysql/md5/bcrypt
|
||||
# token: true/false to check accessToken
|
||||
@@ -169,7 +179,7 @@ auth:
|
||||
publicKey: /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
|
||||
# alarmid_maxnum: the max number of AlarmID, default: 50
|
||||
# pmid_maxnum: the max number of pmID, default: 50
|
||||
@@ -186,4 +196,4 @@ params:
|
||||
|
||||
testConfig:
|
||||
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"
|
||||
)
|
||||
|
||||
var cfg *viper.Viper
|
||||
var conf *viper.Viper
|
||||
|
||||
// 初始化程序配置
|
||||
func InitConfig(configDir, assetsDir embed.FS) {
|
||||
cfg = viper.New()
|
||||
func InitConfig(configDir *embed.FS) {
|
||||
conf = viper.New()
|
||||
initFlag()
|
||||
initViper(configDir, assetsDir)
|
||||
initViper(configDir)
|
||||
}
|
||||
|
||||
// 指定参数绑定
|
||||
@@ -50,63 +50,50 @@ func initFlag() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg.BindPFlags(pflag.CommandLine)
|
||||
conf.BindPFlags(pflag.CommandLine)
|
||||
}
|
||||
|
||||
// 配置文件读取
|
||||
func initViper(configDir, assetsDir embed.FS) {
|
||||
func initViper(configDir *embed.FS) {
|
||||
// 如果配置文件名中没有扩展名,则需要设置Type
|
||||
cfg.SetConfigType("yaml")
|
||||
|
||||
// 从 embed.FS 中读取默认配置文件内容
|
||||
configDefault, err := configDir.ReadFile("config/config.default.yaml")
|
||||
conf.SetConfigType("yaml")
|
||||
// 读取默认配置文件
|
||||
configDefaultByte, err := configDir.ReadFile("config/config.default.yaml")
|
||||
if err != nil {
|
||||
log.Fatalf("ReadFile config default file: %s", err)
|
||||
log.Fatalf("config default file read error: %s", err)
|
||||
return
|
||||
}
|
||||
// 设置默认配置文件内容到 viper
|
||||
err = cfg.ReadConfig(bytes.NewReader(configDefault))
|
||||
if err != nil {
|
||||
log.Fatalf("NewReader config default file: %s", err)
|
||||
if err = conf.ReadConfig(bytes.NewReader(configDefaultByte)); err != nil {
|
||||
log.Fatalf("config default file read error: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 加载运行环境配置
|
||||
env := cfg.GetString("env")
|
||||
if env != "local" && env != "prod" {
|
||||
log.Fatalf("fatal error config env for local or prod : %s", env)
|
||||
}
|
||||
log.Printf("Current service environment operation configuration => %s \n", env)
|
||||
// 当期服务环境运行配置 => local
|
||||
env := conf.GetString("env")
|
||||
log.Printf("current service environment operation configuration => %s \n", env)
|
||||
|
||||
// 加载运行配置文件合并相同配置
|
||||
envPath := "config/config.prod.yaml"
|
||||
if env == "local" {
|
||||
envPath = "config/config.local.yaml"
|
||||
}
|
||||
// 从 embed.FS 中读取默认配置文件内容
|
||||
configEnv, err := configDir.ReadFile(envPath)
|
||||
envConfigPath := fmt.Sprintf("config/config.%s.yaml", env)
|
||||
configEnvByte, err := configDir.ReadFile(envConfigPath)
|
||||
if err != nil {
|
||||
log.Fatalf("ReadFile config local file: %s", err)
|
||||
log.Fatalf("config env %s file read error: %s", env, err)
|
||||
return
|
||||
}
|
||||
// 设置默认配置文件内容到 viper
|
||||
err = cfg.MergeConfig(bytes.NewReader(configEnv))
|
||||
if err != nil {
|
||||
log.Fatalf("NewReader config local file: %s", err)
|
||||
if err = conf.MergeConfig(bytes.NewReader(configEnvByte)); err != nil {
|
||||
log.Fatalf("config env %s file read error: %s", env, err)
|
||||
return
|
||||
}
|
||||
|
||||
// 合并外部使用配置
|
||||
configFile := cfg.GetString("config")
|
||||
if configFile != "" {
|
||||
configInMerge(configFile)
|
||||
// 外部文件配置
|
||||
externalConfig := conf.GetString("config")
|
||||
if externalConfig != "" {
|
||||
// readExternalConfig(externalConfig)
|
||||
// 处理旧配置,存在相同的配置项处理
|
||||
configInMerge(externalConfig)
|
||||
}
|
||||
|
||||
// 记录程序开始运行的时间点
|
||||
cfg.Set("runTime", time.Now())
|
||||
|
||||
// 设置程序内全局资源访问
|
||||
cfg.Set("AssetsDir", assetsDir)
|
||||
conf.Set("runTime", time.Now())
|
||||
}
|
||||
|
||||
// 配置文件读取进行内部参数合并
|
||||
@@ -137,54 +124,62 @@ func configInMerge(configFile string) {
|
||||
if key == "testconfig" || key == "logger" {
|
||||
continue
|
||||
}
|
||||
// 数据库配置
|
||||
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)
|
||||
conf.Set(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Env 获取运行服务环境
|
||||
// local prod
|
||||
func Env() string {
|
||||
return cfg.GetString("env")
|
||||
return conf.GetString("env")
|
||||
}
|
||||
|
||||
// RunTime 程序开始运行的时间
|
||||
func RunTime() time.Time {
|
||||
return cfg.GetTime("runTime")
|
||||
return conf.GetTime("runTime")
|
||||
}
|
||||
|
||||
// Get 获取配置信息
|
||||
//
|
||||
// Get("server.proxy")
|
||||
func Get(key string) any {
|
||||
return cfg.Get(key)
|
||||
return conf.Get(key)
|
||||
}
|
||||
|
||||
// GetAssetsDirFS 访问程序内全局资源访问
|
||||
func GetAssetsDirFS() embed.FS {
|
||||
return cfg.Get("AssetsDir").(embed.FS)
|
||||
func GetAssetsDirFS() *embed.FS {
|
||||
return conf.Get("AssetsDir").(*embed.FS)
|
||||
}
|
||||
|
||||
// IsAdmin 用户是否为管理员
|
||||
func IsAdmin(userID string) bool {
|
||||
if userID == "" {
|
||||
// SetAssetsDirFS 设置程序内全局资源访问
|
||||
func SetAssetsDirFS(assetsDir *embed.FS) {
|
||||
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
|
||||
}
|
||||
// 从本地配置获取user信息
|
||||
admins := Get("user.adminList").([]any)
|
||||
for _, s := range admins {
|
||||
if fmt.Sprint(s) == userID {
|
||||
// 从配置中获取系统管理员ID列表
|
||||
arr := Get("user.system").([]any)
|
||||
for _, v := range arr {
|
||||
if fmt.Sprint(v) == fmt.Sprint(userId) {
|
||||
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"
|
||||
"time"
|
||||
|
||||
"be.ems/src/framework/constants/common"
|
||||
"be.ems/src/framework/constants"
|
||||
"be.ems/src/modules/monitor/model"
|
||||
"be.ems/src/modules/monitor/repository"
|
||||
)
|
||||
@@ -39,7 +39,7 @@ func (s cronlog) Error(err error, msg string, keysAndValues ...any) {
|
||||
Data: data,
|
||||
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,
|
||||
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
|
||||
|
||||
// 任务日志不需要记录
|
||||
if sysJob.SaveLog == "" || sysJob.SaveLog == common.STATUS_NO {
|
||||
if sysJob.SaveLog == "" || sysJob.SaveLog == constants.STATUS_NO {
|
||||
return
|
||||
}
|
||||
|
||||
// 结果信息key的Name
|
||||
resultNmae := "failed"
|
||||
if status == common.STATUS_YES {
|
||||
if status == constants.STATUS_YES {
|
||||
resultNmae = "completed"
|
||||
}
|
||||
|
||||
@@ -108,12 +108,12 @@ func (jl *jobLogData) SaveLog(status string) {
|
||||
JobGroup: sysJob.JobGroup,
|
||||
InvokeTarget: sysJob.InvokeTarget,
|
||||
TargetParams: sysJob.TargetParams,
|
||||
Status: status,
|
||||
StatusFlag: status,
|
||||
JobMsg: jobMsg,
|
||||
CostTime: duration.Milliseconds(),
|
||||
}
|
||||
// 插入数据
|
||||
repository.NewSysJobLogImpl.InsertJobLog(sysJobLog)
|
||||
repository.NewSysJobLog.Insert(sysJobLog)
|
||||
}
|
||||
|
||||
// JobData 调度任务日志收集结构体,执行任务时传入的接收参数
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package datasource
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -7,33 +7,40 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"be.ems/src/framework/config"
|
||||
"be.ems/src/framework/logger"
|
||||
"be.ems/src/framework/utils/parse"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
gormLog "gorm.io/gorm/logger"
|
||||
|
||||
"be.ems/src/framework/config"
|
||||
"be.ems/src/framework/logger"
|
||||
"be.ems/src/framework/utils/parse"
|
||||
)
|
||||
|
||||
// 数据库连接实例
|
||||
var dbMap = make(map[string]*gorm.DB)
|
||||
|
||||
type dialectInfo struct {
|
||||
dialector gorm.Dialector
|
||||
dialectic gorm.Dialector
|
||||
logging bool
|
||||
}
|
||||
|
||||
// 载入数据库连接
|
||||
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 {
|
||||
item := value.(map[string]any)
|
||||
// 数据库类型对应的数据库连接
|
||||
switch item["type"] {
|
||||
case "sqlite":
|
||||
dsn := fmt.Sprint(item["database"])
|
||||
dialects[key] = dialectInfo{
|
||||
dialectic: sqlite.Open(dsn),
|
||||
logging: item["logging"].(bool),
|
||||
}
|
||||
case "mysql":
|
||||
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
|
||||
item["username"],
|
||||
@@ -43,11 +50,11 @@ func loadDialect() map[string]dialectInfo {
|
||||
item["database"],
|
||||
)
|
||||
dialects[key] = dialectInfo{
|
||||
dialector: mysql.Open(dsn),
|
||||
dialectic: mysql.Open(dsn),
|
||||
logging: item["logging"].(bool),
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 连接数据库实例
|
||||
// Connect 连接数据库实例
|
||||
func Connect() {
|
||||
// 遍历进行连接数据库实例
|
||||
for key, info := range loadDialect() {
|
||||
@@ -78,7 +85,7 @@ func Connect() {
|
||||
opts.Logger = loadLogger()
|
||||
}
|
||||
// 创建连接
|
||||
db, err := gorm.Open(info.dialector, opts)
|
||||
db, err := gorm.Open(info.dialectic, opts)
|
||||
if err != nil {
|
||||
logger.Fatalf("failed error db connect: %s", err)
|
||||
}
|
||||
@@ -92,12 +99,18 @@ func Connect() {
|
||||
if err != nil {
|
||||
logger.Fatalf("failed error ping database: %v", err)
|
||||
}
|
||||
// SetMaxIdleConns 用于设置连接池中空闲连接的最大数量。
|
||||
sqlDB.SetMaxIdleConns(10)
|
||||
// SetMaxOpenConns 设置打开数据库连接的最大数量。
|
||||
sqlDB.SetMaxOpenConns(100)
|
||||
// SetConnMaxLifetime 设置了连接可复用的最大时间。
|
||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||
logger.Infof("database %s connection is successful.", key)
|
||||
dbMap[key] = db
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭数据库实例
|
||||
// Close 关闭数据库实例
|
||||
func Close() {
|
||||
for _, db := range dbMap {
|
||||
sqlDB, err := db.DB()
|
||||
@@ -110,36 +123,41 @@ func Close() {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取默认数据源
|
||||
func DefaultDB() *gorm.DB {
|
||||
source := config.Get("gorm.defaultDataSourceName").(string)
|
||||
return dbMap[source]
|
||||
}
|
||||
|
||||
// 获取数据源
|
||||
// DB 获取数据源
|
||||
//
|
||||
// source-数据源
|
||||
func DB(source string) *gorm.DB {
|
||||
// 不指定时获取默认实例
|
||||
if source == "" {
|
||||
source = config.Get("gorm.defaultDataSourceName").(string)
|
||||
}
|
||||
return dbMap[source]
|
||||
}
|
||||
|
||||
// RawDB 原生查询语句
|
||||
func RawDB(source string, sql string, parameters []any) ([]map[string]any, error) {
|
||||
// 数据源
|
||||
db := DefaultDB()
|
||||
if source != "" {
|
||||
db = DB(source)
|
||||
// Names 获取数据源名称列表
|
||||
func Names() []string {
|
||||
var names []string
|
||||
for key := range dbMap {
|
||||
names = append(names, key)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// RawDB 原生语句查询
|
||||
//
|
||||
// source-数据源
|
||||
// sql-预编译的SQL语句
|
||||
// parameters-预编译的SQL语句参数
|
||||
func RawDB(source string, sql string, parameters []any) ([]map[string]any, error) {
|
||||
var rows []map[string]any
|
||||
// 数据源
|
||||
db := DB(source)
|
||||
if db == nil {
|
||||
return rows, fmt.Errorf("not database source")
|
||||
}
|
||||
// 使用正则表达式替换连续的空白字符为单个空格
|
||||
fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ")
|
||||
|
||||
// logger.Infof("sql=> %v", fmtSql)
|
||||
// logger.Infof("parameters=> %v", parameters)
|
||||
|
||||
// 查询结果
|
||||
var rows []map[string]any
|
||||
res := db.Raw(fmtSql, parameters...).Scan(&rows)
|
||||
if res.Error != nil {
|
||||
return nil, res.Error
|
||||
@@ -147,12 +165,16 @@ func RawDB(source string, sql string, parameters []any) ([]map[string]any, error
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// ExecDB 原生执行语句
|
||||
// ExecDB 原生语句执行
|
||||
//
|
||||
// source-数据源
|
||||
// sql-预编译的SQL语句
|
||||
// parameters-预编译的SQL语句参数
|
||||
func ExecDB(source string, sql string, parameters []any) (int64, error) {
|
||||
// 数据源
|
||||
db := DefaultDB()
|
||||
if source != "" {
|
||||
db = DB(source)
|
||||
db := DB(source)
|
||||
if db == nil {
|
||||
return 0, fmt.Errorf("not database source")
|
||||
}
|
||||
// 使用正则表达式替换连续的空白字符为单个空格
|
||||
fmtSql := regexp.MustCompile(`\s+`).ReplaceAllString(sql, " ")
|
||||
@@ -165,6 +187,9 @@ func ExecDB(source string, sql string, parameters []any) (int64, error) {
|
||||
}
|
||||
|
||||
// PageNumSize 分页页码记录数
|
||||
//
|
||||
// pageNum-页码
|
||||
// pageSize-记录数
|
||||
func PageNumSize(pageNum, pageSize any) (int, int) {
|
||||
// 记录起始索引
|
||||
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/logger"
|
||||
"be.ems/src/framework/vo/result"
|
||||
"be.ems/src/framework/resp"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -20,14 +20,14 @@ func ErrorCatch() gin.HandlerFunc {
|
||||
|
||||
// 返回错误响应给客户端
|
||||
if config.Env() == "prod" {
|
||||
c.JSON(500, result.CodeMsg(500, "Internal Server Errors"))
|
||||
c.JSON(500, resp.CodeMsg(500, "Internal Server Errors"))
|
||||
} else {
|
||||
// 通过实现 error 接口的 Error() 方法自定义错误类型进行捕获
|
||||
switch v := err.(type) {
|
||||
case error:
|
||||
c.JSON(500, result.CodeMsg(500, v.Error()))
|
||||
c.JSON(500, resp.CodeMsg(500, v.Error()))
|
||||
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"
|
||||
"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 翻译值查找键 值前缀匹配
|
||||
func TFindKeyPrefix(language, keyPrefix, value string) string {
|
||||
key := value
|
||||
if value == "" {
|
||||
if key == "" {
|
||||
return key
|
||||
}
|
||||
arr, ok := localeMap[language]
|
||||
if !ok || len(arr) == 0 {
|
||||
arr = LoadLocaleData(language)
|
||||
|
||||
langKey := constants.CACHE_I18N + ":" + keyPrefix + "*"
|
||||
prefixKeys, err := redis.GetKeys("", langKey)
|
||||
if err != nil {
|
||||
return key
|
||||
}
|
||||
mkv, err := redis.GetHashBatch("", prefixKeys)
|
||||
if err != nil {
|
||||
return key
|
||||
}
|
||||
|
||||
for _, v := range arr {
|
||||
if strings.HasPrefix(v.Key, keyPrefix) && strings.HasPrefix(v.Value, value) {
|
||||
key = v.Key
|
||||
break
|
||||
for k, m := range mkv {
|
||||
// 跳过-号数据 i18n:menu.system.menu
|
||||
|
||||
if v, ok := m[language]; ok {
|
||||
if strings.HasPrefix(v, value) {
|
||||
key = k[len(constants.CACHE_I18N)+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
// TKey 翻译键
|
||||
// language: zh-中文 en-英文
|
||||
func TKey(language, key string) string {
|
||||
value := key
|
||||
if key == "" {
|
||||
return value
|
||||
}
|
||||
arr, ok := localeMap[language]
|
||||
if !ok || len(arr) == 0 {
|
||||
arr = LoadLocaleData(language)
|
||||
}
|
||||
|
||||
for _, v := range arr {
|
||||
if v.Key == key {
|
||||
value = v.Value
|
||||
break
|
||||
}
|
||||
langKey := constants.CACHE_I18N + ":" + key
|
||||
output, err := redis.GetHash("", langKey, language)
|
||||
if err != nil {
|
||||
return value
|
||||
}
|
||||
return value
|
||||
return output
|
||||
}
|
||||
|
||||
// TTemplate 翻译模板字符串
|
||||
|
||||
@@ -1,39 +1,36 @@
|
||||
package ip2region
|
||||
|
||||
import (
|
||||
"be.ems/src/framework/logger"
|
||||
|
||||
"embed"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"be.ems/src/framework/logger"
|
||||
)
|
||||
|
||||
// 网络地址(内网)
|
||||
const LOCAT_HOST = "127.0.0.1"
|
||||
// LocalHost 网络地址(内网)
|
||||
const LocalHost = "127.0.0.1"
|
||||
|
||||
// 全局查询对象
|
||||
var searcher *Searcher
|
||||
|
||||
//go:embed ip2region.xdb
|
||||
var ip2regionDB embed.FS
|
||||
// InitSearcher 初始化查询对象
|
||||
func InitSearcher(assetsDir *embed.FS) {
|
||||
if searcher != nil {
|
||||
return
|
||||
}
|
||||
|
||||
func init() {
|
||||
// 从 dbPath 加载整个 xdb 到内存
|
||||
buf, err := ip2regionDB.ReadFile("ip2region.xdb")
|
||||
// 从 embed.FS 中读取内嵌文件
|
||||
fileBuff, err := assetsDir.ReadFile("src/assets/ip2region.xdb")
|
||||
if err != nil {
|
||||
logger.Fatalf("failed error load xdb from : %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 用全局的 cBuff 创建完全基于内存的查询对象。
|
||||
base, err := NewWithBuffer(buf)
|
||||
if err != nil {
|
||||
// 用全局的 fileBuff 创建完全基于内存的查询对象。
|
||||
if searcher, err = NewWithBuffer(fileBuff); err != nil {
|
||||
logger.Errorf("failed error create searcher with content: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 赋值到全局查询对象
|
||||
searcher = base
|
||||
}
|
||||
|
||||
// RegionSearchByIp 查询IP所在地
|
||||
@@ -41,14 +38,13 @@ func init() {
|
||||
// 国家|区域|省份|城市|ISP
|
||||
func RegionSearchByIp(ip string) (string, int, int64) {
|
||||
ip = ClientIP(ip)
|
||||
if ip == LOCAT_HOST {
|
||||
// "0|0|0|内网IP|内网IP"
|
||||
if ip == LocalHost {
|
||||
return "0|0|0|app.common.noIPregion|app.common.noIPregion", 0, 0
|
||||
}
|
||||
tStart := time.Now()
|
||||
region, err := searcher.SearchByStr(ip)
|
||||
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 region, 0, time.Since(tStart).Milliseconds()
|
||||
@@ -59,21 +55,21 @@ func RegionSearchByIp(ip string) (string, int, int64) {
|
||||
// 218.4.167.70 江苏省 苏州市
|
||||
func RealAddressByIp(ip string) string {
|
||||
ip = ClientIP(ip)
|
||||
if ip == LOCAT_HOST {
|
||||
return "app.common.noIPregion" // 内网IP
|
||||
if ip == LocalHost {
|
||||
return "app.common.noIPregion"
|
||||
}
|
||||
region, err := searcher.SearchByStr(ip)
|
||||
if err != nil {
|
||||
logger.Errorf("failed to SearchIP(%s): %s\n", ip, err)
|
||||
return "app.common.unknown" // 未知
|
||||
logger.Errorf("failed to RealAddressByIp(%s): %s\n", ip, err)
|
||||
return "app.common.unknown"
|
||||
}
|
||||
parts := strings.Split(region, "|")
|
||||
province := parts[2]
|
||||
city := parts[3]
|
||||
if province == "0" && city == "0" {
|
||||
return "app.common.unknown"
|
||||
}
|
||||
if province == "0" && city != "0" {
|
||||
if city == "内网IP" {
|
||||
return "app.common.noIPregion" // 内网IP
|
||||
}
|
||||
return city
|
||||
}
|
||||
return province + " " + city
|
||||
@@ -86,8 +82,8 @@ func ClientIP(ip string) string {
|
||||
if strings.HasPrefix(ip, "::ffff:") {
|
||||
ip = strings.Replace(ip, "::ffff:", "", 1)
|
||||
}
|
||||
if ip == LOCAT_HOST || ip == "::1" {
|
||||
return LOCAT_HOST
|
||||
if ip == LocalHost || ip == "::1" {
|
||||
return LocalHost
|
||||
}
|
||||
return ip
|
||||
}
|
||||
@@ -172,4 +172,3 @@ func LoadContentFromFile(dbFile string) ([]byte, error) {
|
||||
|
||||
return cBuff, nil
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"be.ems/src/framework/constants/common"
|
||||
tokenConstants "be.ems/src/framework/constants/token"
|
||||
"be.ems/src/framework/constants"
|
||||
"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/vo/result"
|
||||
"be.ems/src/modules/system/model"
|
||||
"be.ems/src/modules/system/service"
|
||||
|
||||
@@ -91,7 +90,7 @@ func OptionNew(title, businessType string) Options {
|
||||
func OperateLog(options Options) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Set("startTime", time.Now())
|
||||
language := ctx.AcceptLanguage(c)
|
||||
language := reqctx.AcceptLanguage(c)
|
||||
|
||||
// 函数名
|
||||
funcName := c.HandlerName()
|
||||
@@ -99,37 +98,31 @@ func OperateLog(options Options) gin.HandlerFunc {
|
||||
funcName = funcName[lastDotIndex+1:]
|
||||
|
||||
// 解析ip地址
|
||||
ipaddr, location := ctx.IPAddrLocation(c)
|
||||
ipaddr, location := reqctx.IPAddrLocation(c)
|
||||
|
||||
// 获取登录用户信息
|
||||
loginUser, err := ctx.LoginUser(c)
|
||||
loginUser, err := reqctx.LoginUser(c)
|
||||
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() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
// 操作日志记录
|
||||
operLog := model.SysLogOperate{
|
||||
Title: options.Title,
|
||||
BusinessType: options.BusinessType,
|
||||
OperatorType: options.OperatorType,
|
||||
Method: funcName,
|
||||
OperURL: c.Request.URL.Path,
|
||||
RequestMethod: c.Request.Method,
|
||||
OperIP: ipaddr,
|
||||
OperLocation: location,
|
||||
OperName: loginUser.User.UserName,
|
||||
DeptName: loginUser.User.Dept.DeptName,
|
||||
}
|
||||
|
||||
if loginUser.User.UserType == "sys" {
|
||||
operLog.OperatorType = OPERATOR_TYPE_MANAGE
|
||||
Title: options.Title,
|
||||
BusinessType: options.BusinessType,
|
||||
OperaMethod: funcName,
|
||||
OperaUrl: c.Request.URL.Path,
|
||||
OperaUrlMethod: c.Request.Method,
|
||||
OperaIp: ipaddr,
|
||||
OperaLocation: location,
|
||||
OperaBy: loginUser.User.UserName,
|
||||
}
|
||||
|
||||
// 是否需要保存request,参数和值
|
||||
if options.IsSaveRequestData {
|
||||
params := ctx.RequestParamsMap(c)
|
||||
params := reqctx.RequestParamsMap(c)
|
||||
// 敏感属性字段进行掩码
|
||||
processSensitiveFields(params)
|
||||
jsonStr, _ := json.Marshal(params)
|
||||
@@ -137,7 +130,7 @@ func OperateLog(options Options) gin.HandlerFunc {
|
||||
if len(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()
|
||||
if status == 200 {
|
||||
operLog.Status = common.STATUS_YES
|
||||
operLog.StatusFlag = constants.STATUS_YES
|
||||
} else {
|
||||
operLog.Status = common.STATUS_NO
|
||||
operLog.StatusFlag = constants.STATUS_NO
|
||||
}
|
||||
|
||||
// 是否需要保存response,参数和值
|
||||
@@ -157,16 +150,16 @@ func OperateLog(options Options) gin.HandlerFunc {
|
||||
contentType := c.Writer.Header().Get("Content-Type")
|
||||
content := contentType + contentDisposition
|
||||
msg := fmt.Sprintf(`{"status":"%d","size":"%d","content-type":"%s"}`, status, c.Writer.Size(), content)
|
||||
operLog.OperMsg = msg
|
||||
operLog.OperaMsg = msg
|
||||
}
|
||||
|
||||
// 日志记录时间
|
||||
duration := time.Since(c.GetTime("startTime"))
|
||||
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",
|
||||
"newPassword",
|
||||
"confirmPassword",
|
||||
tokenConstants.RESPONSE_FIELD,
|
||||
tokenConstants.ACCESS_TOKEN,
|
||||
constants.ACCESS_TOKEN,
|
||||
constants.ACCESS_TOKEN_QUERY,
|
||||
}
|
||||
|
||||
// processSensitiveFields 处理敏感属性字段
|
||||
|
||||
@@ -8,10 +8,11 @@ import (
|
||||
"strings"
|
||||
|
||||
"be.ems/src/framework/config"
|
||||
constResult "be.ems/src/framework/constants/result"
|
||||
"be.ems/src/framework/logger"
|
||||
"be.ems/src/framework/resp"
|
||||
"be.ems/src/framework/utils/crypto"
|
||||
"be.ems/src/framework/utils/parse"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -53,10 +54,7 @@ func CryptoApi(requestDecrypt, responseEncrypt bool) gin.HandlerFunc {
|
||||
|
||||
// 是否存在data字段数据
|
||||
if contentDe == "" {
|
||||
c.JSON(400, map[string]any{
|
||||
"code": constResult.CODE_ERROR,
|
||||
"msg": "decrypt not found field data",
|
||||
})
|
||||
c.JSON(400, resp.ErrMsg("decrypt not found field data"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
@@ -66,10 +64,7 @@ func CryptoApi(requestDecrypt, responseEncrypt bool) gin.HandlerFunc {
|
||||
dataBodyStr, err := crypto.AESDecryptBase64(contentDe, apiKey)
|
||||
if err != nil {
|
||||
logger.Errorf("CryptoApi decrypt err => %v", err)
|
||||
c.JSON(400, map[string]any{
|
||||
"code": constResult.CODE_ERROR,
|
||||
"msg": "decrypted data could not be parsed",
|
||||
})
|
||||
c.JSON(400, resp.ErrMsg("decrypted data could not be parsed"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
@@ -110,19 +105,19 @@ func CryptoApi(requestDecrypt, responseEncrypt bool) gin.HandlerFunc {
|
||||
codeV, codeOk := resBody["code"]
|
||||
dataV, dataOk := resBody["data"]
|
||||
if codeOk && dataOk {
|
||||
if parse.Number(codeV) == constResult.CODE_SUCCESS {
|
||||
if parse.Number(codeV) == resp.CODE_SUCCESS {
|
||||
byteBodyData, _ := json.Marshal(dataV)
|
||||
// 加密-原数据头加入标记16位长度iv终止符
|
||||
apiKey := config.Get("aes.apiKey").(string)
|
||||
contentEn, err := crypto.AESEncryptBase64("=:)"+string(byteBodyData), apiKey)
|
||||
if err != nil {
|
||||
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 {
|
||||
// 响应加密
|
||||
byteBody, _ := json.Marshal(map[string]any{
|
||||
"code": constResult.CODE_ENCRYPT,
|
||||
"msg": constResult.MSG_ENCRYPT,
|
||||
"code": resp.CODE_ENCRYPT,
|
||||
"msg": resp.MSG_ENCRYPT,
|
||||
"data": contentEn,
|
||||
})
|
||||
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"
|
||||
|
||||
"be.ems/src/framework/config"
|
||||
AdminConstants "be.ems/src/framework/constants/admin"
|
||||
commonConstants "be.ems/src/framework/constants/common"
|
||||
"be.ems/src/framework/constants"
|
||||
"be.ems/src/framework/i18n"
|
||||
ctxUtils "be.ems/src/framework/utils/ctx"
|
||||
tokenUtils "be.ems/src/framework/utils/token"
|
||||
"be.ems/src/framework/vo/result"
|
||||
"be.ems/src/framework/reqctx"
|
||||
"be.ems/src/framework/resp"
|
||||
"be.ems/src/framework/token"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -44,17 +43,17 @@ func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
||||
enable = v.(bool)
|
||||
}
|
||||
if !enable {
|
||||
loginUser, _ := ctxUtils.LoginUser(c)
|
||||
loginUser.UserID = "2"
|
||||
loginUser.User.UserID = "2"
|
||||
loginUser, _ := reqctx.LoginUser(c)
|
||||
loginUser.UserId = 2
|
||||
loginUser.User.UserId = 2
|
||||
loginUser.User.UserName = "admin"
|
||||
loginUser.User.NickName = "admin"
|
||||
c.Set(commonConstants.CTX_LOGIN_USER, loginUser)
|
||||
c.Set(constants.CTX_LOGIN_USER, loginUser)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
language := ctxUtils.AcceptLanguage(c)
|
||||
language := reqctx.AcceptLanguage(c)
|
||||
|
||||
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 == "" {
|
||||
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() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
// 验证令牌
|
||||
claims, err := tokenUtils.Verify(tokenStr)
|
||||
claims, err := token.Verify(tokenStr)
|
||||
if err != nil {
|
||||
c.JSON(401, result.CodeMsg(401, err.Error()))
|
||||
c.JSON(401, resp.CodeMsg(401, err.Error()))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
// 获取缓存的用户信息
|
||||
loginUser := tokenUtils.LoginUser(claims)
|
||||
if loginUser.UserID == "" {
|
||||
c.JSON(401, result.CodeMsg(401, i18n.TKey(language, "app.common.err401")))
|
||||
loginUser := token.Info(claims)
|
||||
if loginUser.UserId <= 0 {
|
||||
c.JSON(401, resp.CodeMsg(401, i18n.TKey(language, "app.common.err401")))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
// 检查刷新有效期后存入上下文
|
||||
tokenUtils.RefreshIn(&loginUser)
|
||||
c.Set(commonConstants.CTX_LOGIN_USER, loginUser)
|
||||
token.RefreshIn(&loginUser)
|
||||
c.Set(constants.CTX_LOGIN_USER, loginUser)
|
||||
|
||||
// 登录用户角色权限校验
|
||||
if options != nil {
|
||||
@@ -109,7 +108,7 @@ func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
||||
verifyOk := verifyRolePermission(roles, perms, options)
|
||||
if !verifyOk {
|
||||
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() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
@@ -129,7 +128,7 @@ func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
||||
// options 参数
|
||||
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
|
||||
}
|
||||
opts := make([]bool, 4)
|
||||
|
||||
@@ -5,31 +5,30 @@ import (
|
||||
"strings"
|
||||
"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"
|
||||
|
||||
"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 (
|
||||
// 默认策略全局限流
|
||||
// LIMIT_GLOBAL 默认策略全局限流
|
||||
LIMIT_GLOBAL = 1
|
||||
|
||||
// 根据请求者IP进行限流
|
||||
// LIMIT_IP 根据请求者IP进行限流
|
||||
LIMIT_IP = 2
|
||||
|
||||
// 根据用户ID进行限流
|
||||
// LIMIT_USER 根据用户ID进行限流
|
||||
LIMIT_USER = 3
|
||||
)
|
||||
|
||||
// LimitOption 请求限流参数
|
||||
type LimitOption struct {
|
||||
Time int64 `json:"time"` // 限流时间,单位秒
|
||||
Count int64 `json:"count"` // 限流次数
|
||||
Time int64 `json:"time"` // 限流时间,单位秒 5
|
||||
Count int64 `json:"count"` // 限流次数,单位次 10
|
||||
Type int64 `json:"type"` // 限流条件类型,默认LIMIT_GLOBAL
|
||||
}
|
||||
|
||||
@@ -43,8 +42,6 @@ type LimitOption struct {
|
||||
// 以便获取登录用户信息,无用户信息时默认为 GLOBAL
|
||||
func RateLimit(option LimitOption) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
language := ctx.AcceptLanguage(c)
|
||||
|
||||
// 初始可选参数数据
|
||||
if option.Time < 5 {
|
||||
option.Time = 5
|
||||
@@ -61,40 +58,46 @@ func RateLimit(option LimitOption) gin.HandlerFunc {
|
||||
lastDotIndex := strings.LastIndex(funcName, "/")
|
||||
funcName = funcName[lastDotIndex+1:]
|
||||
// 生成限流key
|
||||
var limitKey string = cachekey.RATE_LIMIT_KEY + funcName
|
||||
limitKey := constants.CACHE_RATE_LIMIT + ":" + funcName
|
||||
|
||||
// 用户
|
||||
if option.Type == LIMIT_USER {
|
||||
loginUser, err := ctx.LoginUser(c)
|
||||
loginUser, err := reqctx.LoginUser(c)
|
||||
if err != nil {
|
||||
c.JSON(401, result.Err(map[string]any{
|
||||
"code": 401,
|
||||
"msg": i18n.TKey(language, err.Error()),
|
||||
}))
|
||||
c.JSON(401, resp.CodeMsg(40003, err.Error()))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
limitKey = cachekey.RATE_LIMIT_KEY + loginUser.UserID + ":" + funcName
|
||||
limitKey = fmt.Sprintf("%s:%d:%s", constants.CACHE_RATE_LIMIT, loginUser.UserId, funcName)
|
||||
}
|
||||
|
||||
// IP
|
||||
if option.Type == LIMIT_IP {
|
||||
clientIP := ip2region.ClientIP(c.ClientIP())
|
||||
limitKey = cachekey.RATE_LIMIT_KEY + clientIP + ":" + funcName
|
||||
limitKey = constants.CACHE_RATE_LIMIT + ":" + clientIP + ":" + funcName
|
||||
}
|
||||
|
||||
// 在Redis查询并记录请求次数
|
||||
rateCount, _ := redis.RateLimit("", limitKey, option.Time, option.Count)
|
||||
rateTime, _ := redis.GetExpire("", limitKey)
|
||||
rateCount, err := redis.RateLimit("", limitKey, option.Time, option.Count)
|
||||
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-Remaining", fmt.Sprintf("%d", option.Count-rateCount)) // 剩余可用请求数
|
||||
c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Unix()+int64(rateTime))) // 重置时间戳
|
||||
c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", option.Count)) // 总请求数限制
|
||||
c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", option.Count-rateCount)) // 剩余可用请求数
|
||||
c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Unix()+rateTime)) // 重置时间戳
|
||||
|
||||
if rateCount >= option.Count {
|
||||
// 访问过于频繁,请稍候再试
|
||||
c.JSON(200, i18n.TKey(language, "app.common.rateLimitTip"))
|
||||
c.JSON(200, resp.CodeMsg(4013, "访问过于频繁,请稍候再试"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"strconv"
|
||||
"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/redis"
|
||||
"be.ems/src/framework/utils/ctx"
|
||||
"be.ems/src/framework/utils/ip2region"
|
||||
"be.ems/src/framework/vo/result"
|
||||
"be.ems/src/framework/reqctx"
|
||||
"be.ems/src/framework/resp"
|
||||
|
||||
"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)
|
||||
if err != nil {
|
||||
logger.Errorf("RepeatSubmit params json marshal err: %v", err)
|
||||
@@ -42,7 +42,7 @@ func RepeatSubmit(interval int64) gin.HandlerFunc {
|
||||
|
||||
// 唯一标识(指定key + 客户端IP + 请求地址)
|
||||
clientIP := ip2region.ClientIP(c.ClientIP())
|
||||
repeatKey := cachekey.REPEAT_SUBMIT_KEY + clientIP + ":" + c.Request.RequestURI
|
||||
repeatKey := constants.CACHE_REPEAT_SUBMIT + clientIP + ":" + c.Request.RequestURI
|
||||
|
||||
// 在Redis查询并记录请求次数
|
||||
repeatStr, _ := redis.Get("", repeatKey)
|
||||
@@ -61,7 +61,7 @@ func RepeatSubmit(interval int64) gin.HandlerFunc {
|
||||
// 小于间隔时间且参数内容一致
|
||||
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()
|
||||
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
|
||||
|
||||
import (
|
||||
"be.ems/src/framework/logger"
|
||||
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"be.ems/src/framework/logger"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
@@ -19,10 +19,7 @@ func Report() gin.HandlerFunc {
|
||||
|
||||
// 计算请求处理时间,并打印日志
|
||||
duration := time.Since(start)
|
||||
// logger.Infof("%s %s report end=> %v", c.Request.Method, c.Request.RequestURI, duration)
|
||||
// 获取当前活跃的goroutine数量
|
||||
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)
|
||||
numGoroutines := runtime.NumGoroutine()
|
||||
logger.Infof("\n访问接口: %s %s\n总耗时: %v\n当前活跃的Goroutine数量: %d", c.Request.Method, c.Request.RequestURI, duration, numGoroutines)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"net/url"
|
||||
|
||||
"be.ems/src/framework/config"
|
||||
"be.ems/src/framework/vo/result"
|
||||
"be.ems/src/framework/resp"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -43,7 +43,7 @@ func referer(c *gin.Context) {
|
||||
referer := c.GetHeader("Referer")
|
||||
if referer == "" {
|
||||
// 无效 Referer 未知
|
||||
c.AbortWithStatusJSON(200, result.ErrMsg("Invalid referer unknown"))
|
||||
c.AbortWithStatusJSON(200, resp.ErrMsg("Invalid referer unknown"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ func referer(c *gin.Context) {
|
||||
u, err := url.Parse(referer)
|
||||
if err != nil {
|
||||
// 无效 Referer 未知
|
||||
c.AbortWithStatusJSON(200, result.ErrMsg("Invalid referer unknown"))
|
||||
c.AbortWithStatusJSON(200, resp.ErrMsg("Invalid referer unknown"))
|
||||
return
|
||||
}
|
||||
host := u.Host
|
||||
@@ -73,7 +73,7 @@ func referer(c *gin.Context) {
|
||||
}
|
||||
if !ok {
|
||||
// 无效 Referer
|
||||
c.AbortWithStatusJSON(200, result.ErrMsg("Invalid referer "+host))
|
||||
c.AbortWithStatusJSON(200, resp.ErrMsg("Invalid referer "+host))
|
||||
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 (
|
||||
constResult "be.ems/src/framework/constants/result"
|
||||
const (
|
||||
// CODE_ERROR 响应-code错误失败
|
||||
CODE_ERROR = 0
|
||||
// MSG_ERROR 响应-msg错误失败
|
||||
MSG_ERROR = "error"
|
||||
|
||||
// CODE_SUCCESS 响应-msg正常成功
|
||||
CODE_SUCCESS = 1
|
||||
// MSG_SUCCCESS 响应-code正常成功
|
||||
MSG_SUCCCESS = "success"
|
||||
|
||||
// 响应-code加密数据
|
||||
CODE_ENCRYPT = 2
|
||||
// 响应-msg加密数据
|
||||
MSG_ENCRYPT = "encrypt"
|
||||
)
|
||||
|
||||
// CodeMsg 响应结果
|
||||
@@ -12,11 +25,11 @@ func CodeMsg(code int, msg string) map[string]any {
|
||||
return args
|
||||
}
|
||||
|
||||
// 响应成功结果 map[string]any{}
|
||||
// Ok 响应成功结果
|
||||
func Ok(v map[string]any) map[string]any {
|
||||
args := make(map[string]any)
|
||||
args["code"] = constResult.CODE_SUCCESS
|
||||
args["msg"] = constResult.MSG_SUCCESS
|
||||
args["code"] = CODE_SUCCESS
|
||||
args["msg"] = MSG_SUCCCESS
|
||||
// v合并到args
|
||||
for key, value := range v {
|
||||
args[key] = value
|
||||
@@ -24,28 +37,28 @@ func Ok(v map[string]any) map[string]any {
|
||||
return args
|
||||
}
|
||||
|
||||
// 响应成功结果信息
|
||||
// OkMsg 响应成功结果信息
|
||||
func OkMsg(msg string) map[string]any {
|
||||
args := make(map[string]any)
|
||||
args["code"] = constResult.CODE_SUCCESS
|
||||
args["code"] = CODE_SUCCESS
|
||||
args["msg"] = msg
|
||||
return args
|
||||
}
|
||||
|
||||
// 响应成功结果数据
|
||||
// OkData 响应成功结果数据
|
||||
func OkData(data any) map[string]any {
|
||||
args := make(map[string]any)
|
||||
args["code"] = constResult.CODE_SUCCESS
|
||||
args["msg"] = constResult.MSG_SUCCESS
|
||||
args["code"] = CODE_SUCCESS
|
||||
args["msg"] = MSG_SUCCCESS
|
||||
args["data"] = data
|
||||
return args
|
||||
}
|
||||
|
||||
// 响应失败结果 map[string]any{}
|
||||
// Err 响应失败结果 map[string]any{}
|
||||
func Err(v map[string]any) map[string]any {
|
||||
args := make(map[string]any)
|
||||
args["code"] = constResult.CODE_ERROR
|
||||
args["msg"] = constResult.MSG_ERROR
|
||||
args["code"] = CODE_ERROR
|
||||
args["msg"] = MSG_ERROR
|
||||
// v合并到args
|
||||
for key, value := range v {
|
||||
args[key] = value
|
||||
@@ -53,19 +66,19 @@ func Err(v map[string]any) map[string]any {
|
||||
return args
|
||||
}
|
||||
|
||||
// 响应失败结果信息
|
||||
// ErrMsg 响应失败结果信息
|
||||
func ErrMsg(msg string) map[string]any {
|
||||
args := make(map[string]any)
|
||||
args["code"] = constResult.CODE_ERROR
|
||||
args["code"] = CODE_ERROR
|
||||
args["msg"] = msg
|
||||
return args
|
||||
}
|
||||
|
||||
// 响应失败结果数据
|
||||
// ErrData 响应失败结果数据
|
||||
func ErrData(data any) map[string]any {
|
||||
args := make(map[string]any)
|
||||
args["code"] = constResult.CODE_ERROR
|
||||
args["msg"] = constResult.MSG_ERROR
|
||||
args["code"] = CODE_ERROR
|
||||
args["msg"] = MSG_ERROR
|
||||
args["data"] = data
|
||||
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
|
||||
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)
|
||||
if err != nil {
|
||||
logger.Infof("utils ParseStrToDate err %v", err)
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"be.ems/src/framework/constants/uploadsubpath"
|
||||
"be.ems/src/framework/constants"
|
||||
"be.ems/src/framework/logger"
|
||||
"be.ems/src/framework/utils/date"
|
||||
|
||||
@@ -26,7 +26,7 @@ func TransferExeclUploadFile(file *multipart.FileHeader) (string, error) {
|
||||
// 上传资源路径
|
||||
_, 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)
|
||||
writePathFile := filepath.Join(dir, filePath, fileName)
|
||||
// 存入新文件路径
|
||||
@@ -138,7 +138,7 @@ func WriteSheet(headerCells map[string]string, dataCells []map[string]any, fileN
|
||||
|
||||
// 上传资源路径
|
||||
_, 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)
|
||||
|
||||
// 创建文件目录
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"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/utils/date"
|
||||
"be.ems/src/framework/utils/generate"
|
||||
@@ -237,7 +237,7 @@ func TransferChunkUploadFile(file *multipart.FileHeader, index, identifier strin
|
||||
// 上传资源路径
|
||||
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)
|
||||
// 存入新文件路径
|
||||
err = transferToNewFile(file, writePathFile)
|
||||
@@ -261,7 +261,7 @@ func ChunkCheckFile(identifier, originalFileName string) ([]string, error) {
|
||||
}
|
||||
// 上传资源路径
|
||||
_, 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)
|
||||
fileList, err := getDirFileNameList(readPath)
|
||||
if err != nil {
|
||||
@@ -286,7 +286,7 @@ func ChunkMergeFile(identifier, originalFileName, subPath string) (string, error
|
||||
// 上传资源路径
|
||||
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)
|
||||
// 组合存放文件路径
|
||||
fileName := generateFileName(originalFileName)
|
||||
@@ -305,7 +305,7 @@ func ChunkMergeFile(identifier, originalFileName, subPath string) (string, error
|
||||
// filePath 上传得到的文件路径 /upload....
|
||||
// dst 新文件路径 /a/xx.pdf
|
||||
func CopyUploadFile(filePath, dst string) error {
|
||||
srcPath := ParseUploadFilePath(filePath)
|
||||
srcPath := ParseUploadFileAbsPath(filePath)
|
||||
src, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -346,10 +346,10 @@ func ParseUploadFileDir(subPath string) string {
|
||||
return filepath.Join(dir, filePath)
|
||||
}
|
||||
|
||||
// ParseUploadFilePath 上传资源本地绝对资源路径
|
||||
// ParseUploadFileAbsPath 上传资源本地绝对资源路径
|
||||
//
|
||||
// filePath 上传文件路径
|
||||
func ParseUploadFilePath(filePath string) string {
|
||||
func ParseUploadFileAbsPath(filePath string) string {
|
||||
prefix, dir := resourceUpload()
|
||||
absPath := strings.Replace(filePath, prefix, dir, 1)
|
||||
return filepath.ToSlash(absPath)
|
||||
|
||||
Binary file not shown.
@@ -9,7 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
"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/utils/cmd"
|
||||
"be.ems/src/framework/utils/crypto"
|
||||
@@ -106,8 +106,8 @@ func Launch() {
|
||||
"code": Code, // 机器码
|
||||
"useTime": time.Now().UnixMilli(), // 首次使用时间
|
||||
|
||||
common.LAUNCH_BOOTLOADER: true, // 启动引导
|
||||
common.LAUNCH_BOOTLOADER + "Time": 0, // 引导完成时间
|
||||
constants.LAUNCH_BOOTLOADER: true, // 启动引导
|
||||
constants.LAUNCH_BOOTLOADER + "Time": 0, // 引导完成时间
|
||||
}
|
||||
codeFileWrite(LaunchInfo)
|
||||
} else {
|
||||
@@ -151,8 +151,8 @@ func SetLaunchInfo(info map[string]any) error {
|
||||
// Bootloader 启动引导标记
|
||||
func Bootloader(flag bool) error {
|
||||
return SetLaunchInfo(map[string]any{
|
||||
common.LAUNCH_BOOTLOADER: flag, // 启动引导 true开 false关
|
||||
common.LAUNCH_BOOTLOADER + "Time": time.Now().UnixMilli(), // 引导完成时间
|
||||
constants.LAUNCH_BOOTLOADER: flag, // 启动引导 true开 false关
|
||||
constants.LAUNCH_BOOTLOADER + "Time": time.Now().UnixMilli(), // 引导完成时间
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -91,31 +91,16 @@ func ConvertToCamelCase(str string) string {
|
||||
return strings.Join(words, "")
|
||||
}
|
||||
|
||||
// Bit 比特位为单位
|
||||
// Bit 比特位为单位 1023.00 B --> 1.00 KB
|
||||
func Bit(bit float64) string {
|
||||
var GB, MB, KB string
|
||||
|
||||
if bit > float64(1<<30) {
|
||||
GB = fmt.Sprintf("%0.2f", bit/(1<<30))
|
||||
}
|
||||
|
||||
if bit > float64(1<<20) && bit < (1<<30) {
|
||||
MB = fmt.Sprintf("%.2f", bit/(1<<20))
|
||||
}
|
||||
|
||||
if bit > float64(1<<10) && bit < (1<<20) {
|
||||
KB = fmt.Sprintf("%.2f", bit/(1<<10))
|
||||
}
|
||||
|
||||
if GB != "" {
|
||||
return GB + "GB"
|
||||
} else if MB != "" {
|
||||
return MB + "MB"
|
||||
} else if KB != "" {
|
||||
return KB + "KB"
|
||||
} else {
|
||||
return fmt.Sprintf("%vB", bit)
|
||||
units := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||
for i := 0; i < len(units); i++ {
|
||||
if bit < 1024 || i == len(units)-1 {
|
||||
return fmt.Sprintf("%.2f %s", bit, units[i])
|
||||
}
|
||||
bit /= 1024
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// CronExpression 解析 Cron 表达式,返回下一次执行的时间戳(毫秒)
|
||||
@@ -146,11 +131,11 @@ func SafeContent(value string) string {
|
||||
}
|
||||
|
||||
// RemoveDuplicates 数组内字符串去重
|
||||
func RemoveDuplicates(ids []string) []string {
|
||||
func RemoveDuplicates(arr []string) []string {
|
||||
uniqueIDs := make(map[string]bool)
|
||||
uniqueIDSlice := make([]string, 0)
|
||||
|
||||
for _, id := range ids {
|
||||
for _, id := range arr {
|
||||
_, ok := uniqueIDs[id]
|
||||
if !ok && id != "" {
|
||||
uniqueIDs[id] = true
|
||||
@@ -161,6 +146,29 @@ func RemoveDuplicates(ids []string) []string {
|
||||
return uniqueIDSlice
|
||||
}
|
||||
|
||||
// RemoveDuplicatesToArray 数组内字符串分隔去重转为字符数组
|
||||
func RemoveDuplicatesToArray(keyStr, sep string) []string {
|
||||
arr := make([]string, 0)
|
||||
if keyStr == "" {
|
||||
return arr
|
||||
}
|
||||
if strings.Contains(keyStr, sep) {
|
||||
// 处理字符转数组后去重
|
||||
strArr := strings.Split(keyStr, sep)
|
||||
uniqueKeys := make(map[string]bool)
|
||||
for _, str := range strArr {
|
||||
_, ok := uniqueKeys[str]
|
||||
if !ok && str != "" {
|
||||
uniqueKeys[str] = true
|
||||
arr = append(arr, str)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
arr = append(arr, keyStr)
|
||||
}
|
||||
return arr
|
||||
}
|
||||
|
||||
// Color 解析颜色 #fafafa
|
||||
func Color(colorStr string) *color.RGBA {
|
||||
// 去除 # 号
|
||||
|
||||
@@ -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