feat: 新增预警与附件模块,优化工位派工功能

1. 新增预警规则、预警消息、业务附件数据库表与CRUD逻辑
2. 为拧紧记录添加审核人、审核时间字段及审核留痕功能
3. 优化产线点位类型与编号描述,更新工位组合下发菜单名称为工艺路线派工
4. 新增上传文件获取原文件名与大小的工具方法
5. 在系统管理菜单新增预警中心与附件中心入口
This commit is contained in:
SunYF
2026-09-14 16:31:08 +08:00
parent 0ab21b1234
commit 5c3b747a20
399 changed files with 147674 additions and 2213 deletions
@@ -0,0 +1,84 @@
package handler
import (
"net/http"
"os"
"path/filepath"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
// AttachmentUploadHandler POST /attachment/upload 上传并登记(multipart: bizType,bizId,file
func AttachmentUploadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseMultipartForm(32 << 20); err != nil {
httpx.BadRequest(w, "表单解析失败")
return
}
bizType := r.FormValue("bizType")
bizId := r.FormValue("bizId")
if bizType == "" || bizId == "" {
httpx.BadRequest(w, "bizType 与 bizId 必填")
return
}
rel, fname, size, err := saveUploadFileFull(r, "file", svcCtx.Config.Upload.Dir)
if err != nil {
httpx.Fail(w, 3311, "文件保存失败:"+err.Error())
return
}
rec, err := logic.New(svcCtx).AddAttachment(r.Context(), bizType, bizId, fname, rel, int(size), operator(r, ""))
if err != nil {
httpx.Fail(w, 3311, err.Error())
return
}
httpx.Ok(w, rec)
}
}
// AttachmentListHandler GET /attachments?bizType=&bizId= 附件列表
func AttachmentListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
data, err := logic.New(svcCtx).ListAttachments(r.Context(), q.Get("bizType"), q.Get("bizId"))
if err != nil {
httpx.Fail(w, 3312, err.Error())
return
}
httpx.Ok(w, data)
}
}
// AttachmentDownloadHandler GET /attachment/download?name= 下载
func AttachmentDownloadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
httpx.BadRequest(w, "缺少 name")
return
}
serveUploadFile(w, r, svcCtx.Config.Upload.Dir, name)
}
}
// AttachmentDeleteHandler POST /attachment/delete {id} 删除(记录+物理文件)
func AttachmentDeleteHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Id int `json:"id"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
att, err := logic.New(svcCtx).DeleteAttachment(r.Context(), req.Id)
if err != nil {
httpx.Fail(w, 3313, err.Error())
return
}
full := filepath.Join(svcCtx.Config.Upload.Dir, filepath.FromSlash(att.FilePath))
_ = os.Remove(full)
httpx.OkMessage(w, "删除成功", nil)
}
}
@@ -0,0 +1,113 @@
package production
import (
"net/http"
"bj_power_mes/common/httpx"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
)
// CreateAlertRuleHandler POST /alert/rule 新增预警规则
func CreateAlertRuleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Name string `json:"name"`
Type string `json:"type"`
Threshold float64 `json:"threshold"`
Receiver string `json:"receiver"`
Enabled bool `json:"enabled"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if err := logic.New(svcCtx).CreateAlertRule(r.Context(), req.Name, req.Type, req.Threshold, req.Receiver, req.Enabled, operator(r, "")); err != nil {
httpx.Fail(w, 3301, err.Error())
return
}
httpx.OkMessage(w, "保存成功", nil)
}
}
// UpdateAlertRuleHandler PUT /alert/rule 修改预警规则
func UpdateAlertRuleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Id int `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Threshold float64 `json:"threshold"`
Receiver string `json:"receiver"`
Enabled bool `json:"enabled"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if err := logic.New(svcCtx).UpdateAlertRule(r.Context(), req.Id, req.Name, req.Type, req.Threshold, req.Receiver, req.Enabled); err != nil {
httpx.Fail(w, 3302, err.Error())
return
}
httpx.OkMessage(w, "保存成功", nil)
}
}
// DeleteAlertRuleHandler DELETE /alert/rule?id= 删除规则
func DeleteAlertRuleHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
id := atoiDefault(r.URL.Query().Get("id"), 0)
if id <= 0 {
httpx.Fail(w, 3303, "缺少 id")
return
}
if err := logic.New(svcCtx).DeleteAlertRule(r.Context(), id); err != nil {
httpx.Fail(w, 3303, err.Error())
return
}
httpx.OkMessage(w, "删除成功", nil)
}
}
// ListAlertRulesHandler GET /alert/rules 规则列表
func ListAlertRulesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := logic.New(svcCtx).ListAlertRules(r.Context())
if err != nil {
httpx.Fail(w, 3304, err.Error())
return
}
httpx.Ok(w, data)
}
}
// ListAlertsHandler GET /alerts?status= 预警消息收件箱
func ListAlertsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
status := r.URL.Query().Get("status")
data, err := logic.New(svcCtx).ListAlerts(r.Context(), status)
if err != nil {
httpx.Fail(w, 3305, err.Error())
return
}
httpx.Ok(w, data)
}
}
// MarkAlertReadHandler POST /alert/read 标记已读 {id}
func MarkAlertReadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
Id int `json:"id"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if err := logic.New(svcCtx).MarkAlertRead(r.Context(), req.Id); err != nil {
httpx.Fail(w, 3306, err.Error())
return
}
httpx.OkMessage(w, "已标记已读", nil)
}
}
@@ -149,3 +149,45 @@ func TorqueManualAddHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
httpx.OkMessage(w, "补录成功", nil)
}
}
// TorqueAuditHandler POST /torque/audit 拧紧记录审核签字
func TorqueAuditHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req struct {
RecordId int `json:"recordId"`
Remark string `json:"remark"`
}
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if req.RecordId <= 0 {
httpx.Fail(w, 3208, "缺少 recordId")
return
}
if err := logic.New(svcCtx).AuditTorqueRecord(r.Context(), req.RecordId, req.Remark, operator(r, "")); err != nil {
httpx.Fail(w, 3208, err.Error())
return
}
httpx.OkMessage(w, "审核成功", nil)
}
}
// TorqueAuditLogHandler GET /torque/audit-log 查询拧紧记录修改留痕
func TorqueAuditLogHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
q := r.URL.Query()
id := atoiDefault(q.Get("recordId"), 0)
if id <= 0 {
httpx.Fail(w, 3209, "缺少 recordId")
return
}
data, err := logic.New(svcCtx).ListTorqueAuditLog(r.Context(), id)
if err != nil {
httpx.Fail(w, 3209, err.Error())
return
}
httpx.Ok(w, data)
}
}
@@ -141,6 +141,8 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
{Method: http.MethodPost, Path: "/torque/report", Handler: production.TorqueReportHandler(serverCtx)},
{Method: http.MethodGet, Path: "/torque/records", Handler: production.TorqueRecordsHandler(serverCtx)},
{Method: http.MethodPost, Path: "/torque/audit", Handler: production.TorqueAuditHandler(serverCtx)},
{Method: http.MethodGet, Path: "/torque/audit-log", Handler: production.TorqueAuditLogHandler(serverCtx)},
{Method: http.MethodPost, Path: "/torque/manual-add", Handler: production.TorqueManualAddHandler(serverCtx)},
{Method: http.MethodPost, Path: "/scan/report", Handler: production.ScanReportHandler(serverCtx)},
@@ -161,6 +163,20 @@ func RegisterProductionRoutes(server *rest.Server, serverCtx *svc.ServiceContext
{Method: http.MethodGet, Path: "/event-logs", Handler: production.ListEventLogsHandler(serverCtx)},
// ---------- 预警规则 + 预警中心(M10 ----------
{Method: http.MethodPost, Path: "/alert/rule", Handler: production.CreateAlertRuleHandler(serverCtx)},
{Method: http.MethodPut, Path: "/alert/rule", Handler: production.UpdateAlertRuleHandler(serverCtx)},
{Method: http.MethodDelete, Path: "/alert/rule", Handler: production.DeleteAlertRuleHandler(serverCtx)},
{Method: http.MethodGet, Path: "/alert/rules", Handler: production.ListAlertRulesHandler(serverCtx)},
{Method: http.MethodGet, Path: "/alerts", Handler: production.ListAlertsHandler(serverCtx)},
{Method: http.MethodPost, Path: "/alert/read", Handler: production.MarkAlertReadHandler(serverCtx)},
// ---------- 附件中心(M10 ----------
{Method: http.MethodPost, Path: "/attachment/upload", Handler: AttachmentUploadHandler(serverCtx)},
{Method: http.MethodGet, Path: "/attachments", Handler: AttachmentListHandler(serverCtx)},
{Method: http.MethodGet, Path: "/attachment/download", Handler: AttachmentDownloadHandler(serverCtx)},
{Method: http.MethodPost, Path: "/attachment/delete", Handler: AttachmentDeleteHandler(serverCtx)},
// ---------- 工艺流程/工位(块3 ----------
{Method: http.MethodGet, Path: "/process-flows", Handler: ListProcessFlowsHandler(serverCtx)},
{Method: http.MethodPost, Path: "/process-flows", Handler: SaveProcessFlowHandler(serverCtx)},
+28
View File
@@ -75,3 +75,31 @@ func serveUploadFile(w http.ResponseWriter, r *http.Request, dir, name string) {
w.Header().Set("Content-Type", ct)
http.ServeContent(w, r, name, time.Time{}, f)
}
// saveUploadFileFull 同 saveUploadFile,但额外返回原始文件名与字节大小(附件登记用)
func saveUploadFileFull(r *http.Request, field, dir string) (string, string, int64, error) {
sub := time.Now().Format("2006-01-02")
full := filepath.Join(dir, sub)
if err := os.MkdirAll(full, 0o755); err != nil {
return "", "", 0, err
}
file, header, err := r.FormFile(field)
if err != nil {
return "", "", 0, err
}
defer file.Close()
ext := strings.ToLower(filepath.Ext(header.Filename))
if ext == "" {
ext = ".bin"
}
name := time.Now().Format("20060102150405") + "_" + randHex(6) + ext
dst, err := os.Create(filepath.Join(full, name))
if err != nil {
return "", "", 0, err
}
defer dst.Close()
if _, err := io.Copy(dst, file); err != nil {
return "", "", 0, err
}
return filepath.ToSlash(filepath.Join(sub, name)), header.Filename, header.Size, nil
}