初始化2
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"bj_power_workstation/internal/auth"
|
||||
"bj_power_workstation/internal/svc"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func healthHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if err := ctx.Store.Ping(); err == nil {
|
||||
ok(w, map[string]any{
|
||||
"status": "UP",
|
||||
"time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
"service": "bj_power_workstation",
|
||||
})
|
||||
return
|
||||
}
|
||||
fail(w, http.StatusServiceUnavailable, "本地数据库不可用")
|
||||
}
|
||||
}
|
||||
|
||||
// loginHandler 本地 SQLite + bcrypt 校验登录,返回 {token, expireAt, user}。
|
||||
// 账号自动记录为操作人(前端通过 localStorage 的 user 保存操作人)。
|
||||
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 strings.TrimSpace(req.Username) == "" || req.Password == "" {
|
||||
fail(w, http.StatusBadRequest, "用户名和密码必填")
|
||||
return
|
||||
}
|
||||
|
||||
u, err := ctx.Store.GetUserByUsername(strings.TrimSpace(req.Username))
|
||||
if err != nil {
|
||||
fail(w, http.StatusUnauthorized, "用户名或密码错误")
|
||||
return
|
||||
}
|
||||
if bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(req.Password)) != nil {
|
||||
fail(w, http.StatusUnauthorized, "用户名或密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
token, expireAt, err := auth.SignToken(
|
||||
ctx.Config.Auth.AccessSecret,
|
||||
ctx.Config.Auth.AccessExpire,
|
||||
auth.TokenInfo{UserID: u.ID, Username: u.Username, RealName: u.RealName, Role: u.Role},
|
||||
)
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "签发 token 失败")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Store.AddEventLog("", u.RealName, "login", "账号登录")
|
||||
ok(w, map[string]any{
|
||||
"token": token,
|
||||
"expireAt": expireAt,
|
||||
"user": map[string]any{
|
||||
"id": u.ID,
|
||||
"username": u.Username,
|
||||
"realName": u.RealName,
|
||||
"role": u.Role,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// configHandler GET /api/config 返回工位配置:
|
||||
// 固定工位号(Station.Code 为空则前端可自选)、本工位需拧紧的螺丝颗数。
|
||||
func configHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ok(w, map[string]any{
|
||||
"station": ctx.Config.Station.Code,
|
||||
"screwCount": ctx.Config.Station.ScrewCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// parseJSON 解析请求体 JSON,失败返回错误。
|
||||
func parseJSON(r *http.Request, v any) error {
|
||||
dec := json.NewDecoder(r.Body)
|
||||
return dec.Decode(v)
|
||||
}
|
||||
|
||||
func atoi(s string, def int) int {
|
||||
if s == "" {
|
||||
return def
|
||||
}
|
||||
n, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
return def
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// errString 压缩错误信息长度便于前端展示。
|
||||
func errString(err error) string {
|
||||
s := err.Error()
|
||||
const maxLen = 180
|
||||
if len(s) > maxLen {
|
||||
return s[:maxLen] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"bj_power_workstation/internal/mes"
|
||||
"bj_power_workstation/internal/pdfcache"
|
||||
"bj_power_workstation/internal/svc"
|
||||
)
|
||||
|
||||
// taskCurrentHandler 代理 MES GET /api/internal/station/task?dockCode=。
|
||||
// 响应体原样透传(MES 已按 {code,message,data} 封装,不再二次包装)。
|
||||
func taskCurrentHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
dockCode := strings.TrimSpace(r.URL.Query().Get("dockCode"))
|
||||
if dockCode == "" {
|
||||
fail(w, http.StatusBadRequest, "缺少 dockCode 参数")
|
||||
return
|
||||
}
|
||||
|
||||
body, _, err := ctx.Mes.Get("/api/internal/station/task", url.Values{"dockCode": {dockCode}})
|
||||
if err != nil {
|
||||
fail(w, http.StatusBadGateway, "MES 服务不可达:"+errString(err))
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(body)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------- 工艺 PDF 直通代理(含本地预缓存) ----------
|
||||
//
|
||||
// 前端 iframe/window.open 无法携带 Authorization,故提供免 JWT 前缀 /pdfopen/<name>,
|
||||
// 内部仍走 MES /api/internal/files/<name>(由 X-API-TOKEN 保护)。
|
||||
// 启用本地预缓存后,首次访问回源下载并落盘,后续直接读本地缓存(断网也能查看)。
|
||||
// 两端点均把文件流原样转发并透传 Content-Type,不套 {code,message,data}。
|
||||
|
||||
// pdfQueryHandler GET /api/pdf?name=<file>(需登录)
|
||||
func pdfQueryHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
forwardFile(w, ctx.Mes, ctx.PdfCache, strings.TrimSpace(r.URL.Query().Get("name")))
|
||||
}
|
||||
}
|
||||
|
||||
// pdfOpenHandler GET /pdfopen/<name>(免鉴权,供 iframe 直链)
|
||||
func pdfOpenHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
raw := strings.TrimPrefix(r.URL.EscapedPath(), "/pdfopen/")
|
||||
name, err := url.PathUnescape(raw)
|
||||
if err != nil {
|
||||
fail(w, http.StatusBadRequest, "非法文件名")
|
||||
return
|
||||
}
|
||||
forwardFile(w, ctx.Mes, ctx.PdfCache, name)
|
||||
}
|
||||
}
|
||||
|
||||
// forwardFile 校验单段文件名后拉取 MES 文件流并原样转发;优先命中本地预缓存。
|
||||
func forwardFile(w http.ResponseWriter, mesc *mes.Client, cache *pdfcache.Cache, name string) {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
fail(w, http.StatusBadRequest, "缺少文件名")
|
||||
return
|
||||
}
|
||||
if strings.ContainsAny(name, `/\`) || name == "." || name == ".." || strings.Contains(name, "..") {
|
||||
fail(w, http.StatusBadRequest, "非法文件名")
|
||||
return
|
||||
}
|
||||
|
||||
// 1) 命中本地预缓存 → 直接回源本地文件
|
||||
if p, ok := cache.Get(name); ok {
|
||||
f, err := os.Open(p)
|
||||
if err == nil {
|
||||
stream(f, w, name, "")
|
||||
return
|
||||
}
|
||||
f.Close()
|
||||
}
|
||||
|
||||
// 2) 回源 MES 下载
|
||||
body, header, err := mesc.Get("/api/internal/files/"+url.PathEscape(name), nil)
|
||||
if err != nil {
|
||||
fail(w, http.StatusBadGateway, "工艺文件获取失败:"+errString(err))
|
||||
return
|
||||
}
|
||||
|
||||
// 3) 写入本地预缓存(失败不影响本次响应)
|
||||
_, _ = cache.Put(name, body)
|
||||
streamBytes(body, w, name, header.Get("Content-Type"))
|
||||
}
|
||||
|
||||
func stream(f io.ReadCloser, w http.ResponseWriter, name, contentType string) {
|
||||
defer f.Close()
|
||||
ct := contentType
|
||||
if ct == "" {
|
||||
ct = "application/pdf"
|
||||
}
|
||||
w.Header().Set("Content-Type", ct)
|
||||
w.Header().Set("X-Original-Filename", strconv.Quote(name))
|
||||
w.Header().Set("X-PDF-Cache", "hit")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = io.Copy(w, f)
|
||||
}
|
||||
|
||||
func streamBytes(body []byte, w http.ResponseWriter, name, contentType string) {
|
||||
ct := contentType
|
||||
if ct == "" {
|
||||
ct = "application/pdf"
|
||||
}
|
||||
w.Header().Set("Content-Type", ct)
|
||||
w.Header().Set("X-Original-Filename", strconv.Quote(name))
|
||||
w.Header().Set("X-PDF-Cache", "miss")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(body)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"bj_power_workstation/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// processDoneHandler POST /api/report/process-done
|
||||
// {dockCode, orderNo, sn, processCode, operator}
|
||||
// 流程:先冲刷一轮积压 → 实时报 MES /api/internal/station/done;
|
||||
// MES 不可达则写入 report_queue(kind=process_done) 并返回 data.queued=true(离线缓存稍后自动重传)。
|
||||
func processDoneHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
DockCode string `json:"dockCode"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
Sn string `json:"sn"`
|
||||
ProcessCode string `json:"processCode"`
|
||||
Operator string `json:"operator"`
|
||||
}
|
||||
if err := parseJSON(r, &req); err != nil {
|
||||
fail(w, http.StatusBadRequest, "参数错误")
|
||||
return
|
||||
}
|
||||
if req.DockCode == "" {
|
||||
fail(w, http.StatusBadRequest, "缺少 dockCode")
|
||||
return
|
||||
}
|
||||
|
||||
// 先尽力冲刷历史积压,失败不影响本次上报
|
||||
ctx.Syncer.RunOnce(r.Context())
|
||||
|
||||
body := map[string]any{
|
||||
"dockCode": req.DockCode,
|
||||
"orderNo": req.OrderNo,
|
||||
"sn": req.Sn,
|
||||
"processCode": req.ProcessCode,
|
||||
"operator": req.Operator,
|
||||
}
|
||||
payloadBytes, _ := json.Marshal(body)
|
||||
|
||||
ctx.Store.AddEventLog(req.OrderNo, req.Operator, "process_done",
|
||||
"工序完成上报 dockCode="+req.DockCode+" sn="+req.Sn+" processCode="+req.ProcessCode)
|
||||
|
||||
if _, _, err := ctx.Mes.Post("/api/internal/station/done", json.RawMessage(payloadBytes)); err != nil {
|
||||
if qerr := ctx.Store.InsertQueueItem("process_done", string(payloadBytes)); qerr != nil {
|
||||
fail(w, http.StatusInternalServerError, "上报失败且离线队列写入失败:"+qerr.Error())
|
||||
return
|
||||
}
|
||||
logx.Errorf("工序完成上报失败已离线排队: %v", err)
|
||||
ok(w, map[string]any{"queued": true})
|
||||
return
|
||||
}
|
||||
|
||||
ok(w, map[string]any{"queued": false})
|
||||
}
|
||||
}
|
||||
|
||||
// tempStoreHandler POST /api/report/temp-store
|
||||
// {dockCode, orderNo, sn, operator} → 调 MES /api/internal/station/checkin,
|
||||
// body 附带 type=temp_store;不可达时以同样 payload 写入 report_queue(kind=temp_store)。
|
||||
func tempStoreHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
DockCode string `json:"dockCode"`
|
||||
OrderNo string `json:"orderNo"`
|
||||
Sn string `json:"sn"`
|
||||
Operator string `json:"operator"`
|
||||
}
|
||||
if err := parseJSON(r, &req); err != nil {
|
||||
fail(w, http.StatusBadRequest, "参数错误")
|
||||
return
|
||||
}
|
||||
if req.DockCode == "" {
|
||||
fail(w, http.StatusBadRequest, "缺少 dockCode")
|
||||
return
|
||||
}
|
||||
|
||||
// 先尽力冲刷历史积压,失败不影响本次上报
|
||||
ctx.Syncer.RunOnce(r.Context())
|
||||
|
||||
body := map[string]any{
|
||||
"type": "temp_store", // checkin 类型标识:暂存退库
|
||||
"dockCode": req.DockCode,
|
||||
"orderNo": req.OrderNo,
|
||||
"sn": req.Sn,
|
||||
"operator": req.Operator,
|
||||
}
|
||||
payloadBytes, _ := json.Marshal(body)
|
||||
|
||||
ctx.Store.AddEventLog(req.OrderNo, req.Operator, "temp_store",
|
||||
"暂存/退回库房 dockCode="+req.DockCode+" sn="+req.Sn)
|
||||
|
||||
if _, _, err := ctx.Mes.Post("/api/internal/station/checkin", json.RawMessage(payloadBytes)); err != nil {
|
||||
if qerr := ctx.Store.InsertQueueItem("temp_store", string(payloadBytes)); qerr != nil {
|
||||
fail(w, http.StatusInternalServerError, "上报失败且离线队列写入失败:"+qerr.Error())
|
||||
return
|
||||
}
|
||||
logx.Errorf("暂存退库上报失败已离线排队: %v", err)
|
||||
ok(w, map[string]any{"queued": true})
|
||||
return
|
||||
}
|
||||
|
||||
ok(w, map[string]any{"queued": false})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest/httpx"
|
||||
)
|
||||
|
||||
// Response 统一响应结构:{ code, message, data }
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data any `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
func ok(w http.ResponseWriter, data any) {
|
||||
httpx.WriteJson(w, http.StatusOK, Response{Code: 0, Message: "ok", Data: data})
|
||||
}
|
||||
|
||||
func fail(w http.ResponseWriter, httpStatus int, message string) {
|
||||
httpx.WriteJson(w, httpStatus, Response{Code: -1, Message: message})
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"bj_power_workstation/internal/auth"
|
||||
"bj_power_workstation/internal/svc"
|
||||
|
||||
"github.com/zeromicro/go-zero/rest"
|
||||
)
|
||||
|
||||
func RegisterHandlers(server *rest.Server, ctx *svc.ServiceContext) {
|
||||
// 免鉴权:健康检查 / 登录 / 工位配置
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{Method: http.MethodGet, Path: "/api/health", Handler: healthHandler(ctx)},
|
||||
{Method: http.MethodPost, Path: "/api/auth/login", Handler: loginHandler(ctx)},
|
||||
{Method: http.MethodGet, Path: "/api/config", Handler: configHandler(ctx)},
|
||||
// 前端静态托管
|
||||
{Method: http.MethodGet, Path: "/", Handler: indexHandler(ctx.WebFS)},
|
||||
{Method: http.MethodGet, Path: "/assets/:file", Handler: assetHandler(ctx.WebFS)},
|
||||
},
|
||||
)
|
||||
|
||||
// 登录鉴权中间件:白名单 /api/health、/api/auth/login、/api/config、/pdfopen/*
|
||||
server.Use(auth.Interceptor(ctx.Config.Auth.AccessSecret, ctx.Config.Auth.AccessExpire))
|
||||
|
||||
// 业务接口(均需登录 JWT;PDF 代理除外——原样转发文件流)
|
||||
server.AddRoutes(
|
||||
[]rest.Route{
|
||||
{Method: http.MethodGet, Path: "/api/task/current", Handler: taskCurrentHandler(ctx)},
|
||||
{Method: http.MethodGet, Path: "/api/pdf", Handler: pdfQueryHandler(ctx)},
|
||||
{Method: http.MethodGet, Path: "/pdfopen/:name", Handler: pdfOpenHandler(ctx)},
|
||||
{Method: http.MethodGet, Path: "/api/tightening/list", Handler: tighteningListHandler(ctx)},
|
||||
{Method: http.MethodPost, Path: "/api/report/process-done", Handler: processDoneHandler(ctx)},
|
||||
{Method: http.MethodPost, Path: "/api/report/temp-store", Handler: tempStoreHandler(ctx)},
|
||||
{Method: http.MethodPost, Path: "/api/sync/flush", Handler: syncFlushHandler(ctx)},
|
||||
{Method: http.MethodGet, Path: "/api/sync/stats", Handler: syncStatsHandler(ctx)},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// indexHandler 服务嵌入式前端首页 web/static/index.html。
|
||||
func indexHandler(fsys fs.FS) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
b, err := fs.ReadFile(fsys, "index.html")
|
||||
if err != nil {
|
||||
http.Error(w, "前端页面尚未构建:请在 frontend 目录执行 pnpm build", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
}
|
||||
|
||||
// mimeFallback 构建产物常见静态资源的 Content-Type 兜底表
|
||||
// (mime.TypeByExtension 在部分 Windows 环境依赖系统注册表,结果不稳定)。
|
||||
var mimeFallback = map[string]string{
|
||||
".html": "text/html; charset=utf-8",
|
||||
".css": "text/css; charset=utf-8",
|
||||
".js": "text/javascript; charset=utf-8",
|
||||
".mjs": "text/javascript; charset=utf-8",
|
||||
".json": "application/json; charset=utf-8",
|
||||
".svg": "image/svg+xml",
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".gif": "image/gif",
|
||||
".ico": "image/x-icon",
|
||||
".woff": "font/woff",
|
||||
".woff2": "font/woff2",
|
||||
".ttf": "font/ttf",
|
||||
".webp": "image/webp",
|
||||
}
|
||||
|
||||
// assetHandler 服务 vite 构建产物 /assets/<file>。
|
||||
// 前端使用 hash 路由,静态托管只需覆盖根路径与扁平的 assets 目录。
|
||||
func assetHandler(fsys fs.FS) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
name := strings.TrimPrefix(r.URL.EscapedPath(), "/assets/")
|
||||
if name == "" || strings.ContainsAny(name, `/\`) || strings.Contains(name, "..") {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if _, err := fs.Stat(fsys, "assets/"+name); err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
ct := mimeFallback[strings.ToLower(path.Ext(name))]
|
||||
if ct == "" {
|
||||
ct = "application/octet-stream"
|
||||
}
|
||||
w.Header().Set("Content-Type", ct)
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") // vite 产物带内容哈希
|
||||
b, err := fs.ReadFile(fsys, "assets/"+name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"bj_power_workstation/internal/svc"
|
||||
)
|
||||
|
||||
// syncFlushHandler POST /api/sync/flush 手动立即执行一轮同步,返回本次成功条数。
|
||||
func syncFlushHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
count := ctx.Syncer.RunOnce(r.Context())
|
||||
ok(w, map[string]any{
|
||||
"count": count,
|
||||
"message": "本轮同步完成",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// syncStatsHandler GET /api/sync/stats 返回待同步条数统计。
|
||||
func syncStatsHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
pendingTorque, err1 := ctx.Store.PendingTorqueCount()
|
||||
pendingQueue, err2 := ctx.Store.PendingQueueCount()
|
||||
if err1 != nil || err2 != nil {
|
||||
fail(w, http.StatusInternalServerError, "查询待同步统计失败")
|
||||
return
|
||||
}
|
||||
ok(w, map[string]any{
|
||||
"pendingTorque": pendingTorque,
|
||||
"pendingQueue": pendingQueue,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"bj_power_workstation/internal/svc"
|
||||
)
|
||||
|
||||
// tighteningListHandler GET /api/tightening/list?sn=&only_pending=true&limit=100
|
||||
// 查询本地 torque_results(最新优先)。
|
||||
func tighteningListHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
sn := q.Get("sn")
|
||||
|
||||
onlyPending := false
|
||||
if v, err := strconv.ParseBool(q.Get("only_pending")); err == nil {
|
||||
onlyPending = v
|
||||
}
|
||||
|
||||
limit := atoi(q.Get("limit"), 100)
|
||||
if limit <= 0 || limit > 500 {
|
||||
limit = 500
|
||||
}
|
||||
|
||||
list, err := ctx.Store.ListTorqueResults(sn, onlyPending, limit)
|
||||
if err != nil {
|
||||
fail(w, http.StatusInternalServerError, "查询拧紧结果失败:"+err.Error())
|
||||
return
|
||||
}
|
||||
ok(w, map[string]any{
|
||||
"total": len(list),
|
||||
"list": list,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user