Merge branch 'lichang' into lite
This commit is contained in:
@@ -12,9 +12,15 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
|
||||
libConfig "be.ems/lib/config"
|
||||
libGlobal "be.ems/lib/global"
|
||||
)
|
||||
|
||||
var (
|
||||
Version string = "-"
|
||||
BuildTime string = "-"
|
||||
GoVer string = "-"
|
||||
)
|
||||
|
||||
// 程序配置
|
||||
var conf *viper.Viper
|
||||
|
||||
// 初始化程序配置
|
||||
@@ -28,9 +34,9 @@ func InitConfig(configDir *embed.FS) {
|
||||
func initFlag() {
|
||||
// --env prod
|
||||
pflag.String("env", "prod", "Specify Run Environment Configuration local or prod")
|
||||
// --c /etc/restconf.yaml
|
||||
// -c /etc/restconf.yaml
|
||||
pflag.StringP("config", "c", "./etc/restconf.yaml", "Specify Configuration File")
|
||||
// --c /usr/local/etc/omc/omc.yaml
|
||||
// -c /usr/local/etc/omc/omc.yaml
|
||||
pflag.StringP("config", "c", "/usr/local/etc/omc/omc.yaml", "Specify Configuration File")
|
||||
// --sqlPath ./sql/20250228.sql
|
||||
pflag.String("sqlPath", "", "Execution SQL File Path")
|
||||
// --sqlSource default
|
||||
@@ -45,7 +51,7 @@ func initFlag() {
|
||||
|
||||
// 参数固定输出
|
||||
if *pVersion {
|
||||
buildInfo := fmt.Sprintf("OMC version: %s\n%s\n%s\n\n", libGlobal.Version, libGlobal.BuildTime, libGlobal.GoVer)
|
||||
buildInfo := fmt.Sprintf("OMC \nBuildVer: %s\nBuildTime: %s\nBuildEnv: %s\n", Version, BuildTime, GoVer)
|
||||
fmt.Println(buildInfo)
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -74,7 +80,7 @@ func initViper(configDir *embed.FS) {
|
||||
|
||||
// 当期服务环境运行配置 => local
|
||||
env := conf.GetString("env")
|
||||
log.Printf("current service environment operation configuration => %s \n", env)
|
||||
log.Printf("current service environment configuration => %s \n", env)
|
||||
|
||||
// 加载运行配置文件合并相同配置
|
||||
envConfigPath := fmt.Sprintf("config/config.%s.yaml", env)
|
||||
@@ -160,7 +166,7 @@ func RunTime() time.Time {
|
||||
|
||||
// Get 获取配置信息
|
||||
//
|
||||
// Get("server.proxy")
|
||||
// Get("redis.defaultDataSourceName")
|
||||
func Get(key string) any {
|
||||
return conf.Get(key)
|
||||
}
|
||||
@@ -181,7 +187,7 @@ func IsSystemUser(userId int64) bool {
|
||||
return false
|
||||
}
|
||||
// 从配置中获取系统管理员ID列表
|
||||
arr := Get("user.system").([]any)
|
||||
arr := Get("systemUser").([]any)
|
||||
for _, v := range arr {
|
||||
if fmt.Sprint(v) == fmt.Sprint(userId) {
|
||||
return true
|
||||
|
||||
@@ -2,8 +2,8 @@ package constants
|
||||
|
||||
// 缓存的key常量
|
||||
const (
|
||||
// CACHE_LOGIN_TOKEN 登录用户
|
||||
CACHE_LOGIN_TOKEN = "login_tokens"
|
||||
// CACHE_TOKEN_DEVICE 登录用户令牌标识
|
||||
CACHE_TOKEN_DEVICE = "token_devices"
|
||||
// CACHE_CAPTCHA_CODE 验证码
|
||||
CACHE_CAPTCHA_CODE = "captcha_codes"
|
||||
// CACHE_SYS_CONFIG 参数管理
|
||||
@@ -16,6 +16,10 @@ const (
|
||||
CACHE_RATE_LIMIT = "rate_limit"
|
||||
// CACHE_PWD_ERR_COUNT 登录账户密码错误次数
|
||||
CACHE_PWD_ERR_COUNT = "pwd_err_count"
|
||||
// CACHE_OAUTH2_DEVICE 授权客户端令牌标识
|
||||
CACHE_OAUTH2_DEVICE = "oauth2_devices"
|
||||
// CACHE_OAUTH2_CODE 客户端授权码
|
||||
CACHE_OAUTH2_CODE = "oauth2_codes"
|
||||
// CACHE_I18N 国际化语言管理
|
||||
CACHE_I18N = "i18n"
|
||||
// CACHE_NE_INFO 网元信息管理
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package constants
|
||||
|
||||
// 验证码常量信息
|
||||
const (
|
||||
// CAPTCHA_EXPIRATION 验证码有效期,单位秒
|
||||
CAPTCHA_EXPIRATION = 2 * 60
|
||||
// CAPTCHA_TYPE_CHAR 验证码类型-数值计算
|
||||
CAPTCHA_TYPE_CHAR = "char"
|
||||
// CAPTCHA_TYPE_MATH 验证码类型-字符验证
|
||||
CAPTCHA_TYPE_MATH = "math"
|
||||
)
|
||||
@@ -10,5 +10,8 @@ const (
|
||||
// CTX_LOGIN_USER 上下文信息-登录用户
|
||||
const CTX_LOGIN_USER = "ctx:login_user"
|
||||
|
||||
// CTX_LOGIN_OAUTH2 上下文信息-认证客户端
|
||||
const CTX_LOGIN_OAUTH2 = "ctx:login_oauth2"
|
||||
|
||||
// 启动-引导系统初始
|
||||
const LAUNCH_BOOTLOADER = "bootloader"
|
||||
|
||||
@@ -3,19 +3,19 @@ package constants
|
||||
// 令牌常量信息
|
||||
|
||||
// HEADER_PREFIX 令牌-请求头标识前缀
|
||||
const HEADER_PREFIX = "Bearer "
|
||||
const HEADER_PREFIX = "Bearer"
|
||||
|
||||
// HEADER_KEY 令牌-请求头标识
|
||||
const HEADER_KEY = "Authorization"
|
||||
|
||||
// JWT_UUID 令牌-JWT唯一标识字段
|
||||
const JWT_UUID = "uuid"
|
||||
// JWT_DEVICE_ID 令牌-JWT设备标识字段
|
||||
const JWT_DEVICE_ID = "device_id"
|
||||
|
||||
// JWT_USER_ID 令牌-JWT标识用户主键字段
|
||||
const JWT_USER_ID = "user_id"
|
||||
|
||||
// JWT_USER_NAME 令牌-JWT标识用户登录账号字段
|
||||
const JWT_USER_NAME = "user_name"
|
||||
// JWT_CLIENT_ID 令牌-JWT标识客户端ID字段
|
||||
const JWT_CLIENT_ID = "client_id"
|
||||
|
||||
// NMS北向使用-数据响应字段和请求头授权
|
||||
const ACCESS_TOKEN = "accessToken"
|
||||
|
||||
@@ -96,8 +96,8 @@ func (jl *jobLogData) SaveLog(statusFlag string) {
|
||||
"message": jl.Result,
|
||||
})
|
||||
jobMsg := string(jsonByte)
|
||||
if len(jobMsg) > 500 {
|
||||
jobMsg = jobMsg[:500]
|
||||
if len(jobMsg) > 2000 {
|
||||
jobMsg = jobMsg[:2000]
|
||||
}
|
||||
|
||||
// 创建日志对象
|
||||
|
||||
@@ -89,7 +89,8 @@ func Connect() {
|
||||
// 创建连接
|
||||
db, err := gorm.Open(info.dialectic, opts)
|
||||
if err != nil {
|
||||
logger.Fatalf("failed error db connect: %s", err)
|
||||
logger.Errorf("failed error db connect: %s", err)
|
||||
continue
|
||||
}
|
||||
// 获取底层 SQL 数据库连接
|
||||
sqlDB, err := db.DB()
|
||||
@@ -135,7 +136,7 @@ func DB(source string) *gorm.DB {
|
||||
}
|
||||
db := dbMap[source]
|
||||
if db == nil {
|
||||
logger.Fatalf("not database source: %s", source)
|
||||
logger.Errorf("not database source: %s", source)
|
||||
return nil
|
||||
}
|
||||
return db
|
||||
|
||||
@@ -60,8 +60,8 @@ func ImportSQL() {
|
||||
processSQLFile(db, sqlPath)
|
||||
}
|
||||
|
||||
log.Println("Import SQL End")
|
||||
os.Exit(1)
|
||||
log.Println("process success")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// 处理单个SQL文件的通用函数
|
||||
|
||||
@@ -16,6 +16,21 @@ import (
|
||||
// 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);`)
|
||||
|
||||
// Connect 连接Redis实例
|
||||
func Connect() {
|
||||
ctx := context.Background()
|
||||
@@ -33,7 +48,7 @@ func Connect() {
|
||||
// 测试数据库连接
|
||||
pong, err := rdb.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
logger.Fatalf("failed error redis connect: %s is %v", k, err)
|
||||
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
|
||||
@@ -139,6 +154,39 @@ func CommandStats(source string) []map[string]string {
|
||||
return statsObjArr
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// SetExpire 设置过期时间
|
||||
func SetExpire(source, key string, expiration time.Duration) error {
|
||||
// 数据源
|
||||
rdb := RDB(source)
|
||||
if rdb == nil {
|
||||
return fmt.Errorf("redis not client")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
err := rdb.Expire(ctx, key, expiration).Err()
|
||||
if err != nil {
|
||||
logger.Errorf("redis Expire err %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExpire 获取键的剩余有效时间(秒)
|
||||
func GetExpire(source string, key string) (int64, error) {
|
||||
// 数据源
|
||||
@@ -227,41 +275,8 @@ func Get(source, key string) (string, error) {
|
||||
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 {
|
||||
func Set(source, key string, value any, expiration time.Duration) error {
|
||||
// 数据源
|
||||
rdb := RDB(source)
|
||||
if rdb == nil {
|
||||
@@ -271,7 +286,7 @@ func SetByExpire(source, key string, value any, expiration time.Duration) error
|
||||
ctx := context.Background()
|
||||
err := rdb.Set(ctx, key, value, expiration).Err()
|
||||
if err != nil {
|
||||
logger.Errorf("redis SetByExpire err %v", err)
|
||||
logger.Errorf("redis Set err %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -329,18 +344,3 @@ func RateLimit(source, limitKey string, time, count int64) (int64, error) {
|
||||
}
|
||||
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);`)
|
||||
|
||||
@@ -20,14 +20,14 @@ func ErrorCatch() gin.HandlerFunc {
|
||||
|
||||
// 返回错误响应给客户端
|
||||
if config.Env() == "prod" {
|
||||
c.JSON(500, resp.CodeMsg(500, "Internal Server Errors"))
|
||||
c.JSON(500, resp.CodeMsg(500001, "Internal Server Errors"))
|
||||
} else {
|
||||
// 通过实现 error 接口的 Error() 方法自定义错误类型进行捕获
|
||||
switch v := err.(type) {
|
||||
case error:
|
||||
c.JSON(500, resp.CodeMsg(500, v.Error()))
|
||||
c.JSON(500, resp.CodeMsg(500001, v.Error()))
|
||||
default:
|
||||
c.JSON(500, resp.CodeMsg(500, fmt.Sprint(err)))
|
||||
c.JSON(500, resp.CodeMsg(500001, fmt.Sprint(err)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
66
src/framework/middleware/authorize_oauth2.go
Normal file
66
src/framework/middleware/authorize_oauth2.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"be.ems/src/framework/constants"
|
||||
"be.ems/src/framework/reqctx"
|
||||
"be.ems/src/framework/resp"
|
||||
"be.ems/src/framework/token"
|
||||
)
|
||||
|
||||
// AuthorizeOauth2 客户端授权认证校验
|
||||
//
|
||||
// scope 客户端授权范围,例如:[]string{"read","write"}
|
||||
func AuthorizeOauth2(scope []string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 获取请求头标识信息
|
||||
tokenStr := reqctx.Authorization(c)
|
||||
if tokenStr == "" {
|
||||
c.JSON(401, resp.CodeMsg(401003, "authorization token is empty"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
// 验证令牌
|
||||
claims, err := token.Oauth2TokenVerify(tokenStr, "access")
|
||||
if err != nil {
|
||||
c.JSON(401, resp.CodeMsg(401001, err.Error()))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
// 获取缓存的用户信息
|
||||
info := token.Oauth2InfoGet(claims)
|
||||
if info.ClientId == "" {
|
||||
c.JSON(401, resp.CodeMsg(401002, "invalid login user information"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
c.Set(constants.CTX_LOGIN_OAUTH2, info)
|
||||
|
||||
// 客户端权限校验
|
||||
if scope != nil {
|
||||
var hasScope bool = false
|
||||
for _, item := range info.Scope {
|
||||
for _, v := range scope {
|
||||
if item == v {
|
||||
hasScope = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasScope {
|
||||
msg := fmt.Sprintf("unauthorized access %s %s", c.Request.Method, c.Request.RequestURI)
|
||||
c.JSON(403, resp.CodeMsg(403001, msg))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 调用下一个处理程序
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"be.ems/src/framework/config"
|
||||
"be.ems/src/framework/constants"
|
||||
"be.ems/src/framework/i18n"
|
||||
"be.ems/src/framework/reqctx"
|
||||
"be.ems/src/framework/resp"
|
||||
"be.ems/src/framework/token"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"be.ems/src/framework/utils/parse"
|
||||
)
|
||||
|
||||
/**无Token可访问白名单 */
|
||||
@@ -26,7 +27,7 @@ var URL_WHITE_LIST = []string{
|
||||
"/oauth/token",
|
||||
}
|
||||
|
||||
// PreAuthorize 用户身份授权认证校验
|
||||
// AuthorizeUser 用户身份授权认证校验
|
||||
//
|
||||
// 只需含有其中角色 "hasRoles": {"xxx"},
|
||||
//
|
||||
@@ -35,13 +36,10 @@ var URL_WHITE_LIST = []string{
|
||||
// 同时匹配其中角色 "matchRoles": {"xxx"},
|
||||
//
|
||||
// 同时匹配其中权限 "matchPerms": {"xxx"},
|
||||
func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
||||
func AuthorizeUser(options map[string][]string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 登录认证,默认打开
|
||||
enable := true
|
||||
if v := config.Get("user.loginAuth"); v != nil {
|
||||
enable = v.(bool)
|
||||
}
|
||||
enable := parse.Boolean(config.Get("serverLoginAuth"))
|
||||
if !enable {
|
||||
loginUser, _ := reqctx.LoginUser(c)
|
||||
loginUser.UserId = 2
|
||||
@@ -53,12 +51,9 @@ func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
language := reqctx.AcceptLanguage(c)
|
||||
|
||||
requestURI := c.Request.RequestURI
|
||||
|
||||
// 判断白名单
|
||||
isWhite := false
|
||||
requestURI := c.Request.RequestURI
|
||||
for _, w := range URL_WHITE_LIST {
|
||||
if strings.Contains(requestURI, w) {
|
||||
isWhite = true
|
||||
@@ -73,42 +68,39 @@ func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
||||
// 获取请求头标识信息
|
||||
tokenStr := reqctx.Authorization(c)
|
||||
if tokenStr == "" {
|
||||
c.JSON(401, resp.CodeMsg(401, i18n.TKey(language, "app.common.err401")))
|
||||
c.JSON(401, resp.CodeMsg(401003, "authorization token is empty"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
// 验证令牌
|
||||
claims, err := token.Verify(tokenStr)
|
||||
claims, err := token.UserTokenVerify(tokenStr, "access")
|
||||
if err != nil {
|
||||
c.JSON(401, resp.CodeMsg(401, err.Error()))
|
||||
c.JSON(401, resp.CodeMsg(401001, err.Error()))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
// 获取缓存的用户信息
|
||||
loginUser := token.Info(claims)
|
||||
if loginUser.UserId <= 0 {
|
||||
c.JSON(401, resp.CodeMsg(401, i18n.TKey(language, "app.common.err401")))
|
||||
info := token.UserInfoGet(claims)
|
||||
if info.UserId <= 0 {
|
||||
c.JSON(401, resp.CodeMsg(401002, "invalid login user information"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
// 检查刷新有效期后存入上下文
|
||||
token.RefreshIn(&loginUser)
|
||||
c.Set(constants.CTX_LOGIN_USER, loginUser)
|
||||
c.Set(constants.CTX_LOGIN_USER, info)
|
||||
|
||||
// 登录用户角色权限校验
|
||||
if options != nil {
|
||||
var roles []string
|
||||
for _, item := range loginUser.User.Roles {
|
||||
for _, item := range info.User.Roles {
|
||||
roles = append(roles, item.RoleKey)
|
||||
}
|
||||
perms := loginUser.Permissions
|
||||
perms := info.Permissions
|
||||
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, resp.CodeMsg(403, msg))
|
||||
msg := fmt.Sprintf("unauthorized access %s %s", c.Request.Method, c.Request.RequestURI)
|
||||
c.JSON(403, resp.CodeMsg(403001, msg))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
@@ -127,7 +119,7 @@ func PreAuthorize(options map[string][]string) gin.HandlerFunc {
|
||||
//
|
||||
// options 参数
|
||||
func verifyRolePermission(roles, perms []string, options map[string][]string) bool {
|
||||
// 直接放行 管理员角色或任意权限
|
||||
// 直接放行 系统管理员角色或任意权限
|
||||
if contains(roles, constants.SYS_ROLE_SYSTEM_KEY) || contains(perms, constants.SYS_PERMISSION_SYSTEM) {
|
||||
return true
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func OperateLog(options Options) gin.HandlerFunc {
|
||||
// 获取登录用户信息
|
||||
loginUser, err := reqctx.LoginUser(c)
|
||||
if err != nil {
|
||||
c.JSON(401, resp.CodeMsg(401, i18n.TKey(language, err.Error())))
|
||||
c.JSON(401, resp.CodeMsg(401002, i18n.TKey(language, err.Error())))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
@@ -26,10 +26,7 @@ import (
|
||||
func CryptoApi(requestDecrypt, responseEncrypt bool) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 登录认证,默认打开
|
||||
enable := true
|
||||
if v := config.Get("user.cryptoApi"); v != nil && enable {
|
||||
enable = v.(bool)
|
||||
}
|
||||
enable := parse.Boolean(config.Get("serverCryptoApi"))
|
||||
if !enable {
|
||||
c.Next()
|
||||
return
|
||||
@@ -54,7 +51,7 @@ func CryptoApi(requestDecrypt, responseEncrypt bool) gin.HandlerFunc {
|
||||
|
||||
// 是否存在data字段数据
|
||||
if contentDe == "" {
|
||||
c.JSON(400, resp.ErrMsg("decrypt not found field data"))
|
||||
c.JSON(422, resp.CodeMsg(422002, "decrypt not found field data"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
@@ -64,7 +61,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, resp.ErrMsg("decrypted data could not be parsed"))
|
||||
c.JSON(422, resp.CodeMsg(422001, "decrypted data could not be parsed"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func OperateLog(options Options) gin.HandlerFunc {
|
||||
// 获取登录用户信息
|
||||
loginUser, err := reqctx.LoginUser(c)
|
||||
if err != nil {
|
||||
c.JSON(401, resp.CodeMsg(401, "无效身份授权"))
|
||||
c.JSON(401, resp.CodeMsg(401002, "invalid login user information"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
@@ -134,7 +134,7 @@ func OperateLog(options Options) gin.HandlerFunc {
|
||||
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)
|
||||
msg := fmt.Sprintf(`{"status":"%d","size":%d,"content-type":"%s"}`, status, c.Writer.Size(), content)
|
||||
operaLog.OperaMsg = msg
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ func RateLimit(option LimitOption) gin.HandlerFunc {
|
||||
if option.Type == LIMIT_USER {
|
||||
loginUser, err := reqctx.LoginUser(c)
|
||||
if err != nil {
|
||||
c.JSON(401, resp.CodeMsg(40003, err.Error()))
|
||||
c.JSON(401, resp.CodeMsg(401002, "invalid login user information"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
@@ -80,13 +80,13 @@ func RateLimit(option LimitOption) gin.HandlerFunc {
|
||||
// 在Redis查询并记录请求次数
|
||||
rateCount, err := redis.RateLimit("", limitKey, option.Time, option.Count)
|
||||
if err != nil {
|
||||
c.JSON(200, resp.CodeMsg(4013, "访问过于频繁,请稍候再试"))
|
||||
c.JSON(200, resp.ErrMsg("access too often, please try again later"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
rateTime, err := redis.GetExpire("", limitKey)
|
||||
if err != nil {
|
||||
c.JSON(200, resp.CodeMsg(4013, "访问过于频繁,请稍候再试"))
|
||||
c.JSON(200, resp.ErrMsg("access too often, please try again later"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func RateLimit(option LimitOption) gin.HandlerFunc {
|
||||
c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", time.Now().Unix()+rateTime)) // 重置时间戳
|
||||
|
||||
if rateCount >= option.Count {
|
||||
c.JSON(200, resp.CodeMsg(4013, "访问过于频繁,请稍候再试"))
|
||||
c.JSON(200, resp.ErrMsg("access too often, please try again later"))
|
||||
c.Abort() // 停止执行后续的处理函数
|
||||
return
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ func RepeatSubmit(interval int64) gin.HandlerFunc {
|
||||
logger.Errorf("RepeatSubmit rp json marshal err: %v", err)
|
||||
}
|
||||
// 保存请求时间和参数
|
||||
redis.SetByExpire("", repeatKey, string(rpJSON), time.Duration(interval)*time.Second)
|
||||
redis.Set("", repeatKey, string(rpJSON), time.Duration(interval)*time.Second)
|
||||
|
||||
// 调用下一个处理程序
|
||||
c.Next()
|
||||
|
||||
@@ -60,7 +60,7 @@ func RepeatSubmit(interval int64) gin.HandlerFunc {
|
||||
|
||||
// 小于间隔时间且参数内容一致
|
||||
if compareTime < interval && compareParams {
|
||||
c.JSON(200, resp.ErrMsg("不允许重复提交,请稍候再试"))
|
||||
c.JSON(200, resp.ErrMsg("repeat submissions are not allowed. Please try again later."))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func RepeatSubmit(interval int64) gin.HandlerFunc {
|
||||
logger.Errorf("RepeatSubmit rp json marshal err: %v", err)
|
||||
}
|
||||
// 保存请求时间和参数
|
||||
_ = redis.SetByExpire("", repeatKey, string(rpJSON), time.Duration(interval)*time.Second)
|
||||
_ = redis.Set("", repeatKey, string(rpJSON), time.Duration(interval)*time.Second)
|
||||
|
||||
// 调用下一个处理程序
|
||||
c.Next()
|
||||
|
||||
@@ -12,12 +12,12 @@ import (
|
||||
)
|
||||
|
||||
// LoginUser 登录用户信息
|
||||
func LoginUser(c *gin.Context) (token.TokenInfo, error) {
|
||||
func LoginUser(c *gin.Context) (token.UserInfo, error) {
|
||||
value, exists := c.Get(constants.CTX_LOGIN_USER)
|
||||
if exists && value != nil {
|
||||
return value.(token.TokenInfo), nil
|
||||
return value.(token.UserInfo), nil
|
||||
}
|
||||
return token.TokenInfo{}, fmt.Errorf("invalid login user information")
|
||||
return token.UserInfo{}, fmt.Errorf("invalid login user information")
|
||||
}
|
||||
|
||||
// LoginUserToUserID 登录用户信息-用户ID
|
||||
@@ -58,14 +58,14 @@ func LoginUserByContainRoles(c *gin.Context, target string) bool {
|
||||
|
||||
// LoginUserByContainPerms 登录用户信息-包含权限标识
|
||||
func LoginUserByContainPerms(c *gin.Context, target string) bool {
|
||||
loginUser, err := LoginUser(c)
|
||||
info, err := LoginUser(c)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if config.IsSystemUser(loginUser.UserId) {
|
||||
if config.IsSystemUser(info.UserId) {
|
||||
return true
|
||||
}
|
||||
perms := loginUser.Permissions
|
||||
perms := info.Permissions
|
||||
for _, str := range perms {
|
||||
if str == target {
|
||||
return true
|
||||
|
||||
@@ -73,11 +73,11 @@ func Authorization(c *gin.Context) string {
|
||||
return ""
|
||||
}
|
||||
// 拆分 Authorization 请求头,提取 JWT 令牌部分
|
||||
arr := strings.SplitN(authHeader, constants.HEADER_PREFIX, 2)
|
||||
if len(arr) < 2 {
|
||||
return ""
|
||||
tokenStr := strings.Replace(authHeader, constants.HEADER_PREFIX, "", 1)
|
||||
if len(tokenStr) > 64 {
|
||||
return strings.TrimSpace(tokenStr) // 去除可能存在的空格
|
||||
}
|
||||
return arr[1]
|
||||
return ""
|
||||
}
|
||||
|
||||
// AcceptLanguage 解析客户端接收语言 zh:中文 en: 英文
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package reqctx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"be.ems/src/framework/ip2region"
|
||||
"be.ems/src/framework/utils/crypto"
|
||||
"be.ems/src/framework/utils/ua"
|
||||
)
|
||||
|
||||
@@ -33,3 +36,9 @@ func UaOsBrowser(c *gin.Context) (string, string) {
|
||||
}
|
||||
return os, browser
|
||||
}
|
||||
|
||||
// DeviceFingerprint 设备指纹信息
|
||||
func DeviceFingerprint(c *gin.Context, v any) string {
|
||||
str := fmt.Sprintf("%v:%s", v, c.Request.UserAgent())
|
||||
return crypto.SHA256ToBase64(str)
|
||||
}
|
||||
|
||||
@@ -2,17 +2,17 @@ package resp
|
||||
|
||||
const (
|
||||
// CODE_ERROR 响应-code错误失败
|
||||
CODE_ERROR = 0
|
||||
CODE_ERROR = 400001
|
||||
// MSG_ERROR 响应-msg错误失败
|
||||
MSG_ERROR = "error"
|
||||
|
||||
// CODE_SUCCESS 响应-msg正常成功
|
||||
CODE_SUCCESS = 1
|
||||
CODE_SUCCESS = 200001
|
||||
// MSG_SUCCCESS 响应-code正常成功
|
||||
MSG_SUCCCESS = "success"
|
||||
|
||||
// 响应-code加密数据
|
||||
CODE_ENCRYPT = 2
|
||||
CODE_ENCRYPT = 200999
|
||||
// 响应-msg加密数据
|
||||
MSG_ENCRYPT = "encrypt"
|
||||
)
|
||||
@@ -54,8 +54,8 @@ func OkData(data any) Resp {
|
||||
// Err 响应失败结果 map[string]any{}
|
||||
func Err(v map[string]any) map[string]any {
|
||||
args := make(map[string]any)
|
||||
args["code"] = CODE_SUCCESS
|
||||
args["msg"] = MSG_SUCCCESS
|
||||
args["code"] = CODE_ERROR
|
||||
args["msg"] = MSG_ERROR
|
||||
// v合并到args
|
||||
for key, value := range v {
|
||||
args[key] = value
|
||||
|
||||
@@ -16,7 +16,7 @@ type FileListRow struct {
|
||||
LinkCount int64 `json:"linkCount"` // 硬链接数目
|
||||
Owner string `json:"owner"` // 所属用户
|
||||
Group string `json:"group"` // 所属组
|
||||
Size string `json:"size"` // 文件的大小
|
||||
Size int64 `json:"size"` // 文件的大小
|
||||
ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒
|
||||
FileName string `json:"fileName"` // 文件的名称
|
||||
}
|
||||
@@ -34,10 +34,10 @@ func FileList(sshClient *ConnSSH, path, search string) ([]FileListRow, error) {
|
||||
if search != "" {
|
||||
searchStr = search + searchStr
|
||||
}
|
||||
// cd /var/log && find. -maxdepth 1 -name'mme*' -exec ls -lthd --time-style=+%s {} +
|
||||
cmdStr := fmt.Sprintf("cd %s && find . -maxdepth 1 -name '%s' -exec ls -lthd --time-style=+%%s {} +", path, searchStr)
|
||||
// cd /var/log && ls -lthd --time-style=+%s mme*
|
||||
// cmdStr := fmt.Sprintf("cd %s && ls -lthd --time-style=+%%s %s", path, searchStr)
|
||||
// cd /var/log && find. -maxdepth 1 -name'mme*' -exec ls -ltd --time-style=+%s {} +
|
||||
cmdStr := fmt.Sprintf("cd %s && find . -maxdepth 1 -name '%s' -exec ls -ltd --time-style=+%%s {} +", path, searchStr)
|
||||
// cd /var/log && ls -ltd --time-style=+%s mme*
|
||||
// cmdStr := fmt.Sprintf("cd %s && ls -ltd --time-style=+%%s %s", path, searchStr)
|
||||
|
||||
// 是否远程客户端读取
|
||||
if sshClient == nil {
|
||||
@@ -94,7 +94,7 @@ func FileList(sshClient *ConnSSH, path, search string) ([]FileListRow, error) {
|
||||
LinkCount: parse.Number(fields[1]),
|
||||
Owner: fields[2],
|
||||
Group: fields[3],
|
||||
Size: fields[4],
|
||||
Size: parse.Number(fields[4]),
|
||||
ModifiedTime: parse.Number(fields[5]),
|
||||
FileName: fileName,
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@ func ConvertToStr(telnetClient *ConnTelnet, cmd string) (string, error) {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf(str)
|
||||
return "", fmt.Errorf("%s", str)
|
||||
}
|
||||
|
||||
// ConvertToMap 转换为map
|
||||
@@ -41,7 +41,7 @@ func ConvertToMap(telnetClient *ConnTelnet, cmd string) (map[string]string, erro
|
||||
if index != -1 {
|
||||
output = output[:index]
|
||||
}
|
||||
return nil, fmt.Errorf(output)
|
||||
return nil, fmt.Errorf("%s", output)
|
||||
}
|
||||
|
||||
// 初始化一个map用于存储拆分后的键值对
|
||||
|
||||
14
src/framework/token/oauth2_info.go
Normal file
14
src/framework/token/oauth2_info.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package token
|
||||
|
||||
// Oauth2Info 第三方客户端令牌信息对象
|
||||
type Oauth2Info struct {
|
||||
DeviceId string `json:"deviceId"` // 用户设备标识
|
||||
ClientId string `json:"clientId"` // 客户端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"` // 操作系统
|
||||
Scope []string `json:"scope"` // 权限列表
|
||||
}
|
||||
167
src/framework/token/oauth2_token.go
Normal file
167
src/framework/token/oauth2_token.go
Normal file
@@ -0,0 +1,167 @@
|
||||
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/parse"
|
||||
)
|
||||
|
||||
// Oauth2TokenCreate 生成令牌
|
||||
// clientId 客户端ID
|
||||
// deviceFingerprint 设备指纹 SHA256
|
||||
// tokenType 令牌类型 access:访问令牌 refresh:刷新令牌
|
||||
func Oauth2TokenCreate(clientId, deviceFingerprint, tokenType string) (string, int64) {
|
||||
// 令牌算法 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
|
||||
default: // 包含HS256和其他所有情况
|
||||
method = jwt.SigningMethodHS256
|
||||
}
|
||||
|
||||
// 生成令牌设置密钥
|
||||
secret := fmt.Sprint(config.Get("jwt.secret"))
|
||||
// 设置令牌过期时间
|
||||
now := time.Now()
|
||||
exp := now
|
||||
if tokenType == "access" {
|
||||
expiresIn := time.Duration(parse.Number(config.Get("jwt.expiresIn")))
|
||||
exp = now.Add(expiresIn * time.Minute)
|
||||
secret = "Oauth2_Access:" + secret
|
||||
}
|
||||
if tokenType == "refresh" {
|
||||
refreshIn := time.Duration(parse.Number(config.Get("jwt.refreshIn")))
|
||||
exp = now.Add(refreshIn * time.Minute)
|
||||
secret = "Oauth2_Refresh:" + secret
|
||||
}
|
||||
|
||||
// 生成令牌负荷绑定uuid标识
|
||||
jwtToken := jwt.NewWithClaims(method, jwt.MapClaims{
|
||||
constants.JWT_DEVICE_ID: deviceFingerprint,
|
||||
constants.JWT_CLIENT_ID: clientId,
|
||||
"exp": exp.Unix(), // 过期时间
|
||||
"iat": now.Unix(), // 签发时间
|
||||
"nbf": now.Unix(), // 生效时间
|
||||
})
|
||||
|
||||
tokenStr, err := jwtToken.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
logger.Infof("jwt sign err : %v", err)
|
||||
return "", 0
|
||||
}
|
||||
expSeconds := int64(exp.Sub(now).Seconds())
|
||||
return tokenStr, expSeconds
|
||||
}
|
||||
|
||||
// Oauth2TokenVerify 校验令牌是否有效
|
||||
// tokenType 令牌类型 access:访问令牌 refresh:刷新令牌
|
||||
func Oauth2TokenVerify(tokenStr, tokenType string) (jwt.MapClaims, error) {
|
||||
jwtToken, err := jwt.Parse(tokenStr, func(jToken *jwt.Token) (any, error) {
|
||||
// 判断加密算法是预期的加密算法
|
||||
if _, ok := jToken.Method.(*jwt.SigningMethodHMAC); ok {
|
||||
secret := config.Get("jwt.secret").(string)
|
||||
if tokenType == "access" {
|
||||
secret = "Oauth2_Access:" + secret
|
||||
}
|
||||
if tokenType == "refresh" {
|
||||
secret = "Oauth2_Refresh:" + secret
|
||||
}
|
||||
return []byte(secret), nil
|
||||
}
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf("Token Verify Err: %v", err)
|
||||
return nil, fmt.Errorf("token invalid")
|
||||
}
|
||||
// 如果解析负荷成功并通过签名校验
|
||||
claims, ok := jwtToken.Claims.(jwt.MapClaims)
|
||||
if ok && jwtToken.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
return nil, fmt.Errorf("token valid error")
|
||||
}
|
||||
|
||||
// Oauth2InfoRemove 清除登录第三方客户端信息
|
||||
func Oauth2InfoRemove(tokenStr string) (string, error) {
|
||||
claims, err := Oauth2TokenVerify(tokenStr, "access")
|
||||
if err != nil {
|
||||
logger.Errorf("token verify err %v", err)
|
||||
return "", err
|
||||
}
|
||||
deviceId, ok := claims[constants.JWT_DEVICE_ID]
|
||||
if ok && deviceId != "" {
|
||||
// 清除缓存KEY
|
||||
tokenKey := constants.CACHE_OAUTH2_DEVICE + ":" + fmt.Sprint(deviceId)
|
||||
return fmt.Sprint(claims[constants.JWT_CLIENT_ID]), redis.Del("", tokenKey)
|
||||
}
|
||||
return "", fmt.Errorf("token invalid")
|
||||
}
|
||||
|
||||
// Oauth2InfoCreate 生成访问第三方客户端信息缓存
|
||||
func Oauth2InfoCreate(info *Oauth2Info, deviceFingerprint string, ilobArr [4]string) {
|
||||
info.DeviceId = deviceFingerprint
|
||||
|
||||
// 设置请求登录客户端
|
||||
info.LoginIp = ilobArr[0]
|
||||
info.LoginLocation = ilobArr[1]
|
||||
info.OS = ilobArr[2]
|
||||
info.Browser = ilobArr[3]
|
||||
|
||||
expiresIn := time.Duration(parse.Number(config.Get("jwt.expiresIn")))
|
||||
now := time.Now()
|
||||
exp := now.Add(expiresIn * time.Minute)
|
||||
info.LoginTime = now.UnixMilli()
|
||||
info.ExpireTime = exp.UnixMilli()
|
||||
// 登录信息标识缓存
|
||||
tokenKey := constants.CACHE_OAUTH2_DEVICE + ":" + info.DeviceId
|
||||
jsonBytes, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = redis.Set("", tokenKey, string(jsonBytes), expiresIn*time.Minute)
|
||||
}
|
||||
|
||||
// Oauth2InfoUpdate 更新访问第三方客户端信息缓存
|
||||
func Oauth2InfoUpdate(info Oauth2Info) {
|
||||
// 登录信息标识缓存
|
||||
tokenKey := constants.CACHE_OAUTH2_DEVICE + ":" + info.DeviceId
|
||||
jsonBytes, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
expiresIn, _ := redis.GetExpire("", tokenKey)
|
||||
expiration := time.Duration(expiresIn) * time.Second
|
||||
_ = redis.Set("", tokenKey, string(jsonBytes), expiration)
|
||||
}
|
||||
|
||||
// Oauth2InfoGet 缓存的登录第三方客户端信息
|
||||
func Oauth2InfoGet(claims jwt.MapClaims) Oauth2Info {
|
||||
info := Oauth2Info{}
|
||||
deviceId := fmt.Sprint(claims[constants.JWT_DEVICE_ID])
|
||||
tokenKey := constants.CACHE_OAUTH2_DEVICE + ":" + deviceId
|
||||
hasKey, err := redis.Has("", tokenKey)
|
||||
if hasKey > 0 && err == nil {
|
||||
infoStr, err := redis.Get("", tokenKey)
|
||||
if infoStr == "" || err != nil {
|
||||
return info
|
||||
}
|
||||
if err := json.Unmarshal([]byte(infoStr), &info); err != nil {
|
||||
logger.Errorf("info json err : %v", err)
|
||||
return info
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -2,9 +2,9 @@ package token
|
||||
|
||||
import systemModel "be.ems/src/modules/system/model"
|
||||
|
||||
// TokenInfo 令牌信息对象
|
||||
type TokenInfo struct {
|
||||
UUID string `json:"uuid"` // 用户唯一标识
|
||||
// UserInfo 系统用户令牌信息对象
|
||||
type UserInfo struct {
|
||||
DeviceId string `json:"deviceId"` // 用户设备标识
|
||||
UserId int64 `json:"userId"` // 用户ID
|
||||
DeptId int64 `json:"deptId"` // 部门ID
|
||||
LoginTime int64 `json:"loginTime"` // 登录时间时间戳
|
||||
173
src/framework/token/user_token.go
Normal file
173
src/framework/token/user_token.go
Normal file
@@ -0,0 +1,173 @@
|
||||
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/parse"
|
||||
)
|
||||
|
||||
// UserTokenCreate 生成令牌
|
||||
// userId 用户ID
|
||||
// deviceFingerprint 设备指纹 SHA256
|
||||
// tokenType 令牌类型 access:访问令牌 refresh:刷新令牌
|
||||
func UserTokenCreate(userId int64, deviceFingerprint, tokenType string) (string, int64) {
|
||||
// 令牌算法 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
|
||||
default: // 包含HS256和其他所有情况
|
||||
method = jwt.SigningMethodHS256
|
||||
}
|
||||
|
||||
// 生成令牌设置密钥
|
||||
secret := fmt.Sprint(config.Get("jwt.secret"))
|
||||
// 设置令牌过期时间
|
||||
now := time.Now()
|
||||
exp := now
|
||||
if tokenType == "access" {
|
||||
expiresIn := time.Duration(parse.Number(config.Get("jwt.expiresIn")))
|
||||
exp = now.Add(expiresIn * time.Minute)
|
||||
secret = "User_Access:" + secret
|
||||
}
|
||||
if tokenType == "refresh" {
|
||||
refreshIn := time.Duration(parse.Number(config.Get("jwt.refreshIn")))
|
||||
exp = now.Add(refreshIn * time.Minute)
|
||||
secret = "User_Refresh:" + secret
|
||||
}
|
||||
|
||||
// 生成令牌负荷绑定uuid标识
|
||||
jwtToken := jwt.NewWithClaims(method, jwt.MapClaims{
|
||||
constants.JWT_DEVICE_ID: deviceFingerprint,
|
||||
constants.JWT_USER_ID: userId,
|
||||
"exp": exp.Unix(), // 过期时间
|
||||
"iat": now.Unix(), // 签发时间
|
||||
"nbf": now.Add(-10 * time.Second).Unix(), // 生效时间
|
||||
})
|
||||
|
||||
tokenStr, err := jwtToken.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
logger.Infof("jwt sign err : %v", err)
|
||||
return "", 0
|
||||
}
|
||||
expSeconds := int64(exp.Sub(now).Seconds())
|
||||
return tokenStr, expSeconds
|
||||
}
|
||||
|
||||
// UserTokenVerify 校验令牌是否有效
|
||||
// tokenType 令牌类型 access:访问令牌 refresh:刷新令牌
|
||||
func UserTokenVerify(tokenStr string, tokenType string) (jwt.MapClaims, error) {
|
||||
jwtToken, err := jwt.Parse(tokenStr, func(jToken *jwt.Token) (any, error) {
|
||||
// 判断加密算法是预期的加密算法
|
||||
if _, ok := jToken.Method.(*jwt.SigningMethodHMAC); ok {
|
||||
secret := config.Get("jwt.secret").(string)
|
||||
if tokenType == "access" {
|
||||
secret = "User_Access:" + secret
|
||||
}
|
||||
if tokenType == "refresh" {
|
||||
secret = "User_Refresh:" + secret
|
||||
}
|
||||
return []byte(secret), nil
|
||||
}
|
||||
return nil, jwt.ErrSignatureInvalid
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf("Token Verify Err: %v", err)
|
||||
return nil, fmt.Errorf("token invalid")
|
||||
}
|
||||
// 如果解析负荷成功并通过签名校验
|
||||
claims, ok := jwtToken.Claims.(jwt.MapClaims)
|
||||
if ok && jwtToken.Valid {
|
||||
return claims, nil
|
||||
}
|
||||
return nil, fmt.Errorf("token valid error")
|
||||
}
|
||||
|
||||
// UserInfoRemove 清除访问用户信息缓存
|
||||
func UserInfoRemove(tokenStr string) (string, error) {
|
||||
claims, err := UserTokenVerify(tokenStr, "access")
|
||||
if err != nil {
|
||||
logger.Errorf("token verify err %v", err)
|
||||
return "", err
|
||||
}
|
||||
info := UserInfoGet(claims)
|
||||
if info.User.UserName != "" {
|
||||
// 清除缓存KEY
|
||||
deviceId := fmt.Sprint(claims[constants.JWT_DEVICE_ID])
|
||||
tokenKey := constants.CACHE_TOKEN_DEVICE + ":" + deviceId
|
||||
return info.User.UserName, redis.Del("", tokenKey)
|
||||
}
|
||||
return "", fmt.Errorf("token invalid")
|
||||
}
|
||||
|
||||
// UserInfoCreate 生成访问用户信息缓存
|
||||
func UserInfoCreate(info *UserInfo, deviceFingerprint string, ilobArr [4]string) {
|
||||
info.DeviceId = deviceFingerprint
|
||||
|
||||
// 设置请求用户登录客户端
|
||||
info.LoginIp = ilobArr[0]
|
||||
info.LoginLocation = ilobArr[1]
|
||||
info.OS = ilobArr[2]
|
||||
info.Browser = ilobArr[3]
|
||||
|
||||
expiresIn := time.Duration(parse.Number(config.Get("jwt.expiresIn")))
|
||||
now := time.Now()
|
||||
exp := now.Add(expiresIn * time.Minute)
|
||||
info.LoginTime = now.UnixMilli()
|
||||
info.ExpireTime = exp.UnixMilli()
|
||||
// 设置新登录IP和登录时间
|
||||
info.User.LoginIp = info.LoginIp
|
||||
info.User.LoginTime = info.LoginTime
|
||||
info.User.Password = ""
|
||||
// 登录信息标识缓存
|
||||
tokenKey := constants.CACHE_TOKEN_DEVICE + ":" + info.DeviceId
|
||||
jsonBytes, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_ = redis.Set("", tokenKey, string(jsonBytes), expiresIn*time.Minute)
|
||||
}
|
||||
|
||||
// UserInfoUpdate 更新访问用户信息缓存
|
||||
func UserInfoUpdate(info UserInfo) {
|
||||
info.User.Password = ""
|
||||
// 登录信息标识缓存
|
||||
tokenKey := constants.CACHE_TOKEN_DEVICE + ":" + info.DeviceId
|
||||
jsonBytes, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
expiresIn, _ := redis.GetExpire("", tokenKey)
|
||||
expiration := time.Duration(expiresIn) * time.Second
|
||||
_ = redis.Set("", tokenKey, string(jsonBytes), expiration)
|
||||
}
|
||||
|
||||
// UserInfoGet 缓存的访问用户信息
|
||||
func UserInfoGet(claims jwt.MapClaims) UserInfo {
|
||||
info := UserInfo{}
|
||||
deviceId := fmt.Sprint(claims[constants.JWT_DEVICE_ID])
|
||||
tokenKey := constants.CACHE_TOKEN_DEVICE + ":" + deviceId
|
||||
hasKey, err := redis.Has("", tokenKey)
|
||||
if hasKey > 0 && err == nil {
|
||||
infoStr, err := redis.Get("", tokenKey)
|
||||
if infoStr == "" || err != nil {
|
||||
return info
|
||||
}
|
||||
if err := json.Unmarshal([]byte(infoStr), &info); err != nil {
|
||||
logger.Errorf("info json err : %v", err)
|
||||
return info
|
||||
}
|
||||
}
|
||||
return info
|
||||
}
|
||||
31
src/framework/utils/crypto/hash.go
Normal file
31
src/framework/utils/crypto/hash.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// SHA256ToBase64 编码字符串
|
||||
func SHA256ToBase64(str string) string {
|
||||
hash := sha256.Sum256([]byte(str))
|
||||
return base64.URLEncoding.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// SHA256Hmac HMAC-SHA256算法
|
||||
func SHA256Hmac(key string, data string) string {
|
||||
mac := hmac.New(sha256.New, []byte(key))
|
||||
mac.Write([]byte(data))
|
||||
return hex.EncodeToString(mac.Sum(nil))
|
||||
}
|
||||
|
||||
// MD5 md5加密
|
||||
func MD5(str string) (md5str string) {
|
||||
data := []byte(str)
|
||||
has := md5.Sum(data)
|
||||
md5str = fmt.Sprintf("%x", has)
|
||||
return md5str
|
||||
}
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
libGlobal "be.ems/lib/global"
|
||||
"be.ems/src/framework/config"
|
||||
)
|
||||
|
||||
// userAgent 自定义 User-Agent
|
||||
var userAgent = fmt.Sprintf("OMC/%s", libGlobal.Version)
|
||||
var userAgent = fmt.Sprintf("OMC/%s", config.Version)
|
||||
|
||||
// Get 发送 GET 请求
|
||||
// timeout 超时时间(毫秒)
|
||||
|
||||
78
src/framework/utils/file/files.go
Normal file
78
src/framework/utils/file/files.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// FileListRow 文件列表行数据
|
||||
type FileListRow struct {
|
||||
FileType string `json:"fileType"` // 文件类型 dir, file, symlink
|
||||
FileMode string `json:"fileMode"` // 文件的权限
|
||||
LinkCount int64 `json:"linkCount"` // 硬链接数目
|
||||
Owner string `json:"owner"` // 所属用户
|
||||
Group string `json:"group"` // 所属组
|
||||
Size int64 `json:"size"` // 文件的大小
|
||||
ModifiedTime int64 `json:"modifiedTime"` // 最后修改时间,单位为秒
|
||||
FileName string `json:"fileName"` // 文件的名称
|
||||
}
|
||||
|
||||
// 文件列表
|
||||
// search 文件名后模糊*
|
||||
//
|
||||
// return 行记录,异常
|
||||
func FileList(path, search string) ([]FileListRow, error) {
|
||||
var rows []FileListRow
|
||||
|
||||
// 构建搜索模式
|
||||
pattern := "*"
|
||||
if search != "" {
|
||||
pattern = search + pattern
|
||||
}
|
||||
|
||||
// 读取目录内容
|
||||
entries, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 遍历目录项
|
||||
for _, entry := range entries {
|
||||
// 匹配文件名
|
||||
matched, err := filepath.Match(pattern, entry.Name())
|
||||
if err != nil || !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
// 获取文件详细信息
|
||||
info, err := entry.Info()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 确定文件类型
|
||||
fileType := "file"
|
||||
if info.IsDir() {
|
||||
fileType = "dir"
|
||||
} else if info.Mode()&os.ModeSymlink != 0 {
|
||||
fileType = "symlink"
|
||||
}
|
||||
|
||||
// 获取系统特定的文件信息
|
||||
linkCount, owner, group := getFileInfo(info)
|
||||
|
||||
// 组装文件信息
|
||||
rows = append(rows, FileListRow{
|
||||
FileMode: info.Mode().String(),
|
||||
FileType: fileType,
|
||||
LinkCount: linkCount,
|
||||
Owner: owner,
|
||||
Group: group,
|
||||
Size: info.Size(),
|
||||
ModifiedTime: info.ModTime().UnixMilli(),
|
||||
FileName: entry.Name(),
|
||||
})
|
||||
}
|
||||
|
||||
return rows, nil
|
||||
}
|
||||
36
src/framework/utils/file/files_unix.go
Normal file
36
src/framework/utils/file/files_unix.go
Normal file
@@ -0,0 +1,36 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// getFileInfo 获取系统特定的文件信息s
|
||||
func getFileInfo(info os.FileInfo) (linkCount int64, owner, group string) {
|
||||
// Unix-like 系统 (Linux, macOS)
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
// 获取用户名
|
||||
ownerName := "root"
|
||||
if stat.Uid != 0 {
|
||||
if u, err := user.LookupId(fmt.Sprint(stat.Uid)); err == nil {
|
||||
ownerName = u.Username
|
||||
}
|
||||
}
|
||||
|
||||
// 获取组名
|
||||
groupName := "root"
|
||||
if stat.Gid != 0 {
|
||||
if g, err := user.LookupGroupId(fmt.Sprint(stat.Gid)); err == nil {
|
||||
groupName = g.Name
|
||||
}
|
||||
}
|
||||
|
||||
return int64(stat.Nlink), ownerName, groupName
|
||||
}
|
||||
return 1, "", ""
|
||||
}
|
||||
13
src/framework/utils/file/files_windows.go
Normal file
13
src/framework/utils/file/files_windows.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
package file
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// getFileInfo 获取系统特定的文件信息
|
||||
func getFileInfo(_ os.FileInfo) (linkCount int64, owner, group string) {
|
||||
return 1, "Administrator", "Administrators"
|
||||
}
|
||||
@@ -163,11 +163,11 @@ func Reset() error {
|
||||
// return fmt.Errorf("not support window")
|
||||
} else {
|
||||
// 重置数据库
|
||||
if _, err := cmd.Execf("sudo cp -rf /usr/local/omc/etc/db/omc_db.sqlite /usr/local/omc/database/omc_db.sqlite"); err != nil {
|
||||
if _, err := cmd.Execf("/usr/local/etc/omc/script/setup.sh -i"); err != nil {
|
||||
return err
|
||||
}
|
||||
// 重启服务
|
||||
if _, err := cmd.Execf("nohup sh -c \"sleep 1s && %s\" > /dev/null 2>&1 &", "sudo systemctl restart restagent"); err != nil {
|
||||
if _, err := cmd.Execf("nohup sh -c \"sleep 1s && %s\" > /dev/null 2>&1 &", "sudo systemctl restart omc"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user