chore: 初始开源发布
iotlcm-admin-svr:管理后台服务。 - Go module:iotlcm → git.5iot.com/iotlcm-public/iotlcm-admin-svr(729 文件) - 凭据与地址脱敏:JWT signingKey(含代码里的 MustGet 默认值)、 MySQL DSN、Redis 口令、告警邮件发件人邮箱全部改为占位; 私网 IP → 文档地址段;库名 xiujie_admin → iotlcm - 真实手机号/邮箱 → 合法 11 位编造号 / example.com - 移除内部资产:CI 触发器、过时 DDL(sql/db_iotlcm.sql)、 私有依赖 go get 行、明文 registry 登录口令 - 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名 - Dockerfile 改用公共基础镜像 - 拼错的 RADEME.md → README.md,README 重写 - 需要真实数据库的用例改为可用 IOTLCM_TEST_WITH_DB=1 显式启用 - 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes 数据库 schema 不随本仓发布(生产 DDL 已过时),README 已说明。
This commit is contained in:
@@ -0,0 +1,731 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/bcache"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolepolicy"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/logic/smsgateway"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type sSysAuth struct {
|
||||
}
|
||||
|
||||
// 手机验证码登录:验证码 Redis 键与有效期。
|
||||
const (
|
||||
smsLoginCodeKeyPrefix = "auth:sms:login:code:"
|
||||
smsLoginLimitKeyPrefix = "auth:sms:login:limit:"
|
||||
smsLoginAttemptKeyPrefix = "auth:sms:login:attempt:"
|
||||
smsLoginCodeTTLSeconds = 300 // 验证码有效期 5 分钟
|
||||
smsLoginLimitTTLSeconds = 60 // 发送间隔 60 秒
|
||||
smsLoginMaxAttempts = 5 // 每个验证码最多尝试次数
|
||||
)
|
||||
|
||||
// smsLoginConsumeScript 原子校验并删除验证码:值匹配返回 1,否则返回 0。
|
||||
const smsLoginConsumeScript = `if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end`
|
||||
|
||||
func NewSysAuth() *sSysAuth {
|
||||
return &sSysAuth{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysAuth(NewSysAuth())
|
||||
}
|
||||
|
||||
func (s *sSysAuth) Login(ctx context.Context, param *model.LoginParams) (res *model.LoginUserOut, token string, err error) {
|
||||
ip := g.RequestFromCtx(ctx).GetClientIp()
|
||||
logininfor := &model.SysLogininforAddModel{
|
||||
TenantId: param.TenantId,
|
||||
UserName: param.Username,
|
||||
ClientKey: "web",
|
||||
DeviceType: "web",
|
||||
Ipaddr: ip,
|
||||
LoginLocation: utility.GetCityByIp(ip),
|
||||
Browser: utility.GetBrowser(ctx),
|
||||
Os: utility.GetOs(ctx),
|
||||
Status: "0",
|
||||
Msg: "登录成功",
|
||||
LoginTime: gtime.Now(),
|
||||
}
|
||||
// 验证验证码
|
||||
err = service.SysCaptcha().VerifyCaptcha(ctx, param.CaptchaID, param.CaptchaValue)
|
||||
if err != nil {
|
||||
logininfor.Status = "1"
|
||||
logininfor.Msg = err.Error()
|
||||
return nil, "", err
|
||||
}
|
||||
// 获取用户信息
|
||||
user, err := service.SysUser().GetUserByUsernameAndPassword(ctx, param.TenantId, param.Username, param.Password)
|
||||
if err != nil {
|
||||
logininfor.Status = "1"
|
||||
logininfor.Msg = err.Error()
|
||||
return nil, "", err
|
||||
}
|
||||
userOut := &model.LoginUserOut{
|
||||
ID: user.UserId,
|
||||
Username: user.UserName,
|
||||
NickName: user.NickName,
|
||||
Avatar: user.Avatar,
|
||||
TenantId: user.TenantId,
|
||||
DeptId: user.DeptId,
|
||||
}
|
||||
// 构建临时的上下文
|
||||
identity := &model.Identity{
|
||||
BaseClaims: model.BaseClaims{
|
||||
ID: user.UserId,
|
||||
Username: user.UserName,
|
||||
NickName: user.NickName,
|
||||
TenantId: user.TenantId,
|
||||
DeptId: user.DeptId,
|
||||
Roles: []model.Role{},
|
||||
LoginAt: time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
contextModel := new(model.Context)
|
||||
contextModel.User = identity
|
||||
|
||||
ctx = contexts.Set(ctx, contextModel)
|
||||
|
||||
// 生成token
|
||||
claims, token, err := s.GenerateToken(ctx, userOut)
|
||||
if err != nil {
|
||||
logininfor.Status = "1"
|
||||
logininfor.Msg = err.Error()
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if err = service.SysUser().UpdateLoginInfo(ctx, user.UserId, ip); err != nil {
|
||||
g.Log().Warningf(ctx, "sSysAuth.Login UpdateLoginInfo failed, userId: %d, ip: %s, error: %v", user.UserId, ip, err)
|
||||
}
|
||||
|
||||
// 保存登录日志
|
||||
go func() {
|
||||
id, err := service.SysLogininfor().AddLogininfor(ctx, logininfor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 保存在线列表
|
||||
service.SysUserOnline().Add(ctx, &model.SysUserOnlineAddModel{
|
||||
OnlineId: id,
|
||||
TenantId: user.TenantId,
|
||||
Uuid: claims.UUID,
|
||||
UserName: user.UserName,
|
||||
ClientKey: "web",
|
||||
DeviceType: "web",
|
||||
Ipaddr: ip,
|
||||
LoginLocation: utility.GetCityByIp(ip),
|
||||
Browser: utility.GetBrowser(ctx),
|
||||
Os: utility.GetOs(ctx),
|
||||
Token: token,
|
||||
LoginTime: gtime.Now(),
|
||||
ExpireTime: gtime.NewFromTime(claims.ExpiresAt.Time),
|
||||
})
|
||||
}()
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyUserLogin, user.UserId)
|
||||
return userOut, token, nil
|
||||
}
|
||||
|
||||
func (s *sSysAuth) HasAccessCode(ctx context.Context, requiredCode string) (bool, error) {
|
||||
requiredCode = strings.TrimSpace(requiredCode)
|
||||
if requiredCode == "" {
|
||||
return false, nil
|
||||
}
|
||||
userId := contexts.GetUserId(ctx)
|
||||
if userId <= 0 {
|
||||
return false, nil
|
||||
}
|
||||
if contexts.IsSuperAdmin(ctx) && contexts.GetTenantId(ctx) == consts.DefaultSystemTenantCode {
|
||||
isAdmin, err := rolepolicy.CurrentSuperAdmin(ctx, userId)
|
||||
if err != nil || isAdmin {
|
||||
return isAdmin, err
|
||||
}
|
||||
}
|
||||
|
||||
accessCodeList, _, err := s.GetUserAccessCodeList(ctx, userId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, userCode := range accessCodeList {
|
||||
if sysAuthMatchAccessCode(strings.TrimSpace(userCode), requiredCode) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func sysAuthMatchAccessCode(pattern, code string) bool {
|
||||
if pattern == code {
|
||||
return true
|
||||
}
|
||||
if strings.HasSuffix(pattern, "*") {
|
||||
prefix := pattern[:len(pattern)-1]
|
||||
return strings.HasPrefix(code, prefix)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 生成Token
|
||||
func (s *sSysAuth) GenerateToken(ctx context.Context, user *model.LoginUserOut) (claims *model.CustomClaims, token string, err error) {
|
||||
ets := g.Cfg().MustGet(ctx, "jwt.expiresTime", "7d").String()
|
||||
bts := g.Cfg().MustGet(ctx, "jwt.bufferTime", "1d").String()
|
||||
iss := g.Cfg().MustGet(ctx, "jwt.issuer", "IotLCM").String()
|
||||
sk := g.Cfg().MustGet(ctx, "jwt.signingKey", "${JWT_SIGNING_KEY}").String()
|
||||
et, _ := utility.ParseDuration(ets)
|
||||
bt, _ := utility.ParseDuration(bts)
|
||||
g.Log().Infof(ctx, "sSysAuth.GenerateToken 生成Token: user:%+v, et:%s, bt:%s, iss:%s, sk:%s", user, et, bt, iss, sk)
|
||||
roles := make([]model.Role, 0)
|
||||
if len(user.RoleIds) > 0 {
|
||||
for _, roleId := range user.RoleIds {
|
||||
roles = append(roles, model.Role{
|
||||
RoleId: roleId,
|
||||
DataScope: consts.SysRoleDataScopeAll,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
authorityIds, err := service.SysUser().GetUserRoleIds(ctx, user.ID)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
rl, _, err := service.SysRole().List(ctx, &model.SysRoleListParam{
|
||||
PageInfo: request.PageInfo{
|
||||
Page: 1,
|
||||
PageSize: 5000,
|
||||
},
|
||||
RoleIds: authorityIds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
for _, r := range rl {
|
||||
roles = append(roles, model.Role{
|
||||
RoleId: r.RoleId,
|
||||
DataScope: r.DataScope,
|
||||
})
|
||||
}
|
||||
}
|
||||
g.Log().Debugf(ctx, "sSysAuth.GenerateToken roles: %v", roles)
|
||||
uuidStr := user.UUID
|
||||
if uuidStr == "" {
|
||||
uuidStr = strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
}
|
||||
claims = &model.CustomClaims{
|
||||
BaseClaims: model.BaseClaims{
|
||||
UUID: uuidStr,
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
NickName: user.NickName,
|
||||
TenantId: user.TenantId,
|
||||
DeptId: user.DeptId,
|
||||
Roles: roles,
|
||||
LoginAt: time.Now().Unix(),
|
||||
},
|
||||
BufferTime: int64(bt / time.Second),
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Audience: jwt.ClaimStrings{"IotLCM"}, // 受众
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(et)), // 过期时间 7天 配置文件
|
||||
NotBefore: jwt.NewNumericDate(time.Now().Add(-1000)), // 签名生效时间
|
||||
Issuer: iss, // 发行人
|
||||
},
|
||||
}
|
||||
t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
token, err = t.SignedString([]byte(sk))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysAuth.GenerateToken token: %s, claims: %+v", token, claims)
|
||||
bcache.SetSysAuthToken(ctx, claims.BaseClaims.ID, claims.BaseClaims.UUID, token, et)
|
||||
return claims, token, nil
|
||||
}
|
||||
|
||||
// 解析Token
|
||||
func (s *sSysAuth) ParseToken(ctx context.Context, token string) (claims *model.CustomClaims, err error) {
|
||||
sk := g.Cfg().MustGet(ctx, "jwt.signingKey", "${JWT_SIGNING_KEY}").String()
|
||||
t, err := jwt.ParseWithClaims(token, &model.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(sk), nil
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
return nil, err
|
||||
}
|
||||
if t.Claims.(*model.CustomClaims).ExpiresAt.Unix() < time.Now().Unix() {
|
||||
return nil, errors.New("token已过期")
|
||||
}
|
||||
baseClaims := t.Claims.(*model.CustomClaims).BaseClaims
|
||||
_, err = bcache.GetSysAuthToken(ctx, baseClaims.ID, baseClaims.UUID)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysAuth.ParseToken GetSysAuthToken failed, userId: %d, uuid: %s, error: %v", baseClaims.ID, baseClaims.UUID, err)
|
||||
return nil, err
|
||||
}
|
||||
return t.Claims.(*model.CustomClaims), nil
|
||||
}
|
||||
|
||||
// 删除Token
|
||||
func (s *sSysAuth) DeleteToken(ctx context.Context, token string) (err error) {
|
||||
sk := g.Cfg().MustGet(ctx, "jwt.signingKey", "${JWT_SIGNING_KEY}").String()
|
||||
t, err := jwt.ParseWithClaims(token, &model.CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(sk), nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timeout := t.Claims.(*model.CustomClaims).ExpiresAt.Unix() - time.Now().Unix()
|
||||
if timeout <= 0 {
|
||||
return nil
|
||||
}
|
||||
err = bcache.DelSysAuthToken(ctx, t.Claims.(*model.CustomClaims).BaseClaims.ID, t.Claims.(*model.CustomClaims).BaseClaims.UUID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bcache.SetSysAuthTokenReject(ctx, t.Claims.(*model.CustomClaims).BaseClaims.ID, t.Claims.(*model.CustomClaims).BaseClaims.UUID, token, time.Duration(timeout)*time.Second)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取token
|
||||
func (s *sSysAuth) GetAccessToken(ctx context.Context) (token string, err error) {
|
||||
authorization := g.RequestFromCtx(ctx).Header.Get("Authorization")
|
||||
authParam := g.RequestFromCtx(ctx).Get("access_token").String()
|
||||
if authorization == "" && authParam == "" {
|
||||
return "", gerror.NewCode(gcode.CodeNotImplemented)
|
||||
}
|
||||
token = strings.TrimPrefix(authorization, "Bearer ")
|
||||
if token == "" {
|
||||
token = authParam
|
||||
}
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// 根据Token获取当前登录用户信息
|
||||
func (s *sSysAuth) GetCurrentUser(ctx context.Context) (claims *model.CustomClaims, err error) {
|
||||
// 获取token
|
||||
token, err := s.GetAccessToken(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 解析token
|
||||
claims, err = s.ParseToken(ctx, token)
|
||||
if err != nil {
|
||||
g.Log().Warningf(ctx, "sSysAuth.GetCurrentUser ParseToken failed, token: %s, error: %v", token, err)
|
||||
return nil, err
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// 获取用户权限码
|
||||
func (s *sSysAuth) GetUserAccessCodeList(ctx context.Context, userId int64) (accessCodeList []string, menuRoleAccessCodeList []string, err error) {
|
||||
accessCodeList = make([]string, 0)
|
||||
menuRoleAccessCodeList = make([]string, 0)
|
||||
roleDataScopeMap := make(map[int64]string)
|
||||
template, templateErr := rolepolicy.Template(ctx)
|
||||
if templateErr != nil {
|
||||
return nil, nil, templateErr
|
||||
}
|
||||
managed := template != nil && template.TenantId == contexts.GetTenantId(ctx) && template.DeptId != contexts.GetDeptId(ctx)
|
||||
// 角色权限码
|
||||
roleIds, err := service.SysUser().GetUserRoleIds(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
var effectiveRoles []*entity.SysRole
|
||||
if len(roleIds) > 0 {
|
||||
if err := dao.SysRole.Ctx(ctx).Where("tenant_id", contexts.GetTenantId(ctx)).WhereIn("role_id", roleIds).Where("status", "0").Scan(&effectiveRoles); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
roleIds = nil
|
||||
for _, role := range effectiveRoles {
|
||||
roleIds = append(roleIds, role.RoleId)
|
||||
}
|
||||
|
||||
for _, role := range effectiveRoles {
|
||||
accessCodeList = append(accessCodeList, consts.SysCheckPermissionRolePrefix+role.RoleKey)
|
||||
roleDataScopeMap[role.RoleId] = role.DataScope
|
||||
}
|
||||
if user := contexts.GetUser(ctx); user != nil && user.ID == userId {
|
||||
user.Roles = make([]model.Role, 0, len(effectiveRoles))
|
||||
for _, role := range effectiveRoles {
|
||||
scope := role.DataScope
|
||||
if managed && (scope == consts.SysRoleDataScopeAll || scope == consts.SysRoleDataScopeCustom) {
|
||||
scope = consts.SysRoleDataScopeDeptAndBelow
|
||||
}
|
||||
if scope == "" {
|
||||
scope = consts.SysRoleDataScopePersonal
|
||||
}
|
||||
user.Roles = append(user.Roles, model.Role{RoleId: role.RoleId, DataScope: scope})
|
||||
}
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "sSysAuth.GetUserAccessCodeList roleIds: %v", roleIds)
|
||||
// 菜单权限码
|
||||
// 获取角色菜单列表
|
||||
rmList, err := service.SysRole().GetRoleListMenu(ctx, roleIds)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
menuIds := make([]int64, 0)
|
||||
for _, menu := range rmList {
|
||||
menuIds = append(menuIds, menu.MenuId)
|
||||
}
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
menuIds, err = rolepolicy.RestrictMenus(ctx, contexts.GetTenantId(ctx), contexts.GetDeptId(ctx), menuIds)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysAuth.GetUserAccessCodeList menuIds: %v", menuIds)
|
||||
if len(menuIds) > 0 {
|
||||
// 获取用户角色菜单
|
||||
menuList, _, err := service.SysMenu().List(ctx, &model.SysMenuListParam{
|
||||
MenuIds: menuIds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysAuth.GetUserAccessCodeList menuList: %v", menuList)
|
||||
for _, m := range menuList {
|
||||
menuAccessCodes := normalizeMenuAccessCodes(m.Perms)
|
||||
if len(menuAccessCodes) == 0 {
|
||||
continue
|
||||
}
|
||||
accessCodeList = append(accessCodeList, menuAccessCodes...)
|
||||
for _, r := range rmList {
|
||||
if r.MenuId == m.MenuId {
|
||||
roleDS := roleDataScopeMap[r.RoleId]
|
||||
if r.DataScope != "" && r.DataScope != consts.SysRoleMenuDataScopeRole {
|
||||
roleDS = r.DataScope
|
||||
}
|
||||
if managed && (roleDS == consts.SysRoleDataScopeAll || roleDS == consts.SysRoleDataScopeCustom) {
|
||||
roleDS = consts.SysRoleDataScopeDeptAndBelow
|
||||
}
|
||||
if len(roleDS) < 1 {
|
||||
roleDS = consts.SysRoleDataScopePersonal
|
||||
}
|
||||
for _, accessCode := range menuAccessCodes {
|
||||
menuRoleAccessCodeList = append(menuRoleAccessCodeList, accessCode+"|"+gconv.String(r.RoleId)+"|"+roleDS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysAuth.GetUserAccessCodeList accessCodeList: %v", accessCodeList)
|
||||
// 获取用户岗位信息
|
||||
postIds, err := service.SysUser().GetUserPostIds(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(postIds) > 0 {
|
||||
pl, _, err := service.SysPost().List(ctx, &model.SysPostListParam{
|
||||
PageInfo: request.PageInfo{
|
||||
Page: 1,
|
||||
PageSize: 5000,
|
||||
},
|
||||
PostIds: postIds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, p := range pl {
|
||||
accessCodeList = append(accessCodeList, consts.SysCheckPermissionPostPrefix+p.PostCode)
|
||||
}
|
||||
}
|
||||
// 获取用户信息
|
||||
user, err := service.SysUser().GetUserById(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
accessCodeList = append(accessCodeList, consts.SysCheckPermissionUserPrefix+user.UserName)
|
||||
accessCodeList = append(accessCodeList, consts.SysCheckPermissionCurrentUser)
|
||||
accessCodeList = slices.Compact(accessCodeList)
|
||||
return accessCodeList, menuRoleAccessCodeList, nil
|
||||
}
|
||||
|
||||
// 根据openId登录
|
||||
func (s *sSysAuth) LoginByOpenId(ctx context.Context, social *model.SysSocialListModel) (res *model.LoginUserOut, token string, err error) {
|
||||
ip := g.RequestFromCtx(ctx).GetClientIp()
|
||||
logininfor := &model.SysLogininforAddModel{
|
||||
TenantId: social.User.TenantId,
|
||||
UserName: social.User.UserName,
|
||||
ClientKey: "web",
|
||||
DeviceType: "web",
|
||||
Ipaddr: ip,
|
||||
LoginLocation: utility.GetCityByIp(ip),
|
||||
Browser: utility.GetBrowser(ctx),
|
||||
Os: utility.GetOs(ctx),
|
||||
Status: "0",
|
||||
Msg: "登录成功",
|
||||
LoginTime: gtime.Now(),
|
||||
}
|
||||
user, err := service.SysUser().GetUserByUsername(ctx, social.User.UserName, social.User.TenantId)
|
||||
if err != nil {
|
||||
return nil, "用户不存在", err
|
||||
}
|
||||
userOut := &model.LoginUserOut{
|
||||
ID: user.UserId,
|
||||
Username: user.UserName,
|
||||
NickName: user.NickName,
|
||||
Avatar: user.Avatar,
|
||||
TenantId: user.TenantId,
|
||||
DeptId: user.DeptId,
|
||||
}
|
||||
// 构建临时的上下文
|
||||
identity := &model.Identity{
|
||||
BaseClaims: model.BaseClaims{
|
||||
ID: user.UserId,
|
||||
Username: user.UserName,
|
||||
NickName: user.NickName,
|
||||
TenantId: user.TenantId,
|
||||
DeptId: user.DeptId,
|
||||
Roles: []model.Role{},
|
||||
LoginAt: time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
contextModel := new(model.Context)
|
||||
contextModel.User = identity
|
||||
|
||||
ctx = contexts.Set(ctx, contextModel)
|
||||
|
||||
// 生成token
|
||||
claims, token, err := s.GenerateToken(ctx, userOut)
|
||||
if err != nil {
|
||||
logininfor.Status = "1"
|
||||
logininfor.Msg = err.Error()
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if err = service.SysUser().UpdateLoginInfo(ctx, user.UserId, ip); err != nil {
|
||||
g.Log().Warningf(ctx, "sSysAuth.LoginByOpenId UpdateLoginInfo failed, userId: %d, ip: %s, error: %v", user.UserId, ip, err)
|
||||
}
|
||||
|
||||
// 保存登录日志
|
||||
go func() {
|
||||
id, err := service.SysLogininfor().AddLogininfor(ctx, logininfor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 保存在线列表
|
||||
service.SysUserOnline().Add(ctx, &model.SysUserOnlineAddModel{
|
||||
OnlineId: id,
|
||||
TenantId: user.TenantId,
|
||||
Uuid: claims.UUID,
|
||||
UserName: user.UserName,
|
||||
ClientKey: "web",
|
||||
DeviceType: "web",
|
||||
Ipaddr: ip,
|
||||
LoginLocation: utility.GetCityByIp(ip),
|
||||
Browser: utility.GetBrowser(ctx),
|
||||
Os: utility.GetOs(ctx),
|
||||
Token: token,
|
||||
LoginTime: gtime.Now(),
|
||||
ExpireTime: gtime.NewFromTime(claims.ExpiresAt.Time),
|
||||
})
|
||||
}()
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyUserLogin, user.UserId)
|
||||
return userOut, token, nil
|
||||
}
|
||||
|
||||
// SendSmsLoginCode 发送手机验证码登录短信。
|
||||
// 验证码 5 分钟有效,60 秒内不允许重复发送(SET NX EX 原子占用,防并发绕过)。
|
||||
// 本接口未鉴权,网关不可用时必须 fail closed:不落库验证码、不打印验证码。
|
||||
func (s *sSysAuth) SendSmsLoginCode(ctx context.Context, phone string) (err error) {
|
||||
phone = strings.TrimSpace(phone)
|
||||
if phone == "" {
|
||||
return gerror.New("手机号不能为空")
|
||||
}
|
||||
// 原子占用发送资格:NX 抢占失败说明间隔内已发送过。
|
||||
limitKey := smsLoginLimitKeyPrefix + phone
|
||||
acquired, err := g.Redis().Do(ctx, "SET", limitKey, "1", "EX", smsLoginLimitTTLSeconds, "NX")
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "校验发送频率失败,请稍后重试")
|
||||
}
|
||||
if acquired.IsNil() {
|
||||
return gerror.New("验证码发送过于频繁,请稍后再试")
|
||||
}
|
||||
code, err := GenerateMfaCode(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
codeKey := smsLoginCodeKeyPrefix + phone
|
||||
if _, err = g.Redis().Do(ctx, "SETEX", codeKey, smsLoginCodeTTLSeconds, code); err != nil {
|
||||
return gerror.Wrap(err, "保存验证码失败,请稍后重试")
|
||||
}
|
||||
sent, err := smsgateway.Send(ctx, smsgateway.Request{
|
||||
Scene: smsgateway.SceneLogin,
|
||||
PhoneNumber: phone,
|
||||
MsgContent: code,
|
||||
})
|
||||
if err != nil {
|
||||
_, _ = g.Redis().Del(ctx, codeKey)
|
||||
return gerror.Wrap(err, "短信发送失败,请稍后重试")
|
||||
}
|
||||
if !sent {
|
||||
_, _ = g.Redis().Del(ctx, codeKey)
|
||||
return gerror.New("短信网关未启用,无法发送验证码")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// smsLoginCheckUserStatus 登录前的账号状态校验,返回用户可读错误。
|
||||
func smsLoginCheckUserStatus(user *entity.SysUser) error {
|
||||
switch user.Status {
|
||||
case consts.SysUserStatusDisable:
|
||||
return gerror.NewCode(consts.CodeUserDisabled, "账号已禁用")
|
||||
case consts.SysUserStatusLocked:
|
||||
return gerror.NewCode(consts.CodeUserLocked, "账号已锁定")
|
||||
case consts.SysUserStatusExpired:
|
||||
return gerror.NewCode(consts.CodeUserExpired, "账号已过期")
|
||||
case consts.SysUserStatusDeleted:
|
||||
return gerror.NewCode(consts.CodeUserDeleted, "账号已删除")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoginByPhoneCode 手机验证码登录:校验验证码后按手机号(跨租户)定位账号并签发 token。
|
||||
// 同一手机号命中多个账号时拒绝登录,引导使用账号密码登录(账号密码可结合租户选择)。
|
||||
// 验证码通过 Lua compare-and-del 原子校验并消费,防并发重放;每手机号限 5 次尝试。
|
||||
func (s *sSysAuth) LoginByPhoneCode(ctx context.Context, phone string, code string) (res *model.LoginUserOut, token string, err error) {
|
||||
phone = strings.TrimSpace(phone)
|
||||
code = strings.TrimSpace(code)
|
||||
ip := g.RequestFromCtx(ctx).GetClientIp()
|
||||
codeKey := smsLoginCodeKeyPrefix + phone
|
||||
attemptKey := smsLoginAttemptKeyPrefix + phone
|
||||
|
||||
// 尝试次数限制:防暴力枚举;TTL 与验证码对齐,成功登录后清除。
|
||||
attempts, err := g.Redis().Do(ctx, "INCR", attemptKey)
|
||||
if err != nil {
|
||||
return nil, "", gerror.Wrap(err, "校验登录尝试失败,请稍后重试")
|
||||
}
|
||||
if attempts.Int64() == 1 {
|
||||
_, _ = g.Redis().Do(ctx, "EXPIRE", attemptKey, smsLoginCodeTTLSeconds)
|
||||
}
|
||||
if attempts.Int64() > smsLoginMaxAttempts {
|
||||
return nil, "", gerror.New("尝试次数过多,请重新获取验证码")
|
||||
}
|
||||
|
||||
// 原子校验并消费验证码(compare-and-delete),消费成功才继续登录。
|
||||
consumed, err := g.Redis().Do(ctx, "EVAL", smsLoginConsumeScript, 1, codeKey, code)
|
||||
if err != nil {
|
||||
return nil, "", gerror.Wrap(err, "校验验证码失败,请稍后重试")
|
||||
}
|
||||
if consumed.Int64() != 1 {
|
||||
return nil, "", gerror.New("验证码错误或已过期")
|
||||
}
|
||||
_, _ = g.Redis().Del(ctx, attemptKey)
|
||||
|
||||
users, err := service.SysUser().ListUsersByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return nil, "", gerror.NewCode(consts.CodeUserNotFound, "账号不存在")
|
||||
}
|
||||
if len(users) > 1 {
|
||||
return nil, "", gerror.New("该手机号绑定了多个账号,请使用账号密码登录")
|
||||
}
|
||||
user := users[0]
|
||||
if err = smsLoginCheckUserStatus(user); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
logininfor := &model.SysLogininforAddModel{
|
||||
TenantId: user.TenantId,
|
||||
UserName: user.UserName,
|
||||
ClientKey: "web",
|
||||
DeviceType: "web",
|
||||
Ipaddr: ip,
|
||||
LoginLocation: utility.GetCityByIp(ip),
|
||||
Browser: utility.GetBrowser(ctx),
|
||||
Os: utility.GetOs(ctx),
|
||||
Status: "0",
|
||||
Msg: "登录成功",
|
||||
LoginTime: gtime.Now(),
|
||||
}
|
||||
userOut := &model.LoginUserOut{
|
||||
ID: user.UserId,
|
||||
Username: user.UserName,
|
||||
NickName: user.NickName,
|
||||
Avatar: user.Avatar,
|
||||
TenantId: user.TenantId,
|
||||
DeptId: user.DeptId,
|
||||
}
|
||||
// 构建临时的上下文
|
||||
identity := &model.Identity{
|
||||
BaseClaims: model.BaseClaims{
|
||||
ID: user.UserId,
|
||||
Username: user.UserName,
|
||||
NickName: user.NickName,
|
||||
TenantId: user.TenantId,
|
||||
DeptId: user.DeptId,
|
||||
Roles: []model.Role{},
|
||||
LoginAt: time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
contextModel := new(model.Context)
|
||||
contextModel.User = identity
|
||||
|
||||
ctx = contexts.Set(ctx, contextModel)
|
||||
|
||||
// 生成token
|
||||
claims, token, err := s.GenerateToken(ctx, userOut)
|
||||
if err != nil {
|
||||
logininfor.Status = "1"
|
||||
logininfor.Msg = err.Error()
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if err = service.SysUser().UpdateLoginInfo(ctx, user.UserId, ip); err != nil {
|
||||
g.Log().Warningf(ctx, "sSysAuth.LoginByPhoneCode UpdateLoginInfo failed, userId: %d, ip: %s, error: %v", user.UserId, ip, err)
|
||||
}
|
||||
|
||||
// 保存登录日志
|
||||
go func() {
|
||||
id, err := service.SysLogininfor().AddLogininfor(ctx, logininfor)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 保存在线列表
|
||||
service.SysUserOnline().Add(ctx, &model.SysUserOnlineAddModel{
|
||||
OnlineId: id,
|
||||
TenantId: user.TenantId,
|
||||
Uuid: claims.UUID,
|
||||
UserName: user.UserName,
|
||||
ClientKey: "web",
|
||||
DeviceType: "web",
|
||||
Ipaddr: ip,
|
||||
LoginLocation: utility.GetCityByIp(ip),
|
||||
Browser: utility.GetBrowser(ctx),
|
||||
Os: utility.GetOs(ctx),
|
||||
Token: token,
|
||||
LoginTime: gtime.Now(),
|
||||
ExpireTime: gtime.NewFromTime(claims.ExpiresAt.Time),
|
||||
})
|
||||
}()
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyUserLogin, user.UserId)
|
||||
return userOut, token, nil
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/mojocn/base64Captcha"
|
||||
)
|
||||
|
||||
var store = base64Captcha.NewMemoryStore(100, 120*time.Second)
|
||||
|
||||
type sSysCaptcha struct {
|
||||
}
|
||||
|
||||
func NewSysCaptcha() *sSysCaptcha {
|
||||
return &sSysCaptcha{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysCaptcha(NewSysCaptcha())
|
||||
}
|
||||
|
||||
type CaptchaConfig struct {
|
||||
KeyLong int
|
||||
ImgWidth int
|
||||
ImgHeight int
|
||||
OpenCaptcha int
|
||||
OpenCaptchaTimeout int
|
||||
}
|
||||
|
||||
// 生成验证码
|
||||
func (l *sSysCaptcha) GenerateCaptcha(ctx context.Context) (key string, image string, err error) {
|
||||
var captchaConfig CaptchaConfig
|
||||
g.Cfg().MustGet(ctx, "captcha").Struct(&captchaConfig)
|
||||
driver := base64Captcha.NewDriverDigit(captchaConfig.ImgHeight, captchaConfig.ImgWidth, captchaConfig.KeyLong, 0.7, 80)
|
||||
captcha := base64Captcha.NewCaptcha(driver, store)
|
||||
key, image, _, err = captcha.Generate()
|
||||
return
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
func (l *sSysCaptcha) VerifyCaptcha(ctx context.Context, key string, value string) (err error) {
|
||||
v := store.Get(key, false)
|
||||
if len(v) == 0 {
|
||||
return gerror.NewCode(consts.CodeCaptchaError, "验证码已失效")
|
||||
}
|
||||
ok := store.Verify(key, value, true)
|
||||
if !ok {
|
||||
err = gerror.NewCode(consts.CodeCaptchaError, "验证码错误")
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/uuid32"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysClient struct {
|
||||
}
|
||||
|
||||
func NewSysClient() *sSysClient {
|
||||
return &sSysClient{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysClient(NewSysClient())
|
||||
}
|
||||
|
||||
func (s *sSysClient) List(ctx context.Context, query *model.SysClientListParam) (items []*model.SysClientListModel, total int, err error) {
|
||||
|
||||
db := dao.SysClient.Ctx(ctx)
|
||||
if query.ClientId != "" {
|
||||
db = db.Where(dao.SysClient.Columns().ClientId, query.ClientId)
|
||||
}
|
||||
|
||||
if query.ClientKey != "" {
|
||||
db = db.Where(dao.SysClient.Columns().ClientKey, query.ClientKey)
|
||||
}
|
||||
|
||||
if query.ClientSecret != "" {
|
||||
db = db.Where(dao.SysClient.Columns().ClientSecret, query.ClientSecret)
|
||||
}
|
||||
|
||||
if query.Status != "" {
|
||||
db = db.Where(dao.SysClient.Columns().Status, query.Status)
|
||||
}
|
||||
|
||||
total, err = db.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = db.Page(query.Page, query.PageSize).OrderAsc(dao.SysClient.Columns().Id).Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
func (s *sSysClient) View(ctx context.Context, param *model.SysClientViewParam) (item *model.SysClientViewModel, err error) {
|
||||
db := dao.SysClient.Ctx(ctx)
|
||||
if param.Id != 0 {
|
||||
db = db.Where(dao.SysClient.Columns().Id, param.Id)
|
||||
} else if param.ClientId != "" {
|
||||
db = db.Where(dao.SysClient.Columns().ClientId, param.ClientId)
|
||||
} else {
|
||||
return nil, errors.New("未填写查询条件")
|
||||
}
|
||||
err = db.Scan(&item)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysClient) Add(ctx context.Context, param *model.SysClientAddParam) (output *model.SysClientAddModel, err error) {
|
||||
data := &do.SysClient{}
|
||||
gconv.Struct(param, data)
|
||||
data.ClientId = uuid32.S()
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.CreatedBy = contexts.GetUserId(ctx)
|
||||
data.CreatedDept = contexts.GetDeptId(ctx)
|
||||
|
||||
db := dao.SysClient.Ctx(ctx)
|
||||
id, err := db.Data(data).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
output = &model.SysClientAddModel{
|
||||
Id: id,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysClient) Edit(ctx context.Context, param *model.SysClientEditParam) (output *model.SysClientEditModel, err error) {
|
||||
data := &do.SysClient{}
|
||||
gconv.Struct(param, data)
|
||||
data.ClientId = nil
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
|
||||
db := dao.SysClient.Ctx(ctx)
|
||||
_, err = db.Data(data).OmitNil().Where(dao.SysClient.Columns().Id, param.Id).Update()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
output = &model.SysClientEditModel{
|
||||
Id: param.Id,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysClient) Delete(ctx context.Context, param *model.SysClientDeleteParam) (output *model.SysClientDeleteModel, err error) {
|
||||
db := dao.SysClient.Ctx(ctx)
|
||||
data := &do.SysClient{}
|
||||
data.DeletedAt = gtime.Now()
|
||||
data.DeletedBy = contexts.GetUserId(ctx)
|
||||
_, err = db.Data(data).OmitNil().Where(dao.SysClient.Columns().Id, param.Ids).Update()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
output = &model.SysClientDeleteModel{
|
||||
Ids: param.Ids,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysClient) Status(ctx context.Context, param *model.SysClientStatusParam) (output *model.SysClientStatusModel, err error) {
|
||||
db := dao.SysClient.Ctx(ctx)
|
||||
_, err = db.Data(param).OmitNil().Where(dao.SysClient.Columns().Id, param.Id).Update()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
output = &model.SysClientStatusModel{
|
||||
Id: param.Id,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolepolicy"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolesync"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysConfig struct {
|
||||
}
|
||||
|
||||
func NewSysConfig() *sSysConfig {
|
||||
return &sSysConfig{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysConfig(NewSysConfig())
|
||||
}
|
||||
|
||||
func (l *sSysConfig) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: false,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysConfig.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (l *sSysConfig) ModelQuery(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: false,
|
||||
})
|
||||
}
|
||||
return handler.Model(service.MemoryDB().DB(ctx).Ctx(ctx).Model(dao.SysConfig.Table()), option...)
|
||||
}
|
||||
|
||||
func (s *sSysConfig) List(ctx context.Context, param *model.SysConfigListParam) (items []*model.SysConfigListModel, total int, err error) {
|
||||
m := s.ModelQuery(ctx)
|
||||
|
||||
if param.ConfigName != "" {
|
||||
m = m.WhereLike(dao.SysConfig.Columns().ConfigName, "%"+param.ConfigName+"%")
|
||||
}
|
||||
|
||||
if param.ConfigKey != "" {
|
||||
m = m.WhereLike(dao.SysConfig.Columns().ConfigKey, "%"+param.ConfigKey+"%")
|
||||
}
|
||||
|
||||
if param.ConfigType != "" {
|
||||
m = m.Where(dao.SysConfig.Columns().ConfigType, param.ConfigType)
|
||||
}
|
||||
|
||||
if param.ConfigValue != "" {
|
||||
m = m.WhereLike(dao.SysConfig.Columns().ConfigValue, "%"+param.ConfigValue+"%")
|
||||
}
|
||||
|
||||
if len(param.CreatedAt) > 0 {
|
||||
startTime := gtime.NewFromStr(param.CreatedAt[0])
|
||||
endTime := gtime.NewFromStr(param.CreatedAt[1])
|
||||
m = m.WhereBetween(dao.SysConfig.Columns().CreatedAt, startTime, endTime.EndOfDay())
|
||||
}
|
||||
|
||||
total, err = m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = m.Page(param.Page, param.PageSize).Order(dao.SysConfig.Columns().ConfigId, "ASC").Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysConfig) Add(ctx context.Context, param *model.SysConfigAddParam) (output *model.SysConfigAddModel, err error) {
|
||||
if param == nil || param.ConfigName == "" {
|
||||
return nil, errors.New("配置名称不能为空")
|
||||
}
|
||||
if param.ConfigKey == rolepolicy.TemplateConfigKey {
|
||||
if !contexts.IsSuperAdmin(ctx) || contexts.GetTenantId(ctx) != consts.DefaultSystemTenantCode {
|
||||
return nil, errors.New("仅平台超级管理员可配置代理模板")
|
||||
}
|
||||
if _, err := rolepolicy.ValidateTemplate(ctx, param.ConfigValue); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count, err := dao.SysConfig.Ctx(ctx).Where("tenant_id", consts.DefaultSystemTenantCode).Where("config_key", rolepolicy.TemplateConfigKey).Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count > 0 {
|
||||
return nil, errors.New("代理模板参数已存在")
|
||||
}
|
||||
}
|
||||
m := s.Model(ctx)
|
||||
|
||||
data := &do.SysConfig{}
|
||||
gconv.Struct(param, data)
|
||||
data.TenantId = contexts.GetTenantId(ctx)
|
||||
data.CreatedDept = contexts.GetDeptId(ctx)
|
||||
data.CreatedBy = contexts.GetUserId(ctx)
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
|
||||
var lastInsertId int64
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
var insertErr error
|
||||
lastInsertId, insertErr = m.Ctx(ctx).Data(data).InsertAndGetId()
|
||||
if insertErr != nil {
|
||||
return insertErr
|
||||
}
|
||||
if param.ConfigKey == rolepolicy.TemplateConfigKey {
|
||||
template, resolveErr := rolepolicy.Template(ctx)
|
||||
if resolveErr != nil {
|
||||
return resolveErr
|
||||
}
|
||||
_ = template
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if param.ConfigKey == rolepolicy.TemplateConfigKey {
|
||||
template, resolveErr := rolepolicy.Template(ctx)
|
||||
if resolveErr != nil {
|
||||
return nil, resolveErr
|
||||
}
|
||||
if err = rolesync.EnqueueRole(ctx, template.RoleId, contexts.GetUserId(ctx)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
output = &model.SysConfigAddModel{
|
||||
ConfigId: lastInsertId,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysConfigCreate, lastInsertId, data.TenantId, data.ConfigKey)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysConfig) Edit(ctx context.Context, param *model.SysConfigEditParam) (output *model.SysConfigEditModel, err error) {
|
||||
if param == nil || param.ConfigId == 0 {
|
||||
return nil, errors.New("配置ID不能为空")
|
||||
}
|
||||
|
||||
var current *entity.SysConfig
|
||||
if err := s.Model(ctx).Where("config_id", param.ConfigId).Scan(¤t); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if current == nil {
|
||||
return nil, errors.New("参数不存在")
|
||||
}
|
||||
nextKey, nextValue := current.ConfigKey, current.ConfigValue
|
||||
if param.ConfigKey != nil {
|
||||
nextKey = *param.ConfigKey
|
||||
}
|
||||
if param.ConfigValue != nil {
|
||||
nextValue = *param.ConfigValue
|
||||
}
|
||||
if current.ConfigKey == rolepolicy.TemplateConfigKey || nextKey == rolepolicy.TemplateConfigKey {
|
||||
if !contexts.IsSuperAdmin(ctx) || current.TenantId != consts.DefaultSystemTenantCode {
|
||||
return nil, errors.New("仅平台超级管理员可配置代理模板")
|
||||
}
|
||||
if current.ConfigKey != nextKey {
|
||||
return nil, errors.New("代理模板参数键不允许更改,请修改角色ID")
|
||||
}
|
||||
nextTemplate, err := rolepolicy.ValidateTemplate(ctx, nextValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
previousTemplate, previousErr := rolepolicy.ValidateTemplate(ctx, current.ConfigValue)
|
||||
if previousErr == nil && previousTemplate.DeptId != nextTemplate.DeptId {
|
||||
return nil, errors.New("模板角色必须属于同一机构,跨机构切换需要迁移下级授权")
|
||||
}
|
||||
}
|
||||
data := &do.SysConfig{}
|
||||
gconv.Struct(param, data)
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.ConfigId = nil
|
||||
|
||||
m := s.Model(ctx)
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
if _, updateErr := m.Ctx(ctx).Data(data).Where(dao.SysConfig.Columns().ConfigId, param.ConfigId).Update(); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
if nextKey == rolepolicy.TemplateConfigKey {
|
||||
template, resolveErr := rolepolicy.Template(ctx)
|
||||
if resolveErr != nil {
|
||||
return resolveErr
|
||||
}
|
||||
return rolesync.EnqueueRole(ctx, template.RoleId, contexts.GetUserId(ctx))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nextKey == rolepolicy.TemplateConfigKey {
|
||||
template, resolveErr := rolepolicy.Template(ctx)
|
||||
if resolveErr != nil {
|
||||
return nil, resolveErr
|
||||
}
|
||||
if err = rolesync.EnqueueRole(ctx, template.RoleId, contexts.GetUserId(ctx)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
output = &model.SysConfigEditModel{
|
||||
ConfigId: param.ConfigId,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysConfigUpdate, param.ConfigId, contexts.GetTenantId(ctx), nextKey)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysConfig) Delete(ctx context.Context, param *model.SysConfigDeleteParam) (output *model.SysConfigDeleteModel, err error) {
|
||||
m := s.Model(ctx)
|
||||
|
||||
if len(param.ConfigIds) == 0 {
|
||||
return nil, errors.New("请选择要删除的配置")
|
||||
}
|
||||
count, checkErr := dao.SysConfig.Ctx(ctx).WhereIn("config_id", param.ConfigIds).Where("tenant_id", consts.DefaultSystemTenantCode).Where("config_key", rolepolicy.TemplateConfigKey).Count()
|
||||
if checkErr != nil {
|
||||
return nil, checkErr
|
||||
}
|
||||
if count > 0 {
|
||||
return nil, errors.New("代理模板参数不能删除,请通过修改角色ID切换模板")
|
||||
}
|
||||
m = m.WhereIn(dao.SysConfig.Columns().ConfigId, param.ConfigIds)
|
||||
data := make([]entity.SysConfig, 0)
|
||||
err = m.Scan(&data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range data {
|
||||
if v.ConfigType == "Y" {
|
||||
return nil, errors.New("系统内置配置不能删除")
|
||||
}
|
||||
}
|
||||
|
||||
_, err = m.Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysConfigDelete, param.ConfigIds, contexts.GetTenantId(ctx))
|
||||
return &model.SysConfigDeleteModel{
|
||||
ConfigIds: param.ConfigIds,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *sSysConfig) View(ctx context.Context, param *model.SysConfigViewParam) (output *model.SysConfigViewModel, err error) {
|
||||
m := s.ModelQuery(ctx)
|
||||
|
||||
err = m.Where(dao.SysConfig.Columns().ConfigId, param.ConfigId).Scan(&output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysConfig) GetConfigByKey(ctx context.Context, configKey string) (config *entity.SysConfig, err error) {
|
||||
m := s.ModelQuery(ctx)
|
||||
err = m.Where(dao.SysConfig.Columns().ConfigKey, configKey).Scan(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,681 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysDept struct {
|
||||
}
|
||||
|
||||
func NewSysDept() *sSysDept {
|
||||
return &sSysDept{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysDept(NewSysDept())
|
||||
}
|
||||
|
||||
func (l *sSysDept) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysDept.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (l *sSysDept) ModelQuery(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(service.MemoryDB().DB(ctx).Ctx(ctx).Model(dao.SysDept.Table()), option...)
|
||||
}
|
||||
|
||||
// GetDeptList 获取部门列表
|
||||
func (l *sSysDept) GetDeptList(ctx context.Context, query model.SysDeptListParam) (items []*model.SysDeptListModel, total int, err error) {
|
||||
m := l.ModelQuery(ctx)
|
||||
if query.DeptName != "" {
|
||||
m = m.WhereLike(dao.SysDept.Columns().DeptName, "%"+query.DeptName+"%")
|
||||
}
|
||||
if query.Status != "" {
|
||||
m = m.Where(dao.SysDept.Columns().Status, query.Status)
|
||||
}
|
||||
if query.DeptType != nil {
|
||||
m = m.Where(dao.SysDept.Columns().DeptType, *query.DeptType)
|
||||
}
|
||||
// 排序:按照显示顺序OrderNum从小到大排序,如果相同则按照部门ID从小到大排序
|
||||
err = m.Page(1, 5000).Order(dao.SysDept.Columns().OrderNum, "ASC").Order(dao.SysDept.Columns().DeptId, "ASC").Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
total = len(items)
|
||||
return
|
||||
}
|
||||
|
||||
// GetDeptById 获取部门详情
|
||||
func (l *sSysDept) GetDeptById(ctx context.Context, id int64) (dept *model.SysDeptViewModel, err error) {
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return nil, gerror.NewCode(consts.CodeLoginExpired, "登录已过期")
|
||||
}
|
||||
mod := l.ModelQuery(ctx).Where(dao.SysDept.Columns().DeptId, id)
|
||||
mod = mod.FieldsPrefix(dao.SysDept.Table(), model.SysDeptViewModel{})
|
||||
err = mod.Scan(&dept)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dept, nil
|
||||
}
|
||||
|
||||
// 构建树结构
|
||||
func (l *sSysDept) DeptTree(ctx context.Context, parentDept *model.SysDeptTreeModel, deptList []*model.SysDeptListModel, ancestors string) (data []*model.SysDeptTreeModel, err error) {
|
||||
data = make([]*model.SysDeptTreeModel, 0)
|
||||
for _, dept := range deptList {
|
||||
pId := int64(0)
|
||||
if parentDept != nil {
|
||||
pId = parentDept.DeptId
|
||||
}
|
||||
if dept.ParentId == pId {
|
||||
item := &model.SysDeptTreeModel{
|
||||
DeptId: dept.DeptId,
|
||||
ParentId: dept.ParentId,
|
||||
DeptName: dept.DeptName,
|
||||
Key: fmt.Sprintf("%d", dept.DeptId),
|
||||
Children: nil,
|
||||
Ancestors: ancestors,
|
||||
DeptType: dept.DeptType,
|
||||
}
|
||||
subDept, err := l.DeptTree(ctx, item, deptList, ancestors+","+fmt.Sprintf("%d", dept.DeptId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.Children = subDept
|
||||
data = append(data, item)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetDeptFullTree 组织管理页专用的完整部门树。
|
||||
//
|
||||
// 与 GetDeptTree 的区别:
|
||||
// 1. 支持按组织名称模糊搜索、按状态/类型筛选(复用 GetDeptList 的过滤能力),
|
||||
// 而 GetDeptTree 只支持 deptType 一个维度;
|
||||
// 2. 节点携带 SysDeptListModel 的全部字段(含 deptCategory/status/createdAt),
|
||||
// 供表格展示。
|
||||
//
|
||||
// 同样由后端一次建好嵌套 children 返回,前端无需再开 treeConfig.transform。
|
||||
func (l *sSysDept) GetDeptFullTree(ctx context.Context, query model.SysDeptFullTreeParam) (items []*model.SysDeptFullTreeModel, err error) {
|
||||
depts, _, err := l.GetDeptList(ctx, query.SysDeptListParam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 带过滤条件时,命中的节点其父级可能被 WhereLike/Where 滤掉。
|
||||
// 补齐这些祖先节点,使搜索结果仍呈现完整的层级链路(与前端
|
||||
// filterDeptTree 的「命中节点保留整棵子树」语义一致),
|
||||
// 否则用户看到的是一个parentId 悬空的孤立行,看不出它归属谁。
|
||||
depts, err = l.withDeptAncestors(ctx, depts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildSysDeptFullTree(depts), nil
|
||||
}
|
||||
|
||||
// withDeptAncestors 为列表中的每个节点补齐缺失的祖先,返回补齐后的完整列表。
|
||||
//
|
||||
// 输入通常是过滤后的结果集,节点可能缺失父级。做法是逐轮收集
|
||||
// 「父级 id 不在当前集合中」的节点,按 id 批量补查,直到没有新节点补入为止。
|
||||
// 补入的祖先沿用原有排序(order_num、dept_id),与 GetDeptList 一致。
|
||||
func (l *sSysDept) withDeptAncestors(ctx context.Context, depts []*model.SysDeptListModel) ([]*model.SysDeptListModel, error) {
|
||||
if len(depts) == 0 {
|
||||
return depts, nil
|
||||
}
|
||||
|
||||
present := make(map[int64]struct{}, len(depts))
|
||||
for _, d := range depts {
|
||||
if d != nil {
|
||||
present[d.DeptId] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
merged := make([]*model.SysDeptListModel, 0, len(depts))
|
||||
merged = append(merged, depts...)
|
||||
|
||||
// 每轮收集缺失父级 → 批量补查;轮数受部门层级深度约束(通常 2~3 轮)。
|
||||
for {
|
||||
missingIds := make([]int64, 0)
|
||||
for _, d := range depts {
|
||||
if d == nil || d.ParentId == 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := present[d.ParentId]; !ok {
|
||||
missingIds = append(missingIds, d.ParentId)
|
||||
}
|
||||
}
|
||||
if len(missingIds) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
ancestors, err := l.GetDeptListByIds(ctx, missingIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ancestors) == 0 {
|
||||
// 父级已不存在(脏数据或跨租户不可见),到此为止,避免死循环。
|
||||
break
|
||||
}
|
||||
|
||||
// 去重后并入;下一轮继续向上补。
|
||||
fresh := make([]*model.SysDeptListModel, 0, len(ancestors))
|
||||
for _, a := range ancestors {
|
||||
if a == nil {
|
||||
continue
|
||||
}
|
||||
if _, ok := present[a.DeptId]; ok {
|
||||
continue
|
||||
}
|
||||
present[a.DeptId] = struct{}{}
|
||||
fresh = append(fresh, a)
|
||||
}
|
||||
if len(fresh) == 0 {
|
||||
break
|
||||
}
|
||||
merged = append(merged, fresh...)
|
||||
depts = merged
|
||||
}
|
||||
|
||||
// 补入的祖先是按「缺失父级 id 批次」查回来的,追加在尾部会打乱原有
|
||||
// order_num / dept_id 排序,而 buildSysDeptFullTree 依赖入参顺序来保证
|
||||
// 同层节点的展示次序,因此这里统一重排一次。
|
||||
sort.SliceStable(merged, func(i, j int) bool {
|
||||
if merged[i].OrderNum != merged[j].OrderNum {
|
||||
return merged[i].OrderNum < merged[j].OrderNum
|
||||
}
|
||||
return merged[i].DeptId < merged[j].DeptId
|
||||
})
|
||||
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
// buildSysDeptFullTree 把部门列表组装成嵌套树。
|
||||
//
|
||||
// 处理「可见根」:ParentId == 0 的顶层部门,以及 ParentId 指向本批次
|
||||
// 中不存在的部门(例如按状态/名称过滤后父级被滤掉)—— 后者若丢弃会
|
||||
// 导致整棵子树不可见,因此提升为根节点。逻辑与 buildSysMenuFullTree 一致。
|
||||
func buildSysDeptFullTree(depts []*model.SysDeptListModel) []*model.SysDeptFullTreeModel {
|
||||
if len(depts) == 0 {
|
||||
return make([]*model.SysDeptFullTreeModel, 0)
|
||||
}
|
||||
|
||||
// 保持入参顺序(查询已按 order_num、dept_id 排序),同时建立 id 索引。
|
||||
nodes := make([]*model.SysDeptFullTreeModel, 0, len(depts))
|
||||
nodeMaps := make(map[int64]*model.SysDeptFullTreeModel, len(depts))
|
||||
for _, item := range depts {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
node := &model.SysDeptFullTreeModel{
|
||||
SysDeptListModel: *item,
|
||||
Children: make([]*model.SysDeptFullTreeModel, 0),
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
nodeMaps[item.DeptId] = node
|
||||
}
|
||||
|
||||
roots := make([]*model.SysDeptFullTreeModel, 0, len(depts))
|
||||
for _, node := range nodes {
|
||||
if node.ParentId == 0 {
|
||||
roots = append(roots, node)
|
||||
continue
|
||||
}
|
||||
if parent, ok := nodeMaps[node.ParentId]; ok {
|
||||
parent.Children = append(parent.Children, node)
|
||||
continue
|
||||
}
|
||||
// 父级被过滤掉:提升为可见根,保证其子树仍可访问。
|
||||
roots = append(roots, node)
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
// GetDeptTree 获取部门树
|
||||
func (l *sSysDept) GetDeptTree(ctx context.Context, query model.SysDeptTreeParam) (items []*model.SysDeptTreeModel, err error) {
|
||||
depts, _, err := l.GetDeptList(ctx, model.SysDeptListParam{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(depts) == 0 {
|
||||
return make([]*model.SysDeptTreeModel, 0), nil
|
||||
}
|
||||
|
||||
// 可见根:ParentId=0 或 ParentId 指向不在列表中的部门(孤立节点)
|
||||
deptIdSet := make(map[int64]struct{}, len(depts))
|
||||
for _, d := range depts {
|
||||
deptIdSet[d.DeptId] = struct{}{}
|
||||
}
|
||||
rootDepts := make([]*model.SysDeptListModel, 0, len(depts))
|
||||
for _, d := range depts {
|
||||
if d.ParentId == 0 {
|
||||
rootDepts = append(rootDepts, d)
|
||||
continue
|
||||
}
|
||||
if _, ok := deptIdSet[d.ParentId]; !ok {
|
||||
rootDepts = append(rootDepts, d)
|
||||
}
|
||||
}
|
||||
|
||||
items = make([]*model.SysDeptTreeModel, 0)
|
||||
|
||||
// 不带过滤:完整树
|
||||
if query.DeptType == nil || *query.DeptType == "" {
|
||||
for _, root := range rootDepts {
|
||||
rootItem := l.deptTreeItem(root, fmt.Sprintf("%d", root.DeptId))
|
||||
rootItem.Children, err = l.DeptTree(ctx, rootItem, depts, rootItem.Ancestors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, rootItem)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// DeptType=0:根部门的直接子(按类型过滤)
|
||||
if *query.DeptType == "0" {
|
||||
for _, root := range rootDepts {
|
||||
for _, dept := range depts {
|
||||
if dept.ParentId == root.DeptId && dept.DeptType == 0 {
|
||||
items = append(items, l.deptTreeItem(dept, fmt.Sprintf("%d,%d", root.DeptId, dept.DeptId)))
|
||||
}
|
||||
}
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// DeptType=1:公司类型树
|
||||
if *query.DeptType == "1" {
|
||||
companyDepts := make([]*model.SysDeptListModel, 0)
|
||||
for _, dept := range depts {
|
||||
if dept.DeptType == 1 {
|
||||
companyDepts = append(companyDepts, dept)
|
||||
}
|
||||
}
|
||||
|
||||
// 公司类型的可见根:ParentId=0 或 ParentId 不在 companyDepts 中
|
||||
companyIdSet := make(map[int64]struct{}, len(companyDepts))
|
||||
for _, d := range companyDepts {
|
||||
companyIdSet[d.DeptId] = struct{}{}
|
||||
}
|
||||
companyRoots := make([]*model.SysDeptListModel, 0, len(companyDepts))
|
||||
for _, d := range companyDepts {
|
||||
if d.ParentId == 0 {
|
||||
companyRoots = append(companyRoots, d)
|
||||
continue
|
||||
}
|
||||
if _, ok := companyIdSet[d.ParentId]; !ok {
|
||||
companyRoots = append(companyRoots, d)
|
||||
}
|
||||
}
|
||||
|
||||
// rootMap:原 rootDepts 的索引,用于补 ancestors
|
||||
rootMap := make(map[int64]*model.SysDeptListModel, len(rootDepts))
|
||||
for _, root := range rootDepts {
|
||||
rootMap[root.DeptId] = root
|
||||
}
|
||||
|
||||
// 公司子树递归
|
||||
var buildCompanySubTree func(parentDept *model.SysDeptTreeModel, ancestors string) ([]*model.SysDeptTreeModel, error)
|
||||
buildCompanySubTree = func(parentDept *model.SysDeptTreeModel, ancestors string) ([]*model.SysDeptTreeModel, error) {
|
||||
data := make([]*model.SysDeptTreeModel, 0)
|
||||
for _, dept := range companyDepts {
|
||||
if dept.ParentId == parentDept.DeptId {
|
||||
item := l.deptTreeItem(dept, ancestors+","+fmt.Sprintf("%d", dept.DeptId))
|
||||
children, err := buildCompanySubTree(item, item.Ancestors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.Children = children
|
||||
data = append(data, item)
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
for _, root := range companyRoots {
|
||||
ancestors := fmt.Sprintf("%d", root.DeptId)
|
||||
if visibleRoot, ok := rootMap[root.ParentId]; ok {
|
||||
ancestors = fmt.Sprintf("%d,%d", visibleRoot.DeptId, root.DeptId)
|
||||
}
|
||||
rootItem := l.deptTreeItem(root, ancestors)
|
||||
children, err := buildCompanySubTree(rootItem, rootItem.Ancestors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rootItem.Children = children
|
||||
items = append(items, rootItem)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
return make([]*model.SysDeptTreeModel, 0), nil
|
||||
}
|
||||
|
||||
// deptTreeItem 构建部门树项
|
||||
func (l *sSysDept) deptTreeItem(dept *model.SysDeptListModel, ancestors string) *model.SysDeptTreeModel {
|
||||
return &model.SysDeptTreeModel{
|
||||
DeptId: dept.DeptId,
|
||||
ParentId: dept.ParentId,
|
||||
DeptName: dept.DeptName,
|
||||
Key: fmt.Sprintf("%d", dept.DeptId),
|
||||
Children: nil,
|
||||
Ancestors: ancestors,
|
||||
DeptType: dept.DeptType,
|
||||
}
|
||||
}
|
||||
|
||||
// 递归构建结构
|
||||
func (l *sSysDept) RecursionDeptIds(ctx context.Context, parentId int64, deptList []*model.SysDeptListModel, data *[]int64) (err error) {
|
||||
for _, dept := range deptList {
|
||||
if dept.ParentId == parentId {
|
||||
*data = append(*data, dept.DeptId)
|
||||
err := l.RecursionDeptIds(ctx, dept.DeptId, deptList, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 根据父部门id获取部门列表
|
||||
func (l *sSysDept) GetDeptIdsByParentId(ctx context.Context, parentId int64) (ids []int64, err error) {
|
||||
depts, _, err := l.GetDeptList(ctx, model.SysDeptListParam{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ids = make([]int64, 0)
|
||||
err = l.RecursionDeptIds(ctx, parentId, depts, &ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// g.Log().Infof(ctx, "sSysDept.GetDeptIdsByParentId 获取部门列表: ids:%+v", ids)
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// AddDept 添加部门
|
||||
func (l *sSysDept) AddDept(ctx context.Context, dept *model.SysDeptAddModel) (deptId int64, err error) {
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return 0, gerror.NewCode(consts.CodeLoginExpired, "登录已过期")
|
||||
}
|
||||
if dept.DeptType == 1 {
|
||||
isCompany, err := l.ValidateParentIsCompany(ctx, dept.ParentId)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysDept.AddDept ValidateParentIsCompany err: %v, parentId:%d, dept:%+v", err, dept.ParentId, dept)
|
||||
return 0, err
|
||||
}
|
||||
if !isCompany {
|
||||
return 0, gerror.New("上级部门必须是公司")
|
||||
}
|
||||
}
|
||||
|
||||
data := do.SysDept{}
|
||||
gconv.Struct(dept, &data)
|
||||
data.CreatedDept = user.DeptId
|
||||
data.CreatedBy = user.ID
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.UpdatedBy = user.ID
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.TenantId = user.TenantId
|
||||
|
||||
deptId, err = l.Model(ctx).Data(data).InsertAndGetId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDeptCreate, deptId)
|
||||
// 刷新部门祖先
|
||||
err = l.RefreshDeptAncestors(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return deptId, nil
|
||||
}
|
||||
|
||||
// EditDept 编辑部门
|
||||
func (l *sSysDept) EditDept(ctx context.Context, dept *model.SysDeptEditModel) (deptId int64, err error) {
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return dept.DeptId, gerror.NewCode(consts.CodeLoginExpired, "登录已过期")
|
||||
}
|
||||
// 查询现有数据
|
||||
existingDept, err := l.GetDeptById(ctx, dept.DeptId)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysDept.EditDept GetDeptById err: %v, deptId:%d, dept:%+v", err, dept.DeptId, gconv.String(dept))
|
||||
return 0, err
|
||||
}
|
||||
if existingDept == nil || existingDept.DeptId == 0 || existingDept.DeptId != dept.DeptId {
|
||||
g.Log().Warningf(ctx, "sSysDept.EditDept GetDeptById err: %v, deptId:%d, dept:%+v", err, dept.DeptId, gconv.String(dept))
|
||||
return 0, gerror.New("部门不存在")
|
||||
}
|
||||
newParentId := existingDept.ParentId
|
||||
if dept.ParentId != nil {
|
||||
newParentId = *dept.ParentId
|
||||
}
|
||||
if dept.DeptType != nil && *dept.DeptType == 1 && *dept.ParentId != 0 {
|
||||
isCompany, err := l.ValidateParentIsCompany(ctx, newParentId)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if !isCompany {
|
||||
return 0, gerror.New("上级组织必须是公司")
|
||||
}
|
||||
}
|
||||
|
||||
if dept.DeptType != nil && *dept.DeptType != existingDept.DeptType {
|
||||
if existingDept.DeptType == 1 {
|
||||
return 0, gerror.New("组织类型不能修改")
|
||||
}
|
||||
}
|
||||
|
||||
data := do.SysDept{}
|
||||
gconv.Struct(dept, &data)
|
||||
data.UpdatedBy = user.ID
|
||||
data.UpdatedAt = gtime.Now()
|
||||
_, err = l.Model(ctx).Data(data).Where(dao.SysDept.Columns().DeptId, dept.DeptId).Update()
|
||||
if err != nil {
|
||||
return dept.DeptId, err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDeptUpdate, dept.DeptId)
|
||||
// 刷新部门祖先
|
||||
err = l.RefreshDeptAncestors(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return dept.DeptId, nil
|
||||
}
|
||||
|
||||
// DeleteDept 删除部门
|
||||
func (l *sSysDept) DeleteDept(ctx context.Context, dept *model.SysDeptDeleteModel) (deptId int64, err error) {
|
||||
count, err := l.ModelQuery(ctx).Where(dao.SysDept.Columns().ParentId, dept.DeptId).Count()
|
||||
if err != nil {
|
||||
return dept.DeptId, err
|
||||
}
|
||||
if count > 0 {
|
||||
return dept.DeptId, gerror.NewCode(consts.CodeDeptHasSub, "该部门下有子部门,不能删除")
|
||||
}
|
||||
|
||||
// 部门下有用户,不能删除
|
||||
userList, _, err := service.SysUser().List(ctx, &request.PageInfo{
|
||||
Page: 1,
|
||||
PageSize: 100,
|
||||
}, &model.UserListParam{
|
||||
DeptId: dept.DeptId,
|
||||
})
|
||||
if err != nil {
|
||||
return dept.DeptId, err
|
||||
}
|
||||
if len(userList) > 0 {
|
||||
return dept.DeptId, gerror.New("该部门下有用户,不能删除")
|
||||
}
|
||||
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
|
||||
err = service.AgentEnterprise().Delete(ctx, &model.TAgentEnterpriseDeleteParam{
|
||||
DeptId: dept.DeptId,
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysDept.DeleteDept Delete err: %v, deptId:%d, Input:%+v", err, dept.DeptId, dept)
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = l.Model(ctx).Where(dao.SysDept.Columns().DeptId, dept.DeptId).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDeptDelete, dept.DeptId)
|
||||
return dept.DeptId, nil
|
||||
}
|
||||
|
||||
// 刷新部门 ancestors
|
||||
func (l *sSysDept) RefreshDeptAncestors(ctx context.Context) (err error) {
|
||||
var depts []*model.SysDeptViewModel
|
||||
err = l.ModelQuery(ctx, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: false,
|
||||
}).Scan(&depts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, dept := range depts {
|
||||
ancestors := ""
|
||||
l.GetParentIDAncestors(ctx, depts, dept.ParentId, &ancestors)
|
||||
|
||||
if ancestors != dept.Ancestors {
|
||||
_, err = l.Model(ctx).Data(do.SysDept{
|
||||
Ancestors: ancestors,
|
||||
}).Where(dao.SysDept.Columns().DeptId, dept.DeptId).Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDeptUpdate, dept.DeptId)
|
||||
g.Log().Infof(ctx, "sSysDept.RefreshDeptAncestors 刷新部门祖先: deptId:%d, ancestors:%s", dept.DeptId, ancestors)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetParentIDAncestors 获取部门祖先
|
||||
func (l *sSysDept) GetParentIDAncestors(ctx context.Context, depts []*model.SysDeptViewModel, printId int64, ancestors *string) (err error) {
|
||||
for _, dept := range depts {
|
||||
if dept.DeptId == printId {
|
||||
*ancestors = fmt.Sprintf("%d", dept.DeptId) + "," + *ancestors
|
||||
l.GetParentIDAncestors(ctx, depts, dept.ParentId, ancestors)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
*ancestors = "0," + *ancestors
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDeptListByIds 根据部门ID列表获取部门列表
|
||||
func (l *sSysDept) GetDeptListByIds(ctx context.Context, ids []int64) (depts []*model.SysDeptListModel, err error) {
|
||||
err = l.ModelQuery(ctx).WhereIn(dao.SysDept.Columns().DeptId, ids).Scan(&depts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return depts, nil
|
||||
}
|
||||
|
||||
// 验证上级是否是公司
|
||||
func (l *sSysDept) ValidateParentIsCompany(ctx context.Context, parentId int64) (isCompany bool, err error) {
|
||||
dept, err := l.GetDeptById(ctx, parentId)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return dept.DeptType == 1 || dept.ParentId == 0, nil
|
||||
}
|
||||
|
||||
// 获取上级公司名称
|
||||
func (l *sSysDept) getParentCompanyInfo(ctx context.Context, depts []*model.SysDeptListModel, deptId int64) *model.SysDeptListModel {
|
||||
var topDept *model.SysDeptListModel
|
||||
for _, dept := range depts {
|
||||
if dept.ParentId == 0 {
|
||||
topDept = dept
|
||||
}
|
||||
if dept.DeptId == deptId {
|
||||
if dept.ParentId == 0 {
|
||||
return dept
|
||||
}
|
||||
if dept.DeptType == 1 {
|
||||
return dept
|
||||
}
|
||||
return l.getParentCompanyInfo(ctx, depts, dept.ParentId)
|
||||
}
|
||||
}
|
||||
return topDept
|
||||
}
|
||||
|
||||
// 获取组织公司Map
|
||||
func (l *sSysDept) GetDeptCompanyMap(ctx context.Context) (map[int64]string, error) {
|
||||
depts, _, err := l.GetDeptList(ctx, model.SysDeptListParam{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
companyMap := make(map[int64]string)
|
||||
for _, dept := range depts {
|
||||
if dept.ParentId == 0 {
|
||||
companyMap[0] = dept.DeptName
|
||||
}
|
||||
if dept.DeptType == 1 {
|
||||
companyMap[dept.DeptId] = dept.DeptName
|
||||
} else if dept.ParentId == 0 {
|
||||
companyMap[dept.DeptId] = dept.DeptName
|
||||
} else {
|
||||
parentDept := l.getParentCompanyInfo(ctx, depts, dept.DeptId)
|
||||
if parentDept != nil {
|
||||
companyMap[dept.DeptId] = parentDept.DeptName
|
||||
} else {
|
||||
companyMap[dept.DeptId] = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
return companyMap, nil
|
||||
}
|
||||
|
||||
// 获取上级公司组织信息
|
||||
func (l *sSysDept) GetParentCompanyInfo(ctx context.Context, deptId int64) *model.SysDeptListModel {
|
||||
depts, _, err := l.GetDeptList(ctx, model.SysDeptListParam{})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysDept.GetParentCompanyInfo GetDeptList err: %v, deptId:%d", err, deptId)
|
||||
return nil
|
||||
}
|
||||
return l.getParentCompanyInfo(ctx, depts, deptId)
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysDictData struct{}
|
||||
|
||||
func NewSysDictData() *sSysDictData {
|
||||
return &sSysDictData{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysDictData(NewSysDictData())
|
||||
}
|
||||
|
||||
func (l *sSysDictData) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: false,
|
||||
FilterAuth: false,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysDictData.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (l *sSysDictData) ModelQuery(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: false,
|
||||
FilterAuth: false,
|
||||
})
|
||||
}
|
||||
return handler.Model(service.MemoryDB().DB(ctx).Ctx(ctx).Model(dao.SysDictData.Table()), option...)
|
||||
}
|
||||
|
||||
func (s *sSysDictData) List(ctx context.Context, param *model.SysDictDataListParam) (items []model.SysDictDataListModel, total int, err error) {
|
||||
typeData := &model.SysDictTypeViewModel{}
|
||||
// 获取字典类型
|
||||
if param.DictType == "" && param.DictId != 0 {
|
||||
typeData, err = service.SysDictType().View(ctx, &model.SysDictTypeViewParam{
|
||||
DictId: param.DictId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if typeData == nil {
|
||||
return nil, 0, gerror.New("字典类型不存在")
|
||||
}
|
||||
param.DictType = typeData.DictType
|
||||
}
|
||||
if param.DictType == "" {
|
||||
return nil, 0, gerror.New("字典类型不能为空")
|
||||
}
|
||||
// 获取字典数据
|
||||
items = make([]model.SysDictDataListModel, 0)
|
||||
m := s.ModelQuery(ctx).Where(dao.SysDictData.Columns().DictType, param.DictType)
|
||||
if param.DictValue != "" {
|
||||
m = m.Where(dao.SysDictData.Columns().DictValue, param.DictValue)
|
||||
}
|
||||
total, err = m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = m.Order(dao.SysDictData.Columns().DictSort, "ASC").Page(param.Page, param.PageSize).Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysDictData) View(ctx context.Context, param *model.SysDictDataViewParam) (data *model.SysDictDataViewModel, err error) {
|
||||
if param.DictCode == 0 {
|
||||
return nil, gerror.New("字典编码不能为空")
|
||||
}
|
||||
|
||||
m := s.ModelQuery(ctx)
|
||||
if param.DictCode != 0 {
|
||||
m = m.Where(dao.SysDictData.Columns().DictCode, param.DictCode)
|
||||
}
|
||||
err = m.Scan(&data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysDictData) Add(ctx context.Context, param *model.SysDictDataAddParam) (output *model.SysDictDataAddModel, err error) {
|
||||
m := s.Model(ctx)
|
||||
data := do.SysDictData{}
|
||||
gconv.Struct(param, &data)
|
||||
// 通过 DictType 查询字典类型列表
|
||||
listParam := &model.SysDictTypeListParam{
|
||||
DictType: param.DictType,
|
||||
}
|
||||
typeList, _, err := service.SysDictType().List(ctx, listParam)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(typeList) == 0 {
|
||||
return nil, gerror.New("字典类型不存在")
|
||||
}
|
||||
isSys := typeList[0].IsSys
|
||||
if isSys == "0" && !contexts.IsSuperAdmin(ctx) {
|
||||
return nil, gerror.New("非超级管理员不能添加系统内置字典数据")
|
||||
}
|
||||
data.TenantId = contexts.GetTenantId(ctx)
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.CreatedBy = contexts.GetUserId(ctx)
|
||||
data.CreatedDept = contexts.GetDeptId(ctx)
|
||||
m = m.Data(data).OmitNil()
|
||||
output = &model.SysDictDataAddModel{}
|
||||
output.DictCode, err = m.InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDictDataCreate, output.DictCode)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysDictData) Edit(ctx context.Context, param *model.SysDictDataEditParam) (output *model.SysDictDataEditModel, err error) {
|
||||
if param.DictCode == 0 {
|
||||
return nil, gerror.New("字典编码不能为空")
|
||||
}
|
||||
m := s.Model(ctx)
|
||||
data := do.SysDictData{}
|
||||
gconv.Struct(param, &data)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
m = m.Data(data).OmitNil().Where(dao.SysDictData.Columns().DictCode, param.DictCode)
|
||||
output = &model.SysDictDataEditModel{
|
||||
DictCode: param.DictCode,
|
||||
}
|
||||
_, err = m.Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDictDataUpdate, param.DictCode)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysDictData) Delete(ctx context.Context, param *model.SysDictDataDeleteParam) (output *model.SysDictDataDeleteModel, err error) {
|
||||
if len(param.DictCodes) == 0 {
|
||||
return nil, gerror.New("字典编码不能为空")
|
||||
}
|
||||
|
||||
// 检查要删除的字典数据所属的字典类型是否为系统内置类型
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
// 查询字典数据所属的字典类型
|
||||
var dictTypes []string
|
||||
err = dao.SysDictData.Ctx(ctx).
|
||||
Fields(dao.SysDictData.Columns().DictType).
|
||||
WhereIn(dao.SysDictData.Columns().DictCode, param.DictCodes).
|
||||
Distinct().
|
||||
Scan(&dictTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查这些字典类型是否有系统内置类型
|
||||
var systemTypeCount int
|
||||
systemTypeCount, err = dao.SysDictType.Ctx(ctx).
|
||||
WhereIn(dao.SysDictType.Columns().DictType, dictTypes).
|
||||
Where(dao.SysDictType.Columns().IsSys, "0").
|
||||
Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if systemTypeCount > 0 {
|
||||
return nil, gerror.New("系统内置字典数据不能删除,请联系管理员")
|
||||
}
|
||||
}
|
||||
|
||||
m := s.Model(ctx)
|
||||
m = m.WhereIn(dao.SysDictData.Columns().DictCode, param.DictCodes)
|
||||
_, err = m.Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysDictDataDeleteModel{
|
||||
DictCodes: param.DictCodes,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDictDataDelete, param.DictCodes)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysDictData) GetDictLabel(ctx context.Context, dictType string, dictCode string) string {
|
||||
dictData, _, err := s.List(ctx, &model.SysDictDataListParam{
|
||||
DictType: dictType,
|
||||
DictValue: dictCode,
|
||||
})
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if len(dictData) > 0 {
|
||||
return dictData[0].DictLabel
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *sSysDictData) GetDictListByTypes(ctx context.Context, dictTypes []string) (dictDataList []model.SysDictDataListModel, err error) {
|
||||
dictDataList = make([]model.SysDictDataListModel, 0)
|
||||
m := s.ModelQuery(ctx).WhereIn(dao.SysDictData.Columns().DictType, dictTypes)
|
||||
err = m.Scan(&dictDataList)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysDictType struct{}
|
||||
|
||||
func NewSysDictType() *sSysDictType {
|
||||
return &sSysDictType{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysDictType(NewSysDictType())
|
||||
}
|
||||
|
||||
func (l *sSysDictType) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
model := dao.SysDictType.Ctx(ctx)
|
||||
model = model.Where("is_sys = '0' OR tenant_id = ?", contexts.GetTenantId(ctx))
|
||||
return model
|
||||
}
|
||||
|
||||
func (l *sSysDictType) ModelQuery(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
model := service.MemoryDB().DB(ctx).Ctx(ctx).Model(dao.SysDictType.Table())
|
||||
model = model.Where("is_sys = '0' OR tenant_id = ?", contexts.GetTenantId(ctx))
|
||||
return model
|
||||
}
|
||||
|
||||
func (s *sSysDictType) List(ctx context.Context, param *model.SysDictTypeListParam) (items []model.SysDictTypeListModel, total int, err error) {
|
||||
m := s.ModelQuery(ctx)
|
||||
if param.DictName != "" {
|
||||
m = m.WhereLike(dao.SysDictType.Columns().DictName, "%"+param.DictName+"%")
|
||||
}
|
||||
if param.DictType != "" {
|
||||
m = m.WhereLike(dao.SysDictType.Columns().DictType, "%"+param.DictType+"%")
|
||||
}
|
||||
if param.IsSys != "" {
|
||||
m = m.Where(dao.SysDictType.Columns().IsSys, param.IsSys)
|
||||
}
|
||||
if len(param.CreatedAt) == 2 {
|
||||
createdAt := gtime.NewFromStr(param.CreatedAt[0])
|
||||
createdAtEnd := gtime.NewFromStr(param.CreatedAt[1])
|
||||
m = m.WhereBetween(dao.SysDictType.Columns().CreatedAt, createdAt, createdAtEnd.EndOfDay())
|
||||
}
|
||||
total, err = m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = m.Order(dao.SysDictType.Columns().DictId, "DESC").Page(param.Page, param.PageSize).Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysDictType) View(ctx context.Context, param *model.SysDictTypeViewParam) (data *model.SysDictTypeViewModel, err error) {
|
||||
m := s.ModelQuery(ctx)
|
||||
|
||||
if param.DictId != 0 {
|
||||
m = m.Where(dao.SysDictType.Columns().DictId, param.DictId)
|
||||
} else if param.DictType != "" {
|
||||
m = m.Where(dao.SysDictType.Columns().DictType, param.DictType)
|
||||
} else {
|
||||
return nil, errors.New("字典类型ID不能为空")
|
||||
}
|
||||
err = m.Scan(&data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysDictType) Add(ctx context.Context, param *model.SysDictTypeAddParam) (output *model.SysDictTypeAddModel, err error) {
|
||||
// 如果设置为系统字典类型,需要检查权限
|
||||
if param.IsSys == "0" {
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
return nil, errors.New("非超级管理员不能添加系统字典类型")
|
||||
}
|
||||
}
|
||||
m := s.Model(ctx)
|
||||
data := do.SysDictType{}
|
||||
gconv.Struct(param, &data)
|
||||
data.TenantId = contexts.GetTenantId(ctx)
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.CreatedBy = contexts.GetUserId(ctx)
|
||||
data.CreatedDept = contexts.GetDeptId(ctx)
|
||||
m = m.Data(data).OmitNil()
|
||||
output = &model.SysDictTypeAddModel{}
|
||||
output.DictId, err = m.InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDictTypeCreate, output.DictId)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysDictType) Edit(ctx context.Context, param *model.SysDictTypeEditParam) (output *model.SysDictTypeEditModel, err error) {
|
||||
if param.DictId == 0 {
|
||||
g.Log().Errorf(ctx, "字典类型ID不能为空,param:%+v", param)
|
||||
return nil, errors.New("字典类型ID不能为空")
|
||||
}
|
||||
|
||||
// 如果设置为系统字典类型,需要检查权限
|
||||
if param.IsSys == "0" {
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
return nil, errors.New("非超级管理员不能修改为系统字典类型")
|
||||
}
|
||||
}
|
||||
|
||||
m := s.Model(ctx)
|
||||
data := do.SysDictType{}
|
||||
gconv.Struct(param, &data)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
m = m.Data(data).OmitNil().Where(dao.SysDictType.Columns().DictId, param.DictId)
|
||||
output = &model.SysDictTypeEditModel{
|
||||
DictId: param.DictId,
|
||||
}
|
||||
_, err = m.Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDictTypeUpdate, param.DictId)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysDictType) Delete(ctx context.Context, param *model.SysDictTypeDeleteParam) (output *model.SysDictTypeDeleteModel, err error) {
|
||||
if len(param.DictIds) == 0 {
|
||||
return nil, errors.New("字典类型ID不能为空")
|
||||
}
|
||||
|
||||
// 检查是否有系统内置字典类型
|
||||
var systemDictTypes []entity.SysDictType
|
||||
err = s.Model(ctx).WhereIn(dao.SysDictType.Columns().DictId, param.DictIds).
|
||||
Where(dao.SysDictType.Columns().IsSys, "0").
|
||||
Scan(&systemDictTypes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 如果有系统内置字典类型,检查当前用户权限
|
||||
if len(systemDictTypes) > 0 {
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
var dictNames []string
|
||||
for _, dict := range systemDictTypes {
|
||||
dictNames = append(dictNames, dict.DictName)
|
||||
}
|
||||
return nil, fmt.Errorf("系统内置字典类型 [%s] 只有超级管理员可以删除", strings.Join(dictNames, ", "))
|
||||
}
|
||||
}
|
||||
|
||||
m := s.Model(ctx)
|
||||
m = m.WhereIn(dao.SysDictType.Columns().DictId, param.DictIds)
|
||||
_, err = m.Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysDictTypeDeleteModel{
|
||||
DictIds: param.DictIds,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDictTypeDelete, param.DictIds)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/tasks"
|
||||
)
|
||||
|
||||
func init() {
|
||||
service.RegisterSysJob(NewSysJob())
|
||||
}
|
||||
|
||||
type sSysJob struct{}
|
||||
|
||||
func NewSysJob() *sSysJob {
|
||||
return &sSysJob{}
|
||||
}
|
||||
|
||||
func (s *sSysJob) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: false,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysJob.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (c *sSysJob) List(ctx context.Context, query *model.SysJobListParam, pageInfo *request.PageInfo) (Data []*model.SysJobListModel, total int, err error) {
|
||||
m := c.Model(ctx)
|
||||
if query.JobName != "" {
|
||||
m = m.WhereLike(dao.SysJob.Columns().JobName, "%"+strings.Trim(query.JobName, " ")+"%")
|
||||
}
|
||||
if query.JobGroup != "" {
|
||||
m = m.Where(dao.SysJob.Columns().JobGroup, query.JobGroup)
|
||||
}
|
||||
if query.Status != "" {
|
||||
m = m.Where(dao.SysJob.Columns().Status, query.Status)
|
||||
}
|
||||
t, err := m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
Data = make([]*model.SysJobListModel, 0)
|
||||
if err := m.Page(pageInfo.Page, pageInfo.PageSize).Order(dao.SysJob.Columns().JobId, "DESC").Scan(&Data); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return Data, t, nil
|
||||
}
|
||||
|
||||
func (c *sSysJob) View(ctx context.Context, jobId int64) (Data *model.SysJobViewModel, err error) {
|
||||
m := c.Model(ctx)
|
||||
Data = &model.SysJobViewModel{}
|
||||
if err := m.Where(dao.SysJob.Columns().JobId, jobId).Scan(&Data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return Data, nil
|
||||
}
|
||||
|
||||
func (c *sSysJob) Add(ctx context.Context, jobAdd *model.SysJobAddModel) (LastInsertId int64, err error) {
|
||||
claims := contexts.GetUser(ctx)
|
||||
if claims == nil {
|
||||
return 0, gerror.NewCode(gcode.CodeMissingParameter, "用户信息不存在")
|
||||
}
|
||||
|
||||
//获取task目录下是否绑定对应的方法
|
||||
_, exist := tasks.TasksInstance(ctx).CheckFuncName(jobAdd.InvokeTarget)
|
||||
if !exist {
|
||||
errInfo := fmt.Sprintf("没有绑定对应的方法:%s", jobAdd.InvokeTarget)
|
||||
return 0, gerror.New(errInfo)
|
||||
}
|
||||
|
||||
jobAdd.CreatedDept = claims.BaseClaims.DeptId
|
||||
jobAdd.CreatedAt = gtime.Now()
|
||||
jobAdd.CreatedBy = claims.BaseClaims.ID
|
||||
jobAdd.UpdatedAt = gtime.Now()
|
||||
jobAdd.UpdatedBy = claims.BaseClaims.ID
|
||||
|
||||
result, err := c.Model(ctx).Data(jobAdd).Insert()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
lastInserId, _ := result.LastInsertId()
|
||||
if lastInserId > 0 && jobAdd.Status == consts.SysJobStatusNormal {
|
||||
job := &model.SysJobViewModel{}
|
||||
if err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, lastInserId).Scan(&job); err != nil {
|
||||
return lastInserId, err
|
||||
}
|
||||
//任务启动
|
||||
err = c.taskRun(ctx, &job.SysJob)
|
||||
if err != nil {
|
||||
return lastInserId, err
|
||||
}
|
||||
}
|
||||
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
func (c *sSysJob) Update(ctx context.Context, jobUpdate *model.SysJobUpdateModel) (RowsAffected int64, err error) {
|
||||
claims := contexts.GetUser(ctx)
|
||||
if claims == nil {
|
||||
return 0, gerror.NewCode(gcode.CodeMissingParameter, "用户信息不存在")
|
||||
}
|
||||
//获取task目录下是否绑定对应的方法
|
||||
_, exist := tasks.TasksInstance(ctx).CheckFuncName(jobUpdate.InvokeTarget)
|
||||
if !exist {
|
||||
errInfo := fmt.Sprintf("没有绑定对应的方法:%s", jobUpdate.InvokeTarget)
|
||||
return 0, gerror.New(errInfo)
|
||||
}
|
||||
|
||||
jobUpdate.UpdatedAt = gtime.Now()
|
||||
jobUpdate.UpdatedBy = claims.BaseClaims.ID
|
||||
result, err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobUpdate.JobId).Update(jobUpdate)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
row, err := result.RowsAffected()
|
||||
if row > 0 && jobUpdate.Status == consts.SysJobStatusNormal {
|
||||
job := &model.SysJobViewModel{}
|
||||
if err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobUpdate.JobId).Scan(&job); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
//任务启动
|
||||
err = c.taskRestart(ctx, &job.SysJob)
|
||||
if err != nil {
|
||||
return row, err
|
||||
}
|
||||
}
|
||||
return row, err
|
||||
}
|
||||
|
||||
func (c *sSysJob) UpdateStatus(ctx context.Context, jobUpdate *model.SysJobUpdateStatusModel) (RowsAffected int64, err error) {
|
||||
claims := contexts.GetUser(ctx)
|
||||
if claims == nil {
|
||||
return 0, gerror.NewCode(gcode.CodeMissingParameter, "用户信息不存在")
|
||||
}
|
||||
|
||||
exists := &model.SysJobViewModel{}
|
||||
if err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobUpdate.JobId).Scan(&exists); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if jobUpdate.Status == consts.SysJobStatusNormal {
|
||||
//任务启动
|
||||
err = c.taskRun(ctx, &exists.SysJob)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
if jobUpdate.Status == consts.SysJobStatusDisable {
|
||||
//任务停止
|
||||
if err = c.taskStop(ctx, &exists.SysJob); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
jobUpdate.UpdatedAt = gtime.Now()
|
||||
jobUpdate.UpdatedBy = claims.BaseClaims.ID
|
||||
result, err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobUpdate.JobId).Update(jobUpdate)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
row, err := result.RowsAffected()
|
||||
return row, err
|
||||
}
|
||||
|
||||
// 任务重启
|
||||
func (c *sSysJob) taskRestart(ctx context.Context, job *entity.SysJob) (err error) {
|
||||
//任务停止
|
||||
if err = c.taskStop(ctx, job); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//任务启动
|
||||
err = c.taskRun(ctx, job)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 任务启动
|
||||
func (c *sSysJob) taskRun(ctx context.Context, job *entity.SysJob) error {
|
||||
//获取task目录下是否绑定对应的方法
|
||||
actualName, exist := tasks.TasksInstance(ctx).CheckFuncName(job.InvokeTarget)
|
||||
if !exist {
|
||||
errInfo := fmt.Sprintf("没有绑定对应的方法:%s", job.InvokeTarget)
|
||||
return gerror.New(errInfo)
|
||||
}
|
||||
|
||||
//传参解析
|
||||
paramArr, err := tasks.TasksInstance(ctx).ParseParameters(job.JobParams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
taskData := tasks.Task{
|
||||
ID: fmt.Sprintf("%s-job-%d", job.InvokeTarget, job.JobId),
|
||||
TaskType: "Type-" + gconv.String(job.MisfirePolicy),
|
||||
MethodName: actualName,
|
||||
Params: paramArr,
|
||||
Explain: job.JobName,
|
||||
}
|
||||
runPayload, _ := json.Marshal(taskData)
|
||||
if job.MisfirePolicy == consts.SysJobMisfirePolicyMulty {
|
||||
// entryID, err := tasks.TasksInstance().Cron(
|
||||
// job.Concurrent,
|
||||
// worker.WithTaskCtx(context.Background()),
|
||||
// worker.WithTaskUid(taskData.ID), // 任务ID
|
||||
// worker.WithTaskGroup(taskData.MethodName), // 任务组
|
||||
// worker.WithTaskExpr(job.CronExpression),
|
||||
// worker.WithTaskTimeout(10),
|
||||
// worker.WithTaskReplace(true),
|
||||
// worker.WithTaskPayload(runPayload),
|
||||
// )
|
||||
entryID, err := tasks.TasksInstance(ctx).Cron(ctx, tasks.TaskOptions{
|
||||
Uid: taskData.ID,
|
||||
Payload: runPayload,
|
||||
Expr: &job.CronExpression,
|
||||
Concurrent: job.Concurrent,
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Debug(ctx, taskData.MethodName, taskData.Explain, "启动任务失败")
|
||||
return err
|
||||
}
|
||||
g.Log().Debug(ctx, taskData.MethodName, taskData.Explain, "启动任务成功",
|
||||
"entryID", entryID, "CronExpression", job.CronExpression,
|
||||
"JobId", job.JobId,
|
||||
"JobName", job.JobName)
|
||||
} else {
|
||||
now := true
|
||||
err := tasks.TasksInstance(ctx).Once(ctx, tasks.TaskOptions{
|
||||
Uid: taskData.ID,
|
||||
Now: &now,
|
||||
Payload: runPayload,
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Debug(ctx, taskData.MethodName, taskData.Explain, "启动任务失败")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *sSysJob) taskStop(ctx context.Context, job *entity.SysJob) error {
|
||||
//获取task目录下是否绑定对应的方法
|
||||
_, exist := tasks.TasksInstance(ctx).CheckFuncName(job.InvokeTarget)
|
||||
if !exist {
|
||||
errInfo := fmt.Sprintf("没有绑定对应的方法:%s", job.InvokeTarget)
|
||||
return gerror.New(errInfo)
|
||||
}
|
||||
|
||||
taskJobId := fmt.Sprintf("%s-job-%d", job.InvokeTarget, job.JobId)
|
||||
|
||||
err := tasks.TasksInstance(ctx).Remove(taskJobId)
|
||||
if err != nil {
|
||||
g.Log().Debug(ctx, job.JobName, "启动停止失败")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *sSysJob) Delete(ctx context.Context, jobDelete *model.SysJobDeleteModel) (RowsAffected int64, err error) {
|
||||
claims, err := service.SysAuth().GetCurrentUser(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
exists := &[]*model.SysJobViewModel{}
|
||||
c.Model(ctx).WhereIn(dao.SysJob.Columns().JobId, jobDelete.JobIds).Where(dao.SysJob.Columns().Status, consts.SysJobStatusNormal).Scan(exists)
|
||||
if len(*exists) > 0 {
|
||||
return 0, gerror.New("存在正常运行的任务,请先停止后再删除")
|
||||
}
|
||||
|
||||
jobDelete.DeletedAt = gtime.Now()
|
||||
jobDelete.DeletedBy = claims.BaseClaims.ID
|
||||
result, err := c.Model(ctx).WhereIn(dao.SysJob.Columns().JobId, jobDelete.JobIds).Update(map[string]interface{}{
|
||||
dao.SysJob.Columns().DeletedAt: jobDelete.DeletedAt,
|
||||
dao.SysJob.Columns().DeletedBy: jobDelete.DeletedBy})
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
func (c *sSysJob) Exec(ctx context.Context, jobId int64) error {
|
||||
exists := &model.SysJobViewModel{}
|
||||
err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobId).Scan(&exists)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actualName, exist := tasks.TasksInstance(ctx).CheckFuncName(exists.InvokeTarget)
|
||||
if !exist {
|
||||
errInfo := fmt.Sprintf("没有绑定对应的方法:%s", exists.InvokeTarget)
|
||||
return gerror.New(errInfo)
|
||||
}
|
||||
|
||||
//传参解析
|
||||
paramArr, err := tasks.TasksInstance(ctx).ParseParameters(exists.JobParams)
|
||||
if err != nil {
|
||||
g.Log().Error(ctx, "sSysJob.JobRun worker.ParseParameters", err)
|
||||
return err
|
||||
}
|
||||
|
||||
taskData := tasks.Task{
|
||||
ID: fmt.Sprintf("%s-job-%d", exists.InvokeTarget, exists.JobId),
|
||||
TaskType: "Type-" + gconv.String(exists.MisfirePolicy),
|
||||
MethodName: actualName,
|
||||
Params: paramArr,
|
||||
Explain: exists.JobName,
|
||||
}
|
||||
runPayload, _ := json.Marshal(taskData)
|
||||
now := true
|
||||
err = tasks.TasksInstance(ctx).Once(ctx, tasks.TaskOptions{
|
||||
Uid: taskData.ID,
|
||||
Payload: runPayload,
|
||||
Now: &now,
|
||||
})
|
||||
// worker.WithTaskCtx(context.Background()),
|
||||
// worker.WithTaskUid(taskData.ID), // 任务ID
|
||||
// worker.WithTaskGroup(taskData.MethodName), // 任务组
|
||||
// worker.WithTaskTimeout(10),
|
||||
// worker.WithTaskNow(true),
|
||||
// worker.WithTaskReplace(true),
|
||||
// worker.WithTaskPayload(runPayload),
|
||||
// )
|
||||
|
||||
if err != nil {
|
||||
errInfo := fmt.Sprintf(exists.InvokeTarget, taskData.Explain, "启动任务失败")
|
||||
return errors.New(errInfo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 查询所有的状态正常的任务列表,并进行初始注册
|
||||
func (c *sSysJob) InitRegister() error {
|
||||
g.Log().Debug(context.Background(), "初始化任务注册")
|
||||
jobs := &[]*model.SysJobViewModel{}
|
||||
err := c.Model(context.Background()).Where(dao.SysJob.Columns().Status, consts.SysJobStatusNormal).Scan(jobs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, job := range *jobs {
|
||||
err = c.taskRun(context.Background(), &job.SysJob)
|
||||
if err != nil {
|
||||
g.Log().Error(context.Background(), "初始化注册任务 %s 失败", job.JobName, err)
|
||||
continue
|
||||
}
|
||||
g.Log().Debug(context.Background(), "初始化注册任务 %s 成功", job.JobName)
|
||||
}
|
||||
g.Log().Debug(context.Background(), "初始化任务注册完成")
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysLogininfor struct{}
|
||||
|
||||
func NewSysLogininfor() *sSysLogininfor {
|
||||
return &sSysLogininfor{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysLogininfor(NewSysLogininfor())
|
||||
}
|
||||
|
||||
func (s *sSysLogininfor) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysLogininfor.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (s *sSysLogininfor) List(ctx context.Context, param *model.SysLogininforListParam) (items []*model.SysLogininforListModel, total int, err error) {
|
||||
m := s.Model(ctx)
|
||||
|
||||
if param.Ipaddr != "" {
|
||||
m = m.WhereLike(dao.SysLogininfor.Columns().Ipaddr, "%"+param.Ipaddr+"%")
|
||||
}
|
||||
if param.UserName != "" {
|
||||
m = m.WhereLike(dao.SysLogininfor.Columns().UserName, "%"+param.UserName+"%")
|
||||
}
|
||||
if param.Status != "" {
|
||||
m = m.Where(dao.SysLogininfor.Columns().Status, param.Status)
|
||||
}
|
||||
if len(param.LoginTime) == 2 {
|
||||
startTime := gtime.NewFromStr(param.LoginTime[0])
|
||||
endTime := gtime.NewFromStr(param.LoginTime[1])
|
||||
m = m.WhereBetween(dao.SysLogininfor.Columns().LoginTime, startTime, endTime.EndOfDay())
|
||||
}
|
||||
total, err = m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = m.Page(param.Page, param.PageSize).OrderDesc(dao.SysLogininfor.Columns().InfoId).Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func (s *sSysLogininfor) AddLogininfor(ctx context.Context, logininfor *model.SysLogininforAddModel) (id int64, err error) {
|
||||
data := do.SysLogininfor{}
|
||||
gconv.Struct(logininfor, &data)
|
||||
result, err := dao.SysLogininfor.Ctx(ctx).Data(logininfor).Save()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
id, err = result.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (s *sSysLogininfor) Delete(ctx context.Context, param *model.SysLogininforDeleteParam) (output *model.SysLogininforDeleteModel, err error) {
|
||||
m := s.Model(ctx)
|
||||
output = &model.SysLogininforDeleteModel{
|
||||
InfoIds: param.InfoIds,
|
||||
}
|
||||
if len(param.InfoIds) == 0 {
|
||||
return nil, errors.New("请选择要删除的配置")
|
||||
}
|
||||
_, err = m.WhereIn(dao.SysLogininfor.Columns().InfoId, param.InfoIds).Delete()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
v1 "git.5iot.com/iotlcm-public/iotlcm-admin-svr/api/system/v1"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolepolicy"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
type sSysMenu struct {
|
||||
}
|
||||
|
||||
func NewSysMenu() *sSysMenu {
|
||||
return &sSysMenu{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysMenu(NewSysMenu())
|
||||
}
|
||||
|
||||
func (s *sSysMenu) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: false,
|
||||
FilterAuth: false,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysMenu.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (s *sSysMenu) ModelQuery(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: false,
|
||||
FilterAuth: false,
|
||||
})
|
||||
}
|
||||
return handler.Model(service.MemoryDB().DB(ctx).Ctx(ctx).Model(dao.SysMenu.Table()), option...)
|
||||
}
|
||||
|
||||
func (l *sSysMenu) List(ctx context.Context, param *model.SysMenuListParam) (data []*model.SysMenuListModel, total int, err error) {
|
||||
m := l.ModelQuery(ctx)
|
||||
if param.MenuIds != nil {
|
||||
m = m.WhereIn(dao.SysMenu.Columns().MenuId, param.MenuIds)
|
||||
}
|
||||
if param.MenuName != "" {
|
||||
m = m.WhereLike(dao.SysMenu.Columns().MenuName, "%"+param.MenuName+"%")
|
||||
}
|
||||
if param.Status != "" {
|
||||
m = m.Where(dao.SysMenu.Columns().Status, param.Status)
|
||||
}
|
||||
total, err = m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = m.Order(dao.SysMenu.Columns().OrderNum).Page(0, 5000).Scan(&data)
|
||||
if err == nil {
|
||||
placeDocumentCenterInMenuList(data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 获取租户菜单列表, 系统租户返回所有菜单,其他租户返回当前租户菜单
|
||||
func (l *sSysMenu) GetTenantMenu(ctx context.Context, query *model.SysMenuListParam) (data []*model.SysMenuListModel, total int, err error) {
|
||||
// 获取当前用户信息
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
m := l.ModelQuery(ctx)
|
||||
|
||||
if tenantId != consts.DefaultSystemTenantCode {
|
||||
// 获取租户信息
|
||||
tenantInfo, err := service.SysTenant().View(ctx, &model.SysTenantViewParam{
|
||||
TenantId: tenantId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
// 获取租户套餐
|
||||
tenantPackage, err := service.SysTenantPackage().View(ctx, &model.SysTenantPackageViewParam{
|
||||
PackageId: tenantInfo.PackageId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if tenantPackage == nil {
|
||||
g.Log().Errorf(ctx, "sSysMenu.GetTenantMenu not found tenantPackage. TenantId:%s PackageId:%d",
|
||||
tenantId, tenantInfo.PackageId)
|
||||
return nil, 0, gerror.NewCode(gcode.CodeInternalError, "系统内部错误")
|
||||
}
|
||||
if tenantPackage.MenuIds != "" {
|
||||
menuIds := strings.Split(tenantPackage.MenuIds, ",")
|
||||
m = m.WhereIn(dao.SysMenu.Columns().MenuId, menuIds)
|
||||
} else {
|
||||
return nil, 0, nil
|
||||
}
|
||||
}
|
||||
if query.MenuName != "" {
|
||||
m = m.WhereLike(dao.SysMenu.Columns().MenuName, "%"+query.MenuName+"%")
|
||||
}
|
||||
if query.Status != "" {
|
||||
m = m.Where(dao.SysMenu.Columns().Status, query.Status)
|
||||
}
|
||||
if query.MenuIds != nil {
|
||||
m = m.WhereIn(dao.SysMenu.Columns().MenuId, query.MenuIds)
|
||||
}
|
||||
|
||||
total, err = m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = m.Order(dao.SysMenu.Columns().OrderNum).Page(0, 5000).Scan(&data)
|
||||
if err == nil {
|
||||
placeDocumentCenterInMenuList(data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func placeDocumentCenterInMenuList(menus []*model.SysMenuListModel) {
|
||||
order := 33
|
||||
for _, menu := range menus {
|
||||
if menu.ParentId == 0 && menu.Path == "batchExecutionLog" {
|
||||
order = menu.OrderNum + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, menu := range menus {
|
||||
if menu.MenuId == 2153 && menu.Path == "docs" && menu.MenuType == consts.SysMenuTypeDir && menu.ParentId != 0 {
|
||||
menu.ParentId = 0
|
||||
menu.OrderNum = order
|
||||
}
|
||||
}
|
||||
sort.SliceStable(menus, func(i, j int) bool { return menus[i].OrderNum < menus[j].OrderNum })
|
||||
}
|
||||
|
||||
// GetTenantMenuTree 获取租户菜单树。
|
||||
//
|
||||
// 与 GetTenantMenu 使用完全相同的租户隔离与筛选条件,区别只在于返回结构:
|
||||
// 这里由后端一次性建好嵌套 children,前端可直接交给 vxe-table 渲染,
|
||||
// 无需再依赖 treeConfig.transform 在浏览器主线程把扁平列表二次转成树。
|
||||
//
|
||||
// 建树采用「id 索引 + 单次遍历」的实现(O(n)),不使用递归逐层过滤(O(n²)),
|
||||
// 因为菜单量级已达数百行(其中绝大多数是 F 按钮),递归过滤会有可观开销。
|
||||
func (l *sSysMenu) GetTenantMenuTree(ctx context.Context, query *model.SysMenuListParam) (items []*model.SysMenuFullTreeModel, err error) {
|
||||
list, _, err := l.GetTenantMenu(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buildSysMenuFullTree(list), nil
|
||||
}
|
||||
|
||||
// buildSysMenuFullTree 把扁平菜单列表组装成嵌套树。
|
||||
//
|
||||
// 处理两种「可见根」:
|
||||
// 1. ParentId == 0 的顶层菜单;
|
||||
// 2. ParentId 指向本批次中不存在的菜单(例如按状态/名称过滤后父级被滤掉),
|
||||
// 这类节点若丢弃会导致整棵子树不可见,因此提升为根节点。
|
||||
func buildSysMenuFullTree(list []*model.SysMenuListModel) []*model.SysMenuFullTreeModel {
|
||||
if len(list) == 0 {
|
||||
return make([]*model.SysMenuFullTreeModel, 0)
|
||||
}
|
||||
|
||||
// 保持入参顺序(查询已按 order_num 排序),同时建立 id 索引。
|
||||
nodes := make([]*model.SysMenuFullTreeModel, 0, len(list))
|
||||
nodeMaps := make(map[int64]*model.SysMenuFullTreeModel, len(list))
|
||||
for _, item := range list {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
node := &model.SysMenuFullTreeModel{
|
||||
SysMenuListModel: *item,
|
||||
Children: make([]*model.SysMenuFullTreeModel, 0),
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
nodeMaps[item.MenuId] = node
|
||||
}
|
||||
|
||||
roots := make([]*model.SysMenuFullTreeModel, 0)
|
||||
for _, node := range nodes {
|
||||
if node.ParentId == 0 {
|
||||
roots = append(roots, node)
|
||||
continue
|
||||
}
|
||||
if parent, ok := nodeMaps[node.ParentId]; ok {
|
||||
parent.Children = append(parent.Children, node)
|
||||
continue
|
||||
}
|
||||
// 父级不在本批次内:提升为根,避免子树整体丢失。
|
||||
roots = append(roots, node)
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
// 构建树结构
|
||||
func (l *sSysMenu) MenuTree(ctx context.Context, parentMenu *model.SysMenuTree, menuList []*entity.SysMenu) (data []*model.SysMenuTree, err error) {
|
||||
data = make([]*model.SysMenuTree, 0)
|
||||
for _, menu := range menuList {
|
||||
pId := int64(0)
|
||||
if parentMenu != nil {
|
||||
pId = parentMenu.MenuId
|
||||
}
|
||||
if menu.ParentId == pId {
|
||||
item := &model.SysMenuTree{
|
||||
SysMenu: *menu,
|
||||
Children: nil,
|
||||
}
|
||||
subMenu, err := l.MenuTree(ctx, item, menuList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.Children = subMenu
|
||||
data = append(data, item)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 获取用户动态路由列表
|
||||
func (l *sSysMenu) GetUserMenu(ctx context.Context) (data []*entity.SysMenu, err error) {
|
||||
assignedRoleIDs, err := service.SysUser().GetUserRoleIds(ctx, contexts.GetUserId(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roleIds := make([]int64, 0)
|
||||
roleIds = append(roleIds, assignedRoleIDs...)
|
||||
g.Log().Infof(ctx, "sSysMenu.GetUserMenu roleIds: %v", roleIds)
|
||||
var validRoles []*entity.SysRole
|
||||
if len(roleIds) > 0 {
|
||||
if err := dao.SysRole.Ctx(ctx).Where("tenant_id", contexts.GetTenantId(ctx)).WhereIn("role_id", roleIds).Where("status", "0").Scan(&validRoles); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
roleIds = nil
|
||||
for _, role := range validRoles {
|
||||
roleIds = append(roleIds, role.RoleId)
|
||||
}
|
||||
if contexts.IsSuperAdmin(ctx) && contexts.GetTenantId(ctx) == consts.DefaultSystemTenantCode && slices.Contains(roleIds, consts.SuperAdminRoleId) {
|
||||
err = l.ModelQuery(ctx).Where(dao.SysMenu.Columns().Status, consts.SysMenuStatusNormal).
|
||||
Order(dao.SysMenu.Columns().OrderNum).Scan(&data)
|
||||
return
|
||||
}
|
||||
roleIds = slices.DeleteFunc(roleIds, func(roleID int64) bool {
|
||||
return roleID == consts.SuperAdminRoleId
|
||||
})
|
||||
// 获取角色菜单列表
|
||||
menuList, err := service.SysRole().GetRoleListMenu(ctx, roleIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysMenu.GetUserMenu menuList: %v", menuList)
|
||||
menuIds := make([]int64, 0)
|
||||
for _, menu := range menuList {
|
||||
menuIds = append(menuIds, menu.MenuId)
|
||||
}
|
||||
menuIds, err = rolepolicy.RestrictMenus(ctx, contexts.GetTenantId(ctx), contexts.GetDeptId(ctx), menuIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(menuIds) == 0 {
|
||||
return []*entity.SysMenu{}, nil
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysMenu.GetUserMenu menuIds: %v", menuIds)
|
||||
// 只返回角色直接勾选的菜单;未勾选的父目录不强制补齐,
|
||||
// 树构建时会把缺少授权父级的菜单提升为一级菜单。
|
||||
err = l.ModelQuery(ctx).WhereIn(dao.SysMenu.Columns().MenuId, menuIds).
|
||||
Where(dao.SysMenu.Columns().Status, consts.SysMenuStatusNormal).
|
||||
Order(dao.SysMenu.Columns().OrderNum).Scan(&data)
|
||||
g.Log().Infof(ctx, "sSysMenu.GetUserMenu data: %v", data)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建用户动态路由树
|
||||
func (l *sSysMenu) BuildUserMenuTree(ctx context.Context, parentMenu *v1.RouteMenu, menuList []*entity.SysMenu, allPath string) (data v1.MenuAllRes, err error) {
|
||||
data = make(v1.MenuAllRes, 0)
|
||||
menuIdSet := make(map[int64]struct{}, len(menuList))
|
||||
for _, menu := range menuList {
|
||||
menuIdSet[menu.MenuId] = struct{}{}
|
||||
}
|
||||
for _, menu := range menuList {
|
||||
if menu.Status == consts.SysMenuStatusDisable {
|
||||
continue
|
||||
}
|
||||
if menu.MenuType == consts.SysMenuTypeBtn {
|
||||
continue
|
||||
}
|
||||
|
||||
pId := int64(0)
|
||||
if parentMenu != nil {
|
||||
pId = parentMenu.Id
|
||||
}
|
||||
isRootOrphan := parentMenu == nil && menu.ParentId != 0
|
||||
if isRootOrphan {
|
||||
_, isRootOrphan = menuIdSet[menu.ParentId]
|
||||
isRootOrphan = !isRootOrphan
|
||||
}
|
||||
if menu.ParentId == pId || isRootOrphan {
|
||||
openInNewWindow := false
|
||||
var link *string
|
||||
if menu.IsFrame == consts.SysMenuIsFrameYes {
|
||||
openInNewWindow = true
|
||||
link = &menu.Path
|
||||
}
|
||||
|
||||
query := make(map[string]string)
|
||||
if menu.QueryParam != "" {
|
||||
query2 := strings.Split(menu.QueryParam, "&")
|
||||
for _, q := range query2 {
|
||||
kv := strings.Split(q, "=")
|
||||
if len(kv) == 2 {
|
||||
query[kv[0]] = kv[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
hideInMenu := false
|
||||
if menu.Visible == consts.SysMenuVisibleHide {
|
||||
hideInMenu = true
|
||||
}
|
||||
keepAlive := true
|
||||
item := &v1.RouteMenu{
|
||||
Id: menu.MenuId,
|
||||
ParentId: menu.ParentId,
|
||||
Path: menu.Path,
|
||||
Redirect: nil,
|
||||
Name: fmt.Sprintf("menu_%d", menu.MenuId),
|
||||
Component: menu.Component,
|
||||
Meta: &v1.RouteMeta{
|
||||
Title: menu.MenuName,
|
||||
Icon: &menu.Icon,
|
||||
Order: &menu.OrderNum,
|
||||
OpenInNewWindow: &openInNewWindow,
|
||||
Query: query,
|
||||
HideInMenu: &hideInMenu,
|
||||
Authority: normalizeMenuAccessCodes(menu.Perms),
|
||||
Link: link,
|
||||
KeepAlive: &keepAlive,
|
||||
},
|
||||
}
|
||||
if hideInMenu {
|
||||
activePath := allPath + "/"
|
||||
if strings.Contains(menu.Path, "-") {
|
||||
activePath = activePath + strings.Split(menu.Path, "-")[0]
|
||||
}
|
||||
item.Meta.ActivePath = &activePath
|
||||
}
|
||||
subMenu, err := l.BuildUserMenuTree(ctx, item, menuList, allPath+"/"+menu.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.Children = subMenu
|
||||
if len(subMenu) == 0 && menu.MenuType == consts.SysMenuTypeDir {
|
||||
item.Children = []*v1.RouteMenu{}
|
||||
}
|
||||
data = append(data, item)
|
||||
}
|
||||
}
|
||||
// 排序
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
return *data[i].Meta.Order < *data[j].Meta.Order
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取用户动态路由树
|
||||
func (l *sSysMenu) GetUserMenuTree(ctx context.Context) (data v1.MenuAllRes, err error) {
|
||||
// 获取用户动态路由列表
|
||||
menuList, err := l.GetUserMenu(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
menuList = placeDocumentCenterAfterExecutionLog(menuList)
|
||||
// 构建树结构
|
||||
data, err = l.BuildUserMenuTree(ctx, nil, menuList, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func placeDocumentCenterAfterExecutionLog(menuList []*entity.SysMenu) []*entity.SysMenu {
|
||||
menus := append([]*entity.SysMenu(nil), menuList...)
|
||||
order := 33
|
||||
for _, menu := range menus {
|
||||
if menu.ParentId == 0 && menu.Path == "batchExecutionLog" {
|
||||
order = menu.OrderNum + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
for index, menu := range menus {
|
||||
if menu.Path != "docs" || menu.MenuName != "文档中心" || menu.MenuType != consts.SysMenuTypeDir || menu.ParentId == 0 {
|
||||
continue
|
||||
}
|
||||
copyOfMenu := *menu
|
||||
copyOfMenu.ParentId = 0
|
||||
copyOfMenu.Level = 1
|
||||
copyOfMenu.OrderNum = order
|
||||
menus[index] = ©OfMenu
|
||||
}
|
||||
return menus
|
||||
}
|
||||
|
||||
func normalizeMenuAccessCodes(perms string) []string {
|
||||
items := strings.Split(perms, ",")
|
||||
codes := make([]string, 0, len(items))
|
||||
seen := make(map[string]struct{}, len(items))
|
||||
for _, item := range items {
|
||||
item = strings.TrimSpace(item)
|
||||
if item == "" {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(item, consts.SysCheckPermissionPrefix) {
|
||||
item = consts.SysCheckPermissionMenuPrefix + item
|
||||
}
|
||||
if _, ok := seen[item]; ok {
|
||||
continue
|
||||
}
|
||||
seen[item] = struct{}{}
|
||||
codes = append(codes, item)
|
||||
}
|
||||
return codes
|
||||
}
|
||||
|
||||
func (l *sSysMenu) GetSysMenuView(ctx context.Context, menuId int64) (data *model.SysMenuViewModel, err error) {
|
||||
menu := &entity.SysMenu{}
|
||||
err = l.ModelQuery(ctx).Where(dao.SysMenu.Columns().MenuId, menuId).Scan(menu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data = &model.SysMenuViewModel{
|
||||
SysMenu: *menu,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysMenu) UpdateSysMenu(ctx context.Context, menu *model.SysMenuUpdateModel) (data *model.SysMenuViewModel, err error) {
|
||||
menu.UpdatedAt = gtime.Now()
|
||||
menu.UpdatedBy = contexts.GetUserId(ctx)
|
||||
_, err = l.Model(ctx).Where(dao.SysMenu.Columns().MenuId, menu.MenuId).Data(menu).OmitNil().Update(menu)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysMenuUpdate, menu.MenuId)
|
||||
data, err = l.GetSysMenuView(ctx, menu.MenuId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysMenu) AddSysMenu(ctx context.Context, menu *model.SysMenuAddModel) (data *model.SysMenuViewModel, err error) {
|
||||
menu.CreatedDept = contexts.GetDeptId(ctx)
|
||||
menu.CreatedAt = gtime.Now()
|
||||
menu.CreatedBy = contexts.GetUserId(ctx)
|
||||
menu.UpdatedAt = gtime.Now()
|
||||
menu.UpdatedBy = contexts.GetUserId(ctx)
|
||||
re, err := l.Model(ctx).Data(menu).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
menuId, err := re.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysMenuCreate, menuId)
|
||||
data, err = l.GetSysMenuView(ctx, menuId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysMenu) DeleteSysMenu(ctx context.Context, menuId int64) (err error) {
|
||||
_, err = l.Model(ctx).Where(dao.SysMenu.Columns().MenuId, menuId).Delete()
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysMenuDelete, menuId)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysMenu) GetFastList(ctx context.Context) (res map[int64]*entity.SysMenu, err error) {
|
||||
var models []*entity.SysMenu
|
||||
if err = s.ModelQuery(ctx).Scan(&models); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
res = make(map[int64]*entity.SysMenu, len(models))
|
||||
for _, v := range models {
|
||||
res[v.MenuId] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
)
|
||||
|
||||
func TestDocumentCenterAppearsAfterExecutionLog(t *testing.T) {
|
||||
menuList := []*entity.SysMenu{
|
||||
{MenuId: 1, MenuName: "系统管理", Path: "system", MenuType: "M", OrderNum: 900},
|
||||
{MenuId: 1836, MenuName: "执行日志", Path: "batchExecutionLog", MenuType: "C", OrderNum: 32},
|
||||
{MenuId: 2153, MenuName: "文档中心", ParentId: 1, Path: "docs", MenuType: "M", OrderNum: 13},
|
||||
{MenuId: 2155, MenuName: "Web 使用手册", ParentId: 2153, Path: "https://admin.iotlcm.example.com/docs/manual/web/", MenuType: "C", IsFrame: 0, OrderNum: 1},
|
||||
{MenuId: 2154, MenuName: "OpenAPI 文档", ParentId: 2153, Path: "https://admin.iotlcm.example.com/docs/openapi/v3/", MenuType: "C", IsFrame: 0, OrderNum: 2},
|
||||
}
|
||||
menus, err := NewSysMenu().BuildUserMenuTree(context.Background(), nil, placeDocumentCenterAfterExecutionLog(menuList), "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(menus) != 3 || menus[0].Id != 1836 || menus[1].Id != 2153 || menus[2].Id != 1 {
|
||||
t.Fatalf("unexpected root menu order: %+v", menus)
|
||||
}
|
||||
if len(menus[1].Children) != 2 || menus[1].Children[0].Id != 2155 || menus[1].Children[1].Id != 2154 {
|
||||
t.Fatalf("document links were not preserved: %+v", menus[1].Children)
|
||||
}
|
||||
if menuList[2].ParentId != 1 || menuList[2].OrderNum != 13 {
|
||||
t.Fatal("original menu record was mutated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocumentCenterMenuListHierarchy(t *testing.T) {
|
||||
menus := []*model.SysMenuListModel{
|
||||
{MenuId: 1, MenuName: "system", ParentId: 0, Path: "system", OrderNum: 900},
|
||||
{MenuId: 2153, MenuName: "docs", ParentId: 1, Path: "docs", MenuType: "M", OrderNum: 13, Perms: "docs:menu"},
|
||||
{MenuId: 2155, ParentId: 2153, Path: "https://admin.iotlcm.example.com/docs/manual/web/", OrderNum: 1},
|
||||
{MenuId: 2154, ParentId: 2153, Path: "https://admin.iotlcm.example.com/docs/openapi/v3/", OrderNum: 2},
|
||||
{MenuId: 1836, ParentId: 0, Path: "batchExecutionLog", OrderNum: 32},
|
||||
}
|
||||
placeDocumentCenterInMenuList(menus)
|
||||
if menus[3].MenuId != 2153 || menus[2].MenuId != 1836 || menus[3].ParentId != 0 || menus[3].OrderNum != 33 || menus[3].Perms != "docs:menu" {
|
||||
t.Fatalf("document center must follow execution log without losing permissions: %+v", menus)
|
||||
}
|
||||
if menus[0].ParentId != 2153 || menus[1].ParentId != 2153 {
|
||||
t.Fatalf("document links must remain children: %+v", menus)
|
||||
}
|
||||
items := buildSysMenuFullTree(menus)
|
||||
if len(items) != 3 || items[1].MenuId != 2153 || len(items[1].Children) != 2 {
|
||||
t.Fatalf("unexpected root hierarchy: %+v", items)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysNotice struct {
|
||||
}
|
||||
|
||||
func NewSysNotice() *sSysNotice {
|
||||
return &sSysNotice{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysNotice(NewSysNotice())
|
||||
}
|
||||
|
||||
func (l *sSysNotice) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysNotice.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (s *sSysNotice) List(ctx context.Context, param *model.SysNoticeListParam) (items []*model.SysNoticeListModel, total int, err error) {
|
||||
db := s.Model(ctx)
|
||||
|
||||
if param.NoticeTitle != "" {
|
||||
db = db.WhereLike(dao.SysNotice.Columns().NoticeTitle, "%"+param.NoticeTitle+"%")
|
||||
}
|
||||
|
||||
if param.NoticeType != "" {
|
||||
db = db.Where(dao.SysNotice.Columns().NoticeType, param.NoticeType)
|
||||
}
|
||||
|
||||
if param.CreatedBy != "" {
|
||||
db = db.Where(dao.SysNotice.Columns().CreatedBy, param.CreatedBy)
|
||||
}
|
||||
|
||||
if len(param.CreatedAt) == 2 {
|
||||
createdAt1 := gtime.NewFromStr(param.CreatedAt[0])
|
||||
createdAt2 := gtime.NewFromStr(param.CreatedAt[1])
|
||||
|
||||
db = db.WhereBetween(dao.SysNotice.Columns().CreatedAt, createdAt1, createdAt2.EndOfDay())
|
||||
}
|
||||
|
||||
db = db.OrderDesc(dao.SysNotice.Columns().NoticeId)
|
||||
|
||||
total, err = db.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = db.Page(param.Page, param.PageSize).WithAll().Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysNotice) insertNoticeUserList(ctx context.Context, userIds *gset.IntSet, param *entity.SysNotice) (err error) {
|
||||
|
||||
sysNotifyUserList := make([]*model.SysNoticeUserEditParam, 0)
|
||||
for _, userId := range userIds.Slice() {
|
||||
sysNotifyUserList = append(sysNotifyUserList, &model.SysNoticeUserEditParam{
|
||||
NoticeId: param.NoticeId,
|
||||
NoticeTitle: param.NoticeTitle,
|
||||
NoticeType: param.NoticeType,
|
||||
NoticeContent: string(param.NoticeContent),
|
||||
Status: param.Status,
|
||||
UserId: int64(userId),
|
||||
IsRead: 0,
|
||||
Remark: param.Remark,
|
||||
TenantId: contexts.GetTenantId(ctx),
|
||||
CreatedDept: contexts.GetDeptId(ctx),
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
CreatedAt: gtime.Now(),
|
||||
UpdatedBy: contexts.GetUserId(ctx),
|
||||
UpdatedAt: gtime.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
_, err = g.DB().Model(dao.SysNoticeUser.Table()).Data(sysNotifyUserList).OmitNil().Insert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysNotice) Add(ctx context.Context, param *model.SysNoticeAddParam) (err error) {
|
||||
if param == nil || param.NoticeTitle == "" {
|
||||
return errors.New("公告标题不能为空")
|
||||
}
|
||||
if param.NoticeType == "" {
|
||||
return errors.New("公告类型不能为空")
|
||||
}
|
||||
if param.NoticeContent == "" {
|
||||
return errors.New("公告内容不能为空")
|
||||
}
|
||||
if param.NoticeRange == 0 {
|
||||
return errors.New("公告范围不能为空")
|
||||
}
|
||||
|
||||
data := do.SysNotice{}
|
||||
gconv.Struct(param, &data)
|
||||
if param.NoticeRange == consts.NoticeRangeDept {
|
||||
if len(param.DeptIdList) == 0 {
|
||||
return errors.New("通知组织ID列表不能为空")
|
||||
}
|
||||
deptIds, err := json.Marshal(param.DeptIdList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data.DeptIds = string(deptIds)
|
||||
}
|
||||
|
||||
if param.NoticeRange == consts.NoticeRangeUser {
|
||||
if len(param.UserIdList) == 0 {
|
||||
return errors.New("通知用户ID列表不能为空")
|
||||
}
|
||||
userIds, err := json.Marshal(param.UserIdList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data.UserIds = string(userIds)
|
||||
}
|
||||
|
||||
data.TenantId = contexts.GetTenantId(ctx)
|
||||
data.CreatedDept = contexts.GetDeptId(ctx)
|
||||
data.CreatedBy = contexts.GetUserId(ctx)
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
id, err := s.Model(ctx).Data(data).OmitEmpty().InsertAndGetId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if id <= 0 {
|
||||
return errors.New("新增公告失败")
|
||||
}
|
||||
data.NoticeId = id
|
||||
|
||||
// getUserIds
|
||||
userIds := gset.NewIntSet()
|
||||
if param.NoticeRange == consts.NoticeRangeUser {
|
||||
userIds.Add(gconv.Ints(param.UserIdList)...)
|
||||
g.Log().Debugf(ctx, "userIds: %+v", userIds.Slice())
|
||||
}
|
||||
if param.NoticeRange == consts.NoticeRangeDept {
|
||||
userList, _, err := service.SysUser().List(ctx, &request.PageInfo{
|
||||
Page: 1,
|
||||
PageSize: 10000,
|
||||
}, &model.UserListParam{
|
||||
DeptIdList: param.DeptIdList,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, user := range userList {
|
||||
userIds.Add(int(user.UserId))
|
||||
}
|
||||
}
|
||||
if param.NoticeRange == consts.NoticeRangeAll {
|
||||
userList, _, err := service.SysUser().List(ctx, &request.PageInfo{
|
||||
Page: 1,
|
||||
PageSize: 10000,
|
||||
}, &model.UserListParam{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, user := range userList {
|
||||
userIds.Add(int(user.UserId))
|
||||
}
|
||||
}
|
||||
|
||||
if userIds.Size() <= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
entityData := entity.SysNotice{}
|
||||
gconv.Struct(data, &entityData)
|
||||
err = s.insertNoticeUserList(ctx, userIds, &entityData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysNotice) Edit(ctx context.Context, param *model.SysNoticeEditParam) (err error) {
|
||||
if param == nil || param.NoticeId == 0 {
|
||||
return errors.New("公告ID不能为空")
|
||||
}
|
||||
if param.NoticeTitle == nil || *param.NoticeTitle == "" {
|
||||
return errors.New("公告标题不能为空")
|
||||
}
|
||||
if param.NoticeType == nil || *param.NoticeType == "" {
|
||||
return errors.New("公告类型不能为空")
|
||||
}
|
||||
if param.NoticeContent == nil || len(*param.NoticeContent) == 0 {
|
||||
return errors.New("公告内容不能为空")
|
||||
}
|
||||
|
||||
data := do.SysNotice{}
|
||||
gconv.Struct(param, &data)
|
||||
if param.NoticeRange == consts.NoticeRangeDept {
|
||||
if len(param.DeptIdList) == 0 {
|
||||
return errors.New("通知组织ID列表不能为空")
|
||||
}
|
||||
deptIds, err := json.Marshal(param.DeptIdList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data.DeptIds = string(deptIds)
|
||||
}
|
||||
|
||||
if param.NoticeRange == consts.NoticeRangeUser {
|
||||
if len(param.UserIdList) == 0 {
|
||||
return errors.New("通知用户ID列表不能为空")
|
||||
}
|
||||
userIds, err := json.Marshal(param.UserIdList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data.UserIds = string(userIds)
|
||||
}
|
||||
data.TenantId = contexts.GetTenantId(ctx)
|
||||
data.NoticeContent = []byte(*param.NoticeContent)
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
|
||||
g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
m := s.Model(ctx)
|
||||
|
||||
m = m.Where(dao.SysNotice.Columns().NoticeId, param.NoticeId)
|
||||
g.Log().Debugf(ctx, "param: %+v", string(*param.NoticeContent))
|
||||
g.Log().Debugf(ctx, "data: %+v", string(data.NoticeContent))
|
||||
_, err = m.Data(data).OmitEmpty().Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 根据 notifyId删除,添加新的列表
|
||||
userIds := gset.NewIntSet()
|
||||
if param.NoticeRange == consts.NoticeRangeUser {
|
||||
userIds.Add(gconv.Ints(param.UserIdList)...)
|
||||
}
|
||||
if param.NoticeRange == consts.NoticeRangeDept || param.NoticeRange == consts.NoticeRangeAll {
|
||||
userList, _, err := service.SysUser().List(ctx, &request.PageInfo{
|
||||
Page: 1,
|
||||
PageSize: 10000,
|
||||
}, &model.UserListParam{
|
||||
DeptIdList: param.DeptIdList,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, user := range userList {
|
||||
userIds.Add(int(user.UserId))
|
||||
}
|
||||
}
|
||||
|
||||
// 删除
|
||||
_, err = g.DB().Model(dao.SysNoticeUser.Table()).Where(dao.SysNoticeUser.Columns().NoticeId, param.NoticeId).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if userIds.Size() <= 0 {
|
||||
return
|
||||
}
|
||||
entityData := entity.SysNotice{}
|
||||
gconv.Struct(data, &entityData)
|
||||
err = s.insertNoticeUserList(ctx, userIds, &entityData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysNotice) Delete(ctx context.Context, param *model.SysNoticeDeleteParam) (err error) {
|
||||
g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
_, err = s.Model(ctx).WhereIn(dao.SysNotice.Columns().NoticeId, param.NoticeIds).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除SysNoticeUser
|
||||
_, err = g.DB().Model(dao.SysNoticeUser.Table()).WhereIn(dao.SysNoticeUser.Columns().NoticeId, param.NoticeIds).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysNotice) View(ctx context.Context, param *model.SysNoticeViewParam) (data *model.SysNoticeViewModel, err error) {
|
||||
if param == nil || param.NoticeId == 0 {
|
||||
return nil, errors.New("公告ID不能为空")
|
||||
}
|
||||
data = &model.SysNoticeViewModel{}
|
||||
err = s.Model(ctx).Where(dao.SysNotice.Columns().NoticeId, param.NoticeId).Scan(&data)
|
||||
g.Log().Debugf(ctx, "data: %+v", string(data.NoticeContent))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if data.UserIds != "" {
|
||||
json.Unmarshal([]byte(data.UserIds), &data.UserIdList)
|
||||
}
|
||||
if data.DeptIds != "" {
|
||||
json.Unmarshal([]byte(data.DeptIds), &data.DeptIdList)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// Package gen
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
// @AutoGenerate Version
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysNoticeUser struct{}
|
||||
|
||||
func NewSysNoticeUser() *sSysNoticeUser {
|
||||
return &sSysNoticeUser{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysNoticeUser(NewSysNoticeUser())
|
||||
}
|
||||
|
||||
// Model 用户通知公告表ORM模型
|
||||
func (s *sSysNoticeUser) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysNoticeUser.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// List 获取用户通知公告表列表
|
||||
func (s *sSysNoticeUser) List(ctx context.Context, in *model.SysNoticeUserListParam) (list []*model.SysNoticeUserListModel, totalCount int, err error) {
|
||||
mod := s.Model(ctx)
|
||||
|
||||
// 根据当前用户查询
|
||||
mod = mod.Where(dao.SysNoticeUser.Columns().UserId, contexts.GetUserId(ctx))
|
||||
mod = mod.Where(dao.SysNoticeUser.Columns().Status, consts.SysStatusNormal)
|
||||
// 字段过滤
|
||||
mod = mod.Fields(model.SysNoticeUserListModel{})
|
||||
|
||||
if in.IsRead != "" {
|
||||
mod = mod.Where(dao.SysNoticeUser.Columns().IsRead, in.IsRead)
|
||||
}
|
||||
|
||||
if in.NoticeId > 0 {
|
||||
mod = mod.Where(dao.SysNoticeUser.Columns().NoticeId, in.NoticeId)
|
||||
}
|
||||
|
||||
if in.NoticeTitle != "" {
|
||||
mod = mod.WhereLike(dao.SysNoticeUser.Columns().NoticeTitle, "%"+in.NoticeTitle+"%")
|
||||
}
|
||||
|
||||
if in.NoticeType != "" {
|
||||
mod = mod.Where(dao.SysNoticeUser.Columns().NoticeType, in.NoticeType)
|
||||
}
|
||||
// 分页
|
||||
mod = mod.Page(in.Page, in.PageSize)
|
||||
|
||||
// 排序
|
||||
mod = mod.OrderDesc(dao.SysNoticeUser.Columns().Id)
|
||||
|
||||
// 操作人摘要信息
|
||||
mod = mod.Hook(hook.MemberSummary)
|
||||
|
||||
// 查询数据
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取用户通知公告表列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出用户通知公告表
|
||||
func (s *sSysNoticeUser) Export(ctx context.Context, in *model.SysNoticeUserListParam) (err error) {
|
||||
list, totalCount, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 字段的排序是依据tags的字段顺序,如果你不想使用默认的排序方式,可以直接定义 tags = []string{"字段名称", "字段名称2", ...}
|
||||
tags, err := convert.GetEntityDescTags(model.SysNoticeUserExportModel{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
fileName = "导出用户通知公告表-" + gctx.CtxId(ctx)
|
||||
sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, (totalCount+in.PageSize-1)/in.PageSize, in.Page, len(list))
|
||||
exports []model.SysNoticeUserExportModel
|
||||
)
|
||||
|
||||
if err = gconv.Scan(list, &exports); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 修改/新增用户通知公告表
|
||||
func (s *sSysNoticeUser) Edit(ctx context.Context, in *model.SysNoticeUserEditParam) (err error) {
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
|
||||
// 修改
|
||||
if in.Id > 0 {
|
||||
in.UpdatedBy = contexts.GetUserId(ctx)
|
||||
in.UpdatedAt = gtime.Now()
|
||||
if _, err = s.Model(ctx).
|
||||
Fields(model.SysNoticeUserUpdateFields{}).
|
||||
WherePri(in.Id).Data(in).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "修改用户通知公告表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 新增
|
||||
in.TenantId = contexts.GetTenantId(ctx)
|
||||
in.CreatedDept = contexts.GetDeptId(ctx)
|
||||
in.CreatedBy = contexts.GetUserId(ctx)
|
||||
in.CreatedAt = gtime.Now()
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
|
||||
Fields(model.SysNoticeUserInsertFields{}).
|
||||
Data(in).Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增用户通知公告表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Delete 删除用户通知公告表
|
||||
func (s *sSysNoticeUser) Delete(ctx context.Context, in *model.SysNoticeUserDeleteParam) (err error) {
|
||||
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
|
||||
err = gerror.Wrap(err, "删除用户通知公告表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取用户通知公告表指定信息
|
||||
func (s *sSysNoticeUser) View(ctx context.Context, in *model.SysNoticeUserViewParam) (res *model.SysNoticeUserViewModel, err error) {
|
||||
if err = s.Model(ctx).WherePri(in.Id).Hook(hook.MemberSummary).Scan(&res); err != nil {
|
||||
err = gerror.Wrap(err, "获取用户通知公告表信息,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新用户通知公告表状态
|
||||
func (s *sSysNoticeUser) Status(ctx context.Context, in *model.SysNoticeUserStatusParam) (err error) {
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{
|
||||
dao.SysNoticeUser.Columns().Status: in.Status,
|
||||
dao.SysNoticeUser.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
}).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "更新用户通知公告表状态失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Read 已读
|
||||
func (s *sSysNoticeUser) Read(ctx context.Context, in *model.SysNoticeUserReadParam) (err error) {
|
||||
|
||||
if _, err = s.Model(ctx).WherePri(in.Ids).Data(g.Map{
|
||||
dao.SysNoticeUser.Columns().IsRead: 1,
|
||||
dao.SysNoticeUser.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
dao.SysNoticeUser.Columns().UpdatedAt: gtime.Now(),
|
||||
}).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "更新用户通知公告表状态失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/mcache"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysOperLog struct {
|
||||
}
|
||||
|
||||
func NewSysOperLog() *sSysOperLog {
|
||||
return &sSysOperLog{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysOperLog(NewSysOperLog())
|
||||
}
|
||||
|
||||
func (s *sSysOperLog) GetOperLogList(ctx context.Context, query *model.SysOperLogListParam, page *request.PageInfo) (items []*model.SysOperLogListModel, total int, err error) {
|
||||
// 获取当前用户租户编码
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
|
||||
db := dao.SysOperLog.Ctx(ctx).Where(dao.SysOperLog.Columns().TenantId, tenantId)
|
||||
|
||||
if query.Title != "" {
|
||||
db = db.WhereLike(dao.SysOperLog.Columns().Title, "%"+query.Title+"%")
|
||||
}
|
||||
|
||||
if query.BusinessType != "" {
|
||||
db = db.Where(dao.SysOperLog.Columns().BusinessType, query.BusinessType)
|
||||
}
|
||||
|
||||
if query.Method != "" {
|
||||
db = db.WhereLike(dao.SysOperLog.Columns().Method, "%"+query.Method+"%")
|
||||
}
|
||||
|
||||
total, err = db.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = db.Page(page.Page, page.PageSize).OrderDesc(dao.SysOperLog.Columns().OperId).Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
func (s *sSysOperLog) AnalysisLog(ctx context.Context) (data *model.SysOperLogAddParam, err error) {
|
||||
ctxData := contexts.Get(ctx)
|
||||
request := ghttp.RequestFromCtx(ctx)
|
||||
handlerResponse := request.GetHandlerResponse() // 响应结果
|
||||
param := request.GetMap() // 请求参数
|
||||
path := request.URL.Path
|
||||
|
||||
res := gconv.Map(handlerResponse)
|
||||
deptName := ""
|
||||
operName := ""
|
||||
businessType := 0
|
||||
operatorType := 0
|
||||
status := 0
|
||||
costTime := gtime.Now().Sub(gtime.New(request.EnterTime)).Milliseconds()
|
||||
message := ""
|
||||
if ctxData != nil && ctxData.User != nil {
|
||||
deptName, _ = mcache.GetDeptName(ctx, ctxData.User.DeptId)
|
||||
operName = ctxData.User.Username
|
||||
}
|
||||
jsonResult := ""
|
||||
var code gcode.Code = gcode.CodeOK
|
||||
if erro := request.GetError(); erro != nil {
|
||||
status = 1
|
||||
code = gerror.Code(erro)
|
||||
message = gjson.New(g.Map{
|
||||
"code": code,
|
||||
"message": erro.Error(),
|
||||
}).String()
|
||||
} else {
|
||||
status = 0
|
||||
if b, err := gjson.Encode(res); err == nil {
|
||||
if len(b) > 1024 {
|
||||
jsonResult = "数据过大,未记录"
|
||||
} else {
|
||||
jsonResult = string(b)
|
||||
}
|
||||
}
|
||||
}
|
||||
if strings.Contains(path, "/add") {
|
||||
businessType = 1
|
||||
}
|
||||
if strings.Contains(path, "/edit") {
|
||||
businessType = 2
|
||||
}
|
||||
if strings.Contains(path, "/delete") {
|
||||
businessType = 3
|
||||
}
|
||||
url := request.URL.String()
|
||||
if len(url) > 100 {
|
||||
url = url[:100] + "..."
|
||||
}
|
||||
operParam := gconv.String(param)
|
||||
if len(operParam) > 1024 {
|
||||
operParam = "数据过大,未记录"
|
||||
}
|
||||
data = &model.SysOperLogAddParam{
|
||||
TenantId: contexts.GetTenantId(ctx),
|
||||
Title: "",
|
||||
BusinessType: businessType,
|
||||
Method: request.URL.Path,
|
||||
RequestMethod: request.Method,
|
||||
OperatorType: operatorType,
|
||||
OperName: operName,
|
||||
DeptName: deptName,
|
||||
OperUrl: url,
|
||||
OperIp: utility.GetClientIp(ctx),
|
||||
OperLocation: utility.GetCityByIp(utility.GetClientIp(ctx)),
|
||||
OperParam: operParam,
|
||||
JsonResult: jsonResult,
|
||||
Status: status,
|
||||
ErrorMsg: message,
|
||||
OperTime: gtime.Now(),
|
||||
CostTime: costTime,
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (s *sSysOperLog) ClearOperationLogByDays(ctx context.Context, days int) error {
|
||||
_, err := dao.SysOperLog.Ctx(ctx).Delete("to_days(now())-to_days(`oper_time`) > ?", days+1)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *sSysOperLog) RealWrite(ctx context.Context, data entity.SysOperLog) (err error) {
|
||||
_, err = dao.SysOperLog.Ctx(ctx).Data(data).Save()
|
||||
if err != nil {
|
||||
g.Log().Error(ctx, "sSysOperLog.RealWrite error:%v,data:%+v", err, data)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,981 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/hash"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/uuid32"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/crypto/gmd5"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/net/ghttp"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysOss struct {
|
||||
}
|
||||
|
||||
func NewSysOss() *sSysOss {
|
||||
return &sSysOss{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysOss(NewSysOss())
|
||||
}
|
||||
|
||||
func (s *sSysOss) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysOss.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (s *sSysOss) List(ctx context.Context, param *model.SysOssListParam, pageInfo *request.PageInfo) (items []*model.SysOssListModel, total int, err error) {
|
||||
|
||||
db := s.Model(ctx)
|
||||
|
||||
if param.FileName != "" {
|
||||
db = db.WhereLike(dao.SysOss.Columns().FileName, "%"+param.FileName+"%")
|
||||
}
|
||||
|
||||
if param.OriginalName != "" {
|
||||
db = db.WhereLike(dao.SysOss.Columns().OriginalName, "%"+param.OriginalName+"%")
|
||||
}
|
||||
|
||||
if param.FileSuffix != "" {
|
||||
db = db.Where(dao.SysOss.Columns().FileSuffix, param.FileSuffix)
|
||||
}
|
||||
|
||||
if param.Service != "" {
|
||||
db = db.Where(dao.SysOss.Columns().Service, param.Service)
|
||||
}
|
||||
|
||||
if len(param.CreatedAt) == 2 {
|
||||
startTime, err := gtime.StrToTime(param.CreatedAt[0])
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
endTime, err := gtime.StrToTime(param.CreatedAt[1])
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
db = db.WhereBetween(dao.SysOss.Columns().CreatedAt, startTime, endTime.EndOfDay())
|
||||
}
|
||||
|
||||
total, err = db.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = db.Page(pageInfo.Page, pageInfo.PageSize).Order(dao.SysOss.Columns().OssId, "DESC").Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) View(ctx context.Context, param *model.SysOssViewParam) (output *model.SysOssViewModel, err error) {
|
||||
if param.OssId == 0 {
|
||||
return nil, errors.New("未选择文件")
|
||||
}
|
||||
db := s.Model(ctx).Where(dao.SysOss.Columns().OssId, param.OssId)
|
||||
err = db.Scan(&output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) Download(ctx context.Context, param *model.SysOssDownloadParam) (output *model.SysOssDownloadModel, err error) {
|
||||
if param.OssId == 0 {
|
||||
return nil, errors.New("未选择文件")
|
||||
}
|
||||
data := &entity.SysOss{}
|
||||
db := s.Model(ctx).Where(dao.SysOss.Columns().OssId, param.OssId)
|
||||
err = db.Scan(&data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if data.OssId == 0 {
|
||||
return nil, errors.New("文件不存在")
|
||||
}
|
||||
if data.Service != "local" {
|
||||
err = gerror.New("非本地文件")
|
||||
return
|
||||
}
|
||||
output = &model.SysOssDownloadModel{
|
||||
SysOss: data,
|
||||
}
|
||||
err = s.DownloadLocal(ctx, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) Delete(ctx context.Context, param *model.SysOssDeleteParam) (output *model.SysOssDeleteModel, err error) {
|
||||
if len(param.OssIds) == 0 {
|
||||
return nil, errors.New("未选择文件")
|
||||
}
|
||||
items := make([]*entity.SysOss, 0)
|
||||
err = s.Model(ctx).WhereIn(dao.SysOss.Columns().OssId, param.OssIds).Scan(&items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return nil, errors.New("文件不存在")
|
||||
}
|
||||
_, err = s.Model(ctx).WhereIn(dao.SysOss.Columns().OssId, param.OssIds).Delete()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range items {
|
||||
if item.Service == "local" {
|
||||
s.DeleteLocal(ctx, item)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) Upload(ctx context.Context, param *model.SysOssUploadParam) (output *model.SysOssUploadModel, err error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
file := r.GetUploadFile("file")
|
||||
if file == nil {
|
||||
err = gerror.New("上传文件必须")
|
||||
return
|
||||
}
|
||||
// 检查文件类型
|
||||
err = s.CheckType(ctx, param.FileType, file)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件大小
|
||||
err = s.CheckSize(ctx, param.FileType, file)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO:待实现其他云存储
|
||||
if param.NewFileType == 0 {
|
||||
if param.IsDevice {
|
||||
param.NewFileType = consts.SysUploadNewFileTypeDeviceFile
|
||||
} else if param.FileType == consts.SysUploadFileTypeImg {
|
||||
param.NewFileType = consts.SysUploadNewFileTypeUserImg
|
||||
} else {
|
||||
param.NewFileType = consts.SysUploadNewFileTypeUserFile
|
||||
}
|
||||
}
|
||||
result, err := s.UploadLocal(ctx, file, param.NewFileType, param.SaveOriginalName, param.SubDirName, param.NotAddDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := s.Model(ctx).Data(g.Map{
|
||||
dao.SysOss.Columns().TenantId: contexts.GetTenantId(ctx),
|
||||
dao.SysOss.Columns().FileName: result.Name,
|
||||
dao.SysOss.Columns().OriginalName: result.OriginalName,
|
||||
dao.SysOss.Columns().FileSuffix: result.Ext,
|
||||
dao.SysOss.Columns().Path: result.Path,
|
||||
dao.SysOss.Columns().Url: result.FullPath,
|
||||
dao.SysOss.Columns().CreatedDept: contexts.GetDeptId(ctx),
|
||||
dao.SysOss.Columns().CreatedAt: gtime.Now(),
|
||||
dao.SysOss.Columns().CreatedBy: contexts.GetUserId(ctx),
|
||||
dao.SysOss.Columns().UpdatedAt: gtime.Now(),
|
||||
dao.SysOss.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
dao.SysOss.Columns().Service: "local",
|
||||
dao.SysOss.Columns().Md5: result.Md5,
|
||||
dao.SysOss.Columns().FileSize: result.Size,
|
||||
dao.SysOss.Columns().FileCrc16: result.Crc16,
|
||||
dao.SysOss.Columns().FileSum: result.Sum16,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysOssUploadModel{}
|
||||
err = s.Model(ctx).Where(dao.SysOss.Columns().OssId, id).Scan(&output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) UploadWithBizName(ctx context.Context, param *model.SysOssUploadParam) (output *model.SysOssUploadModel, err error) {
|
||||
r := g.RequestFromCtx(ctx)
|
||||
file := r.GetUploadFile("file")
|
||||
if file == nil {
|
||||
err = gerror.New("上传文件必须")
|
||||
return
|
||||
}
|
||||
if err = s.CheckType(ctx, param.FileType, file); err != nil {
|
||||
return
|
||||
}
|
||||
if err = s.CheckSize(ctx, param.FileType, file); err != nil {
|
||||
return
|
||||
}
|
||||
if param.NewFileType == 0 {
|
||||
if param.IsDevice {
|
||||
param.NewFileType = consts.SysUploadNewFileTypeDeviceFile
|
||||
} else if param.FileType == consts.SysUploadFileTypeImg {
|
||||
param.NewFileType = consts.SysUploadNewFileTypeUserImg
|
||||
} else {
|
||||
param.NewFileType = consts.SysUploadNewFileTypeUserFile
|
||||
}
|
||||
}
|
||||
|
||||
var result model.UploadResponse
|
||||
for i := 0; i < 3; i++ {
|
||||
fileName := makeBizUploadFileName(file.Filename)
|
||||
result, err = s.UploadLocalWithFileName(ctx, file, param.NewFileType, fileName, param.SubDirName, param.NotAddDate)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if !errors.Is(err, os.ErrExist) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := s.Model(ctx).Data(g.Map{
|
||||
dao.SysOss.Columns().TenantId: contexts.GetTenantId(ctx),
|
||||
dao.SysOss.Columns().FileName: result.Name,
|
||||
dao.SysOss.Columns().OriginalName: result.OriginalName,
|
||||
dao.SysOss.Columns().FileSuffix: result.Ext,
|
||||
dao.SysOss.Columns().Path: result.Path,
|
||||
dao.SysOss.Columns().Url: result.FullPath,
|
||||
dao.SysOss.Columns().CreatedDept: contexts.GetDeptId(ctx),
|
||||
dao.SysOss.Columns().CreatedAt: gtime.Now(),
|
||||
dao.SysOss.Columns().CreatedBy: contexts.GetUserId(ctx),
|
||||
dao.SysOss.Columns().UpdatedAt: gtime.Now(),
|
||||
dao.SysOss.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
dao.SysOss.Columns().Service: "local",
|
||||
dao.SysOss.Columns().Md5: result.Md5,
|
||||
dao.SysOss.Columns().FileSize: result.Size,
|
||||
dao.SysOss.Columns().FileCrc16: result.Crc16,
|
||||
dao.SysOss.Columns().FileSum: result.Sum16,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysOssUploadModel{}
|
||||
err = s.Model(ctx).Where(dao.SysOss.Columns().OssId, id).Scan(&output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) MoveFile(ctx context.Context, param *model.SysOssMoveFileParam) (output *model.SysOssMoveFileModel, err error) {
|
||||
result, err := s.MoveFileLocal(ctx, param.FilePath, param.NewFileType, param.SaveOriginalName, param.SubDirName, param.NotAddDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := s.Model(ctx).Data(g.Map{
|
||||
dao.SysOss.Columns().TenantId: contexts.GetTenantId(ctx),
|
||||
dao.SysOss.Columns().FileName: result.Name,
|
||||
dao.SysOss.Columns().OriginalName: result.OriginalName,
|
||||
dao.SysOss.Columns().FileSuffix: result.Ext,
|
||||
dao.SysOss.Columns().Path: result.Path,
|
||||
dao.SysOss.Columns().Url: result.FullPath,
|
||||
dao.SysOss.Columns().CreatedDept: contexts.GetDeptId(ctx),
|
||||
dao.SysOss.Columns().CreatedAt: gtime.Now(),
|
||||
dao.SysOss.Columns().CreatedBy: contexts.GetUserId(ctx),
|
||||
dao.SysOss.Columns().UpdatedAt: gtime.Now(),
|
||||
dao.SysOss.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
dao.SysOss.Columns().Service: "local",
|
||||
dao.SysOss.Columns().Md5: result.Md5,
|
||||
dao.SysOss.Columns().FileSize: result.Size,
|
||||
dao.SysOss.Columns().FileCrc16: result.Crc16,
|
||||
dao.SysOss.Columns().FileSum: result.Sum16,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oss := &entity.SysOss{}
|
||||
err = s.Model(ctx).Where(dao.SysOss.Columns().OssId, id).Scan(&oss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysOssMoveFileModel{
|
||||
SysOss: oss,
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (s *sSysOss) SaveContent(ctx context.Context, param *model.SysOssSaveContentParam) (output *model.SysOssSaveContentModel, err error) {
|
||||
result, err := s.SaveContentLocal(ctx, param.Content, param.NewFileType, param.FileName, param.SubDirName, param.NotAddDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id, err := s.Model(ctx).Data(g.Map{
|
||||
dao.SysOss.Columns().TenantId: contexts.GetTenantId(ctx),
|
||||
dao.SysOss.Columns().FileName: result.Name,
|
||||
dao.SysOss.Columns().OriginalName: result.OriginalName,
|
||||
dao.SysOss.Columns().FileSuffix: result.Ext,
|
||||
dao.SysOss.Columns().Path: result.Path,
|
||||
dao.SysOss.Columns().Url: result.FullPath,
|
||||
dao.SysOss.Columns().CreatedDept: contexts.GetDeptId(ctx),
|
||||
dao.SysOss.Columns().CreatedAt: gtime.Now(),
|
||||
dao.SysOss.Columns().CreatedBy: contexts.GetUserId(ctx),
|
||||
dao.SysOss.Columns().UpdatedAt: gtime.Now(),
|
||||
dao.SysOss.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
dao.SysOss.Columns().Service: "local",
|
||||
dao.SysOss.Columns().Md5: result.Md5,
|
||||
dao.SysOss.Columns().FileSize: result.Size,
|
||||
dao.SysOss.Columns().FileCrc16: result.Crc16,
|
||||
dao.SysOss.Columns().FileSum: result.Sum16,
|
||||
}).InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oss := &entity.SysOss{}
|
||||
err = s.Model(ctx).Where(dao.SysOss.Columns().OssId, id).Scan(&oss)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysOssSaveContentModel{
|
||||
SysOss: oss,
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (s *sSysOss) getNewFileTypePath(ctx context.Context, newFileType int) string {
|
||||
switch newFileType {
|
||||
case consts.SysUploadNewFileTypeUserFile:
|
||||
return consts.SysUploadNewFileTypePathUserFile
|
||||
case consts.SysUploadNewFileTypeUserImg:
|
||||
return consts.SysUploadNewFileTypePathUserImg
|
||||
case consts.SysUploadNewFileTypeDeviceFile:
|
||||
return consts.SysUploadNewFileTypePathDeviceFile
|
||||
default:
|
||||
key := fmt.Sprintf(consts.ConfigOssNewFileTypePath, newFileType)
|
||||
config, err := service.SysConfig().GetConfigByKey(ctx, key)
|
||||
if err == nil && config != nil {
|
||||
return config.ConfigValue
|
||||
}
|
||||
}
|
||||
return "other"
|
||||
}
|
||||
|
||||
func (s *sSysOss) GetSaveFilePathConfig(ctx context.Context, newFileType int, notAddDate int) (string, error) {
|
||||
uploadPath := g.Cfg().MustGet(ctx, "system.upload.local.path").String()
|
||||
if uploadPath == "" {
|
||||
return "", errors.New("上传路径未配置")
|
||||
}
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
if tenantId == "" {
|
||||
tenantId = "default"
|
||||
}
|
||||
fileTypePath := s.getNewFileTypePath(ctx, newFileType)
|
||||
fullDirPath := uploadPath + "/" + tenantId + "/" + fileTypePath
|
||||
if notAddDate == 0 {
|
||||
nowData := time.Now().Format("20060102")
|
||||
fullDirPath = fullDirPath + "/" + nowData
|
||||
}
|
||||
return fullDirPath, nil
|
||||
}
|
||||
|
||||
func (s *sSysOss) MoveFileLocal(ctx context.Context, filePath string, newFileType int, useOriginalName int, subDirName string, isAddDate int) (result model.UploadResponse, err error) {
|
||||
if filePath == "" {
|
||||
err = errors.New("文件路径必须")
|
||||
return
|
||||
}
|
||||
fileName := gfile.Basename(filePath)
|
||||
fullDirPath, err := s.GetSaveFilePathConfig(ctx, newFileType, isAddDate)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if subDirName != "" {
|
||||
fullDirPath = fullDirPath + "/" + subDirName
|
||||
}
|
||||
if !gfile.Exists(fullDirPath) {
|
||||
err = gfile.Mkdir(fullDirPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = gfile.Move(filePath, fullDirPath)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysOss MoveFileLocal move file error: %+v, filePath: %s, fullDirPath: %s, newFileType: %d, useOriginalName: %d, subDirName: %s", err, filePath, fullDirPath, newFileType, useOriginalName, subDirName)
|
||||
return
|
||||
}
|
||||
fullFilePath := fullDirPath + "/" + fileName
|
||||
url := localUploadURL(ctx, fullFilePath)
|
||||
md5, _ := gmd5.EncryptFile(fullFilePath)
|
||||
crc16 := hash.Crc16ChecksumFile(fullFilePath)
|
||||
sum16 := hash.Sum16ChecksumFile(fullFilePath)
|
||||
result = model.UploadResponse{
|
||||
Size: gfile.Size(fullFilePath),
|
||||
Path: fullFilePath,
|
||||
FullPath: url,
|
||||
OriginalName: fileName,
|
||||
Name: fileName,
|
||||
Type: gfile.ExtName(fileName),
|
||||
Ext: gfile.ExtName(fileName),
|
||||
Md5: md5,
|
||||
Crc16: crc16,
|
||||
Sum16: sum16,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// 保存内容
|
||||
func (s *sSysOss) SaveContentLocal(ctx context.Context, content []byte, newFileType int, fileName string, subDirName string, isAddDate int) (result model.UploadResponse, err error) {
|
||||
fullDirPath, err := s.GetSaveFilePathConfig(ctx, newFileType, isAddDate)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if subDirName != "" {
|
||||
fullDirPath = fullDirPath + "/" + subDirName
|
||||
}
|
||||
if !gfile.Exists(fullDirPath) {
|
||||
err = gfile.Mkdir(fullDirPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
fullFilePath := fullDirPath + "/" + fileName
|
||||
err = gfile.PutBytes(fullFilePath, content)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
url := localUploadURL(ctx, fullFilePath)
|
||||
md5, _ := gmd5.EncryptFile(fullFilePath)
|
||||
crc16 := hash.Crc16ChecksumFile(fullFilePath)
|
||||
sum16 := hash.Sum16ChecksumFile(fullFilePath)
|
||||
result = model.UploadResponse{
|
||||
Size: gfile.Size(fullFilePath),
|
||||
Path: fullFilePath,
|
||||
FullPath: url,
|
||||
OriginalName: fileName,
|
||||
Name: fileName,
|
||||
Type: "application/json",
|
||||
Ext: gfile.ExtName(fileName),
|
||||
Md5: md5,
|
||||
Crc16: crc16,
|
||||
Sum16: sum16,
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *sSysOss) UploadLocal(ctx context.Context, file *ghttp.UploadFile, newFileType int, useOriginalName int, subDirName string, notAddDate int) (result model.UploadResponse, err error) {
|
||||
if file == nil {
|
||||
err = errors.New("文件必须")
|
||||
return
|
||||
}
|
||||
|
||||
fullDirPath, err := s.GetSaveFilePathConfig(ctx, newFileType, notAddDate)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if subDirName != "" {
|
||||
fullDirPath = fullDirPath + "/" + subDirName
|
||||
}
|
||||
if !gfile.Exists(fullDirPath) {
|
||||
err = gfile.Mkdir(fullDirPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
fileName := gfile.Basename(file.Filename)
|
||||
ext := gfile.Ext(file.Filename)
|
||||
fileNewName, err := file.Save(fullDirPath, useOriginalName == 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fullFilePath := fullDirPath + "/" + fileNewName
|
||||
url := localUploadURL(ctx, fullFilePath)
|
||||
md5, _ := gmd5.EncryptFile(fullFilePath)
|
||||
crc16 := hash.Crc16ChecksumFile(fullFilePath)
|
||||
sum16 := hash.Sum16ChecksumFile(fullFilePath)
|
||||
result = model.UploadResponse{
|
||||
Size: file.Size,
|
||||
Path: fullFilePath,
|
||||
FullPath: url,
|
||||
OriginalName: fileName,
|
||||
Name: fileNewName,
|
||||
Type: file.Header.Get("Content-type"),
|
||||
Ext: ext,
|
||||
Md5: md5,
|
||||
Crc16: crc16,
|
||||
Sum16: sum16,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) UploadLocalWithFileName(ctx context.Context, file *ghttp.UploadFile, newFileType int, fileName string, subDirName string, notAddDate int) (result model.UploadResponse, err error) {
|
||||
if file == nil {
|
||||
err = errors.New("文件必须")
|
||||
return
|
||||
}
|
||||
fileName = safeUploadFileName(fileName)
|
||||
fullDirPath, err := s.GetSaveFilePathConfig(ctx, newFileType, notAddDate)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if subDirName != "" {
|
||||
fullDirPath = fullDirPath + "/" + subDirName
|
||||
}
|
||||
if !gfile.Exists(fullDirPath) {
|
||||
err = gfile.Mkdir(fullDirPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = saveUploadFileToPath(file, fullDirPath, fileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
fullFilePath := fullDirPath + "/" + fileName
|
||||
url := localUploadURL(ctx, fullFilePath)
|
||||
md5, _ := gmd5.EncryptFile(fullFilePath)
|
||||
crc16 := hash.Crc16ChecksumFile(fullFilePath)
|
||||
sum16 := hash.Sum16ChecksumFile(fullFilePath)
|
||||
result = model.UploadResponse{
|
||||
Size: file.Size,
|
||||
Path: fullFilePath,
|
||||
FullPath: url,
|
||||
OriginalName: gfile.Basename(file.Filename),
|
||||
Name: fileName,
|
||||
Type: file.Header.Get("Content-type"),
|
||||
Ext: gfile.Ext(fileName),
|
||||
Md5: md5,
|
||||
Crc16: crc16,
|
||||
Sum16: sum16,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func saveUploadFileToPath(file *ghttp.UploadFile, dirPath string, fileName string) error {
|
||||
source, err := file.Open()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "open upload file failed")
|
||||
}
|
||||
defer source.Close()
|
||||
|
||||
filePath := gfile.Join(dirPath, fileName)
|
||||
target, err := gfile.OpenWithFlagPerm(filePath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer target.Close()
|
||||
|
||||
if _, err = io.Copy(target, source); err != nil {
|
||||
return gerror.Wrapf(err, `copy upload file failed from "%s" to "%s"`, file.Filename, filePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func safeUploadFileName(fileName string) string {
|
||||
fileName = strings.TrimSpace(gfile.Basename(fileName))
|
||||
if fileName == "" {
|
||||
fileName = "file"
|
||||
}
|
||||
for _, old := range []string{"\\", "/", ":", "*", "?", "\"", "<", ">", "|"} {
|
||||
fileName = strings.ReplaceAll(fileName, old, "_")
|
||||
}
|
||||
return fileName
|
||||
}
|
||||
|
||||
func makeBizUploadFileName(fileName string) string {
|
||||
fileName = safeUploadFileName(fileName)
|
||||
ext := gfile.Ext(fileName)
|
||||
baseName := strings.TrimSuffix(fileName, ext)
|
||||
if baseName == "" {
|
||||
baseName = "file"
|
||||
}
|
||||
uid := uuid32.S()
|
||||
if len(uid) > 8 {
|
||||
uid = uid[:8]
|
||||
}
|
||||
return fmt.Sprintf("%s_%s_%s.xlsx", time.Now().Format("20060102150405"), baseName, uid)
|
||||
}
|
||||
|
||||
func localUploadURL(ctx context.Context, filePath string) string {
|
||||
filePath = strings.TrimSpace(filepath.ToSlash(filepath.Clean(filePath)))
|
||||
if filePath == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if configType, err := service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssUrlPath); err == nil && configType != nil {
|
||||
configValue := strings.TrimSpace(configType.ConfigValue)
|
||||
if configValue != "" {
|
||||
if strings.Contains(configValue, "|") {
|
||||
split := strings.SplitN(configValue, "|", 2)
|
||||
from := strings.TrimSpace(filepath.ToSlash(split[0]))
|
||||
to := strings.TrimSpace(filepath.ToSlash(split[1]))
|
||||
if suffix, ok := localUploadMappedSuffix(filePath, from); ok {
|
||||
return joinUploadURL(to, suffix)
|
||||
}
|
||||
} else {
|
||||
if suffix := localUploadRelativePath(filePath); suffix != "" {
|
||||
return joinUploadURL(configValue, suffix)
|
||||
}
|
||||
return joinUploadURL(configValue, filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if suffix := localUploadRelativePath(filePath); suffix != "" {
|
||||
return joinUploadURL("/upload", suffix)
|
||||
}
|
||||
return filePath
|
||||
}
|
||||
|
||||
func localUploadMappedSuffix(filePath string, from string) (string, bool) {
|
||||
from = strings.Trim(strings.TrimSpace(filepath.ToSlash(from)), "/")
|
||||
if from == "" {
|
||||
return "", false
|
||||
}
|
||||
normalized := strings.TrimSpace(filepath.ToSlash(filePath))
|
||||
marker := "/" + from + "/"
|
||||
if idx := strings.Index(normalized, marker); idx >= 0 {
|
||||
return strings.TrimPrefix(normalized[idx+len(marker):], "/"), true
|
||||
}
|
||||
prefix := from + "/"
|
||||
if strings.HasPrefix(normalized, prefix) {
|
||||
return strings.TrimPrefix(normalized[len(prefix):], "/"), true
|
||||
}
|
||||
if strings.Trim(normalized, "/") == from {
|
||||
return "", true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func joinUploadURL(base string, suffix string) string {
|
||||
base = strings.TrimRight(strings.TrimSpace(filepath.ToSlash(base)), "/")
|
||||
suffix = strings.TrimLeft(strings.TrimSpace(filepath.ToSlash(suffix)), "/")
|
||||
if base == "" {
|
||||
base = "/upload"
|
||||
}
|
||||
if suffix == "" {
|
||||
return base
|
||||
}
|
||||
return base + "/" + suffix
|
||||
}
|
||||
|
||||
func (s *sSysOss) DownloadLocal(ctx context.Context, file *entity.SysOss) (err error) {
|
||||
if file == nil {
|
||||
err = errors.New("文件必须")
|
||||
return
|
||||
}
|
||||
finalPath, err := resolveLocalOssPath(ctx, file.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := ghttp.RequestFromCtx(ctx)
|
||||
if r == nil {
|
||||
err = gerror.New("ctx not http request")
|
||||
return
|
||||
}
|
||||
r.Response.ServeFileDownload(finalPath)
|
||||
r.Response.Status = http.StatusOK
|
||||
r.Response.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
func resolveLocalOssPath(ctx context.Context, storedPath string) (string, error) {
|
||||
storedPath = strings.TrimSpace(storedPath)
|
||||
if storedPath == "" {
|
||||
return "", errors.New("文件路径为空")
|
||||
}
|
||||
|
||||
candidates := localOssPathCandidates(ctx, storedPath)
|
||||
seen := make(map[string]struct{}, len(candidates))
|
||||
tried := make([]string, 0, len(candidates))
|
||||
for _, candidate := range candidates {
|
||||
candidate = strings.TrimSpace(candidate)
|
||||
if candidate == "" {
|
||||
continue
|
||||
}
|
||||
candidate = filepath.Clean(candidate)
|
||||
if _, ok := seen[candidate]; ok {
|
||||
continue
|
||||
}
|
||||
seen[candidate] = struct{}{}
|
||||
tried = append(tried, candidate)
|
||||
if info, statErr := os.Stat(candidate); statErr == nil && !info.IsDir() {
|
||||
return candidate, nil
|
||||
}
|
||||
}
|
||||
return "", gerror.Newf("本地文件不存在,请检查Web服务和Worker是否共用上传目录,原始路径: %s,尝试路径: %s", storedPath, strings.Join(tried, "; "))
|
||||
}
|
||||
|
||||
func localOssPathCandidates(ctx context.Context, storedPath string) []string {
|
||||
candidates := []string{storedPath}
|
||||
if !filepath.IsAbs(storedPath) {
|
||||
if absPath, err := filepath.Abs(storedPath); err == nil {
|
||||
candidates = append(candidates, absPath)
|
||||
}
|
||||
}
|
||||
|
||||
uploadPath := strings.TrimSpace(g.Cfg().MustGet(ctx, "system.upload.local.path").String())
|
||||
if uploadPath == "" {
|
||||
return candidates
|
||||
}
|
||||
uploadRoots := localOssUploadRootCandidates(uploadPath)
|
||||
suffixes := localOssRelativeSuffixes(storedPath)
|
||||
for _, root := range uploadRoots {
|
||||
if !filepath.IsAbs(storedPath) {
|
||||
candidates = append(candidates, filepath.Join(root, storedPath))
|
||||
}
|
||||
for _, suffix := range suffixes {
|
||||
candidates = append(candidates, filepath.Join(root, suffix))
|
||||
}
|
||||
}
|
||||
return candidates
|
||||
}
|
||||
|
||||
func localOssUploadRootCandidates(uploadPath string) []string {
|
||||
roots := make([]string, 0, 4)
|
||||
for _, root := range []string{
|
||||
os.Getenv("IOTLCM_SERVER_UPLOAD_ROOT"),
|
||||
os.Getenv("IOTLCM_UPLOAD_ROOT"),
|
||||
uploadPath,
|
||||
} {
|
||||
root = strings.TrimSpace(root)
|
||||
if root == "" {
|
||||
continue
|
||||
}
|
||||
roots = appendLocalOssUploadRootCandidate(roots, root)
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
func appendLocalOssUploadRootCandidate(roots []string, root string) []string {
|
||||
for _, candidate := range localOssEquivalentUploadRoots(root) {
|
||||
if filepath.IsAbs(candidate) {
|
||||
roots = appendUniqueLocalOssPath(roots, candidate)
|
||||
continue
|
||||
}
|
||||
if absPath, err := filepath.Abs(candidate); err == nil {
|
||||
roots = appendUniqueLocalOssPath(roots, absPath)
|
||||
}
|
||||
roots = appendUniqueLocalOssPath(roots, candidate)
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
func localOssEquivalentUploadRoots(root string) []string {
|
||||
root = strings.TrimSpace(root)
|
||||
if root == "" {
|
||||
return nil
|
||||
}
|
||||
roots := []string{root}
|
||||
normalized := filepath.ToSlash(filepath.Clean(root))
|
||||
if normalized == "resource/upload" || strings.HasSuffix(normalized, "/resource/upload") {
|
||||
prefix := strings.TrimSuffix(normalized, "resource/upload")
|
||||
roots = appendUniqueLocalOssPath(roots, filepath.FromSlash(prefix+"upload"))
|
||||
}
|
||||
return roots
|
||||
}
|
||||
|
||||
func appendUniqueLocalOssPath(values []string, value string) []string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return values
|
||||
}
|
||||
cleaned := filepath.Clean(value)
|
||||
for _, existing := range values {
|
||||
if filepath.Clean(strings.TrimSpace(existing)) == cleaned {
|
||||
return values
|
||||
}
|
||||
}
|
||||
return append(values, value)
|
||||
}
|
||||
|
||||
func localOssRelativeSuffixes(storedPath string) []string {
|
||||
normalized := filepath.ToSlash(filepath.Clean(strings.TrimSpace(storedPath)))
|
||||
markers := localUploadPathMarkers()
|
||||
suffixes := make([]string, 0, len(markers))
|
||||
for _, marker := range markers {
|
||||
if idx := strings.Index(normalized, marker); idx >= 0 {
|
||||
suffix := strings.TrimPrefix(normalized[idx+len(marker):], "/")
|
||||
if suffix != "" {
|
||||
suffixes = append(suffixes, filepath.FromSlash(suffix))
|
||||
}
|
||||
}
|
||||
}
|
||||
return suffixes
|
||||
}
|
||||
|
||||
func localUploadRelativePath(filePath string) string {
|
||||
normalized := filepath.ToSlash(filepath.Clean(strings.TrimSpace(filePath)))
|
||||
for _, marker := range localUploadPathMarkers() {
|
||||
if idx := strings.Index(normalized, marker); idx >= 0 {
|
||||
return strings.TrimPrefix(normalized[idx+len(marker):], "/")
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func localUploadPathMarkers() []string {
|
||||
return []string{
|
||||
"resource/upload/",
|
||||
"/resource/upload/",
|
||||
"/app/resource/upload/",
|
||||
"/app/upload/",
|
||||
"upload/",
|
||||
"/upload/",
|
||||
}
|
||||
}
|
||||
func (s *sSysOss) DeleteLocal(ctx context.Context, file *entity.SysOss) (err error) {
|
||||
if file == nil {
|
||||
err = errors.New("文件不存在")
|
||||
return
|
||||
}
|
||||
filePath := file.Path
|
||||
if !gfile.Exists(filePath) {
|
||||
err = errors.New("文件不存在")
|
||||
return
|
||||
}
|
||||
err = gfile.Remove(filePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) CheckType(ctx context.Context, checkFileType string, file *ghttp.UploadFile) (err error) {
|
||||
|
||||
var (
|
||||
configType *entity.SysConfig
|
||||
)
|
||||
|
||||
if checkFileType == consts.SysUploadFileTypeFile {
|
||||
//获取上传类型配置
|
||||
configType, err = service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssFileTypeKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
} else if checkFileType == consts.SysUploadFileTypeImg {
|
||||
//获取上传类型配置
|
||||
configType, err = service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssImgTypeKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("文件检查类型错误:%s|%s, 文件类型:%s", consts.SysUploadFileTypeFile, consts.SysUploadFileTypeImg, checkFileType)
|
||||
}
|
||||
ext := gfile.ExtName(file.Filename)
|
||||
ext = strings.ToLower(ext)
|
||||
imageType := gstr.Split(configType.ConfigValue, ",")
|
||||
rightType := false
|
||||
for _, v := range imageType {
|
||||
if v == ext {
|
||||
rightType = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !rightType {
|
||||
err = gerror.New("上传文件类型错误,只能包含后缀为:" + configType.ConfigValue + "的文件。")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) CheckSize(ctx context.Context, checkFileType string, file *ghttp.UploadFile) (err error) {
|
||||
|
||||
var (
|
||||
configType *entity.SysConfig
|
||||
)
|
||||
|
||||
if checkFileType == consts.SysUploadFileTypeFile {
|
||||
//获取上传类型配置
|
||||
configType, err = service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssFileSizeKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
} else if checkFileType == consts.SysUploadFileTypeImg {
|
||||
//获取上传类型配置
|
||||
configType, err = service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssImgSizeKey)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("文件检查类型错误:%s|%s", consts.SysUploadFileTypeFile, consts.SysUploadFileTypeImg)
|
||||
}
|
||||
match, err := gregex.MatchString(`^([0-9]+)(?i:([a-z]*))$`, configType.ConfigValue)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(match) == 0 {
|
||||
err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB)")
|
||||
return
|
||||
}
|
||||
var cfSize int64
|
||||
switch gstr.ToUpper(match[2]) {
|
||||
case "MB", "M":
|
||||
cfSize = gconv.Int64(match[1]) * 1024 * 1024
|
||||
case "KB", "K":
|
||||
cfSize = gconv.Int64(match[1]) * 1024
|
||||
case "":
|
||||
cfSize = gconv.Int64(match[1])
|
||||
}
|
||||
if cfSize == 0 {
|
||||
err = gerror.New("上传文件大小未设置,请在后台配置,格式为(30M,30k,30MB),最大单位为MB")
|
||||
return
|
||||
}
|
||||
if file.Size > cfSize {
|
||||
err = gerror.New("上传文件大小超过限制,最大为" + configType.ConfigValue)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) GetAllUrl(ctx context.Context, url string) (allUrl string, ossId int64, fileSize int64, OriginalName string, md5 string, hmac string, err error) {
|
||||
entity := &entity.SysOss{}
|
||||
s.Model(ctx).Where(dao.SysOss.Columns().Path, url).Scan(&entity)
|
||||
if entity.OssId <= 0 {
|
||||
return
|
||||
}
|
||||
ossId = entity.OssId
|
||||
ossConfig, err := service.SysOssConfig().View(ctx, &model.SysOssConfigViewParam{
|
||||
ConfigKey: entity.Service,
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysOss GetAllUrl error: %+v, url: %s", err, url)
|
||||
return "", 0, 0, "", "", "", err
|
||||
}
|
||||
if ossConfig.IsHttps == "Y" {
|
||||
allUrl = "https://" + ossConfig.Domain + entity.Path
|
||||
} else {
|
||||
allUrl = "http://" + ossConfig.Domain + entity.Path
|
||||
}
|
||||
fileSize = gfile.Size(entity.Path)
|
||||
OriginalName = entity.OriginalName
|
||||
md5, _ = gmd5.Encrypt(entity.Path)
|
||||
hmac = ""
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysOssConfig struct {
|
||||
}
|
||||
|
||||
func NewSysOssConfig() *sSysOssConfig {
|
||||
return &sSysOssConfig{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysOssConfig(NewSysOssConfig())
|
||||
}
|
||||
|
||||
func (s *sSysOssConfig) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysOssConfig.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (s *sSysOssConfig) List(ctx context.Context, param *model.SysOssConfigListParam) (items []*model.SysOssConfigListModel, total int, err error) {
|
||||
|
||||
db := s.Model(ctx)
|
||||
|
||||
if param.ConfigKey != "" {
|
||||
db = db.WhereLike(dao.SysOssConfig.Columns().ConfigKey, "%"+param.ConfigKey+"%")
|
||||
}
|
||||
|
||||
if param.BucketName != "" {
|
||||
db = db.WhereLike(dao.SysOssConfig.Columns().BucketName, "%"+param.BucketName+"%")
|
||||
}
|
||||
if param.Status != "" {
|
||||
db = db.Where(dao.SysOssConfig.Columns().Status, param.Status)
|
||||
}
|
||||
|
||||
total, err = db.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = db.Page(param.Page, param.PageSize).OrderAsc(dao.SysOssConfig.Columns().OssConfigId).Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOssConfig) View(ctx context.Context, param *model.SysOssConfigViewParam) (item *model.SysOssConfigViewModel, err error) {
|
||||
db := s.Model(ctx)
|
||||
if param.OssConfigId > 0 {
|
||||
db = db.Where(dao.SysOssConfig.Columns().OssConfigId, param.OssConfigId)
|
||||
} else if param.ConfigKey != "" {
|
||||
db = db.Where(dao.SysOssConfig.Columns().ConfigKey, param.ConfigKey)
|
||||
} else {
|
||||
g.Log().Errorf(ctx, "sSysOssConfig View 参数错误: %+v", param)
|
||||
return nil, gerror.New("参数错误")
|
||||
}
|
||||
err = db.Scan(&item)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOssConfig) Add(ctx context.Context, param *model.SysOssConfigAddParam) (item *model.SysOssConfigAddModel, err error) {
|
||||
db := s.Model(ctx)
|
||||
data := &do.SysOssConfig{}
|
||||
gconv.Struct(param, data)
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.CreatedBy = contexts.GetUserId(ctx)
|
||||
data.CreatedDept = contexts.GetDeptId(ctx)
|
||||
data.TenantId = contexts.GetTenantId(ctx)
|
||||
if len(param.Status) == 0 {
|
||||
data.Status = "1"
|
||||
}
|
||||
|
||||
id, err := db.Data(data).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item = &model.SysOssConfigAddModel{
|
||||
OssConfigId: id,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOssConfig) Edit(ctx context.Context, param *model.SysOssConfigEditParam) (item *model.SysOssConfigEditModel, err error) {
|
||||
item = &model.SysOssConfigEditModel{
|
||||
OssConfigId: param.OssConfigId,
|
||||
}
|
||||
db := s.Model(ctx)
|
||||
data := &do.SysOssConfig{}
|
||||
gconv.Struct(param, data)
|
||||
data.OssConfigId = nil
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
if param.Status == nil || *param.Status == "" {
|
||||
data.Status = nil
|
||||
}
|
||||
|
||||
_, err = db.Data(data).OmitNil().Where(dao.SysOssConfig.Columns().OssConfigId, param.OssConfigId).Update()
|
||||
if err != nil {
|
||||
return item, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOssConfig) Delete(ctx context.Context, param *model.SysOssConfigDeleteParam) (item *model.SysOssConfigDeleteModel, err error) {
|
||||
item = &model.SysOssConfigDeleteModel{
|
||||
OssConfigIds: param.OssConfigIds,
|
||||
}
|
||||
db := s.Model(ctx)
|
||||
_, err = db.WhereIn(dao.SysOssConfig.Columns().OssConfigId, param.OssConfigIds).Delete()
|
||||
if err != nil {
|
||||
return item, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOssConfig) GetAllUrlByService(ctx context.Context, path string, tenantId string, ossService string) (allUrl string, err error) {
|
||||
ossConfig := &entity.SysOssConfig{}
|
||||
db := s.Model(ctx)
|
||||
db = db.Where(dao.SysOssConfig.Columns().ConfigKey, ossService)
|
||||
db = db.Where(dao.SysOssConfig.Columns().TenantId, tenantId)
|
||||
err = db.Scan(&ossConfig)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysOss GetAllUrlByService error: %+v, url: %s ,service: %s", err, path, ossService)
|
||||
return "", err
|
||||
}
|
||||
if ossConfig == nil {
|
||||
g.Log().Errorf(ctx, "sSysOss GetAllUrlByService error: ossConfig is empty, url: %s ,service: %s", path, ossService)
|
||||
err = gerror.New("ossConfig is empty")
|
||||
return
|
||||
}
|
||||
domain := ossConfig.Endpoint
|
||||
if ossConfig.Domain != "" {
|
||||
domain = ossConfig.Domain
|
||||
}
|
||||
if ossConfig.IsHttps == "Y" {
|
||||
allUrl = "https://" + domain + path
|
||||
} else {
|
||||
allUrl = "http://" + domain + path
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysOss GetAllUrlByService success: %s, url: %s, ossConfig: %+v", ossService, allUrl, ossConfig)
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysPost struct {
|
||||
}
|
||||
|
||||
func NewSysPost() *sSysPost {
|
||||
return &sSysPost{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysPost(NewSysPost())
|
||||
}
|
||||
|
||||
func (l *sSysPost) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysPost.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (l *sSysPost) ModelQuery(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(service.MemoryDB().DB(ctx).Ctx(ctx).Model(dao.SysPost.Table()), option...)
|
||||
}
|
||||
|
||||
func (l *sSysPost) List(ctx context.Context, query *model.SysPostListParam) (items []*model.SysPostListModel, total int, err error) {
|
||||
m := l.ModelQuery(ctx)
|
||||
deptIds := make([]int64, 0)
|
||||
if query.DeptId != 0 {
|
||||
deptIds = append(deptIds, query.DeptId)
|
||||
} else if query.BelongDeptId != 0 {
|
||||
deptIds = append(deptIds, query.BelongDeptId)
|
||||
subDeptIds, err := service.SysDept().GetDeptIdsByParentId(ctx, query.BelongDeptId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
deptIds = append(deptIds, subDeptIds...)
|
||||
}
|
||||
if query.PostCode != "" {
|
||||
m = m.WhereLike(dao.SysPost.Columns().PostCode, "%"+query.PostCode+"%")
|
||||
}
|
||||
if query.PostName != "" {
|
||||
m = m.WhereLike(dao.SysPost.Columns().PostName, "%"+query.PostName+"%")
|
||||
}
|
||||
if len(deptIds) > 0 {
|
||||
m = m.WhereIn(dao.SysPost.Columns().DeptId, deptIds)
|
||||
}
|
||||
if query.Status != "" {
|
||||
m = m.Where(dao.SysPost.Columns().Status, query.Status)
|
||||
}
|
||||
if len(query.CreatedAt) == 2 {
|
||||
startTime := gtime.NewFromStr(query.CreatedAt[0])
|
||||
endTime := gtime.NewFromStr(query.CreatedAt[1])
|
||||
m = m.WhereBetween(dao.SysPost.Columns().CreatedAt, startTime, endTime.EndOfDay())
|
||||
}
|
||||
total, err = m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = m.WithAll().Page(query.Page, query.PageSize).OrderAsc(dao.SysPost.Columns().PostSort).Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysPost) View(ctx context.Context, param *model.SysPostViewParam) (post *model.SysPostViewModel, err error) {
|
||||
err = l.ModelQuery(ctx).WithAll().Where(dao.SysPost.Columns().PostId, param.PostId).Scan(&post)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysPost) Add(ctx context.Context, param *model.SysPostAddParam) (post *model.SysPostAddModel, err error) {
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return nil, gerror.NewCode(consts.CodeLoginExpired, "登录已过期")
|
||||
}
|
||||
data := do.SysPost{}
|
||||
gconv.Struct(param, &data)
|
||||
data.TenantId = user.TenantId
|
||||
data.CreatedDept = user.DeptId
|
||||
data.CreatedBy = user.ID
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.UpdatedBy = user.ID
|
||||
data.UpdatedAt = gtime.Now()
|
||||
postId, err := l.Model(ctx).Data(data).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
post = &model.SysPostAddModel{
|
||||
PostId: postId,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysPostCreate, postId)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysPost) Edit(ctx context.Context, param *model.SysPostEditParam) (post *model.SysPostEditModel, err error) {
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return nil, gerror.NewCode(consts.CodeLoginExpired, "登录已过期")
|
||||
}
|
||||
data := do.SysPost{}
|
||||
gconv.Struct(param, &data)
|
||||
data.UpdatedBy = user.ID
|
||||
data.UpdatedAt = gtime.Now()
|
||||
_, err = l.Model(ctx).Data(data).OmitNil().Where(dao.SysPost.Columns().PostId, param.PostId).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
post = &model.SysPostEditModel{
|
||||
PostId: param.PostId,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysPostUpdate, param.PostId)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysPost) Delete(ctx context.Context, param *model.SysPostDeleteParam) (post *model.SysPostDeleteModel, err error) {
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return nil, gerror.NewCode(consts.CodeLoginExpired, "登录已过期")
|
||||
}
|
||||
ids := make([]int64, 0)
|
||||
if param.PostId != 0 {
|
||||
ids = append(ids, param.PostId)
|
||||
}
|
||||
if len(param.PostIds) > 0 {
|
||||
ids = append(ids, param.PostIds...)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil, gerror.New("请选择要删除的岗位")
|
||||
}
|
||||
data := do.SysPost{}
|
||||
data.DeletedBy = user.ID
|
||||
data.DeletedAt = gtime.Now()
|
||||
_, err = l.Model(ctx).Data(data).OmitNil().WhereIn(dao.SysPost.Columns().PostId, ids).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
post = &model.SysPostDeleteModel{
|
||||
PostId: param.PostId,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysPostDelete, ids)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysPost) Export(ctx context.Context, param *model.SysPostExportParam) (post *model.SysPostExportModel, err error) {
|
||||
if param.Page == 0 {
|
||||
param.Page = 1
|
||||
}
|
||||
if param.PageSize == 0 {
|
||||
param.PageSize = 1000
|
||||
}
|
||||
items, total, err := l.List(ctx, param.SysPostListParam)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// 字段的排序是依据tags的字段顺序,如果你不想使用默认的排序方式,可以直接定义 tags = []string{"字段名称", "字段名称2", ...}
|
||||
tags, err := convert.GetEntityDescTags(model.SysPostExportModel{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
fileName = "导出CURD列表-" + gctx.CtxId(ctx)
|
||||
sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", total, total+param.PageSize-1/param.PageSize, param.Page, len(items))
|
||||
exports []model.SysPostExportModel
|
||||
)
|
||||
g.Log().Info(ctx, " sSysPost.Export total", total)
|
||||
if err = gconv.Scan(items, &exports); err != nil {
|
||||
return
|
||||
}
|
||||
g.Log().Info(ctx, " sSysPost.Export exports", len(exports))
|
||||
err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
g.Log().Info(ctx, " sSysPost.Export err", err)
|
||||
post = &model.SysPostExportModel{}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,829 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gset"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type sSysQrcode struct {
|
||||
}
|
||||
|
||||
func NewSysQrcode() *sSysQrcode {
|
||||
return &sSysQrcode{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysQrcode(NewSysQrcode())
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Second * 60)
|
||||
g.Log().Debug(context.Background(), "检查二维码是否过期,过期则删除, 每分钟检查一次")
|
||||
checkQrcodeExpiredToRemove(context.Background())
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
type SysQrcodeConfig struct {
|
||||
Timeout string `json:"timeout" dc:"请求超时"`
|
||||
ThirdId int `json:"thirdId" dc:"第三方ID"`
|
||||
SceneIdLogin int `json:"sceneIdLogin" dc:"场景ID-登录"`
|
||||
SceneIdBind int `json:"sceneIdBind" dc:"场景ID-绑定"`
|
||||
Token string `json:"token" dc:"Token"`
|
||||
ServerApi string `json:"serverApi" dc:"服务端API"`
|
||||
}
|
||||
|
||||
// 检查二维码是否过期,过期则删除
|
||||
func checkQrcodeExpiredToRemove(ctx context.Context) {
|
||||
var sysQrcodeConfig *SysQrcodeConfig
|
||||
g.TryCatch(ctx, func(ctx context.Context) {
|
||||
var err error
|
||||
sysQrcodeConfig, err = getSysQrcodeConfig(ctx)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "获取系统二维码配置失败, 错误: %v", err)
|
||||
return
|
||||
}
|
||||
}, func(ctx context.Context, exception error) {
|
||||
g.Log().Errorf(ctx, "检查二维码是否过期, 获取系统二维码配置失败, 错误: %v", exception)
|
||||
})
|
||||
|
||||
if sysQrcodeConfig == nil {
|
||||
return
|
||||
}
|
||||
|
||||
g.TryCatch(ctx, func(ctx context.Context) {
|
||||
redisKey := fmt.Sprintf(consts.KeyLoginScanWechat, sysQrcodeConfig.SceneIdLogin)
|
||||
err := checkQrcodeExpiredToRemoveByKey(ctx, redisKey)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "检查登录二维码是否过期失败, 错误: %v", err)
|
||||
return
|
||||
}
|
||||
}, func(ctx context.Context, exception error) {
|
||||
g.Log().Errorf(ctx, "检查登录二维码是否过期, 错误: %v", exception)
|
||||
})
|
||||
|
||||
g.TryCatch(ctx, func(ctx context.Context) {
|
||||
redisKey := fmt.Sprintf(consts.KeyUserBindWechat, sysQrcodeConfig.SceneIdBind)
|
||||
err := checkQrcodeExpiredToRemoveByKey(ctx, redisKey)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "检查绑定二维码是否过期失败, 错误: %v", err)
|
||||
return
|
||||
}
|
||||
}, func(ctx context.Context, exception error) {
|
||||
g.Log().Errorf(ctx, "检查绑定二维码是否过期, 错误: %v", exception)
|
||||
})
|
||||
}
|
||||
|
||||
// 检查指定redisKey的Hash的Field是否过期,过期则删除对应的feild
|
||||
func checkQrcodeExpiredToRemoveByKey(ctx context.Context, redisKey string) (err error) {
|
||||
val, err := g.Redis().HGetAll(ctx, redisKey)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "获取登录二维码缓存信息失败 redisKey:%s, 错误: %v", redisKey, err)
|
||||
return err
|
||||
}
|
||||
mapData := val.Map()
|
||||
if len(mapData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for field, v := range mapData {
|
||||
cacheModel := &model.QrcodeCacheModel{}
|
||||
err = gconv.Struct(v, cacheModel)
|
||||
if err != nil {
|
||||
// 删除redisKey
|
||||
g.Log().Debug(ctx, "数据转换失败, 删除对应的数据 redisKey:%s, field:%s", redisKey, field)
|
||||
g.Redis().Do(ctx, "HDEL", redisKey, field)
|
||||
continue
|
||||
}
|
||||
// 判断是否过期 一分钟
|
||||
if cacheModel.ExpireTime.Before(gtime.Now().Add(time.Minute * -1)) {
|
||||
// 删除redisKey
|
||||
g.Log().Debug(ctx, "删除过期数据, redisKey:%s, field:%s", redisKey, field)
|
||||
g.Redis().Do(ctx, "HDEL", redisKey, field)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取系统二维码配置
|
||||
func getSysQrcodeConfig(ctx context.Context) (*SysQrcodeConfig, error) {
|
||||
|
||||
thirdId, err := g.Cfg().Get(ctx, "wx.qrcode.thirdId")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sceneIdLogin, err := g.Cfg().Get(ctx, "wx.qrcode.sceneIdLogin")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sceneIdBind, err := g.Cfg().Get(ctx, "wx.qrcode.sceneIdBind")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, err := g.Cfg().Get(ctx, "wx.qrcode.token")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
serverApi, err := g.Cfg().Get(ctx, "wx.qrcode.serverApi")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timeout, err := g.Cfg().Get(ctx, "wx.qrcode.timeout", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SysQrcodeConfig{
|
||||
Timeout: timeout.String(),
|
||||
ThirdId: thirdId.Int(),
|
||||
SceneIdLogin: sceneIdLogin.Int(),
|
||||
SceneIdBind: sceneIdBind.Int(),
|
||||
Token: token.String(),
|
||||
ServerApi: serverApi.String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 调用二维码API
|
||||
func callQrcodeApi(ctx context.Context, sysQrcodeConfig *SysQrcodeConfig, isLogin bool) (res *model.ApiCallQrcodeModel, err error) {
|
||||
|
||||
// 调用API
|
||||
sceneId := sysQrcodeConfig.SceneIdLogin
|
||||
if !isLogin {
|
||||
sceneId = sysQrcodeConfig.SceneIdBind
|
||||
}
|
||||
request := g.Client().Timeout(utility.ExternalRequestTimeout(sysQrcodeConfig.Timeout))
|
||||
request.SetHeader("Content-Type", "application/json")
|
||||
timeStamp, sign := tokenEncode(sysQrcodeConfig.Token)
|
||||
response, err := request.Get(ctx, sysQrcodeConfig.ServerApi, g.Map{
|
||||
"thirdId": sysQrcodeConfig.ThirdId,
|
||||
"sceneId": sceneId,
|
||||
"timeStamp": timeStamp,
|
||||
"sign": sign,
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "调用二维码API失败, 请求数据: %v, 错误: %v", request, err)
|
||||
return nil, err
|
||||
}
|
||||
defer response.Close()
|
||||
if response.StatusCode != 200 {
|
||||
g.Log().Errorf(ctx, "调用二维码API失败, 请求数据: %v, 响应数据: %v", request, response)
|
||||
return nil, errors.New("调用二维码API失败")
|
||||
}
|
||||
|
||||
responseBytes, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
return nil, gerror.Wrap(err, "读取二维码服务响应失败")
|
||||
}
|
||||
responseData := string(responseBytes)
|
||||
|
||||
g.Log().Debugf(ctx, "调用二维码API成功, 请求数据: %v, 响应数据: %v", request, responseData)
|
||||
result := &model.ApiCallQrcodeResult{}
|
||||
err = json.Unmarshal([]byte(responseData), &result)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "调用二维码API失败, 数据转换失败, 请求数据: %v, 响应数据: %v, 错误: %v", request, responseData, err)
|
||||
return nil, err
|
||||
}
|
||||
if result.Code != 0 {
|
||||
g.Log().Errorf(ctx, "调用二维码API失败, 错误信息: %v", result.Error)
|
||||
return nil, errors.New(result.Error)
|
||||
}
|
||||
return &model.ApiCallQrcodeModel{
|
||||
Ticket: result.Data.Ticket,
|
||||
ExpireTime: result.Data.ExpireTime,
|
||||
QrUrl: result.Data.QrUrl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 获取登录二维码
|
||||
func (l *sSysQrcode) GetQrcodeLogin(ctx context.Context) (res *model.QrcodeLoginModel, err error) {
|
||||
sysQrcodeConfig, err := getSysQrcodeConfig(ctx)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "获取系统二维码配置失败, 错误: %v", err)
|
||||
return nil, gerror.New("获取系统二维码配置失败")
|
||||
}
|
||||
if sysQrcodeConfig.ServerApi == "" || sysQrcodeConfig.Token == "" || sysQrcodeConfig.ThirdId == 0 || sysQrcodeConfig.SceneIdLogin == 0 || sysQrcodeConfig.SceneIdBind == 0 {
|
||||
return nil, errors.New("系统二维码配置错误")
|
||||
}
|
||||
apiResult, err := callQrcodeApi(ctx, sysQrcodeConfig, true)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "获取二维码失败, 错误: %v", err)
|
||||
return nil, gerror.New("获取二维码失败")
|
||||
}
|
||||
|
||||
expireTime := gtime.Now().Add(time.Second * time.Duration(apiResult.ExpireTime))
|
||||
// 将结果保存到redis中
|
||||
tempUserId := uuid.New().String()
|
||||
redisKey := fmt.Sprintf(consts.KeyLoginScanWechat, sysQrcodeConfig.SceneIdLogin)
|
||||
cacheModel := &model.QrcodeCacheModel{
|
||||
Ticket: apiResult.Ticket,
|
||||
ExpireTime: expireTime,
|
||||
QrUrl: apiResult.QrUrl,
|
||||
Scanned: false,
|
||||
OpenId: "",
|
||||
}
|
||||
mapData := make(map[string]interface{})
|
||||
mapData[tempUserId] = cacheModel
|
||||
g.Redis().HSet(ctx, redisKey, mapData)
|
||||
|
||||
return &model.QrcodeLoginModel{
|
||||
TempUserId: tempUserId,
|
||||
QrcodeCacheModel: *cacheModel,
|
||||
Bound: false,
|
||||
Expired: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 对openId进行加密
|
||||
func openIdEncode(openId string) string {
|
||||
data := []byte(openId)
|
||||
hash := md5.Sum(data)
|
||||
md5String := hex.EncodeToString(hash[:])
|
||||
return base64.StdEncoding.EncodeToString([]byte(openId + "," + md5String))
|
||||
}
|
||||
|
||||
// 对openId进行解密
|
||||
func openIdDecode(openIdEncode string) (string, error) {
|
||||
decode, err := base64.StdEncoding.DecodeString(openIdEncode)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
strs := strings.Split(string(decode), ",")
|
||||
if len(strs) != 2 {
|
||||
return "", errors.New("openId 格式错误")
|
||||
}
|
||||
|
||||
openId := strs[0]
|
||||
md5String := strs[1]
|
||||
|
||||
data := []byte(openId)
|
||||
hash := md5.Sum(data)
|
||||
md5String2 := hex.EncodeToString(hash[:])
|
||||
if md5String != md5String2 {
|
||||
return "", errors.New("openId 格式错误")
|
||||
}
|
||||
|
||||
return openId, nil
|
||||
}
|
||||
|
||||
// 对token进行加密
|
||||
func tokenEncode(token string) (timeStamp int64, md5String string) {
|
||||
nowTime := time.Now().Unix()
|
||||
tokenStr := fmt.Sprintf("%d%s", nowTime, token)
|
||||
data := []byte(tokenStr)
|
||||
hash := md5.Sum(data)
|
||||
md5String = hex.EncodeToString(hash[:])
|
||||
return nowTime, md5String
|
||||
}
|
||||
|
||||
func tokenTimeStampEncode(timeStamp int64, token string) (md5String string) {
|
||||
tokenStr := fmt.Sprintf("%d%s", timeStamp, token)
|
||||
data := []byte(tokenStr)
|
||||
hash := md5.Sum(data)
|
||||
md5String = hex.EncodeToString(hash[:])
|
||||
return md5String
|
||||
}
|
||||
|
||||
// 获取登录二维码扫码结果
|
||||
func (l *sSysQrcode) GetQrcodeLoginStatus(ctx context.Context, tempUserId string) (res *model.QrcodeLoginModel, err error) {
|
||||
sysQrcodeConfig, err := getSysQrcodeConfig(ctx)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "获取系统二维码配置失败, 错误: %v", err)
|
||||
return nil, gerror.New("获取系统二维码配置失败")
|
||||
}
|
||||
|
||||
res = &model.QrcodeLoginModel{
|
||||
TempUserId: tempUserId,
|
||||
QrcodeCacheModel: model.QrcodeCacheModel{
|
||||
Ticket: "",
|
||||
ExpireTime: gtime.Now(),
|
||||
QrUrl: "",
|
||||
Scanned: false,
|
||||
OpenId: "",
|
||||
},
|
||||
Bound: false,
|
||||
Expired: false,
|
||||
}
|
||||
|
||||
// 获取不到缓存,统一标记为过期
|
||||
redisKey := fmt.Sprintf(consts.KeyLoginScanWechat, sysQrcodeConfig.SceneIdLogin)
|
||||
cashValue, err := g.Redis().HGet(ctx, redisKey, tempUserId)
|
||||
if err != nil || cashValue == nil || cashValue.Map() == nil {
|
||||
g.Log().Errorf(ctx, "未获取到%s的二维码扫码结果, 错误: %v", tempUserId, err)
|
||||
res.Expired = true
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 将val 的数据保存到cacheModel中'
|
||||
cacheModel := &model.QrcodeCacheModel{}
|
||||
g.Log().Debugf(ctx, "获取登录二维码扫码结果, 缓存数据: %v", cashValue.Map())
|
||||
mapData := cashValue.Map()
|
||||
err = gconv.Struct(mapData, cacheModel)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "获取登录二维码扫码结果失败, 错误: %v", err)
|
||||
res.Expired = true
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 将缓存结果赋值给返回结果
|
||||
res.Ticket = cacheModel.Ticket
|
||||
res.ExpireTime = cacheModel.ExpireTime
|
||||
res.QrUrl = cacheModel.QrUrl
|
||||
res.Scanned = cacheModel.Scanned
|
||||
res.OpenId = openIdEncode(cacheModel.OpenId)
|
||||
g.Log().Debugf(ctx, "获取登录二维码扫码结果, 缓存数据: %v", cacheModel)
|
||||
|
||||
//先看扫码状态
|
||||
if !cacheModel.Scanned {
|
||||
// 未扫码 需要判断是否过期
|
||||
if cacheModel.ExpireTime.Before(gtime.Now()) {
|
||||
res.Expired = true
|
||||
// 删除redisKey
|
||||
g.Redis().Do(ctx, "HDEL", redisKey, tempUserId)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
// 已扫码 需要判断是否绑定
|
||||
//根据openId和source查询数据库
|
||||
userBindList, _, err := service.SysSocial().List(ctx, &model.SysSocialListParam{
|
||||
OpenId: cacheModel.OpenId,
|
||||
Source: "wechat_mp",
|
||||
}, &request.PageInfo{Page: 1, PageSize: 100})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "获取社会化关系表信息失败, 错误: %v", err)
|
||||
return nil, gerror.New("获取绑定信息失败")
|
||||
}
|
||||
if len(userBindList) == 0 {
|
||||
res.Bound = false
|
||||
// 删除redisKey
|
||||
g.Redis().Do(ctx, "HDEL", redisKey, tempUserId)
|
||||
return res, nil
|
||||
}
|
||||
validBindUserList := make([]*model.SysSocialListModel, 0)
|
||||
tenantIdList := gset.NewStrSet()
|
||||
for _, bindUser := range userBindList {
|
||||
user, err := service.SysUser().GetUserByUsername(ctx, bindUser.UserName, bindUser.TenantId)
|
||||
if err != nil || user == nil {
|
||||
g.Log().Errorf(ctx, "关联信息无效, Id:%d, openId:%s, source:%s, userName:%s, tenantId:%s", bindUser.Id, cacheModel.OpenId, "wechat_mp", bindUser.UserName, bindUser.TenantId)
|
||||
continue
|
||||
}
|
||||
bindUser.User = &model.SysUserViewModel{
|
||||
UserId: user.UserId,
|
||||
UserName: user.UserName,
|
||||
NickName: user.NickName,
|
||||
Avatar: user.Avatar,
|
||||
TenantId: user.TenantId,
|
||||
}
|
||||
tenantIdList.Add(bindUser.TenantId)
|
||||
validBindUserList = append(validBindUserList, bindUser)
|
||||
}
|
||||
//租户Idmap
|
||||
tenantMap := make(map[string]*model.SysTenantListModel)
|
||||
if tenantIdList.Size() > 0 {
|
||||
// 查询租户列表
|
||||
tenantList := make([]*model.SysTenantListModel, 0)
|
||||
err := dao.SysTenant.Ctx(ctx).WhereIn(dao.SysTenant.Columns().TenantId, tenantIdList.Slice()).Scan(&tenantList)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "获取租户列表失败, 错误: %v", err)
|
||||
return nil, gerror.New("获取租户列表失败")
|
||||
}
|
||||
for _, tenant := range tenantList {
|
||||
tenantMap[tenant.TenantId] = tenant
|
||||
}
|
||||
}
|
||||
|
||||
if len(validBindUserList) == 1 {
|
||||
//单账号绑定 执行登录操作
|
||||
bindUser := validBindUserList[0]
|
||||
res.Bound = true
|
||||
|
||||
// 执行登录操作
|
||||
userOut, token, err := service.SysAuth().LoginByOpenId(ctx, bindUser)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "登录操作失败, 错误: %v", err)
|
||||
return nil, gerror.New("登录失败, 请稍后重试")
|
||||
}
|
||||
res.LoginUserOut = userOut
|
||||
res.Token = token
|
||||
} else {
|
||||
// 多账号绑定,返回用户列表
|
||||
userList := make([]*model.SysUserSocialModel, 0)
|
||||
|
||||
for _, bindUser := range validBindUserList {
|
||||
user := bindUser.User
|
||||
tenant := tenantMap[user.TenantId]
|
||||
tenantName := ""
|
||||
if tenant != nil {
|
||||
tenantName = tenant.CompanyName
|
||||
}
|
||||
userList = append(userList, &model.SysUserSocialModel{
|
||||
TenantId: user.TenantId,
|
||||
UserId: user.UserId,
|
||||
UserName: user.UserName,
|
||||
NickName: user.NickName,
|
||||
Phonenumber: user.Phonenumber,
|
||||
Avatar: user.Avatar,
|
||||
TenantName: tenantName,
|
||||
})
|
||||
}
|
||||
res.UserList = userList
|
||||
|
||||
}
|
||||
// 删除redisKey
|
||||
g.Redis().Do(ctx, "HDEL", redisKey, tempUserId)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 获取绑定二维码
|
||||
func (l *sSysQrcode) GetQrcodeBind(ctx context.Context) (res *model.QrcodeCacheModel, err error) {
|
||||
userId := contexts.GetUserId(ctx)
|
||||
if userId == 0 {
|
||||
return nil, gerror.New("用户未登录")
|
||||
}
|
||||
|
||||
sysQrcodeConfig, err := getSysQrcodeConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sysQrcodeConfig.ServerApi == "" || sysQrcodeConfig.Token == "" || sysQrcodeConfig.ThirdId == 0 || sysQrcodeConfig.SceneIdLogin == 0 || sysQrcodeConfig.SceneIdBind == 0 {
|
||||
return nil, errors.New("系统二维码配置错误")
|
||||
}
|
||||
apiResult, err := callQrcodeApi(ctx, sysQrcodeConfig, false)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "获取绑定二维码失败, 错误: %v", err)
|
||||
return nil, gerror.New("获取绑定二维码失败")
|
||||
}
|
||||
|
||||
expireTime := gtime.Now().Add(time.Second * time.Duration(apiResult.ExpireTime))
|
||||
// 将结果保存到redis中
|
||||
res = &model.QrcodeCacheModel{
|
||||
Ticket: apiResult.Ticket,
|
||||
ExpireTime: expireTime,
|
||||
QrUrl: apiResult.QrUrl,
|
||||
Scanned: false,
|
||||
OpenId: "",
|
||||
}
|
||||
|
||||
redisKey := fmt.Sprintf(consts.KeyUserBindWechat, sysQrcodeConfig.SceneIdBind)
|
||||
mapData := make(map[string]interface{})
|
||||
g.Log().Debugf(ctx, "获取绑定二维码, 缓存数据: %v", res)
|
||||
mapData[strconv.FormatInt(userId, 10)] = res
|
||||
g.Log().Debugf(ctx, "用户 %d 设置绑定二维码缓存: key: %s field: %s value: %v", userId, redisKey, strconv.FormatInt(userId, 10), res)
|
||||
g.Redis().HSet(ctx, redisKey, mapData)
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 获取绑定二维码扫码结果
|
||||
func (l *sSysQrcode) GetQrcodeBindStatus(ctx context.Context) (res *model.QrcodeBindStatusModel, err error) {
|
||||
userId := contexts.GetUserId(ctx)
|
||||
if userId == 0 {
|
||||
return nil, gerror.New("用户未登录")
|
||||
}
|
||||
sysQrcodeConfig, err := getSysQrcodeConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if sysQrcodeConfig.ServerApi == "" || sysQrcodeConfig.Token == "" || sysQrcodeConfig.ThirdId == 0 || sysQrcodeConfig.SceneIdLogin == 0 || sysQrcodeConfig.SceneIdBind == 0 {
|
||||
return nil, errors.New("系统二维码配置错误")
|
||||
}
|
||||
|
||||
redisKey := fmt.Sprintf(consts.KeyUserBindWechat, sysQrcodeConfig.SceneIdBind)
|
||||
val, err := g.Redis().HGet(ctx, redisKey, strconv.FormatInt(userId, 10))
|
||||
g.Log().Debugf(ctx, "获取绑定二维码扫码结果, 用户 %d 的缓存数据: %v", userId, val.Map())
|
||||
if err != nil || val == nil || val.Map() == nil {
|
||||
return &model.QrcodeBindStatusModel{
|
||||
QrcodeCacheModel: model.QrcodeCacheModel{
|
||||
Ticket: "",
|
||||
ExpireTime: gtime.Now(),
|
||||
QrUrl: "",
|
||||
},
|
||||
Expired: true,
|
||||
Bound: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
cacheModel := &model.QrcodeCacheModel{}
|
||||
mapData := val.Map()
|
||||
err = gconv.Struct(mapData, cacheModel)
|
||||
if err != nil {
|
||||
return nil, gerror.New("获取绑定二维码扫码结果失败")
|
||||
}
|
||||
res = &model.QrcodeBindStatusModel{
|
||||
QrcodeCacheModel: *cacheModel,
|
||||
Bound: false,
|
||||
Expired: false,
|
||||
}
|
||||
if !cacheModel.Scanned {
|
||||
// TODO 判断是否过期
|
||||
if cacheModel.ExpireTime.Before(gtime.Now()) {
|
||||
res.Expired = true
|
||||
// 删除redisKey
|
||||
g.Redis().Do(ctx, "HDEL", redisKey, strconv.FormatInt(userId, 10))
|
||||
}
|
||||
return res, nil
|
||||
} else {
|
||||
// 已扫码 需要判断是否绑定
|
||||
// 判断是否已绑定
|
||||
userBindList, _, err := service.SysSocial().List(ctx, &model.SysSocialListParam{
|
||||
UserId: userId,
|
||||
OpenId: cacheModel.OpenId,
|
||||
Source: "wechat_mp",
|
||||
}, &request.PageInfo{Page: 1, PageSize: 1})
|
||||
if err != nil {
|
||||
return nil, gerror.New("获取绑定信息失败")
|
||||
}
|
||||
if len(userBindList) > 0 {
|
||||
res.Bound = true
|
||||
} else {
|
||||
res.Bound = false
|
||||
}
|
||||
// 删除redisKey
|
||||
g.Redis().Do(ctx, "HDEL", redisKey, strconv.FormatInt(userId, 10))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// 扫码回调
|
||||
func (l *sSysQrcode) QrcodeScanCallback(ctx context.Context, param *model.QrcodeScanCallbackParam) (err error) {
|
||||
g.Log().Debug(ctx, "扫码回调, 参数: %v", param)
|
||||
if param.TimeStamp == 0 || param.Sign == "" {
|
||||
return gerror.New("签名或时间戳不能为空")
|
||||
}
|
||||
|
||||
sysQrcodeConfig, err := getSysQrcodeConfig(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sysQrcodeConfig.ServerApi == "" || sysQrcodeConfig.Token == "" || sysQrcodeConfig.ThirdId == 0 || sysQrcodeConfig.SceneIdLogin == 0 || sysQrcodeConfig.SceneIdBind == 0 {
|
||||
return errors.New("系统二维码配置错误")
|
||||
}
|
||||
|
||||
//nowTime := gtime.Now().Unix()
|
||||
sign := tokenTimeStampEncode(param.TimeStamp, sysQrcodeConfig.Token)
|
||||
// if math.Abs(float64(param.TimeStamp-nowTime)) > 60 {
|
||||
// return gerror.New("时间戳无效")
|
||||
// }
|
||||
if param.Sign != sign {
|
||||
return gerror.New("签名无效")
|
||||
}
|
||||
|
||||
if param.SceneId == sysQrcodeConfig.SceneIdLogin {
|
||||
redisKey := fmt.Sprintf(consts.KeyLoginScanWechat, param.SceneId)
|
||||
// 登录回调 更新一个扫码缓存状态为已扫码
|
||||
val, err := g.Redis().HGetAll(ctx, redisKey)
|
||||
if err != nil {
|
||||
return gerror.New("获取登录二维码扫码结果失败")
|
||||
}
|
||||
|
||||
g.Log().Debugf(ctx, "登录回调, 获取登录二维码扫码结果: %v", val)
|
||||
mapData := val.Map()
|
||||
if len(mapData) == 0 {
|
||||
return gerror.New("获取登录二维码扫码结果失败")
|
||||
}
|
||||
|
||||
for tempUserId, v := range mapData {
|
||||
cacheModel := &model.QrcodeCacheModel{}
|
||||
err = gconv.Struct(v, cacheModel)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "缓存数据转换失败,数据: %v, 错误: %v", v, err)
|
||||
continue
|
||||
}
|
||||
if cacheModel.Scanned || cacheModel.Ticket != param.Ticket {
|
||||
continue
|
||||
}
|
||||
// 未扫码
|
||||
cacheModel.Scanned = true
|
||||
cacheModel.OpenId = param.OpenId
|
||||
updateMap := make(map[string]interface{})
|
||||
updateMap[tempUserId] = cacheModel
|
||||
g.Redis().HSet(ctx, redisKey, updateMap)
|
||||
break
|
||||
}
|
||||
}
|
||||
if param.SceneId == sysQrcodeConfig.SceneIdBind {
|
||||
// 绑定回调 保存绑定结果
|
||||
redisKey := fmt.Sprintf(consts.KeyUserBindWechat, param.SceneId)
|
||||
val, err := g.Redis().HGetAll(ctx, redisKey)
|
||||
if err != nil {
|
||||
return gerror.New("获取绑定二维码扫码结果失败 redisKey: " + redisKey)
|
||||
}
|
||||
mapData := val.Map()
|
||||
if len(mapData) == 0 {
|
||||
return gerror.New("获取绑定二维码扫码结果失败 redisKey: " + redisKey)
|
||||
}
|
||||
for userId, v := range mapData {
|
||||
cacheModel := &model.QrcodeCacheModel{}
|
||||
err = gconv.Struct(v, cacheModel)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "缓存数据转换失败,数据: %v, 错误: %v", v, err)
|
||||
continue
|
||||
}
|
||||
if cacheModel.Scanned || cacheModel.Ticket != param.Ticket {
|
||||
continue
|
||||
}
|
||||
// 未扫码
|
||||
cacheModel.Scanned = true
|
||||
cacheModel.OpenId = param.OpenId
|
||||
updateMap := make(map[string]interface{})
|
||||
updateMap[userId] = cacheModel
|
||||
g.Redis().HSet(ctx, redisKey, updateMap)
|
||||
|
||||
user := &entity.SysUser{}
|
||||
// 查询用户信息
|
||||
err = dao.SysUser.Ctx(ctx).Where(dao.SysUser.Columns().UserId, gconv.Int64(userId)).Scan(&user)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "查询用户信息失败, 错误: %v", err)
|
||||
return gerror.New("查询用户信息失败")
|
||||
}
|
||||
if user == nil {
|
||||
g.Log().Errorf(ctx, "用户不存在, 用户ID: %s", userId)
|
||||
return gerror.New("用户不存在")
|
||||
}
|
||||
|
||||
// 保存绑定结果
|
||||
err = service.SysSocial().Create(ctx, &model.SysSocialSaveParam{
|
||||
UserId: gconv.Int64(userId),
|
||||
AuthId: fmt.Sprintf("%s-%s", consts.SourceWechatMp, param.OpenId),
|
||||
OpenId: param.OpenId,
|
||||
Source: consts.SourceWechatMp,
|
||||
UserName: user.UserName,
|
||||
AccessToken: param.OpenId,
|
||||
TenantId: user.TenantId,
|
||||
CreatedBy: 0, // 创建者为0 表示系统自动创建
|
||||
CreatedAt: gtime.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "保存绑定结果失败, 错误: %v", err)
|
||||
break
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 扫码后选择用户登录
|
||||
func (l *sSysQrcode) QrcodeLoginSelectUserId(ctx context.Context, param *model.QrcodeLoginSelectUserIdParam) (res *model.QrcodeLoginAndBindOpenIdModel, err error) {
|
||||
openId, err := openIdDecode(param.OpenId)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "openId 解密失败, 错误: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
g.Log().Debugf(ctx, "扫码后选择用户登录, 参数: %v", param)
|
||||
user, err := service.SysUser().GetUserByUsername(ctx, param.UserName, param.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, gerror.New("用户不存在")
|
||||
}
|
||||
|
||||
userOut, token, err := service.SysAuth().LoginByOpenId(ctx, &model.SysSocialListModel{
|
||||
UserId: user.UserId,
|
||||
OpenId: openId,
|
||||
Source: consts.SourceWechatMp,
|
||||
TenantId: user.TenantId,
|
||||
CreatedBy: 0, // 创建者为0 表示系统自动创建
|
||||
CreatedAt: gtime.Now(),
|
||||
User: &model.SysUserViewModel{
|
||||
UserId: user.UserId,
|
||||
UserName: user.UserName,
|
||||
NickName: user.NickName,
|
||||
Avatar: user.Avatar,
|
||||
TenantId: user.TenantId,
|
||||
},
|
||||
})
|
||||
return &model.QrcodeLoginAndBindOpenIdModel{
|
||||
LoginUserOut: userOut,
|
||||
Token: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 登录并绑定
|
||||
func (l *sSysQrcode) QrcodeLoginAndBindOpenId(ctx context.Context, param *model.QrcodeLoginAndBindOpenIdParam) (res *model.QrcodeLoginAndBindOpenIdModel, err error) {
|
||||
openId, err := openIdDecode(param.OpenId)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "openId 解密失败, 错误: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
// 1. 执行登录
|
||||
userOut, token, err := service.SysAuth().Login(ctx, &model.LoginParams{
|
||||
Username: param.Username,
|
||||
Password: param.Password,
|
||||
CaptchaID: param.CaptchaID,
|
||||
CaptchaValue: param.CaptchaValue,
|
||||
TenantId: param.TenantId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 2. 执行绑定
|
||||
err = service.SysSocial().Create(ctx, &model.SysSocialSaveParam{
|
||||
UserId: userOut.ID,
|
||||
AuthId: fmt.Sprintf("%s-%s", consts.SourceWechatMp, openId),
|
||||
OpenId: openId,
|
||||
Source: consts.SourceWechatMp,
|
||||
UserName: param.Username,
|
||||
AccessToken: openId,
|
||||
TenantId: param.TenantId,
|
||||
CreatedBy: 0, // 创建者为0 表示系统自动创建
|
||||
CreatedAt: gtime.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.QrcodeLoginAndBindOpenIdModel{
|
||||
LoginUserOut: userOut,
|
||||
Token: token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 注册并绑定
|
||||
func (l *sSysQrcode) QrcodeRegisterAndBindOpenId(ctx context.Context, param *model.QrcodeRegisterAndBindOpenIdParam) (res *model.QrcodeLoginAndBindOpenIdModel, err error) {
|
||||
openId, err := openIdDecode(param.OpenId)
|
||||
if err != nil || len(openId) == 0 {
|
||||
g.Log().Errorf(ctx, "openId 解密失败, 错误: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
// 1. 执行注册
|
||||
err = service.SysUser().Register(ctx, &model.SysUserRegisterModel{
|
||||
UserName: param.UserName,
|
||||
Password: param.Password,
|
||||
TenantId: param.TenantId,
|
||||
CaptchaID: param.CaptchaID,
|
||||
CaptchaValue: param.CaptchaValue,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user, err := service.SysUser().GetUserByUsername(ctx, param.UserName, param.TenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, gerror.New("用户注册失败")
|
||||
}
|
||||
// 2. 执行绑定
|
||||
err = service.SysSocial().Create(ctx, &model.SysSocialSaveParam{
|
||||
UserId: user.UserId,
|
||||
AuthId: fmt.Sprintf("%s-%s", consts.SourceWechatMp, openId),
|
||||
OpenId: openId,
|
||||
Source: consts.SourceWechatMp,
|
||||
UserName: param.UserName,
|
||||
AccessToken: openId,
|
||||
TenantId: param.TenantId,
|
||||
CreatedBy: 0, // 创建者为0 表示系统自动创建
|
||||
CreatedAt: gtime.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. 执行登录
|
||||
userOut, token, err := service.SysAuth().LoginByOpenId(ctx, &model.SysSocialListModel{
|
||||
UserId: user.UserId,
|
||||
OpenId: openId,
|
||||
Source: consts.SourceWechatMp,
|
||||
TenantId: param.TenantId,
|
||||
CreatedBy: 0, // 创建者为0 表示系统自动创建
|
||||
CreatedAt: gtime.Now(),
|
||||
User: &model.SysUserViewModel{
|
||||
UserId: user.UserId,
|
||||
UserName: user.UserName,
|
||||
NickName: user.NickName,
|
||||
Avatar: user.Avatar,
|
||||
TenantId: user.TenantId,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.QrcodeLoginAndBindOpenIdModel{
|
||||
LoginUserOut: userOut,
|
||||
Token: token,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestQrcodeAPISuccessAndBodyTimeout(t *testing.T) {
|
||||
for _, scenario := range []string{"success", "header-timeout", "body-timeout", "partial-body-timeout"} {
|
||||
t.Run(scenario, func(t *testing.T) {
|
||||
release := make(chan struct{})
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
_, _ = io.Copy(io.Discard, request.Body)
|
||||
if scenario != "success" {
|
||||
if scenario != "header-timeout" {
|
||||
writer.Header().Set("Content-Length", "1024")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
if scenario == "partial-body-timeout" {
|
||||
_, _ = writer.Write([]byte(`{"code":0,"data":{"ticket":"partial"}}`))
|
||||
}
|
||||
writer.(http.Flusher).Flush()
|
||||
}
|
||||
select {
|
||||
case <-request.Context().Done():
|
||||
case <-release:
|
||||
}
|
||||
return
|
||||
}
|
||||
_, _ = writer.Write([]byte(`{"code":0,"data":{"ticket":"test-ticket","expireTime":300,"qrUrl":"test-url"}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
defer close(release)
|
||||
started := time.Now()
|
||||
result, err := callQrcodeApi(context.Background(), &SysQrcodeConfig{
|
||||
ServerApi: server.URL, Timeout: "70ms", Token: "test-token", SceneIdLogin: 1,
|
||||
}, true)
|
||||
if scenario != "success" {
|
||||
if err == nil || result != nil || time.Since(started) > time.Second {
|
||||
t.Fatalf("stalled response was not rejected: result=%v error=%v", result, err)
|
||||
}
|
||||
} else if err != nil || result == nil || result.Ticket != "test-ticket" {
|
||||
t.Fatalf("successful response changed: result=%v error=%v", result, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserCodeHTTPTimeoutAndStatus(t *testing.T) {
|
||||
for _, scenario := range []string{"success", "rejected", "timeout"} {
|
||||
t.Run(scenario, func(t *testing.T) {
|
||||
release := make(chan struct{})
|
||||
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
_, _ = io.Copy(io.Discard, request.Body)
|
||||
if scenario == "timeout" {
|
||||
select {
|
||||
case <-request.Context().Done():
|
||||
case <-release:
|
||||
}
|
||||
return
|
||||
}
|
||||
if scenario == "rejected" {
|
||||
writer.WriteHeader(http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
writer.WriteHeader(http.StatusAccepted)
|
||||
}))
|
||||
defer server.Close()
|
||||
defer close(release)
|
||||
started := time.Now()
|
||||
err := callUserCodeHTTP(context.Background(), model.UserCodeHttpSenderConfig{
|
||||
Url: server.URL, Timeout: "70ms", SuccessStatus: http.StatusAccepted,
|
||||
}, "user@example.com", "123456")
|
||||
if (err != nil) != (scenario != "success") || time.Since(started) > time.Second {
|
||||
t.Fatalf("unexpected sender result: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/mcache"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolepolicy"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/rolesync"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility"
|
||||
"slices"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysRole struct {
|
||||
}
|
||||
|
||||
func NewSysRole() *sSysRole {
|
||||
return &sSysRole{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysRole(NewSysRole())
|
||||
}
|
||||
|
||||
func (l *sSysRole) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: false, // 角色表不验证数据权限
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysRole.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (l *sSysRole) ModelQuery(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: false, // 角色表不验证数据权限
|
||||
})
|
||||
}
|
||||
return handler.Model(service.MemoryDB().DB(ctx).Ctx(ctx).Model(dao.SysRole.Table()), option...)
|
||||
}
|
||||
|
||||
// 获取租户下角色列表
|
||||
func (s *sSysRole) List(ctx context.Context, param *model.SysRoleListParam) (res []*model.SysRoleListModel, total int, err error) {
|
||||
db := s.ModelQuery(ctx)
|
||||
// 注意:ModelQuery使用内存数据库,删除的记录会被物理删除,所以不需要过滤deleted_at
|
||||
if param.RoleName != "" {
|
||||
db = db.WhereLike(dao.SysRole.Columns().RoleName, "%"+param.RoleName+"%")
|
||||
}
|
||||
if param.Status != "" {
|
||||
db = db.Where(dao.SysRole.Columns().Status, param.Status)
|
||||
}
|
||||
if param.DeptId != nil {
|
||||
db = db.Where(dao.SysRole.Columns().DeptId, *param.DeptId)
|
||||
}
|
||||
if len(param.CreatedAt) == 2 {
|
||||
start, end := gtime.NewFromStr(param.CreatedAt[0]), gtime.NewFromStr(param.CreatedAt[1])
|
||||
db = db.WhereBetween(dao.SysRole.Columns().CreatedAt, start, end.EndOfDay())
|
||||
}
|
||||
if len(param.RoleIds) > 0 {
|
||||
db = db.WhereIn(dao.SysRole.Columns().RoleId, param.RoleIds)
|
||||
}
|
||||
total, err = db.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if total == 0 {
|
||||
return nil, 0, nil
|
||||
}
|
||||
|
||||
companyMap, err := service.SysDept().GetDeptCompanyMap(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
g.Log().Debugf(ctx, "sSysRole.List companyMap: %v", companyMap)
|
||||
err = db.Page(param.Page, param.PageSize).Scan(&res)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, role := range res {
|
||||
role.DeptName = companyMap[role.DeptId]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 获取角色详情
|
||||
func (s *sSysRole) View(ctx context.Context, param *model.SysRoleViewParam) (res *model.SysRoleViewModel, err error) {
|
||||
err = s.ModelQuery(ctx).Where(dao.SysRole.Columns().RoleId, param.RoleId).Scan(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 获取角色菜单
|
||||
roleMenus, err := s.getRoleMenuRelations(ctx, param.RoleId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
menuIdList := make([]int64, 0)
|
||||
roleMenuDataScopes := make(map[int64]string)
|
||||
for _, menu := range roleMenus {
|
||||
menuIdList = append(menuIdList, menu.MenuId)
|
||||
roleMenuDataScopes[menu.MenuId] = menu.DataScope
|
||||
}
|
||||
res.MenuIds = menuIdList
|
||||
res.RoleMenuDataScopes = roleMenuDataScopes
|
||||
// 获取角色部门
|
||||
roleDepts, err := s.GetRoleDept(ctx, param.RoleId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deptIdList := make([]int64, 0)
|
||||
for _, dept := range roleDepts {
|
||||
deptIdList = append(deptIdList, dept.DeptId)
|
||||
}
|
||||
res.DeptIds = deptIdList
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysRole) getRoleMenuRelations(ctx context.Context, id int64) (res []*entity.SysRoleMenu, err error) {
|
||||
err = dao.SysRoleMenu.Ctx(ctx).
|
||||
Where(dao.SysRoleMenu.Columns().TenantId, contexts.GetTenantId(ctx)).
|
||||
Where(dao.SysRoleMenu.Columns().RoleId, id).Scan(&res)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取角色菜单
|
||||
func (s *sSysRole) GetRoleMenu(ctx context.Context, id int64) (res []*entity.SysMenu, err error) {
|
||||
err = dao.SysRoleMenu.Ctx(ctx).
|
||||
Where(dao.SysRoleMenu.Columns().TenantId, contexts.GetTenantId(ctx)).
|
||||
Where(dao.SysRoleMenu.Columns().RoleId, id).Scan(&res)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取角色部门
|
||||
func (s *sSysRole) GetRoleDept(ctx context.Context, id int64) (res []*entity.SysDept, err error) {
|
||||
err = dao.SysRoleDept.Ctx(ctx).
|
||||
Where(dao.SysRoleDept.Columns().TenantId, contexts.GetTenantId(ctx)).
|
||||
Where(dao.SysRoleDept.Columns().RoleId, id).Scan(&res)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取角色列表对应菜单
|
||||
func (s *sSysRole) GetRoleListMenu(ctx context.Context, ids []int64) (res []*entity.SysRoleMenu, err error) {
|
||||
res = make([]*entity.SysRoleMenu, 0)
|
||||
if slices.Contains(ids, consts.SuperAdminRoleId) {
|
||||
menus, _, err := service.SysMenu().List(ctx, &model.SysMenuListParam{
|
||||
Status: consts.SysMenuStatusNormal,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, menu := range menus {
|
||||
res = append(res, &entity.SysRoleMenu{
|
||||
TenantId: contexts.GetTenantId(ctx),
|
||||
RoleId: consts.SuperAdminRoleId,
|
||||
MenuId: menu.MenuId,
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
err = dao.SysRoleMenu.Ctx(ctx).
|
||||
Where(dao.SysRoleMenu.Columns().TenantId, contexts.GetTenantId(ctx)).
|
||||
WhereIn(dao.SysRoleMenu.Columns().RoleId, ids).Scan(&res)
|
||||
return
|
||||
}
|
||||
|
||||
// 新增角色
|
||||
func (s *sSysRole) Add(ctx context.Context, param *model.SysRoleAddParam) (role *model.SysRoleAddModel, err error) {
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
var saveErr error
|
||||
role, saveErr = s.addRoleAtomic(ctx, param)
|
||||
if saveErr != nil {
|
||||
return saveErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil && role != nil {
|
||||
err = rolesync.EnqueueRole(ctx, role.RoleId, contexts.GetUserId(ctx))
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysRoleCreate, role.RoleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysRole) addRoleAtomic(ctx context.Context, param *model.SysRoleAddParam) (role *model.SysRoleAddModel, err error) {
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return nil, gerror.New("登录已过期")
|
||||
}
|
||||
data := do.SysRole{}
|
||||
gconv.Struct(param, &data)
|
||||
if data.DataScope == nil || data.DataScope == "" {
|
||||
data.DataScope = consts.SysRoleDataScopePersonal
|
||||
}
|
||||
data.CreatedBy = user.ID
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.CreatedDept = user.DeptId
|
||||
data.TenantId = user.TenantId
|
||||
data.UpdatedBy = user.ID
|
||||
data.UpdatedAt = gtime.Now()
|
||||
|
||||
roleId, err := s.Model(ctx).Data(data).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
role = &model.SysRoleAddModel{
|
||||
RoleId: roleId,
|
||||
}
|
||||
// 新增角色菜单
|
||||
err = s.RoleMenu(ctx, roleId, param.MenuIds, param.RoleMenuDataScopes)
|
||||
if err != nil {
|
||||
return role, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑角色
|
||||
func (s *sSysRole) Edit(ctx context.Context, param *model.SysRoleEditParam) (role *model.SysRoleEditModel, err error) {
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
if _, err := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where("role_id", param.RoleId).Where("tenant_id", contexts.GetTenantId(ctx)).Data(do.SysRole{UpdatedBy: contexts.GetUserId(ctx)}).Update(); err != nil {
|
||||
return err
|
||||
}
|
||||
var saveErr error
|
||||
role, saveErr = s.editRoleAtomic(ctx, param)
|
||||
if saveErr != nil {
|
||||
return saveErr
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
err = rolesync.EnqueueRole(ctx, param.RoleId, contexts.GetUserId(ctx))
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysRoleUpdate, param.RoleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysRole) editRoleAtomic(ctx context.Context, param *model.SysRoleEditParam) (role *model.SysRoleEditModel, err error) {
|
||||
role = &model.SysRoleEditModel{
|
||||
RoleId: param.RoleId,
|
||||
}
|
||||
if err = s.ensureRolesNotInherited(ctx, []int64{param.RoleId}); err != nil {
|
||||
return role, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return role, gerror.New("登录已过期")
|
||||
}
|
||||
template, templateErr := rolepolicy.Template(ctx)
|
||||
if templateErr != nil {
|
||||
return role, templateErr
|
||||
}
|
||||
if template != nil && template.RoleId == param.RoleId {
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
return role, gerror.New("仅平台超级管理员可修改代理模板")
|
||||
}
|
||||
if param.Status != nil && *param.Status != "0" {
|
||||
return role, gerror.New("代理模板不能停用,请先切换模板角色")
|
||||
}
|
||||
if param.DeptId != nil && *param.DeptId != template.DeptId {
|
||||
return role, gerror.New("代理模板不能变更所属机构")
|
||||
}
|
||||
}
|
||||
data := do.SysRole{}
|
||||
gconv.Struct(param, &data)
|
||||
data.UpdatedBy = user.ID
|
||||
data.UpdatedAt = gtime.Now()
|
||||
|
||||
_, err = s.Model(ctx).Data(data).Where(dao.SysRole.Columns().RoleId, param.RoleId).OmitNil().Update()
|
||||
if err != nil {
|
||||
return role, err
|
||||
}
|
||||
// 更新角色菜单
|
||||
err = s.RoleMenu(ctx, param.RoleId, param.MenuIds, param.RoleMenuDataScopes)
|
||||
if err != nil {
|
||||
return role, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
func (s *sSysRole) Delete(ctx context.Context, param *model.SysRoleDeleteParam) (role *model.SysRoleDeleteModel, err error) {
|
||||
template, templateErr := rolepolicy.Template(ctx)
|
||||
if templateErr != nil {
|
||||
return nil, templateErr
|
||||
}
|
||||
if template != nil && (slices.Contains(param.RoleIds, template.RoleId) || param.RoleId == template.RoleId) {
|
||||
return nil, gerror.New("代理模板不能删除,请先切换模板角色")
|
||||
}
|
||||
role = &model.SysRoleDeleteModel{
|
||||
RoleId: param.RoleId,
|
||||
RoleIds: param.RoleIds,
|
||||
}
|
||||
roleIds := make([]int64, 0)
|
||||
if param.RoleId != 0 {
|
||||
roleIds = append(roleIds, param.RoleId)
|
||||
} else {
|
||||
roleIds = param.RoleIds
|
||||
}
|
||||
if len(roleIds) == 0 {
|
||||
return role, gerror.New("请选择要删除的角色")
|
||||
}
|
||||
if err = s.ensureRolesNotInherited(ctx, roleIds); err != nil {
|
||||
return role, err
|
||||
}
|
||||
if err = s.ensureRolesHaveNoInheritedChildren(ctx, roleIds); err != nil {
|
||||
return role, err
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
return role, gerror.New("登录已过期")
|
||||
}
|
||||
data := do.SysRole{}
|
||||
data.DeletedBy = user.ID
|
||||
data.DeletedAt = gtime.Now()
|
||||
_, err = s.Model(ctx).WhereIn(dao.SysRole.Columns().RoleId, roleIds).Data(data).OmitNil().Update()
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysRoleDelete, roleIds)
|
||||
clearRoleUsersAccessCache(ctx, roleIds...)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysRole) RoleMenu(ctx context.Context, roleId int64, menuIds []int64, dataScopes map[int64]string) (err error) {
|
||||
template, templateErr := rolepolicy.Template(ctx)
|
||||
if templateErr != nil {
|
||||
return templateErr
|
||||
}
|
||||
if template != nil && template.RoleId == roleId {
|
||||
for _, scope := range dataScopes {
|
||||
if scope == consts.SysRoleDataScopeAll || scope == consts.SysRoleDataScopeCustom {
|
||||
return gerror.New("模板按钮不允许全部或自定义数据范围")
|
||||
}
|
||||
}
|
||||
}
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
if !contexts.IsSuperAdmin(ctx) {
|
||||
allowed, err := rolepolicy.RestrictMenus(ctx, tenantId, contexts.GetDeptId(ctx), menuIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, id := range menuIds {
|
||||
if !slices.Contains(allowed, id) {
|
||||
return gerror.New("角色授权超出机构或模板范围")
|
||||
}
|
||||
}
|
||||
for _, scope := range dataScopes {
|
||||
if scope == consts.SysRoleDataScopeAll || scope == consts.SysRoleDataScopeCustom {
|
||||
return gerror.New("不允许授予全部或自定义数据范围")
|
||||
}
|
||||
}
|
||||
}
|
||||
// 查询角色菜单
|
||||
roleMenu := make([]*entity.SysRoleMenu, 0)
|
||||
err = dao.SysRoleMenu.Ctx(ctx).Where(dao.SysRoleMenu.Columns().TenantId, tenantId).Where(dao.SysRoleMenu.Columns().RoleId, roleId).Scan(&roleMenu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currMenuIds := make([]int64, 0)
|
||||
for _, menu := range roleMenu {
|
||||
currMenuIds = append(currMenuIds, menu.MenuId)
|
||||
}
|
||||
|
||||
delMenuIds := utility.ArrayRightDiff(currMenuIds, menuIds)
|
||||
addMenuIds := utility.ArrayRightDiff(menuIds, currMenuIds)
|
||||
updateMenuIds := utility.ArrayRightDiff(menuIds, addMenuIds)
|
||||
|
||||
// 删除角色菜单
|
||||
if len(delMenuIds) > 0 {
|
||||
_, err = dao.SysRoleMenu.Ctx(ctx).Where(dao.SysRoleMenu.Columns().TenantId, tenantId).Where(dao.SysRoleMenu.Columns().RoleId, roleId).WhereIn(dao.SysRoleMenu.Columns().MenuId, delMenuIds).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 新增角色菜单
|
||||
if len(addMenuIds) > 0 {
|
||||
addData := make([]*do.SysRoleMenu, 0)
|
||||
for _, menuId := range addMenuIds {
|
||||
addData = append(addData, &do.SysRoleMenu{
|
||||
TenantId: tenantId,
|
||||
RoleId: roleId,
|
||||
MenuId: menuId,
|
||||
DataScope: normalizeRoleMenuDataScope(dataScopes[menuId]),
|
||||
})
|
||||
}
|
||||
_, err = dao.SysRoleMenu.Ctx(ctx).Data(addData).Insert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, menuId := range updateMenuIds {
|
||||
_, err = dao.SysRoleMenu.Ctx(ctx).
|
||||
Where(dao.SysRoleMenu.Columns().TenantId, tenantId).
|
||||
Where(dao.SysRoleMenu.Columns().RoleId, roleId).
|
||||
Where(dao.SysRoleMenu.Columns().MenuId, menuId).
|
||||
Data(do.SysRoleMenu{
|
||||
DataScope: normalizeRoleMenuDataScope(dataScopes[menuId]),
|
||||
}).Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
clearRoleUsersAccessCache(ctx, roleId)
|
||||
return
|
||||
}
|
||||
|
||||
func normalizeRoleMenuDataScope(dataScope string) string {
|
||||
switch dataScope {
|
||||
case consts.SysRoleDataScopeAll,
|
||||
consts.SysRoleDataScopeDept,
|
||||
consts.SysRoleDataScopeDeptAndBelow,
|
||||
consts.SysRoleDataScopePersonal,
|
||||
consts.SysRoleDataScopeDeptAndBelowOrPersonal,
|
||||
consts.SysRoleDataScopeOrgAndNextLevel,
|
||||
consts.SysRoleDataScopeOrgNextLevel:
|
||||
return dataScope
|
||||
default:
|
||||
return consts.SysRoleMenuDataScopeRole
|
||||
}
|
||||
}
|
||||
|
||||
// clearRoleUsersAccessCache removes stale permission caches after role changes.
|
||||
func clearRoleUsersAccessCache(ctx context.Context, roleIds ...int64) {
|
||||
if len(roleIds) == 0 {
|
||||
return
|
||||
}
|
||||
relations := make([]*entity.SysUserRole, 0)
|
||||
if err := dao.SysUserRole.Ctx(ctx).
|
||||
Fields(dao.SysUserRole.Columns().UserId).
|
||||
Where(dao.SysUserRole.Columns().TenantId, contexts.GetTenantId(ctx)).
|
||||
WhereIn(dao.SysUserRole.Columns().RoleId, roleIds).
|
||||
Scan(&relations); err != nil {
|
||||
g.Log().Warningf(ctx, "clear role users access cache failed, roleIds:%v, err:%+v", roleIds, err)
|
||||
return
|
||||
}
|
||||
seen := make(map[int64]struct{}, len(relations))
|
||||
for _, relation := range relations {
|
||||
if relation == nil || relation.UserId <= 0 {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[relation.UserId]; ok {
|
||||
continue
|
||||
}
|
||||
seen[relation.UserId] = struct{}{}
|
||||
if err := mcache.RemoveUserAccessCodeList(ctx, relation.UserId); err != nil {
|
||||
g.Log().Warningf(ctx, "remove user access code cache failed, userId:%d, err:%+v", relation.UserId, err)
|
||||
}
|
||||
if err := mcache.RemoveUserRoleDataAccessCodeList(ctx, relation.UserId); err != nil {
|
||||
g.Log().Warningf(ctx, "remove user role data access code cache failed, userId:%d, err:%+v", relation.UserId, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义角色部门数据权限
|
||||
func (s *sSysRole) RoleDept(ctx context.Context, roleId int64, deptIds []int64) (err error) {
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
// 查询角色部门
|
||||
roleDept := make([]*entity.SysRoleDept, 0)
|
||||
err = dao.SysRoleDept.Ctx(ctx).Where(dao.SysRoleDept.Columns().TenantId, tenantId).Where(dao.SysRoleDept.Columns().RoleId, roleId).Scan(&roleDept)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currDeptIds := make([]int64, 0)
|
||||
for _, dept := range roleDept {
|
||||
currDeptIds = append(currDeptIds, dept.DeptId)
|
||||
}
|
||||
|
||||
delDeptIds := utility.ArrayRightDiff(currDeptIds, deptIds)
|
||||
addDeptIds := utility.ArrayRightDiff(deptIds, currDeptIds)
|
||||
|
||||
// 删除角色部门
|
||||
if len(delDeptIds) > 0 {
|
||||
_, err = dao.SysRoleDept.Ctx(ctx).Where(dao.SysRoleDept.Columns().TenantId, tenantId).Where(dao.SysRoleDept.Columns().RoleId, roleId).WhereIn(dao.SysRoleDept.Columns().DeptId, delDeptIds).Delete()
|
||||
}
|
||||
|
||||
// 新增角色部门
|
||||
if len(addDeptIds) > 0 {
|
||||
addData := make([]map[string]interface{}, 0)
|
||||
for _, deptId := range addDeptIds {
|
||||
addData = append(addData, map[string]interface{}{
|
||||
dao.SysRoleDept.Columns().TenantId: tenantId,
|
||||
dao.SysRoleDept.Columns().RoleId: roleId,
|
||||
dao.SysRoleDept.Columns().DeptId: deptId,
|
||||
})
|
||||
}
|
||||
_, err = dao.SysRoleDept.Ctx(ctx).Data(addData).Insert()
|
||||
}
|
||||
if err == nil {
|
||||
clearRoleUsersAccessCache(ctx, roleId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑角色数据权限
|
||||
func (s *sSysRole) EditRoleDataScope(ctx context.Context, model *model.SysRoleDataScopeEditParam) (err error) {
|
||||
template, templateErr := rolepolicy.Template(ctx)
|
||||
if templateErr != nil {
|
||||
return templateErr
|
||||
}
|
||||
if !contexts.IsSuperAdmin(ctx) || (template != nil && template.RoleId == model.RoleId) {
|
||||
if model.DataScope == consts.SysRoleDataScopeAll || model.DataScope == consts.SysRoleDataScopeCustom || model.DataScope == "" {
|
||||
return gerror.New("不允许全部、自定义或空数据权限")
|
||||
}
|
||||
}
|
||||
if err = s.ensureRolesNotInherited(ctx, []int64{model.RoleId}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data := do.SysRole{}
|
||||
gconv.Struct(model, &data)
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
|
||||
if template != nil && template.RoleId == model.RoleId && model.DataScope != consts.SysRoleDataScopeDeptAndBelow {
|
||||
return gerror.New("代理模板数据范围必须为本机构及下级")
|
||||
}
|
||||
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
if _, updateErr := s.Model(ctx).Data(data).Where(dao.SysRole.Columns().RoleId, model.RoleId).OmitNil().Update(); updateErr != nil {
|
||||
return updateErr
|
||||
}
|
||||
if err := s.RoleDept(ctx, model.RoleId, model.DeptIds); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = rolesync.EnqueueRole(ctx, model.RoleId, contexts.GetUserId(ctx)); err != nil {
|
||||
return err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysRoleUpdate, model.RoleId)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysRole) ensureRolesNotInherited(ctx context.Context, roleIds []int64) error {
|
||||
if len(roleIds) == 0 {
|
||||
return nil
|
||||
}
|
||||
if contexts.IsSuperAdmin(ctx) {
|
||||
return nil
|
||||
}
|
||||
count, err := g.Model(dao.SysRole.Table()+" r").
|
||||
LeftJoin(dao.SysDept.Table()+" d", "d.dept_id = r.dept_id AND d.tenant_id = r.tenant_id").
|
||||
WhereIn("r.role_id", roleIds).
|
||||
Where("r.deleted_at IS NULL").
|
||||
Where("d.deleted_at IS NULL").
|
||||
Where("r.created_dept > 0").
|
||||
Where("r.dept_id <> r.created_dept").
|
||||
Where("d.parent_id = r.created_dept").
|
||||
Where("r.tenant_id", contexts.GetTenantId(ctx)).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("上级下发角色由上级统一维护,当前组织只能查看和使用")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sSysRole) ensureRolesHaveNoInheritedChildren(ctx context.Context, roleIds []int64) error {
|
||||
if len(roleIds) == 0 {
|
||||
return nil
|
||||
}
|
||||
count, err := g.Model(dao.SysRole.Table()+" r").
|
||||
LeftJoin(dao.SysDept.Table()+" child_dept", "child_dept.dept_id = r.dept_id AND child_dept.tenant_id = r.tenant_id").
|
||||
Where("r.created_dept IN (SELECT dept_id FROM sys_role WHERE role_id IN (?) AND is_pass = 1 AND deleted_at IS NULL)", roleIds).
|
||||
Where("r.deleted_at IS NULL").
|
||||
Where("child_dept.deleted_at IS NULL").
|
||||
Where("r.created_dept > 0").
|
||||
Where("r.dept_id <> r.created_dept").
|
||||
Where("child_dept.parent_id = r.created_dept").
|
||||
Where("r.tenant_id", contexts.GetTenantId(ctx)).
|
||||
Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("该角色已下发给下级,不能删除;可关闭后续下发或停用角色")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type sSysSocial struct {
|
||||
}
|
||||
|
||||
func NewSysSocial() *sSysSocial {
|
||||
return &sSysSocial{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysSocial(NewSysSocial())
|
||||
}
|
||||
|
||||
// 查询社会化关系表
|
||||
func (s *sSysSocial) List(ctx context.Context, query *model.SysSocialListParam, page *request.PageInfo) (items []*model.SysSocialListModel, total int, err error) {
|
||||
if query.UserId == 0 {
|
||||
query.UserId = contexts.GetUserId(ctx)
|
||||
}
|
||||
|
||||
db := dao.SysSocial.Ctx(ctx)
|
||||
if query.UserId != 0 {
|
||||
db = db.Where(dao.SysSocial.Columns().UserId, query.UserId)
|
||||
}
|
||||
if query.Source != "" {
|
||||
db = db.Where(dao.SysSocial.Columns().Source, query.Source)
|
||||
}
|
||||
if query.OpenId != "" {
|
||||
db = db.Where(dao.SysSocial.Columns().OpenId, query.OpenId)
|
||||
}
|
||||
total, err = db.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = db.Page(page.Page, page.PageSize).Scan(&items)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// 删除社会化关系表
|
||||
func (s *sSysSocial) Delete(ctx context.Context, id int64) (err error) {
|
||||
userId := contexts.GetUserId(ctx)
|
||||
_, err = dao.SysSocial.Ctx(ctx).WherePri(id).Where(dao.SysSocial.Columns().UserId, userId).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *sSysSocial) Create(ctx context.Context, social *model.SysSocialSaveParam) (err error) {
|
||||
g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
||||
// 查询是否存在
|
||||
exist, err := dao.SysSocial.Ctx(ctx).Where(dao.SysSocial.Columns().OpenId, social.OpenId).Where(dao.SysSocial.Columns().Source, social.Source).Where(dao.SysSocial.Columns().UserId, social.UserId).Exist()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
// 绑定信息已存在
|
||||
g.Log().Errorf(ctx, "绑定信息已存在, openId: %s, source: %s, userId: %d", social.OpenId, social.Source, social.UserId)
|
||||
return gerror.New("绑定信息已存在")
|
||||
}
|
||||
|
||||
_, err = dao.SysSocial.Ctx(ctx).Data(social).OmitEmpty().Save()
|
||||
return err
|
||||
})
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,502 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysTenant struct {
|
||||
}
|
||||
|
||||
func NewSysTenant() *sSysTenant {
|
||||
return &sSysTenant{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
s := NewSysTenant()
|
||||
service.RegisterSysTenant(s)
|
||||
}
|
||||
func (l *sSysTenant) ModelQuery(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: false,
|
||||
})
|
||||
}
|
||||
return handler.Model(service.MemoryDB().DB(ctx).Ctx(ctx).Model(dao.SysTenant.Table()), option...)
|
||||
}
|
||||
|
||||
// 获取租户信息
|
||||
func (l *sSysTenant) View(ctx context.Context, param *model.SysTenantViewParam) (data *model.SysTenantViewModel, err error) {
|
||||
db := l.ModelQuery(ctx)
|
||||
if param.Id != 0 {
|
||||
db = db.Where(dao.SysTenant.Columns().Id, param.Id)
|
||||
} else if param.TenantId != "" {
|
||||
db = db.Where(dao.SysTenant.Columns().TenantId, param.TenantId)
|
||||
} else {
|
||||
return nil, errors.New("参数错误")
|
||||
}
|
||||
err = db.Scan(&data)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取租户列表
|
||||
func (l *sSysTenant) List(ctx context.Context, param *model.SysTenantListParam) (data []*model.SysTenantListModel, total int, err error) {
|
||||
|
||||
// db := dao.SysTenant.Ctx(ctx)
|
||||
// 内存数据库优化
|
||||
db := l.ModelQuery(ctx)
|
||||
if param.TenantId != "" {
|
||||
db = db.Where(dao.SysTenant.Columns().TenantId, param.TenantId)
|
||||
}
|
||||
if param.ContactUserName != "" {
|
||||
db = db.WhereLike(dao.SysTenant.Columns().ContactUserName, "%"+param.ContactUserName+"%")
|
||||
}
|
||||
if param.ContactPhone != "" {
|
||||
db = db.WhereLike(dao.SysTenant.Columns().ContactPhone, "%"+param.ContactPhone+"%")
|
||||
}
|
||||
if param.CompanyName != "" {
|
||||
db = db.WhereLike(dao.SysTenant.Columns().CompanyName, "%"+param.CompanyName+"%")
|
||||
}
|
||||
if param.LicenseNumber != "" {
|
||||
db = db.WhereLike(dao.SysTenant.Columns().LicenseNumber, "%"+param.LicenseNumber+"%")
|
||||
}
|
||||
if param.Status != "" {
|
||||
db = db.Where(dao.SysTenant.Columns().Status, param.Status)
|
||||
}
|
||||
total, err = db.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = db.Page(param.Page, param.PageSize).OrderAsc(dao.SysTenant.Columns().PackageId).Scan(&data)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return data, total, nil
|
||||
}
|
||||
|
||||
func (l *sSysTenant) Add(ctx context.Context, param *model.SysTenantAddParam) (output *model.SysTenantAddModel, err error) {
|
||||
data := &do.SysTenant{}
|
||||
gconv.Struct(param, &data)
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.CreatedBy = contexts.GetUserId(ctx)
|
||||
data.CreatedDept = contexts.GetDeptId(ctx)
|
||||
// 查询租户套餐
|
||||
tenantPackage, err := service.SysTenantPackage().View(ctx, &model.SysTenantPackageViewParam{
|
||||
PackageId: param.PackageId,
|
||||
})
|
||||
if err != nil || tenantPackage == nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add SysTenantPackage().View err: %v, packageId: %d, param: %+v",
|
||||
err, param.PackageId, param)
|
||||
err = errors.New("租户套餐不存在")
|
||||
return nil, err
|
||||
}
|
||||
if tenantPackage.MenuIds == "" {
|
||||
err = errors.New("租户套餐菜单为空")
|
||||
return nil, err
|
||||
}
|
||||
menuIds := strings.Split(tenantPackage.MenuIds, ",")
|
||||
|
||||
tenantId := ""
|
||||
deptId := int64(0)
|
||||
roleId := int64(0)
|
||||
userId := int64(0)
|
||||
sysConfigIds := make([]int64, 0)
|
||||
err = g.DB().Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error {
|
||||
data.TenantId = "100000"
|
||||
id, err := tx.Ctx(ctx).Model(dao.SysTenant.Table()).Data(data).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add SysTenant.Table().InsertAndGetId err: %v, data: %+v", err, data)
|
||||
return err
|
||||
}
|
||||
tenantId = fmt.Sprintf("%06d", 100000+id)
|
||||
|
||||
// 创建部门
|
||||
dataDeptInsert := do.SysDept{}
|
||||
dataDeptInsert.TenantId = tenantId
|
||||
dataDeptInsert.DeptName = param.CompanyName
|
||||
dataDeptInsert.ParentId = 0
|
||||
dataDeptInsert.OrderNum = 0
|
||||
dataDeptInsert.DeptType = 1 // 0:部门 1是公司
|
||||
dataDeptInsert.Status = consts.SysDeptStatusNormal
|
||||
dataDeptInsert.CreatedBy = contexts.GetUserId(ctx)
|
||||
dataDeptInsert.CreatedAt = gtime.Now()
|
||||
dataDeptInsert.UpdatedBy = contexts.GetUserId(ctx)
|
||||
dataDeptInsert.UpdatedAt = gtime.Now()
|
||||
deptId, err = tx.Ctx(ctx).Model(dao.SysDept.Table()).Data(dataDeptInsert).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add SysDept.Table().InsertAndGetId err: %v, data: %+v", err, dataDeptInsert)
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建角色
|
||||
dataRoleInsert := do.SysRole{}
|
||||
dataRoleInsert.TenantId = tenantId
|
||||
dataRoleInsert.RoleName = "租户管理员"
|
||||
dataRoleInsert.RoleKey = "tenantAdmin"
|
||||
dataRoleInsert.RoleSort = 1
|
||||
dataRoleInsert.Status = "0"
|
||||
dataRoleInsert.DataScope = consts.SysRoleDataScopeAll
|
||||
dataRoleInsert.MenuCheckStrictly = 0
|
||||
dataRoleInsert.DeptCheckStrictly = 0
|
||||
dataRoleInsert.CreatedBy = contexts.GetUserId(ctx)
|
||||
dataRoleInsert.CreatedAt = gtime.Now()
|
||||
dataRoleInsert.UpdatedBy = contexts.GetUserId(ctx)
|
||||
dataRoleInsert.UpdatedAt = gtime.Now()
|
||||
roleId, err = tx.Ctx(ctx).Model(dao.SysRole.Table()).Data(dataRoleInsert).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add SysRole.Table().InsertAndGetId err: %v, data: %+v", err, dataRoleInsert)
|
||||
return err
|
||||
}
|
||||
|
||||
// 角色关联菜单
|
||||
dataRoleMenuInserts := []*do.SysRoleMenu{}
|
||||
for _, menuId := range menuIds {
|
||||
dataRoleMenuInserts = append(dataRoleMenuInserts, &do.SysRoleMenu{
|
||||
TenantId: tenantId,
|
||||
RoleId: roleId,
|
||||
MenuId: menuId,
|
||||
DataScope: consts.SysRoleMenuDataScopeRole,
|
||||
})
|
||||
}
|
||||
_, err = tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Data(dataRoleMenuInserts).OmitNil().Insert()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add SysRoleMenu.Table().Insert err: %v, data: %+v", err, dataRoleMenuInserts)
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建系统管理员
|
||||
salt := utility.RandomString(5)
|
||||
password := utility.PasswordEncrypt(param.Password, salt)
|
||||
|
||||
dataUserInsert := do.SysUser{}
|
||||
dataUserInsert.TenantId = tenantId
|
||||
dataUserInsert.DeptId = deptId
|
||||
dataUserInsert.NickName = param.ContactUserName
|
||||
dataUserInsert.UserName = param.Username
|
||||
dataUserInsert.Password = password
|
||||
dataUserInsert.UserType = consts.SysUserTypeSys
|
||||
dataUserInsert.Salt = salt
|
||||
dataUserInsert.Phonenumber = param.ContactPhone
|
||||
dataUserInsert.Email = ""
|
||||
dataUserInsert.Sex = consts.SysUserSexUnknown
|
||||
dataUserInsert.Status = consts.SysUserStatusNormal
|
||||
dataUserInsert.CreatedDept = contexts.GetDeptId(ctx)
|
||||
dataUserInsert.CreatedBy = contexts.GetUserId(ctx)
|
||||
dataUserInsert.CreatedAt = gtime.Now()
|
||||
dataUserInsert.UpdatedBy = contexts.GetUserId(ctx)
|
||||
dataUserInsert.UpdatedAt = gtime.Now()
|
||||
|
||||
userId, err = tx.Ctx(ctx).Model(dao.SysUser.Table()).Data(dataUserInsert).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add SysUser.Table().InsertAndGetId err: %v, data: %+v", err, dataUserInsert)
|
||||
return err
|
||||
}
|
||||
// 修改租户信息
|
||||
_, err = tx.Ctx(ctx).Model(dao.SysTenant.Table()).Data(g.Map{
|
||||
dao.SysTenant.Columns().TenantId: tenantId,
|
||||
dao.SysTenant.Columns().AdminRoleId: roleId,
|
||||
dao.SysTenant.Columns().AdminDeptId: deptId,
|
||||
dao.SysTenant.Columns().AdminUserId: userId,
|
||||
}).Where(dao.SysTenant.Columns().Id, id).Update()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add SysTenant.Table().Update err: %v, data: %+v", err, data)
|
||||
return err
|
||||
}
|
||||
// 修改部门负责人
|
||||
dataDeptUpdate := do.SysDept{}
|
||||
dataDeptUpdate.DeptId = deptId
|
||||
dataDeptUpdate.Leader = userId
|
||||
dataDeptUpdate.UpdatedBy = contexts.GetUserId(ctx)
|
||||
dataDeptUpdate.UpdatedAt = gtime.Now()
|
||||
_, err = tx.Ctx(ctx).Model(dao.SysDept.Table()).Data(dataDeptUpdate).OmitNil().Where(dao.SysDept.Columns().DeptId, deptId).Update()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add SysDept.Table().Update err: %v, data: %+v", err, dataDeptUpdate)
|
||||
return err
|
||||
}
|
||||
output = &model.SysTenantAddModel{
|
||||
Id: id,
|
||||
}
|
||||
// 角色关联用户
|
||||
dataUserRoleInserts := []*do.SysUserRole{}
|
||||
dataUserRoleInserts = append(dataUserRoleInserts, &do.SysUserRole{
|
||||
TenantId: tenantId,
|
||||
UserId: userId,
|
||||
RoleId: roleId,
|
||||
})
|
||||
_, err = tx.Ctx(ctx).Model(dao.SysUserRole.Table()).Data(dataUserRoleInserts).OmitNil().Insert()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add SysUserRole.Table().Insert err: %v, data: %+v", err, dataUserRoleInserts)
|
||||
return err
|
||||
}
|
||||
// 插入系统参数
|
||||
sysConfigIds, err = l.BatchInsertSysParam(ctx, tx, tenantId, userId, deptId)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add batchInsertSysParam err: %v, tenantId: %s, createdBy: %d, createdDept: %d", err, tenantId, contexts.GetUserId(ctx), contexts.GetDeptId(ctx))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add err: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
// 更新缓存
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysDeptCreate, deptId)
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysRoleCreate, roleId)
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysTenantCreate, output.Id)
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyUserCreate, userId)
|
||||
for _, sysConfigId := range sysConfigIds {
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysConfigCreate, sysConfigId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysTenant) Edit(ctx context.Context, param *model.SysTenantEditParam) (output *model.SysTenantEditModel, err error) {
|
||||
if param.Id == 0 {
|
||||
return nil, errors.New("参数错误")
|
||||
}
|
||||
data := &do.SysTenant{}
|
||||
gconv.Struct(param, &data)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
db := dao.SysTenant.Ctx(ctx)
|
||||
db = db.Where(dao.SysTenant.Columns().Id, param.Id)
|
||||
_, err = db.Data(data).OmitNil().Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysTenantEditModel{
|
||||
Id: param.Id,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysTenantUpdate, param.Id)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysTenant) Delete(ctx context.Context, param *model.SysTenantDeleteParam) (output *model.SysTenantDeleteModel, err error) {
|
||||
if len(param.Ids) == 0 {
|
||||
return nil, errors.New("参数错误")
|
||||
}
|
||||
if slices.Contains(param.Ids, 1) {
|
||||
return nil, errors.New("不能删除默认租户")
|
||||
}
|
||||
data := &do.SysTenant{}
|
||||
gconv.Struct(param, &data)
|
||||
data.DeletedAt = gtime.Now()
|
||||
data.DeletedBy = contexts.GetUserId(ctx)
|
||||
|
||||
db := dao.SysTenant.Ctx(ctx)
|
||||
db = db.WhereIn(dao.SysTenant.Columns().Id, param.Ids)
|
||||
_, err = db.Data(data).OmitNil().Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysTenantDeleteModel{
|
||||
Ids: param.Ids,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysTenantDelete, param.Ids)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysTenant) Status(ctx context.Context, param *model.SysTenantStatusParam) (output *model.SysTenantStatusModel, err error) {
|
||||
if param.Id == 0 {
|
||||
return nil, errors.New("参数错误")
|
||||
}
|
||||
data := &do.SysTenant{}
|
||||
gconv.Struct(param, &data)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
db := dao.SysTenant.Ctx(ctx)
|
||||
db = db.Where(dao.SysTenant.Columns().Id, param.Id)
|
||||
_, err = db.Data(data).OmitNil().Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysTenantStatusModel{
|
||||
Id: param.Id,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysTenantUpdate, param.Id)
|
||||
return
|
||||
}
|
||||
|
||||
// 新建租户插入系统参数表数据
|
||||
func (l *sSysTenant) BatchInsertSysParam(ctx context.Context, tx gdb.TX, tenantId string, createdBy int64, createdDept int64) (ids []int64, err error) {
|
||||
sql := `
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), '初始化密码 123456');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), '深色主题theme-dark,浅色主题theme-light');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), '是否开启注册用户功能(true开启,false关闭)');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', 'OSS预览列表资源开关', 'sys.oss.previewListResource', 'true', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), 'true:开启, false:关闭');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '上传文件类型', 'sys.oss.fileType', 'txt,md,doc,docx,pdf,xls,xlsx,ppt,pptx,txt,jpg,jpeg,png,gif,mp4,avi,mov,mp3,wav,m4a,csv,json,application/json,bin,hex,fw,zip', 'Y', 103, 1, SYSDATE(), '');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '上传图片类型', 'sys.oss.imgType', 'jpg,jpeg,png,gif,webp', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), '');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '上传文件大小', 'sys.oss.fileSize', '20M', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), '');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '上传图片大小', 'sys.oss.imgSize', '5M', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), '');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '上传文件Url路径', 'sys.oss.urlPath', 'resource/upload|/upload', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), '\"http://ab.com/\" 或 \"/\" 或 \"resource/upload|http://ab.com/upload\"');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '是否启动在线用户强制退出', 'sys.online.forceLogout', 'true', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), 'true开启,false关闭');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '系统启用的监控通道', 'iot.default.channel.gb28181', '0', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), '');
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '报警资源匹配时间', 'iot.alarm.image.timesec', '10', 'N', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), NULL);
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '自主注册组织ID', 'iot.register.dept', '0', 'N', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), NULL);
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '自主注册角色ID', 'iot.register.role', '0', 'N', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), NULL);
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '系统默认MQTT渠道', 'iot.default.channel.mqtt', '0', 'N', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), NULL);
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '系统默认TCP渠道', 'iot.default.channel.tcp', '0', 'N', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), NULL);
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '系统默认UDP渠道', 'iot.default.channel.udp', '0', 'N', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), NULL);
|
||||
INSERT INTO sys_config (tenant_id, config_name, config_key, config_value, config_type, created_dept, created_by, created_at, remark) VALUES ('{{.TenantId}}', '项目API默认地址', 'iot.project.api.default.addr', 'http://127.0.0.1:8510', 'Y', {{.CreatedDept}}, {{.CreatedBy}}, SYSDATE(), '');
|
||||
`
|
||||
tmpl, err := template.New("example").Parse(sql)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data := struct {
|
||||
TenantId string
|
||||
CreatedBy int64
|
||||
CreatedDept int64
|
||||
}{
|
||||
TenantId: tenantId,
|
||||
CreatedBy: createdBy,
|
||||
CreatedDept: createdDept,
|
||||
}
|
||||
var b strings.Builder
|
||||
err = tmpl.Execute(&b, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sql = b.String()
|
||||
g.Log().Infof(ctx, "sSysTenant.batchInsertSysParam sql: %s", sql)
|
||||
|
||||
sqlArr := strings.Split(sql, ";")
|
||||
ids = make([]int64, 0)
|
||||
for _, sql := range sqlArr {
|
||||
sql = strings.ReplaceAll(sql, "\n", "")
|
||||
sql = strings.TrimLeft(sql, " ")
|
||||
sql = strings.TrimRight(sql, " ")
|
||||
if sql == "" {
|
||||
continue
|
||||
}
|
||||
res, err := tx.Ctx(ctx).Exec(sql)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.batchInsertSysParam tx.Ctx(ctx).Exec err: %v, sql: %s", err, sql)
|
||||
return nil, err
|
||||
}
|
||||
lastInsertId, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.batchInsertSysParam LastInsertId err: %v, sql: %s", err, sql)
|
||||
return nil, err
|
||||
}
|
||||
ids = append(ids, lastInsertId)
|
||||
}
|
||||
|
||||
g.Log().Infof(ctx, "sSysTenant.batchInsertSysParam ids: %v", ids)
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// 同步租户菜单
|
||||
func (l *sSysTenant) SyncTenantMenu(ctx context.Context, tenantId string) (err error) {
|
||||
tenantInfo, err := l.View(ctx, &model.SysTenantViewParam{
|
||||
TenantId: tenantId,
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.SyncTenantMenu SysTenant().View err: %v, tenantId: %s", err, tenantId)
|
||||
return err
|
||||
}
|
||||
tenantPackage, err := service.SysTenantPackage().View(ctx, &model.SysTenantPackageViewParam{
|
||||
PackageId: tenantInfo.PackageId,
|
||||
})
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.SyncTenantMenu SysTenantPackage().View err: %v, tenantId: %s, packageId: %d", err, tenantId, tenantInfo.PackageId)
|
||||
return err
|
||||
}
|
||||
if tenantPackage == nil {
|
||||
return errors.New("租户套餐不存在")
|
||||
}
|
||||
menuIds := strings.Split(tenantPackage.MenuIds, ",")
|
||||
// 获取角色对应菜单
|
||||
roleMenuList := make([]*entity.SysRoleMenu, 0)
|
||||
err = dao.SysRoleMenu.Ctx(ctx).
|
||||
Where(dao.SysRoleMenu.Columns().TenantId, tenantId).
|
||||
Where(dao.SysRoleMenu.Columns().RoleId, tenantInfo.AdminRoleId).
|
||||
Scan(&roleMenuList)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.SyncTenantMenu SysRole().GetRoleMenu err: %v, tenantId: %s, roleId: %d", err, tenantId, tenantInfo.AdminRoleId)
|
||||
return err
|
||||
}
|
||||
insertMenuIds := make([]do.SysRoleMenu, 0)
|
||||
for _, menuId := range menuIds {
|
||||
found := false
|
||||
for _, roleMenu := range roleMenuList {
|
||||
if roleMenu.MenuId == gconv.Int64(menuId) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
insertMenuIds = append(insertMenuIds, do.SysRoleMenu{
|
||||
TenantId: tenantId,
|
||||
RoleId: tenantInfo.AdminRoleId,
|
||||
MenuId: gconv.Int64(menuId),
|
||||
DataScope: consts.SysRoleMenuDataScopeRole,
|
||||
})
|
||||
}
|
||||
}
|
||||
delMenuIds := make([]int64, 0)
|
||||
for _, roleMenu := range roleMenuList {
|
||||
if !slices.Contains(menuIds, gconv.String(roleMenu.MenuId)) {
|
||||
delMenuIds = append(delMenuIds, roleMenu.MenuId)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(delMenuIds) > 0 {
|
||||
_, err = dao.SysRoleMenu.Ctx(ctx).Where(dao.SysRoleMenu.Columns().TenantId, tenantId).WhereIn(dao.SysRoleMenu.Columns().RoleId, tenantInfo.AdminRoleId).
|
||||
WhereIn(dao.SysRoleMenu.Columns().MenuId, delMenuIds).Delete()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.SyncTenantMenu SysRoleMenu.Ctx(ctx).Delete err: %v, tenantId: %s, roleId: %d, delMenuIds: %v", err, tenantId, tenantInfo.AdminRoleId, delMenuIds)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(insertMenuIds) > 0 {
|
||||
_, err = dao.SysRoleMenu.Ctx(ctx).Data(insertMenuIds).Insert()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.SyncTenantMenu SysRoleMenu.Ctx(ctx).Insert err: %v, tenantId: %s, roleId: %d, insertMenuIds: %v", err, tenantId, tenantInfo.AdminRoleId, insertMenuIds)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 查询用户ID是否是租户管理员
|
||||
func (l *sSysTenant) IsTenantAdmin(ctx context.Context, userIds []int64) (isAdminUserIds []int64, err error) {
|
||||
entityList := make([]*entity.SysTenant, 0)
|
||||
err = dao.SysTenant.Ctx(ctx).WhereIn(dao.SysTenant.Columns().AdminUserId, userIds).Scan(&entityList)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
isAdminUserIds = make([]int64, 0)
|
||||
for _, entity := range entityList {
|
||||
isAdminUserIds = append(isAdminUserIds, entity.AdminUserId)
|
||||
}
|
||||
return isAdminUserIds, nil
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysTenantPackage struct {
|
||||
}
|
||||
|
||||
func NewSysTenantPackage() *sSysTenantPackage {
|
||||
return &sSysTenantPackage{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysTenantPackage(NewSysTenantPackage())
|
||||
}
|
||||
|
||||
// 获取租户套餐
|
||||
func (l *sSysTenantPackage) View(ctx context.Context, param *model.SysTenantPackageViewParam) (data *model.SysTenantPackageViewModel, err error) {
|
||||
if param.PackageId == 0 {
|
||||
return nil, errors.New("参数错误")
|
||||
}
|
||||
db := dao.SysTenantPackage.Ctx(ctx)
|
||||
db = db.Where(dao.SysTenantPackage.Columns().PackageId, param.PackageId)
|
||||
err = db.Scan(&data)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysTenantPackage) List(ctx context.Context, param *model.SysTenantPackageListParam) (data []*model.SysTenantPackageListModel, total int, err error) {
|
||||
db := dao.SysTenantPackage.Ctx(ctx)
|
||||
if param.PackageName != "" {
|
||||
db = db.WhereLike(dao.SysTenantPackage.Columns().PackageName, "%"+param.PackageName+"%")
|
||||
}
|
||||
|
||||
total, err = db.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err = db.Page(param.Page, param.PageSize).OrderAsc(dao.SysTenantPackage.Columns().PackageId).Scan(&data)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return data, total, nil
|
||||
}
|
||||
|
||||
func (l *sSysTenantPackage) Add(ctx context.Context, param *model.SysTenantPackageAddParam) (output *model.SysTenantPackageAddModel, err error) {
|
||||
data := &do.SysTenantPackage{}
|
||||
gconv.Struct(param, &data)
|
||||
data.CreatedAt = gtime.Now()
|
||||
data.CreatedBy = contexts.GetUserId(ctx)
|
||||
data.CreatedDept = contexts.GetDeptId(ctx)
|
||||
|
||||
db := dao.SysTenantPackage.Ctx(ctx)
|
||||
id, err := db.Data(data).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysTenantPackageAddModel{
|
||||
PackageId: id,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysTenantPackage) Edit(ctx context.Context, param *model.SysTenantPackageEditParam) (output *model.SysTenantPackageEditModel, err error) {
|
||||
if param.PackageId == 0 {
|
||||
return nil, errors.New("参数错误")
|
||||
}
|
||||
data := &do.SysTenantPackage{}
|
||||
gconv.Struct(param, &data)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
|
||||
db := dao.SysTenantPackage.Ctx(ctx)
|
||||
db = db.Where(dao.SysTenantPackage.Columns().PackageId, param.PackageId)
|
||||
_, err = db.Data(data).OmitNil().Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysTenantPackageEditModel{
|
||||
PackageId: param.PackageId,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysTenantPackage) Delete(ctx context.Context, param *model.SysTenantPackageDeleteParam) (output *model.SysTenantPackageDeleteModel, err error) {
|
||||
if len(param.PackageIds) == 0 {
|
||||
return nil, errors.New("参数错误")
|
||||
}
|
||||
data := &do.SysTenantPackage{}
|
||||
gconv.Struct(param, &data)
|
||||
data.DeletedAt = gtime.Now()
|
||||
data.DeletedBy = contexts.GetUserId(ctx)
|
||||
|
||||
db := dao.SysTenantPackage.Ctx(ctx)
|
||||
db = db.WhereIn(dao.SysTenantPackage.Columns().PackageId, param.PackageIds)
|
||||
_, err = db.Data(data).OmitNil().Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysTenantPackageDeleteModel{
|
||||
PackageIds: param.PackageIds,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (l *sSysTenantPackage) Status(ctx context.Context, param *model.SysTenantPackageStatusParam) (output *model.SysTenantPackageStatusModel, err error) {
|
||||
if param.PackageId == 0 {
|
||||
return nil, errors.New("参数错误")
|
||||
}
|
||||
data := &do.SysTenantPackage{}
|
||||
gconv.Struct(param, &data)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
|
||||
db := dao.SysTenantPackage.Ctx(ctx)
|
||||
db = db.Where(dao.SysTenantPackage.Columns().PackageId, param.PackageId)
|
||||
_, err = db.Data(data).OmitNil().Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
output = &model.SysTenantPackageStatusModel{
|
||||
PackageId: param.PackageId,
|
||||
}
|
||||
return
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,140 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysUserOnline struct {
|
||||
}
|
||||
|
||||
func NewSysUserOnline() *sSysUserOnline {
|
||||
return &sSysUserOnline{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysUserOnline(NewSysUserOnline())
|
||||
}
|
||||
|
||||
func (s *sSysUserOnline) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.SysUserOnline.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
func (s *sSysUserOnline) Add(ctx context.Context, userOnline *model.SysUserOnlineAddModel) (err error) {
|
||||
data := do.SysUserOnline{}
|
||||
gconv.Struct(userOnline, &data)
|
||||
_, err = s.Model(ctx).Data(data).OmitEmpty().Save()
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysUserOnline) cleanupExpired(ctx context.Context, tenantId string) (err error) {
|
||||
cols := dao.SysUserOnline.Columns()
|
||||
var data []*model.SysUserOnlineViewModel
|
||||
err = s.Model(ctx).
|
||||
Where(cols.TenantId, tenantId).
|
||||
WhereLT(cols.ExpireTime, gtime.Now()).
|
||||
Scan(&data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
ids := make([]int64, 0, len(data))
|
||||
for _, v := range data {
|
||||
ids = append(ids, v.OnlineId)
|
||||
if v.Token != "" {
|
||||
service.SysAuth().DeleteToken(ctx, v.Token)
|
||||
}
|
||||
}
|
||||
_, err = s.Model(ctx).WhereIn(cols.OnlineId, ids).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *sSysUserOnline) List(ctx context.Context, query *model.SysUserOnlineListParam, page *request.PageInfo) (items []*model.SysUserOnlineListModel, total int, err error) {
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
if err = s.cleanupExpired(ctx, tenantId); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
db := s.Model(ctx).Where(dao.SysUserOnline.Columns().TenantId, tenantId)
|
||||
if query.UserName != "" {
|
||||
db = db.Where(dao.SysUserOnline.Columns().UserName, query.UserName)
|
||||
}
|
||||
if query.ClientKey != "" {
|
||||
db = db.WhereLike(dao.SysUserOnline.Columns().ClientKey, "%"+query.ClientKey+"%")
|
||||
}
|
||||
if query.DeviceType != "" {
|
||||
db = db.Where(dao.SysUserOnline.Columns().DeviceType, query.DeviceType)
|
||||
}
|
||||
if query.Ipaddr != "" {
|
||||
db = db.Where(dao.SysUserOnline.Columns().Ipaddr, query.Ipaddr)
|
||||
}
|
||||
if len(query.LoginTime) == 2 {
|
||||
startTime := gtime.NewFromStr(query.LoginTime[0])
|
||||
endTime := gtime.NewFromStr(query.LoginTime[1])
|
||||
db = db.WhereBetween(dao.SysUserOnline.Columns().LoginTime, startTime, endTime.EndOfDay())
|
||||
}
|
||||
if query.Token != "" {
|
||||
db = db.Where(dao.SysUserOnline.Columns().Token, query.Token)
|
||||
}
|
||||
total, err = db.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = db.Page(page.Page, page.PageSize).Order(dao.SysUserOnline.Columns().LoginTime, "desc").Scan(&items)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysUserOnline) Delete(ctx context.Context, ids []int64) (err error) {
|
||||
if len(ids) == 0 {
|
||||
return errors.New("ids不能为空")
|
||||
}
|
||||
var data = make([]*model.SysUserOnlineViewModel, 0)
|
||||
err = s.Model(ctx).WhereIn(dao.SysUserOnline.Columns().OnlineId, ids).Scan(&data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range data {
|
||||
service.SysAuth().DeleteToken(ctx, v.Token)
|
||||
}
|
||||
_, err = s.Model(ctx).WhereIn(dao.SysUserOnline.Columns().OnlineId, ids).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysUserOnline) DeleteByToken(ctx context.Context, token string) (err error) {
|
||||
var data *model.SysUserOnlineViewModel
|
||||
err = s.Model(ctx).Where(dao.SysUserOnline.Columns().Token, token).Scan(&data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if data != nil && data.Token != "" {
|
||||
service.SysAuth().DeleteToken(ctx, data.Token)
|
||||
}
|
||||
_, err = s.Model(ctx).Where(dao.SysUserOnline.Columns().Token, token).Delete()
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
// Package gen
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2026 LiXiujie
|
||||
// @Author Lxj <li@xiujie.cn>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
// @AutoGenerate Version
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
)
|
||||
|
||||
type sSysTTplatformSetting struct{}
|
||||
|
||||
func NewSysTTplatformSetting() *sSysTTplatformSetting {
|
||||
return &sSysTTplatformSetting{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysTTplatformSetting(NewSysTTplatformSetting())
|
||||
}
|
||||
|
||||
// Model 二级域名站点个性化配置表ORM模型
|
||||
func (s *sSysTTplatformSetting) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
|
||||
if len(option) == 0 {
|
||||
option = append(option, &handler.Option{
|
||||
FilterTenant: true,
|
||||
FilterAuth: true,
|
||||
})
|
||||
}
|
||||
return handler.Model(dao.TTplatformSetting.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// List 获取二级域名站点个性化配置表列表
|
||||
func (s *sSysTTplatformSetting) List(ctx context.Context, in *model.TTplatformSettingListParam) (list []*model.TTplatformSettingListModel, totalCount int, err error) {
|
||||
mod := s.Model(ctx)
|
||||
|
||||
// 字段过滤
|
||||
mod = mod.Fields(model.TTplatformSettingListModel{})
|
||||
|
||||
// 查询主键ID
|
||||
if in.Id > 0 {
|
||||
mod = mod.Where(dao.TTplatformSetting.Columns().Id, in.Id)
|
||||
}
|
||||
|
||||
// 查询状态 0启用 1禁用
|
||||
if in.Status > 0 {
|
||||
mod = mod.Where(dao.TTplatformSetting.Columns().Status, in.Status)
|
||||
}
|
||||
|
||||
// 查询创建时间
|
||||
if len(in.CreatedAt) == 2 {
|
||||
in.CreatedAt[1] = in.CreatedAt[1].EndOfDay()
|
||||
}
|
||||
if len(in.CreatedAt) == 2 {
|
||||
mod = mod.WhereBetween(dao.TTplatformSetting.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
|
||||
}
|
||||
|
||||
// 分页
|
||||
mod = mod.Page(in.Page, in.PageSize)
|
||||
|
||||
// 排序
|
||||
mod = mod.OrderAsc(dao.TTplatformSetting.Columns().Sort).OrderDesc(dao.TTplatformSetting.Columns().Id)
|
||||
|
||||
// 操作人摘要信息
|
||||
mod = mod.Hook(hook.MemberSummary)
|
||||
|
||||
// 查询数据
|
||||
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
|
||||
err = gerror.Wrap(err, "获取二级域名站点个性化配置表列表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Export 导出二级域名站点个性化配置表
|
||||
func (s *sSysTTplatformSetting) Export(ctx context.Context, in *model.TTplatformSettingListParam) (err error) {
|
||||
list, totalCount, err := s.List(ctx, in)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 字段的排序是依据tags的字段顺序,如果你不想使用默认的排序方式,可以直接定义 tags = []string{"字段名称", "字段名称2", ...}
|
||||
tags, err := convert.GetEntityDescTags(model.TTplatformSettingExportModel{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
fileName = "导出二级域名站点个性化配置表-" + gctx.CtxId(ctx)
|
||||
sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, (totalCount+in.PageSize-1)/in.PageSize, in.Page, len(list))
|
||||
exports []model.TTplatformSettingExportModel
|
||||
)
|
||||
|
||||
if err = gconv.Scan(list, &exports); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
|
||||
return
|
||||
}
|
||||
|
||||
// Edit 修改/新增二级域名站点个性化配置表
|
||||
func (s *sSysTTplatformSetting) Edit(ctx context.Context, in *model.TTplatformSettingEditParam) (err error) {
|
||||
// 验证'FullDomain'唯一
|
||||
if err = xgorm.IsUnique(ctx, &dao.TTplatformSetting, g.Map{dao.TTplatformSetting.Columns().FullDomain: in.FullDomain}, "完整域名已存在", in.Id); err != nil {
|
||||
return
|
||||
}
|
||||
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
|
||||
// 修改
|
||||
if in.Id > 0 {
|
||||
updatedBy := contexts.GetUserId(ctx)
|
||||
in.UpdatedBy = &updatedBy
|
||||
in.UpdatedAt = gtime.Now()
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
in.TenantId = &tenantId
|
||||
if _, err = s.Model(ctx).
|
||||
Fields(model.TTplatformSettingUpdateFields{}).
|
||||
WherePri(in.Id).Data(in).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "修改二级域名站点个性化配置表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 新增
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
in.TenantId = &tenantId
|
||||
createdDept := contexts.GetDeptId(ctx)
|
||||
in.CreatedDept = &createdDept
|
||||
createdBy := contexts.GetUserId(ctx)
|
||||
in.CreatedBy = &createdBy
|
||||
in.CreatedAt = gtime.Now()
|
||||
if _, err = s.Model(ctx).
|
||||
Fields(model.TTplatformSettingInsertFields{}).
|
||||
Data(in).Insert(); err != nil {
|
||||
err = gerror.Wrap(err, "新增二级域名站点个性化配置表失败,请稍后重试!")
|
||||
}
|
||||
return
|
||||
})
|
||||
}
|
||||
|
||||
// Delete 删除二级域名站点个性化配置表
|
||||
func (s *sSysTTplatformSetting) Delete(ctx context.Context, in *model.TTplatformSettingDeleteParam) (err error) {
|
||||
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
|
||||
err = gerror.Wrap(err, "删除二级域名站点个性化配置表失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MaxSort 获取二级域名站点个性化配置表最大排序
|
||||
func (s *sSysTTplatformSetting) MaxSort(ctx context.Context, in *model.TTplatformSettingMaxSortParam) (res *model.TTplatformSettingMaxSortModel, err error) {
|
||||
if err = dao.TTplatformSetting.Ctx(ctx).Fields(dao.TTplatformSetting.Columns().Sort).OrderDesc(dao.TTplatformSetting.Columns().Sort).Scan(&res); err != nil {
|
||||
err = gerror.Wrap(err, "获取二级域名站点个性化配置表最大排序,请稍后重试!")
|
||||
return
|
||||
}
|
||||
|
||||
if res == nil {
|
||||
res = new(model.TTplatformSettingMaxSortModel)
|
||||
}
|
||||
|
||||
res.Sort = res.Sort + 1
|
||||
return
|
||||
}
|
||||
|
||||
// View 获取二级域名站点个性化配置表指定信息
|
||||
func (s *sSysTTplatformSetting) View(ctx context.Context, in *model.TTplatformSettingViewParam) (res *model.TTplatformSettingViewModel, err error) {
|
||||
g.Log().Info(ctx, "sSysTTplatformSetting.View:", in)
|
||||
|
||||
if err = dao.TTplatformSetting.Ctx(ctx).Where(dao.TTplatformSetting.Columns().FullDomain, in.FullDomain).Hook(hook.MemberSummary).Scan(&res); err != nil {
|
||||
err = gerror.Wrap(err, "获取二级域名站点个性化配置表信息,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Status 更新二级域名站点个性化配置表状态
|
||||
func (s *sSysTTplatformSetting) Status(ctx context.Context, in *model.TTplatformSettingStatusParam) (err error) {
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Data(g.Map{
|
||||
dao.TTplatformSetting.Columns().Status: in.Status,
|
||||
dao.TTplatformSetting.Columns().UpdatedBy: contexts.GetUserId(ctx),
|
||||
}).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "更新二级域名站点个性化配置表状态失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
Reference in New Issue
Block a user