Merge branch 'lichang' into lite

This commit is contained in:
TsMask
2025-04-29 16:15:16 +08:00
670 changed files with 17749 additions and 19324 deletions

View File

@@ -7,12 +7,15 @@ import (
"be.ems/src/framework/errorcatch"
"be.ems/src/framework/middleware"
"be.ems/src/framework/middleware/security"
"be.ems/src/modules/auth"
"be.ems/src/modules/chart"
"be.ems/src/modules/common"
"be.ems/src/modules/crontask"
"be.ems/src/modules/monitor"
networkdata "be.ems/src/modules/network_data"
networkelement "be.ems/src/modules/network_element"
"be.ems/src/modules/oauth2"
"be.ems/src/modules/system"
"be.ems/src/modules/tool"
"be.ems/src/modules/trace"
@@ -21,26 +24,8 @@ import (
"github.com/gin-gonic/gin"
)
// AppEngine 路由函数句柄,交给由 http.ListenAndServe(addr, router)
func AppEngine() *gin.Engine {
app := initAppEngine()
// 初始全局默认
initDefeat(app)
// 初始模块路由
initModulesRoute(app)
// 首次安装启动记录
// machine.Launch()
// 读取服务配置
app.ForwardedByClientIP = config.Get("server.proxy").(bool)
return app
}
// 初始应用引擎
func initAppEngine() *gin.Engine {
func AppEngine() *gin.Engine {
var app *gin.Engine
// 禁止控制台日志输出的颜色
@@ -54,12 +39,12 @@ func initAppEngine() *gin.Engine {
} else {
app = gin.Default()
}
app.ForwardedByClientIP = true
return app
}
// 初始全局默认
func initDefeat(app *gin.Engine) {
func DefeatConfig(app *gin.Engine) {
// 全局中间件
if config.Env() == "local" {
app.Use(middleware.Report())
@@ -94,15 +79,21 @@ func initDefeat(app *gin.Engine) {
}
// 初始模块路由
func initModulesRoute(app *gin.Engine) {
// 通用模块
common.Setup(app)
func ModulesRoute(app *gin.Engine) {
// 系统模块
system.Setup(app)
// 认证模块
auth.Setup(app)
// 开放客户端模块
oauth2.Setup(app)
// 通用模块
common.Setup(app)
// 网元功能模块
networkelement.Setup(app)
// 网元数据模块
networkdata.Setup(app)
// 跟踪模块
trace.Setup(app)
// 图表模块
@@ -111,6 +102,7 @@ func initModulesRoute(app *gin.Engine) {
tool.Setup(app)
// ws 模块
ws.Setup(app)
// 调度任务模块--暂无接口
crontask.Setup(app)
// 监控模块 - 含调度处理加入队列,放最后

View File

@@ -1,7 +1,11 @@
# 应用服务配置
server:
# 是否开启代理
proxy: false
# 运行版本 std/lite
serverVersion: "std"
# 运行模式 system/docker
serverMode: "system"
# 登录认证,默认打开
serverLoginAuth: true
# 接口加密,默认关闭
serverCryptoApi: false
# 日志
logger:
@@ -131,33 +135,33 @@ security:
# JWT 令牌配置
jwt:
# 令牌算法 HS256 HS384 HS512
algorithm: "HS512"
algorithm: "HS256"
# 令牌密钥
secret: "217a0481c7f9cfe1cb547d32ee012b0f"
# 令牌有效期默认120分钟)
expiresIn: 120
# 验证令牌有效期相差不足xx分钟自动刷新缓存
refreshIn: 20
# 访问令牌有效期默认15分钟)
expiresIn: 15
# 刷新令牌有效期默认7*24*60分钟
refreshIn: 10080
# DB 数据源
database:
dataSource:
# 默认数据库实例
# default:
# type: "mysql"
# host: "127.0.0.1"
# port: 3306
# username: "<username>"
# password: "<password>"
# database: "<database>"
# logging: false
std:
type: "mysql"
host: "127.0.0.1"
port: 3306
username: "<username>"
password: "<password>"
database: "<database>"
logging: false
# 内置轻量级数据库
lite:
type: "sqlite"
database: "<database path>"
logging: false
# 多个数据源时可以用这个指定默认的数据源
defaultDataSourceName: "default"
defaultDataSourceName: "std"
# Redis 缓存数据
redis:
@@ -179,21 +183,9 @@ aes:
# 应用密钥
appKey: "E83dbfeb35BA4839232e2761b0FE5f32"
# 用户配置
user:
# 登录认证,默认打开
loginAuth: true
# 接口加密,默认打开
cryptoApi: false
# 密码
password:
# 密码最大错误次数
maxRetryCount: 5
# 密码锁定时间,单位分钟默认10分钟
lockTime: 10
# 设定为系统管理员的用户ID
system:
- 1
# 设定为系统管理员的用户ID
systemUser:
- 1
# char 字符验证码配置
charCaptcha:

View File

@@ -1,10 +1,6 @@
# 应用服务配置
server:
proxy: true
# 日志
logger:
fileDir: "C:/var/log"
fileDir: "/var/log"
fileName: "omc.log"
level: 0 # 日志记录的等级 0:silent<1:info<2:warn<3:error
maxDay: 7 # 日志会保留 180 天
@@ -15,31 +11,31 @@ staticFile:
# 默认资源dir目录需要预先创建
default:
prefix: "/static"
dir: "C:/usr/local/omc/static"
dir: "/usr/local/omc/static"
# 文件上传资源目录映射,与项目目录同级
upload:
prefix: "/upload"
dir: "C:/usr/local/omc/upload"
dir: "/usr/local/omc/upload"
# DB 数据源
database:
dataSource:
# 默认数据库实例
# default:
# type: "mysql"
# host: "127.0.0.1"
# port: 3306
# username: "<username>"
# password: "<password>"
# database: "<database>"
# logging: false
std:
type: "mysql"
host: "127.0.0.1"
port: 3306
username: "root"
password: "1000omc@kp!"
database: "omc_db"
logging: true
# 内置轻量级数据库
lite:
type: "sqlite"
database: "<database path>"
logging: false
database: "/usr/local/etc/omc/database/omc_db.sqlite"
logging: true
# 多个数据源时可以用这个指定默认的数据源
defaultDataSourceName: "lite"
defaultDataSourceName: "std"
# Redis 缓存数据
redis:
@@ -47,7 +43,7 @@ redis:
default:
port: 6379 # Redis port
host: "127.0.0.1" # Redis host
password: "<password>"
password: "helloearth"
db: 0 # Redis db_num
# 多个数据源时可以用这个指定默认的数据源
defaultDataSourceName: "default"

View File

@@ -1,27 +1,22 @@
# 应用服务配置
server:
# 是否开启代理
proxy: true
# DB 数据源
database:
dataSource:
# 默认数据库实例
# default:
# type: "mysql"
# host: "127.0.0.1"
# port: 3306
# username: "<username>"
# password: "<password>"
# database: "<database>"
# logging: false
std:
type: "mysql"
host: "127.0.0.1"
port: 33066
username: "root"
password: "1000omc@kp!"
database: "omc_db"
logging: false
# 内置轻量级数据库
lite:
type: "sqlite"
database: "/usr/local/omc/database/omc_db.sqlite"
database: "/usr/local/etc/omc/database/omc_db.sqlite"
logging: false
# 多个数据源时可以用这个指定默认的数据源
defaultDataSourceName: "lite"
defaultDataSourceName: "std"
# Redis 缓存数据
redis:
@@ -30,6 +25,6 @@ redis:
port: 6379 # Redis port
host: "127.0.0.1" # Redis host
password: "helloearth"
db: 10 # Redis db_num
db: 0 # Redis db_num
# 多个数据源时可以用这个指定默认的数据源
defaultDataSourceName: "default"

View File

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

View File

@@ -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 网元信息管理

View File

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

View File

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

View File

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

View File

@@ -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]
}
// 创建日志对象

View File

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

View File

@@ -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文件的通用函数

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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: 英文

View File

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

View File

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

View File

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

View File

@@ -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用于存储拆分后的键值对

View 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"` // 权限列表
}

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

View File

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

View File

@@ -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"` // 登录时间时间戳

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

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

View File

@@ -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 超时时间(毫秒)

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

View 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, "", ""
}

View 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"
}

View File

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

85
src/modules/auth/auth.go Normal file
View File

@@ -0,0 +1,85 @@
package auth
import (
"be.ems/src/framework/logger"
"be.ems/src/framework/middleware"
"be.ems/src/modules/auth/controller"
"github.com/gin-gonic/gin"
)
// 模块路由注册
func Setup(router *gin.Engine) {
logger.Infof("开始加载 ====> auth 模块路由")
// 系统可暴露的配置信息
router.GET("/sys-conf", controller.NewSysConf.Handler)
// 系统引导初始化
guideGroup := router.Group("/bootloader")
{
guideGroup.POST("", controller.NewBootloader.Start)
guideGroup.PUT("", middleware.AuthorizeUser(nil), controller.NewBootloader.Done)
guideGroup.DELETE("", middleware.AuthorizeUser(nil), controller.NewBootloader.Reset)
guideGroup.PUT("/account", middleware.AuthorizeUser(nil), controller.NewBootloader.Account)
}
// 验证码操作
router.GET("/captcha-image",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 60,
Type: middleware.LIMIT_IP,
}),
controller.NewCaptcha.Image,
)
// 账号身份操作
{
router.POST("/auth/login",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
controller.NewAccount.Login,
)
router.POST("/auth/logout",
middleware.RateLimit(middleware.LimitOption{
Time: 120,
Count: 15,
Type: middleware.LIMIT_IP,
}),
controller.NewAccount.Logout,
)
router.POST("/auth/refresh-token",
middleware.RateLimit(middleware.LimitOption{
Time: 60,
Count: 5,
Type: middleware.LIMIT_IP,
}),
controller.NewAccount.RefreshToken,
)
router.GET("/me",
middleware.AuthorizeUser(nil),
controller.NewAccount.Me,
)
router.GET("/router",
middleware.AuthorizeUser(nil),
controller.NewAccount.Router,
)
}
// 账号注册操作
{
router.POST("/auth/register",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 10,
Type: middleware.LIMIT_IP,
}),
controller.NewRegister.Register,
)
}
}

View File

