chore: 并入 iotlcm-app 历史到 app/(原提交 f40bda9)
This commit is contained in:
@@ -0,0 +1,513 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/bcache"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/utility"
|
||||
|
||||
"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 {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 生成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", "XiuAdmin").String()
|
||||
sk := g.Cfg().MustGet(ctx, "jwt.signingKey", "39c54195e73304e74a8429b178965865").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 user.RoleIds != nil {
|
||||
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{"XiuAdmin"}, // 受众
|
||||
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", "39c54195e73304e74a8429b178965865").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", "39c54195e73304e74a8429b178965865").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)
|
||||
|
||||
// 角色权限码
|
||||
roleIds, err := service.SysUser().GetUserRoleIds(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(roleIds) > 0 {
|
||||
rl, _, err := service.SysRole().List(ctx, &model.SysRoleListParam{
|
||||
PageInfo: request.PageInfo{
|
||||
Page: 1,
|
||||
PageSize: 5000,
|
||||
},
|
||||
RoleIds: roleIds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, r := range rl {
|
||||
accessCodeList = append(accessCodeList, consts.SysCheckPermissionRolePrefix+r.RoleKey)
|
||||
roleDataScopeMap[r.RoleId] = r.DataScope
|
||||
}
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysAuth.GetUserAccessCodeList roleIds: %v", roleIds)
|
||||
// 菜单权限码
|
||||
// 获取角色菜单列表
|
||||
rmList, err := service.SysRole().GetRoleListMenu(ctx, roleIds)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
type roleMenuDataScopeRow struct {
|
||||
RoleId int64 `orm:"role_id"`
|
||||
MenuId int64 `orm:"menu_id"`
|
||||
DataScope string `orm:"data_scope"`
|
||||
}
|
||||
roleMenuDataScopeMap := make(map[string]string)
|
||||
if len(roleIds) > 0 {
|
||||
var roleMenuDataScopes []*roleMenuDataScopeRow
|
||||
roleMenuScopeModel := dao.SysRoleMenu.Ctx(ctx).
|
||||
Fields(
|
||||
dao.SysRoleMenu.Columns().RoleId,
|
||||
dao.SysRoleMenu.Columns().MenuId,
|
||||
dao.SysRoleMenu.Columns().DataScope,
|
||||
).
|
||||
WhereIn(dao.SysRoleMenu.Columns().RoleId, roleIds)
|
||||
if tenantId := contexts.GetTenantId(ctx); tenantId != "" {
|
||||
roleMenuScopeModel = roleMenuScopeModel.Where(dao.SysRoleMenu.Columns().TenantId, tenantId)
|
||||
}
|
||||
if err = roleMenuScopeModel.Scan(&roleMenuDataScopes); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, row := range roleMenuDataScopes {
|
||||
if row == nil {
|
||||
continue
|
||||
}
|
||||
roleMenuDataScopeMap[gconv.String(row.RoleId)+"_"+gconv.String(row.MenuId)] = row.DataScope
|
||||
}
|
||||
}
|
||||
menuIds := make([]int64, 0)
|
||||
for _, menu := range rmList {
|
||||
menuIds = append(menuIds, menu.MenuId)
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysAuth.GetUserAccessCodeList menuIds: %v", menuIds)
|
||||
if len(menuIds) > 0 {
|
||||
// 权限码必须读取当前业务库,避免内存菜单库未刷新时拿不到新配置的按钮权限。
|
||||
var menuList []*entity.SysMenu
|
||||
err = dao.SysMenu.Ctx(ctx).
|
||||
WhereIn(dao.SysMenu.Columns().MenuId, menuIds).
|
||||
Where(dao.SysMenu.Columns().Status, consts.SysMenuStatusNormal).
|
||||
Order(dao.SysMenu.Columns().OrderNum).
|
||||
Scan(&menuList)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysAuth.GetUserAccessCodeList menuList: %v", menuList)
|
||||
for _, m := range menuList {
|
||||
menuAccessCode := normalizeSysMenuAccessCode(m.Perms)
|
||||
if menuAccessCode == "" {
|
||||
continue
|
||||
}
|
||||
accessCodeList = append(accessCodeList, menuAccessCode)
|
||||
for _, r := range rmList {
|
||||
if r.MenuId == m.MenuId {
|
||||
roleDS := roleDataScopeMap[r.RoleId]
|
||||
if menuDataScope := roleMenuDataScopeMap[gconv.String(r.RoleId)+"_"+gconv.String(r.MenuId)]; menuDataScope != "" && menuDataScope != consts.SysRoleMenuDataScopeRole {
|
||||
roleDS = menuDataScope
|
||||
}
|
||||
if len(roleDS) < 1 {
|
||||
roleDS = consts.SysRoleDataScopeAll
|
||||
}
|
||||
menuRoleAccessCodeList = append(menuRoleAccessCodeList, menuAccessCode+"|"+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
|
||||
}
|
||||
|
||||
func normalizeSysMenuAccessCode(perms string) string {
|
||||
perms = strings.TrimSpace(perms)
|
||||
if perms == "" {
|
||||
return ""
|
||||
}
|
||||
for strings.HasPrefix(perms, consts.SysCheckPermissionMenuPrefix+consts.SysCheckPermissionMenuPrefix) {
|
||||
perms = strings.TrimPrefix(perms, consts.SysCheckPermissionMenuPrefix)
|
||||
}
|
||||
if strings.HasPrefix(perms, consts.SysCheckPermissionMenuPrefix) {
|
||||
return perms
|
||||
}
|
||||
return consts.SysCheckPermissionMenuPrefix + perms
|
||||
}
|
||||
|
||||
// 根据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
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
|
||||
"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,141 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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)
|
||||
data := &do.SysClient{}
|
||||
gconv.Struct(param, data)
|
||||
data.Id = nil
|
||||
_, err = db.Data(data).OmitNil().Where(dao.SysClient.Columns().Id, param.Id).Update()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
output = &model.SysClientStatusModel{
|
||||
Id: param.Id,
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"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("配置名称不能为空")
|
||||
}
|
||||
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()
|
||||
|
||||
lastInsertId, err := m.Data(data).InsertAndGetId()
|
||||
if 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不能为空")
|
||||
}
|
||||
|
||||
data := &do.SysConfig{}
|
||||
gconv.Struct(param, data)
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
data.ConfigId = nil
|
||||
|
||||
m := s.Model(ctx)
|
||||
_, err = m.Data(data).Where(dao.SysConfig.Columns().ConfigId, param.ConfigId).Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output = &model.SysConfigEditModel{
|
||||
ConfigId: param.ConfigId,
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysConfigUpdate, param.ConfigId, contexts.GetTenantId(ctx), data.ConfigKey)
|
||||
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("请选择要删除的配置")
|
||||
}
|
||||
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,422 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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...)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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, "登录已过期")
|
||||
}
|
||||
err = l.ModelQuery(ctx).Where(dao.SysDept.Columns().DeptId, id).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,
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (l *sSysDept) GetDeptTree(ctx context.Context, query model.SysDeptTreeParam) (items []*model.SysDeptTreeModel, err error) {
|
||||
depts, _, err := l.GetDeptList(ctx, model.SysDeptListParam{
|
||||
DeptType: query.DeptType,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(depts) == 0 {
|
||||
return make([]*model.SysDeptTreeModel, 0), nil
|
||||
}
|
||||
//找出列表中的顶级部门(可能包含多个)
|
||||
topDepts := make([]*model.SysDeptTreeModel, 0)
|
||||
deptMap := make(map[int64]*model.SysDeptTreeModel)
|
||||
for _, dept := range depts {
|
||||
deptMap[dept.DeptId] = &model.SysDeptTreeModel{
|
||||
DeptId: dept.DeptId,
|
||||
ParentId: dept.ParentId,
|
||||
DeptName: dept.DeptName,
|
||||
Key: fmt.Sprintf("%d", dept.DeptId),
|
||||
Children: nil,
|
||||
Ancestors: "0",
|
||||
}
|
||||
}
|
||||
|
||||
for _, dept := range depts {
|
||||
if _, ok := deptMap[dept.ParentId]; !ok {
|
||||
topDepts = append(topDepts, deptMap[dept.DeptId])
|
||||
}
|
||||
}
|
||||
|
||||
for _, dept := range topDepts {
|
||||
children, err := l.DeptTree(ctx, dept, depts, fmt.Sprintf("%d", dept.DeptId))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dept.Children = children
|
||||
}
|
||||
|
||||
return topDepts, nil
|
||||
}
|
||||
|
||||
// 递归构建结构
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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 = l.Model(ctx).Where(dao.SysDept.Columns().DeptId, dept.DeptId).Delete()
|
||||
if err != nil {
|
||||
return dept.DeptId, err
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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,82 @@
|
||||
// package system_test
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
_ "git.5iot.com/iotlcm-public/iotlcm-app/internal/logic/memorydb"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type deptTestMemoryDB struct {
|
||||
service.IMemoryDB
|
||||
database gdb.DB
|
||||
}
|
||||
|
||||
func (memory *deptTestMemoryDB) DB(ctx context.Context) gdb.DB {
|
||||
return memory.database
|
||||
}
|
||||
|
||||
func TestSysDept_RefreshDeptAncestors(t *testing.T) {
|
||||
var (
|
||||
ctx = contexts.Set(context.Background(), &model.Context{
|
||||
User: &model.Identity{BaseClaims: model.BaseClaims{ID: 1, TenantId: "A", DeptId: 1}},
|
||||
})
|
||||
previous = gdb.GetConfig("default")
|
||||
previousMemory = service.MemoryDB()
|
||||
)
|
||||
if err := gdb.SetConfigGroup("default", gdb.ConfigGroup{{Type: "sqlite", Link: "sqlite::@file(:memory:)", MaxOpenConnCount: 1}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
database := g.DB()
|
||||
service.RegisterMemoryDB(&deptTestMemoryDB{database: database})
|
||||
t.Cleanup(func() {
|
||||
service.RegisterMemoryDB(previousMemory)
|
||||
if err := database.GetCore().ClearCacheAll(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := database.Close(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := gdb.SetConfigGroup("default", previous); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
if err := database.GetCore().ClearCacheAll(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
statements := []string{
|
||||
"CREATE TABLE sys_dept (dept_id INTEGER PRIMARY KEY, parent_id INTEGER, tenant_id TEXT, ancestors TEXT, updated_at DATETIME, deleted_at DATETIME)",
|
||||
"INSERT INTO sys_dept VALUES (1,0,'A','wrong',NULL,NULL),(2,1,'A','wrong',NULL,NULL),(3,2,'A','wrong',NULL,NULL),(4,1,'B','other-tenant',NULL,NULL),(5,1,'A','deleted',NULL,'2026-01-01')",
|
||||
}
|
||||
for _, statement := range statements {
|
||||
if _, err := database.Exec(ctx, statement); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
for attempt := 0; attempt < 2; attempt++ {
|
||||
if err := service.SysDept().RefreshDeptAncestors(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for deptID, expected := range map[int]string{1: "0,", 2: "0,1,", 3: "0,1,2,", 4: "other-tenant", 5: "deleted"} {
|
||||
actual, err := database.GetValue(ctx, "SELECT ancestors FROM sys_dept WHERE dept_id = ?", deptID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if actual.String() != expected {
|
||||
t.Fatalf("attempt=%d, dept=%d, ancestors=%q, expected=%q", attempt, deptID, actual.String(), expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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 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,392 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @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-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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
|
||||
|
||||
data := do.SysJob{}
|
||||
gconv.Struct(jobAdd, &data)
|
||||
result, err := c.Model(ctx).Data(data).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
|
||||
data := do.SysJob{}
|
||||
gconv.Struct(jobUpdate, &data)
|
||||
result, err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobUpdate.JobId).Data(data).Update()
|
||||
|
||||
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
|
||||
data := do.SysJob{}
|
||||
gconv.Struct(jobUpdate, &data)
|
||||
result, err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobUpdate.JobId).Data(data).Update()
|
||||
|
||||
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
|
||||
data := do.SysJob{
|
||||
DeletedAt: jobDelete.DeletedAt,
|
||||
DeletedBy: jobDelete.DeletedBy,
|
||||
}
|
||||
result, err := c.Model(ctx).WhereIn(dao.SysJob.Columns().JobId, jobDelete.JobIds).Data(data).Update()
|
||||
|
||||
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@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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(data).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,361 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
v1 "git.5iot.com/iotlcm-public/iotlcm-app/api/system/v1"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
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)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建树结构
|
||||
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) {
|
||||
roles := contexts.GetRoles(ctx)
|
||||
roleIds := make([]int64, 0)
|
||||
for _, role := range roles {
|
||||
roleIds = append(roleIds, role.RoleId)
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysMenu.GetUserMenu roleIds: %v", roleIds)
|
||||
// 判断是否存在超级管理员角色
|
||||
if slices.Contains(roleIds, consts.SuperAdminRoleId) {
|
||||
// 获取所有菜单
|
||||
err = l.ModelQuery(ctx).Where(dao.SysMenu.Columns().Status, consts.SysMenuStatusNormal).
|
||||
Order(dao.SysMenu.Columns().OrderNum).Scan(&data)
|
||||
return
|
||||
}
|
||||
// 获取角色菜单列表
|
||||
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)
|
||||
}
|
||||
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)
|
||||
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
|
||||
}
|
||||
if menu.ParentId == pId {
|
||||
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: strings.Split(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
|
||||
}
|
||||
// 构建树结构
|
||||
data, err = l.BuildUserMenuTree(ctx, nil, menuList, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
updateData := do.SysMenu{}
|
||||
gconv.Struct(menu, &updateData)
|
||||
updateData.MenuId = nil
|
||||
_, err = l.Model(ctx).Where(dao.SysMenu.Columns().MenuId, menu.MenuId).Data(updateData).OmitNil().Update()
|
||||
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)
|
||||
insertData := do.SysMenu{}
|
||||
gconv.Struct(menu, &insertData)
|
||||
insertData.MenuId = nil
|
||||
re, err := l.Model(ctx).Data(insertData).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,348 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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, tx gdb.TX, userIds *gset.IntSet, param *entity.SysNotice) (err error) {
|
||||
|
||||
sysNotifyUserList := make([]*do.SysNoticeUser, 0, userIds.Size())
|
||||
for _, userId := range userIds.Slice() {
|
||||
sysNotifyUserList = append(sysNotifyUserList, &do.SysNoticeUser{
|
||||
NoticeId: param.NoticeId,
|
||||
NoticeTitle: param.NoticeTitle,
|
||||
NoticeType: param.NoticeType,
|
||||
NoticeContent: 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 = dao.SysNoticeUser.Ctx(ctx).TX(tx).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()
|
||||
err = dao.SysNotice.Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
id, err := s.Model(ctx).TX(tx).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, tx, userIds, &entityData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
err = dao.SysNotice.Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
m := s.Model(ctx).TX(tx)
|
||||
|
||||
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 = dao.SysNoticeUser.Ctx(ctx).TX(tx).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, tx, userIds, &entityData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *sSysNotice) Delete(ctx context.Context, param *model.SysNoticeDeleteParam) (err error) {
|
||||
err = dao.SysNotice.Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||
_, err = s.Model(ctx).TX(tx).WhereIn(dao.SysNotice.Columns().NoticeId, param.NoticeIds).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除SysNoticeUser
|
||||
_, err = dao.SysNoticeUser.Ctx(ctx).TX(tx).WhereIn(dao.SysNoticeUser.Columns().NoticeId, param.NoticeIds).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
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,204 @@
|
||||
// Package gen
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
// @AutoGenerate Version
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/hook"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/utility/convert"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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()
|
||||
data := do.SysNoticeUser{}
|
||||
gconv.Struct(in, &data)
|
||||
data.Id = nil
|
||||
data.NoticeContent = []byte(in.NoticeContent)
|
||||
if _, err = s.Model(ctx).TX(tx).
|
||||
Fields(model.SysNoticeUserUpdateFields{}).
|
||||
WherePri(in.Id).Data(data).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()
|
||||
data := do.SysNoticeUser{}
|
||||
gconv.Struct(in, &data)
|
||||
data.Id = nil
|
||||
data.NoticeContent = []byte(in.NoticeContent)
|
||||
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).TX(tx).
|
||||
Fields(model.SysNoticeUserInsertFields{}).
|
||||
Data(data).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) {
|
||||
data := do.SysNoticeUser{
|
||||
Status: in.Status,
|
||||
UpdatedBy: contexts.GetUserId(ctx),
|
||||
}
|
||||
if _, err = s.Model(ctx).WherePri(in.Id).Data(data).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "更新用户通知公告表状态失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Read 已读
|
||||
func (s *sSysNoticeUser) Read(ctx context.Context, in *model.SysNoticeUserReadParam) (err error) {
|
||||
|
||||
data := do.SysNoticeUser{
|
||||
IsRead: 1,
|
||||
UpdatedBy: contexts.GetUserId(ctx),
|
||||
UpdatedAt: gtime.Now(),
|
||||
}
|
||||
if _, err = s.Model(ctx).WherePri(in.Ids).Data(data).Update(); err != nil {
|
||||
err = gerror.Wrap(err, "更新用户通知公告表状态失败,请稍后重试!")
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/mcache"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/utility"
|
||||
|
||||
"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 model.SysOperLogAddParam) (err error) {
|
||||
writeData := do.SysOperLog{}
|
||||
if err = gconv.Struct(data, &writeData); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = dao.SysOperLog.Ctx(ctx).Data(writeData).Save()
|
||||
if err != nil {
|
||||
g.Log().Error(ctx, "sSysOperLog.RealWrite error:%v,data:%+v", err, data)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,801 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/utility/hash"
|
||||
|
||||
"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 sysOssDataFromUpload(ctx context.Context, result model.UploadResponse) do.SysOss {
|
||||
now := gtime.Now()
|
||||
return do.SysOss{
|
||||
TenantId: contexts.GetTenantId(ctx),
|
||||
FileName: result.Name,
|
||||
OriginalName: result.OriginalName,
|
||||
FileSuffix: result.Ext,
|
||||
Path: result.Path,
|
||||
Url: result.FullPath,
|
||||
CreatedDept: contexts.GetDeptId(ctx),
|
||||
CreatedAt: now,
|
||||
CreatedBy: contexts.GetUserId(ctx),
|
||||
UpdatedAt: now,
|
||||
UpdatedBy: contexts.GetUserId(ctx),
|
||||
Service: "local",
|
||||
Md5: result.Md5,
|
||||
FileSize: result.Size,
|
||||
FileCrc16: result.Crc16,
|
||||
FileSum: result.Sum16,
|
||||
}
|
||||
}
|
||||
|
||||
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.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(sysOssDataFromUpload(ctx, result)).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(sysOssDataFromUpload(ctx, result)).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(sysOssDataFromUpload(ctx, result)).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 := s.buildLocalFileURL(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 := s.buildLocalFileURL(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 := s.buildLocalFileURL(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) buildLocalFileURL(ctx context.Context, fullFilePath string) string {
|
||||
fullFilePath = strings.TrimSpace(fullFilePath)
|
||||
if fullFilePath == "" {
|
||||
return ""
|
||||
}
|
||||
uploadPath := strings.TrimSpace(g.Cfg().MustGet(ctx, "system.upload.local.path").String())
|
||||
publicURL := strings.TrimSpace(os.Getenv("IOTLCM_UPLOAD_PUBLIC_URL"))
|
||||
if publicURL == "" {
|
||||
publicURL = strings.TrimSpace(g.Cfg().MustGet(ctx, "system.upload.local.publicUrl", "").String())
|
||||
}
|
||||
if publicURL != "" {
|
||||
if fileURL, ok := localFileURLByBase(fullFilePath, uploadPath, publicURL); ok {
|
||||
return fileURL
|
||||
}
|
||||
}
|
||||
configValue := ""
|
||||
configType, err := service.SysConfig().GetConfigByKey(ctx, consts.ConfigOssUrlPath)
|
||||
if err == nil && configType != nil {
|
||||
configValue = strings.TrimSpace(configType.ConfigValue)
|
||||
}
|
||||
if url, ok := localFileURLByMapping(fullFilePath, configValue); ok {
|
||||
return url
|
||||
}
|
||||
|
||||
baseURL := localFileURLFallbackBase(configValue)
|
||||
if baseURL != "" {
|
||||
if url, ok := localFileURLByBase(fullFilePath, uploadPath, baseURL); ok {
|
||||
return url
|
||||
}
|
||||
}
|
||||
if url, ok := localFileURLByBase(fullFilePath, uploadPath, "/upload"); ok {
|
||||
return url
|
||||
}
|
||||
if configValue != "" && !strings.Contains(configValue, "|") {
|
||||
return strings.TrimRight(configValue, "/") + "/" + strings.TrimLeft(filepath.ToSlash(fullFilePath), "/")
|
||||
}
|
||||
return fullFilePath
|
||||
}
|
||||
|
||||
func localFileURLFallbackBase(configValue string) string {
|
||||
pairs := localFileURLMappingPairs(configValue)
|
||||
for _, pair := range pairs {
|
||||
if pair[1] != "" {
|
||||
return pair[1]
|
||||
}
|
||||
}
|
||||
configValue = strings.TrimSpace(configValue)
|
||||
if configValue == "" || strings.Contains(configValue, "|") {
|
||||
return ""
|
||||
}
|
||||
return configValue
|
||||
}
|
||||
|
||||
func localFileURLByMapping(fullFilePath string, configValue string) (string, bool) {
|
||||
for _, pair := range localFileURLMappingPairs(configValue) {
|
||||
if suffix, ok := localPathSuffix(fullFilePath, pair[0]); ok {
|
||||
return joinURLPath(pair[1], suffix), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func localFileURLByBase(fullFilePath string, uploadPath string, baseURL string) (string, bool) {
|
||||
if strings.TrimSpace(uploadPath) == "" || strings.TrimSpace(baseURL) == "" {
|
||||
return "", false
|
||||
}
|
||||
suffix, ok := localPathSuffix(fullFilePath, uploadPath)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return joinURLPath(baseURL, suffix), true
|
||||
}
|
||||
|
||||
func localFileURLMappingPairs(configValue string) [][2]string {
|
||||
configValue = strings.TrimSpace(configValue)
|
||||
if configValue == "" {
|
||||
return nil
|
||||
}
|
||||
items := strings.FieldsFunc(configValue, func(r rune) bool {
|
||||
return r == ';' || r == '\n' || r == '\r'
|
||||
})
|
||||
pairs := make([][2]string, 0, len(items))
|
||||
for _, item := range items {
|
||||
parts := strings.SplitN(strings.TrimSpace(item), "|", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
source := strings.TrimSpace(parts[0])
|
||||
target := strings.TrimSpace(parts[1])
|
||||
if source == "" || target == "" {
|
||||
continue
|
||||
}
|
||||
pairs = append(pairs, [2]string{source, target})
|
||||
}
|
||||
return pairs
|
||||
}
|
||||
|
||||
func localPathSuffix(fullFilePath string, prefix string) (string, bool) {
|
||||
full := normalizeLocalPath(fullFilePath)
|
||||
base := normalizeLocalPath(prefix)
|
||||
if full == "" || base == "" {
|
||||
return "", false
|
||||
}
|
||||
if strings.EqualFold(full, base) {
|
||||
return "", true
|
||||
}
|
||||
prefixWithSlash := strings.TrimRight(base, "/") + "/"
|
||||
if len(full) > len(prefixWithSlash) && strings.EqualFold(full[:len(prefixWithSlash)], prefixWithSlash) {
|
||||
return full[len(prefixWithSlash):], true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func normalizeLocalPath(path string) string {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
path = filepath.ToSlash(filepath.Clean(path))
|
||||
return strings.TrimRight(path, "/")
|
||||
}
|
||||
|
||||
func joinURLPath(baseURL string, suffix string) string {
|
||||
baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/")
|
||||
suffix = strings.TrimLeft(filepath.ToSlash(strings.TrimSpace(suffix)), "/")
|
||||
if suffix == "" {
|
||||
if baseURL == "" {
|
||||
return "/"
|
||||
}
|
||||
return baseURL
|
||||
}
|
||||
return baseURL + "/" + suffix
|
||||
}
|
||||
|
||||
func (s *sSysOss) DownloadLocal(ctx context.Context, file *entity.SysOss) (err error) {
|
||||
if file == nil {
|
||||
err = errors.New("文件必须")
|
||||
return
|
||||
}
|
||||
finalPath, err := s.resolveLocalOssPath(ctx, file.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r := ghttp.RequestFromCtx(ctx)
|
||||
if r == nil {
|
||||
err = gerror.New("ctx not http request")
|
||||
return
|
||||
}
|
||||
reader, err := os.Open(finalPath)
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "打开下载文件失败")
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
fileInfo, err := reader.Stat()
|
||||
if err != nil {
|
||||
return gerror.Wrap(err, "读取下载文件信息失败")
|
||||
}
|
||||
fileName := strings.TrimSpace(file.FileName)
|
||||
if fileName == "" {
|
||||
fileName = filepath.Base(finalPath)
|
||||
}
|
||||
contentType := appOssDownloadContentType(finalPath, file.FileSuffix)
|
||||
r.Response.Header().Set("Content-Type", contentType)
|
||||
r.Response.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))
|
||||
r.Response.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, strings.ReplaceAll(fileName, `"`, "")))
|
||||
r.Response.Status = http.StatusOK
|
||||
http.ServeContent(r.Response.Writer, r.Request, fileName, fileInfo.ModTime(), reader)
|
||||
r.Response.Flush()
|
||||
return
|
||||
}
|
||||
|
||||
func appOssDownloadContentType(filePath string, suffix string) string {
|
||||
ext := strings.TrimSpace(suffix)
|
||||
if ext == "" {
|
||||
ext = filepath.Ext(filePath)
|
||||
}
|
||||
ext = strings.TrimPrefix(ext, ".")
|
||||
if ext == "" {
|
||||
return "application/octet-stream"
|
||||
}
|
||||
if contentType := mime.TypeByExtension("." + ext); contentType != "" {
|
||||
return contentType
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
func (s *sSysOss) DeleteLocal(ctx context.Context, file *entity.SysOss) (err error) {
|
||||
if file == nil {
|
||||
err = errors.New("文件不存在")
|
||||
return
|
||||
}
|
||||
filePath, err := s.resolveLocalOssPath(ctx, file.Path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = gfile.Remove(filePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysOss) resolveLocalOssPath(ctx context.Context, filePath string) (string, error) {
|
||||
filePath = strings.TrimSpace(filePath)
|
||||
if filePath == "" {
|
||||
return "", errors.New("文件路径为空")
|
||||
}
|
||||
|
||||
candidates := make([]string, 0, 4)
|
||||
addCandidate := func(path string) {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
return
|
||||
}
|
||||
cleaned := filepath.Clean(path)
|
||||
for _, item := range candidates {
|
||||
if item == cleaned {
|
||||
return
|
||||
}
|
||||
}
|
||||
candidates = append(candidates, cleaned)
|
||||
}
|
||||
|
||||
addCandidate(filePath)
|
||||
if !filepath.IsAbs(filePath) {
|
||||
if absPath, err := filepath.Abs(filePath); err == nil {
|
||||
addCandidate(absPath)
|
||||
}
|
||||
if workDir, err := os.Getwd(); err == nil {
|
||||
addCandidate(filepath.Join(workDir, "server", filePath))
|
||||
}
|
||||
if uploadPath := strings.TrimSpace(g.Cfg().MustGet(ctx, "system.upload.local.path").String()); uploadPath != "" && !filepath.IsAbs(uploadPath) {
|
||||
if absUploadPath, err := filepath.Abs(uploadPath); err == nil {
|
||||
normalizedUploadPath := filepath.ToSlash(filepath.Clean(uploadPath))
|
||||
normalizedFilePath := filepath.ToSlash(filepath.Clean(filePath))
|
||||
if strings.HasPrefix(normalizedFilePath, normalizedUploadPath+"/") {
|
||||
suffix := strings.TrimPrefix(normalizedFilePath, normalizedUploadPath+"/")
|
||||
addCandidate(filepath.Join(absUploadPath, filepath.FromSlash(suffix)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, candidate := range candidates {
|
||||
if gfile.Exists(candidate) {
|
||||
return candidate, nil
|
||||
}
|
||||
}
|
||||
return "", gerror.Newf("文件不存在:%s", filePath)
|
||||
}
|
||||
|
||||
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@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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,137 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
)
|
||||
|
||||
func TestLocalFileURLUsesConfiguredPublicURL(t *testing.T) {
|
||||
t.Setenv("IOTLCM_UPLOAD_PUBLIC_URL", "")
|
||||
previous := g.Cfg().GetAdapter()
|
||||
t.Cleanup(func() { g.Cfg().SetAdapter(previous) })
|
||||
cases := []struct {
|
||||
name string
|
||||
root string
|
||||
base string
|
||||
file string
|
||||
want string
|
||||
}{
|
||||
{"本地跨端预览", "resource/upload", "http://localhost:8010/upload/", "resource/upload/000000/user/file/photo.jpeg", "http://localhost:8010/upload/000000/user/file/photo.jpeg"},
|
||||
{"独立部署前缀", "/data/app/upload", "https://files.example.com/app/upload", "/data/app/upload/000000/user/file/photo.jpeg", "https://files.example.com/app/upload/000000/user/file/photo.jpeg"},
|
||||
{"Windows共享目录", "F:/shared/upload", "https://files.example.com/upload", "F:/shared/upload/000000/user/file/photo.jpeg", "https://files.example.com/upload/000000/user/file/photo.jpeg"},
|
||||
}
|
||||
for _, testCase := range cases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
adapter, err := gcfg.NewAdapterContent(
|
||||
"system:\n upload:\n local:\n path: " + testCase.root + "\n publicUrl: " + testCase.base + "\n",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
g.Cfg().SetAdapter(adapter)
|
||||
got := NewSysOss().buildLocalFileURL(context.Background(), testCase.file)
|
||||
if got != testCase.want {
|
||||
t.Fatalf("url = %q, want %q", got, testCase.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalFileURLUsesEnvironmentOverride(t *testing.T) {
|
||||
t.Setenv("IOTLCM_UPLOAD_PUBLIC_URL", "http://localhost:8010/upload")
|
||||
previous := g.Cfg().GetAdapter()
|
||||
t.Cleanup(func() { g.Cfg().SetAdapter(previous) })
|
||||
adapter, err := gcfg.NewAdapterContent("system:\n upload:\n local:\n path: resource/upload\n publicUrl: https://files.example.com/upload\n")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
g.Cfg().SetAdapter(adapter)
|
||||
got := NewSysOss().buildLocalFileURL(context.Background(), "resource/upload/000000/photo.jpeg")
|
||||
if got != "http://localhost:8010/upload/000000/photo.jpeg" {
|
||||
t.Fatalf("environment override did not take precedence: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveLocalOssPathFromProjectRoot(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
relativePath := filepath.FromSlash("resource/upload/000000/user/file/test.xlsx")
|
||||
realPath := filepath.Join(tempDir, "server", relativePath)
|
||||
if err := os.MkdirAll(filepath.Dir(realPath), 0o755); err != nil {
|
||||
t.Fatalf("mkdir test oss path: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(realPath, []byte("xlsx"), 0o644); err != nil {
|
||||
t.Fatalf("write test oss file: %v", err)
|
||||
}
|
||||
|
||||
oldWd, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatalf("get wd: %v", err)
|
||||
}
|
||||
if err = os.Chdir(tempDir); err != nil {
|
||||
t.Fatalf("chdir temp root: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.Chdir(oldWd); err != nil {
|
||||
t.Fatalf("restore wd: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
got, err := NewSysOss().resolveLocalOssPath(context.Background(), relativePath)
|
||||
if err != nil {
|
||||
t.Fatalf("resolve local oss path: %v", err)
|
||||
}
|
||||
if got != realPath {
|
||||
t.Fatalf("resolved path = %q, want %q", got, realPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppOssDownloadContentTypeXlsx(t *testing.T) {
|
||||
got := appOssDownloadContentType("resource/upload/test.xlsx", "xlsx")
|
||||
if got != "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" {
|
||||
t.Fatalf("content type = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalFileURLByMappingWithProductionUploadPath(t *testing.T) {
|
||||
filePath := "/data/app/iotlcmadmin-web-svc/upload/000000/user/file/20260710/sim-card-renew/result/REN-result.xlsx"
|
||||
configValue := "/data/app/iotlcmadmin-web-svc/upload|http://example.com:8008/upload"
|
||||
|
||||
got, ok := localFileURLByMapping(filePath, configValue)
|
||||
if !ok {
|
||||
t.Fatal("expected production upload path mapping to match")
|
||||
}
|
||||
want := "http://example.com:8008/upload/000000/user/file/20260710/sim-card-renew/result/REN-result.xlsx"
|
||||
if got != want {
|
||||
t.Fatalf("url = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalFileURLByBaseWithProductionUploadPath(t *testing.T) {
|
||||
filePath := "/data/app/iotlcmadmin-web-svc/upload/000000/user/file/20260710/sim-card-renew/import/REN-import.xlsx"
|
||||
got, ok := localFileURLByBase(filePath, "/data/app/iotlcmadmin-web-svc/upload", "http://example.com:8008/upload")
|
||||
if !ok {
|
||||
t.Fatal("expected upload path base mapping to match")
|
||||
}
|
||||
want := "http://example.com:8008/upload/000000/user/file/20260710/sim-card-renew/import/REN-import.xlsx"
|
||||
if got != want {
|
||||
t.Fatalf("url = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalFileURLByMappingWithDevelopmentUploadPath(t *testing.T) {
|
||||
got, ok := localFileURLByMapping(
|
||||
filepath.FromSlash("resource/upload/000000/user/file/test.xlsx"),
|
||||
"resource/upload|/upload",
|
||||
)
|
||||
if !ok {
|
||||
t.Fatal("expected development upload path mapping to match")
|
||||
}
|
||||
if got != "/upload/000000/user/file/test.xlsx" {
|
||||
t.Fatalf("url = %q", got)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/utility/convert"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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,816 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
|
||||
"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 {
|
||||
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
|
||||
}
|
||||
|
||||
return &SysQrcodeConfig{
|
||||
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()
|
||||
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
|
||||
}
|
||||
if response.StatusCode != 200 {
|
||||
g.Log().Errorf(ctx, "调用二维码API失败, 请求数据: %v, 响应数据: %v", request, response)
|
||||
return nil, errors.New("调用二维码API失败")
|
||||
}
|
||||
|
||||
responseData := response.ReadAllString()
|
||||
|
||||
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,355 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/utility"
|
||||
|
||||
"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.GetRoleMenu(ctx, param.RoleId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
menuIdList := make([]int64, 0)
|
||||
for _, menu := range roleMenus {
|
||||
menuIdList = append(menuIdList, menu.MenuId)
|
||||
}
|
||||
res.MenuIds = menuIdList
|
||||
// 获取角色部门
|
||||
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) GetRoleMenu(ctx context.Context, id int64) (res []*entity.SysMenu, err error) {
|
||||
mod := dao.SysRoleMenu.Ctx(ctx)
|
||||
if tenantId := contexts.GetTenantId(ctx); tenantId != "" {
|
||||
mod = mod.Where("tenant_id", tenantId)
|
||||
}
|
||||
err = mod.Where(dao.SysRoleMenu.Columns().RoleId, id).Scan(&res)
|
||||
return
|
||||
}
|
||||
|
||||
// 获取角色部门
|
||||
func (s *sSysRole) GetRoleDept(ctx context.Context, id int64) (res []*entity.SysDept, err error) {
|
||||
mod := dao.SysRoleDept.Ctx(ctx)
|
||||
if tenantId := contexts.GetTenantId(ctx); tenantId != "" {
|
||||
mod = mod.Where("tenant_id", tenantId)
|
||||
}
|
||||
err = mod.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{
|
||||
RoleId: consts.SuperAdminRoleId,
|
||||
MenuId: menu.MenuId,
|
||||
})
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
mod := dao.SysRoleMenu.Ctx(ctx)
|
||||
if tenantId := contexts.GetTenantId(ctx); tenantId != "" {
|
||||
mod = mod.Where("tenant_id", tenantId)
|
||||
}
|
||||
err = mod.WhereIn(dao.SysRoleMenu.Columns().RoleId, ids).Scan(&res)
|
||||
return
|
||||
}
|
||||
|
||||
// 新增角色
|
||||
func (s *sSysRole) Add(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)
|
||||
data.DataScope = "1"
|
||||
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
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysRoleCreate, roleId)
|
||||
|
||||
role = &model.SysRoleAddModel{
|
||||
RoleId: roleId,
|
||||
}
|
||||
// 新增角色菜单
|
||||
err = s.RoleMenu(ctx, roleId, param.MenuIds)
|
||||
if err != nil {
|
||||
return role, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑角色
|
||||
func (s *sSysRole) Edit(ctx context.Context, param *model.SysRoleEditParam) (role *model.SysRoleEditModel, err error) {
|
||||
role = &model.SysRoleEditModel{
|
||||
RoleId: param.RoleId,
|
||||
}
|
||||
user := contexts.GetUser(ctx)
|
||||
if user == nil {
|
||||
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
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysRoleUpdate, param.RoleId)
|
||||
// 更新角色菜单
|
||||
err = s.RoleMenu(ctx, param.RoleId, param.MenuIds)
|
||||
if err != nil {
|
||||
return role, err
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 删除角色
|
||||
func (s *sSysRole) Delete(ctx context.Context, param *model.SysRoleDeleteParam) (role *model.SysRoleDeleteModel, err error) {
|
||||
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("请选择要删除的角色")
|
||||
}
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
func (s *sSysRole) RoleMenu(ctx context.Context, roleId int64, menuIds []int64) (err error) {
|
||||
// 查询角色菜单
|
||||
roleMenu := make([]*entity.SysRoleMenu, 0)
|
||||
err = dao.SysRoleMenu.Ctx(ctx).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)
|
||||
|
||||
// 删除角色菜单
|
||||
if len(delMenuIds) > 0 {
|
||||
_, err = dao.SysRoleMenu.Ctx(ctx).Where(dao.SysRoleMenu.Columns().RoleId, roleId).WhereIn(dao.SysRoleMenu.Columns().MenuId, delMenuIds).Delete()
|
||||
}
|
||||
|
||||
// 新增角色菜单
|
||||
if len(addMenuIds) > 0 {
|
||||
addData := make([]*do.SysRoleMenu, 0, len(addMenuIds))
|
||||
for _, menuId := range addMenuIds {
|
||||
addData = append(addData, &do.SysRoleMenu{
|
||||
RoleId: roleId,
|
||||
MenuId: menuId,
|
||||
})
|
||||
}
|
||||
_, err = dao.SysRoleMenu.Ctx(ctx).Data(addData).Insert()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 自定义角色部门数据权限
|
||||
func (s *sSysRole) RoleDept(ctx context.Context, roleId int64, deptIds []int64) (err error) {
|
||||
// 查询角色部门
|
||||
roleDept := make([]*entity.SysRoleDept, 0)
|
||||
err = dao.SysRoleDept.Ctx(ctx).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().RoleId, roleId).WhereIn(dao.SysRoleDept.Columns().DeptId, delDeptIds).Delete()
|
||||
}
|
||||
|
||||
// 新增角色部门
|
||||
if len(addDeptIds) > 0 {
|
||||
addData := make([]*do.SysRoleDept, 0, len(addDeptIds))
|
||||
for _, deptId := range addDeptIds {
|
||||
addData = append(addData, &do.SysRoleDept{
|
||||
RoleId: roleId,
|
||||
DeptId: deptId,
|
||||
})
|
||||
}
|
||||
_, err = dao.SysRoleDept.Ctx(ctx).Data(addData).Insert()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑角色数据权限
|
||||
func (s *sSysRole) EditRoleDataScope(ctx context.Context, model *model.SysRoleDataScopeEditParam) (err error) {
|
||||
|
||||
data := do.SysRole{}
|
||||
gconv.Struct(model, &data)
|
||||
data.UpdatedBy = contexts.GetUserId(ctx)
|
||||
data.UpdatedAt = gtime.Now()
|
||||
|
||||
_, err = s.Model(ctx).Data(data).Where(dao.SysRole.Columns().RoleId, model.RoleId).OmitNil().Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyDBSysRoleUpdate, model.RoleId)
|
||||
|
||||
// 更新角色部门
|
||||
err = s.RoleDept(ctx, model.RoleId, model.DeptIds)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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/util/gconv"
|
||||
)
|
||||
|
||||
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) {
|
||||
return dao.SysSocial.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("绑定信息已存在")
|
||||
}
|
||||
|
||||
data := do.SysSocial{}
|
||||
gconv.Struct(social, &data)
|
||||
_, err = dao.SysSocial.Ctx(ctx).Data(data).OmitEmpty().Save()
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,639 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/utility"
|
||||
|
||||
"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 := dao.SysTenant.Ctx(ctx).TX(tx).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 = dao.SysDept.Ctx(ctx).TX(tx).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 = dao.SysRole.Ctx(ctx).TX(tx).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{
|
||||
RoleId: roleId,
|
||||
MenuId: menuId,
|
||||
})
|
||||
}
|
||||
_, err = dao.SysRoleMenu.Ctx(ctx).TX(tx).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 = dao.SysUser.Ctx(ctx).TX(tx).Data(dataUserInsert).OmitNil().InsertAndGetId()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysTenant.Add SysUser.Table().InsertAndGetId err: %v, data: %+v", err, dataUserInsert)
|
||||
return err
|
||||
}
|
||||
// 修改租户信息
|
||||
tenantUpdate := do.SysTenant{
|
||||
TenantId: tenantId,
|
||||
AdminRoleId: roleId,
|
||||
AdminDeptId: deptId,
|
||||
AdminUserId: userId,
|
||||
}
|
||||
_, err = dao.SysTenant.Ctx(ctx).TX(tx).Data(tenantUpdate).
|
||||
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 = dao.SysDept.Ctx(ctx).TX(tx).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{
|
||||
UserId: userId,
|
||||
RoleId: roleId,
|
||||
})
|
||||
_, err = dao.SysUserRole.Ctx(ctx).TX(tx).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) {
|
||||
now := gtime.Now()
|
||||
configs := []do.SysConfig{
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "主框架页-默认皮肤样式名称",
|
||||
ConfigKey: "sys.index.skinName",
|
||||
ConfigValue: "skin-blue",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
Remark: "蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow",
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "用户管理-账号初始密码",
|
||||
ConfigKey: "sys.user.initPassword",
|
||||
ConfigValue: "123456",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
Remark: "初始化密码 123456",
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "主框架页-侧边栏主题",
|
||||
ConfigKey: "sys.index.sideTheme",
|
||||
ConfigValue: "theme-dark",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
Remark: "深色主题theme-dark,浅色主题theme-light",
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "账号自助-是否开启用户注册功能",
|
||||
ConfigKey: "sys.account.registerUser",
|
||||
ConfigValue: "false",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
Remark: "是否开启注册用户功能(true开启,false关闭)",
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "OSS预览列表资源开关",
|
||||
ConfigKey: "sys.oss.previewListResource",
|
||||
ConfigValue: "true",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
Remark: "true:开启, false:关闭",
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "上传文件类型",
|
||||
ConfigKey: "sys.oss.fileType",
|
||||
ConfigValue: "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",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "上传图片类型",
|
||||
ConfigKey: "sys.oss.imgType",
|
||||
ConfigValue: "jpg,jpeg,png,gif,webp",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "上传文件大小",
|
||||
ConfigKey: "sys.oss.fileSize",
|
||||
ConfigValue: "20M",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "上传图片大小",
|
||||
ConfigKey: "sys.oss.imgSize",
|
||||
ConfigValue: "5M",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "上传文件Url路径",
|
||||
ConfigKey: "sys.oss.urlPath",
|
||||
ConfigValue: "/data/app/iotlcmadmin-web-svc/upload|http://example.com:8008/upload",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
Remark: "\"http://ab.com/\" 或 \"/\" 或 \"resource/upload|http://ab.com/upload\"",
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "是否启动在线用户强制退出",
|
||||
ConfigKey: "sys.online.forceLogout",
|
||||
ConfigValue: "true",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
Remark: "true开启,false关闭",
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "系统启用的监控通道",
|
||||
ConfigKey: "iot.default.channel.gb28181",
|
||||
ConfigValue: "0",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "报警资源匹配时间",
|
||||
ConfigKey: "iot.alarm.image.timesec",
|
||||
ConfigValue: "10",
|
||||
ConfigType: "N",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "自主注册组织ID",
|
||||
ConfigKey: "iot.register.dept",
|
||||
ConfigValue: "0",
|
||||
ConfigType: "N",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "自主注册角色ID",
|
||||
ConfigKey: "iot.register.role",
|
||||
ConfigValue: "0",
|
||||
ConfigType: "N",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "系统默认MQTT渠道",
|
||||
ConfigKey: "iot.default.channel.mqtt",
|
||||
ConfigValue: "0",
|
||||
ConfigType: "N",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "系统默认TCP渠道",
|
||||
ConfigKey: "iot.default.channel.tcp",
|
||||
ConfigValue: "0",
|
||||
ConfigType: "N",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "系统默认UDP渠道",
|
||||
ConfigKey: "iot.default.channel.udp",
|
||||
ConfigValue: "0",
|
||||
ConfigType: "N",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
{
|
||||
TenantId: tenantId,
|
||||
ConfigName: "项目API默认地址",
|
||||
ConfigKey: "iot.project.api.default.addr",
|
||||
ConfigValue: "http://127.0.0.1:8510",
|
||||
ConfigType: "Y",
|
||||
CreatedDept: createdDept,
|
||||
CreatedBy: createdBy,
|
||||
CreatedAt: now,
|
||||
},
|
||||
}
|
||||
|
||||
ids = make([]int64, 0, len(configs))
|
||||
configDao := dao.SysConfig.Ctx(ctx).TX(tx)
|
||||
for _, config := range configs {
|
||||
id, insertErr := configDao.Data(config).InsertAndGetId()
|
||||
if insertErr != nil {
|
||||
return nil, insertErr
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
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, err := service.SysRole().GetRoleMenu(ctx, tenantInfo.AdminRoleId)
|
||||
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{
|
||||
RoleId: tenantInfo.AdminRoleId,
|
||||
MenuId: gconv.Int64(menuId),
|
||||
})
|
||||
}
|
||||
}
|
||||
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).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@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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
|
||||
}
|
||||
@@ -0,0 +1,768 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/event"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/utility"
|
||||
|
||||
"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"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type sSysUser struct {
|
||||
}
|
||||
|
||||
func NewSysUser() *sSysUser {
|
||||
return &sSysUser{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
service.RegisterSysUser(NewSysUser())
|
||||
}
|
||||
|
||||
func (l *sSysUser) 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.SysUser.Ctx(ctx), option...)
|
||||
}
|
||||
|
||||
// 根据用户名获取用户信息,不验证当前租户
|
||||
func (l *sSysUser) GetUserByUsername(ctx context.Context, username string, tenantId string) (user *entity.SysUser, err error) {
|
||||
var data *entity.SysUser
|
||||
err = dao.SysUser.Ctx(ctx).Where(dao.SysUser.Columns().UserName, username).Where(dao.SysUser.Columns().TenantId, tenantId).Scan(&data)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, gerror.NewCode(consts.CodeUserNotFound, "账号不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if data == nil {
|
||||
return nil, gerror.NewCode(consts.CodeUserNotFound, "账号不存在")
|
||||
}
|
||||
if data.Status == consts.SysUserStatusDisable {
|
||||
return nil, gerror.NewCode(consts.CodeUserDisabled, "账号已禁用")
|
||||
}
|
||||
if data.Status == consts.SysUserStatusLocked {
|
||||
return nil, gerror.NewCode(consts.CodeUserLocked, "账号已锁定")
|
||||
}
|
||||
if data.Status == consts.SysUserStatusExpired {
|
||||
return nil, gerror.NewCode(consts.CodeUserExpired, "账号已过期")
|
||||
}
|
||||
if data.Status == consts.SysUserStatusDeleted {
|
||||
return nil, gerror.NewCode(consts.CodeUserDeleted, "账号已删除")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// 根据邮箱获取用户信息,不验证当前租户
|
||||
func (l *sSysUser) GetUserByEmail(ctx context.Context, email string, tenantId string) (user *entity.SysUser, err error) {
|
||||
err = dao.SysUser.Ctx(ctx).Where(dao.SysUser.Columns().Email, email).Where(dao.SysUser.Columns().TenantId, tenantId).Scan(&user)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, gerror.NewCode(consts.CodeUserNotFound, "账号不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, gerror.NewCode(consts.CodeUserNotFound, "账号不存在")
|
||||
}
|
||||
if user.Status == consts.SysUserStatusDisable {
|
||||
return nil, gerror.NewCode(consts.CodeUserDisabled, "账号已禁用")
|
||||
}
|
||||
if user.Status == consts.SysUserStatusLocked {
|
||||
return nil, gerror.NewCode(consts.CodeUserLocked, "账号已锁定")
|
||||
}
|
||||
if user.Status == consts.SysUserStatusExpired {
|
||||
return nil, gerror.NewCode(consts.CodeUserExpired, "账号已过期")
|
||||
}
|
||||
if user.Status == consts.SysUserStatusDeleted {
|
||||
return nil, gerror.NewCode(consts.CodeUserDeleted, "账号已删除")
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// 根据手机号获取用户信息,不验证当前租户
|
||||
func (l *sSysUser) GetUserByPhone(ctx context.Context, phone string, tenantId string) (user *entity.SysUser, err error) {
|
||||
err = dao.SysUser.Ctx(ctx).Where(dao.SysUser.Columns().Phonenumber, phone).Where(dao.SysUser.Columns().TenantId, tenantId).Scan(&user)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, gerror.NewCode(consts.CodeUserNotFound, "账号不存在")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if user == nil {
|
||||
return nil, gerror.NewCode(consts.CodeUserNotFound, "账号不存在")
|
||||
}
|
||||
if user.Status == consts.SysUserStatusDisable {
|
||||
return nil, gerror.NewCode(consts.CodeUserDisabled, "账号已禁用")
|
||||
}
|
||||
if user.Status == consts.SysUserStatusLocked {
|
||||
return nil, gerror.NewCode(consts.CodeUserLocked, "账号已锁定")
|
||||
}
|
||||
if user.Status == consts.SysUserStatusExpired {
|
||||
return nil, gerror.NewCode(consts.CodeUserExpired, "账号已过期")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 根据用户名和密码获取用户信息
|
||||
func (l *sSysUser) GetUserByUsernameAndPassword(ctx context.Context, tenantId string, username string, password string) (user *entity.SysUser, err error) {
|
||||
// 判断用户名是否存在
|
||||
user, err = l.GetUserByUsername(ctx, username, tenantId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 判断密码是否正确
|
||||
password = utility.PasswordEncrypt(password, user.Salt)
|
||||
if password != user.Password {
|
||||
g.Log().Errorf(ctx, "密码错误: username:%s, password:%s, user:%+v", username, password, user)
|
||||
return nil, gerror.NewCode(consts.CodeUserPasswordError, "密码错误")
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// 根据用户ID获取用户信息
|
||||
func (l *sSysUser) GetUserById(ctx context.Context, id int64) (user *model.SysUserViewModel, err error) {
|
||||
err = l.Model(ctx).WithAll().Where(dao.SysUser.Columns().UserId, id).Scan(&user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 获取用户角色
|
||||
userRoles := make([]*entity.SysUserRole, 0)
|
||||
err = dao.SysUserRole.Ctx(ctx).Where(dao.SysUserRole.Columns().UserId, id).Scan(&userRoles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roleIds := make([]int64, 0)
|
||||
for _, userRole := range userRoles {
|
||||
roleIds = append(roleIds, userRole.RoleId)
|
||||
}
|
||||
roles := make([]*entity.SysRole, 0)
|
||||
err = dao.SysRole.Ctx(ctx).WhereIn(dao.SysRole.Columns().RoleId, roleIds).Scan(&roles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Roles = make([]*model.SysRoleMiniModel, 0)
|
||||
for _, role := range roles {
|
||||
mini := &model.SysRoleMiniModel{}
|
||||
gconv.Struct(role, mini)
|
||||
user.Roles = append(user.Roles, mini)
|
||||
}
|
||||
// 获取用户岗位
|
||||
userPosts := make([]*entity.SysUserPost, 0)
|
||||
err = dao.SysUserPost.Ctx(ctx).Where(dao.SysUserPost.Columns().UserId, id).Scan(&userPosts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
postIds := make([]int64, 0)
|
||||
for _, userPost := range userPosts {
|
||||
postIds = append(postIds, userPost.PostId)
|
||||
}
|
||||
posts := make([]*entity.SysPost, 0)
|
||||
err = dao.SysPost.Ctx(ctx).WhereIn(dao.SysPost.Columns().PostId, postIds).Scan(&posts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Posts = make([]*model.SysPostMiniModel, 0)
|
||||
for _, post := range posts {
|
||||
mini := &model.SysPostMiniModel{}
|
||||
gconv.Struct(post, mini)
|
||||
user.Posts = append(user.Posts, mini)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
func (l *sSysUser) List(ctx context.Context, page *request.PageInfo, query *model.UserListParam) (items []*model.SysUserListModel, total int, err error) {
|
||||
deptIds := make([]int64, 0)
|
||||
if query.DeptId != 0 {
|
||||
deptIds = append(deptIds, query.DeptId)
|
||||
subDeptIds, err := service.SysDept().GetDeptIdsByParentId(ctx, query.DeptId)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
deptIds = append(deptIds, subDeptIds...)
|
||||
}
|
||||
g.Log().Infof(ctx, "sSysUser.List 获取部门列表: deptIds:%+v", deptIds)
|
||||
// 实现分页查询逻辑
|
||||
var users []*model.SysUserListModel
|
||||
m := l.Model(ctx)
|
||||
if query.UserId != 0 {
|
||||
m = m.Where(dao.SysUser.Columns().UserId, query.UserId)
|
||||
}
|
||||
if len(deptIds) > 0 {
|
||||
m = m.WhereIn(dao.SysUser.Columns().DeptId, deptIds)
|
||||
}
|
||||
if query.UserName != "" {
|
||||
m = m.WhereLike(dao.SysUser.Columns().UserName, "%"+query.UserName+"%")
|
||||
}
|
||||
if query.NickName != "" {
|
||||
m = m.WhereLike(dao.SysUser.Columns().NickName, "%"+query.NickName+"%")
|
||||
}
|
||||
if query.Email != "" {
|
||||
m = m.WhereLike(dao.SysUser.Columns().Email, "%"+query.Email+"%")
|
||||
}
|
||||
if query.Phonenumber != "" {
|
||||
m = m.WhereLike(dao.SysUser.Columns().Phonenumber, "%"+query.Phonenumber+"%")
|
||||
}
|
||||
if query.Status != "" {
|
||||
m = m.Where(dao.SysUser.Columns().Status, query.Status)
|
||||
}
|
||||
if len(query.CreatedAt) == 2 {
|
||||
begin, end := gtime.NewFromStr(query.CreatedAt[0]), gtime.NewFromStr(query.CreatedAt[1])
|
||||
m = m.WhereBetween(dao.SysUser.Columns().CreatedAt, begin, end.EndOfDay())
|
||||
}
|
||||
if len(query.DeptIdList) > 0 {
|
||||
m = m.WhereIn(dao.SysUser.Columns().DeptId, query.DeptIdList)
|
||||
}
|
||||
total, err = m.Count()
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = m.WithAll().Page(page.Page, page.PageSize).Scan(&users)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
// g.Log().Infof(ctx, "获取用户列表成功: total:%d, users:%+v", total, users)
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
// 新增用户
|
||||
func (l *sSysUser) AddUser(ctx context.Context, req *model.SysUserAddModel) (data *model.SysUserViewModel, err error) {
|
||||
// 获取当前登录用户的租户
|
||||
if req.TenantId == "" {
|
||||
req.TenantId = contexts.GetTenantId(ctx)
|
||||
}
|
||||
// 判断部门是否存在
|
||||
dept, err := service.SysDept().GetDeptById(ctx, req.DeptId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dept == nil || dept.TenantId != req.TenantId {
|
||||
return nil, gerror.NewCode(consts.CodeDeptNotFound, "部门不存在")
|
||||
}
|
||||
|
||||
// 判断用户名是否存在(在当前租户下)
|
||||
var user *entity.SysUser
|
||||
err = dao.SysUser.Ctx(ctx).Where(dao.SysUser.Columns().UserName, req.UserName).Where(dao.SysUser.Columns().TenantId, req.TenantId).Scan(&user)
|
||||
if err != nil {
|
||||
if err != gorm.ErrRecordNotFound {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if user != nil {
|
||||
return nil, gerror.NewCode(consts.CodeUserExists, "用户名已存在")
|
||||
}
|
||||
// // 判断邮箱是否存在
|
||||
// user, err = l.GetUserByEmail(ctx, req.Email)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if user != nil {
|
||||
// return nil, gerror.NewCode(consts.CodeEmailExists, "邮箱已存在")
|
||||
// }
|
||||
// // 判断手机号是否存在
|
||||
// user, err = l.GetUserByPhone(ctx, req.Phone)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if user != nil {
|
||||
// return nil, gerror.NewCode(consts.CodePhoneExists, "手机号已存在")
|
||||
// }
|
||||
|
||||
// 随机生成5位字符
|
||||
salt := utility.RandomString(5)
|
||||
password := utility.PasswordEncrypt(req.Password, salt)
|
||||
|
||||
dataInsert := do.SysUser{}
|
||||
gconv.Struct(req, &dataInsert)
|
||||
dataInsert.Password = password
|
||||
dataInsert.Salt = salt
|
||||
dataInsert.CreatedDept = contexts.GetDeptId(ctx)
|
||||
dataInsert.CreatedBy = contexts.GetUserId(ctx)
|
||||
dataInsert.CreatedAt = gtime.Now()
|
||||
dataInsert.UpdatedBy = contexts.GetUserId(ctx)
|
||||
dataInsert.UpdatedAt = gtime.Now()
|
||||
dataInsert.TenantId = req.TenantId
|
||||
result, err := dao.SysUser.Ctx(ctx).Data(dataInsert).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userId, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err = l.GetUserById(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 新增用户角色
|
||||
d := make([]*do.SysUserRole, 0, len(req.RoleIds))
|
||||
if len(req.RoleIds) > 0 {
|
||||
for _, roleId := range req.RoleIds {
|
||||
d = append(d, &do.SysUserRole{
|
||||
UserId: userId,
|
||||
RoleId: roleId,
|
||||
})
|
||||
}
|
||||
_, err = dao.SysUserRole.Ctx(ctx).Data(d).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// 新增用户岗位
|
||||
if len(req.PostIds) > 0 {
|
||||
postData := make([]*do.SysUserPost, 0, len(req.PostIds))
|
||||
for _, postId := range req.PostIds {
|
||||
postData = append(postData, &do.SysUserPost{
|
||||
UserId: userId,
|
||||
PostId: postId,
|
||||
})
|
||||
}
|
||||
_, err = dao.SysUserPost.Ctx(ctx).Data(postData).Insert()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (l *sSysUser) Profile(ctx context.Context) (user *model.UserProfileModel, err error) {
|
||||
|
||||
u, err := l.GetUserById(ctx, contexts.GetUserId(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
companyInfo := service.SysDept().GetParentCompanyInfo(ctx, u.DeptId)
|
||||
return &model.UserProfileModel{
|
||||
SysUserViewModel: *u,
|
||||
CompanyInfo: &model.SysDeptMiniModel{
|
||||
DeptId: companyInfo.DeptId,
|
||||
DeptName: companyInfo.DeptName,
|
||||
ParentId: companyInfo.ParentId,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *sSysUser) UpdateCurrentUser(ctx context.Context, req *model.UpdateCurrentUserModel) (user *model.SysUserViewModel, err error) {
|
||||
userId := contexts.GetUserId(ctx)
|
||||
data := do.SysUser{}
|
||||
gconv.Struct(req, &data)
|
||||
data.UserId = nil
|
||||
_, err = l.Model(ctx).Where(dao.SysUser.Columns().UserId, userId).Data(data).OmitEmpty().Update()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err = l.GetUserById(ctx, userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyUserUpdate, userId)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (l *sSysUser) UpdateCurrentUserPassword(ctx context.Context, req *model.UpdateCurrentUserPasswordModel) (err error) {
|
||||
userId := contexts.GetUserId(ctx)
|
||||
var user *entity.SysUser
|
||||
err = l.Model(ctx).Where(dao.SysUser.Columns().UserId, userId).Scan(&user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if user == nil {
|
||||
return gerror.NewCode(consts.CodeUserNotFound, "用户不存在")
|
||||
}
|
||||
|
||||
oldPassword := utility.PasswordEncrypt(req.OldPassword, user.Salt)
|
||||
if oldPassword != user.Password {
|
||||
return gerror.NewCode(consts.CodeUserPasswordError, "旧密码错误")
|
||||
}
|
||||
salt := utility.RandomString(5)
|
||||
password := utility.PasswordEncrypt(req.NewPassword, salt)
|
||||
data := do.SysUser{
|
||||
Password: password,
|
||||
Salt: salt,
|
||||
UpdatedBy: userId,
|
||||
UpdatedAt: gtime.Now(),
|
||||
}
|
||||
_, err = l.Model(ctx).Where(dao.SysUser.Columns().UserId, userId).Data(data).OmitEmpty().Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *sSysUser) UpdateUser(ctx context.Context, req *model.SysUserUpdateModel) (err error) {
|
||||
currUserId := contexts.GetUserId(ctx)
|
||||
req.UpdatedAt = gtime.Now()
|
||||
req.UpdatedBy = &currUserId
|
||||
data := do.SysUser{}
|
||||
gconv.Struct(req, &data)
|
||||
data.UserId = nil
|
||||
_, err = l.Model(ctx).Where(dao.SysUser.Columns().UserId, req.UserId).Data(data).OmitNil().Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 用户角色
|
||||
urList := make([]*entity.SysUserRole, 0)
|
||||
err = dao.SysUserRole.Ctx(ctx).Where(dao.SysUserRole.Columns().UserId, req.UserId).Scan(&urList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 计算删除和新增的角色
|
||||
delRoleIds := make([]int64, 0)
|
||||
addRoleIds := make([]int64, 0)
|
||||
hasRoleId := false
|
||||
for _, ur := range urList {
|
||||
hasRoleId = false
|
||||
for _, roleId := range req.RoleIds {
|
||||
if ur.RoleId == roleId {
|
||||
hasRoleId = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasRoleId {
|
||||
delRoleIds = append(delRoleIds, ur.RoleId)
|
||||
}
|
||||
}
|
||||
for _, roleId := range req.RoleIds {
|
||||
hasRoleId = false
|
||||
for _, ur := range urList {
|
||||
if ur.RoleId == roleId {
|
||||
hasRoleId = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasRoleId {
|
||||
addRoleIds = append(addRoleIds, roleId)
|
||||
}
|
||||
}
|
||||
// 删除多余的角色
|
||||
if len(delRoleIds) > 0 {
|
||||
_, err = dao.SysUserRole.Ctx(ctx).Where(dao.SysUserRole.Columns().UserId, req.UserId).WhereIn(dao.SysUserRole.Columns().RoleId, delRoleIds).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 新增角色
|
||||
d := make([]*do.SysUserRole, 0, len(addRoleIds))
|
||||
if len(addRoleIds) > 0 {
|
||||
for _, roleId := range addRoleIds {
|
||||
d = append(d, &do.SysUserRole{
|
||||
UserId: req.UserId,
|
||||
RoleId: roleId,
|
||||
})
|
||||
}
|
||||
_, err = dao.SysUserRole.Ctx(ctx).Data(d).Insert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 查询用户岗位
|
||||
upList := make([]*entity.SysUserPost, 0)
|
||||
err = dao.SysUserPost.Ctx(ctx).Where(dao.SysUserPost.Columns().UserId, req.UserId).Scan(&upList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 计算删除和新增的岗位
|
||||
delPostIds := make([]int64, 0)
|
||||
addPostIds := make([]int64, 0)
|
||||
hasPostId := false
|
||||
for _, up := range upList {
|
||||
hasPostId = false
|
||||
for _, postId := range req.PostIds {
|
||||
if up.PostId == postId {
|
||||
hasPostId = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasPostId {
|
||||
delPostIds = append(delPostIds, up.PostId)
|
||||
}
|
||||
}
|
||||
for _, postId := range req.PostIds {
|
||||
hasPostId = false
|
||||
for _, up := range upList {
|
||||
if up.PostId == postId {
|
||||
hasPostId = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasPostId {
|
||||
addPostIds = append(addPostIds, postId)
|
||||
}
|
||||
}
|
||||
// 删除多余岗位
|
||||
if len(delPostIds) > 0 {
|
||||
_, err = dao.SysUserPost.Ctx(ctx).Where(dao.SysUserPost.Columns().UserId, req.UserId).WhereIn(dao.SysUserPost.Columns().PostId, delPostIds).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 新增岗位
|
||||
postData := make([]*do.SysUserPost, 0, len(addPostIds))
|
||||
if len(addPostIds) > 0 {
|
||||
for _, postId := range addPostIds {
|
||||
postData = append(postData, &do.SysUserPost{
|
||||
UserId: req.UserId,
|
||||
PostId: postId,
|
||||
})
|
||||
}
|
||||
_, err = dao.SysUserPost.Ctx(ctx).Data(postData).Insert()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyUserUpdate, req.UserId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *sSysUser) DeleteUser(ctx context.Context, userIds []int64) (err error) {
|
||||
if len(userIds) == 0 {
|
||||
g.Log().Warningf(ctx, "sSysUser.DeleteUser 用户ID列表不能为空. userIds: %v", userIds)
|
||||
return errors.New("用户ID列表不能为空")
|
||||
}
|
||||
currUserId := contexts.GetUserId(ctx)
|
||||
if slices.Contains(userIds, 1) {
|
||||
g.Log().Warningf(ctx, "sSysUser.DeleteUser 不能删除超级管理员. userIds: %v", userIds)
|
||||
return errors.New("不能删除超级管理员")
|
||||
}
|
||||
if slices.Contains(userIds, currUserId) {
|
||||
g.Log().Warningf(ctx, "sSysUser.DeleteUser 不能删除当前用户. userIds: %v, currUserId: %d", userIds, currUserId)
|
||||
return errors.New("不能删除当前用户")
|
||||
}
|
||||
isAdminUserIds, err := service.SysTenant().IsTenantAdmin(ctx, userIds)
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysUser.DeleteUser IsTenantAdmin err: %v, userIds: %v", err, userIds)
|
||||
return err
|
||||
}
|
||||
if len(isAdminUserIds) > 0 {
|
||||
g.Log().Warningf(ctx, "sSysUser.DeleteUser 不能删除租户管理员. isAdminUserIds: %v, userIds: %v", isAdminUserIds, userIds)
|
||||
return errors.New(fmt.Sprintf("不能删除租户管理员ID: %v", isAdminUserIds))
|
||||
}
|
||||
|
||||
data := do.SysUser{
|
||||
DeletedAt: gtime.Now(),
|
||||
DeletedBy: currUserId,
|
||||
}
|
||||
_, err = l.Model(ctx).WhereIn(dao.SysUser.Columns().UserId, userIds).Data(data).OmitEmpty().Update()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysUser.DeleteUser Update err: %v, userIds: %v", err, userIds)
|
||||
return err
|
||||
}
|
||||
|
||||
// 删除用户绑定关系
|
||||
_, err = dao.SysSocial.Ctx(ctx).WhereIn(dao.SysSocial.Columns().UserId, userIds).Delete()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysUser.DeleteUser SysSocial.Ctx(ctx).Delete err: %v, userIds: %v", err, userIds)
|
||||
return err
|
||||
}
|
||||
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyUserDelete, userIds)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *sSysUser) ResetPassword(ctx context.Context, userId int64, password string) (err error) {
|
||||
currUserId := contexts.GetUserId(ctx)
|
||||
salt := utility.RandomString(5)
|
||||
password = utility.PasswordEncrypt(password, salt)
|
||||
data := do.SysUser{
|
||||
Password: password,
|
||||
Salt: salt,
|
||||
UpdatedBy: currUserId,
|
||||
UpdatedAt: gtime.Now(),
|
||||
}
|
||||
_, err = l.Model(ctx).Where(dao.SysUser.Columns().UserId, userId).Data(data).OmitEmpty().Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
event.EventsInstance().Emit(ctx, consts.EventKeyUserUpdate, userId)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *sSysUser) UpdateLoginInfo(ctx context.Context, userId int64, loginIp string) (err error) {
|
||||
data := do.SysUser{
|
||||
LoginIp: loginIp,
|
||||
LoginDate: gtime.Now(),
|
||||
}
|
||||
_, err = dao.SysUser.Ctx(ctx).Where(dao.SysUser.Columns().UserId, userId).Data(data).Update()
|
||||
if err != nil {
|
||||
g.Log().Errorf(ctx, "sSysUser.UpdateLoginInfo err: %v, userId: %d, loginIp: %s", err, userId, loginIp)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取用户角色ID列表
|
||||
func (l *sSysUser) GetUserRoleIds(ctx context.Context, userId int64) (roleIds []int64, err error) {
|
||||
urList := make([]*entity.SysUserRole, 0)
|
||||
err = dao.SysUserRole.Ctx(ctx).Where(dao.SysUserRole.Columns().UserId, userId).Scan(&urList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, ur := range urList {
|
||||
roleIds = append(roleIds, ur.RoleId)
|
||||
}
|
||||
return roleIds, nil
|
||||
}
|
||||
|
||||
// 获取用户岗位ID列表
|
||||
func (l *sSysUser) GetUserPostIds(ctx context.Context, userId int64) (postIds []int64, err error) {
|
||||
upList := make([]*entity.SysUserPost, 0)
|
||||
err = dao.SysUserPost.Ctx(ctx).Where(dao.SysUserPost.Columns().UserId, userId).Scan(&upList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, up := range upList {
|
||||
postIds = append(postIds, up.PostId)
|
||||
}
|
||||
return postIds, nil
|
||||
}
|
||||
|
||||
func (l *sSysUser) Register(ctx context.Context, param *model.SysUserRegisterModel) (err error) {
|
||||
// 验证验证码
|
||||
err = service.SysCaptcha().VerifyCaptcha(ctx, param.CaptchaID, param.CaptchaValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 检查是否开启用户注册功能
|
||||
registerConfig := &model.SysConfigViewModel{}
|
||||
mod := dao.SysConfig.Ctx(ctx)
|
||||
mod = mod.Where(dao.SysConfig.Columns().ConfigKey, "sys.account.registerUser")
|
||||
mod = mod.Where(dao.SysConfig.Columns().TenantId, param.TenantId)
|
||||
err = mod.Scan(®isterConfig)
|
||||
if err != nil {
|
||||
return gerror.New("请联系管理员开启用户注册功能")
|
||||
}
|
||||
if registerConfig == nil || registerConfig.ConfigValue != "true" {
|
||||
return gerror.New("请联系管理员开启用户注册功能")
|
||||
}
|
||||
|
||||
// 获取租户信息
|
||||
tenant, err := service.SysTenant().View(ctx, &model.SysTenantViewParam{
|
||||
TenantId: param.TenantId,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tenant == nil {
|
||||
return gerror.New("租户无效")
|
||||
}
|
||||
|
||||
// 获取租户的默认注册组织ID
|
||||
config := &model.SysConfigViewModel{}
|
||||
mod = dao.SysConfig.Ctx(ctx)
|
||||
mod = mod.Where(dao.SysConfig.Columns().ConfigKey, consts.ConfigKeyTenantDefaultRegisterDeptId)
|
||||
mod = mod.Where(dao.SysConfig.Columns().TenantId, param.TenantId)
|
||||
err = mod.Scan(&config)
|
||||
if err != nil {
|
||||
return gerror.New("该租户不允许自主注册")
|
||||
}
|
||||
if config == nil || config.ConfigValue == "" {
|
||||
return gerror.New("该租户不允许自主注册")
|
||||
}
|
||||
|
||||
// 验证用户名是否存在
|
||||
mod1 := dao.SysUser.Ctx(ctx)
|
||||
mod1 = mod1.Where(dao.SysUser.Columns().UserName, param.UserName)
|
||||
mod1 = mod1.Where(dao.SysUser.Columns().TenantId, param.TenantId)
|
||||
total, err := mod1.Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if total > 0 {
|
||||
return gerror.New("用户名已存在")
|
||||
}
|
||||
// 随机生成5位字符
|
||||
salt := utility.RandomString(5)
|
||||
password := utility.PasswordEncrypt(param.Password, salt)
|
||||
|
||||
newUser := do.SysUser{
|
||||
UserName: param.UserName,
|
||||
NickName: "",
|
||||
Password: password,
|
||||
Salt: salt,
|
||||
TenantId: param.TenantId,
|
||||
DeptId: gconv.Int64(config.ConfigValue),
|
||||
Status: consts.SysDeptStatusNormal,
|
||||
CreatedBy: 0,
|
||||
CreatedAt: gtime.Now(),
|
||||
UpdatedBy: 0,
|
||||
UpdatedAt: gtime.Now(),
|
||||
}
|
||||
|
||||
userId, err := l.Model(ctx).Data(newUser).InsertAndGetId()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//配置角色
|
||||
// 新增用户角色
|
||||
mod = dao.SysConfig.Ctx(ctx)
|
||||
mod = mod.Where(dao.SysConfig.Columns().ConfigKey, consts.ConfigKeyTenantDefaultRegisterRoleId)
|
||||
mod = mod.Where(dao.SysConfig.Columns().TenantId, param.TenantId)
|
||||
err = mod.Scan(&config)
|
||||
if err != nil {
|
||||
g.Log().Error(ctx, "获取租户的默认角色失败", err)
|
||||
}
|
||||
if config != nil && config.ConfigValue != "" {
|
||||
roleId := gconv.Int64(config.ConfigValue)
|
||||
data := do.SysUserRole{
|
||||
UserId: userId,
|
||||
RoleId: roleId,
|
||||
}
|
||||
_, err = dao.SysUserRole.Ctx(ctx).Data(data).Insert()
|
||||
if err != nil {
|
||||
g.Log().Error(ctx, "获取租户的默认角色失败", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status 更新用户状态
|
||||
func (l *sSysUser) Status(ctx context.Context, param *model.SysUserStatusParam) (err error) {
|
||||
data := do.SysUser{
|
||||
Status: param.Status,
|
||||
}
|
||||
_, err = l.Model(ctx).Where(dao.SysUser.Columns().UserId, param.UserId).Data(data).OmitEmpty().Update()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 批量查询用户迷你信息
|
||||
func (l *sSysUser) BatchGetUserMiniInfo(ctx context.Context, userIds []int64) (users []*model.SysUserMiniModel, err error) {
|
||||
err = dao.SysUser.Ctx(ctx).WhereIn(dao.SysUser.Columns().UserId, userIds).Scan(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// package system
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/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) List(ctx context.Context, query *model.SysUserOnlineListParam, page *request.PageInfo) (items []*model.SysUserOnlineListModel, total int, err error) {
|
||||
tenantId := contexts.GetTenantId(ctx)
|
||||
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,82 @@
|
||||
// package system_test
|
||||
// @Link https://github.com/xiujiecn/xiu-admin
|
||||
// @Copyright Copyright (c) 2025 LiXiujie
|
||||
// @Author Lxj <li@example.com>
|
||||
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-app/utility"
|
||||
|
||||
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gctx"
|
||||
)
|
||||
|
||||
func TestSysUser_UpdateUserPassword(t *testing.T) {
|
||||
// 设置上下文用户身份为用户
|
||||
ctx := contexts.Set(gctx.New(), &model.Context{
|
||||
// 为了测试只设置了hook中需要用到的数据
|
||||
User: &model.Identity{
|
||||
BaseClaims: model.BaseClaims{
|
||||
ID: 1,
|
||||
TenantId: "000000",
|
||||
DeptId: 1,
|
||||
},
|
||||
},
|
||||
})
|
||||
previous := gdb.GetConfig("default")
|
||||
if err := gdb.SetConfigGroup("default", gdb.ConfigGroup{{Type: "sqlite", Link: "sqlite::@file(:memory:)", MaxOpenConnCount: 1}}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
database := g.DB()
|
||||
t.Cleanup(func() {
|
||||
if err := database.GetCore().ClearCacheAll(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := database.Close(ctx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := gdb.SetConfigGroup("default", previous); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
if err := database.GetCore().ClearCacheAll(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, statement := range []string{
|
||||
"CREATE TABLE sys_user (user_id INTEGER PRIMARY KEY, password TEXT, salt TEXT, updated_by INTEGER, updated_at DATETIME, tenant_id TEXT)",
|
||||
"INSERT INTO sys_user VALUES (1,'old','old',0,NULL,'000000'),(2,'unchanged','unchanged',0,NULL,'000000')",
|
||||
} {
|
||||
if _, err := database.Exec(ctx, statement); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
newPassword := "123456"
|
||||
salt := "123456"
|
||||
password := utility.PasswordEncrypt(utility.Md5(newPassword), salt)
|
||||
data := do.SysUser{
|
||||
Password: password,
|
||||
Salt: salt,
|
||||
UpdatedBy: 1,
|
||||
}
|
||||
_, err := dao.SysUser.Ctx(ctx).Where(dao.SysUser.Columns().UserId, 1).Data(data).OmitEmpty().Update()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual, err := database.GetValue(ctx, "SELECT password FROM sys_user WHERE user_id = 1")
|
||||
if err != nil || actual.String() != password {
|
||||
t.Fatalf("password=%v, error=%v", actual, err)
|
||||
}
|
||||
unchanged, err := database.GetValue(ctx, "SELECT password FROM sys_user WHERE user_id = 2")
|
||||
if err != nil || unchanged.String() != "unchanged" {
|
||||
t.Fatalf("unrelated user changed: password=%v, error=%v", unchanged, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// // package system_test
|
||||
// // @Link https://github.com/xiujiecn/xiu-admin
|
||||
// // @Copyright Copyright (c) 2025 LiXiujie
|
||||
// // @Author Lxj <li@example.com>
|
||||
// // @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
|
||||
package system_test
|
||||
|
||||
// import (
|
||||
// "context"
|
||||
// "encoding/json"
|
||||
// "fmt"
|
||||
// "testing"
|
||||
// "time"
|
||||
// "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
|
||||
// "git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
|
||||
// "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/worker"
|
||||
// "git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
|
||||
// "git.5iot.com/iotlcm-public/iotlcm-app/internal/tasks"
|
||||
|
||||
// _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
// "github.com/gogf/gf/v2/frame/g"
|
||||
// "github.com/gogf/gf/v2/os/gctx"
|
||||
// "github.com/gogf/gf/v2/util/gconv"
|
||||
// )
|
||||
|
||||
// func init() {
|
||||
// // 数据库只读,不执行
|
||||
// // g.DB().SetDryRun(true)
|
||||
// g.DB().SetDebug(true)
|
||||
// testing.Init()
|
||||
// }
|
||||
|
||||
// func TestTask_runOnce(t *testing.T) {
|
||||
// // 设置上下文用户身份为用户
|
||||
// ctx := context.WithValue(gctx.New(), consts.ContextKey, &model.Context{
|
||||
// // 为了测试只设置了hook中需要用到的数据
|
||||
// User: &model.Identity{
|
||||
// BaseClaims: model.BaseClaims{
|
||||
// ID: 1,
|
||||
// TenantId: "000000",
|
||||
// DeptId: 1,
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
|
||||
// exists := &model.SysJobViewModel{}
|
||||
// dao.SysJob.Ctx(ctx).Where(dao.SysJob.Columns().JobId, 4).Scan(&exists)
|
||||
// paramArr, _ := tasks.TasksInstance().ParseParameters(exists.JobParams)
|
||||
// taskData := tasks.Task{
|
||||
// ID: fmt.Sprintf("%s-job-%d", exists.InvokeTarget, exists.JobId),
|
||||
// TaskType: "Type-" + gconv.String(exists.MisfirePolicy),
|
||||
// MethodName: exists.InvokeTarget,
|
||||
// Params: paramArr,
|
||||
// Explain: exists.JobName,
|
||||
// }
|
||||
// runPayload, _ := json.Marshal(taskData)
|
||||
|
||||
// tasks.TasksInstance().Once(
|
||||
// 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),
|
||||
// )
|
||||
// }
|
||||
|
||||
// func TestTask_schedule(t *testing.T) {
|
||||
// // 设置上下文用户身份为用户
|
||||
// ctx := context.WithValue(gctx.New(), consts.ContextKey, &model.Context{
|
||||
// // 为了测试只设置了hook中需要用到的数据
|
||||
// User: &model.Identity{
|
||||
// BaseClaims: model.BaseClaims{
|
||||
// ID: 1,
|
||||
// TenantId: "000000",
|
||||
// DeptId: 1,
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
|
||||
// exists := &model.SysJobViewModel{}
|
||||
// dao.SysJob.Ctx(ctx).Where(dao.SysJob.Columns().JobId, 4).Scan(&exists)
|
||||
// paramArr, _ := tasks.TasksInstance().ParseParameters(exists.JobParams)
|
||||
// //获取task目录下是否绑定对应的方法
|
||||
// actualName, exist := tasks.TasksInstance().CheckFuncName(exists.InvokeTarget)
|
||||
// if !exist {
|
||||
// errInfo := fmt.Sprintf("没有绑定对应的方法:%s", exists.InvokeTarget)
|
||||
// g.Log().Error(ctx, errInfo)
|
||||
// }
|
||||
|
||||
// //传参解析
|
||||
// paramArr, err := tasks.TasksInstance().ParseParameters(exists.JobParams)
|
||||
// if err != nil {
|
||||
// g.Log().Error(ctx, 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)
|
||||
// _, err2 := tasks.TasksInstance().Cron(
|
||||
// 1,
|
||||
// worker.WithTaskCtx(context.Background()),
|
||||
// worker.WithTaskUid(taskData.ID), // 任务ID
|
||||
// worker.WithTaskGroup(taskData.MethodName), // 任务组
|
||||
// worker.WithTaskExpr("0/2 * * * * *"),
|
||||
// //worker.WithTaskExpr("@every 2s"),
|
||||
// worker.WithTaskTimeout(10),
|
||||
// worker.WithTaskReplace(true),
|
||||
// worker.WithTaskPayload(runPayload),
|
||||
// )
|
||||
// if err2 != nil {
|
||||
// g.Log().Debug(ctx, taskData.MethodName, taskData.Explain, "启动任务失败")
|
||||
// }
|
||||
|
||||
// time.Sleep(10 * time.Minute)
|
||||
// }
|
||||
|
||||
// func TestTask_remove(t *testing.T) {
|
||||
// // 设置上下文用户身份为用户
|
||||
// ctx := context.WithValue(gctx.New(), consts.ContextKey, &model.Context{
|
||||
// // 为了测试只设置了hook中需要用到的数据
|
||||
// User: &model.Identity{
|
||||
// BaseClaims: model.BaseClaims{
|
||||
// ID: 1,
|
||||
// TenantId: "000000",
|
||||
// DeptId: 1,
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
|
||||
// exists := &model.SysJobViewModel{}
|
||||
// dao.SysJob.Ctx(ctx).Where(dao.SysJob.Columns().JobId, 4).Scan(&exists)
|
||||
// taskJobId := fmt.Sprintf("%s-job-%d", exists.InvokeTarget, exists.JobId)
|
||||
// g.Log().Debug(ctx, taskJobId)
|
||||
// err := tasks.TasksInstance().Remove(taskJobId)
|
||||
// if err != nil {
|
||||
// g.Log().Debug(ctx, exists.JobName, "启动停止失败")
|
||||
// g.Log().Error(ctx, err)
|
||||
// }
|
||||
// }
|
||||
|
||||
// func TestTask_getTask(t *testing.T) {
|
||||
// ctx := context.WithValue(gctx.New(), consts.ContextKey, &model.Context{
|
||||
// // 为了测试只设置了hook中需要用到的数据
|
||||
// User: &model.Identity{
|
||||
// BaseClaims: model.BaseClaims{
|
||||
// ID: 1,
|
||||
// TenantId: "000000",
|
||||
// DeptId: 1,
|
||||
// },
|
||||
// },
|
||||
// })
|
||||
|
||||
// exists := &model.SysJobViewModel{}
|
||||
// dao.SysJob.Ctx(ctx).Where(dao.SysJob.Columns().JobId, 4).Scan(&exists)
|
||||
// taskJobId := fmt.Sprintf("%s-job-%d", exists.InvokeTarget, exists.JobId)
|
||||
// task, err := tasks.TasksInstance().GetTask(taskJobId)
|
||||
// if err != nil {
|
||||
// g.Log().Debug(ctx, exists.JobName, "获取任务失败")
|
||||
// g.Log().Error(ctx, err)
|
||||
// }
|
||||
// fmt.Println("task", task)
|
||||
// }
|
||||
Reference in New Issue
Block a user