2026-08-28 15:06:01 +08:00
|
|
|
|
package logic
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
tokenx "bj_power_mes/common/token"
|
|
|
|
|
|
"bj_power_mes/ent"
|
|
|
|
|
|
"bj_power_mes/ent/permission"
|
|
|
|
|
|
"bj_power_mes/ent/role"
|
|
|
|
|
|
"bj_power_mes/ent/user"
|
2026-08-31 08:06:55 +08:00
|
|
|
|
"bj_power_mes/ent/userstation"
|
2026-08-28 15:06:01 +08:00
|
|
|
|
|
|
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// ------ 请求/返回 DTO ------
|
|
|
|
|
|
|
|
|
|
|
|
type LoginReq struct {
|
|
|
|
|
|
Username string `json:"username"`
|
|
|
|
|
|
Password string `json:"password"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type LoginResp struct {
|
|
|
|
|
|
AccessToken string `json:"accessToken"`
|
|
|
|
|
|
RefreshToken string `json:"refreshToken"`
|
|
|
|
|
|
AccessExpire int64 `json:"accessExpire"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type RefreshReq struct {
|
|
|
|
|
|
RefreshToken string `json:"refreshToken"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type UserInfoResp struct {
|
|
|
|
|
|
UserId int `json:"userId"`
|
|
|
|
|
|
Username string `json:"username"`
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
RoleId int `json:"roleId"`
|
|
|
|
|
|
RoleCode string `json:"roleCode"`
|
|
|
|
|
|
RoleName string `json:"roleName"`
|
|
|
|
|
|
Menus []permissionItem `json:"menus"`
|
|
|
|
|
|
PermissionCodes []string `json:"permissionCodes"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-28 17:03:03 +08:00
|
|
|
|
type ChangePasswordReq struct {
|
|
|
|
|
|
OldPassword string `json:"oldPassword"`
|
|
|
|
|
|
NewPassword string `json:"newPassword"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-28 15:06:01 +08:00
|
|
|
|
type permissionItem struct {
|
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
Path string `json:"path"`
|
|
|
|
|
|
Icon string `json:"icon"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// currentToken 生成 access + refresh 双 token
|
|
|
|
|
|
func (s *Service) issueTokens(usr *ent.User, roleEntity *ent.Role) (LoginResp, error) {
|
|
|
|
|
|
claims := tokenx.Claims{UserId: usr.ID, Username: usr.Username, Name: usr.Name, RoleId: usr.RoleId}
|
|
|
|
|
|
if roleEntity != nil {
|
|
|
|
|
|
claims.RoleCode = roleEntity.Code
|
|
|
|
|
|
claims.RoleName = roleEntity.Name
|
|
|
|
|
|
} else {
|
|
|
|
|
|
claims.RoleCode = ""
|
|
|
|
|
|
claims.RoleName = ""
|
|
|
|
|
|
}
|
|
|
|
|
|
access, err := tokenx.Issue(s.ctx.Config.Auth.AccessSecret, time.Duration(s.ctx.Config.Auth.AccessExpire)*time.Second, claims)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return LoginResp{}, err
|
|
|
|
|
|
}
|
|
|
|
|
|
refresh, err := tokenx.Issue(s.ctx.Config.Auth.AccessSecret, time.Duration(s.ctx.Config.Auth.RefreshExpire)*time.Second, claims)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return LoginResp{}, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return LoginResp{AccessToken: access, RefreshToken: refresh, AccessExpire: s.ctx.Config.Auth.AccessExpire}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Login 登录
|
|
|
|
|
|
func (s *Service) Login(ctx context.Context, req LoginReq) (LoginResp, error) {
|
|
|
|
|
|
var resp LoginResp
|
|
|
|
|
|
usr, err := s.ctx.EntClient.User.Query().Where(user.Username(req.Username)).First(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return resp, errors.New("用户名或密码错误")
|
|
|
|
|
|
}
|
|
|
|
|
|
if usr.Status != "ENABLED" {
|
|
|
|
|
|
return resp, errors.New("账号已禁用")
|
|
|
|
|
|
}
|
|
|
|
|
|
if bcrypt.CompareHashAndPassword([]byte(usr.Password), []byte(req.Password)) != nil {
|
|
|
|
|
|
return resp, errors.New("用户名或密码错误")
|
|
|
|
|
|
}
|
2026-09-08 11:44:04 +08:00
|
|
|
|
// 记录最近登录时间(仅后台登录;工位终端 StationLogin 不计入)
|
|
|
|
|
|
now := time.Now().Unix()
|
|
|
|
|
|
if err := s.ctx.EntClient.User.UpdateOneID(usr.ID).SetLastLoginAt(now).Exec(ctx); err != nil {
|
|
|
|
|
|
// 不阻塞登录,仅记日志
|
|
|
|
|
|
}
|
|
|
|
|
|
usr.LastLoginAt = &now
|
2026-08-28 15:06:01 +08:00
|
|
|
|
roleEntity, _ := s.ctx.EntClient.Role.Get(ctx, usr.RoleId)
|
|
|
|
|
|
return s.issueTokens(usr, roleEntity)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Refresh 刷新访问令牌(滑动续签)
|
|
|
|
|
|
func (s *Service) Refresh(ctx context.Context, req RefreshReq) (LoginResp, error) {
|
|
|
|
|
|
var resp LoginResp
|
|
|
|
|
|
claims, err := tokenx.Parse(s.ctx.Config.Auth.AccessSecret, req.RefreshToken)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return resp, errors.New("刷新令牌无效或已过期")
|
|
|
|
|
|
}
|
|
|
|
|
|
usr, err := s.ctx.EntClient.User.Get(ctx, claims.UserId)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return resp, errors.New("用户不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
if usr.Status != "ENABLED" {
|
|
|
|
|
|
return resp, errors.New("账号已禁用")
|
|
|
|
|
|
}
|
|
|
|
|
|
roleEntity, _ := s.ctx.EntClient.Role.Get(ctx, usr.RoleId)
|
|
|
|
|
|
return s.issueTokens(usr, roleEntity)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UserInfo 当前用户信息 + 菜单权限
|
|
|
|
|
|
func (s *Service) UserInfo(ctx context.Context, userId int) (UserInfoResp, error) {
|
|
|
|
|
|
var out UserInfoResp
|
|
|
|
|
|
usr, err := s.ctx.EntClient.User.Get(ctx, userId)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return out, err
|
|
|
|
|
|
}
|
|
|
|
|
|
out.UserId = usr.ID
|
|
|
|
|
|
out.Username = usr.Username
|
|
|
|
|
|
out.Name = usr.Name
|
|
|
|
|
|
out.RoleId = usr.RoleId
|
|
|
|
|
|
|
|
|
|
|
|
roleEntity, err := s.ctx.EntClient.Role.Get(ctx, usr.RoleId)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
roleEntity = nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if roleEntity != nil {
|
|
|
|
|
|
out.RoleCode = roleEntity.Code
|
|
|
|
|
|
out.RoleName = roleEntity.Name
|
|
|
|
|
|
out.PermissionCodes = roleEntity.PermissionCodes
|
|
|
|
|
|
}
|
|
|
|
|
|
// 取所拥有权限对应的菜单
|
|
|
|
|
|
perms, err := s.ctx.EntClient.Permission.Query().
|
|
|
|
|
|
Where(permission.Type("MENU")).
|
|
|
|
|
|
Order(ent.Asc(permission.FieldSort)).All(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return out, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
owned := map[string]bool{}
|
|
|
|
|
|
for _, c := range out.PermissionCodes {
|
|
|
|
|
|
owned[c] = true
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, p := range perms {
|
|
|
|
|
|
if roleEntity == nil {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if roleEntity.Code == "SUPER_ADMIN" || owned[p.Code] || owned["*"] {
|
|
|
|
|
|
out.Menus = append(out.Menus, permissionItem{Code: p.Code, Name: p.Name, Path: p.Path, Icon: p.Icon})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return out, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-28 17:03:03 +08:00
|
|
|
|
// ChangePassword 当前登录用户修改自己的密码
|
|
|
|
|
|
func (s *Service) ChangePassword(ctx context.Context, userId int, req ChangePasswordReq) error {
|
|
|
|
|
|
if userId <= 0 {
|
|
|
|
|
|
return errors.New("未登录")
|
|
|
|
|
|
}
|
|
|
|
|
|
if req.OldPassword == "" || req.NewPassword == "" {
|
|
|
|
|
|
return errors.New("请输入旧密码与新密码")
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(req.NewPassword) < 6 {
|
|
|
|
|
|
return errors.New("新密码长度至少 6 位")
|
|
|
|
|
|
}
|
2026-08-29 15:30:25 +08:00
|
|
|
|
if err := checkPasswordStrong(req.NewPassword); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-08-28 17:03:03 +08:00
|
|
|
|
usr, err := s.ctx.EntClient.User.Get(ctx, userId)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.New("用户不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
if bcrypt.CompareHashAndPassword([]byte(usr.Password), []byte(req.OldPassword)) != nil {
|
|
|
|
|
|
return errors.New("旧密码不正确")
|
|
|
|
|
|
}
|
|
|
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return s.ctx.EntClient.User.UpdateOneID(userId).SetPassword(string(hash)).Exec(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-31 08:06:55 +08:00
|
|
|
|
// ------ 工位终端登录(块1:用户体系来源于 MES,终端登录改调本接口) ------
|
|
|
|
|
|
|
|
|
|
|
|
type StationLoginReq struct {
|
|
|
|
|
|
Username string `json:"username"`
|
|
|
|
|
|
Password string `json:"password"`
|
|
|
|
|
|
StationNo int `json:"stationNo"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type StationLoginResp struct {
|
|
|
|
|
|
AccessToken string `json:"accessToken"`
|
|
|
|
|
|
ExpireAt int64 `json:"expireAt"`
|
|
|
|
|
|
User UserInfoResp `json:"user"`
|
|
|
|
|
|
Stations []int `json:"stations"`
|
|
|
|
|
|
StationNo int `json:"stationNo"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// StationLogin 工位终端登录:校验 用户名 + 工位终端密码 + 工位权限
|
|
|
|
|
|
func (s *Service) StationLogin(ctx context.Context, req StationLoginReq) (StationLoginResp, error) {
|
|
|
|
|
|
var resp StationLoginResp
|
|
|
|
|
|
if req.Username == "" || req.Password == "" {
|
|
|
|
|
|
return resp, errors.New("用户名和密码必填")
|
|
|
|
|
|
}
|
|
|
|
|
|
usr, err := s.ctx.EntClient.User.Query().Where(user.Username(req.Username)).First(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return resp, errors.New("用户名或密码错误")
|
|
|
|
|
|
}
|
|
|
|
|
|
if usr.Status != "ENABLED" {
|
|
|
|
|
|
return resp, errors.New("账号已禁用")
|
|
|
|
|
|
}
|
|
|
|
|
|
if !usr.CanLoginWorkstation {
|
|
|
|
|
|
return resp, errors.New("该账号未开通工位终端登录权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
if usr.WorkstationPassword == "" || bcrypt.CompareHashAndPassword([]byte(usr.WorkstationPassword), []byte(req.Password)) != nil {
|
|
|
|
|
|
return resp, errors.New("用户名或密码错误")
|
|
|
|
|
|
}
|
|
|
|
|
|
stations := s.UserStations(ctx, usr.ID)
|
|
|
|
|
|
if len(stations) == 0 {
|
|
|
|
|
|
for i := 1; i <= 12; i++ {
|
|
|
|
|
|
stations = append(stations, i)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if req.StationNo > 0 {
|
|
|
|
|
|
allowed := false
|
|
|
|
|
|
for _, no := range stations {
|
|
|
|
|
|
if no == req.StationNo {
|
|
|
|
|
|
allowed = true
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if !allowed {
|
|
|
|
|
|
return resp, errors.New("该账号无此工位的操作权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
roleEntity, _ := s.ctx.EntClient.Role.Get(ctx, usr.RoleId)
|
|
|
|
|
|
claims := tokenx.Claims{UserId: usr.ID, Username: usr.Username, Name: usr.Name, RoleId: usr.RoleId, StationNo: req.StationNo}
|
|
|
|
|
|
if roleEntity != nil {
|
|
|
|
|
|
claims.RoleCode = roleEntity.Code
|
|
|
|
|
|
claims.RoleName = roleEntity.Name
|
|
|
|
|
|
}
|
|
|
|
|
|
access, err := tokenx.Issue(s.ctx.Config.Auth.AccessSecret, time.Duration(s.ctx.Config.Auth.AccessExpire)*time.Second, claims)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return resp, err
|
|
|
|
|
|
}
|
|
|
|
|
|
resp.AccessToken = access
|
|
|
|
|
|
resp.ExpireAt = time.Now().Add(time.Duration(s.ctx.Config.Auth.AccessExpire) * time.Second).Unix()
|
|
|
|
|
|
resp.User = UserInfoResp{UserId: usr.ID, Username: usr.Username, Name: usr.Name, RoleId: usr.RoleId}
|
|
|
|
|
|
if roleEntity != nil {
|
|
|
|
|
|
resp.User.RoleCode = roleEntity.Code
|
|
|
|
|
|
resp.User.RoleName = roleEntity.Name
|
|
|
|
|
|
}
|
|
|
|
|
|
resp.Stations = stations
|
|
|
|
|
|
resp.StationNo = req.StationNo
|
|
|
|
|
|
return resp, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// StationChangePassword 工位终端修改自己的工位终端密码
|
|
|
|
|
|
func (s *Service) StationChangePassword(ctx context.Context, userId int, oldPwd, newPwd string) error {
|
|
|
|
|
|
if userId <= 0 {
|
|
|
|
|
|
return errors.New("未登录")
|
|
|
|
|
|
}
|
|
|
|
|
|
if oldPwd == "" || newPwd == "" {
|
|
|
|
|
|
return errors.New("请输入原密码与新密码")
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := checkPasswordStrong(newPwd); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
usr, err := s.ctx.EntClient.User.Get(ctx, userId)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.New("用户不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
if !usr.CanLoginWorkstation {
|
|
|
|
|
|
return errors.New("该账号未开通工位终端登录权限")
|
|
|
|
|
|
}
|
|
|
|
|
|
if bcrypt.CompareHashAndPassword([]byte(usr.WorkstationPassword), []byte(oldPwd)) != nil {
|
|
|
|
|
|
return errors.New("原工位终端密码不正确")
|
|
|
|
|
|
}
|
|
|
|
|
|
hash, _ := bcrypt.GenerateFromPassword([]byte(newPwd), bcrypt.DefaultCost)
|
|
|
|
|
|
return s.ctx.EntClient.User.UpdateOneID(userId).SetWorkstationPassword(string(hash)).Exec(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-28 15:06:01 +08:00
|
|
|
|
// ------ 用户 / 角色 / 权限 管理 ------
|
|
|
|
|
|
|
|
|
|
|
|
type UserReq struct {
|
2026-08-31 08:06:55 +08:00
|
|
|
|
Id int `json:"id"`
|
|
|
|
|
|
Username string `json:"username"`
|
|
|
|
|
|
Password string `json:"password"`
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
RoleId int `json:"roleId"`
|
|
|
|
|
|
Status string `json:"status"`
|
|
|
|
|
|
CanLoginWorkstation bool `json:"canLoginWorkstation"`
|
|
|
|
|
|
WorkstationPassword string `json:"workstationPassword"`
|
|
|
|
|
|
Stations []int `json:"stations"` // 允许操作的工位号列表
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-29 15:30:25 +08:00
|
|
|
|
// checkPasswordStrong 校验密码:长度>=6 且不能是纯数字
|
|
|
|
|
|
func checkPasswordStrong(pwd string) error {
|
|
|
|
|
|
if len(pwd) < 6 {
|
|
|
|
|
|
return errors.New("密码长度至少 6 位")
|
|
|
|
|
|
}
|
|
|
|
|
|
allDigit := true
|
|
|
|
|
|
for _, r := range pwd {
|
|
|
|
|
|
if r < '0' || r > '9' {
|
|
|
|
|
|
allDigit = false
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if allDigit {
|
|
|
|
|
|
return errors.New("密码不能是纯数字")
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-28 15:06:01 +08:00
|
|
|
|
func (s *Service) CreateUser(ctx context.Context, req UserReq) error {
|
2026-08-29 15:30:25 +08:00
|
|
|
|
if err := checkPasswordStrong(req.Password); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-08-28 15:06:01 +08:00
|
|
|
|
hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
status := req.Status
|
|
|
|
|
|
if status == "" {
|
|
|
|
|
|
status = "ENABLED"
|
|
|
|
|
|
}
|
2026-08-31 08:06:55 +08:00
|
|
|
|
create := s.ctx.EntClient.User.Create().
|
2026-08-28 15:06:01 +08:00
|
|
|
|
SetUsername(req.Username).
|
|
|
|
|
|
SetPassword(string(hash)).
|
|
|
|
|
|
SetName(req.Name).
|
|
|
|
|
|
SetRoleId(req.RoleId).
|
|
|
|
|
|
SetStatus(status).
|
2026-08-31 08:06:55 +08:00
|
|
|
|
SetCanLoginWorkstation(req.CanLoginWorkstation)
|
|
|
|
|
|
if req.CanLoginWorkstation {
|
|
|
|
|
|
wsPwd := req.WorkstationPassword
|
|
|
|
|
|
if wsPwd == "" {
|
|
|
|
|
|
wsPwd = req.Password // 默认复用登录密码
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := checkPasswordStrong(wsPwd); err != nil {
|
|
|
|
|
|
return errors.New("工位终端密码" + err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
wsHash, err := bcrypt.GenerateFromPassword([]byte(wsPwd), bcrypt.DefaultCost)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
create.SetWorkstationPassword(string(wsHash))
|
|
|
|
|
|
}
|
|
|
|
|
|
usr, err := create.Save(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return s.replaceUserStations(ctx, usr.ID, req.Stations)
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) UpdateUser(ctx context.Context, req UserReq) error {
|
2026-09-03 18:13:59 +08:00
|
|
|
|
// 内置管理员账号(SUPER_ADMIN)不允许通过管理接口修改(含禁用/改角色/改密码等)
|
|
|
|
|
|
if usr, e := s.ctx.EntClient.User.Get(ctx, req.Id); e == nil {
|
|
|
|
|
|
if usr.Username == "admin" {
|
|
|
|
|
|
return errors.New("系统内置管理员账号不允许修改")
|
|
|
|
|
|
}
|
|
|
|
|
|
if rl, e2 := s.ctx.EntClient.Role.Get(ctx, usr.RoleId); e2 == nil && rl.Code == "SUPER_ADMIN" {
|
|
|
|
|
|
return errors.New("系统内置管理员账号不允许修改")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-31 08:06:55 +08:00
|
|
|
|
u := s.ctx.EntClient.User.UpdateOneID(req.Id).SetRoleId(req.RoleId).SetName(req.Name).
|
|
|
|
|
|
SetCanLoginWorkstation(req.CanLoginWorkstation)
|
2026-08-28 15:06:01 +08:00
|
|
|
|
if req.Password != "" {
|
2026-08-29 15:30:25 +08:00
|
|
|
|
if err := checkPasswordStrong(req.Password); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
2026-08-28 15:06:01 +08:00
|
|
|
|
hash, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
|
|
|
|
|
u.SetPassword(string(hash))
|
|
|
|
|
|
}
|
2026-08-31 08:06:55 +08:00
|
|
|
|
if req.CanLoginWorkstation {
|
|
|
|
|
|
if req.WorkstationPassword != "" {
|
|
|
|
|
|
if err := checkPasswordStrong(req.WorkstationPassword); err != nil {
|
|
|
|
|
|
return errors.New("工位终端密码" + err.Error())
|
|
|
|
|
|
}
|
|
|
|
|
|
wsHash, _ := bcrypt.GenerateFromPassword([]byte(req.WorkstationPassword), bcrypt.DefaultCost)
|
|
|
|
|
|
u.SetWorkstationPassword(string(wsHash))
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
u.SetWorkstationPassword("")
|
|
|
|
|
|
}
|
2026-08-28 15:06:01 +08:00
|
|
|
|
if req.Status != "" {
|
|
|
|
|
|
u.SetStatus(req.Status)
|
|
|
|
|
|
}
|
2026-08-31 08:06:55 +08:00
|
|
|
|
if err := u.Exec(ctx); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
return s.replaceUserStations(ctx, req.Id, req.Stations)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// replaceUserStations 全量替换用户-工位关联
|
|
|
|
|
|
func (s *Service) replaceUserStations(ctx context.Context, userId int, stations []int) error {
|
|
|
|
|
|
if _, err := s.ctx.EntClient.UserStation.Delete().Where(userstation.UserId(userId)).Exec(ctx); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, no := range stations {
|
|
|
|
|
|
if no <= 0 {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := s.ctx.EntClient.UserStation.Create().
|
|
|
|
|
|
SetUserId(userId).SetStationNo(no).Exec(ctx); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// UserStations 查询用户绑定的工位号
|
|
|
|
|
|
func (s *Service) UserStations(ctx context.Context, userId int) []int {
|
|
|
|
|
|
rows, _ := s.ctx.EntClient.UserStation.Query().
|
|
|
|
|
|
Where(userstation.UserId(userId)).All(ctx)
|
|
|
|
|
|
out := make([]int, 0, len(rows))
|
|
|
|
|
|
for _, r := range rows {
|
|
|
|
|
|
out = append(out, r.StationNo)
|
|
|
|
|
|
}
|
|
|
|
|
|
return out
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) DeleteUser(ctx context.Context, id int) error {
|
2026-09-03 18:13:59 +08:00
|
|
|
|
usr, err := s.ctx.EntClient.User.Get(ctx, id)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.New("用户不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
// 内置管理员账号(SUPER_ADMIN)不允许删除
|
|
|
|
|
|
if usr.Username == "admin" {
|
|
|
|
|
|
return errors.New("系统内置管理员账号不允许删除")
|
|
|
|
|
|
}
|
|
|
|
|
|
if rl, e2 := s.ctx.EntClient.Role.Get(ctx, usr.RoleId); e2 == nil && rl.Code == "SUPER_ADMIN" {
|
|
|
|
|
|
return errors.New("系统内置管理员账号不允许删除")
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
return s.ctx.EntClient.User.DeleteOneID(id).Exec(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-31 08:06:55 +08:00
|
|
|
|
// UserVO 用户列表返回体(含工位终端属性与允许操作工位)
|
|
|
|
|
|
type UserVO struct {
|
|
|
|
|
|
Id int `json:"id"`
|
|
|
|
|
|
Username string `json:"username"`
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
RoleId int `json:"roleId"`
|
|
|
|
|
|
Status string `json:"status"`
|
|
|
|
|
|
CanLoginWorkstation bool `json:"canLoginWorkstation"`
|
|
|
|
|
|
Stations []int `json:"stations"` // 允许登录/操作的工位号(空=全部工位)
|
2026-09-08 11:44:04 +08:00
|
|
|
|
LastLoginAt *int64 `json:"lastLoginAt"` // 最近登录时间(unix秒),null=从未登录
|
2026-08-31 08:06:55 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) ListUsers(ctx context.Context) ([]UserVO, error) {
|
|
|
|
|
|
usrs, err := s.ctx.EntClient.User.Query().Order(ent.Asc(user.FieldID)).All(ctx)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
out := make([]UserVO, 0, len(usrs))
|
|
|
|
|
|
for _, u := range usrs {
|
|
|
|
|
|
out = append(out, UserVO{
|
|
|
|
|
|
Id: u.ID,
|
|
|
|
|
|
Username: u.Username,
|
|
|
|
|
|
Name: u.Name,
|
|
|
|
|
|
RoleId: u.RoleId,
|
|
|
|
|
|
Status: u.Status,
|
|
|
|
|
|
CanLoginWorkstation: u.CanLoginWorkstation,
|
|
|
|
|
|
Stations: s.UserStations(ctx, u.ID),
|
2026-09-08 11:44:04 +08:00
|
|
|
|
LastLoginAt: u.LastLoginAt,
|
2026-08-31 08:06:55 +08:00
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return out, nil
|
2026-08-28 15:06:01 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type RoleReq struct {
|
|
|
|
|
|
Id int `json:"id"`
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
|
Remark string `json:"remark"`
|
|
|
|
|
|
PermissionCodes []string `json:"permissionCodes"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) CreateRole(ctx context.Context, req RoleReq) error {
|
|
|
|
|
|
return s.ctx.EntClient.Role.Create().
|
|
|
|
|
|
SetName(req.Name).SetCode(req.Code).SetRemark(req.Remark).
|
|
|
|
|
|
SetPermissionCodes(req.PermissionCodes).Exec(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) UpdateRole(ctx context.Context, req RoleReq) error {
|
2026-09-03 18:13:59 +08:00
|
|
|
|
// 内置管理员角色(SUPER_ADMIN)不允许修改其权限(admin 拥有所有权限,且不可被缩减)
|
2026-09-04 14:18:53 +08:00
|
|
|
|
rl, err := s.ctx.EntClient.Role.Get(ctx, req.Id)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.New("角色不存在")
|
|
|
|
|
|
}
|
|
|
|
|
|
if rl.Code == "SUPER_ADMIN" {
|
2026-09-03 18:13:59 +08:00
|
|
|
|
return errors.New("内置管理员角色不允许修改其权限")
|
|
|
|
|
|
}
|
2026-09-04 14:18:53 +08:00
|
|
|
|
// 角色编码创建后不可修改(对齐 WMS 语义;内置角色 code 变更会破坏权限体系)
|
|
|
|
|
|
if req.Code != "" && req.Code != rl.Code {
|
|
|
|
|
|
return errors.New("角色编码创建后不可修改")
|
|
|
|
|
|
}
|
2026-08-28 15:06:01 +08:00
|
|
|
|
return s.ctx.EntClient.Role.UpdateOneID(req.Id).
|
2026-09-04 14:18:53 +08:00
|
|
|
|
SetName(req.Name).SetCode(rl.Code).SetRemark(req.Remark).
|
2026-08-28 15:06:01 +08:00
|
|
|
|
SetPermissionCodes(req.PermissionCodes).Exec(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) DeleteRole(ctx context.Context, id int) error {
|
2026-09-04 14:18:53 +08:00
|
|
|
|
// 保护:不可删除内置角色(超级管理员/生产操作员/质检员)
|
2026-08-29 15:30:25 +08:00
|
|
|
|
r, err := s.ctx.EntClient.Role.Get(ctx, id)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return errors.New("角色不存在")
|
|
|
|
|
|
}
|
2026-09-04 14:18:53 +08:00
|
|
|
|
if r.Code == "SUPER_ADMIN" || r.Code == "OPERATOR" || r.Code == "INSPECTOR" {
|
|
|
|
|
|
return errors.New("内置角色不允许删除")
|
2026-08-29 15:30:25 +08:00
|
|
|
|
}
|
|
|
|
|
|
// 保护:仍有关联用户的角色不可删除(避免产生无主用户/失去全部管理员)
|
|
|
|
|
|
cnt, err := s.ctx.EntClient.User.Query().
|
|
|
|
|
|
Where(user.RoleId(id)).Count(ctx)
|
|
|
|
|
|
if err == nil && cnt > 0 {
|
|
|
|
|
|
return errors.New("该角色下仍有用户,请先移除相关用户")
|
|
|
|
|
|
}
|
2026-08-28 15:06:01 +08:00
|
|
|
|
return s.ctx.EntClient.Role.DeleteOneID(id).Exec(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) ListRoles(ctx context.Context) ([]*ent.Role, error) {
|
|
|
|
|
|
return s.ctx.EntClient.Role.Query().Order(ent.Asc(role.FieldID)).All(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
type PermissionReq struct {
|
|
|
|
|
|
Id int `json:"id"`
|
|
|
|
|
|
Code string `json:"code"`
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
|
Path string `json:"path"`
|
|
|
|
|
|
Icon string `json:"icon"`
|
|
|
|
|
|
Sort int `json:"sort"`
|
|
|
|
|
|
Remark string `json:"remark"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) CreatePermission(ctx context.Context, req PermissionReq) error {
|
|
|
|
|
|
return s.ctx.EntClient.Permission.Create().
|
|
|
|
|
|
SetCode(req.Code).SetName(req.Name).SetType(req.Type).
|
|
|
|
|
|
SetPath(req.Path).SetIcon(req.Icon).SetSort(req.Sort).SetRemark(req.Remark).Exec(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) UpdatePermission(ctx context.Context, req PermissionReq) error {
|
|
|
|
|
|
return s.ctx.EntClient.Permission.UpdateOneID(req.Id).
|
|
|
|
|
|
SetCode(req.Code).SetName(req.Name).SetType(req.Type).
|
|
|
|
|
|
SetPath(req.Path).SetIcon(req.Icon).SetSort(req.Sort).SetRemark(req.Remark).Exec(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) DeletePermission(ctx context.Context, id int) error {
|
|
|
|
|
|
return s.ctx.EntClient.Permission.DeleteOneID(id).Exec(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) ListPermissions(ctx context.Context) ([]*ent.Permission, error) {
|
|
|
|
|
|
return s.ctx.EntClient.Permission.Query().Order(ent.Asc(permission.FieldSort), ent.Asc(permission.FieldID)).All(ctx)
|
|
|
|
|
|
}
|