@@ -0,0 +1,303 @@
package controller
import (
"fmt"
"time"
"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"
"be.ems/src/framework/utils/parse"
"be.ems/src/modules/auth/model"
"be.ems/src/modules/auth/service"
systemModelVO "be.ems/src/modules/system/model/vo"
systemService "be.ems/src/modules/system/service"
"github.com/gin-gonic/gin"
)
// 实例化控制层 AccountController 结构体
var NewAccount = &AccountController{
accountService: service.NewAccount,
sysLogLoginService: systemService.NewSysLogLogin,
}
// 账号身份操作处理
//
// PATH /
type AccountController struct {
accountService *service.Account // 账号身份操作服务
sysLogLoginService *systemService.SysLogLogin // 系统登录访问
}
// Login 系统登录
//
// POST /auth/login
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /auth/login [post]
func (s AccountController) Login(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.LoginBody
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 校验验证码 根据错误信息,创建系统访问记录
if err := s.accountService.ValidateCaptcha(body.Code, body.UUID); err != nil {
msg := fmt.Sprintf("%s code %s", err.Error(), body.Code)
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, msg,
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
// 登录用户信息
info, err := s.accountService.ByUsername(body.Username, body.Password)
if err != nil {
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, err.Error(),
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
data := map[string]any{}
if !config.IsSystemUser(info.UserId) {
// 强制改密码
forcePasswdChange, err := s.accountService.PasswordCountOrExpireTime(info.User.LoginCount, info.User.PasswordUpdateTime)
if err != nil {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
if forcePasswdChange {
data["forcePasswdChange"] = true
}
}
deviceFingerprint := reqctx.DeviceFingerprint(c, info.UserId)
// 生成访问令牌
accessToken, expiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "access")
if accessToken == "" || expiresIn == 0 {
c.JSON(200, resp.ErrMsg("token generation failed"))
return
}
// 生成刷新令牌
refreshToken, refreshExpiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "refresh")
// 记录令牌,创建系统访问记录
token.UserInfoCreate(&info, deviceFingerprint, [4]string{ipaddr, location, os, browser})
s.accountService.UpdateLoginDateAndIP(info)
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_YES, "app.common.loginSuccess",
[4]string{ipaddr, location, os, browser},
)
data["tokenType"] = constants.HEADER_PREFIX
data["accessToken"] = accessToken
data["expiresIn"] = expiresIn
data["refreshToken"] = refreshToken
data["refreshExpiresIn"] = refreshExpiresIn
data["userId"] = info.UserId
c.JSON(200, resp.OkData(data))
}
// Logout 系统登出
//
// POST /auth/logout
func (s AccountController) Logout(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
tokenStr := reqctx.Authorization(c)
if tokenStr != "" {
// 存在token时记录退出信息
userName, err := token.UserInfoRemove(tokenStr)
if err != nil {
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 创建系统访问记录
s.sysLogLoginService.Insert(
userName, constants.STATUS_YES, "app.common.logoutSuccess",
[4]string{ipaddr, location, os, browser},
)
}
}
c.JSON(200, resp.OkMsg(i18n.TKey(language, "app.common.logoutSuccess")))
}
// RefreshToken 刷新Token
//
// POST /auth/refresh-token
func (s AccountController) RefreshToken(c *gin.Context) {
var body struct {
RefreshToken string `json:"refreshToken" binding:"required"` // 刷新令牌
}
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 验证刷新令牌是否有效
claims, err := token.UserTokenVerify(body.RefreshToken, "refresh")
if err != nil {
c.JSON(401, resp.CodeMsg(401001, err.Error()))
return
}
userId := parse.Number(claims[constants.JWT_USER_ID])
// 登录用户信息
info, err := s.accountService.ByUserId(userId)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
// 设备指纹信息是否一致
deviceId := fmt.Sprint(claims[constants.JWT_DEVICE_ID])
deviceFingerprint := reqctx.DeviceFingerprint(c, userId)
if deviceId != deviceFingerprint {
c.JSON(200, resp.ErrMsg("device fingerprint mismatch"))
return
}
// 生成访问令牌
accessToken, expiresIn := token.UserTokenCreate(userId, deviceFingerprint, "access")
if accessToken == "" || expiresIn == 0 {
c.JSON(200, resp.ErrMsg("token generation failed"))
return
}
// 生成刷新令牌
now := time.Now()
exp, _ := claims.GetExpirationTime()
iat, _ := claims.GetIssuedAt()
refreshExpiresIn := int64(exp.Sub(now).Seconds())
refreshToken := body.RefreshToken
// 如果当前时间大于过期时间的一半,则生成新令牌
halfExp := exp.Add(-(exp.Sub(iat.Time)) / 2)
if now.After(halfExp) {
refreshToken, refreshExpiresIn = token.UserTokenCreate(userId, deviceFingerprint, "refresh")
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 记录令牌,创建系统访问记录
token.UserInfoCreate(&info, deviceFingerprint, [4]string{ipaddr, location, os, browser})
s.accountService.UpdateLoginDateAndIP(info)
s.sysLogLoginService.Insert(
info.User.UserName, constants.STATUS_YES, "Refresh Access Token Successful",
[4]string{ipaddr, location, os, browser},
)
// 返回访问令牌和刷新令牌
c.JSON(200, resp.OkData(map[string]any{
"tokenType": constants.HEADER_PREFIX,
"accessToken": accessToken,
"expiresIn": expiresIn,
"refreshToken": refreshToken,
"refreshExpiresIn": refreshExpiresIn,
"userId": userId,
}))
}
// Me 登录用户信息
//
// GET /me
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Login User Information
// @Description Login User Information
// @Router /me [get]
func (s AccountController) Me(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
info, err := reqctx.LoginUser(c)
if err != nil {
c.JSON(401, resp.CodeMsg(401002, err.Error()))
return
}
// 角色权限集合,系统管理员拥有所有权限
isSystemUser := config.IsSystemUser(info.UserId)
roles, perms := s.accountService.RoleAndMenuPerms(info.UserId, isSystemUser)
info.User.NickName = i18n.TKey(language, info.User.NickName)
info.User.Remark = i18n.TKey(language, info.User.Remark)
info.User.Dept.DeptName = i18n.TKey(language, info.User.Dept.DeptName)
for ri := range info.User.Roles {
info.User.Roles[ri].RoleName = i18n.TKey(language, info.User.Roles[ri].RoleName)
}
data := map[string]any{
"user": info.User,
"roles": roles,
"permissions": perms,
}
if !isSystemUser {
// 强制改密码
forcePasswdChange, _ := s.accountService.PasswordCountOrExpireTime(info.User.LoginCount, info.User.PasswordUpdateTime)
if forcePasswdChange {
data["forcePasswdChange"] = true
}
}
c.JSON(200, resp.OkData(data))
}
// Router 登录用户路由信息
//
// GET /router
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Login User Routing Information
// @Description Login User Routing Information
// @Router /router [get]
func (s AccountController) Router(c *gin.Context) {
loginUserId := reqctx.LoginUserToUserID(c)
// 前端路由,系统管理员拥有所有
isSystemUser := config.IsSystemUser(loginUserId)
buildMenus := s.accountService.RouteMenus(loginUserId, isSystemUser)
// 闭包函数处理多语言
language := reqctx.AcceptLanguage(c)
var converI18n func(language string, arr *[]systemModelVO.Router)
converI18n = func(language string, arr *[]systemModelVO.Router) {
for i := range *arr {
(*arr)[i].Meta.Title = i18n.TKey(language, (*arr)[i].Meta.Title)
if len((*arr)[i].Children) > 0 {
converI18n(language, &(*arr)[i].Children)
}
}
}
converI18n(language, &buildMenus)
c.JSON(200, resp.OkData(buildMenus))
}

View File

@@ -1,18 +1,16 @@
package controller
import (
"fmt"
"runtime"
"strings"
"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"
"be.ems/src/framework/utils/cmd"
"be.ems/src/framework/utils/machine"
"be.ems/src/framework/utils/regular"
"be.ems/src/modules/common/service"
"be.ems/src/modules/auth/service"
systemService "be.ems/src/modules/system/service"
"github.com/gin-gonic/gin"
@@ -55,7 +53,7 @@ func (s *BootloaderController) Start(c *gin.Context) {
}
// 登录用户信息
loginUser := token.TokenInfo{
info := token.UserInfo{
UserId: sysUser.UserId,
DeptId: sysUser.DeptId,
User: sysUser,
@@ -65,21 +63,26 @@ func (s *BootloaderController) Start(c *gin.Context) {
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
deviceFingerprint := reqctx.DeviceFingerprint(c, info.UserId)
// 生成令牌,创建系统访问记录
tokenStr := token.Create(&loginUser, [4]string{ipaddr, location, os, browser})
if tokenStr == "" {
c.JSON(200, resp.Err(nil))
// 生成访问令牌
accessToken, expiresIn := token.UserTokenCreate(info.UserId, deviceFingerprint, "access")
if accessToken == "" || expiresIn == 0 {
c.JSON(200, resp.ErrMsg("token generation failed"))
return
} else {
s.accountService.UpdateLoginDateAndIP(loginUser)
}
// 记录令牌,创建系统访问记录
token.UserInfoCreate(&info, deviceFingerprint, [4]string{ipaddr, location, os, browser})
// 创建系统访问记录
s.accountService.UpdateLoginDateAndIP(info)
c.JSON(200, resp.OkData(map[string]any{
"accessToken": tokenStr,
"tokenType": strings.TrimRight(constants.HEADER_PREFIX, " "),
"expiresIn": (loginUser.ExpireTime - loginUser.LoginTime) / 1000,
"userId": loginUser.UserId,
"tokenType": constants.HEADER_PREFIX,
"accessToken": accessToken,
"expiresIn": expiresIn,
"refreshToken": "",
"refreshExpiresIn": 0,
"userId": info.UserId,
}))
}
@@ -105,7 +108,7 @@ func (s *BootloaderController) Done(c *gin.Context) {
}
// 清除授权信息
token.Remove(reqctx.Authorization(c))
token.UserInfoRemove(reqctx.Authorization(c))
c.JSON(200, resp.Ok(nil))
}
@@ -146,7 +149,7 @@ func (s *BootloaderController) Reset(c *gin.Context) {
}
// 清除授权信息
token.Remove(reqctx.Authorization(c))
token.UserInfoRemove(reqctx.Authorization(c))
c.JSON(200, resp.Ok(nil))
}
@@ -160,15 +163,22 @@ func (s *BootloaderController) Account(c *gin.Context) {
Password string `json:"password" binding:"required"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if !regular.ValidPassword(body.Password) {
// 登录密码至少包含大小写字母、数字、特殊符号且不少于6位
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "user.errPasswd")))
// 检查用户密码策略强度
ok, errMsg := s.sysUserService.ValidatePasswordPolicy(body.Password, language)
if !ok {
c.JSON(200, resp.ErrMsg(errMsg))
return
}
// if !regular.ValidPassword(body.Password) {
// // 登录密码至少包含大小写字母、数字、特殊符号且不少于6位
// c.JSON(200, resp.ErrMsg(i18n.TKey(language, "user.errPasswd")))
// return
// }
// 是否完成引导
launchInfo := machine.LaunchInfo

View File

@@ -1,8 +1,12 @@
package controller
import (
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
"be.ems/src/framework/config"
"be.ems/src/framework/constants"
"be.ems/src/framework/database/redis"
@@ -10,42 +14,30 @@ import (
"be.ems/src/framework/resp"
"be.ems/src/framework/utils/parse"
systemService "be.ems/src/modules/system/service"
"github.com/gin-gonic/gin"
"github.com/mojocn/base64Captcha"
)
// 实例化控制层 CaptchaController 结构体
// NewCaptcha 实例化控制层
var NewCaptcha = &CaptchaController{
sysConfigService: systemService.NewSysConfig,
}
// 验证码操作处理
// CaptchaController 验证码操作 控制层处理
//
// PATH /
type CaptchaController struct {
sysConfigService *systemService.SysConfig // 参数配置服务
}
// 获取验证码
// Image 获取验证码-图片
//
// GET /captchaImage
//
// @Tags common
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Get CAPTCHA
// @Description Get CAPTCHA
// @Router /captchaImage [get]
func (s *CaptchaController) Image(c *gin.Context) {
// GET /captcha-image
func (s CaptchaController) Image(c *gin.Context) {
// 从数据库配置获取验证码开关 true开启false关闭
captchaEnabledStr := s.sysConfigService.FindValueByKey("sys.account.captchaEnabled")
captchaEnabled := parse.Boolean(captchaEnabledStr)
if !captchaEnabled {
c.JSON(200, resp.Ok(map[string]any{
"captchaEnabled": captchaEnabled,
c.JSON(200, resp.OkData(map[string]any{
"enabled": captchaEnabled,
}))
return
}
@@ -53,14 +45,16 @@ func (s *CaptchaController) Image(c *gin.Context) {
// 生成唯一标识
verifyKey := ""
data := map[string]any{
"captchaEnabled": captchaEnabled,
"uuid": "",
"img": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
"enabled": captchaEnabled,
"uuid": "",
"img": "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
}
// 验证码有效期,单位秒
captchaExpiration := 2 * 60 * time.Second
// 从数据库配置获取验证码类型 math 数值计算 char 字符验证
captchaType := s.sysConfigService.FindValueByKey("sys.account.captchaType")
if captchaType == constants.CAPTCHA_TYPE_MATH {
if captchaType == "math" {
math := config.Get("mathCaptcha").(map[string]any)
driverCaptcha := &base64Captcha.DriverMath{
//Height png height in pixel.
@@ -81,16 +75,15 @@ func (s *CaptchaController) Image(c *gin.Context) {
// 验证码表达式解析输出
item, err := driverCaptcha.DrawCaptcha(question)
if err != nil {
logger.Infof("Generate Id Question Answer %s %s : %v", captchaType, question, err)
logger.Infof("generate id question answer %s %s : %v", captchaType, question, err)
} else {
data["uuid"] = id
data["img"] = item.EncodeB64string()
expiration := constants.CAPTCHA_EXPIRATION * time.Second
verifyKey = constants.CACHE_CAPTCHA_CODE + ":" + id
redis.SetByExpire("", verifyKey, answer, expiration)
_ = redis.Set("", verifyKey, answer, captchaExpiration)
}
}
if captchaType == constants.CAPTCHA_TYPE_CHAR {
if captchaType == "char" {
char := config.Get("charCaptcha").(map[string]any)
driverCaptcha := &base64Captcha.DriverString{
//Height png height in pixel.
@@ -115,13 +108,12 @@ func (s *CaptchaController) Image(c *gin.Context) {
// 验证码表达式解析输出
item, err := driverCaptcha.DrawCaptcha(question)
if err != nil {
logger.Infof("Generate Id Question Answer %s %s : %v", captchaType, question, err)
logger.Infof("generate id question answer %s %s : %v", captchaType, question, err)
} else {
data["uuid"] = id
data["img"] = item.EncodeB64string()
expiration := constants.CAPTCHA_EXPIRATION * time.Second
verifyKey = constants.CACHE_CAPTCHA_CODE + ":" + id
redis.SetByExpire("", verifyKey, answer, expiration)
_ = redis.Set("", verifyKey, strings.ToLower(answer), captchaExpiration)
}
}
@@ -129,8 +121,8 @@ func (s *CaptchaController) Image(c *gin.Context) {
if config.Env() == "local" {
text, _ := redis.Get("", verifyKey)
data["text"] = text
c.JSON(200, resp.Ok(data))
c.JSON(200, resp.OkData(data))
return
}
c.JSON(200, resp.Ok(data))
c.JSON(200, resp.OkData(data))
}

View File

@@ -8,8 +8,8 @@ import (
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/utils/regular"
"be.ems/src/modules/common/model"
"be.ems/src/modules/common/service"
"be.ems/src/modules/auth/model"
"be.ems/src/modules/auth/service"
systemService "be.ems/src/modules/system/service"
"github.com/gin-gonic/gin"
@@ -29,15 +29,32 @@ type RegisterController struct {
sysLogLoginService *systemService.SysLogLogin // 系统登录访问服务
}
// 账号注册
// Register 账号注册
//
// GET /register
func (s *RegisterController) Register(c *gin.Context) {
// POST /auth/register
func (s RegisterController) Register(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.RegisterBody
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 校验验证码
err := s.registerService.ValidateCaptcha(body.Code, body.UUID)
// 根据错误信息,创建系统访问记录
if err != nil {
msg := fmt.Sprintf("%s code %s", err.Error(), body.Code)
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, msg,
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
@@ -58,26 +75,7 @@ func (s *RegisterController) Register(c *gin.Context) {
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 校验验证码
err := s.registerService.ValidateCaptcha(
body.Code,
body.UUID,
)
// 根据错误信息,创建系统访问记录
if err != nil {
msg := err.Error() + " code: " + body.Code
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, msg,
[4]string{ipaddr, location, os, browser},
)
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
// 进行注册
userId, err := s.registerService.ByUserName(body.Username, body.Password)
if err == nil {
msg := i18n.TTemplate(language, "register.successMsg", map[string]any{"name": body.Username, "id": userId})

View File

@@ -1,31 +1,47 @@
package service
package controller
import (
"fmt"
"be.ems/lib/global"
"be.ems/src/framework/config"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
systemService "be.ems/src/modules/system/service"
"github.com/gin-gonic/gin"
)
// 实例化服务层 Commont 结构体
var NewCommont = &Commont{
// 实例化控制层 SysConfController 结构体
var NewSysConf = &SysConfController{
sysUserService: systemService.NewSysUser,
sysConfigService: systemService.NewSysConfig,
}
// 通用请求 服务层处理
type Commont struct {
// 系统的配置信息
//
// PATH /sys-conf
type SysConfController struct {
sysUserService *systemService.SysUser // 用户信息服务
sysConfigService *systemService.SysConfig // 参数配置服务
}
// SystemConfigInfo 系统配置信息
func (s *Commont) SystemConfigInfo() map[string]string {
// 系统配置信息
//
// GET /
//
// @Tags common
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Summary Configuration information for the system
// @Description Configuration information for the system
// @Router /sys-conf [get]
func (s SysConfController) Handler(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
infoMap := map[string]string{}
// 获取打包注入的全局变量信息
infoMap["version"] = global.Version
// infoMap["buildTime"] = global.BuildTime
infoMap["version"] = config.Version
// 系统首次使用标记
// launchInfo := machine.LaunchInfo
// if launchInfo != nil {
@@ -37,10 +53,12 @@ func (s *Commont) SystemConfigInfo() map[string]string {
// } else {
// infoMap[constants.LAUNCH_BOOTLOADER] = "true"
// }
// 服务版本
infoMap["serverVersion"] = fmt.Sprint(config.Get("serverVersion"))
// 用户登录认证
infoMap["loginAuth"] = fmt.Sprint(config.Get("user.loginAuth"))
infoMap["loginAuth"] = fmt.Sprint(config.Get("serverLoginAuth"))
// 用户接口加密
infoMap["cryptoApi"] = fmt.Sprint(config.Get("user.cryptoApi"))
infoMap["cryptoApi"] = fmt.Sprint(config.Get("serverCryptoApi"))
// 序列号
infoMap["serialNum"] = fmt.Sprint(config.Get("omc.sn"))
// 获取LOGO类型
@@ -53,10 +71,10 @@ func (s *Commont) SystemConfigInfo() map[string]string {
infoMap["filePathBrand"] = filePathBrand
// 获取系统名称
title := s.sysConfigService.FindValueByKey("sys.title")
infoMap["title"] = title
infoMap["title"] = i18n.TKey(language, title)
// 获取版权声明
copyright := s.sysConfigService.FindValueByKey("sys.copyright")
infoMap["copyright"] = copyright
infoMap["copyright"] = i18n.TKey(language, copyright)
// 获取是否开启用户注册功能
registerUser := s.sysConfigService.FindValueByKey("sys.account.registerUser")
infoMap["registerUser"] = registerUser
@@ -75,5 +93,6 @@ func (s *Commont) SystemConfigInfo() map[string]string {
// 国际化默认语言
i18nDefault := s.sysConfigService.FindValueByKey("sys.i18n.default")
infoMap["i18nDefault"] = i18nDefault
return infoMap
c.JSON(200, resp.OkData(infoMap))
}

View File

@@ -56,25 +56,25 @@ func (s *Account) ValidateCaptcha(code, uuid string) error {
}
// ByUsername 登录创建用户信息
func (s Account) ByUsername(username, password string) (token.TokenInfo, error) {
tokenInfo := token.TokenInfo{}
func (s Account) ByUsername(username, password string) (token.UserInfo, error) {
info := token.UserInfo{}
// 检查密码重试次数
retryKey, retryCount, lockTime, err := s.passwordRetryCount(username)
if err != nil {
return tokenInfo, err
return info, err
}
// 查询用户登录账号
sysUser := s.sysUserService.FindByUserName(username)
if sysUser.UserName != username {
return tokenInfo, fmt.Errorf("login.errNameOrPasswd")
return info, fmt.Errorf("login.errNameOrPasswd")
}
if sysUser.DelFlag == constants.STATUS_YES {
return tokenInfo, fmt.Errorf("login.errDelFlag")
return info, fmt.Errorf("login.errDelFlag")
}
if sysUser.StatusFlag == constants.STATUS_NO {
return tokenInfo, fmt.Errorf("login.errStatus")
return info, fmt.Errorf("login.errStatus")
}
// 检验用户密码
@@ -82,30 +82,61 @@ func (s Account) ByUsername(username, password string) (token.TokenInfo, error)
if compareBool {
s.CleanLoginRecordCache(sysUser.UserName) // 清除错误记录次数
} else {
_ = redis.SetByExpire("", retryKey, retryCount+1, lockTime)
return tokenInfo, fmt.Errorf("login.errNameOrPasswd")
_ = redis.Set("", retryKey, retryCount+1, lockTime)
return info, fmt.Errorf("login.errNameOrPasswd")
}
// 登录用户信息
tokenInfo.UserId = sysUser.UserId
tokenInfo.DeptId = sysUser.DeptId
tokenInfo.User = sysUser
info.UserId = sysUser.UserId
info.DeptId = sysUser.DeptId
info.User = sysUser
// 用户权限组标识
if config.IsSystemUser(sysUser.UserId) {
tokenInfo.Permissions = []string{constants.SYS_PERMISSION_SYSTEM}
info.Permissions = []string{constants.SYS_PERMISSION_SYSTEM}
} else {
perms := s.sysMenuService.FindPermsByUserId(sysUser.UserId)
tokenInfo.Permissions = parse.RemoveDuplicates(perms)
info.Permissions = parse.RemoveDuplicates(perms)
}
return tokenInfo, nil
return info, nil
}
// ByUserId 用户ID刷新令牌创建用户信息
func (s Account) ByUserId(userId int64) (token.UserInfo, error) {
info := token.UserInfo{}
// 查询用户登录账号
sysUser := s.sysUserService.FindById(userId)
if sysUser.UserId != userId {
return info, fmt.Errorf("user does not exist")
}
if sysUser.DelFlag == constants.STATUS_YES {
return info, fmt.Errorf("sorry, your account has been deleted. Sorry, your account has been deleted")
}
if sysUser.StatusFlag == constants.STATUS_NO {
return info, fmt.Errorf("sorry, your account has been disabled")
}
// 登录用户信息
info.UserId = sysUser.UserId
info.DeptId = sysUser.DeptId
info.User = sysUser
// 用户权限组标识
if config.IsSystemUser(sysUser.UserId) {
info.Permissions = []string{constants.SYS_PERMISSION_SYSTEM}
} else {
perms := s.sysMenuService.FindPermsByUserId(sysUser.UserId)
info.Permissions = parse.RemoveDuplicates(perms)
}
return info, nil
}
// UpdateLoginDateAndIP 更新登录时间和IP
func (s Account) UpdateLoginDateAndIP(tokenInfo token.TokenInfo) bool {
user := s.sysUserService.FindById(tokenInfo.UserId)
func (s Account) UpdateLoginDateAndIP(info token.UserInfo) bool {
user := s.sysUserService.FindById(info.UserId)
user.Password = "" // 密码不更新
user.LoginIp = tokenInfo.LoginIp
user.LoginTime = tokenInfo.LoginTime
user.LoginCount += 1
user.LoginIp = info.LoginIp
user.LoginTime = info.LoginTime
return s.sysUserService.Update(user) > 0
}
@@ -127,9 +158,6 @@ func (s Account) passwordRetryCount(userName string) (string, int64, time.Durati
// 验证登录次数和错误锁定时间
maxRetryCount := parse.Number(maxRetryCountStr)
lockTime := parse.Number(lockTimeStr)
// 验证登录次数和错误锁定时间
// maxRetryCount := config.Get("user.password.maxRetryCount").(int)
// lockTime := config.Get("user.password.lockTime").(int)
// 验证缓存记录次数
retryKey := fmt.Sprintf("%s:%s", constants.CACHE_PWD_ERR_COUNT, userName)
@@ -147,6 +175,26 @@ func (s Account) passwordRetryCount(userName string) (string, int64, time.Durati
return retryKey, retryCountInt64, time.Duration(lockTime) * time.Minute, nil
}
// PasswordCountOrExpireTime 首次登录或密码过期时间
func (s Account) PasswordCountOrExpireTime(loginCount, passwordUpdateTime int64) (bool, error) {
forcePasswdChange := false
// 从数据库配置获取-首次登录密码修改
fristPasswdChangeStr := s.sysConfigService.FindValueByKey("sys.user.fristPasswdChange")
if parse.Boolean(fristPasswdChangeStr) {
forcePasswdChange = loginCount < 1 || passwordUpdateTime == 0
}
// 非首次登录,判断密码是否过期
if !forcePasswdChange {
alert, err := s.sysUserService.ValidatePasswordExpireTime(passwordUpdateTime)
if err != nil {
return alert, err
}
forcePasswdChange = alert
}
return forcePasswdChange, nil
}
// RoleAndMenuPerms 角色和菜单数据权限
func (s Account) RoleAndMenuPerms(userId int64, isSystemUser bool) ([]string, []string) {
if isSystemUser {

View File

@@ -78,7 +78,7 @@ func (s Register) ByUserName(username, password string) (int64, error) {
if insertId > 0 {
return insertId, nil
}
return 0, fmt.Errorf("failed to register user [%s]. Please contact the system administrator", username)
return 0, fmt.Errorf("failed to register user [%s]. Please contact the GM", username)
}
// registerRoleInit 注册初始角色

View File

@@ -17,20 +17,20 @@ func Setup(router *gin.Engine) {
chartGraphGroup := router.Group("/chart/graph")
{
chartGraphGroup.GET("",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewChartGraph.Load,
)
chartGraphGroup.GET("/groups",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewChartGraph.GroupNames,
)
chartGraphGroup.POST("",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.chartGraph", collectlogs.BUSINESS_TYPE_UPDATE)),
controller.NewChartGraph.Save,
)
chartGraphGroup.DELETE("/:group",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.chartGraph", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewChartGraph.Delete,
)

View File

@@ -3,8 +3,6 @@ package controller
import (
"fmt"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/modules/chart/service"
@@ -62,7 +60,7 @@ func (s *ChartGraphController) Load(c *gin.Context) {
}
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -94,7 +92,7 @@ func (s *ChartGraphController) Save(c *gin.Context) {
}
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -125,10 +123,9 @@ func (s *ChartGraphController) Save(c *gin.Context) {
// @Description Deleting Relationship Diagram Data
// @Router /chart/graph/{group} [delete]
func (s *ChartGraphController) Delete(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
group := c.Param("group")
if group == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: group is empty"))
return
}

View File

@@ -22,78 +22,24 @@ func Setup(router *gin.Engine) {
controller.NewIndex.Handler,
)
// 系统可暴露的配置信息
router.GET("/sys-conf", controller.NewCommon.SysConfig)
// 系统引导初始化
guideGroup := router.Group("/bootloader")
{
guideGroup.POST("", controller.NewBootloader.Start)
guideGroup.PUT("", middleware.PreAuthorize(nil), controller.NewBootloader.Done)
guideGroup.DELETE("", middleware.PreAuthorize(nil), controller.NewBootloader.Reset)
guideGroup.PUT("/account", middleware.PreAuthorize(nil), controller.NewBootloader.Account)
}
// 验证码操作
router.GET("/captcha-image",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 60,
Type: middleware.LIMIT_IP,
}),
controller.NewCaptcha.Image,
)
// 账号身份操作处理
{
router.POST("/login",
middleware.RateLimit(middleware.LimitOption{
Time: 180,
Count: 15,
Type: middleware.LIMIT_IP,
}),
middleware.CryptoApi(true, true),
controller.NewAccount.Login,
)
router.GET("/me", middleware.PreAuthorize(nil), controller.NewAccount.Me)
router.GET("/router", middleware.PreAuthorize(nil), controller.NewAccount.Router)
router.POST("/logout",
middleware.RateLimit(middleware.LimitOption{
Time: 120,
Count: 15,
Type: middleware.LIMIT_IP,
}),
controller.NewAccount.Logout,
)
}
// 账号注册操作
{
router.POST("/register",
middleware.RateLimit(middleware.LimitOption{
Time: 300,
Count: 10,
Type: middleware.LIMIT_IP,
}),
middleware.CryptoApi(true, true),
controller.NewRegister.Register,
)
}
// 通用请求
commonGroup := router.Group("/common")
{
commonGroup.POST("/hash", middleware.PreAuthorize(nil), controller.NewCommon.Hash)
commonGroup.POST("/hash", middleware.AuthorizeUser(nil), controller.NewCommon.Hash)
commonGroup.GET("/i18n", controller.NewCommon.I18n)
}
// 文件操作处理
fileGroup := router.Group("/file")
{
fileGroup.POST("/upload", middleware.PreAuthorize(nil), controller.NewFile.Upload)
fileGroup.POST("/chunk-check", middleware.PreAuthorize(nil), controller.NewFile.ChunkCheck)
fileGroup.POST("/chunk-upload", middleware.PreAuthorize(nil), controller.NewFile.ChunkUpload)
fileGroup.POST("/chunk-merge", middleware.PreAuthorize(nil), controller.NewFile.ChunkMerge)
fileGroup.GET("/download/:filePath", middleware.PreAuthorize(nil), controller.NewFile.Download)
fileGroup.POST("/transfer-static-file", middleware.PreAuthorize(nil), controller.NewFile.TransferStaticFile)
fileGroup.POST("/upload", middleware.AuthorizeUser(nil), controller.NewFile.Upload)
fileGroup.POST("/chunk-check", middleware.AuthorizeUser(nil), controller.NewFile.ChunkCheck)
fileGroup.POST("/chunk-upload", middleware.AuthorizeUser(nil), controller.NewFile.ChunkUpload)
fileGroup.POST("/chunk-merge", middleware.AuthorizeUser(nil), controller.NewFile.ChunkMerge)
fileGroup.GET("/download/:filePath", middleware.AuthorizeUser(nil), controller.NewFile.Download)
fileGroup.GET("/list", middleware.AuthorizeUser(nil), controller.NewFile.List)
fileGroup.GET("", middleware.AuthorizeUser(nil), controller.NewFile.File)
fileGroup.DELETE("", middleware.AuthorizeUser(nil), controller.NewFile.Remove)
fileGroup.POST("/transfer-static-file", middleware.AuthorizeUser(nil), controller.NewFile.TransferStaticFile)
}
}

View File

@@ -1,203 +0,0 @@
package controller
import (
"fmt"
"strings"
"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"
"be.ems/src/modules/common/model"
"be.ems/src/modules/common/service"
systemModelVO "be.ems/src/modules/system/model/vo"
systemService "be.ems/src/modules/system/service"
"github.com/gin-gonic/gin"
)
// 实例化控制层 AccountController 结构体
var NewAccount = &AccountController{
accountService: service.NewAccount,
sysLogLoginService: systemService.NewSysLogLogin,
}
// 账号身份操作处理
//
// PATH /
type AccountController struct {
accountService *service.Account // 账号身份操作服务
sysLogLoginService *systemService.SysLogLogin // 系统登录访问
}
// Login 系统登录
//
// POST /login
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Summary System Login
// @Description System Login
// @Router /login [post]
func (s AccountController) Login(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body model.LoginBody
if err := c.ShouldBindJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
return
}
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 校验验证码 根据错误信息,创建系统访问记录
if err := s.accountService.ValidateCaptcha(body.Code, body.UUID); err != nil {
msg := fmt.Sprintf("%s code: %s", err.Error(), body.Code)
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_NO, msg,
[4]string{ipaddr, location, os, browser},
)
c.JSON(400, resp.CodeMsg(40012, i18n.TKey(language, err.Error())))
return
}
// 登录用户信息
loginUser, err := s.accountService.ByUsername(body.Username, body.Password)
if err != nil {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, err.Error())))
return
}
// 生成令牌,创建系统访问记录
tokenStr := token.Create(&loginUser, [4]string{ipaddr, location, os, browser})
if tokenStr == "" {
c.JSON(200, resp.Err(nil))
return
} else {
s.accountService.UpdateLoginDateAndIP(loginUser)
// 登录成功
s.sysLogLoginService.Insert(
body.Username, constants.STATUS_YES, "app.common.loginSuccess",
[4]string{ipaddr, location, os, browser},
)
}
c.JSON(200, resp.OkData(map[string]any{
"accessToken": tokenStr,
"tokenType": strings.TrimRight(constants.HEADER_PREFIX, " "),
"expiresIn": (loginUser.ExpireTime - loginUser.LoginTime) / 1000,
"userId": loginUser.UserId,
}))
}
// Me 登录用户信息
//
// GET /me
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Login User Information
// @Description Login User Information
// @Router /me [get]
func (s AccountController) Me(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
info, err := reqctx.LoginUser(c)
if err != nil {
c.JSON(401, resp.CodeMsg(40003, err.Error()))
return
}
// 角色权限集合,系统管理员拥有所有权限
isSystemUser := config.IsSystemUser(info.UserId)
roles, perms := s.accountService.RoleAndMenuPerms(info.UserId, isSystemUser)
info.User.NickName = i18n.TKey(language, info.User.NickName)
info.User.Remark = i18n.TKey(language, info.User.Remark)
info.User.Dept.DeptName = i18n.TKey(language, info.User.Dept.DeptName)
for ri := range info.User.Roles {
info.User.Roles[ri].RoleName = i18n.TKey(language, info.User.Roles[ri].RoleName)
}
c.JSON(200, resp.OkData(map[string]any{
"user": info.User,
"roles": roles,
"permissions": perms,
}))
}
// Router 登录用户路由信息
//
// GET /router
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Login User Routing Information
// @Description Login User Routing Information
// @Router /router [get]
func (s AccountController) Router(c *gin.Context) {
userId := reqctx.LoginUserToUserID(c)
// 前端路由,系统管理员拥有所有
isSystemUser := config.IsSystemUser(userId)
buildMenus := s.accountService.RouteMenus(userId, isSystemUser)
// 闭包函数处理多语言
language := reqctx.AcceptLanguage(c)
var converI18n func(language string, arr *[]systemModelVO.Router)
converI18n = func(language string, arr *[]systemModelVO.Router) {
for i := range *arr {
(*arr)[i].Meta.Title = i18n.TKey(language, (*arr)[i].Meta.Title)
if len((*arr)[i].Children) > 0 {
converI18n(language, &(*arr)[i].Children)
}
}
}
converI18n(language, &buildMenus)
c.JSON(200, resp.OkData(buildMenus))
}
// Logout 系统登出
//
// POST /logout
//
// @Tags common/authorization
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary System Logout
// @Description System Logout
// @Router /logout [post]
func (s AccountController) Logout(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
tokenStr := reqctx.Authorization(c)
if tokenStr != "" {
// 存在token时记录退出信息
userName := token.Remove(tokenStr)
if userName != "" {
// 当前请求信息
ipaddr, location := reqctx.IPAddrLocation(c)
os, browser := reqctx.UaOsBrowser(c)
// 创建系统访问记录
s.sysLogLoginService.Insert(
userName, constants.STATUS_YES, "app.common.logoutSuccess",
[4]string{ipaddr, location, os, browser},
)
}
}
c.JSON(200, resp.OkMsg(i18n.TKey(language, "app.common.logoutSuccess")))
}

View File

@@ -12,21 +12,16 @@ import (
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
commonService "be.ems/src/modules/common/service"
"github.com/gin-gonic/gin"
)
// 实例化控制层 CommonController 结构体
var NewCommon = &CommonController{
commontService: commonService.NewCommont,
}
var NewCommon = &CommonController{}
// 通用请求
//
// PATH /
type CommonController struct {
commontService *commonService.Commont // 通用请求服务
}
type CommonController struct{}
// Hash 哈希编码
//
@@ -37,10 +32,8 @@ func (s CommonController) Hash(c *gin.Context) {
Str string `json:"str" binding:"required"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(200, gin.H{
"code": 400,
"msg": "参数错误",
})
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -100,29 +93,3 @@ func (s *CommonController) I18n(c *gin.Context) {
"errorFields": errorFields,
})
}
// 系统的配置信息
//
// GET /sys-conf
//
// @Tags common
// @Accept json
// @Produce json
// @Success 200 {object} object "Response Results"
// @Summary Configuration information for the system
// @Description Configuration information for the system
// @Router /sys-conf [get]
func (s CommonController) SysConfig(c *gin.Context) {
data := s.commontService.SystemConfigInfo()
// 闭包函数处理多语言
language := reqctx.AcceptLanguage(c)
converI18n := func(language string, arr *map[string]string) {
for k, v := range *arr {
(*arr)[k] = i18n.TKey(language, v)
}
}
converI18n(language, &data)
c.JSON(200, resp.OkData(data))
}

View File

@@ -4,7 +4,9 @@ import (
"encoding/base64"
"fmt"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"be.ems/src/framework/config"
@@ -32,13 +34,13 @@ func (s *FileController) Download(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
filePath := c.Param("filePath")
if len(filePath) < 8 {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, i18n.TKey(language, "app.common.err400")))
return
}
// base64解析出地址
decodedBytes, err := base64.StdEncoding.DecodeString(filePath)
if err != nil {
c.JSON(400, resp.CodeMsg(400, err.Error()))
c.JSON(422, resp.CodeMsg(422002, err.Error()))
return
}
routerPath := string(decodedBytes)
@@ -85,14 +87,14 @@ func (s *FileController) Upload(c *gin.Context) {
// 上传的文件
formFile, err := c.FormFile("file")
if err != nil {
c.JSON(400, resp.CodeMsg(40010, "bind err: file is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: file is empty"))
return
}
// 子路径需要在指定范围内
subPath := c.PostForm("subPath")
_, ok := constants.UPLOAD_SUB_PATH[subPath]
if subPath != "" && !ok {
c.JSON(400, resp.CodeMsg(40010, "bind err: subPath not in range"))
c.JSON(422, resp.CodeMsg(422002, "bind err: subPath not in range"))
return
}
if subPath == "" {
@@ -132,9 +134,9 @@ func (s *FileController) ChunkCheck(c *gin.Context) {
Identifier string `json:"identifier" binding:"required"` // 唯一标识
FileName string `json:"fileName" binding:"required"` // 文件名
}
if err := c.ShouldBindJSON(&body); err != nil {
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -166,14 +168,14 @@ func (s *FileController) ChunkMerge(c *gin.Context) {
FileName string `json:"fileName" binding:"required"` // 文件名
SubPath string `json:"subPath"` // 子路径类型
}
if err := c.ShouldBindJSON(&body); err != nil {
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 子路径需要在指定范围内
if _, ok := constants.UPLOAD_SUB_PATH[body.SubPath]; body.SubPath != "" && !ok {
c.JSON(400, resp.CodeMsg(40010, "bind err: subPath not in range"))
c.JSON(422, resp.CodeMsg(422002, "bind err: subPath not in range"))
return
}
if body.SubPath == "" {
@@ -216,13 +218,13 @@ func (s *FileController) ChunkUpload(c *gin.Context) {
// 切片唯一标识
identifier := c.PostForm("identifier")
if index == "" || identifier == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: index and identifier must be set"))
c.JSON(422, resp.CodeMsg(422002, "bind err: index and identifier must be set"))
return
}
// 上传的文件
formFile, err := c.FormFile("file")
if err != nil {
c.JSON(400, resp.CodeMsg(40010, "bind err: file is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: file is empty"))
return
}
@@ -235,6 +237,176 @@ func (s *FileController) ChunkUpload(c *gin.Context) {
c.JSON(206, resp.OkData(chunkFilePath))
}
// 本地文件列表
//
// GET /list
//
// @Tags common/file
// @Accept json
// @Produce json
// @Param path query string true "file path" default(/var/log)
// @Param pageNum query number true "pageNum" default(1)
// @Param pageSize query number true "pageSize" default(10)
// @Param search query string false "search prefix" default(upf)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Local file list
// @Description Local file list
// @Router /file/list [get]
func (s *FileController) List(c *gin.Context) {
var querys struct {
Path string `form:"path" binding:"required"`
PageNum int64 `form:"pageNum" binding:"required"`
PageSize int64 `form:"pageSize" binding:"required"`
Search string `form:"search"`
}
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 获取文件列表
localFilePath := querys.Path
if runtime.GOOS == "windows" {
localFilePath = fmt.Sprintf("C:%s", localFilePath)
}
rows, err := file.FileList(localFilePath, querys.Search)
if err != nil {
c.JSON(200, resp.OkData(map[string]any{
"path": querys.Path,
"total": len(rows),
"rows": []file.FileListRow{},
}))
return
}
// 对数组进行切片分页
lenNum := int64(len(rows))
start := (querys.PageNum - 1) * querys.PageSize
end := start + querys.PageSize
var splitRows []file.FileListRow
if start >= lenNum {
splitRows = []file.FileListRow{}
} else if end >= lenNum {
splitRows = rows[start:]
} else {
splitRows = rows[start:end]
}
c.JSON(200, resp.OkData(map[string]any{
"path": querys.Path,
"total": lenNum,
"rows": splitRows,
}))
}
// 本地文件获取下载
//
// DELETE /
//
// @Tags common/file
// @Accept json
// @Produce json
// @Param path query string true "file path" default(/var/log)
// @Param fileName query string true "file name" default(omc.log)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Local files for download
// @Description Local files for download
// @Router /file [get]
func (s *FileController) File(c *gin.Context) {
var querys struct {
Path string `form:"path" binding:"required"`
Filename string `form:"fileName" binding:"required"`
}
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 检查路径是否在允许的目录范围内
allowedPaths := []string{"/var/log", "/tmp", "/usr/local/omc/backup"}
allowed := false
for _, p := range allowedPaths {
if strings.HasPrefix(querys.Path, p) {
allowed = true
break
}
}
if !allowed {
c.JSON(200, resp.ErrMsg("operation path is not within the allowed range"))
return
}
// 获取文件路径并下载
localFilePath := filepath.Join(querys.Path, querys.Filename)
if runtime.GOOS == "windows" {
localFilePath = fmt.Sprintf("C:%s", localFilePath)
}
if _, err := os.Stat(localFilePath); os.IsNotExist(err) {
c.JSON(200, resp.ErrMsg("file does not exist"))
return
}
c.FileAttachment(localFilePath, querys.Filename)
}
// 本地文件删除
//
// DELETE /
//
// @Tags common/file
// @Accept json
// @Produce json
// @Param path query string true "file path" default(/var/log)
// @Param fileName query string true "file name" default(omc.log)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Local file deletion
// @Description Local file deletion
// @Router /file [delete]
func (s *FileController) Remove(c *gin.Context) {
var querys struct {
Path string `form:"path" binding:"required"`
Filename string `form:"fileName" binding:"required"`
}
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 检查路径是否在允许的目录范围内
allowedPaths := []string{"/tmp", "/usr/local/omc/backup"}
allowed := false
for _, p := range allowedPaths {
if strings.HasPrefix(querys.Path, p) {
allowed = true
break
}
}
if !allowed {
c.JSON(200, resp.ErrMsg("operation path is not within the allowed range"))
return
}
// 获取文件路径并删除
localFilePath := filepath.Join(querys.Path, querys.Filename)
if runtime.GOOS == "windows" {
localFilePath = fmt.Sprintf("C:%s", localFilePath)
}
if _, err := os.Stat(localFilePath); os.IsNotExist(err) {
c.JSON(200, resp.ErrMsg("file does not exist"))
return
}
if err := os.Remove(localFilePath); err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
c.JSON(200, resp.Ok(nil))
}
// 转存指定对应文件到静态目录
//
// POST /transfer-static-file
@@ -246,7 +418,7 @@ func (s *FileController) TransferStaticFile(c *gin.Context) {
}
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -257,7 +429,7 @@ func (s *FileController) TransferStaticFile(c *gin.Context) {
static := config.Get("staticFile.default").(map[string]any)
dir, err := filepath.Abs(static["dir"].(string))
if err != nil {
c.JSON(400, resp.CodeMsg(400, err.Error()))
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
@@ -267,7 +439,7 @@ func (s *FileController) TransferStaticFile(c *gin.Context) {
err = file.CopyUploadFile(body.UploadPath, newFile)
if err != nil {
c.JSON(400, resp.CodeMsg(400, err.Error()))
c.JSON(200, resp.ErrMsg(err.Error()))
return
}

View File

@@ -1,9 +1,7 @@
package controller
import (
"fmt"
libGlobal "be.ems/lib/global"
"be.ems/src/framework/config"
"be.ems/src/framework/resp"
"github.com/gin-gonic/gin"
@@ -29,9 +27,9 @@ type IndexController struct{}
// @Description Root Route
// @Router / [get]
func (s *IndexController) Handler(c *gin.Context) {
name := "OMC"
version := libGlobal.Version
// str := "欢迎使用%s核心网管理平台当前版本%s请通过前台地址访问。"
str := "%s Core Network Management, current version: %s"
c.JSON(200, resp.OkMsg(fmt.Sprintf(str, name, version)))
c.JSON(200, resp.OkData(map[string]any{
"name": "OMC",
"version": config.Version,
}))
}

View File

@@ -1,10 +1,10 @@
package exportTable
package backup_export_table
import (
"encoding/json"
"fmt"
"path"
"path/filepath"
"runtime"
"strings"
"time"
@@ -12,25 +12,27 @@ import (
"be.ems/src/framework/database/db"
"be.ems/src/framework/i18n"
"be.ems/src/framework/logger"
"be.ems/src/framework/ssh"
"be.ems/src/framework/utils/date"
"be.ems/src/framework/utils/file"
"be.ems/src/framework/utils/parse"
neDataModel "be.ems/src/modules/network_data/model"
neDataService "be.ems/src/modules/network_data/service"
systemModel "be.ems/src/modules/system/model"
systemService "be.ems/src/modules/system/service"
)
var NewProcessor = &BarProcessor{
count: 0,
var NewProcessor = &BackupExportTableProcessor{
backupService: neDataService.NewBackup,
count: 0,
}
// bar 队列任务处理
type BarProcessor struct {
count int // 执行次数
// BackupExportTable 备份导出数据表
type BackupExportTableProcessor struct {
backupService *neDataService.Backup // 备份相关服务
count int // 执行次数
}
func (s *BarProcessor) Execute(data any) (any, error) {
func (s *BackupExportTableProcessor) Execute(data any) (any, error) {
s.count++ // 执行次数加一
options := data.(cron.JobData)
sysJob := options.SysJob
@@ -41,10 +43,10 @@ func (s *BarProcessor) Execute(data any) (any, error) {
}
var params struct {
Hour int `json:"hour"` // hour
TableName string `json:"tableName"`
Columns []string `json:"columns"`
FilePath string `json:"filePath"` // file path
Hour int `json:"hour"` // 数据时间从任务执行时间前的小时数
TableName string `json:"tableName"` // 数据表名
Columns []string `json:"columns"` // 支持字段
BackupPath string `json:"backupPath"` // 备份输出路径 /usr/local/omc/backup/{backupPath}
}
err := json.Unmarshal([]byte(sysJob.TargetParams), &params)
if err != nil {
@@ -53,7 +55,11 @@ func (s *BarProcessor) Execute(data any) (any, error) {
var affected int64
var errMsg error
filePath := fmt.Sprintf("%s/%s_export_%s.csv", params.FilePath, strings.ToLower(params.TableName), time.Now().Format("20060102150405"))
fileName := fmt.Sprintf("%s_export_%s.csv", strings.ToLower(params.TableName), time.Now().Format("20060102150405"))
filePath := filepath.Join("/usr/local/omc/backup", params.BackupPath, fileName)
if runtime.GOOS == "windows" {
filePath = fmt.Sprintf("C:%s", filePath)
}
switch params.TableName {
case "sys_log_operate":
affected, errMsg = s.exportSysLogOperate(params.Hour, params.Columns, filePath)
@@ -70,18 +76,20 @@ func (s *BarProcessor) Execute(data any) (any, error) {
return nil, errMsg
}
// put ftp
// 上传到FTP服务器
if affected > 0 {
result["affected"] = affected
s.putFTP(filePath)
if err := s.backupService.FTPPushFile(filePath, params.BackupPath); err != nil {
result["ftpErr"] = err.Error()
}
}
result["affected"] = affected
// 返回结果,用于记录执行结果
return result, nil
}
// exportSysLogOperate 导出csv
func (s BarProcessor) exportSysLogOperate(hour int, columns []string, filePath string) (int64, error) {
func (s BackupExportTableProcessor) exportSysLogOperate(hour int, columns []string, filePath string) (int64, error) {
// 前 hour 小时
now := time.Now()
end := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
@@ -182,7 +190,7 @@ func (s BarProcessor) exportSysLogOperate(hour int, columns []string, filePath s
}
// exportSMF 导出csv
func (s BarProcessor) exportSMF(hour int, columns []string, filePath string) (int64, error) {
func (s BackupExportTableProcessor) exportSMF(hour int, columns []string, filePath string) (int64, error) {
// 前 hour 小时
now := time.Now()
end := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
@@ -413,7 +421,7 @@ func (s BarProcessor) exportSMF(hour int, columns []string, filePath string) (in
}
// exportIMS 导出csv
func (s BarProcessor) exportIMS(hour int, columns []string, filePath string) (int64, error) {
func (s BackupExportTableProcessor) exportIMS(hour int, columns []string, filePath string) (int64, error) {
// 前 hour 小时
now := time.Now()
end := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
@@ -521,7 +529,6 @@ func (s BarProcessor) exportIMS(hour int, columns []string, filePath string) (in
break
}
}
}
arr[i] = callResult
}
@@ -558,7 +565,7 @@ func (s BarProcessor) exportIMS(hour int, columns []string, filePath string) (in
}
// exportSMSC 导出csv
func (s BarProcessor) exportSMSC(hour int, columns []string, filePath string) (int64, error) {
func (s BackupExportTableProcessor) exportSMSC(hour int, columns []string, filePath string) (int64, error) {
// 前 hour 小时
now := time.Now()
end := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
@@ -683,7 +690,7 @@ func (s BarProcessor) exportSMSC(hour int, columns []string, filePath string) (i
}
// exportSGWC 导出csv
func (s BarProcessor) exportSGWC(hour int, columns []string, filePath string) (int64, error) {
func (s BackupExportTableProcessor) exportSGWC(hour int, columns []string, filePath string) (int64, error) {
// 前 hour 小时
now := time.Now()
end := time.Date(now.Year(), now.Month(), now.Day(), now.Hour(), 0, 0, 0, now.Location())
@@ -918,54 +925,3 @@ func (s BarProcessor) exportSGWC(hour int, columns []string, filePath string) (i
return tx.RowsAffected, err
}
// putFTP 提交到服务器ssh
func (s BarProcessor) putFTP(localFilePath string) {
// 获取配置
var cfgData struct {
Password string `json:"password" `
Username string `json:"username" binding:"required"`
ToIp string `json:"toIp" binding:"required"`
ToPort int64 `json:"toPort" binding:"required"`
Enable bool `json:"enable"`
Dir string `json:"dir" binding:"required"`
}
cfg := systemService.NewSysConfig.FindByKeyDecryptValue("neData.exportTableFTP")
if cfg.ConfigId > 0 {
if err := json.Unmarshal([]byte(cfg.ConfigValue), &cfgData); err != nil {
logger.Errorf("putFTP unmarshal error: %v", err)
return
}
}
if !cfgData.Enable {
return
}
connSSH := ssh.ConnSSH{
User: cfgData.Username,
Password: cfgData.Password,
Addr: cfgData.ToIp,
Port: cfgData.ToPort,
AuthMode: "0",
}
sshClient, err := connSSH.NewClient()
if err != nil {
logger.Errorf("putFTP ssh error: %v", err)
return
}
defer sshClient.Close()
// 网元主机的SSH客户端进行文件传输
sftpClient, err := sshClient.NewClientSFTP()
if err != nil {
logger.Errorf("putFTP sftp error: %v", err)
return
}
defer sftpClient.Close()
// 远程文件
remotePath := filepath.Join(cfgData.Dir, path.Base(localFilePath), filepath.Base(localFilePath))
// 复制到远程
if err = sftpClient.CopyFileLocalToRemote(localFilePath, remotePath); err != nil {
logger.Errorf("putFTP uploading error: %v", err)
return
}
}

View File

@@ -0,0 +1,273 @@
package backup_export_udm
import (
"encoding/json"
"fmt"
"path/filepath"
"runtime"
"time"
"be.ems/src/framework/cron"
"be.ems/src/framework/logger"
"be.ems/src/framework/utils/file"
neDataModel "be.ems/src/modules/network_data/model"
neDataService "be.ems/src/modules/network_data/service"
neModel "be.ems/src/modules/network_element/model"
neService "be.ems/src/modules/network_element/service"
)
var NewProcessor = &BackupExportUDMProcessor{
count: 0,
neInfoService: neService.NewNeInfo,
backupService: neDataService.NewBackup,
udmAuthService: neDataService.NewUDMAuthUser,
udmSubService: neDataService.NewUDMSubUser,
udmVOIPService: neDataService.NewUDMVOIPUser,
udmVolteIMSService: neDataService.NewUDMVolteIMSUser,
}
// BackupExportUDM 队列任务处理
type BackupExportUDMProcessor struct {
count int // 执行次数
neInfoService *neService.NeInfo // 网元信息服务
backupService *neDataService.Backup // 备份相关服务
udmAuthService *neDataService.UDMAuthUser // UDM鉴权信息服务
udmSubService *neDataService.UDMSubUser // UDM签约信息服务
udmVOIPService *neDataService.UDMVOIPUser // UDMVOIP信息服务
udmVolteIMSService *neDataService.UDMVolteIMSUser // UDMVolteIMS信息服务
}
func (s *BackupExportUDMProcessor) Execute(data any) (any, error) {
s.count++ // 执行次数加一
options := data.(cron.JobData)
sysJob := options.SysJob
logger.Infof("重复:%v 任务ID:%d 执行次数:%d", options.Repeat, sysJob.JobId, s.count)
// 返回结果,用于记录执行结果
result := map[string]any{
"count": s.count,
}
var params struct {
DataType []string `json:"dataType"` // 类型支持 auth/sub/voip/volte
FileType string `json:"fileType"` // 文件类型 csv/txt
}
if err := json.Unmarshal([]byte(sysJob.TargetParams), &params); err != nil {
return nil, err
}
if !(params.FileType == "csv" || params.FileType == "txt") {
return nil, fmt.Errorf("file type error, only support csv,txt")
}
neList := s.neInfoService.Find(neModel.NeInfo{NeType: "UDM"}, false, false)
for _, neInfo := range neList {
for _, v := range params.DataType {
switch v {
case "auth":
result[fmt.Sprintf("%s-%s", neInfo.NeId, v)] = s.exportAuth(neInfo.NeId, params.FileType)
case "sub":
result[fmt.Sprintf("%s-%s", neInfo.NeId, v)] = s.exportSub(neInfo.NeId, params.FileType)
case "voip":
result[fmt.Sprintf("%s-%s", neInfo.NeId, v)] = s.exportVOIP(neInfo.NeId, params.FileType)
case "volte":
result[fmt.Sprintf("%s-%s", neInfo.NeId, v)] = s.exportVolte(neInfo.NeId, params.FileType)
}
}
}
// 返回结果,用于记录执行结果
return result, nil
}
// exportAuth 导出鉴权用户数据
func (s BackupExportUDMProcessor) exportAuth(neId, fileType string) string {
rows := s.udmAuthService.Find(neDataModel.UDMAuthUser{NeId: neId})
if len(rows) <= 0 {
return "no data"
}
// 文件名
fileName := fmt.Sprintf("auth_%s_export_%s.%s", neId, time.Now().Format("20060102150405"), fileType)
filePath := filepath.Join("/usr/local/omc/backup/udm_data/auth", fileName)
if runtime.GOOS == "windows" {
filePath = fmt.Sprintf("C:%s", filePath)
}
if fileType == "csv" {
// 转换数据
data := [][]string{}
data = append(data, []string{"imsi", "ki", "algo", "amf", "opc"})
for _, v := range rows {
opc := v.Opc
if opc == "-" {
opc = ""
}
data = append(data, []string{v.IMSI, v.Ki, v.AlgoIndex, v.Amf, opc})
}
// 输出到文件
if err := file.WriterFileCSV(data, filePath); err != nil {
return err.Error()
}
}
if fileType == "txt" {
// 转换数据
data := [][]string{}
for _, v := range rows {
opc := v.Opc
if opc == "-" {
opc = ""
}
data = append(data, []string{v.IMSI, v.Ki, v.AlgoIndex, v.Amf, opc})
}
// 输出到文件
if err := file.WriterFileTXT(data, ",", filePath); err != nil {
return err.Error()
}
}
// 上传到FTP服务器
if err := s.backupService.FTPPushFile(filePath, "udm_data"); err != nil {
return "ok, ftp err:" + err.Error()
}
return "ok"
}
// exportSub 导出签约用户数据
func (s BackupExportUDMProcessor) exportSub(neId, fileType string) string {
rows := s.udmSubService.Find(neDataModel.UDMSubUser{NeId: neId})
if len(rows) <= 0 {
return "no data"
}
// 文件名
fileName := fmt.Sprintf("sub_%s_export_%s.%s", neId, time.Now().Format("20060102150405"), fileType)
filePath := filepath.Join("/usr/local/omc/backup/udm_data/sub", fileName)
if runtime.GOOS == "windows" {
filePath = fmt.Sprintf("C:%s", filePath)
}
if fileType == "csv" {
// 转换数据
data := [][]string{}
data = append(data, []string{"IMSI", "MSISDN", "UeAmbrTpl", "NssaiTpl", "AreaForbiddenTpl", "ServiceAreaRestrictionTpl", "RatRestrictions", "CnTypeRestrictions", "SmfSel", "SmData", "EPSDat"})
for _, v := range rows {
epsDat := fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%s", v.EpsFlag, v.EpsOdb, v.HplmnOdb, v.Ard, v.Epstpl, v.ContextId, v.ApnContext, v.StaticIp)
data = append(data, []string{v.IMSI, v.MSISDN, v.UeAmbrTpl, v.NssaiTpl, v.AreaForbiddenTpl, v.ServiceAreaRestrictionTpl, v.RatRestrictions, v.CnTypeRestrictions, v.SmfSel, v.SmData, epsDat})
}
// 输出到文件
if err := file.WriterFileCSV(data, filePath); err != nil {
return err.Error()
}
}
if fileType == "txt" {
// 转换数据
data := [][]string{}
for _, v := range rows {
epsDat := fmt.Sprintf("%s,%s,%s,%s,%s,%s,%s,%s", v.EpsFlag, v.EpsOdb, v.HplmnOdb, v.Ard, v.Epstpl, v.ContextId, v.ApnContext, v.StaticIp)
data = append(data, []string{v.IMSI, v.MSISDN, v.UeAmbrTpl, v.NssaiTpl, v.AreaForbiddenTpl, v.ServiceAreaRestrictionTpl, v.RatRestrictions, v.CnTypeRestrictions, v.SmfSel, v.SmData, epsDat})
}
// 输出到文件
if err := file.WriterFileTXT(data, ",", filePath); err != nil {
return err.Error()
}
}
// 上传到FTP服务器
if err := s.backupService.FTPPushFile(filePath, "udm_data"); err != nil {
return "ok, ftp err:" + err.Error()
}
return "ok"
}
// exportVOIP 导出VOIP用户数据
func (s BackupExportUDMProcessor) exportVOIP(neId, fileType string) string {
rows := s.udmVOIPService.Find(neDataModel.UDMVOIPUser{NeId: neId})
if len(rows) <= 0 {
return "no data"
}
// 文件名
fileName := fmt.Sprintf("voip_%s_export_%s.%s", neId, time.Now().Format("20060102150405"), fileType)
filePath := filepath.Join("/usr/local/omc/backup/udm_data/voip", fileName)
if runtime.GOOS == "windows" {
filePath = fmt.Sprintf("C:%s", filePath)
}
if fileType == "csv" {
// 转换数据
data := [][]string{}
data = append(data, []string{"username", "password"})
for _, v := range rows {
data = append(data, []string{v.UserName, v.Password})
}
// 输出到文件
if err := file.WriterFileCSV(data, filePath); err != nil {
return err.Error()
}
}
if fileType == "txt" {
// 转换数据
data := [][]string{}
for _, v := range rows {
data = append(data, []string{v.UserName, v.Password})
}
// 输出到文件
if err := file.WriterFileTXT(data, ",", filePath); err != nil {
return err.Error()
}
}
// 上传到FTP服务器
if err := s.backupService.FTPPushFile(filePath, "udm_data"); err != nil {
return "ok, ftp err:" + err.Error()
}
return "ok"
}
// exportVolte 导出Volte用户数据
func (s BackupExportUDMProcessor) exportVolte(neId, fileType string) string {
rows := s.udmVolteIMSService.Find(neDataModel.UDMVolteIMSUser{NeId: neId})
if len(rows) <= 0 {
return "no data"
}
// 文件名
fileName := fmt.Sprintf("volte_%s_export_%s.%s", neId, time.Now().Format("20060102150405"), fileType)
filePath := filepath.Join("/usr/local/omc/backup/udm_data/volte", fileName)
if runtime.GOOS == "windows" {
filePath = fmt.Sprintf("C:%s", filePath)
}
if fileType == "csv" {
// 转换数据
data := [][]string{}
data = append(data, []string{"IMSI", "MSISDN", "TAG", "VNI"})
for _, v := range rows {
data = append(data, []string{v.IMSI, v.MSISDN, v.Tag, v.VNI})
}
// 输出到文件
if err := file.WriterFileCSV(data, filePath); err != nil {
return err.Error()
}
}
if fileType == "txt" {
// 转换数据
data := [][]string{}
for _, v := range rows {
data = append(data, []string{v.IMSI, v.MSISDN, v.Tag, v.VNI})
}
// 输出到文件
if err := file.WriterFileTXT(data, ",", filePath); err != nil {
return err.Error()
}
}
// 上传到FTP服务器
if err := s.backupService.FTPPushFile(filePath, "udm_data"); err != nil {
return "ok, ftp err:" + err.Error()
}
return "ok"
}

View File

@@ -0,0 +1,113 @@
package backup_remove_file
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
"be.ems/src/framework/cron"
"be.ems/src/framework/logger"
)
type FileInfo struct {
Path string
Info os.FileInfo
}
var NewProcessor = &BackupRemoveFileProcessor{
count: 0,
}
// BackupRemoveFileProcessor 删除备份文件
type BackupRemoveFileProcessor struct {
count int // 执行次数
}
func (s *BackupRemoveFileProcessor) Execute(data any) (any, error) {
s.count++ // 执行次数加一
options := data.(cron.JobData)
sysJob := options.SysJob
logger.Infof("重复:%v 任务ID:%d 执行次数:%d", options.Repeat, sysJob.JobId, s.count)
// 返回结果,用于记录执行结果
result := map[string]any{
"count": s.count,
}
// 读取参数值
var params []struct {
BackupPath string `json:"backupPath"` // 备份路径 /usr/local/omc/backup/{backupPath}
StoreDays int `json:"storeDays"` // 保留天数
StoreNum int `json:"storeNum"` // 保留数量默认保留7
}
err := json.Unmarshal([]byte(sysJob.TargetParams), &params)
if err != nil {
return nil, fmt.Errorf("json params err: %v", err)
}
for _, item := range params {
result[item.BackupPath] = ""
if item.StoreDays < 0 {
result[item.BackupPath] = "params storeDays less than 0"
continue
}
if item.StoreNum <= 0 {
item.StoreNum = 7
}
// 构建完整备份路径
filePath := filepath.Join("/usr/local/omc/backup", item.BackupPath)
if runtime.GOOS == "windows" {
filePath = fmt.Sprintf("C:%s", filePath)
}
// 获取目录下所有备份文件
files, err := s.files(filePath)
if err != nil {
result[item.BackupPath] = "read files err"
continue
}
// 按修改时间排序(从旧到新)
sort.Slice(files, func(i, j int) bool {
return files[i].Info.ModTime().Before(files[j].Info.ModTime())
})
// 如果文件数量少于保留数量,则不删除
if len(files) <= item.StoreNum {
result[item.BackupPath] = fmt.Sprintf("less StoreNum: %d, file number %d", item.StoreNum, len(files))
continue
}
// 计算截止日期
cutoff := time.Now().AddDate(0, 0, -item.StoreDays)
// 删除超过保留天数的文件
deletedErr := []string{}
for _, file := range files {
if file.Info.ModTime().Before(cutoff) {
if err := os.Remove(file.Path); err != nil {
deletedErr = append(deletedErr, file.Info.Name()) // 记录删除失败的文件名称
}
}
}
result[item.BackupPath] = strings.Join(deletedErr, ", ")
}
// 返回结果,用于记录执行结果
return result, nil
}
func (s *BackupRemoveFileProcessor) files(dir string) ([]FileInfo, error) {
var files []FileInfo
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, FileInfo{Path: path, Info: info})
}
return nil
})
return files, err
}

View File

@@ -39,7 +39,8 @@ func (s *DeleteNeConfigBackupProcessor) Execute(data any) (any, error) {
// 读取参数值
var params struct {
StoreDays int `json:"storeDays"` // store days
StoreDays int `json:"storeDays"` // 保留天数
StoreNum int `json:"storeNum"` // 保留数量默认保留7
}
err := json.Unmarshal([]byte(sysJob.TargetParams), &params)
if err != nil {
@@ -48,6 +49,9 @@ func (s *DeleteNeConfigBackupProcessor) Execute(data any) (any, error) {
if params.StoreDays < 0 {
return nil, fmt.Errorf("params storeDays less than 0 ")
}
if params.StoreNum <= 0 {
params.StoreNum = 7
}
neList := s.neInfoService.Find(neModel.NeInfo{}, false, false)
for _, neInfo := range neList {
@@ -55,6 +59,17 @@ func (s *DeleteNeConfigBackupProcessor) Execute(data any) (any, error) {
tx := db.DB("").Model(&neModel.NeConfigBackup{})
tx = tx.Where("ne_type = ? and ne_id = ?", neInfo.NeType, neInfo.NeId)
// 查询数量为0直接返回
var total int64 = 0
if err := tx.Count(&total).Error; err != nil {
result[neTypeAndId] = err.Error()
continue
}
if total <= int64(params.StoreNum) {
result[neTypeAndId] = "less than storeNum"
continue
}
// 查询最后记录数据
var lastCreateTime int64 = 0
lastTx := tx.Select("create_time").Order("create_time DESC").Limit(1)
@@ -62,7 +77,6 @@ func (s *DeleteNeConfigBackupProcessor) Execute(data any) (any, error) {
result[neTypeAndId] = err.Error()
continue
}
if lastCreateTime <= 1e12 {
result[neTypeAndId] = "no data"
continue
@@ -90,7 +104,7 @@ func (s *DeleteNeConfigBackupProcessor) Execute(data any) (any, error) {
// deleteFile 删除本地文件
func (s DeleteNeConfigBackupProcessor) deleteFile(neType, neId string, oldFileDate time.Time) {
neTypeLower := strings.ToLower(neType)
localPath := fmt.Sprintf("/usr/local/etc/omc/ne_config/%s/%s/backup ", neTypeLower, neId)
localPath := fmt.Sprintf("/usr/local/omc/backup/ne_config/%s/%s ", neTypeLower, neId)
files, err := os.ReadDir(localPath)
if err != nil {
logger.Errorf("logger Remove ne_config File ReadDir err: %v", err.Error())

View File

@@ -1,160 +0,0 @@
package getStateFromNE
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"time"
"be.ems/lib/config"
"be.ems/lib/dborm"
"be.ems/lib/log"
"be.ems/src/framework/cron"
"github.com/go-resty/resty/v2"
)
var NewProcessor = &BarProcessor{
progress: 0,
count: 0,
}
// bar 队列任务处理
type BarProcessor struct {
// 任务进度
progress int
// 执行次数
count int
}
type BarParams struct {
Duration int `json:"duration"`
}
type CpuUsage struct {
NfCpuUsage uint16 `json:"nfCpuUsage"`
SysCpuUsage uint16 `json:"sysCpuUsage"`
}
type MemUsage struct {
TotalMem uint32 `json:"totalMem"`
NfUsedMem uint32 `json:"nfUsedMem"`
SysMemUsage uint16 `json:"sysMemUsage"`
}
type PartitionInfo struct {
Total uint32 `json:"total"` // MB
Used uint32 `json:"used"` // MB
}
type DiskSpace struct {
PartitionNum uint8 `json:"partitionNum"`
PartitionInfo []PartitionInfo `json:"partitionInfo"`
}
type SystemState struct {
Version string `json:"version"`
Capability uint32 `json:"capability"`
SerialNum string `json:"serialNum"`
ExpiryDate string `json:"expiryDate"`
//Timestamp string `json:"timestamp"`
CpuUsage CpuUsage `json:"cpuUsage"`
MemUsage MemUsage `json:"memUsage"`
DiskSpace DiskSpace `json:"diskSpace"`
}
var client = resty.New()
func init() {
client.
SetTimeout(time.Duration(400 * time.Millisecond))
}
func (s *BarProcessor) Execute(data any) (any, error) {
var err error
s.count++
options := data.(cron.JobData)
sysJob := options.SysJob
var params BarParams
_ = json.Unmarshal([]byte(sysJob.TargetParams), &params)
// if err == nil {
// duration = params.Duration
// }
var nes []dborm.NeInfo
_, err = dborm.XormGetAllNeInfo(&nes)
if err != nil {
log.Error("Failed to get all ne info:", err)
return nil, err
}
failNum := 0
succNum := 0
for _, ne := range nes {
requestURI := fmt.Sprintf("/api/rest/systemManagement/v1/elementType/%s/objectType/systemState", strings.ToLower(ne.NeType))
requestURL := fmt.Sprintf("http://%s:%s%s", ne.Ip, ne.Port, requestURI)
log.Debug("requestURL: Get", requestURL)
response, err := client.R().
EnableTrace().
SetHeaders(map[string]string{"User-Agent": config.GetDefaultUserAgent()}).
SetHeaders(map[string]string{"Content-Type": "application/json;charset=UTF-8"}).
Get(requestURL)
if err != nil {
log.Error("Failed to Get:", err)
failNum++
continue
}
log.Debug("StatusCode: ", response.StatusCode())
switch response.StatusCode() {
case http.StatusOK, http.StatusCreated, http.StatusNoContent, http.StatusAccepted:
log.Trace("response body:", string(response.Body()))
state := new(SystemState)
_ = json.Unmarshal(response.Body(), &state)
// var dateStr *string = nil
// if state.ExpiryDate != "" && state.ExpiryDate != "-" {
// dateStr = &state.ExpiryDate
// }
neState := new(dborm.NeState)
neState.NeType = ne.NeType
neState.NeId = ne.NeId
neState.Version = state.Version
neState.Capability = state.Capability
neState.SerialNum = state.SerialNum
// if dateStr != nil {
// neState.ExpiryDate = *dateStr
// }
neState.ExpiryDate = state.ExpiryDate
cu, _ := json.Marshal(state.CpuUsage)
neState.CpuUsage = string(cu)
mu, _ := json.Marshal(state.MemUsage)
neState.MemUsage = string(mu)
ds, _ := json.Marshal(state.DiskSpace)
neState.DiskSpace = string(ds)
log.Trace("neState:", neState)
_, err := dborm.XormInsertNeState(neState)
if err != nil {
log.Error("Failed to insert ne_state:", err)
failNum++
continue
}
succNum++
default:
log.Trace("response body:", string(response.Body()))
body := new(map[string]interface{})
_ = json.Unmarshal(response.Body(), &body)
failNum++
}
}
// 返回结果,用于记录执行结果
return map[string]any{
"succNum": succNum,
"failNum": failNum,
}, nil
}

View File

@@ -66,6 +66,9 @@ func (s *NeAlarmStateCheckProcessor) Execute(data any) (any, error) {
neList := s.neInfoService.Find(neModel.NeInfo{}, true, false)
for _, neInfo := range neList {
if neInfo.CreateTime == 0 {
continue
}
neTypeAndId := fmt.Sprintf("%s_%s", neInfo.NeType, neInfo.NeId)
// 网元在线状态
isOnline := parse.Boolean(neInfo.ServerState["online"])

View File

@@ -6,6 +6,7 @@ import (
"be.ems/src/framework/cron"
"be.ems/src/framework/logger"
neDataService "be.ems/src/modules/network_data/service"
neModel "be.ems/src/modules/network_element/model"
neService "be.ems/src/modules/network_element/service"
)
@@ -13,6 +14,7 @@ import (
var NewProcessor = &NeConfigBackupProcessor{
neConfigBackupService: neService.NewNeConfigBackup,
neInfoService: neService.NewNeInfo,
backupService: neDataService.NewBackup,
count: 0,
}
@@ -20,6 +22,7 @@ var NewProcessor = &NeConfigBackupProcessor{
type NeConfigBackupProcessor struct {
neConfigBackupService *neService.NeConfigBackup // 网元配置文件备份记录服务
neInfoService *neService.NeInfo // 网元信息服务
backupService *neDataService.Backup // 备份相关服务
count int // 执行次数
}
@@ -42,6 +45,7 @@ func (s *NeConfigBackupProcessor) Execute(data any) (any, error) {
result[neTypeAndId] = err.Error()
continue
}
// 新增备份记录
item := neModel.NeConfigBackup{
NeType: neInfo.NeType,
@@ -50,8 +54,18 @@ func (s *NeConfigBackupProcessor) Execute(data any) (any, error) {
Path: zipFilePath,
CreateBy: "system",
}
s.neConfigBackupService.Insert(item)
result[neTypeAndId] = "ok"
rows := s.neConfigBackupService.Insert(item)
if rows <= 0 {
result[neTypeAndId] = "failed"
continue
}
msg := "ok"
// 上传到FTP服务器
if err := s.backupService.FTPPushFile(zipFilePath, "ne_config"); err != nil {
result[neTypeAndId] = msg + ", ftp err:" + err.Error()
}
result[neTypeAndId] = msg
}
return result, nil

View File

@@ -2,16 +2,17 @@ package processor
import (
"be.ems/src/framework/cron"
processorBackupExportTable "be.ems/src/modules/crontask/processor/backup_export_table"
processorBackupExportUDM "be.ems/src/modules/crontask/processor/backup_export_udm"
processorBackupRemoveFile "be.ems/src/modules/crontask/processor/backup_remove_file"
processorDeleteAlarmRecord "be.ems/src/modules/crontask/processor/delete_alarm_record"
processorDeleteDataRecord "be.ems/src/modules/crontask/processor/delete_data_record"
processorDeleteKPIRecord "be.ems/src/modules/crontask/processor/delete_kpi_record"
processorDeleteNeConfigBackup "be.ems/src/modules/crontask/processor/delete_ne_config_backup"
"be.ems/src/modules/crontask/processor/exportTable"
processorMonitorSysResource "be.ems/src/modules/crontask/processor/monitor_sys_resource"
processorNeAlarmStateCheck "be.ems/src/modules/crontask/processor/ne_alarm_state_check"
processorNeConfigBackup "be.ems/src/modules/crontask/processor/ne_config_backup"
processorNeDataUDM "be.ems/src/modules/crontask/processor/ne_data_udm"
"be.ems/src/modules/crontask/processor/removeFile"
)
// InitCronQueue 初始定时任务队列
@@ -20,7 +21,7 @@ func InitCronQueue() {
cron.CreateQueue("monitor_sys_resource", processorMonitorSysResource.NewProcessor)
// 网元-网元配置文件定期备份
cron.CreateQueue("ne_config_backup", processorNeConfigBackup.NewProcessor)
// 网元数据-UDM数据刷新同步
// 网元数据-UDM用户数据同步
cron.CreateQueue("ne_data_udm", processorNeDataUDM.NewProcessor)
// 网元告警-状态检查
cron.CreateQueue("ne_alarm_state_check", processorNeAlarmStateCheck.NewProcessor)
@@ -34,6 +35,10 @@ func InitCronQueue() {
// 删除-网元配置文件定期备份
cron.CreateQueue("delete_ne_config_backup", processorDeleteNeConfigBackup.NewProcessor)
cron.CreateQueue("exportTable", exportTable.NewProcessor)
cron.CreateQueue("removeFile", removeFile.NewProcessor)
// 备份-导出数据表
cron.CreateQueue("backup_export_table", processorBackupExportTable.NewProcessor)
// 备份-删除备份目录下文件
cron.CreateQueue("backup_remove_file", processorBackupRemoveFile.NewProcessor)
// 备份-导出UDM用户数据
cron.CreateQueue("backup_export_udm", processorBackupExportUDM.NewProcessor)
}

View File

@@ -1,159 +0,0 @@
package removeFile
import (
"encoding/json"
"os"
"path/filepath"
"sort"
"time"
"be.ems/lib/log"
"be.ems/src/framework/cron"
)
var NewProcessor = &BarProcessor{
progress: 0,
count: 0,
}
// bar 队列任务处理
type BarProcessor struct {
// 任务进度
progress int
// 执行次数
count int
}
type BarParams struct {
FilePath string `json:"filePath"` // file path
MaxDays int `json:"maxDays"`
MaxFiles *int `json:"maxFiles"` // keep max files
MaxSize *int64 `json:"maxSize"`
Extras string `json:"extras"` // extras condition for where
}
type FileInfo struct {
Path string
Info os.FileInfo
}
func (s *BarProcessor) Execute(data any) (any, error) {
s.count++
options := data.(cron.JobData)
sysJob := options.SysJob
var params []BarParams
err := json.Unmarshal([]byte(sysJob.TargetParams), &params)
if err != nil {
return nil, err
}
result := []map[string]any{}
for _, param := range params {
res, _ := s.ExecuteOne(param)
result = append(result, res)
}
// 返回结果,用于记录执行结果
return map[string]any{
"result": result,
}, nil
}
func (s *BarProcessor) ExecuteOne(params BarParams) (map[string]any, error) {
var maxFiles int = 0
var maxSize int64 = 0
if params.MaxFiles != nil {
maxFiles = *params.MaxFiles
}
if params.MaxSize != nil {
maxSize = int64(*params.MaxSize * 1024 * 1024)
}
files, err := getFiles(params.FilePath)
if err != nil {
return map[string]any{
"msg": "failed",
"err": err.Error(),
}, err
}
// 获取本地时区
loc, err := time.LoadLocation("Local")
if err != nil {
return map[string]any{
"msg": "failed",
"err": err.Error(),
}, err
}
cutoff := time.Now().In(loc).AddDate(0, 0, -params.MaxDays)
var oldFiles []FileInfo
for _, file := range files {
if file.Info.ModTime().Before(cutoff) {
oldFiles = append(oldFiles, file)
}
}
// 按修改时间排序文件(最旧的在前)
sort.Slice(oldFiles, func(i, j int) bool {
return oldFiles[i].Info.ModTime().Before(oldFiles[j].Info.ModTime())
})
deleted, errorDel := 0, 0
// 删除文件直到满足文件总数不超过maxFiles个且总大小不超过maxSize的条件
var totalSize int64
for i, file := range oldFiles {
if (maxFiles > 0 && i >= maxFiles) || (maxSize > 0 && totalSize+file.Info.Size() > maxSize) {
break
}
err := os.Remove(file.Path)
if err != nil {
log.Error("Error deleting file:", file.Path, err)
errorDel++
continue
}
totalSize += file.Info.Size()
deleted++
}
// 如果仍然有超过maxFiles个文件或总大小超过maxSize继续删除最旧的文件
remainingFiles := files
sort.Slice(remainingFiles, func(i, j int) bool {
return remainingFiles[i].Info.ModTime().Before(remainingFiles[j].Info.ModTime())
})
for (maxFiles > 0 && len(remainingFiles) > maxFiles) || (maxSize > 0 && totalSize > maxSize) {
file := remainingFiles[0]
err := os.Remove(file.Path)
if err != nil {
log.Error("Error deleting file:", file.Path, err)
remainingFiles = remainingFiles[1:]
continue
}
totalSize -= file.Info.Size()
remainingFiles = remainingFiles[1:]
}
// 返回结果,用于记录执行结果
return map[string]any{
"msg": "successed",
"filePath": params.FilePath,
"deleted": deleted,
"errorDel": errorDel,
}, nil
}
func getFiles(dir string) ([]FileInfo, error) {
var files []FileInfo
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
files = append(files, FileInfo{Path: path, Info: info})
}
return nil
})
return files, err
}

View File

@@ -50,7 +50,7 @@ func (s *MonitorController) Load(c *gin.Context) {
}
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}

View File

@@ -48,13 +48,15 @@ func (s *SysCacheController) Info(c *gin.Context) {
func (s SysCacheController) Names(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
caches := []model.SysCache{
model.NewNames(i18n.TKey(language, "cache.name.user"), constants.CACHE_LOGIN_TOKEN),
model.NewNames(i18n.TKey(language, "cache.name.token"), constants.CACHE_TOKEN_DEVICE),
model.NewNames(i18n.TKey(language, "cache.name.sys_config"), constants.CACHE_SYS_CONFIG),
model.NewNames(i18n.TKey(language, "cache.name.sys_dict"), constants.CACHE_SYS_DICT),
model.NewNames(i18n.TKey(language, "cache.name.captcha_codes"), constants.CACHE_CAPTCHA_CODE),
model.NewNames(i18n.TKey(language, "cache.name.repeat_submit"), constants.CACHE_REPEAT_SUBMIT),
model.NewNames(i18n.TKey(language, "cache.name.rate_limit"), constants.CACHE_RATE_LIMIT),
model.NewNames(i18n.TKey(language, "cache.name.pwd_err_cnt"), constants.CACHE_PWD_ERR_COUNT),
model.NewNames(i18n.TKey(language, "cache.name.oauth2_codes"), constants.CACHE_OAUTH2_CODE),
model.NewNames(i18n.TKey(language, "cache.name.oauth2_devices"), constants.CACHE_OAUTH2_DEVICE),
model.NewNames(i18n.TKey(language, "cache.name.i18n"), constants.CACHE_I18N),
model.NewNames(i18n.TKey(language, "cache.name.ne_info"), constants.CACHE_NE_INFO),
model.NewNames(i18n.TKey(language, "cache.name.ne_data"), constants.CACHE_NE_DATA),
@@ -71,7 +73,7 @@ func (s SysCacheController) Keys(c *gin.Context) {
}
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -94,7 +96,7 @@ func (s SysCacheController) Value(c *gin.Context) {
}
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -163,10 +165,10 @@ func (s SysCacheController) CleanKeys(c *gin.Context) {
}
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if constants.CACHE_LOGIN_TOKEN == query.CacheName {
if constants.CACHE_TOKEN_DEVICE == query.CacheName {
c.JSON(200, resp.ErrMsg("Cannot delete user information cache"))
return
}
@@ -194,7 +196,7 @@ func (s SysCacheController) CleanValue(c *gin.Context) {
}
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}

View File

@@ -64,7 +64,7 @@ func (s *SysJobController) Info(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
jobId := parse.Number(c.Param("jobId"))
if jobId <= 0 {
c.JSON(400, resp.CodeMsg(40010, "bind err: jobId is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: jobId is empty"))
return
}
@@ -87,11 +87,11 @@ func (s *SysJobController) Add(c *gin.Context) {
var body model.SysJob
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if body.JobId > 0 {
c.JSON(400, resp.CodeMsg(40010, "bind err: jobId not is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: jobId not is empty"))
return
}
@@ -143,11 +143,11 @@ func (s *SysJobController) Edit(c *gin.Context) {
var body model.SysJob
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if body.JobId <= 0 {
c.JSON(400, resp.CodeMsg(40010, "bind err: jobId is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: jobId is empty"))
return
}
@@ -229,7 +229,7 @@ func (s *SysJobController) Remove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
jobId := c.Param("jobId")
if jobId == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: jobId is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: jobId is empty"))
return
}
@@ -261,7 +261,7 @@ func (s *SysJobController) Status(c *gin.Context) {
}
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -299,7 +299,7 @@ func (s *SysJobController) Run(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
jobId := parse.Number(c.Param("jobId"))
if jobId <= 0 {
c.JSON(400, resp.CodeMsg(40010, "bind err: jobId is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: jobId is empty"))
return
}

View File

@@ -73,7 +73,7 @@ func (s *SysJobLogController) List(c *gin.Context) {
func (s *SysJobLogController) Info(c *gin.Context) {
logId := parse.Number(c.Param("logId"))
if logId <= 0 {
c.JSON(400, resp.CodeMsg(40010, "bind err: logId is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: logId is empty"))
return
}
@@ -92,7 +92,7 @@ func (s *SysJobLogController) Remove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
logId := c.Param("logId")
if logId == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: logId is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: logId is empty"))
return
}
@@ -143,13 +143,6 @@ func (s *SysJobLogController) Export(c *gin.Context) {
return
}
// rows := s.sysJobLogService.SelectJobLogList(model.SysJobLog{})
if len(rows) <= 0 {
// 导出数据记录为空
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
return
}
// 闭包函数处理多语言
converI18n := func(language string, arr *[]model.SysJobLog) {
for i := range *arr {

View File

@@ -46,7 +46,7 @@ func (s *SysUserOnlineController) List(c *gin.Context) {
userName := c.Query("userName")
// 获取所有在线用户key
keys, _ := redis.GetKeys("", constants.CACHE_LOGIN_TOKEN+":*")
keys, _ := redis.GetKeys("", constants.CACHE_TOKEN_DEVICE+":*")
// 分批获取
arr := make([]string, 0)
@@ -69,13 +69,13 @@ func (s *SysUserOnlineController) List(c *gin.Context) {
continue
}
var tokenInfo token.TokenInfo
err := json.Unmarshal([]byte(str), &tokenInfo)
var info token.UserInfo
err := json.Unmarshal([]byte(str), &info)
if err != nil {
continue
}
onlineUser := s.sysUserOnlineService.TokenInfoToUserOnline(tokenInfo)
onlineUser := s.sysUserOnlineService.UserInfoToUserOnline(info)
if onlineUser.TokenID != "" {
userOnlines = append(userOnlines, onlineUser)
}
@@ -122,15 +122,14 @@ func (s *SysUserOnlineController) List(c *gin.Context) {
func (s SysUserOnlineController) Logout(c *gin.Context) {
tokenIdStr := c.Param("tokenId")
if tokenIdStr == "" || strings.Contains(tokenIdStr, "*") {
c.JSON(400, resp.CodeMsg(40010, "bind err: tokenId is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: tokenId is empty"))
return
}
// 处理字符转id数组后去重
ids := strings.Split(tokenIdStr, ",")
uniqueIDs := parse.RemoveDuplicates(ids)
uniqueIDs := parse.RemoveDuplicatesToArray(tokenIdStr, ",")
for _, v := range uniqueIDs {
key := constants.CACHE_LOGIN_TOKEN + ":" + v
key := constants.CACHE_TOKEN_DEVICE + ":" + v
if err := redis.Del("", key); err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return

View File

@@ -23,15 +23,14 @@ func Setup(router *gin.Engine) {
monitorGroup := router.Group("/monitor")
{
monitorGroup.GET("/load",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewMonitor.Load,
)
}
// 服务器信息
router.GET("/monitor/system",
// middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:system:info"}}),
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:system:info"}}),
controller.NewSystem.Info,
)
@@ -39,11 +38,11 @@ func Setup(router *gin.Engine) {
sysUserOnlineGroup := router.Group("/monitor/user-online")
{
sysUserOnlineGroup.GET("/list",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:online:list"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:online:list"}}),
controller.NewSysUserOnline.List,
)
sysUserOnlineGroup.DELETE("/logout/:tokenId",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:online:logout"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:online:logout"}}),
controller.NewSysUserOnline.Logout,
)
}
@@ -52,32 +51,31 @@ func Setup(router *gin.Engine) {
sysCacheGroup := router.Group("/monitor/cache")
{
sysCacheGroup.GET("",
// middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:info"}}),
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:cache:info"}}),
controller.NewSysCache.Info,
)
sysCacheGroup.GET("/names",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:list"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:cache:list"}}),
controller.NewSysCache.Names,
)
sysCacheGroup.GET("/keys",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:list"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:cache:list"}}),
controller.NewSysCache.Keys,
)
sysCacheGroup.GET("/value",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:query"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:cache:query"}}),
controller.NewSysCache.Value,
)
sysCacheGroup.DELETE("/names",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:cache:remove"}}),
controller.NewSysCache.CleanNames,
)
sysCacheGroup.DELETE("/keys",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:cache:remove"}}),
controller.NewSysCache.CleanKeys,
)
sysCacheGroup.DELETE("/value",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:cache:remove"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:cache:remove"}}),
controller.NewSysCache.CleanValue,
)
}
@@ -86,26 +84,26 @@ func Setup(router *gin.Engine) {
sysJobLogGroup := router.Group("/monitor/job/log")
{
sysJobLogGroup.GET("/list",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:list"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:list"}}),
controller.NewSysJobLog.List,
)
sysJobLogGroup.GET("/:logId",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:query"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:query"}}),
controller.NewSysJobLog.Info,
)
sysJobLogGroup.DELETE("/:logId",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:remove"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJobLog", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewSysJobLog.Remove,
)
sysJobLogGroup.DELETE("/clean",
repeat.RepeatSubmit(5),
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:remove"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJobLog", collectlogs.BUSINESS_TYPE_CLEAN)),
controller.NewSysJobLog.Clean,
)
sysJobLogGroup.GET("/export",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:export"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:export"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJobLog", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewSysJobLog.Export,
)
@@ -115,47 +113,47 @@ func Setup(router *gin.Engine) {
sysJobGroup := router.Group("/monitor/job")
{
sysJobGroup.GET("/list",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:list"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:list"}}),
controller.NewSysJob.List,
)
sysJobGroup.GET("/:jobId",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:query"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:query"}}),
controller.NewSysJob.Info,
)
sysJobGroup.POST("",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:add"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:add"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_INSERT)),
controller.NewSysJob.Add,
)
sysJobGroup.PUT("",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:edit"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:edit"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_UPDATE)),
controller.NewSysJob.Edit,
)
sysJobGroup.DELETE("/:jobId",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:remove"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:remove"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewSysJob.Remove,
)
sysJobGroup.PUT("/status",
repeat.RepeatSubmit(5),
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_UPDATE)),
controller.NewSysJob.Status,
)
sysJobGroup.PUT("/run/:jobId",
repeat.RepeatSubmit(10),
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_UPDATE)),
controller.NewSysJob.Run,
)
sysJobGroup.PUT("/reset",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:changeStatus"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_CLEAN)),
controller.NewSysJob.ResetQueueJob,
)
sysJobGroup.GET("/export",
middleware.PreAuthorize(map[string][]string{"hasPerms": {"monitor:job:export"}}),
middleware.AuthorizeUser(map[string][]string{"hasPerms": {"monitor:job:export"}}),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysJob", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewSysJob.Export,
)

View File

@@ -11,23 +11,23 @@ var NewSysUserOnline = &SysUserOnline{}
// SysUserOnline 在线用户 服务层处理
type SysUserOnline struct{}
// TokenInfoToUserOnline 在线用户信息
func (s SysUserOnline) TokenInfoToUserOnline(tokenInfo token.TokenInfo) model.SysUserOnline {
if tokenInfo.UserId <= 0 {
// UserInfoToUserOnline 在线用户信息
func (s SysUserOnline) UserInfoToUserOnline(info token.UserInfo) model.SysUserOnline {
if info.UserId <= 0 {
return model.SysUserOnline{}
}
sysUserOnline := model.SysUserOnline{
TokenID: tokenInfo.UUID,
UserName: tokenInfo.User.UserName,
LoginIp: tokenInfo.LoginIp,
LoginLocation: tokenInfo.LoginLocation,
Browser: tokenInfo.Browser,
OS: tokenInfo.OS,
LoginTime: tokenInfo.LoginTime,
TokenID: info.DeviceId,
UserName: info.User.UserName,
LoginIp: info.LoginIp,
LoginLocation: info.LoginLocation,
Browser: info.Browser,
OS: info.OS,
LoginTime: info.LoginTime,
}
if tokenInfo.User.DeptId > 0 {
sysUserOnline.DeptName = tokenInfo.User.Dept.DeptName
if info.User.DeptId > 0 {
sysUserOnline.DeptName = info.User.Dept.DeptName
}
return sysUserOnline
}

View File

@@ -2,6 +2,7 @@ package controller
import (
"fmt"
"time"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
@@ -56,7 +57,7 @@ func (s AlarmController) List(c *gin.Context) {
var query model.AlarmQuery
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 查询数据
@@ -71,7 +72,7 @@ func (s AlarmController) Remove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
id := c.Param("id")
if id == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: id is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: id is empty"))
return
}
@@ -101,7 +102,7 @@ func (s AlarmController) Clear(c *gin.Context) {
}
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -124,7 +125,7 @@ func (s AlarmController) Ack(c *gin.Context) {
}
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -136,3 +137,60 @@ func (s AlarmController) Ack(c *gin.Context) {
}
c.JSON(200, resp.OkData(rows))
}
// 告警列表导出
//
// GET /export
//
// @Tags network_data/alarm
// @Accept json
// @Produce json
// @Param neType query string false "NE Type" Enums(IMS,AMF,AUSF,UDM,SMF,PCF,NSSF,NRF,UPF,MME,CBC,OMC,SGWC,SMSC)
// @Param neId query string false "NE ID The actual record is the network element RmUid"
// @Param neName query string false "NE Name"
// @Param pvFlag query string false "PV Flag" Enums(PNF,VNF)
// @Param alarmCode query string false "alarm status code"
// @Param alarmType query string false "Alarm type Communication alarms=1, Equipment alarms=2, Processing faults=3, Environmental alarms=4, Quality of service alarms=5" Enums(1,2,3,4,5)
// @Param alarmStatus query string false "Alarm status 0:clear, 1:active" Enums(0,1)
// @Param origSeverity query string false "Alarm Type 1: Critical, 2: Major, 3: Minor, 4: Warning" Enums(1,2,3,4)
// @Param sortField query string false "Sort fields, fill in result fields" default(event_time)
// @Param sortOrder query string false "Sort by ascending or descending order, asc desc" default(asc)
// @Param pageNum query number true "pageNum" default(1)
// @Param pageSize query number true "pageSize" default(10)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Alarm List Export
// @Description Alarm List Export
// @Router /neData/alarm/export [get]
func (s AlarmController) Export(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
// 查询结果,根据查询条件结果,单页最大值限制
var query model.AlarmQuery
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 限制导出数据集
if query.PageSize > 10000 {
query.PageSize = 10000
}
// 查询数据
rows, total := s.alarmService.FindByPage(query)
if total == 0 {
// 导出数据记录为空
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
return
}
// 导出文件名称
fileName := fmt.Sprintf("alarm_export_%d_%s_%d.xlsx", len(rows), query.AlarmStatus, time.Now().UnixMilli())
// 导出数据表格
saveFilePath, err := s.alarmService.ExportXlsx(rows, fileName, language, query.AlarmStatus)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
c.FileAttachment(saveFilePath, fileName)
}

View File

@@ -29,12 +29,12 @@ type AlarmForwardController struct {
// @Tags network_data/alarm_forward
// @Accept json
// @Produce json
// @Param neType query string false "NE Type" Enums(IMS,AMF,AUSF,UDM,SMF,PCF,NSSF,NRF,UPF,MME,CBC,OMC,SGWC,SMSC) Enums(1,2,3,4)
// @Param sortField query string false "Sort fields, fill in result fields" default(event_time)
// @Param sortOrder query string false "Sort by ascending or descending order, asc desc" default(asc)
// @Param pageNum query number true "pageNum" default(1)
// @Param pageSize query number true "pageSize" default(10)
// @Success 200 {object} object "Response Results"
// @Param neType query string false "NE Type" Enums(IMS,AMF,AUSF,UDM,SMF,PCF,NSSF,NRF,UPF,MME,CBC,OMC,SGWC,SMSC) Enums(1,2,3,4)
// @Param sortField query string false "Sort fields, fill in result fields" default(event_time)
// @Param sortOrder query string false "Sort by ascending or descending order, asc desc" default(asc)
// @Param pageNum query number true "pageNum" default(1)
// @Param pageSize query number true "pageSize" default(10)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary Alarm Forward Log List
// @Description Alarm Forward Log List
@@ -43,7 +43,7 @@ func (s AlarmForwardController) List(c *gin.Context) {
var query model.AlarmForwardLogQuery
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 查询数据

View File

@@ -34,7 +34,7 @@ type AlarmLogController struct {
// @Param neType query string false "NE Type" Enums(IMS,AMF,AUSF,UDM,SMF,PCF,NSSF,NRF,UPF,MME,CBC,OMC,SGWC,SMSC)
// @Param neId query string false "NE ID The actual record is the network element RmUid"
// @Param alarmLogType query string false "AlarmLog type Communication AlarmLogs=1, Equipment AlarmLogs=2, Processing faults=3, Environmental AlarmLogs=4, Quality of service AlarmLogs=5" Enums(1,2,3,4,5)
// @Param alarmStatus query string false "Alarm status 0:clear, 1:active" Enums(0,1)
// @Param alarmStatus query string false "Alarm status 0:clear, 1:active" Enums(0,1)
// @Param origSeverity query string false "Alarm Type 1: Critical, 2: Major, 3: Minor, 4: Warning" Enums(1,2,3,4)
// @Param sortField query string false "Sort fields, fill in result fields" default(event_time)
// @Param sortOrder query string false "Sort by ascending or descending order, asc desc" default(asc)
@@ -49,7 +49,7 @@ func (s AlarmLogController) List(c *gin.Context) {
var query model.AlarmLogQuery
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 查询数据
@@ -67,7 +67,7 @@ func (s AlarmLogController) List(c *gin.Context) {
// @Param neType query string false "NE Type" Enums(IMS,AMF,AUSF,UDM,SMF,PCF,NSSF,NRF,UPF,MME,CBC,OMC,SGWC,SMSC)
// @Param neId query string false "NE ID The actual record is the network element RmUid"
// @Param alarmLogType query string false "AlarmLog type Communication AlarmLogs=1, Equipment AlarmLogs=2, Processing faults=3, Environmental AlarmLogs=4, Quality of service AlarmLogs=5" Enums(1,2,3,4,5)
// @Param alarmStatus query string false "Alarm status 0:clear, 1:active" Enums(0,1)
// @Param alarmStatus query string false "Alarm status 0:clear, 1:active" Enums(0,1)
// @Param origSeverity query string false "Alarm Type 1: Critical, 2: Major, 3: Minor, 4: Warning" Enums(1,2,3,4)
// @Param sortField query string false "Sort fields, fill in result fields" default(event_time)
// @Param sortOrder query string false "Sort by ascending or descending order, asc desc" default(asc)
@@ -82,7 +82,7 @@ func (s AlarmLogController) Event(c *gin.Context) {
var query model.AlarmEventQuery
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 查询数据

View File

@@ -0,0 +1,101 @@
package controller
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/modules/network_data/model"
"be.ems/src/modules/network_data/service"
"github.com/gin-gonic/gin"
)
// 实例化控制层 BackupController 结构体
var NewBackup = &BackupController{
backupService: service.NewBackup,
}
// 备份数据
//
// PATH /backup
type BackupController struct {
backupService *service.Backup // 备份相关服务
}
// 备份文件-更新FTP配置
//
// PUT /ftp
func (s BackupController) FTPUpdate(c *gin.Context) {
var body model.BackupDataFTP
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
byteData, err := json.Marshal(body)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
up := s.backupService.FTPConfigUpdate(string(byteData), reqctx.LoginUserToUserName(c))
if up <= 0 {
c.JSON(200, resp.ErrMsg("update failed"))
return
}
c.JSON(200, resp.Ok(nil))
}
// 备份文件-获取FTP配置
//
// GET /ftp
func (s BackupController) FTPInfo(c *gin.Context) {
info := s.backupService.FTPConfigInfo()
c.JSON(200, resp.OkData(info))
}
// 备份文件-文件FTP发送
//
// POST /ftp
func (s BackupController) FTPPush(c *gin.Context) {
var body struct {
Path string `form:"path" binding:"required"` // 路径必须是 BACKUP_DIR 开头的路径
Filename string `form:"fileName" binding:"required"`
Tag string `form:"tag" binding:"required"` // 标签,用于区分不同的备份文件
}
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 判断路径是否合法
if !strings.HasPrefix(body.Path, s.backupService.BACKUP_DIR) {
c.JSON(200, resp.ErrMsg("operation path is not within the allowed range"))
return
}
// 判断文件是否存在
localFilePath := filepath.Join(body.Path, body.Filename)
if runtime.GOOS == "windows" {
localFilePath = fmt.Sprintf("C:%s", localFilePath)
}
if _, err := os.Stat(localFilePath); os.IsNotExist(err) {
c.JSON(200, resp.ErrMsg("file does not exist"))
return
}
// 发送文件
err := s.backupService.FTPPushFile(localFilePath, body.Tag)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
c.JSON(200, resp.Ok(nil))
}

View File

@@ -49,7 +49,7 @@ func (s KPIController) KPIData(c *gin.Context) {
var querys model.KPIQuery
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -82,7 +82,7 @@ func (s KPIController) KPIData(c *gin.Context) {
func (s KPIController) KPITitle(c *gin.Context) {
neType := c.Query("neType")
if neType == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: neType is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: neType is empty"))
return
}
kpiTitles := s.kpiReportService.FindTitle(neType)

View File

@@ -53,7 +53,7 @@ func (s NBStateController) List(c *gin.Context) {
var query model.NBStateQuery
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -89,7 +89,7 @@ func (s NBStateController) Export(c *gin.Context) {
var querys model.NBStateQuery
if err := c.ShouldBindBodyWithJSON(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 限制导出数据集

View File

@@ -52,7 +52,7 @@ func (s *AMFController) UEList(c *gin.Context) {
var querys model.UEEventAMFQuery
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -86,7 +86,7 @@ func (s *AMFController) UERemove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
id := c.Param("id")
if id == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: id is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: id is empty"))
return
}
@@ -126,7 +126,7 @@ func (s *AMFController) UEExport(c *gin.Context) {
var querys model.UEEventAMFQuery
if err := c.ShouldBindBodyWithJSON(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 限制导出数据集
@@ -181,7 +181,7 @@ func (s *AMFController) NbInfoList(c *gin.Context) {
}
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -221,7 +221,7 @@ func (s *AMFController) NbStateList(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Query("neId")
if neId == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: neId is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}

View File

@@ -53,7 +53,7 @@ func (s *IMSController) CDRList(c *gin.Context) {
var querys model.CDREventIMSQuery
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -87,7 +87,7 @@ func (s *IMSController) CDRRemove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
id := c.Param("id")
if id == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: id is empty"))
c.JSON(422, resp.CodeMsg(442002, "bind err: id is empty"))
return
}
@@ -127,7 +127,7 @@ func (s *IMSController) CDRExport(c *gin.Context) {
var querys model.CDREventIMSQuery
if err := c.ShouldBindBodyWithJSON(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 限制导出数据集
@@ -177,7 +177,7 @@ func (s *IMSController) UeSessionNum(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Query("neId")
if neId == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: neId is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
@@ -222,7 +222,7 @@ func (s *IMSController) UeSessionList(c *gin.Context) {
}
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}

View File

@@ -52,7 +52,7 @@ func (s *MMEController) UEList(c *gin.Context) {
var querys model.UEEventMMEQuery
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -86,7 +86,7 @@ func (s *MMEController) UERemove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
id := c.Param("id")
if id == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: id is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: id is empty"))
return
}
@@ -126,7 +126,7 @@ func (s *MMEController) UEExport(c *gin.Context) {
var querys model.UEEventMMEQuery
if err := c.ShouldBindBodyWithJSON(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 限制导出数据集
@@ -181,7 +181,7 @@ func (s *MMEController) NbInfoList(c *gin.Context) {
}
if err := c.ShouldBindQuery(&query); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -221,7 +221,7 @@ func (s *MMEController) NbStateList(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Query("neId")
if neId == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: neId is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}

View File

@@ -53,7 +53,7 @@ func (s *SGWCController) CDRList(c *gin.Context) {
var querys model.CDREventSGWCQuery
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -87,7 +87,7 @@ func (s *SGWCController) CDRRemove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
id := c.Param("id")
if id == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: id is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: id is empty"))
return
}
@@ -127,7 +127,7 @@ func (s *SGWCController) CDRExport(c *gin.Context) {
var querys model.CDREventSGWCQuery
if err := c.ShouldBindBodyWithJSON(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 限制导出数据集

View File

@@ -55,7 +55,7 @@ func (s *SMFController) CDRList(c *gin.Context) {
var querys model.CDREventSMFQuery
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -89,7 +89,7 @@ func (s *SMFController) CDRRemove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
id := c.Param("id")
if id == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: id is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: id is empty"))
return
}
@@ -129,7 +129,7 @@ func (s *SMFController) CDRExport(c *gin.Context) {
var querys model.CDREventSMFQuery
if err := c.ShouldBindBodyWithJSON(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 限制导出数据集
@@ -181,7 +181,8 @@ func (s *SMFController) SubUserNum(c *gin.Context) {
NeId string `form:"neId" binding:"required"`
}
if err := c.ShouldBindQuery(&query); err != nil {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -229,7 +230,8 @@ func (s *SMFController) SubUserList(c *gin.Context) {
PageNum string `form:"pageNum"`
}
if err := c.ShouldBindQuery(&query); err != nil {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}

View File

@@ -52,7 +52,7 @@ func (s *SMSCController) CDRList(c *gin.Context) {
var querys model.CDREventSMSCQuery
if err := c.ShouldBindQuery(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
@@ -86,7 +86,7 @@ func (s *SMSCController) CDRRemove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
id := c.Param("id")
if id == "" {
c.JSON(400, resp.CodeMsg(40010, "bind err: id is empty"))
c.JSON(422, resp.CodeMsg(422002, "bind err: id is empty"))
return
}
@@ -126,7 +126,7 @@ func (s *SMSCController) CDRExport(c *gin.Context) {
var querys model.CDREventSMSCQuery
if err := c.ShouldBindBodyWithJSON(&querys); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 限制导出数据集

View File

@@ -49,10 +49,9 @@ type UDMAuthController struct {
// @Description UDM Authenticated User Data List Refresh Synchronization Latest
// @Router /neData/udm/auth/resetData/{neId} [put]
func (s *UDMAuthController) ResetData(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
if neId == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
@@ -101,7 +100,7 @@ func (s *UDMAuthController) Info(c *gin.Context) {
neId := c.Param("neId")
imsi := c.Param("imsi")
if neId == "" || imsi == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId/imsi is empty"))
return
}
@@ -156,14 +155,18 @@ func (s *UDMAuthController) Add(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
if neId == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
var body model.UDMAuthUser
err := c.ShouldBindBodyWithJSON(&body)
if err != nil || body.IMSI == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if body.IMSI == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: imsi is empty"))
return
}
@@ -217,14 +220,18 @@ func (s *UDMAuthController) Adds(c *gin.Context) {
neId := c.Param("neId")
num := c.Param("num")
if neId == "" || num == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId/num is empty"))
return
}
var body model.UDMAuthUser
err := c.ShouldBindBodyWithJSON(&body)
if err != nil || body.IMSI == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if body.IMSI == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: imsi is empty"))
return
}
@@ -276,14 +283,18 @@ func (s *UDMAuthController) Edit(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
if neId == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
var body model.UDMAuthUser
err := c.ShouldBindBodyWithJSON(&body)
if err != nil || body.IMSI == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if body.IMSI == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: imsi is empty"))
return
}
@@ -336,7 +347,7 @@ func (s *UDMAuthController) Remove(c *gin.Context) {
neId := c.Param("neId")
imsi := c.Param("imsi")
if neId == "" || imsi == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId/imsi is empty"))
return
}
@@ -402,7 +413,7 @@ func (s *UDMAuthController) Removes(c *gin.Context) {
imsi := c.Param("imsi")
num := c.Param("num")
if neId == "" || imsi == "" || num == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId/imsi/num is empty"))
return
}
@@ -458,7 +469,7 @@ func (s *UDMAuthController) Export(c *gin.Context) {
neId := c.Query("neId")
fileType := c.Query("type")
if neId == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
if !(fileType == "csv" || fileType == "txt") {
@@ -497,8 +508,7 @@ func (s *UDMAuthController) Export(c *gin.Context) {
data = append(data, []string{v.IMSI, v.Ki, v.AlgoIndex, v.Amf, opc})
}
// 输出到文件
err := file.WriterFileCSV(data, filePath)
if err != nil {
if err := file.WriterFileCSV(data, filePath); err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
@@ -515,7 +525,6 @@ func (s *UDMAuthController) Export(c *gin.Context) {
data = append(data, []string{v.IMSI, v.Ki, v.AlgoIndex, v.Amf, opc})
}
// 输出到文件
if err := file.WriterFileTXT(data, ",", filePath); err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
@@ -548,7 +557,7 @@ func (s *UDMAuthController) Import(c *gin.Context) {
}
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}

View File

@@ -48,10 +48,9 @@ type UDMSubController struct {
// @Description UDM Subscriber User Reload Data
// @Router /neData/udm/sub/resetData/{neId} [put]
func (s *UDMSubController) ResetData(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
if neId == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
@@ -101,7 +100,7 @@ func (s *UDMSubController) Info(c *gin.Context) {
neId := c.Param("neId")
imsi := c.Param("imsi")
if neId == "" || imsi == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId or imsi is empty"))
return
}
@@ -156,18 +155,18 @@ func (s *UDMSubController) Add(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
if neId == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
var body model.UDMSubUser
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if len(body.IMSI) != 15 {
c.JSON(400, resp.CodeMsg(40010, "bind err: IMSI length is not 15 bits"))
c.JSON(422, resp.CodeMsg(422002, "bind err: IMSI length is not 15 bits"))
return
}
@@ -222,18 +221,18 @@ func (s *UDMSubController) Adds(c *gin.Context) {
neId := c.Param("neId")
num := c.Param("num")
if neId == "" || num == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId/num is empty"))
return
}
var body model.UDMSubUser
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if len(body.IMSI) != 15 {
c.JSON(400, resp.CodeMsg(40010, "bind err: IMSI length is not 15 bits"))
c.JSON(422, resp.CodeMsg(422002, "bind err: IMSI length is not 15 bits"))
return
}
@@ -288,18 +287,18 @@ func (s *UDMSubController) Edit(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
if neId == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
var body model.UDMSubUser
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if len(body.IMSI) != 15 {
c.JSON(400, resp.CodeMsg(40010, "bind err: IMSI length is not 15 bits"))
c.JSON(422, resp.CodeMsg(422002, "bind err: IMSI length is not 15 bits"))
return
}
@@ -353,7 +352,7 @@ func (s *UDMSubController) Remove(c *gin.Context) {
neId := c.Param("neId")
imsi := c.Param("imsi")
if neId == "" || len(imsi) < 15 {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId/imsi is empty"))
return
}
@@ -419,7 +418,7 @@ func (s *UDMSubController) Removes(c *gin.Context) {
imsi := c.Param("imsi")
num := c.Param("num")
if neId == "" || len(imsi) < 15 || num == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId/imsi/num is empty"))
return
}
@@ -476,7 +475,7 @@ func (s *UDMSubController) Export(c *gin.Context) {
neId := c.Query("neId")
fileType := c.Query("type")
if neId == "" || fileType == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
c.JSON(422, resp.CodeMsg(422002, "bind err: neId or type is empty"))
return
}
if !(fileType == "csv" || fileType == "txt") {
@@ -556,7 +555,7 @@ func (s *UDMSubController) Import(c *gin.Context) {
}
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(40422, errMsgs))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}

View File

@@ -0,0 +1,561 @@
package controller
import (
"fmt"
"path/filepath"
"strings"
"time"
"be.ems/src/framework/constants"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/telnet"
"be.ems/src/framework/utils/file"
"be.ems/src/framework/utils/parse"
"be.ems/src/modules/network_data/model"
neDataService "be.ems/src/modules/network_data/service"
neService "be.ems/src/modules/network_element/service"
"github.com/gin-gonic/gin"
)
// 实例化控制层 UDMVOIPController 结构体
var NewUDMVOIP = &UDMVOIPController{
udmVOIPService: neDataService.NewUDMVOIPUser,
neInfoService: neService.NewNeInfo,
}
// UDMVOIP用户
//
// PATH /udm/voip
type UDMVOIPController struct {
udmVOIPService *neDataService.UDMVOIPUser // UDMVOIP信息服务
neInfoService *neService.NeInfo // 网元信息服务
}
// UDMVOIP用户重载数据
//
// PUT /resetData/:neId
//
// @Tags network_data/udm/voip
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VOIP User Data Refresh
// @Description UDM VOIP User Data List Refresh Synchronization Latest
// @Router /neData/udm/voip/resetData/{neId} [put]
func (s *UDMVOIPController) ResetData(c *gin.Context) {
neId := c.Param("neId")
if neId == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
data := s.udmVOIPService.ResetData(neId)
c.JSON(200, resp.OkData(data))
}
// UDMVOIP用户列表
//
// GET /list
//
// @Tags network_data/udm/voip
// @Accept json
// @Produce json
// @Param neId query string true "NE ID" default(001)
// @Param username query string false "User Name"
// @Param pageNum query number true "pageNum" default(1)
// @Param pageSize query number true "pageSize" default(10)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VOIP User List
// @Description UDM VOIP User List
// @Router /neData/udm/voip/list [get]
func (s *UDMVOIPController) List(c *gin.Context) {
query := reqctx.QueryMap(c)
total, rows := s.udmVOIPService.FindByPage(query)
c.JSON(200, resp.OkData(map[string]any{"total": total, "rows": rows}))
}
// UDMVOIP用户信息
//
// GET /:neId/:username
//
// @Tags network_data/udm/voip
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Param value path string true "User Name"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VOIP User Information
// @Description UDM VOIP User Information
// @Router /neData/udm/voip/{neId}/{value} [get]
func (s *UDMVOIPController) Info(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
username := c.Param("username")
if neId == "" || username == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId or username is empty"))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", neId)
if neInfo.NeId != neId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 发送MML
cmd := fmt.Sprintf("dsp voip:username=%s", username)
data, err := telnet.ConvertToMap(telnetClient, cmd)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
if len(data) == 0 {
c.JSON(200, resp.ErrMsg("No VOIP Data"))
return
}
// 解析返回的数据
u := s.udmVOIPService.ParseInfo(neId, data)
if u.ID != "" {
s.udmVOIPService.Insert(neId, u.UserName)
c.JSON(200, resp.OkData(u))
return
}
c.JSON(200, resp.ErrMsg("No VOIP Data"))
}
// UDMVOIP用户新增
//
// POST /:neId
//
// @Tags network_data/udm/voip
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VOIP User Added
// @Description UDM VOIP User Added
// @Router /neData/udm/voip/{neId} [post]
func (s *UDMVOIPController) Add(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
if neId == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
var body model.UDMVOIPUser
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if body.UserName == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: username is empty"))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", neId)
if neInfo.NeId != neId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 发送MML
cmd := fmt.Sprintf("add voip:username=%s,password=%s", body.UserName, body.Password)
data, err := telnet.ConvertToStr(telnetClient, cmd)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
// 命令ok时
if strings.Contains(data, "ok") {
s.udmVOIPService.Insert(neId, body.UserName)
}
c.JSON(200, resp.OkData(data))
}
// UDMVOIP用户批量新增
//
// POST /:neId/:num
//
// @Tags network_data/udm/voip
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Param value path number true "Number of releases, value includes start username"
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VOIP User Batch Add
// @Description UDM VOIP User Batch Add
// @Router /neData/udm/voip/{neId}/{value} [post]
func (s *UDMVOIPController) Adds(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
num := c.Param("num")
if neId == "" || num == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId or num is empty"))
return
}
var body model.UDMVOIPUser
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if body.UserName == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: username is empty"))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", neId)
if neInfo.NeId != neId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 发送MML
cmd := fmt.Sprintf("baa voip:sub_num=%s,start_username=%s,password=%s", num, body.UserName, body.Password)
data, err := telnet.ConvertToStr(telnetClient, cmd)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
// 命令ok时
if strings.Contains(data, "ok") {
s.udmVOIPService.LoadData(neId, body.UserName, num)
}
c.JSON(200, resp.OkData(data))
}
// UDMVOIP用户删除
//
// DELETE /:neId/:username
//
// @Tags network_data/udm/voip
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Param value path string true "User Name, multiple separated by a , sign"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VOIP User Deletion
// @Description UDM VOIP User Deletion
// @Router /neData/udm/voip/{neId}/{value} [delete]
func (s *UDMVOIPController) Remove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
username := c.Param("username")
if neId == "" || username == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId or username is empty"))
return
}
// 处理字符转id数组后去重
usernameArr := strings.Split(username, ",")
uniqueIDs := parse.RemoveDuplicates(usernameArr)
if len(uniqueIDs) <= 0 {
c.JSON(200, resp.Err(nil))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", neId)
if neInfo.NeId != neId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
resultData := map[string]string{}
for _, v := range uniqueIDs {
// 发送MML
cmd := fmt.Sprintf("del voip:username=%s", v)
data, err := telnet.ConvertToStr(telnetClient, cmd)
if err != nil {
resultData[v] = err.Error()
continue
}
// 命令ok时
if strings.Contains(data, "ok") {
s.udmVOIPService.Delete(v, neId)
}
resultData[v] = data
}
c.JSON(200, resp.OkData(resultData))
}
// UDMVOIP用户批量删除
//
// DELETE /:neId/:username/:num
//
// @Tags network_data/udm/voip
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Param username path string true "User Name"
// @Param num path number true "Number of releases, value includes start username"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VOIP User Batch Deletion
// @Description UDM VOIP User Batch Deletion
// @Router /neData/udm/voip/{neId}/{username}/{num} [delete]
func (s *UDMVOIPController) Removes(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
username := c.Param("username")
num := c.Param("num")
if neId == "" || username == "" || num == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId/username/num is empty"))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", neId)
if neInfo.NeId != neId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 发送MML
cmd := fmt.Sprintf("bde voip:start_username=%s,sub_num=%s", username, num)
data, err := telnet.ConvertToStr(telnetClient, cmd)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
// 命令ok时
if strings.Contains(data, "ok") {
s.udmVOIPService.LoadData(neId, username, num)
}
c.JSON(200, resp.OkData(data))
}
// UDMVOIP用户导出
//
// GET /export
//
// @Tags network_data/udm/voip
// @Accept json
// @Produce json
// @Param neId query string true "NE ID" default(001)
// @Param type query string true "File Type" Enums(csv,txt) default(txt)
// @Param username query string false "User Name"
// @Param pageNum query number true "pageNum" default(1)
// @Param pageSize query number true "pageSize" default(10)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VOIP User Export
// @Description UDM VOIP User Export
// @Router /neData/udm/voip/export [get]
func (s *UDMVOIPController) Export(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
// 查询结果,根据查询条件结果,单页最大值限制
neId := c.Query("neId")
fileType := c.Query("type")
if neId == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
if !(fileType == "csv" || fileType == "txt") {
c.JSON(200, resp.ErrMsg("file type error, only support csv,txt"))
return
}
query := reqctx.QueryMap(c)
total, rows := s.udmVOIPService.FindByPage(query)
if total == 0 {
// 导出数据记录为空
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
return
}
// rows := s.udmVOIPService.SelectList(model.UDMVOIPUser{NeId: neId})
if len(rows) <= 0 {
// 导出数据记录为空
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
return
}
// 文件名
fileName := fmt.Sprintf("udm_voip_user_export_%s_%d.%s", neId, time.Now().UnixMilli(), fileType)
filePath := filepath.Join(file.ParseUploadFileDir(constants.UPLOAD_EXPORT), fileName)
if fileType == "csv" {
// 转换数据
data := [][]string{}
data = append(data, []string{"username", "password"})
for _, v := range rows {
data = append(data, []string{v.UserName, v.Password})
}
// 输出到文件
if err := file.WriterFileCSV(data, filePath); err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
}
if fileType == "txt" {
// 转换数据
data := [][]string{}
for _, v := range rows {
data = append(data, []string{v.UserName, v.Password})
}
// 输出到文件
if err := file.WriterFileTXT(data, ",", filePath); err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
}
c.FileAttachment(filePath, fileName)
}
// UDMVOIP用户导入
//
// POST /import
//
// @Tags network_data/udm/voip
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VOIP User Import
// @Description UDM VOIP User Import
// @Router /neData/udm/voip/import [post]
func (s *UDMVOIPController) Import(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body struct {
NeId string `json:"neId" binding:"required"` // 网元ID
UploadPath string `json:"uploadPath" binding:"required"` // 上传文件路径
}
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 判断文件名
if !(strings.HasSuffix(body.UploadPath, ".csv") || strings.HasSuffix(body.UploadPath, ".txt")) {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserAuthFileFormat")))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", body.NeId)
if neInfo.NeId != body.NeId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的SSH客户端
sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer sshClient.Close()
// 网元主机的SSH客户端进行文件传输
sftpClient, err := sshClient.NewClientSFTP()
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer sftpClient.Close()
// 本地文件
localFilePath := file.ParseUploadFileAbsPath(body.UploadPath)
neFilePath := fmt.Sprintf("/tmp/%s", filepath.Base(localFilePath))
// 复制到远程
if err = sftpClient.CopyFileLocalToRemote(localFilePath, neFilePath); err != nil {
c.JSON(200, resp.ErrMsg("error uploading file"))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient(neInfo.NeType, neInfo.NeId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 结果信息
var resultMsg string
var resultErr error
// 发送MML
cmd := fmt.Sprintf("import voip:path=%s", neFilePath)
resultMsg, resultErr = telnet.ConvertToStr(telnetClient, cmd)
if resultErr != nil {
c.JSON(200, resp.ErrMsg(resultErr.Error()))
return
}
// 命令ok时
if strings.Contains(resultMsg, "ok") {
if strings.HasSuffix(body.UploadPath, ".csv") {
data := file.ReadFileCSV(localFilePath)
go s.udmVOIPService.InsertData(neInfo.NeId, "csv", data)
}
if strings.HasSuffix(body.UploadPath, ".txt") {
data := file.ReadFileTXT(",", localFilePath)
go s.udmVOIPService.InsertData(neInfo.NeId, "txt", data)
}
}
c.JSON(200, resp.OkMsg(resultMsg))
}

View File

@@ -0,0 +1,590 @@
package controller
import (
"fmt"
"path/filepath"
"strings"
"time"
"be.ems/src/framework/constants"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
"be.ems/src/framework/telnet"
"be.ems/src/framework/utils/file"
"be.ems/src/framework/utils/parse"
"be.ems/src/modules/network_data/model"
neDataService "be.ems/src/modules/network_data/service"
neService "be.ems/src/modules/network_element/service"
"github.com/gin-gonic/gin"
)
// 实例化控制层 UDMVolteIMSController 结构体
var NewUDMVolteIMS = &UDMVolteIMSController{
udmVolteIMSService: neDataService.NewUDMVolteIMSUser,
neInfoService: neService.NewNeInfo,
}
// UDMVolteIMS用户
//
// PATH /udm/volte-ims
type UDMVolteIMSController struct {
udmVolteIMSService *neDataService.UDMVolteIMSUser // UDMVolteIMS信息服务
neInfoService *neService.NeInfo // 网元信息服务
}
// UDMVolteIMS用户重载数据
//
// PUT /resetData/:neId
//
// @Tags network_data/udm/volte-ims
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VolteIMS User Data Refresh
// @Description UDM Authenticated User Data List Refresh Synchronization Latest
// @Router /neData/udm/volte-ims/resetData/{neId} [put]
func (s *UDMVolteIMSController) ResetData(c *gin.Context) {
neId := c.Param("neId")
if neId == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
data := s.udmVolteIMSService.ResetData(neId)
c.JSON(200, resp.OkData(data))
}
// UDMVolteIMS用户列表
//
// GET /list
//
// @Tags network_data/udm/volte-ims
// @Accept json
// @Produce json
// @Param neId query string true "NE ID" default(001)
// @Param imsi query string false "IMSI"
// @Param pageNum query number true "pageNum" default(1)
// @Param pageSize query number true "pageSize" default(10)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VolteIMS User List
// @Description UDM VolteIMS User List
// @Router /neData/udm/volte-ims/list [get]
func (s *UDMVolteIMSController) List(c *gin.Context) {
query := reqctx.QueryMap(c)
total, rows := s.udmVolteIMSService.FindByPage(query)
c.JSON(200, resp.OkData(map[string]any{"total": total, "rows": rows}))
}
// UDMVolteIMS用户信息
//
// GET /:neId/:imsi
//
// @Tags network_data/udm/volte-ims
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Param value path string true "IMSI"
// @Param msisdn query string true "MSISDN"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VolteIMS User Information
// @Description UDM VolteIMS User Information
// @Router /neData/udm/volte-ims/{neId}/{value} [get]
func (s *UDMVolteIMSController) Info(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
imsi := c.Param("imsi")
msisdn := c.Query("msisdn")
if neId == "" || imsi == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId or imsi is empty"))
return
}
if msisdn == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: msisdn is required"))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", neId)
if neInfo.NeId != neId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 发送MML
cmd := fmt.Sprintf("dsp imsuser:imsi=%s,msisdn=%s", imsi, msisdn)
data, err := telnet.ConvertToMap(telnetClient, cmd)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
if len(data) == 0 {
c.JSON(200, resp.ErrMsg("No Volte IMS Data"))
return
}
// 解析返回的数据
u := s.udmVolteIMSService.ParseInfo(neId, data)
if u.ID != "" {
s.udmVolteIMSService.InsertByIMSI(imsi, neId)
c.JSON(200, resp.OkData(u))
return
}
c.JSON(200, resp.ErrMsg("No Volte IMS Data"))
}
// UDMVolteIMS用户新增
//
// POST /:neId
//
// @Tags network_data/udm/volte-ims
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VolteIMS User Added
// @Description UDM VolteIMS User Added If VoIP tag=0, then MSISDN and IMSI need to be the same.
// @Router /neData/udm/volte-ims/{neId} [post]
func (s *UDMVolteIMSController) Add(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
if neId == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
var body model.UDMVolteIMSUser
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if body.IMSI == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: imsi is empty"))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", neId)
if neInfo.NeId != neId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 检查同IMSI下msisdn是否存在
hasMsisdns := s.udmVolteIMSService.Find(model.UDMVolteIMSUser{IMSI: body.IMSI, MSISDN: body.MSISDN, NeId: neId})
if len(hasMsisdns) > 0 {
c.JSON(200, resp.ErrMsg("IMSI and MSISDN already exist"))
return
}
// 发送MML
cmd := fmt.Sprintf("add imsuser:imsi=%s,msisdn=%s,volte=%s,vni=%s", body.IMSI, body.MSISDN, body.Tag, body.VNI)
data, err := telnet.ConvertToStr(telnetClient, cmd)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
// 命令ok时
if strings.Contains(data, "ok") {
s.udmVolteIMSService.InsertByIMSI(body.IMSI, neId)
}
c.JSON(200, resp.OkData(data))
}
// UDMVolteIMS用户批量新增
//
// POST /:neId/:num
//
// @Tags network_data/udm/volte-ims
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Param value path number true "Number of releases, value includes start imsi"
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VolteIMS User Batch Add
// @Description UDM VolteIMS User Batch Add
// @Router /neData/udm/volte-ims/{neId}/{value} [post]
func (s *UDMVolteIMSController) Adds(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
num := c.Param("num")
if neId == "" || num == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
return
}
var body model.UDMVolteIMSUser
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
if body.IMSI == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: imsi is empty"))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", neId)
if neInfo.NeId != neId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 发送MML
cmd := fmt.Sprintf("baa imsuser:sub_num=%s,start_imsi=%s,start_msisdn=%s,volte=%s,vni=%s", num, body.IMSI, body.MSISDN, body.Tag, body.VNI)
data, err := telnet.ConvertToStr(telnetClient, cmd)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
// 命令ok时
if strings.Contains(data, "ok") {
s.udmVolteIMSService.LoadData(neId, body.IMSI, num)
}
c.JSON(200, resp.OkData(data))
}
// UDMVolteIMS用户删除
//
// DELETE /:neId/:imsi
//
// @Tags network_data/udm/volte-ims
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Param value path string true "IMSI, multiple separated by a , sign"
// @Param msisdn query string false "MSISDN"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM Authenticated User Deletion
// @Description UDM Authenticated User Deletion
// @Router /neData/udm/volte-ims/{neId}/{value} [delete]
func (s *UDMVolteIMSController) Remove(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
imsi := c.Param("imsi")
msisdn := c.Query("msisdn")
if neId == "" || imsi == "" {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
return
}
imsiArr := strings.Split(imsi, ",")
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", neId)
if neInfo.NeId != neId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 精确msisdn删除
if msisdn != "" {
// 发送MML
cmd := fmt.Sprintf("del imsuser:imsi=%s,msisdn=%s", imsiArr[0], msisdn)
data, err := telnet.ConvertToStr(telnetClient, cmd)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
// 命令ok时
if strings.Contains(data, "ok") {
s.udmVolteIMSService.Delete(imsi, neId)
}
c.JSON(200, resp.OkData(data))
return
} else {
// 处理字符转id数组后去重
uniqueIDs := parse.RemoveDuplicates(imsiArr)
if len(uniqueIDs) <= 0 {
c.JSON(200, resp.Err(nil))
return
}
resultData := map[string]string{}
for _, imsi := range uniqueIDs {
// 发送MML
cmd := fmt.Sprintf("del imsuser:imsi=%s", imsi)
data, err := telnet.ConvertToStr(telnetClient, cmd)
if err != nil {
resultData[imsi] = err.Error()
continue
}
// 命令ok时
if strings.Contains(data, "ok") {
s.udmVolteIMSService.Delete(imsi, neId)
}
resultData[imsi] = data
}
c.JSON(200, resp.OkData(resultData))
return
}
}
// UDMVolteIMS用户批量删除
//
// DELETE /:neId/:imsi/:num
//
// @Tags network_data/udm/volte-ims
// @Accept json
// @Produce json
// @Param neId path string true "NE ID" default(001)
// @Param imsi path string true "IMSI"
// @Param num path number true "Number of releases, value includes start imsi"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM VolteIMS User Batch Deletion
// @Description UDM VolteIMS User Batch Deletion
// @Router /neData/udm/volte-ims/{neId}/{imsi}/{num} [delete]
func (s *UDMVolteIMSController) Removes(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
neId := c.Param("neId")
imsi := c.Param("imsi")
num := c.Param("num")
if neId == "" || imsi == "" || num == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId/imsi/num is empty"))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", neId)
if neInfo.NeId != neId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient("UDM", neId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 发送MML
cmd := fmt.Sprintf("bde imsuser:start_imsi=%s,sub_num=%s", imsi, num)
data, err := telnet.ConvertToStr(telnetClient, cmd)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
// 命令ok时
if strings.Contains(data, "ok") {
s.udmVolteIMSService.LoadData(neId, imsi, num)
}
c.JSON(200, resp.OkData(data))
}
// UDMVolteIMS用户导出
//
// GET /export
//
// @Tags network_data/udm/volte-ims
// @Accept json
// @Produce json
// @Param neId query string true "NE ID" default(001)
// @Param type query string true "File Type" Enums(csv,txt) default(txt)
// @Param imsi query string false "IMSI"
// @Param pageNum query number true "pageNum" default(1)
// @Param pageSize query number true "pageSize" default(10)
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM Authenticated User Export
// @Description UDM Authenticated User Export
// @Router /neData/udm/volte-ims/export [get]
func (s *UDMVolteIMSController) Export(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
// 查询结果,根据查询条件结果,单页最大值限制
neId := c.Query("neId")
fileType := c.Query("type")
if neId == "" {
c.JSON(422, resp.CodeMsg(422002, "bind err: neId is empty"))
return
}
if !(fileType == "csv" || fileType == "txt") {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserSubFileFormat")))
return
}
query := reqctx.QueryMap(c)
total, rows := s.udmVolteIMSService.FindByPage(query)
if total == 0 {
// 导出数据记录为空
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
return
}
if len(rows) <= 0 {
// 导出数据记录为空
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.exportEmpty")))
return
}
// 文件名
fileName := fmt.Sprintf("udm_volte_user_export_%s_%d.%s", neId, time.Now().UnixMilli(), fileType)
filePath := filepath.Join(file.ParseUploadFileDir(constants.UPLOAD_EXPORT), fileName)
if fileType == "csv" {
// 转换数据
data := [][]string{}
data = append(data, []string{"IMSI", "MSISDN", "TAG", "VNI"})
for _, v := range rows {
data = append(data, []string{v.IMSI, v.MSISDN, v.Tag, v.VNI})
}
// 输出到文件
if err := file.WriterFileCSV(data, filePath); err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
}
if fileType == "txt" {
// 转换数据
data := [][]string{}
for _, v := range rows {
data = append(data, []string{v.IMSI, v.MSISDN, v.Tag, v.VNI})
}
// 输出到文件
if err := file.WriterFileTXT(data, ",", filePath); err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
}
c.FileAttachment(filePath, fileName)
}
// UDMVolteIMS用户导入
//
// POST /import
//
// @Tags network_data/udm/volte-ims
// @Accept json
// @Produce json
// @Param data body object true "Request Param"
// @Success 200 {object} object "Response Results"
// @Security TokenAuth
// @Summary UDM Authenticated User Import
// @Description UDM Authenticated User Import
// @Router /neData/udm/volte-ims/import [post]
func (s *UDMVolteIMSController) Import(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var body struct {
NeId string `json:"neId" binding:"required"` // 网元ID
UploadPath string `json:"uploadPath" binding:"required"` // 上传文件路径
}
if err := c.ShouldBindBodyWithJSON(&body); err != nil {
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}
// 判断文件名
if !(strings.HasSuffix(body.UploadPath, ".csv") || strings.HasSuffix(body.UploadPath, ".txt")) {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "ne.udm.errImportUserAuthFileFormat")))
return
}
// 查询网元获取IP
neInfo := s.neInfoService.FindByNeTypeAndNeID("UDM", body.NeId)
if neInfo.NeId != body.NeId || neInfo.IP == "" {
c.JSON(200, resp.ErrMsg(i18n.TKey(language, "app.common.noNEInfo")))
return
}
// 网元主机的SSH客户端
sshClient, err := s.neInfoService.NeRunSSHClient(neInfo.NeType, neInfo.NeId)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer sshClient.Close()
// 网元主机的SSH客户端进行文件传输
sftpClient, err := sshClient.NewClientSFTP()
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer sftpClient.Close()
// 本地文件
localFilePath := file.ParseUploadFileAbsPath(body.UploadPath)
neFilePath := fmt.Sprintf("/tmp/%s", filepath.Base(localFilePath))
// 复制到远程
if err = sftpClient.CopyFileLocalToRemote(localFilePath, neFilePath); err != nil {
c.JSON(200, resp.ErrMsg("error uploading file"))
return
}
// 网元主机的Telnet客户端
telnetClient, err := s.neInfoService.NeRunTelnetClient(neInfo.NeType, neInfo.NeId, 1)
if err != nil {
c.JSON(200, resp.ErrMsg(err.Error()))
return
}
defer telnetClient.Close()
// 结果信息
var resultMsg string
var resultErr error
// 发送MML
cmd := fmt.Sprintf("import imsuser:path=%s", neFilePath)
resultMsg, resultErr = telnet.ConvertToStr(telnetClient, cmd)
if resultErr != nil {
c.JSON(200, resp.ErrMsg(resultErr.Error()))
return
}
// 命令ok时
if strings.Contains(resultMsg, "ok") {
if strings.HasSuffix(body.UploadPath, ".csv") {
data := file.ReadFileCSV(localFilePath)
go s.udmVolteIMSService.InsertData(neInfo.NeId, "csv", data)
}
if strings.HasSuffix(body.UploadPath, ".txt") {
data := file.ReadFileTXT(",", localFilePath)
go s.udmVolteIMSService.InsertData(neInfo.NeId, "txt", data)
}
}
c.JSON(200, resp.OkMsg(resultMsg))
}

View File

@@ -1,6 +1,8 @@
package controller
import (
"fmt"
"be.ems/src/framework/i18n"
"be.ems/src/framework/reqctx"
"be.ems/src/framework/resp"
@@ -27,7 +29,7 @@ type UPFController struct {
// 总流量数 N3上行 N6下行
// 单位 比特(bit)
//
// GET /totalFlow
// GET /flow-total
//
// @Tags network_data/upf
// @Accept json
@@ -38,15 +40,16 @@ type UPFController struct {
// @Security TokenAuth
// @Summary Total number of flows N3 upstream N6 downstream
// @Description Total number of flows N3 upstream N6 downstream
// @Router /neData/upf/totalFlow [get]
func (s UPFController) TotalFlow(c *gin.Context) {
// @Router /neData/upf/flow-total [get]
func (s UPFController) FlowTotal(c *gin.Context) {
language := reqctx.AcceptLanguage(c)
var querys struct {
NeID string `form:"neId" binding:"required"`
Day int `form:"day"`
}
if err := c.ShouldBindQuery(&querys); querys.Day < 0 || err != nil {
c.JSON(400, resp.CodeMsg(400, i18n.TKey(language, "app.common.err400")))
errMsgs := fmt.Sprintf("bind err: %s", resp.FormatBindError(err))
c.JSON(422, resp.CodeMsg(422001, errMsgs))
return
}

View File

@@ -0,0 +1,11 @@
package model
// BackupDataFTP 备份数据FTP服务参数结构体
type BackupDataFTP struct {
Password string `json:"password"` // FTP密码
Username string `json:"username" binding:"required"` // FTP用户名
ToIp string `json:"toIp" binding:"required"` // FTP服务器IP
ToPort int64 `json:"toPort" binding:"required"` // FTP服务器端口
Dir string `json:"dir" binding:"required"` // FTP服务器目录
Enable bool `json:"enable"` // 是否启用
}

View File

@@ -0,0 +1,15 @@
package model
// UDMVOIPUser UDMVOIP用户 udm_voip
type UDMVOIPUser struct {
ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` // 主键
NeId string `json:"neId" gorm:"column:ne_id"` // UDM网元标识
UserName string `json:"username" gorm:"column:username"` // 用户名
Password string `json:"password" gorm:"column:password"` // 密码
}
// TableName 表名称
func (*UDMVOIPUser) TableName() string {
return "udm_voip"
}

View File

@@ -0,0 +1,17 @@
package model
// UDMVolteIMSUser UDMVolteIMS用户 udm_volte_ims
type UDMVolteIMSUser struct {
ID string `json:"id" gorm:"column:id;primaryKey;autoIncrement"` // 主键
IMSI string `json:"imsi" gorm:"column:imsi"` // SIM卡/USIM卡ID
MSISDN string `json:"msisdn" gorm:"column:msisdn"` // 用户电话号码
NeId string `json:"neId" gorm:"column:ne_id"` // UDM网元标识
Tag string `json:"tag" gorm:"column:tag"` // 0=VoIP, 1=VoLTE
VNI string `json:"vni" gorm:"column:vni"` // VNI
}
// TableName 表名称
func (*UDMVolteIMSUser) TableName() string {
return "udm_volte_ims"
}

View File

@@ -24,11 +24,11 @@ func Setup(router *gin.Engine) {
kpiGroup := neDataGroup.Group("/kpi")
{
kpiGroup.GET("/title",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewKPI.KPITitle,
)
kpiGroup.GET("/data",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewKPI.KPIData,
)
}
@@ -37,33 +37,40 @@ func Setup(router *gin.Engine) {
alarmGroup := neDataGroup.Group("/alarm")
{
alarmGroup.GET("/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewAlarm.List,
)
alarmGroup.DELETE("/:id",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewAlarm.Remove,
)
alarmGroup.PUT("/clear",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.alarm", collectlogs.BUSINESS_TYPE_UPDATE)),
controller.NewAlarm.Clear,
)
alarmGroup.PUT("/ack",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.alarm", collectlogs.BUSINESS_TYPE_UPDATE)),
controller.NewAlarm.Ack,
)
alarmGroup.GET("/export",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.alarm", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewAlarm.Export,
)
}
// 告警日志数据信息
alarmLogGroup := neDataGroup.Group("/alarm/log")
{
alarmLogGroup.GET("/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewAlarmLog.List,
)
alarmLogGroup.GET("/event",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewAlarmLog.Event,
)
}
@@ -72,7 +79,7 @@ func Setup(router *gin.Engine) {
alarmForwardGroup := neDataGroup.Group("/alarm/forward")
{
alarmForwardGroup.GET("/log/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewAlarmForward.List,
)
}
@@ -81,11 +88,11 @@ func Setup(router *gin.Engine) {
nbStateGroup := neDataGroup.Group("/nb-state")
{
nbStateGroup.GET("/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewNBState.List,
)
nbStateGroup.POST("/export",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewNBState.Export,
)
}
@@ -94,25 +101,25 @@ func Setup(router *gin.Engine) {
imsGroup := neDataGroup.Group("/ims")
{
imsGroup.GET("/cdr/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewIMS.CDRList,
)
imsGroup.DELETE("/cdr/:id",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.imsCDR", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewIMS.CDRRemove,
)
imsGroup.POST("/cdr/export",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.imsCDR", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewIMS.CDRExport,
)
imsGroup.GET("/session/num",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewIMS.UeSessionNum,
)
imsGroup.GET("/session/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewIMS.UeSessionList,
)
}
@@ -121,16 +128,16 @@ func Setup(router *gin.Engine) {
smscGroup := neDataGroup.Group("/smsc")
{
smscGroup.GET("/cdr/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewSMSC.CDRList,
)
smscGroup.DELETE("/cdr/:id",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.smscCDR", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewSMSC.CDRRemove,
)
smscGroup.POST("/cdr/export",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.smscCDR", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewSMSC.CDRExport,
)
@@ -140,25 +147,25 @@ func Setup(router *gin.Engine) {
smfGroup := neDataGroup.Group("/smf")
{
smfGroup.GET("/cdr/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewSMF.CDRList,
)
smfGroup.DELETE("/cdr/:id",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.smfCDR", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewSMF.CDRRemove,
)
smfGroup.POST("/cdr/export",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.smfCDR", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewSMF.CDRExport,
)
smfGroup.GET("/sub/num",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewSMF.SubUserNum,
)
smfGroup.GET("/sub/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewSMF.SubUserList,
)
}
@@ -167,25 +174,25 @@ func Setup(router *gin.Engine) {
amfGroup := neDataGroup.Group("/amf")
{
amfGroup.GET("/ue/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewAMF.UEList,
)
amfGroup.DELETE("/ue/:id",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.amfUE", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewAMF.UERemove,
)
amfGroup.POST("/ue/export",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.amfUE", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewAMF.UEExport,
)
amfGroup.GET("/nb/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewAMF.NbInfoList,
)
amfGroup.GET("/nb/list-cfg",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewAMF.NbStateList,
)
}
@@ -193,9 +200,28 @@ func Setup(router *gin.Engine) {
// 网元UPF
upfGroup := neDataGroup.Group("/upf")
{
upfGroup.GET("/totalFlow",
middleware.PreAuthorize(nil),
controller.NewUPF.TotalFlow,
upfGroup.GET("/flow-total",
middleware.AuthorizeUser(nil),
controller.NewUPF.FlowTotal,
)
}
// 备份数据
backupGroup := neDataGroup.Group("/backup")
{
backupGroup.GET("/ftp",
middleware.AuthorizeUser(nil),
controller.NewBackup.FTPInfo,
)
backupGroup.PUT("/ftp",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.backup", collectlogs.BUSINESS_TYPE_OTHER)),
controller.NewBackup.FTPUpdate,
)
backupGroup.POST("/ftp",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.backup", collectlogs.BUSINESS_TYPE_OTHER)),
controller.NewBackup.FTPPush,
)
}
@@ -204,50 +230,50 @@ func Setup(router *gin.Engine) {
{
udmAuthGroup.PUT("/resetData/:neId",
repeat.RepeatSubmit(5),
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_CLEAN)),
controller.NewUDMAuth.ResetData,
)
udmAuthGroup.GET("/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewUDMAuth.List,
)
udmAuthGroup.GET("/:neId/:imsi",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewUDMAuth.Info,
)
udmAuthGroup.POST("/:neId",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_INSERT)),
controller.NewUDMAuth.Add,
)
udmAuthGroup.POST("/:neId/:num",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_INSERT)),
controller.NewUDMAuth.Adds,
)
udmAuthGroup.PUT("/:neId",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_UPDATE)),
controller.NewUDMAuth.Edit,
)
udmAuthGroup.DELETE("/:neId/:imsi",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewUDMAuth.Remove,
)
udmAuthGroup.DELETE("/:neId/:imsi/:num",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewUDMAuth.Removes,
)
udmAuthGroup.GET("/export",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewUDMAuth.Export,
)
udmAuthGroup.POST("/import",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmAuth", collectlogs.BUSINESS_TYPE_IMPORT)),
controller.NewUDMAuth.Import,
)
@@ -258,78 +284,176 @@ func Setup(router *gin.Engine) {
{
udmSubGroup.PUT("/resetData/:neId",
repeat.RepeatSubmit(5),
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_CLEAN)),
controller.NewUDMSub.ResetData,
)
udmSubGroup.GET("/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewUDMSub.List,
)
udmSubGroup.GET("/:neId/:imsi",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewUDMSub.Info,
)
udmSubGroup.POST("/:neId",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_INSERT)),
controller.NewUDMSub.Add,
)
udmSubGroup.POST("/:neId/:num",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_INSERT)),
controller.NewUDMSub.Adds,
)
udmSubGroup.PUT("/:neId",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_UPDATE)),
controller.NewUDMSub.Edit,
)
udmSubGroup.DELETE("/:neId/:imsi",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewUDMSub.Remove,
)
udmSubGroup.DELETE("/:neId/:imsi/:num",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewUDMSub.Removes,
)
udmSubGroup.GET("/export",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewUDMSub.Export,
)
udmSubGroup.POST("/import",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmSub", collectlogs.BUSINESS_TYPE_IMPORT)),
controller.NewUDMSub.Import,
)
}
// 网元UDM VOIP用户信息
udmVOIPGroup := neDataGroup.Group("/udm/voip")
{
udmVOIPGroup.PUT("/resetData/:neId",
repeat.RepeatSubmit(5),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVOIP", collectlogs.BUSINESS_TYPE_CLEAN)),
controller.NewUDMVOIP.ResetData,
)
udmVOIPGroup.GET("/list",
middleware.AuthorizeUser(nil),
controller.NewUDMVOIP.List,
)
udmVOIPGroup.GET("/:neId/:username",
middleware.AuthorizeUser(nil),
controller.NewUDMVOIP.Info,
)
udmVOIPGroup.POST("/:neId",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVOIP", collectlogs.BUSINESS_TYPE_INSERT)),
controller.NewUDMVOIP.Add,
)
udmVOIPGroup.POST("/:neId/:num",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVOIP", collectlogs.BUSINESS_TYPE_INSERT)),
controller.NewUDMVOIP.Adds,
)
udmVOIPGroup.DELETE("/:neId/:username",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVOIP", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewUDMVOIP.Remove,
)
udmVOIPGroup.DELETE("/:neId/:username/:num",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVOIP", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewUDMVOIP.Removes,
)
udmVOIPGroup.GET("/export",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVOIP", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewUDMVOIP.Export,
)
udmVOIPGroup.POST("/import",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVOIP", collectlogs.BUSINESS_TYPE_IMPORT)),
controller.NewUDMVOIP.Import,
)
}
// 网元UDM VolteIMS用户信息
udmVolteIMSGroup := neDataGroup.Group("/udm/volte-ims")
{
udmVolteIMSGroup.PUT("/resetData/:neId",
repeat.RepeatSubmit(5),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVolteIMS", collectlogs.BUSINESS_TYPE_CLEAN)),
controller.NewUDMVolteIMS.ResetData,
)
udmVolteIMSGroup.GET("/list",
middleware.AuthorizeUser(nil),
controller.NewUDMVolteIMS.List,
)
udmVolteIMSGroup.GET("/:neId/:imsi",
middleware.AuthorizeUser(nil),
controller.NewUDMVolteIMS.Info,
)
udmVolteIMSGroup.POST("/:neId",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVolteIMS", collectlogs.BUSINESS_TYPE_INSERT)),
controller.NewUDMVolteIMS.Add,
)
udmVolteIMSGroup.POST("/:neId/:num",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVolteIMS", collectlogs.BUSINESS_TYPE_INSERT)),
controller.NewUDMVolteIMS.Adds,
)
udmVolteIMSGroup.DELETE("/:neId/:imsi",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVolteIMS", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewUDMVolteIMS.Remove,
)
udmVolteIMSGroup.DELETE("/:neId/:imsi/:num",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVolteIMS", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewUDMVolteIMS.Removes,
)
udmVolteIMSGroup.GET("/export",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVolteIMS", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewUDMVolteIMS.Export,
)
udmVolteIMSGroup.POST("/import",
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.udmVolteIMS", collectlogs.BUSINESS_TYPE_IMPORT)),
controller.NewUDMVolteIMS.Import,
)
}
// 网元MME
mmeGroup := neDataGroup.Group("/mme")
{
mmeGroup.GET("/ue/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewMME.UEList,
)
mmeGroup.DELETE("/ue/:id",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.mmeUE", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewMME.UERemove,
)
mmeGroup.POST("/ue/export",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.mmeUE", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewMME.UEExport,
)
mmeGroup.GET("/nb/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewMME.NbInfoList,
)
mmeGroup.GET("/nb/list-cfg",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewMME.NbStateList,
)
}
@@ -338,16 +462,16 @@ func Setup(router *gin.Engine) {
sgwcGroup := neDataGroup.Group("/sgwc")
{
sgwcGroup.GET("/cdr/list",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
controller.NewSGWC.CDRList,
)
sgwcGroup.DELETE("/cdr/:id",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sgwcCDR", collectlogs.BUSINESS_TYPE_DELETE)),
controller.NewSGWC.CDRRemove,
)
sgwcGroup.POST("/cdr/export",
middleware.PreAuthorize(nil),
middleware.AuthorizeUser(nil),
collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sgwcCDR", collectlogs.BUSINESS_TYPE_EXPORT)),
controller.NewSGWC.CDRExport,
)

View File

@@ -18,7 +18,7 @@ type UDMAuthUser struct{}
// ClearAndInsert 清空ne_id后新增实体
func (r *UDMAuthUser) ClearAndInsert(neId string, uArr []model.UDMAuthUser) int64 {
// 不指定neID时用 TRUNCATE 清空表快
// _, err := datasource.ExecDB("", "TRUNCATE TABLE u_auth_user", nil)
// _, err := datasource.ExecDB("", "TRUNCATE TABLE udm_auth", nil)
result := db.DB("").Where("ne_id = ?", neId).Unscoped().Delete(&model.UDMAuthUser{})
if result.Error != nil {
logger.Errorf("Delete err => %v", result.Error)
@@ -39,6 +39,7 @@ func (r *UDMAuthUser) SelectPage(query map[string]string) (int64, []model.UDMAut
if v, ok := query["imsis"]; ok && v != "" {
arr := strings.Split(v, ",")
tx = tx.Where("imsi in ?", arr)
// 勾选时pageSize为勾选的数量
query["pageSize"] = fmt.Sprint(len(arr))
}
@@ -126,7 +127,7 @@ func (r *UDMAuthUser) Delete(imsi, neId string) int64 {
}
// DeletePrefixByIMSI 删除前缀匹配的实体
func (r *UDMAuthUser) DeletePrefixByIMSI(neId, imsi string) int64 {
func (r *UDMAuthUser) DeletePrefixByIMSI(imsi, neId string) int64 {
tx := db.DB("").Where("imsi like ? and ne_id = ?", fmt.Sprintf("%s%%", imsi), neId).Delete(&model.UDMAuthUser{})
if err := tx.Error; err != nil {
logger.Errorf("DeletePrefixByIMSI err => %v", err)

View File

@@ -18,7 +18,7 @@ type UDMSubUser struct{}
// ClearAndInsert 清空ne_id后新增实体
func (r *UDMSubUser) ClearAndInsert(neId string, u []model.UDMSubUser) int64 {
// 不指定neID时用 TRUNCATE 清空表快
// _, err := datasource.ExecDB("", "TRUNCATE TABLE u_sub_user", nil)
// _, err := datasource.ExecDB("", "TRUNCATE TABLE udm_sub", nil)
result := db.DB("").Where("ne_id = ?", neId).Unscoped().Delete(&model.UDMSubUser{})
if result.Error != nil {
logger.Errorf("Delete err => %v", result.Error)
@@ -42,6 +42,7 @@ func (r *UDMSubUser) SelectPage(query map[string]string) (int64, []model.UDMSubU
if v, ok := query["imsis"]; ok && v != "" {
arr := strings.Split(v, ",")
tx = tx.Where("imsi in ?", arr)
// 勾选时pageSize为勾选的数量
query["pageSize"] = fmt.Sprint(len(arr))
}

View File

@@ -0,0 +1,138 @@
package repository
import (
"fmt"
"strings"
"be.ems/src/framework/database/db"
"be.ems/src/framework/logger"
"be.ems/src/modules/network_data/model"
)
// 实例化数据层 UDMVOIPUser 结构体
var NewUDMVOIPUser = &UDMVOIPUser{}
// UDMVOIPUser UDMVOIP用户信息表 数据层处理
type UDMVOIPUser struct{}
// ClearAndInsert 清空ne_id后新增实体
func (r UDMVOIPUser) ClearAndInsert(neId string, uArr []model.UDMVOIPUser) int64 {
// 不指定neID时用 TRUNCATE 清空表快
// _, err := datasource.ExecDB("", "TRUNCATE TABLE udm_voip", nil)
result := db.DB("").Where("ne_id = ?", neId).Unscoped().Delete(&model.UDMVOIPUser{})
if result.Error != nil {
logger.Errorf("Delete err => %v", result.Error)
}
return r.Inserts(uArr)
}
// SelectPage 根据条件分页查询
func (r UDMVOIPUser) SelectPage(query map[string]string) (int64, []model.UDMVOIPUser) {
tx := db.DB("").Model(&model.UDMVOIPUser{})
// 查询条件拼接
if v, ok := query["username"]; ok && v != "" {
tx = tx.Where("username like ?", fmt.Sprintf("%%%s%%", v))
}
if v, ok := query["neId"]; ok && v != "" {
tx = tx.Where("ne_id = ?", v)
}
if v, ok := query["usernames"]; ok && v != "" {
arr := strings.Split(v, ",")
tx = tx.Where("username in ?", arr)
// 勾选时pageSize为勾选的数量
query["pageSize"] = fmt.Sprint(len(arr))
}
var total int64 = 0
rows := []model.UDMVOIPUser{}
// 查询数量 长度为0直接返回
if err := tx.Count(&total).Error; err != nil || total <= 0 {
return total, rows
}
// 分页
pageNum, pageSize := db.PageNumSize(query["pageNum"], query["pageSize"])
tx = tx.Offset(int(pageNum * pageSize)).Limit(int(pageSize))
// 排序
if v, ok := query["sortField"]; ok && v != "" {
sortSql := v
if o, ok := query["sortOrder"]; ok && o != "" {
if o == "desc" {
sortSql += " desc "
} else {
sortSql += " asc "
}
}
tx = tx.Order(sortSql)
} else {
tx = tx.Order("username asc")
}
// 查询数据
if err := tx.Find(&rows).Error; err != nil {
logger.Errorf("query err => %v", err)
}
return total, rows
}
// SelectList 根据实体查询
func (r UDMVOIPUser) SelectList(u model.UDMVOIPUser) []model.UDMVOIPUser {
tx := db.DB("").Model(&model.UDMVOIPUser{})
// 查询条件拼接
if u.UserName != "" {
tx = tx.Where("username = ?", u.UserName)
}
if u.NeId != "" {
tx = tx.Where("ne_id = ?", u.NeId)
}
// 查询数据
arr := []model.UDMVOIPUser{}
if err := tx.Order("username asc").Find(&arr).Error; err != nil {
logger.Errorf("query err => %v", err)
}
return arr
}
// SelectByUserNameAndNeID 通过username和ne_id查询
func (r UDMVOIPUser) SelectByUserNameAndNeID(username, neId string) model.UDMVOIPUser {
tx := db.DB("").Model(&model.UDMVOIPUser{})
item := model.UDMVOIPUser{}
// 查询条件拼接
tx = tx.Where("username = ? and ne_id = ?", username, neId)
// 查询数据
if err := tx.Order("username asc").Limit(1).Find(&item).Error; err != nil {
logger.Errorf("query err => %v", err)
}
return item
}
// Insert 批量添加
func (r UDMVOIPUser) Inserts(uArr []model.UDMVOIPUser) int64 {
tx := db.DB("").CreateInBatches(uArr, 500)
if err := tx.Error; err != nil {
logger.Errorf("CreateInBatches err => %v", err)
}
return tx.RowsAffected
}
// Delete 删除实体
func (r UDMVOIPUser) Delete(username, neId string) int64 {
tx := db.DB("").Where("username = ? and ne_id = ?", username, neId).Delete(&model.UDMVOIPUser{})
if err := tx.Error; err != nil {
logger.Errorf("Delete err => %v", err)
}
return tx.RowsAffected
}
// DeletePrefixByUserName 删除前缀匹配的实体
func (r UDMVOIPUser) DeletePrefixByUserName(username, neId string) int64 {
tx := db.DB("").Where("username like ? and ne_id = ?", fmt.Sprintf("%s%%", username), neId).Delete(&model.UDMVOIPUser{})
if err := tx.Error; err != nil {
logger.Errorf("DeletePrefixByUserName err => %v", err)
}
return tx.RowsAffected
}

View File

@@ -0,0 +1,150 @@
package repository
import (
"fmt"
"strings"
"be.ems/src/framework/database/db"
"be.ems/src/framework/logger"
"be.ems/src/modules/network_data/model"
)
// 实例化数据层 UDMVolteIMSUser 结构体
var NewUDMVolteIMSUser = &UDMVolteIMSUser{}
// UDMVolteIMSUser UDMVOIP用户信息表 数据层处理
type UDMVolteIMSUser struct{}
// ClearAndInsert 清空ne_id后新增实体
func (r UDMVolteIMSUser) ClearAndInsert(neId string, uArr []model.UDMVolteIMSUser) int64 {
// 不指定neID时用 TRUNCATE 清空表快
// _, err := datasource.ExecDB("", "TRUNCATE TABLE udm_volte_ims", nil)
result := db.DB("").Where("ne_id = ?", neId).Unscoped().Delete(&model.UDMVolteIMSUser{})
if result.Error != nil {
logger.Errorf("Delete err => %v", result.Error)
}
return r.Inserts(uArr)
}
// SelectPage 根据条件分页查询
func (r UDMVolteIMSUser) SelectPage(query map[string]string) (int64, []model.UDMVolteIMSUser) {
tx := db.DB("").Model(&model.UDMVolteIMSUser{})
// 查询条件拼接
if v, ok := query["imsi"]; ok && v != "" {
tx = tx.Where("imsi like ?", fmt.Sprintf("%%%s%%", v))
}
if v, ok := query["msisdn"]; ok && v != "" {
tx = tx.Where("msisdn like ?", fmt.Sprintf("%%%s%%", v))
}
if v, ok := query["neId"]; ok && v != "" {
tx = tx.Where("ne_id = ?", v)
}
if v, ok := query["tag"]; ok && v != "" {
tx = tx.Where("tag = ?", v)
}
if v, ok := query["vni"]; ok && v != "" {
tx = tx.Where("vni like ?", fmt.Sprintf("%%%s%%", v))
}
if v, ok := query["imsis"]; ok && v != "" {
arr := strings.Split(v, ",")
tx = tx.Where("imsi in ?", arr)
// 勾选时pageSize为勾选的数量
query["pageSize"] = fmt.Sprint(len(arr))
}
var total int64 = 0
rows := []model.UDMVolteIMSUser{}
// 查询数量 长度为0直接返回
if err := tx.Count(&total).Error; err != nil || total <= 0 {
return total, rows
}
// 分页
pageNum, pageSize := db.PageNumSize(query["pageNum"], query["pageSize"])
tx = tx.Offset(int(pageNum * pageSize)).Limit(int(pageSize))
// 排序
if v, ok := query["sortField"]; ok && v != "" {
sortSql := v
if o, ok := query["sortOrder"]; ok && o != "" {
if o == "desc" {
sortSql += " desc "
} else {
sortSql += " asc "
}
}
tx = tx.Order(sortSql)
} else {
tx = tx.Order("imsi asc")
}
// 查询数据
if err := tx.Find(&rows).Error; err != nil {
logger.Errorf("query err => %v", err)
}
return total, rows
}
// SelectList 根据实体查询
func (r UDMVolteIMSUser) SelectList(u model.UDMVolteIMSUser) []model.UDMVolteIMSUser {
tx := db.DB("").Model(&model.UDMVolteIMSUser{})
// 查询条件拼接
if u.IMSI != "" {
tx = tx.Where("imsi = ?", u.IMSI)
}
if u.NeId != "" {
tx = tx.Where("ne_id = ?", u.NeId)
}
if u.Tag != "" {
tx = tx.Where("tag = ?", u.Tag)
}
// 查询数据
arr := []model.UDMVolteIMSUser{}
if err := tx.Order("imsi asc").Find(&arr).Error; err != nil {
logger.Errorf("query err => %v", err)
}
return arr
}
// SelectByIMSIAndMSISDNAndNeID 通过imsi,msisdn,ne_id查询
func (r UDMVolteIMSUser) SelectByIMSIAndMSISDNAndNeID(imsi, msisdn, neId string) model.UDMVolteIMSUser {
tx := db.DB("").Model(&model.UDMVolteIMSUser{})
item := model.UDMVolteIMSUser{}
// 查询条件拼接
tx = tx.Where("imsi = ? and msisdn = ? and ne_id = ?", imsi, msisdn, neId)
// 查询数据
if err := tx.Order("imsi asc").Limit(1).Find(&item).Error; err != nil {
logger.Errorf("query err => %v", err)
}
return item
}
// Insert 批量添加
func (r UDMVolteIMSUser) Inserts(uArr []model.UDMVolteIMSUser) int64 {
tx := db.DB("").CreateInBatches(uArr, 500)
if err := tx.Error; err != nil {
logger.Errorf("CreateInBatches err => %v", err)
}
return tx.RowsAffected
}
// Delete 删除实体
func (r UDMVolteIMSUser) Delete(imsi, neId string) int64 {
tx := db.DB("").Where("imsi = ? and ne_id = ?", imsi, neId).Delete(&model.UDMVolteIMSUser{})
if err := tx.Error; err != nil {
logger.Errorf("Delete err => %v", err)
}
return tx.RowsAffected
}
// DeletePrefixByIMSI 删除前缀匹配的实体
func (r UDMVolteIMSUser) DeletePrefixByIMSI(imsi, neId string) int64 {
tx := db.DB("").Where("imsi like ? and ne_id = ?", fmt.Sprintf("%s%%", imsi), neId).Delete(&model.UDMVolteIMSUser{})
if err := tx.Error; err != nil {
logger.Errorf("DeletePrefixByIMSI err => %v", err)
}
return tx.RowsAffected
}

View File

@@ -2,11 +2,16 @@ package service
import (
"fmt"
"strconv"
"time"
"be.ems/src/framework/constants"
"be.ems/src/framework/i18n"
"be.ems/src/framework/utils/date"
"be.ems/src/framework/utils/file"
"be.ems/src/modules/network_data/model"
"be.ems/src/modules/network_data/repository"
sysService "be.ems/src/modules/system/service"
)
// 实例化数据层 Alarm 结构体
@@ -114,3 +119,83 @@ func (r Alarm) AlarmAckByIds(ids []int64, ackUser string, ackState bool) (int64,
// 清除失败!
return 0, fmt.Errorf("ack fail")
}
// ExportXlsx 导出数据到 xlsx 文件
func (r Alarm) ExportXlsx(rows []model.Alarm, fileName, language, alarmStatus string) (string, error) {
// 第一行表头标题
headerCells := map[string]string{
"A1": i18n.TKey(language, "alarm.export.alarmType"),
"B1": i18n.TKey(language, "alarm.export.origSeverity"),
"C1": i18n.TKey(language, "alarm.export.alarmTitle"),
"D1": i18n.TKey(language, "alarm.export.eventTime"),
"E1": i18n.TKey(language, "alarm.export.alarmId"),
"F1": i18n.TKey(language, "alarm.export.alarmCode"),
"G1": i18n.TKey(language, "ne.common.neType"),
"H1": i18n.TKey(language, "ne.common.neName"),
"I1": i18n.TKey(language, "ne.common.neId"),
}
if alarmStatus == "0" {
headerCells["J1"] = i18n.TKey(language, "alarm.export.clearUser")
headerCells["K1"] = i18n.TKey(language, "alarm.export.clearTime")
headerCells["L1"] = i18n.TKey(language, "alarm.export.clearType")
}
// 读取字典数据 告警原始严重程度
dictActiveAlarmType := sysService.NewSysDictData.FindByType("active_alarm_type")
// 读取字典数据 告警类型
dictActiveClearType := sysService.NewSysDictData.FindByType("active_clear_type")
// 读取字典数据 告警确认类型
dictActiveAlarmSeverity := sysService.NewSysDictData.FindByType("active_alarm_severity")
// 从第二行开始的数据
dataCells := make([]map[string]any, 0)
for i, row := range rows {
idx := strconv.Itoa(i + 2)
// 原始严重程度
origSeverity := "-"
for _, v := range dictActiveAlarmSeverity {
if row.OrigSeverity == v.DataValue {
origSeverity = i18n.TKey(language, v.DataLabel)
break
}
}
// 活动告警类型
alarmType := "-"
for _, v := range dictActiveAlarmType {
if row.AlarmType == v.DataValue {
alarmType = i18n.TKey(language, v.DataLabel)
break
}
}
// 告警清除类型
clearType := "-"
for _, v := range dictActiveClearType {
if fmt.Sprint(row.ClearType) == v.DataValue {
clearType = i18n.TKey(language, v.DataLabel)
break
}
}
eventTimeStr := date.ParseDateToStr(row.EventTime, date.YYYY_MM_DDTHH_MM_SSZ)
cells := map[string]any{
"A" + idx: alarmType,
"B" + idx: origSeverity,
"C" + idx: row.AlarmTitle,
"D" + idx: eventTimeStr,
"E" + idx: row.AlarmId,
"F" + idx: row.AlarmCode,
"G" + idx: row.NeType,
"H" + idx: row.NeName,
"I" + idx: row.NeId,
}
if alarmStatus == "0" {
clearTimeStr := date.ParseDateToStr(row.ClearTime, date.YYYY_MM_DDTHH_MM_SSZ)
cells["J"+idx] = row.ClearUser
cells["K"+idx] = clearType
cells["L"+idx] = clearTimeStr
}
dataCells = append(dataCells, cells)
}
// 导出数据表格
return file.WriteSheet(headerCells, dataCells, fileName, "")
}

View File

@@ -0,0 +1,86 @@
package service
import (
"encoding/json"
"fmt"
"path/filepath"
"strings"
"be.ems/src/framework/ssh"
"be.ems/src/modules/network_data/model"
neService "be.ems/src/modules/network_element/service"
systemService "be.ems/src/modules/system/service"
)
// 实例化数据层 Backup 结构体
var NewBackup = &Backup{
BACKUP_DIR: "/usr/local/omc/backup",
neInfoService: neService.NewNeInfo,
sysConfigService: systemService.NewSysConfig,
}
// Backup 备份相关 服务层处理
type Backup struct {
BACKUP_DIR string // 备份目录
neInfoService *neService.NeInfo // 网元信息服务
sysConfigService *systemService.SysConfig // 参数配置服务
}
// FTPConfigUpdate 更新FTP配置信息
func (r Backup) FTPConfigUpdate(value, updateBy string) int64 {
cfg := r.sysConfigService.FindByKey("neData.backupDataFTP")
if cfg.ConfigId == 0 {
return 0
}
cfg.ConfigValue = value
cfg.UpdateBy = updateBy
return r.sysConfigService.UpdateEncryptValue(cfg)
}
// FTPConfigInfo 获取FTP配置信息
func (r Backup) FTPConfigInfo() model.BackupDataFTP {
info := model.BackupDataFTP{}
// 获取配置
cfg := r.sysConfigService.FindByKeyDecryptValue("neData.backupDataFTP")
if cfg.ConfigId > 0 && cfg.ConfigValue != "" {
if err := json.Unmarshal([]byte(cfg.ConfigValue), &info); err != nil {
return info
}
}
return info
}
// FTPPushFile 推送文件到FTP
func (r Backup) FTPPushFile(localFilePath, tag string) error {
cfgData := r.FTPConfigInfo()
if !cfgData.Enable {
return fmt.Errorf("setting remote backup ftp is disabled")
}
connSSH := ssh.ConnSSH{
User: cfgData.Username,
Password: cfgData.Password,
Addr: cfgData.ToIp,
Port: cfgData.ToPort,
AuthMode: "0",
}
sshClient, err := connSSH.NewClient()
if err != nil {
return err
}
defer sshClient.Close()
// 网元主机的SSH客户端进行文件传输
sftpClient, err := sshClient.NewClientSFTP()
if err != nil {
return err
}
defer sftpClient.Close()
remotePath := strings.Replace(localFilePath, r.BACKUP_DIR, tag, 1)
remotePath = filepath.Join(cfgData.Dir, remotePath)
// 复制到远程
if err = sftpClient.CopyFileLocalToRemote(localFilePath, remotePath); err != nil {
return fmt.Errorf("error uploading file")
}
return nil
}

View File

@@ -149,7 +149,7 @@ func (r *UDMAuthUser) InsertData(neId, dataType string, data any) int64 {
var num int64 = 0
for prefix := range prefixes {
// 直接删除前缀的记录
r.udmAuthRepository.DeletePrefixByIMSI(neId, prefix)
r.udmAuthRepository.DeletePrefixByIMSI(prefix, neId)
// keys ausf:4600001000004*
arr := r.dataByRedis(prefix+"*", neId)
if len(arr) > 0 {

Some files were not shown because too many files have changed in this diff Show More