Files
bj_power/bj_power_mes/internal/logic/auth.go
T
SunYF 0ab21b1234 feat&refactor: 完成多模块功能迭代与配置优化
本次提交覆盖多个业务模块的功能完善与体验优化:
1.  **鉴权与配置调整**:
    - 统一JWT滑动续签逻辑,简化Token存储,移除RefreshToken相关冗余代码
    - 调整多项目配置文件中JWT过期时间为3600秒,统一会话闲置窗口
    - 工位配置放开1~12限制,改为仅校验大于0
2.  **术语统一替换**:全链路将"精密件"替换为"电气件",修正物料管理描述
3.  **功能新增**:
    - 新增工位类型、工艺路线与产线点位台账模块
    - 添加工艺PDF预览面板、工位终端代理转发接口
    - 新增操作日志按操作人列表筛选、工位登出日志记录
    - 新增PLC移料指令与产线点位状态管理
4.  **业务流程优化**:
    - 调整BOM物料删除校验逻辑,优化工单备料计算
    - 补充物料图号、检测单号等追溯字段
    - 完善工艺流程图与工位绑定关系说明
    - 优化前端页面文案与交互细节
5.  **代码规范与维护**:
    - 新增通用工具函数与前端静态资源
    - 整理路由权限与中间件逻辑
    - 修复部分接口与配置的不兼容问题
2026-09-10 16:59:15 +08:00

541 lines
17 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"
"bj_power_mes/ent/userstation"
"golang.org/x/crypto/bcrypt"
)
// ------ 请求/返回 DTO ------
type LoginReq struct {
Username string `json:"username"`
Password string `json:"password"`
}
type LoginResp struct {
AccessToken string `json:"accessToken"`
AccessExpire int64 `json:"accessExpire"`
}
// sessionExpire 会话闲置窗口(秒),默认 1 小时
func (s *Service) sessionExpire() int64 {
if s.ctx.Config.Auth.AccessExpire > 0 {
return s.ctx.Config.Auth.AccessExpire
}
return 3600
}
// currentToken 签发 access token(滑动续签模式,无 refresh)
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
}
expire := s.sessionExpire()
access, err := tokenx.Issue(s.ctx.Config.Auth.AccessSecret, time.Duration(expire)*time.Second, claims)
if err != nil {
return LoginResp{}, err
}
return LoginResp{AccessToken: access, AccessExpire: expire}, nil
}
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"`
}
type ChangePasswordReq struct {
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
}
type permissionItem struct {
Code string `json:"code"`
Name string `json:"name"`
Path string `json:"path"`
Icon string `json:"icon"`
}
// 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("用户名或密码错误")
}
// 记录最近登录时间(仅后台登录;工位终端 StationLogin 不计入)
now := time.Now().Unix()
if err := s.ctx.EntClient.User.UpdateOneID(usr.ID).SetLastLoginAt(now).Exec(ctx); err != nil {
// 不阻塞登录,仅记日志
}
usr.LastLoginAt = &now
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
}
// 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 位")
}
if err := checkPasswordStrong(req.NewPassword); err != nil {
return err
}
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)
}
// ------ 工位终端登录(块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)
}
// ------ 用户 / 角色 / 权限 管理 ------
type UserReq struct {
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"` // 允许操作的工位号列表
}
// 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
}
func (s *Service) CreateUser(ctx context.Context, req UserReq) error {
if err := checkPasswordStrong(req.Password); err != nil {
return err
}
hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
if err != nil {
return err
}
status := req.Status
if status == "" {
status = "ENABLED"
}
create := s.ctx.EntClient.User.Create().
SetUsername(req.Username).
SetPassword(string(hash)).
SetName(req.Name).
SetRoleId(req.RoleId).
SetStatus(status).
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)
}
func (s *Service) UpdateUser(ctx context.Context, req UserReq) error {
// 内置管理员账号(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("系统内置管理员账号不允许修改")
}
}
u := s.ctx.EntClient.User.UpdateOneID(req.Id).SetRoleId(req.RoleId).SetName(req.Name).
SetCanLoginWorkstation(req.CanLoginWorkstation)
if req.Password != "" {
if err := checkPasswordStrong(req.Password); err != nil {
return err
}
hash, _ := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
u.SetPassword(string(hash))
}
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("")
}
if req.Status != "" {
u.SetStatus(req.Status)
}
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
}
func (s *Service) DeleteUser(ctx context.Context, id int) error {
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("系统内置管理员账号不允许删除")
}
return s.ctx.EntClient.User.DeleteOneID(id).Exec(ctx)
}
// 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"` // 允许登录/操作的工位号(空=全部工位)
LastLoginAt *int64 `json:"lastLoginAt"` // 最近登录时间(unix秒)null=从未登录
}
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),
LastLoginAt: u.LastLoginAt,
})
}
return out, nil
}
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 {
// 内置管理员角色(SUPER_ADMIN)不允许修改其权限(admin 拥有所有权限,且不可被缩减)
rl, err := s.ctx.EntClient.Role.Get(ctx, req.Id)
if err != nil {
return errors.New("角色不存在")
}
if rl.Code == "SUPER_ADMIN" {
return errors.New("内置管理员角色不允许修改其权限")
}
// 角色编码创建后不可修改(对齐 WMS 语义;内置角色 code 变更会破坏权限体系)
if req.Code != "" && req.Code != rl.Code {
return errors.New("角色编码创建后不可修改")
}
return s.ctx.EntClient.Role.UpdateOneID(req.Id).
SetName(req.Name).SetCode(rl.Code).SetRemark(req.Remark).
SetPermissionCodes(req.PermissionCodes).Exec(ctx)
}
func (s *Service) DeleteRole(ctx context.Context, id int) error {
// 保护:不可删除内置角色(超级管理员/生产操作员/质检员)
r, err := s.ctx.EntClient.Role.Get(ctx, id)
if err != nil {
return errors.New("角色不存在")
}
if r.Code == "SUPER_ADMIN" || r.Code == "OPERATOR" || r.Code == "INSPECTOR" {
return errors.New("内置角色不允许删除")
}
// 保护:仍有关联用户的角色不可删除(避免产生无主用户/失去全部管理员)
cnt, err := s.ctx.EntClient.User.Query().
Where(user.RoleId(id)).Count(ctx)
if err == nil && cnt > 0 {
return errors.New("该角色下仍有用户,请先移除相关用户")
}
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)
}