feat&refactor: 完成多模块功能迭代与配置优化
本次提交覆盖多个业务模块的功能完善与体验优化:
1. **鉴权与配置调整**:
- 统一JWT滑动续签逻辑,简化Token存储,移除RefreshToken相关冗余代码
- 调整多项目配置文件中JWT过期时间为3600秒,统一会话闲置窗口
- 工位配置放开1~12限制,改为仅校验大于0
2. **术语统一替换**:全链路将"精密件"替换为"电气件",修正物料管理描述
3. **功能新增**:
- 新增工位类型、工艺路线与产线点位台账模块
- 添加工艺PDF预览面板、工位终端代理转发接口
- 新增操作日志按操作人列表筛选、工位登出日志记录
- 新增PLC移料指令与产线点位状态管理
4. **业务流程优化**:
- 调整BOM物料删除校验逻辑,优化工单备料计算
- 补充物料图号、检测单号等追溯字段
- 完善工艺流程图与工位绑定关系说明
- 优化前端页面文案与交互细节
5. **代码规范与维护**:
- 新增通用工具函数与前端静态资源
- 整理路由权限与中间件逻辑
- 修复部分接口与配置的不兼容问题
This commit is contained in:
@@ -78,23 +78,50 @@ func LoginHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
httpx.Fail(w, 1001, err.Error())
|
||||
return
|
||||
}
|
||||
// 操作日志:登录成功(写失败不阻断登录)
|
||||
svcCtx.EventLog.Write(r.Context(), "auth.login", "", req.Username, "auth", req.Username,
|
||||
"登录成功", map[string]any{"ip": clientIP(r)})
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
func RefreshHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
// 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/v1/logout 退出登录(需登录 JWT)
|
||||
// 记录退出日志;登录态清除由前端完成(无状态 JWT 无服务端会话)。
|
||||
func LogoutHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req logic.RefreshReq
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
username, _ := r.Context().Value("username").(string)
|
||||
if username == "" {
|
||||
if u, err := svcCtx.EntClient.User.Get(r.Context(), uidFromRequest(r, svcCtx)); err == nil {
|
||||
username = u.Username
|
||||
}
|
||||
}
|
||||
data, err := logic.New(svcCtx).Refresh(r.Context(), req)
|
||||
if err != nil {
|
||||
httpx.Fail(w, 1002, err.Error())
|
||||
return
|
||||
if username != "" {
|
||||
svcCtx.EventLog.Write(r.Context(), "auth.logout", "", username, "auth", username,
|
||||
"退出登录", map[string]any{"ip": clientIP(r)})
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
httpx.OkMessage(w, "已退出", nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"bj_power_mes/common/httpx"
|
||||
"bj_power_mes/internal/logic"
|
||||
"bj_power_mes/internal/svc"
|
||||
)
|
||||
|
||||
// OccupyPointHandler POST /line/occupy(点位人工记账:IN 上料占位 / OUT 下料释放)
|
||||
// 供工位终端在无 PLC 联调时、或上料位(IN)/末端下料检测位(11/12) 人工操作时维护点位台账(唯一事实源)。
|
||||
func OccupyPointHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
PointNo string `json:"pointNo"`
|
||||
Sn string `json:"sn"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
Operator string `json:"operator"`
|
||||
Action string `json:"action"` // IN=上料 / OUT=下料
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
action := strings.ToUpper(strings.TrimSpace(req.Action))
|
||||
if action == "" {
|
||||
action = "IN"
|
||||
}
|
||||
if action != "IN" && action != "OUT" {
|
||||
httpx.BadRequest(w, "action 仅支持 IN(上料) / OUT(下料)")
|
||||
return
|
||||
}
|
||||
op := req.Operator
|
||||
if op == "" {
|
||||
op = operator(r, "")
|
||||
}
|
||||
if err := logic.New(svcCtx).OccupyPoint(r.Context(), req.PointNo, req.Sn, req.OrderNo, op, action); err != nil {
|
||||
httpx.Fail(w, 2407, err.Error())
|
||||
return
|
||||
}
|
||||
msg := "上料已记账"
|
||||
if action == "OUT" {
|
||||
msg = "下料已释放"
|
||||
}
|
||||
httpx.OkMessage(w, msg, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// ListPointsHandler GET /line/points(产线点位台账:上料位/工位/缓存位/接驳台/末端 当前工件)
|
||||
func ListPointsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
_ = logic.New(svcCtx).InitLinePoints(r.Context())
|
||||
data, err := logic.New(svcCtx).ListPoints(r.Context())
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2401, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// IssueMoveHandler POST /line/move(上位机下发移料指令:起点位 → 目标点位)
|
||||
func IssueMoveHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Sn string `json:"sn"`
|
||||
FromPoint string `json:"fromPoint"`
|
||||
ToPoint string `json:"toPoint"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
data, err := logic.New(svcCtx).IssueMove(r.Context(), req.Sn, req.FromPoint, req.ToPoint, req.OrderNo, operator(r, ""))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2402, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "移料指令已下发", data)
|
||||
}
|
||||
}
|
||||
|
||||
// AckMoveHandler POST /line/move/ack(PLC 应答)
|
||||
func AckMoveHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
CmdNo string `json:"cmdNo"`
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
if err := logic.New(svcCtx).AckMove(r.Context(), req.CmdNo); err != nil {
|
||||
httpx.Fail(w, 2403, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "已应答", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// ArriveMoveHandler POST /line/move/arrive(工件到位:回写台账并闭环指令)
|
||||
func ArriveMoveHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
CmdNo string `json:"cmdNo"`
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
if err := logic.New(svcCtx).ArriveMove(r.Context(), req.CmdNo); err != nil {
|
||||
httpx.Fail(w, 2404, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "到位已记账", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// ListMoveCmdsHandler GET /line/moves?status=&sn=&limit=
|
||||
func ListMoveCmdsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
data, err := logic.New(svcCtx).ListMoveCmds(r.Context(), q.Get("status"), q.Get("sn"),
|
||||
atoiDefault(q.Get("limit"), 50))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2405, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// CallMaterialHandler POST /line/call-material(工位终端叫料:选接驳台)
|
||||
func CallMaterialHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
StationNo int `json:"stationNo"`
|
||||
Dock string `json:"dock"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
Items []logic.CallMaterialItem `json:"items"`
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
n, err := logic.New(svcCtx).CallMaterial(r.Context(), req.StationNo, req.Dock, req.OrderNo, operator(r, ""), req.Items)
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2406, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "叫料已提交", map[string]any{"count": n})
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,8 @@ var pathPermMap = map[string]string{
|
||||
"POST:/api/v1/process-flows/upload": "produce.processflow:upload",
|
||||
"POST:/api/v1/process-steps": "produce.processflow:edit",
|
||||
"POST:/api/v1/stations": "produce.station:edit",
|
||||
"POST:/api/v1/process-routes": "produce.route:add",
|
||||
"DELETE:/api/v1/process-routes/*": "produce.route:delete",
|
||||
// 装机绑定(报工前扫料/撤销,属报工操作域)
|
||||
"POST:/api/v1/binds": "produce.scan",
|
||||
"POST:/api/v1/binds/remove": "produce.scan",
|
||||
|
||||
@@ -190,7 +190,7 @@ func buildCard(ctx context.Context, svcCtx *svc.ServiceContext, sn string) (*car
|
||||
missing = append(missing, "拧紧数据")
|
||||
}
|
||||
|
||||
// 关联物料(批次/精密件SN)
|
||||
// 关联物料(批次/电气件SN)
|
||||
assoc, _ := client.AssociationTrace.Query().
|
||||
Where(associationtrace.FinishedSn(sn)).First(ctx)
|
||||
if assoc != nil {
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"bj_power_mes/common/httpx"
|
||||
"bj_power_mes/internal/logic"
|
||||
"bj_power_mes/internal/svc"
|
||||
)
|
||||
|
||||
// ListRoutesHandler GET /process-routes?name=&status=&page=&pageSize=
|
||||
func ListRoutesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
data, err := logic.New(svcCtx).ListRoutes(r.Context(), q.Get("name"), q.Get("status"),
|
||||
atoiDefault(q.Get("page"), 0), atoiDefault(q.Get("pageSize"), 20))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2301, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// SaveRouteHandler POST /process-routes(新建或更新工艺路线,含段)
|
||||
func SaveRouteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req logic.RouteReq
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
if err := logic.New(svcCtx).SaveRoute(r.Context(), req, operator(r, "")); err != nil {
|
||||
httpx.Fail(w, 2302, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "工艺路线已保存", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// GetRouteHandler GET /process-routes/:id
|
||||
func GetRouteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := logic.New(svcCtx).GetRoute(r.Context(), pathId(r))
|
||||
if err != nil {
|
||||
httpx.Fail(w, 2303, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteRouteHandler DELETE /process-routes/:id
|
||||
func DeleteRouteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := logic.New(svcCtx).DeleteRoute(r.Context(), pathId(r), operator(r, "")); err != nil {
|
||||
httpx.Fail(w, 2304, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.OkMessage(w, "工艺路线已删除", nil)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
package production
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"bj_power_mes/common/httpx"
|
||||
tokenx "bj_power_mes/common/token"
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/user"
|
||||
"bj_power_mes/internal/logic"
|
||||
"bj_power_mes/internal/svc"
|
||||
)
|
||||
@@ -110,6 +115,9 @@ func ListSemiFlowsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
|
||||
// ---------- 操作日志 ----------
|
||||
|
||||
// ListEventLogsHandler GET /api/v1/event-logs 操作日志分页查询
|
||||
// 可见性:admin(SUPER_ADMIN)可看全部并可按操作人筛选;普通用户强制只看自己的日志。
|
||||
// operatorName:操作人中文名(users.name 批量映射,历史数据同样生效)。
|
||||
func ListEventLogsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
@@ -117,12 +125,109 @@ func ListEventLogsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
pageSize := atoiDefault(q.Get("pageSize"), 20)
|
||||
fromMs, _ := strconv.ParseInt(q.Get("from"), 10, 64)
|
||||
toMs, _ := strconv.ParseInt(q.Get("to"), 10, 64)
|
||||
|
||||
// 当前身份:uid → user(历史数据 operator 中文名/用户名混杂,过滤需同时带两者)
|
||||
uid := currentUid(r, svcCtx)
|
||||
me, _ := svcCtx.EntClient.User.Get(context.Background(), uid)
|
||||
meUsername, _ := r.Context().Value("username").(string)
|
||||
if me == nil && meUsername != "" {
|
||||
me, _ = svcCtx.EntClient.User.Query().Where(user.UsernameEQ(meUsername)).First(context.Background())
|
||||
}
|
||||
var candidates []string
|
||||
isAdmin := false
|
||||
if me != nil {
|
||||
candidates = []string{me.Username, me.Name}
|
||||
if rl, err := svcCtx.EntClient.Role.Get(context.Background(), me.RoleId); err == nil && rl.Code == "SUPER_ADMIN" {
|
||||
isAdmin = true
|
||||
}
|
||||
}
|
||||
explicitFilter := q.Get("operator")
|
||||
if !isAdmin {
|
||||
// 普通用户仅能查看自己的操作日志(用户名 + 中文名任一命中),忽略显式筛选
|
||||
explicitFilter = ""
|
||||
}
|
||||
|
||||
list, total, err := logic.New(svcCtx).ListEventLogs(r.Context(),
|
||||
q.Get("orderNo"), q.Get("operator"), q.Get("eventType"), fromMs, toMs, page, pageSize)
|
||||
q.Get("orderNo"), operatorCandidates(explicitFilter, candidates), q.Get("eventType"), fromMs, toMs, page, pageSize)
|
||||
if err != nil {
|
||||
httpx.Fail(w, 3308, err.Error())
|
||||
return
|
||||
}
|
||||
httpx.Ok(w, map[string]any{"list": list, "total": total})
|
||||
|
||||
// 批量映射操作人中文名,缺档显示原用户名
|
||||
nameMap := realNameMapOf(svcCtx, list)
|
||||
rows := make([]map[string]any, 0, len(list))
|
||||
for _, e := range list {
|
||||
name := nameMap[e.Operator]
|
||||
if name == "" {
|
||||
name = e.Operator
|
||||
}
|
||||
rows = append(rows, map[string]any{
|
||||
"id": e.ID,
|
||||
"eventType": e.EventType,
|
||||
"workOrderNo": e.WorkOrderNo,
|
||||
"description": e.Description,
|
||||
"entityType": e.EntityType,
|
||||
"entityId": e.EntityId,
|
||||
"operator": e.Operator,
|
||||
"operatorName": name,
|
||||
"payload": e.Payload,
|
||||
"createdAt": e.CreatedAt.UnixMilli(),
|
||||
})
|
||||
}
|
||||
httpx.Ok(w, map[string]any{"list": rows, "total": total, "isAdmin": isAdmin})
|
||||
}
|
||||
}
|
||||
|
||||
// currentUid 取当前登录用户ID:优先 JWT 上下文,缺失时回退解析 Bearer token
|
||||
func currentUid(r *http.Request, svcCtx *svc.ServiceContext) int {
|
||||
switch v := r.Context().Value("userId").(type) {
|
||||
case float64:
|
||||
return int(v)
|
||||
case int:
|
||||
return v
|
||||
case int64:
|
||||
return int(v)
|
||||
}
|
||||
auth := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
|
||||
if auth != "" && strings.HasPrefix(r.Header.Get("Authorization"), "Bearer ") {
|
||||
if claims, err := tokenx.Parse(svcCtx.Config.Auth.AccessSecret, auth); err == nil {
|
||||
return claims.UserId
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// operatorCandidates 计算操作人过滤候选:admin 显式筛选时精确匹配输入值;否则取当前用户身份候选
|
||||
func operatorCandidates(filter string, self []string) []string {
|
||||
if filter != "" {
|
||||
return []string{filter}
|
||||
}
|
||||
return self
|
||||
}
|
||||
|
||||
// realNameMapOf 批量取操作人中文名(users.name)
|
||||
func realNameMapOf(svcCtx *svc.ServiceContext, logs []*ent.EventLog) map[string]string {
|
||||
set := make([]string, 0, 8)
|
||||
seen := map[string]bool{}
|
||||
for _, e := range logs {
|
||||
if e.Operator != "" && !seen[e.Operator] {
|
||||
seen[e.Operator] = true
|
||||
set = append(set, e.Operator)
|
||||
}
|
||||
}
|
||||
out := map[string]string{}
|
||||
if len(set) == 0 {
|
||||
return out
|
||||
}
|
||||
us, err := svcCtx.EntClient.User.Query().Where(user.UsernameIn(set...)).All(context.Background())
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
for _, u := range us {
|
||||
if u.Name != "" {
|
||||
out[u.Username] = u.Name
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tokenx "bj_power_mes/common/token"
|
||||
"bj_power_mes/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
)
|
||||
|
||||
// RenewalMiddleware 滑动续签中间件(对齐 WMS 的 X-Renewed-Token 模式):
|
||||
// 每个携带有效 Bearer token 的请求都重签一个同负载的新 token,
|
||||
// 会话有效期 = 距最后一次请求 sessionExpire 秒(默认 1 小时),闲置超时由 jwtAuth 返回 401。
|
||||
// 无 Bearer 头(内部 X-API-TOKEN、登录接口、静态资源)直接放行不做任何处理。
|
||||
func RenewalMiddleware(svcCtx *svc.ServiceContext) rest.Middleware {
|
||||
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
if strings.HasPrefix(authHeader, "Bearer ") {
|
||||
tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
if claims, err := tokenx.Parse(svcCtx.Config.Auth.AccessSecret, tokenStr); err == nil && time.Until(claims.ExpiresAt.Time) > 0 {
|
||||
expire := svcCtx.Config.Auth.AccessExpire
|
||||
if expire <= 0 {
|
||||
expire = 3600
|
||||
}
|
||||
if newToken, err := tokenx.Issue(svcCtx.Config.Auth.AccessSecret, time.Duration(expire)*time.Second, *claims); err == nil {
|
||||
w.Header().Set("X-Renewed-Token", newToken)
|
||||
}
|
||||
}
|
||||
}
|
||||
next(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,6 @@ func RegisterRoutes(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
// 免鉴权
|
||||
server.AddRoutes([]rest.Route{
|
||||
{Method: http.MethodPost, Path: "/login", Handler: LoginHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/refresh", Handler: RefreshHandler(serverCtx)},
|
||||
}, rest.WithPrefix("/api/v1"), rest.WithMaxBytes(serverCtx.Config.Upload.MaxMB<<20))
|
||||
|
||||
// JWT 保护:用户信息 + 用户/角色/权限 管理
|
||||
@@ -21,6 +20,7 @@ func RegisterRoutes(server *rest.Server, serverCtx *svc.ServiceContext) {
|
||||
{Method: http.MethodGet, Path: "/userinfo", Handler: UserInfoHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/seed", Handler: SeedHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/user/change-password", Handler: ChangePasswordHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/logout", Handler: LogoutHandler(serverCtx)},
|
||||
|
||||
{Method: http.MethodGet, Path: "/users", Handler: ListUsersHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/users", Handler: CreateUserHandler(serverCtx)},
|
||||
|
||||
@@ -41,6 +41,9 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost, Path: "/api/internal/station/login", Handler: internal(StationLoginInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost, Path: "/api/internal/station/logout", Handler: internal(StationLogoutInternalHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost, Path: "/api/internal/station/change-password", Handler: internal(StationChangePasswordInternalHandler(serverCtx)),
|
||||
})
|
||||
@@ -81,6 +84,29 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
Method: http.MethodPost, Path: "/api/internal/dock/pallet/set", Handler: internal(production.SetDockPalletInternalHandler(serverCtx)),
|
||||
})
|
||||
|
||||
// ---------- 产线点位/移料(供工位终端调用,X-API-TOKEN 保护)----------
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet, Path: "/api/internal/line/points", Handler: internal(ListPointsHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost, Path: "/api/internal/line/move", Handler: internal(IssueMoveHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost, Path: "/api/internal/line/move/ack", Handler: internal(AckMoveHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost, Path: "/api/internal/line/move/arrive", Handler: internal(ArriveMoveHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodGet, Path: "/api/internal/line/moves", Handler: internal(ListMoveCmdsHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost, Path: "/api/internal/line/call-material", Handler: internal(CallMaterialHandler(serverCtx)),
|
||||
})
|
||||
server.AddRoute(rest.Route{
|
||||
Method: http.MethodPost, Path: "/api/internal/line/occupy", Handler: internal(OccupyPointHandler(serverCtx)),
|
||||
})
|
||||
|
||||
// ---------- JWT 业务 API ----------
|
||||
jwtRoutes := []rest.Route{
|
||||
{Method: http.MethodGet, Path: "/product-types", Handler: production.ListProductTypesHandler(serverCtx)},
|
||||
@@ -143,6 +169,16 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
|
||||
{Method: http.MethodPost, Path: "/process-flows/upload", Handler: UploadPdfHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/stations", Handler: ListStationsHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/stations", Handler: SaveStationHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/process-routes", Handler: ListRoutesHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/line/points", Handler: ListPointsHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/line/move", Handler: IssueMoveHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/line/move/ack", Handler: AckMoveHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/line/move/arrive", Handler: ArriveMoveHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/line/moves", Handler: ListMoveCmdsHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/line/call-material", Handler: CallMaterialHandler(serverCtx)},
|
||||
{Method: http.MethodPost, Path: "/process-routes", Handler: SaveRouteHandler(serverCtx)},
|
||||
{Method: http.MethodGet, Path: "/process-routes/:id", Handler: GetRouteHandler(serverCtx)},
|
||||
{Method: http.MethodDelete, Path: "/process-routes/:id", Handler: DeleteRouteHandler(serverCtx)},
|
||||
|
||||
// ---------- PAD 巡检终端(块8) ----------
|
||||
{Method: http.MethodPost, Path: "/inspections", Handler: CreateInspectionHandler(serverCtx)},
|
||||
|
||||
@@ -53,10 +53,34 @@ func StationLoginInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
httpx.Fail(w, 2101, err.Error())
|
||||
return
|
||||
}
|
||||
// 操作日志:工位终端登录(写失败不阻断登录)
|
||||
svcCtx.EventLog.Write(r.Context(), "auth.station_login", "", req.Username, "auth", req.Username,
|
||||
"工位终端登录(工位"+strconv.Itoa(req.StationNo)+")",
|
||||
map[string]any{"ip": clientIP(r), "stationNo": req.StationNo})
|
||||
httpx.Ok(w, data)
|
||||
}
|
||||
}
|
||||
|
||||
// StationLogoutInternalHandler 工位终端退出(内部接口,X-API-TOKEN 保护)
|
||||
func StationLogoutInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Username string `json:"username"`
|
||||
StationNo int `json:"stationNo"`
|
||||
}
|
||||
if err := httpx.ParseJSON(r, &req); err != nil {
|
||||
httpx.BadRequest(w, "请求体解析失败")
|
||||
return
|
||||
}
|
||||
if req.Username != "" {
|
||||
svcCtx.EventLog.Write(r.Context(), "auth.station_logout", "", req.Username, "auth", req.Username,
|
||||
"工位终端退出(工位"+strconv.Itoa(req.StationNo)+")",
|
||||
map[string]any{"ip": clientIP(r), "stationNo": req.StationNo})
|
||||
}
|
||||
httpx.OkMessage(w, "已退出", nil)
|
||||
}
|
||||
}
|
||||
|
||||
// StationChangePasswordInternalHandler 工位终端修改自己的工位终端密码
|
||||
func StationChangePasswordInternalHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
Reference in New Issue
Block a user