Files
bj_power/bj_power_wms/internal/handler/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

182 lines
5.3 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 handler
import (
"net/http"
"strings"
"time"
"bj_power_wms/ent/user"
"bj_power_wms/internal/svc"
"golang.org/x/crypto/bcrypt"
)
func healthHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
_, err := ctx.EntClient.User.Query().First(ctx0())
if err == nil || strings.Contains(err.Error(), "no rows") {
ok(w, map[string]any{
"status": "UP",
"time": time.Now().Format("2006-01-02 15:04:05"),
"service": "bj_power_wms",
})
return
}
fail(w, http.StatusServiceUnavailable, "database unavailable")
}
}
// isWeakPassword 弱密码判定:密码为纯数字(安全性低)。
// 登录时不拦截此类账号(业务上仍允许登录),仅在登录响应里标记,由前端提示尽快修改。
func isWeakPassword(pwd string) bool {
if pwd == "" {
return false
}
for _, r := range pwd {
if r < '0' || r > '9' {
return false
}
}
return true
}
func loginHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := parseJSON(r, &req); err != nil {
fail(w, http.StatusBadRequest, "参数错误")
return
}
if req.Username == "" || req.Password == "" {
fail(w, http.StatusBadRequest, "用户名和密码必填")
return
}
u, err := ctx.EntClient.User.Query().
Where(user.UsernameEQ(req.Username)).
Only(ctx0())
if err != nil {
fail(w, http.StatusUnauthorized, "用户名或密码错误")
return
}
if !u.IsActive {
fail(w, http.StatusForbidden, "账号已停用")
return
}
if bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(req.Password)) != nil {
fail(w, http.StatusUnauthorized, "用户名或密码错误")
return
}
ctx.EntClient.User.UpdateOneID(u.ID).SetLastLoginAt(time.Now().Unix()).ExecX(ctx0())
token, expireAt, err := SignToken(ctx, u.ID, u.Username, u.RealName, u.Role)
if err != nil {
fail(w, http.StatusInternalServerError, "签发 token 失败")
return
}
// 操作日志:登录(写失败不阻断登录)
ctx.EventLog.Write(ctx0(), "auth.login", u.Username, "auth", u.Username, "登录成功",
map[string]any{"ip": clientIP(r)})
ok(w, map[string]any{
"token": token,
"expireAt": expireAt,
// 弱密码(纯数字)仅提示,不影响登录
"weakPassword": isWeakPassword(req.Password),
"user": map[string]any{
"id": u.ID, "username": u.Username, "realName": u.RealName,
"role": u.Role, "roleId": u.RoleID, "dept": u.Dept,
"permissionCodes": permissionsForUser(ctx, u),
},
})
}
}
// clientIP 取客户端 IP(优先代理头,兜底 RemoteAddr)
func clientIP(r *http.Request) string {
if v := r.Header.Get("X-Real-IP"); v != "" {
return v
}
if v := r.Header.Get("X-Forwarded-For"); v != "" {
// 多级代理取第一个
for i := 0; i < len(v); i++ {
if v[i] == ',' {
return strings.TrimSpace(v[:i])
}
}
return strings.TrimSpace(v)
}
if host := r.RemoteAddr; host != "" {
if i := strings.LastIndex(host, ":"); i > 0 {
return host[:i]
}
return host
}
return ""
}
// logoutHandler POST /api/user/logout 退出登录(需登录 JWT
// 记录退出日志;真正的登录态清除由前端完成(无状态 JWT 无服务端会话)。
func logoutHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
username := strings.TrimSpace(r.Header.Get("X-Username"))
if username != "" {
ctx.EventLog.Write(ctx0(), "auth.logout", username, "auth", username, "退出登录",
map[string]any{"ip": clientIP(r)})
}
ok(w, nil)
}
}
// changePasswordHandler POST /api/user/change-password(需登录 JWT
// 当前登录用户修改自己的密码;登录人由中间件写入的 X-Username 头确定。
func changePasswordHandler(ctx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
username := strings.TrimSpace(r.Header.Get("X-Username"))
if username == "" {
fail(w, http.StatusUnauthorized, "未登录或登录状态缺失")
return
}
var req struct {
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
}
if err := parseJSON(r, &req); err != nil {
fail(w, http.StatusBadRequest, "参数错误")
return
}
if req.OldPassword == "" || req.NewPassword == "" {
fail(w, http.StatusBadRequest, "请输入旧密码和新密码")
return
}
if len(req.NewPassword) < 6 {
fail(w, http.StatusBadRequest, "新密码长度至少 6 位")
return
}
u, err := ctx.EntClient.User.Query().
Where(user.UsernameEQ(username)).
Only(ctx0())
if err != nil {
fail(w, http.StatusNotFound, "用户不存在")
return
}
if bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(req.OldPassword)) != nil {
fail(w, http.StatusBadRequest, "旧密码不正确")
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil {
fail(w, http.StatusInternalServerError, "密码加密失败")
return
}
if err := ctx.EntClient.User.UpdateOneID(u.ID).SetPassword(string(hash)).Exec(ctx0()); err != nil {
fail(w, http.StatusInternalServerError, "密码保存失败")
return
}
ok(w, nil)
}
}