瀏覽代碼

新增套筒压接

hou 5 天之前
父節點
當前提交
3da52fbd05
共有 34 個文件被更改,包括 3346 次插入12420 次删除
  1. 17 0
      db/mes_product_rivet.sql
  2. 13 0
      db/mes_product_rivet_menu.sql
  3. 二進制
      docs/MES气密工位对接说明-设备方.docx
  4. 357 0
      docs/generate_qm_word.py
  5. 331 0
      docs/qm-api-设备对接手册.md
  6. 400 0
      docs/qm-api.md
  7. 二進制
      docs/气密工位MES对接说明(设备方).docx
  8. 280 513
      mes_cloud_199_0y7a.sql
  9. 14 0
      src/main/java/com/jeesite/modules/mes/dao/MesProductRivetDao.java
  10. 159 0
      src/main/java/com/jeesite/modules/mes/entity/MesProductRivet.java
  11. 105 0
      src/main/java/com/jeesite/modules/mes/service/MesProductRivetService.java
  12. 163 0
      src/main/java/com/jeesite/modules/mes/web/MesProductRivetController.java
  13. 1 0
      src/main/resources/config/application.yml
  14. 18 0
      src/main/resources/mappings/modules/mes/MesProductRivetDao.xml
  15. 128 0
      src/main/resources/views/modules/mes/mesProductRivetForm.html
  16. 100 0
      src/main/resources/views/modules/mes/mesProductRivetList.html
  17. 二進制
      src/main/webapp/WEB-INF/classes/com/jeesite/modules/mes/dao/MesProductRivetDao.class
  18. 二進制
      src/main/webapp/WEB-INF/classes/com/jeesite/modules/mes/entity/MesProductRivet.class
  19. 二進制
      src/main/webapp/WEB-INF/classes/com/jeesite/modules/mes/service/MesProductRivetService.class
  20. 二進制
      src/main/webapp/WEB-INF/classes/com/jeesite/modules/mes/web/MesProductRivetController.class
  21. 2 1
      src/main/webapp/WEB-INF/classes/config/application.yml
  22. 二進制
      src/main/webapp/WEB-INF/classes/logs/debug.2026-06-08.0.log.zip
  23. 二進制
      src/main/webapp/WEB-INF/classes/logs/debug.2026-06-09.0.log.zip
  24. 二進制
      src/main/webapp/WEB-INF/classes/logs/debug.2026-06-10.0.log.zip
  25. 671 11565
      src/main/webapp/WEB-INF/classes/logs/debug.log
  26. 18 0
      src/main/webapp/WEB-INF/classes/mappings/modules/mes/MesProductRivetDao.xml
  27. 128 0
      src/main/webapp/WEB-INF/classes/views/modules/mes/mesProductRivetForm.html
  28. 100 0
      src/main/webapp/WEB-INF/classes/views/modules/mes/mesProductRivetList.html
  29. 341 341
      target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  30. 二進制
      target/test-classes/com/jeesite/test/InitData.class
  31. 二進制
      target/test-classes/com/jeesite/test/InsertBatchTest.class
  32. 二進制
      target/test-classes/com/jeesite/test/MultiDataSourceTest$1.class
  33. 二進制
      target/test-classes/com/jeesite/test/MultiDataSourceTest.class
  34. 二進制
      target/test-classes/com/jeesite/test/RememberMeKeyGen.class

+ 17 - 0
db/mes_product_rivet.sql

@@ -0,0 +1,17 @@
+CREATE TABLE IF NOT EXISTS mes_product_rivet (
+    id           VARCHAR(64)   NOT NULL PRIMARY KEY COMMENT '主键',
+    sn           VARCHAR(50)   NOT NULL COMMENT '工件码',
+    line_sn      VARCHAR(30)   NOT NULL COMMENT '产线编号',
+    oprno        VARCHAR(20)   NOT NULL COMMENT '工位号',
+    point_no     INT           NOT NULL COMMENT '铆接点序号(1-7)',
+    result       VARCHAR(4)    NOT NULL COMMENT 'OK/NG',
+    stroke       DECIMAL(10,3) NULL COMMENT '行程值',
+    pressure     DECIMAL(10,3) NULL COMMENT '压力值',
+    hold_time    INT           NULL COMMENT '保压时间(ms)',
+    record_time  DATETIME      NULL COMMENT '采集时间',
+    create_by    VARCHAR(64)   NULL COMMENT '上传人',
+    create_date  DATETIME      NULL COMMENT '上传时间',
+    update_by    VARCHAR(64)   NULL,
+    update_date  DATETIME      NULL,
+    UNIQUE KEY uk_sn_line_oprno_point (sn, line_sn, oprno, point_no)
+) COMMENT='套筒压铆过程参数';

+ 13 - 0
db/mes_product_rivet_menu.sql

@@ -0,0 +1,13 @@
+-- 菜单需在 MES 后台「菜单管理」中配置,或执行以下 SQL(请根据实际 parent_code 调整上级菜单)
+-- 上级菜单建议:生产管理 / 工艺过程参数(与拉铆过程参数同级)
+
+-- INSERT INTO js_sys_menu (
+--   menu_code, parent_code, parent_codes, tree_sort, tree_sorts, tree_leaf, tree_level, tree_names,
+--   menu_name, menu_type, menu_href, menu_target, menu_icon, menu_color, menu_title, permission,
+--   weight, is_show, sys_code, module_codes, status, create_by, create_date, update_by, update_date
+-- ) VALUES (
+--   'mes_product_rivet_menu', '工艺过程参数父菜单code', '...', 160, '...', '1', 2, '生产管理/工艺过程参数/套筒压铆过程参数',
+--   '套筒压铆过程参数', '1', '/mes/mesProductRivet/list', '', '', '', '套筒压铆过程参数',
+--   'mes:mesProductRivet:view,mes:mesProductRivet:edit', 40, '1', 'default', 'core', '0',
+--   'system', NOW(), 'system', NOW()
+-- );

二進制
docs/MES气密工位对接说明-设备方.docx


+ 357 - 0
docs/generate_qm_word.py

@@ -0,0 +1,357 @@
+# -*- coding: utf-8 -*-
+"""Generate Word doc from qm-api device integration manual."""
+
+from docx import Document
+from docx.shared import Pt, Cm, RGBColor
+from docx.enum.text import WD_ALIGN_PARAGRAPH
+from docx.enum.table import WD_TABLE_ALIGNMENT
+from docx.oxml.ns import qn
+import os
+
+OUTPUT = os.path.join(os.path.dirname(__file__), "气密工位MES对接说明(设备方).docx")
+
+
+def set_cell_shading(cell, color="D9E2F3"):
+    from docx.oxml import OxmlElement
+    shading = OxmlElement("w:shd")
+    shading.set(qn("w:fill"), color)
+    shading.set(qn("w:val"), "clear")
+    cell._tc.get_or_add_tcPr().append(shading)
+
+
+def add_heading(doc, text, level=1):
+    h = doc.add_heading(text, level=level)
+    for run in h.runs:
+        run.font.name = "微软雅黑"
+        run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
+    return h
+
+
+def add_para(doc, text, bold=False, size=11):
+    p = doc.add_paragraph()
+    run = p.add_run(text)
+    run.font.name = "微软雅黑"
+    run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
+    run.font.size = Pt(size)
+    run.bold = bold
+    p.paragraph_format.space_after = Pt(6)
+    return p
+
+
+def add_code(doc, text):
+    p = doc.add_paragraph()
+    p.paragraph_format.left_indent = Cm(0.5)
+    run = p.add_run(text)
+    run.font.name = "Consolas"
+    run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
+    run.font.size = Pt(9)
+    run.font.color.rgb = RGBColor(0x33, 0x33, 0x33)
+    p.paragraph_format.space_after = Pt(8)
+    return p
+
+
+def add_table(doc, headers, rows, col_widths=None):
+    table = doc.add_table(rows=1 + len(rows), cols=len(headers))
+    table.style = "Table Grid"
+    table.alignment = WD_TABLE_ALIGNMENT.CENTER
+    hdr = table.rows[0].cells
+    for i, h in enumerate(headers):
+        hdr[i].text = h
+        set_cell_shading(hdr[i])
+        for p in hdr[i].paragraphs:
+            for run in p.runs:
+                run.bold = True
+                run.font.name = "微软雅黑"
+                run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
+                run.font.size = Pt(10)
+    for ri, row in enumerate(rows):
+        cells = table.rows[ri + 1].cells
+        for ci, val in enumerate(row):
+            cells[ci].text = str(val)
+            for p in cells[ci].paragraphs:
+                for run in p.runs:
+                    run.font.name = "微软雅黑"
+                    run._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
+                    run.font.size = Pt(10)
+    if col_widths:
+        for i, w in enumerate(col_widths):
+            for row in table.rows:
+                row.cells[i].width = Cm(w)
+    doc.add_paragraph()
+    return table
+
+
+def build():
+    doc = Document()
+    sec = doc.sections[0]
+    sec.top_margin = Cm(2.5)
+    sec.bottom_margin = Cm(2.5)
+    sec.left_margin = Cm(2.8)
+    sec.right_margin = Cm(2.8)
+
+    # Title
+    title = doc.add_paragraph()
+    title.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    tr = title.add_run("气密工位 MES 对接说明(设备方)")
+    tr.bold = True
+    tr.font.size = Pt(18)
+    tr.font.name = "微软雅黑"
+    tr._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
+
+    sub = doc.add_paragraph()
+    sub.alignment = WD_ALIGN_PARAGRAPH.CENTER
+    sr = sub.add_run("文档版本:v1.0    更新日期:2026-06-08")
+    sr.font.size = Pt(10)
+    sr.font.color.rgb = RGBColor(0x66, 0x66, 0x66)
+    sr.font.name = "微软雅黑"
+    sr._element.rPr.rFonts.set(qn("w:eastAsia"), "微软雅黑")
+    doc.add_paragraph()
+
+    add_para(doc, "文档用途:气密检测设备与 MES 系统对接,完成本工位进站校验与结果上传。")
+    add_para(doc, "对接方:气密设备软件 / 上位机开发")
+    add_para(doc, "MES 地址:http://192.168.16.99:8980", bold=True)
+    doc.add_paragraph()
+
+    # Section 1
+    add_heading(doc, "一、先看这个(1 分钟读懂)", 1)
+    add_para(doc, "您的设备软件只需要做 2 件事:")
+    add_table(doc, ["时机", "调用哪个接口", "作用"], [
+        ["扫码后、开始测试前", "接口 1:qmcheck", "问 MES:这个工件能不能测?"],
+        ["测试结束后", "接口 2:qmresult", "告诉 MES:测完了,结果 OK 还是 NG"],
+    ], [4, 4.5, 6])
+    add_para(doc, "不需要登录、不需要 Token,设备能访问 MES 服务器 IP 即可。")
+    add_para(doc, "MES 会提前提供给您的固定参数(请填入下表,对接前向 MES 方确认):")
+    add_table(doc, ["参数", "含义", "示例(以 MES 方确认为准)"], [
+        ["oprno", "工位号", "OP450"],
+        ["lineSn", "产线编号", "XT"],
+    ], [3, 4, 6])
+
+    # Section 2
+    add_heading(doc, "二、整体流程(设备侧逻辑)", 1)
+    add_code(doc, """操作员扫码(得到 sn)
+        │
+        ▼
+  调用 qmcheck(进站校验)
+        │
+  result=true 且 data=UD ?
+        │
+   否 ──┴── 是
+   │         │
+   ▼         ▼
+屏幕提示   开始气密测试
+禁止测试   (设备自行测试)
+                 │
+                 ▼
+          调用 qmresult(上传结果)
+                 │
+           result=true ?
+                 │
+          否 ────┴──── 是
+          │            │
+          ▼            ▼
+       提示失败     显示上传成功""")
+
+    # Section 3
+    add_heading(doc, "三、通信方式(统一约定)", 1)
+    add_table(doc, ["项目", "值"], [
+        ["协议", "HTTP"],
+        ["方法", "POST"],
+        ["参数格式", "表单:application/x-www-form-urlencoded"],
+        ["编码", "UTF-8"],
+        ["返回格式", "JSON 字符串"],
+    ], [4, 10])
+    add_para(doc, "请求示例格式(所有接口相同):")
+    add_code(doc, """POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/接口名
+Content-Type: application/x-www-form-urlencoded
+
+参数1=值1&参数2=值2&...""")
+    add_para(doc, "建议每个请求都带上:__ajax=json(固定值,便于返回 JSON)。")
+
+    # Section 4
+    add_heading(doc, "四、接口 1:进站校验 qmcheck", 1)
+    add_heading(doc, "4.1 什么时候调", 2)
+    add_para(doc, "• 操作员扫完条码、设备尚未开始充气/测试之前")
+    add_para(doc, "• 每个工件测一次(换件后重新调)")
+
+    add_heading(doc, "4.2 地址", 2)
+    add_code(doc, "POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmcheck")
+
+    add_heading(doc, "4.3 要传哪些参数", 2)
+    add_table(doc, ["参数", "必填", "从哪里来", "说明"], [
+        ["sn", "是", "扫码枪 / 人工输入", "工件条码"],
+        ["oprno", "是", "MES 提供,写死在配置里", "工位号"],
+        ["lineSn", "是", "MES 提供,写死在配置里", "产线号"],
+        ["workNum", "建议", "登录工号,没有就传 system", "操作员"],
+        ["__ajax", "建议", "固定 json", "—"],
+    ], [2.5, 1.5, 4.5, 4.5])
+
+    add_heading(doc, "4.4 请求示例", 2)
+    add_para(doc, "表单内容:")
+    add_code(doc, "__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&workNum=system")
+    add_para(doc, "curl(Windows):")
+    add_code(doc, 'curl -X POST "http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmcheck" ^\n  -H "Content-Type: application/x-www-form-urlencoded" ^\n  -d "__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&workNum=system"')
+
+    add_heading(doc, "4.5 返回怎么判断(设备程序重点)", 2)
+    add_para(doc, "返回示例:")
+    add_code(doc, '{\n  "result": "true",\n  "message": "工件可以加工",\n  "data": "UD"\n}')
+    add_para(doc, "判断规则(写进设备程序):")
+    add_code(doc, 'IF result == "true" AND data == "UD"\n    → 允许开始测试\nELSE\n    → 不允许测试,屏幕显示 message 原文')
+    add_table(doc, ["result", "data", "设备怎么处理"], [
+        ["true", "UD", "允许测试"],
+        ["false", "任意", "禁止测试,显示 message"],
+    ], [2.5, 2.5, 8])
+    add_para(doc, "常见 message 含义:")
+    add_table(doc, ["message 示例", "含义"], [
+        ["工件可以加工", "正常,可以测"],
+        ["该工件OP180未加工", "前面工位没做,不能测"],
+        ["该工件本工位已加工,结果:OK", "已经测过且合格,不必再测"],
+        ["该工件未录入系统", "条码错误或 MES 无此件"],
+        ["两次气密必须间隔15分钟", "同一工件 15 分钟内不能重复测"],
+        ["参数错误1", "sn / oprno / lineSn 有缺失"],
+    ], [6, 7])
+
+    # Section 5
+    add_heading(doc, "五、接口 2:结果上传 qmresult", 1)
+    add_heading(doc, "5.1 什么时候调", 2)
+    add_para(doc, "• 气密测试结束后(无论 OK 还是 NG 都要调)")
+    add_para(doc, "• 必须先 qmcheck 通过再测;测完再调本接口")
+
+    add_heading(doc, "5.2 地址", 2)
+    add_code(doc, "POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmresult")
+
+    add_heading(doc, "5.3 要传哪些参数", 2)
+    add_para(doc, "必填(少了会报「参数错误1」):")
+    add_table(doc, ["参数", "说明", "示例"], [
+        ["sn", "工件条码(与 qmcheck 相同)", "扫码值"],
+        ["oprno", "工位号", "OP450"],
+        ["lineSn", "产线号", "XT"],
+        ["result", "测试结果", "OK 或 NG(必须大写)"],
+        ["craft", "工艺号", "固定传 100000"],
+        ["__ajax", "建议", "json"],
+    ], [2.5, 5, 5.5])
+    add_para(doc, "建议传:workNum(操作员工号,没有传 system)")
+    add_para(doc, "可选(有就传,便于 MES 存过程数据):")
+    add_table(doc, ["参数", "是否编码", "说明"], [
+        ["testPressure", "Base64", "测试压力"],
+        ["testPressureUnit", "明文", "单位,如 Pa"],
+        ["leakVal", "Base64", "泄漏值"],
+        ["leakValUnit", "明文", "单位"],
+        ["testTime", "Base64", "测试时间,格式 yyyy-MM-dd HH:mm:ss"],
+        ["cxm", "明文", "设备程序号"],
+        ["deviceType", "明文", "设备编号"],
+        ["remark", "明文", "备注"],
+    ], [3.5, 2, 8])
+    add_para(doc, "关于 Base64:testPressure、leakVal、testTime 需先 Base64 编码再提交。若暂时不做 Base64,最少只传必填项也能完成工位对接。")
+    add_para(doc, "Base64 对照(方便联调):")
+    add_table(doc, ["原始值", "传参值"], [
+        ["200", "MjAw"],
+        ["5", "NQ=="],
+        ["2024-05-16 12:12:00", "MjAyNC0wNS0xNiAxMjoxMjowMA=="],
+    ], [5, 8])
+
+    add_heading(doc, "5.4 请求示例", 2)
+    add_para(doc, "最简版(能完成工位对接):")
+    add_code(doc, "__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&craft=100000&result=OK&workNum=system")
+    add_para(doc, "完整版(含过程数据):")
+    add_code(doc, "__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&craft=100000&result=OK&workNum=system&testPressure=MjAw&testPressureUnit=Pa&leakVal=NQ==&leakValUnit=Pa&testTime=MjAyNC0wNS0xNiAxMjoxMjowMA==&cxm=PROGRAM_01")
+
+    add_heading(doc, "5.5 返回怎么判断", 2)
+    add_para(doc, "成功:")
+    add_code(doc, '{\n  "result": "true",\n  "message": "操作成功!"\n}')
+    add_para(doc, "失败:")
+    add_code(doc, '{\n  "result": "false",\n  "message": "该工件本工位已加工,结果:OK"\n}')
+    add_code(doc, 'IF result == "true"\n    → 上传成功,MES 工位结果已更新\nELSE\n    → 上传失败,显示 message,建议本地保存待人工处理')
+    add_table(doc, ["常见 message", "原因", "建议"], [
+        ["操作成功!", "正常", "—"],
+        ["参数错误1!", "缺 sn/oprno/lineSn/result", "检查必填参数"],
+        ["子工位未配置!", "MES 未配置该工位", "联系 MES 管理员"],
+        ["该工件本工位已加工…", "重复上传", "提示用户,勿重复提交"],
+        ["该工件OPxxx未加工", "未做 qmcheck 或前序工位未完成", "先校验流程"],
+    ], [4.5, 4.5, 4])
+
+    # Section 6
+    add_heading(doc, "六、设备程序伪代码", 1)
+    add_code(doc, """// 配置项(MES 方提供,写死在设备软件里)
+MES_HOST = "192.168.16.99"
+MES_PORT = 8980
+OPRNO    = "OP450"      // 工位号,向 MES 确认
+LINE_SN  = "XT"         // 产线号,向 MES 确认
+
+// 1. 扫码后
+sn = 扫码结果
+body = "__ajax=json&sn=" + urlEncode(sn)
+     + "&oprno=" + OPRNO + "&lineSn=" + LINE_SN
+     + "&workNum=" + urlEncode(当前工号或"system")
+resp = HTTP_POST("http://" + MES_HOST + ":8980/js/a/mes/mesProductRecord/qmcheck", body)
+if resp.result != "true" OR resp.data != "UD":
+    显示(resp.message); 停止,不允许测试
+
+// 2. 设备自行做气密测试
+testResult = 设备判定结果  // "OK" 或 "NG"
+
+// 3. 测试结束后上传
+body = "__ajax=json&sn=" + urlEncode(sn)
+     + "&oprno=" + OPRNO + "&lineSn=" + LINE_SN
+     + "&craft=100000" + "&result=" + testResult
+     + "&workNum=" + urlEncode(当前工号或"system")
+resp = HTTP_POST("http://" + MES_HOST + ":8980/js/a/mes/mesProductRecord/qmresult", body)
+if resp.result != "true":
+    显示("MES上传失败:" + resp.message); 本地记录,供补传
+else
+    显示("MES上传成功")""")
+
+    # Section 7
+    add_heading(doc, "七、联调步骤(双方配合)", 1)
+    add_heading(doc, "第 1 步:网络", 2)
+    add_para(doc, "□ 气密设备电脑能 ping 192.168.16.99")
+    add_para(doc, "□ 浏览器或 Postman 能访问 http://192.168.16.99:8980")
+
+    add_heading(doc, "第 2 步:用 Postman 测通两个接口", 2)
+    add_para(doc, "1. 向 MES 方要一个真实可测的工件码 sn")
+    add_para(doc, "2. 调 qmcheck,确认返回 result=true, data=UD")
+    add_para(doc, "3. 调 qmresult,result=OK,确认返回「操作成功!」")
+    add_para(doc, "4. MES 方在后台确认工位结果已更新")
+
+    add_heading(doc, "第 3 步:设备软件接入", 2)
+    add_para(doc, "按第六节伪代码接入;先实现最简参数,过程数据稳定后再补。")
+
+    add_heading(doc, "第 4 步:现场验证", 2)
+    add_para(doc, "□ 正常件:check 通过 → 测试 → result 上传成功")
+    add_para(doc, "□ NG 件:result=NG 上传成功,MES 显示 NG")
+    add_para(doc, "□ 重复扫码:check 提示已加工,不允许再测")
+    add_para(doc, "□ 错误条码:check 提示未录入系统")
+
+    # Section 8 FAQ
+    add_heading(doc, "八、常见问题 FAQ", 1)
+    faqs = [
+        ("Q1:要不要登录 MES?", "不需要。两个接口均已开放匿名访问。"),
+        ("Q2:只调 qmresult 不调 qmcheck 可以吗?", "不建议。可能因前序工位、重复加工等原因上传失败。"),
+        ("Q3:craft 为什么固定 100000?", "MES 约定 100000 表示工位总结果(OK/NG),必须传。"),
+        ("Q4:result 用小写 ok/ng 可以吗?", "不可以,必须大写 OK / NG。"),
+        ("Q5:qmcheck 和 qmresult 的 sn 必须一致吗?", "必须一致,为同一工件条码。"),
+        ("Q6:上传失败要不要重试?", "看 message。若是「已加工」等业务拦截,不要盲重试;网络超时可在本地缓存后定时重试 qmresult。"),
+        ("Q7:工件码有特殊字符怎么办?", "做 URL 编码(如空格、&、= 等)。"),
+    ]
+    for q, a in faqs:
+        add_para(doc, q, bold=True)
+        add_para(doc, "A:" + a)
+
+    # Section 9
+    add_heading(doc, "九、对接确认单(请 MES 方填写后发给设备方)", 1)
+    add_table(doc, ["项", "值"], [
+        ["MES 服务器 IP", "192.168.16.99"],
+        ["端口", "8980"],
+        ["工位号 oprno", "__________"],
+        ["产线号 lineSn", "__________"],
+        ["测试用工件码 sn", "__________"],
+        ["是否需要传过程数据(压力/泄漏值)", "是 / 否"],
+        ["MES 联系人", "__________"],
+        ["联系电话 / 微信", "__________"],
+    ], [5.5, 8.5])
+
+    doc.save(OUTPUT)
+    print("Generated:", OUTPUT)
+
+
+if __name__ == "__main__":
+    build()

+ 331 - 0
docs/qm-api-设备对接手册.md

@@ -0,0 +1,331 @@
+# 气密工位 MES 对接说明(设备方)
+
+**文档用途**:气密检测设备与 MES 系统对接,完成本工位进站校验与结果上传。  
+**对接方**:气密设备软件 / 上位机开发  
+**MES 地址**:`http://192.168.16.99:8980`
+
+---
+
+## 一、先看这个
+
+您的设备软件只需要做 **2 件事**:
+
+| 时机 | 调用哪个接口 | 作用 |
+|------|-------------|------|
+| **扫码后、开始测试前** | 接口 1:`qmcheck` | 问 MES:这个工件能不能测? |
+| **测试结束后** | 接口 2:`qmresult` | 告诉 MES:测完了,结果 OK 还是 NG |
+
+**不需要登录、不需要 Token**,设备能访问 MES 服务器 IP 即可。
+
+**MES 会提前提供给您的固定参数**(请填入下表,对接前向 MES 方确认):
+
+| 参数 | 含义 | 示例(以 MES 方确认为准) |
+|------|------|--------------------------|
+| `oprno` | 工位号 | `OP450` |
+| `lineSn` | 产线编号 | `XT` |
+
+---
+
+## 二、整体流程(设备侧逻辑)
+
+```
+操作员扫码(得到 sn)
+        │
+        ▼
+┌───────────────────┐
+│  调用 qmcheck     │  ← 进站校验
+└─────────┬─────────┘
+          │
+    result=true 且 data=UD ?
+          │
+     否 ──┴── 是
+     │         │
+     ▼         ▼
+  屏幕提示     开始气密测试
+  message      (设备自行测试)
+  禁止测试           │
+                     ▼
+              ┌──────────────┐
+              │ 调用 qmresult │  ← 上传结果
+              └──────┬───────┘
+                     │
+               result=true ?
+                     │
+              否 ────┴──── 是
+              │            │
+              ▼            ▼
+           提示失败      显示上传成功
+           可本地记录     放行/打印等
+```
+
+---
+
+## 三、通信方式(统一约定)
+
+| 项目 | 值 |
+|------|-----|
+| 协议 | HTTP |
+| 方法 | `POST` |
+| 参数格式 | 表单:`application/x-www-form-urlencoded` |
+| 编码 | UTF-8 |
+| 返回格式 | JSON 字符串 |
+
+**请求示例格式**(所有接口相同):
+
+```
+POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/接口名
+Content-Type: application/x-www-form-urlencoded
+
+参数1=值1&参数2=值2&...
+```
+
+建议每个请求都带上:`__ajax=json`(固定值,便于返回 JSON)。
+
+---
+
+## 四、接口 1:进站校验 qmcheck
+
+### 4.1 什么时候调
+
+- 操作员扫完条码、**设备尚未开始充气/测试之前**
+- 每个工件测一次(换件后重新调)
+
+### 4.2 地址
+
+```
+POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmcheck
+```
+
+### 4.3 要传哪些参数
+
+| 参数 | 必填 | 从哪里来 | 说明 |
+|------|:----:|----------|------|
+| sn | ✅ | 扫码枪 / 人工输入 | 工件条码 |
+| oprno | ✅ | MES 提供,写死在配置里 | 工位号 |
+| lineSn | ✅ | MES 提供,写死在配置里 | 产线号 |
+| workNum | 建议 | 登录工号,没有就传 `system` | 操作员 |
+| __ajax | 建议 | 固定 `json` | — |
+
+
+
+### 4.4 请求示例(可直接用 Postman / curl 测)
+
+**表单内容:**
+
+```
+__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&workNum=system
+```
+
+**curl:**
+
+```bash
+curl -X POST "http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmcheck" ^
+  -H "Content-Type: application/x-www-form-urlencoded" ^
+  -d "__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&workNum=system"
+```
+
+### 4.5 返回怎么判断(设备程序重点看这里)
+
+**返回示例:**
+
+```json
+{
+  "result": "true",
+  "message": "工件可以加工",
+  "data": "UD"
+}
+```
+
+**判断规则(写进设备程序):**
+
+```
+IF result == "true" AND data == "UD"
+    → 允许开始测试
+ELSE
+    → 不允许测试,屏幕显示 message 原文
+```
+
+| result | data | 设备怎么处理 |
+|--------|------|-------------|
+| true | UD | ✅ **允许测试** |
+| false | 任意 | ❌ 禁止测试,显示 `message` |
+
+**常见 message 含义(给用户看):**
+
+| message 示例 | 含义 |
+|-------------|------|
+| 工件可以加工 | 正常,可以测 |
+| 该工件OP180未加工 | 前面工位没做,不能测 |
+| 该工件本工位已加工,结果:OK | 已经测过且合格,不必再测 |
+| 该工件未录入系统 | 条码错误或 MES 无此件 |
+| 两次气密必须间隔15分钟 | 同一工件 15 分钟内不能重复测 |
+| 参数错误1 | sn / oprno / lineSn 有缺失 |
+
+---
+
+## 五、接口 2:结果上传 qmresult
+
+### 5.1 什么时候调
+
+- 气密测试**结束后**(无论 OK 还是 NG 都要调)
+- **必须先 qmcheck 通过再测**;测完再调本接口
+
+### 5.2 地址
+
+```
+POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmresult
+```
+
+### 5.3 要传哪些参数
+
+#### 必填(少了会报「参数错误1」)
+
+| 参数 | 说明 | 示例                |
+|------|-----|-------------------|
+| sn | 工件条码(与 qmcheck 相同) | 扫码值               |
+| oprno | 工位号 | OPXXX             |
+| lineSn | 产线号 | XT                |
+| result | 测试结果 | `OK` 或 `NG`(必须大写) |
+| craft | 工艺号 | **固定传 `100000`**  |
+| __ajax | 建议 | `json`            |
+
+#### 建议传
+
+| 参数 | 说明 |
+|------|------|
+| workNum | 操作员工号,没有传 `system` |
+
+#### 可选(有就传,便于 MES 存过程数据)
+
+| 参数 | 是否编码 | 说明 |
+|------|:--------:|------|
+| testPressure | Base64 | 测试压力 |
+| testPressureUnit | 明文 | 单位,如 Pa |
+| leakVal | Base64 | 泄漏值 |
+| leakValUnit | 明文 | 单位 |
+| testTime | Base64 | 测试时间,原文格式 `yyyy-MM-dd HH:mm:ss` |
+| cxm | 明文 | 设备程序号 |
+| deviceType | 明文 | 设备编号 |
+| remark | 明文 | 备注 |
+
+> **关于 Base64**:`testPressure`、`leakVal`、`testTime` 三个字段,需要先把原始字符串做 Base64,再作为表单参数提交。  
+> 若暂时不想做 Base64,**最少只传必填项也能完成工位对接**;过程数据可后续再补。
+
+**Base64 对照(方便联调):**
+
+| 原始值 | 传参值 |
+|--------|--------|
+| 200 | MjAw |
+| 5 | NQ== |
+| 2024-05-16 12:12:00 | MjAyNC0wNS0xNiAxMjoxMjowMA== |
+
+### 5.4 请求示例
+
+#### 最简版(能完成工位对接)
+
+```
+__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&craft=100000&result=OK&workNum=system
+```
+
+#### 完整版(含过程数据)
+
+```
+__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&craft=100000&result=OK&workNum=system&testPressure=MjAw&testPressureUnit=Pa&leakVal=NQ==&leakValUnit=Pa&testTime=MjAyNC0wNS0xNiAxMjoxMjowMA==&cxm=PROGRAM_01
+```
+
+**curl(最简版):**
+
+```bash
+curl -X POST "http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmresult" ^
+  -H "Content-Type: application/x-www-form-urlencoded" ^
+  -d "__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&craft=100000&result=OK&workNum=system"
+```
+
+### 5.5 返回怎么判断
+
+**成功:**
+
+```json
+{
+  "result": "true",
+  "message": "操作成功!"
+}
+```
+
+**失败:**
+
+```json
+{
+  "result": "false",
+  "message": "该工件本工位已加工,结果:OK"
+}
+```
+
+**判断规则:**
+
+```
+IF result == "true"
+    → 上传成功,MES 工位结果已更新
+ELSE
+    → 上传失败,显示 message,建议本地保存待人工处理
+```
+
+| 常见 message | 原因 | 建议 |
+|-------------|------|------|
+| 操作成功! | 正常 | — |
+| 参数错误1! | 缺 sn/oprno/lineSn/result | 检查必填参数 |
+| 子工位未配置! | MES 未配置该工位 | 联系 MES 管理员 |
+| 该工件本工位已加工… | 重复上传 | 提示用户,勿重复提交 |
+| 该工件OPxxx未加工 | 未做 qmcheck 或前序工位未完成 | 先校验流程 |
+
+---
+
+## 六、设备程序伪代码(可直接交给开发)
+
+```text
+// ===== 配置项(MES 方提供,写死在设备软件里)=====
+MES_HOST = "192.168.16.99"
+MES_PORT = 8980
+OPRNO    = "OP450"      // 工位号,向 MES 确认
+LINE_SN  = "XT"         // 产线号,向 MES 确认
+
+// ===== 1. 扫码后 =====
+sn = 扫码结果
+body = "__ajax=json&sn=" + urlEncode(sn)
+     + "&oprno=" + OPRNO
+     + "&lineSn=" + LINE_SN
+     + "&workNum=" + urlEncode(当前工号或"system")
+
+resp = HTTP_POST("http://" + MES_HOST + ":8980/js/a/mes/mesProductRecord/qmcheck", body)
+
+if resp.result != "true" OR resp.data != "UD":
+    显示(resp.message)
+    停止,不允许测试
+end if
+
+// ===== 2. 设备自行做气密测试 =====
+testResult = 设备判定结果  // "OK" 或 "NG"
+
+// ===== 3. 测试结束后上传 =====
+body = "__ajax=json&sn=" + urlEncode(sn)
+     + "&oprno=" + OPRNO
+     + "&lineSn=" + LINE_SN
+     + "&craft=100000"
+     + "&result=" + testResult
+     + "&workNum=" + urlEncode(当前工号或"system")
+     // 可选:追加 testPressure、leakVal、testTime 等
+
+resp = HTTP_POST("http://" + MES_HOST + ":8980/js/a/mes/mesProductRecord/qmresult", body)
+
+if resp.result != "true":
+    显示("MES上传失败:" + resp.message)
+    本地记录,供补传
+else
+    显示("MES上传成功")
+end if
+```
+
+
+
+**文档版本**:v1.0  
+**更新日期**:2026-06-08

+ 400 - 0
docs/qm-api.md

@@ -0,0 +1,400 @@
+# 气密检测接口对接文档
+
+> 接口:`MesProductRecordController.qmcheck` / `MesProductRecordController.qmresult`  
+> MES 服务地址:`http://192.168.16.99:8980`  
+> 版本依据:mescloud 源码(2026-06)
+
+---
+
+## 1. 基本信息
+
+| 项目 | 说明 |
+|------|------|
+| 服务 IP | `192.168.16.99` |
+| 端口 | `8980` |
+| 上下文路径 | `/js` |
+| 管理路径 | `/a` |
+| 完整 Base URL | `http://192.168.16.99:8980/js/a/mes/mesProductRecord` |
+| 请求方式 | `POST` |
+| Content-Type | `application/x-www-form-urlencoded`(推荐) |
+| 字符编码 | UTF-8 |
+| 认证 | 无需登录(Shiro 已配置 `anon`) |
+
+### 1.1 对接时序
+
+```
+1. 测试前  → 调用 qmcheck(进站校验,确认能否测试)
+2. 测试后  → 调用 qmresult(上传气密过程数据 + 更新工位总结果)
+```
+
+---
+
+## 2. 接口一:qmcheck(气密进站校验)
+
+### 2.1 接口地址
+
+```
+POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmcheck
+```
+
+### 2.2 功能说明
+
+- 校验工件是否允许在当前气密工位进行测试
+
+### 2.3 请求参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|--------|------|:----:|------|
+| `__ajax` | String | 建议 | 固定传 `json`,JeeSite 约定,服务端返回 JSON 格式 |
+| `sn` | String | **是** | 工件码/产品序列号,MES 中唯一标识该工件 |
+| `oprno` | String | **是** | 工位号,如 `OP330`、`OP450`;支持子工位如 `OP450A`,服务端会自动格式化 |
+| `lineSn` | String | **是** | 产线编号,如 `XT` |
+| `workNum` | String | 否 | 工号/操作员账号,参与 MES 质量校验(开班点检等) |
+| `craft` | String | 否 | 工艺号;本接口读取但未参与业务逻辑,可不传 |
+| `cxm` | String | 否 | 气密程序名/程序号,仅写入 API 日志,不参与校验 |
+| `by` | String | 否 | 保压时间(秒),仅写入 API 日志 |
+| `cq` | String | 否 | 充气时间(秒),仅写入 API 日志 |
+| `cs` | String | 否 | 测试时间(秒),仅写入 API 日志 |
+
+### 2.4 请求示例
+
+#### 表单 Body(推荐)
+
+```
+__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&workNum=system
+```
+
+#### 带注释的完整参数示例
+
+```
+__ajax=json          # JeeSite 返回 JSON,建议固定带上
+&sn=501901660045119990G4JB100071   # 工件码(必填)
+&oprno=OP450         # 工位号(必填)
+&lineSn=XT           # 产线编号(必填)
+```
+
+#### 原始 HTTP 报文
+
+```http
+POST /js/a/mes/mesProductRecord/qmcheck HTTP/1.1
+Host: 192.168.16.99:8980
+Content-Type: application/x-www-form-urlencoded
+
+__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT
+```
+
+#### cURL
+
+```bash
+curl -X POST "http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmcheck" \
+  -H "Content-Type: application/x-www-form-urlencoded" \
+  -d "__ajax=json" \
+  -d "sn=501901660045119990G4JB100071" \
+  -d "oprno=OP450" \
+  -d "lineSn=XT"
+```
+
+### 2.5 响应格式
+
+返回 JSON 对象 `CommonResp`:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `result` | String | `"true"` 允许继续 / `"false"` 不允许 |
+| `message` | String | 中文提示信息 |
+| `data` | String | 质量校验状态码(2 位字母) |
+
+### 2.6 响应示例
+
+**允许测试:**
+
+```json
+{
+  "result": "true",
+  "message": "工件可以加工",
+  "data": "UD"
+}
+```
+
+**不允许测试(前置工位未完成):**
+
+```json
+{
+  "result": "false",
+  "message": "该工件OP180未加工",
+  "data": "QD"
+}
+```
+
+**两次气密间隔不足 15 分钟(需配置 `mes.qm.ng.time=1` 开启):**
+
+```json
+{
+  "result": "false",
+  "message": "两次气密必须间隔15分钟",
+  "data": "UD"
+}
+```
+
+**参数缺失:**
+
+```json
+{
+  "result": "false",
+  "message": "参数错误1",
+  "data": null
+}
+```
+
+### 2.7 data 状态码说明
+
+> **对接规则:仅当 `result="true"` 且 `data="UD"` 时,设备方可开始气密测试。**
+
+| data | 含义 | message 示例 |
+|------|------|----------------|
+| `UD` | 可以加工/测试 | 工件可以加工 |
+| `OK` | 本工位已加工且 OK | 该工件本工位已加工,结果:OK |
+| `NG` | 本工位已加工且 NG | 该工件本工位已加工,结果:NG |
+| `NE` | 工件未录入系统 | 该工件未录入系统 |
+| `NN` | 跳过该工位 | 该工件跳过该工位 |
+| `QD` | 前置工位未加工 | 该工件OPxxx未加工 |
+| `QN` | 前置工位 NG | 该工件OPxxx加工NG |
+| `NF` | 已合格下线 | 该工件已合格下线 |
+| `NR` | 返修中 | 该工件离线返修中 |
+| `NB` | 已报废 | 该工件已报废 |
+| `ND` | NG 待处理 | 该工件NG待处理 |
+| `DJ` | 未开班点检 | 未进行开班点检 |
+| `BM` | 未绑定物料 | 未绑定物料 |
+| `PL` | 配件寿命不足 | 配件寿命不足 |
+| `PZ` | 未拍照 | 请先上传照片 |
+
+---
+
+## 3. 接口二:qmresult(气密结果上传)
+
+### 3.1 接口地址
+
+```
+POST http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmresult
+```
+
+### 3.2 功能说明
+
+一次请求完成两件事:
+
+1. **保存气密过程数据** → 写入 `mes_product_qm` 表  
+2. **更新 MES 工位总结果** → 调用 `updateQualityCommon`,更新 `craft=100000` 对应记录
+
+### 3.3 请求参数
+
+#### 3.3.1 基础参数
+
+| 参数名 | 类型 | 必填 | 说明                                     |
+|--------|------|:----:|----------------------------------------|
+| `__ajax` | String | 建议 | 固定传 `json`                             |
+| `sn` | String | **是** | 工件码/产品序列号                              |
+| `oprno` | String | **是** | 工位号,例如 `OP330`、`OP450`                 |
+| `lineSn` | String | **是** | 产线编号,固定传`XT`                           |
+| `result` | String | **是** | 测试结果,固定大写:`OK`(合格)或 `NG`(不合格)          |
+| `craft` | String | **建议** | 工艺号,更新工位总结果时使用;**固定传 `100000`**(工位总结果) |
+| `workNum` | String | 建议 | 工号/操作员账号,写入气密记录及工位结果                   |
+
+#### 3.3.2 气密过程参数
+
+| 参数名 | 类型 | 必填 | Base64 | 说明 |
+|--------|------|:----:|:------:|------|
+| `cxm` | String | 否 | 否 | 气密程序号/程序名 |
+| `deviceType` | String | 否 | 否 | 设备类型/设备编号 |
+| `remark` | String | 否 | 否 | 备注信息 |
+| `testPressure` | String | 否 | **是** | 测试压力值,传 Base64 编码后的字符串 |
+| `testPressureUnit` | String | 否 | 否 | 测试压力单位,如 `Pa`、`kPa` |
+| `leakVal` | String | 否 | **是** | 泄漏值,传 Base64 编码后的字符串 |
+| `leakValUnit` | String | 否 | 否 | 泄漏值单位,如 `Pa` |
+| `testTime` | String | 否 | **是** | 测试完成时间,Base64 编码;解码后格式 `yyyy-MM-dd HH:mm:ss` |
+| `title` | String | 否 | **是** | 测试标题,Base64 编码 |
+
+#### 3.3.3 Base64 编码对照表
+
+| 原始值                   | Base64 编码值 |
+|-----------------------|---------------|
+| `200`                 | `MjAw` |
+| `5`                   | `NQ==` |
+| `2026-06-09 12:12:00` | `MjAyNC0wNS0xNiAxMjoxMjowMA==` |
+
+> 编码步骤:原始字符串 → UTF-8 → Base64 编码 → URL 编码(作为表单参数提交)
+
+### 3.4 请求示例
+
+#### 表单 Body(OK 件,完整参数)
+
+```
+__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&workNum=system&craft=100000&result=OK&cxm=PROGRAM_01&testPressure=MjAw&testPressureUnit=Pa&leakVal=NQ==&leakValUnit=Pa&testTime=MjAyNC0wNS0xNiAxMjoxMjowMA==&deviceType=QM001&remark=
+```
+
+#### 带注释的完整参数示例
+
+```
+__ajax=json          # JeeSite 返回 JSON(建议)
+&sn=501901660045119990G4JB100071   # 工件码(必填)
+&oprno=OP450         # 工位号(必填)
+&lineSn=XT           # 产线编号(必填)
+&craft=100000        # 工艺号,工位总结果固定传 100000(强烈建议)
+&result=OK           # 测试结果 OK/NG(必填,大写)
+&workNum=system      # 操作员工号(建议)
+&cxm=PROGRAM_01      # 气密程序号(可选)
+&deviceType=QM001    # 设备类型/编号(可选)
+&testPressure=MjAw   # 测试压 200,Base64 编码(可选)
+&testPressureUnit=Pa # 测试压单位(可选)
+&leakVal=NQ==        # 泄漏值 5,Base64 编码(可选)
+&leakValUnit=Pa      # 泄漏值单位(可选)
+&testTime=MjAyNC0wNS0xNiAxMjoxMjowMA==  # 测试时间,Base64 编码(可选)
+&remark=             # 备注(可选)
+```
+
+#### NG 件最小请求(仅必填项)
+
+```
+__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&craft=100000&result=NG&workNum=system
+```
+
+#### 原始 HTTP 报文
+
+```http
+POST /js/a/mes/mesProductRecord/qmresult HTTP/1.1
+Host: 192.168.16.99:8980
+Content-Type: application/x-www-form-urlencoded
+
+__ajax=json&sn=501901660045119990G4JB100071&oprno=OP450&lineSn=XT&workNum=system&craft=100000&result=OK&cxm=PROGRAM_01&testPressure=MjAw&testPressureUnit=Pa&leakVal=NQ==&leakValUnit=Pa&testTime=MjAyNC0wNS0xNiAxMjoxMjowMA==&deviceType=QM001
+```
+
+#### cURL
+
+```bash
+curl -X POST "http://192.168.16.99:8980/js/a/mes/mesProductRecord/qmresult" \
+  -H "Content-Type: application/x-www-form-urlencoded" \
+  --data-urlencode "__ajax=json" \
+  --data-urlencode "sn=501901660045119990G4JB100071" \
+  --data-urlencode "oprno=OP450" \
+  --data-urlencode "lineSn=XT" \
+  --data-urlencode "workNum=system" \
+  --data-urlencode "craft=100000" \
+  --data-urlencode "result=OK" \
+  --data-urlencode "cxm=PROGRAM_01" \
+  --data-urlencode "testPressure=MjAw" \
+  --data-urlencode "testPressureUnit=Pa" \
+  --data-urlencode "leakVal=NQ==" \
+  --data-urlencode "leakValUnit=Pa" \
+  --data-urlencode "testTime=MjAyNC0wNS0xNiAxMjoxMjowMA==" \
+  --data-urlencode "deviceType=QM001"
+```
+
+
+
+### 3.5 响应格式
+
+返回 JeeSite 标准 JSON 字符串:
+
+| 字段 | 类型 | 说明 |
+|------|------|------|
+| `result` | String | `"true"` 成功 / `"false"` 失败 |
+| `message` | String | 中文提示信息 |
+
+### 3.6 响应示例
+
+**成功:**
+
+```json
+{
+  "result": "true",
+  "message": "操作成功!"
+}
+```
+
+**参数错误:**
+
+```json
+{
+  "result": "false",
+  "message": "参数错误1!"
+}
+```
+
+**工位未配置:**
+
+```json
+{
+  "result": "false",
+  "message": "子工位未配置!"
+}
+```
+
+**工位结果更新失败(防呆拦截):**
+
+```json
+{
+  "result": "false",
+  "message": "该工件本工位已加工,结果:OK"
+}
+```
+
+---
+
+## 4. 两个接口对比
+
+| 对比项 | qmcheck | qmresult |
+|--------|---------|----------|
+| 用途 | 测试前进站校验 | 测试后上传结果 |
+| 是否写气密明细表 | 否 | 是(`mes_product_qm`) |
+| 是否更新工位总结果 | 否 | 是(`craft=100000`) |
+| 响应结构 | `result` + `message` + `data` | `result` + `message` |
+| 必填参数 | sn, oprno, lineSn | sn, oprno, lineSn, result |
+| 操作员字段 | `workNum` | `workNum` |
+| 特殊校验 | 15 分钟间隔(可配置) | 样件判定、防呆、多次气密 |
+
+---
+
+## 5. 与 pccheck / pcresult 字段差异
+
+| 接口 | 操作员字段 | craft 默认值 | 说明 |
+|------|-----------|-------------|------|
+| pccheck / pcresult | `ucode` | pcresult 服务端默认 `100000` | 通用工位校验/结果 |
+| qmcheck / qmresult | `workNum` | qmresult **需客户端传** `craft=100000` | 气密专用接口 |
+
+---
+
+## 6. 对接注意事项
+
+1. **先 check 后 result**:测试前必须 qmcheck 通过,再调用 qmresult,否则工位结果可能更新失败。  
+2. **craft 固定传 `100000`**:与 MES 工位总结果工艺号一致,不传可能导致工位结果无法正确更新。  
+3. **result 大小写**:固定大写 `OK` / `NG`。  
+4. **Base64 字段**:`testPressure`、`leakVal`、`testTime`、`title` 需 Base64 编码后再 URL 编码提交。  
+5. **testTime 格式**:解码后必须为 `yyyy-MM-dd HH:mm:ss`,解析失败则入库为 null。  
+6. **15 分钟间隔**:由配置项 `mes.qm.ng.time=1` 控制,未开启时不校验。  
+7. **URL 编码**:工件码含 `-`、空格等特殊字符时必须 URL 编码。  
+8. **失败重试**:qmresult 失败时根据 `message` 判断原因,不建议盲目重试(可能重复写入气密明细)。
+
+---
+
+## 7. 联调检查清单
+
+- [ ] qmcheck:正常工件返回 `result=true, data=UD`
+- [ ] qmcheck:已加工工件返回 `result=false` 及对应 message
+- [ ] qmcheck:15 分钟内重复测试被拦截(如已开启配置)
+- [ ] qmresult:OK 件上传成功,MES 可查气密明细 + 工位总结果 OK
+- [ ] qmresult:NG 件上传成功,工位总结果 NG
+- [ ] qmresult:缺少 sn/oprno/lineSn/result 返回参数错误
+- [ ] Base64 字段解码后数值、时间正确
+- [ ] 无登录/token 可正常访问
+
+---
+
+## 8. 参考源码
+
+| 内容 | 路径 |
+|------|------|
+| 接口实现 | `src/main/java/com/jeesite/modules/mes/web/MesProductRecordController.java` |
+| 请求 DTO | `src/main/java/com/jeesite/modules/mes/req/MesProductQmReq.java` |
+| 响应对象 | `src/main/java/com/jeesite/modules/mes/resp/CommonResp.java` |
+| 错误码文案 | `src/main/java/com/jeesite/modules/mes/utils/ErrorMsg.java` |
+| 匿名访问配置 | `src/main/resources/config/application.yml` |
+| 本地测试示例 | `api.http`(约 318~349 行) |

二進制
docs/气密工位MES对接说明(设备方).docx


文件差異過大導致無法顯示
+ 280 - 513
mes_cloud_199_0y7a.sql


+ 14 - 0
src/main/java/com/jeesite/modules/mes/dao/MesProductRivetDao.java

@@ -0,0 +1,14 @@
+package com.jeesite.modules.mes.dao;
+
+import com.jeesite.common.dao.CrudDao;
+import com.jeesite.common.mybatis.annotation.MyBatisDao;
+import com.jeesite.modules.mes.entity.MesProductRivet;
+
+/**
+ * 套筒压铆过程参数DAO
+ */
+@MyBatisDao
+public interface MesProductRivetDao extends CrudDao<MesProductRivet> {
+
+	MesProductRivet findByUniqueKey(MesProductRivet query);
+}

+ 159 - 0
src/main/java/com/jeesite/modules/mes/entity/MesProductRivet.java

@@ -0,0 +1,159 @@
+package com.jeesite.modules.mes.entity;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.jeesite.common.entity.DataEntity;
+import com.jeesite.common.mybatis.annotation.Column;
+import com.jeesite.common.mybatis.annotation.Table;
+import com.jeesite.common.mybatis.mapper.query.QueryType;
+import com.jeesite.common.utils.excel.annotation.ExcelField;
+import com.jeesite.common.utils.excel.annotation.ExcelField.Align;
+import com.jeesite.common.utils.excel.annotation.ExcelFields;
+
+import javax.validation.constraints.Size;
+import java.util.Date;
+
+/**
+ * 套筒压铆过程参数Entity
+ */
+@Table(name = "mes_product_rivet", alias = "a", label = "套筒压铆过程参数", columns = {
+		@Column(name = "id", attrName = "id", label = "id", isPK = true),
+		@Column(name = "sn", attrName = "sn", label = "工件码", queryType = QueryType.LIKE),
+		@Column(name = "line_sn", attrName = "lineSn", label = "产线编号"),
+		@Column(name = "oprno", attrName = "oprno", label = "工位号"),
+		@Column(name = "point_no", attrName = "pointNo", label = "铆接点序号"),
+		@Column(name = "result", attrName = "result", label = "结果"),
+		@Column(name = "stroke", attrName = "stroke", label = "行程值"),
+		@Column(name = "pressure", attrName = "pressure", label = "压力值"),
+		@Column(name = "hold_time", attrName = "holdTime", label = "保压时间"),
+		@Column(name = "record_time", attrName = "recordTime", label = "采集时间", isUpdateForce = true),
+		@Column(name = "create_by", attrName = "createBy", label = "上传人", isUpdate = false),
+		@Column(name = "create_date", attrName = "createDate", label = "上传时间", isUpdate = false, isUpdateForce = true),
+		@Column(name = "update_by", attrName = "updateBy", label = "更新人"),
+		@Column(name = "update_date", attrName = "updateDate", label = "更新时间", isUpdateForce = true),
+}, orderBy = "a.create_date DESC")
+public class MesProductRivet extends DataEntity<MesProductRivet> {
+
+	private static final long serialVersionUID = 1L;
+	private String sn;
+	private String lineSn;
+	private String oprno;
+	private Integer pointNo;
+	private String result;
+	private Double stroke;
+	private Double pressure;
+	private Integer holdTime;
+	private Date recordTime;
+
+	@ExcelFields({
+			@ExcelField(title = "工件码", attrName = "sn", align = Align.CENTER, sort = 20),
+			@ExcelField(title = "产线编号", attrName = "lineSn", align = Align.CENTER, sort = 30),
+			@ExcelField(title = "工位号", attrName = "oprno", align = Align.CENTER, sort = 40),
+			@ExcelField(title = "铆接点", attrName = "pointNo", align = Align.CENTER, sort = 50),
+			@ExcelField(title = "结果", attrName = "result", align = Align.CENTER, sort = 60),
+			@ExcelField(title = "行程值", attrName = "stroke", align = Align.CENTER, sort = 70),
+			@ExcelField(title = "压力值", attrName = "pressure", align = Align.CENTER, sort = 80),
+			@ExcelField(title = "保压时间", attrName = "holdTime", align = Align.CENTER, sort = 90),
+			@ExcelField(title = "上传人", attrName = "createBy", align = Align.CENTER, sort = 100),
+			@ExcelField(title = "上传时间", attrName = "createDate", align = Align.CENTER, sort = 110, dataFormat = "yyyy-MM-dd hh:mm"),
+	})
+	public MesProductRivet() {
+		this(null);
+	}
+
+	public MesProductRivet(String id) {
+		super(id);
+	}
+
+	@Size(min = 0, max = 50, message = "工件码长度不能超过 50 个字符")
+	public String getSn() {
+		return sn;
+	}
+
+	public void setSn(String sn) {
+		this.sn = sn;
+	}
+
+	@Size(min = 0, max = 30, message = "产线编号长度不能超过 30 个字符")
+	public String getLineSn() {
+		return lineSn;
+	}
+
+	public void setLineSn(String lineSn) {
+		this.lineSn = lineSn;
+	}
+
+	@Size(min = 0, max = 20, message = "工位号长度不能超过 20 个字符")
+	public String getOprno() {
+		return oprno;
+	}
+
+	public void setOprno(String oprno) {
+		this.oprno = oprno;
+	}
+
+	public Integer getPointNo() {
+		return pointNo;
+	}
+
+	public void setPointNo(Integer pointNo) {
+		this.pointNo = pointNo;
+	}
+
+	@Size(min = 0, max = 4, message = "结果长度不能超过 4 个字符")
+	public String getResult() {
+		return result;
+	}
+
+	public void setResult(String result) {
+		this.result = result;
+	}
+
+	public Double getStroke() {
+		return stroke;
+	}
+
+	public void setStroke(Double stroke) {
+		this.stroke = stroke;
+	}
+
+	public Double getPressure() {
+		return pressure;
+	}
+
+	public void setPressure(Double pressure) {
+		this.pressure = pressure;
+	}
+
+	public Integer getHoldTime() {
+		return holdTime;
+	}
+
+	public void setHoldTime(Integer holdTime) {
+		this.holdTime = holdTime;
+	}
+
+	@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+	public Date getRecordTime() {
+		return recordTime;
+	}
+
+	public void setRecordTime(Date recordTime) {
+		this.recordTime = recordTime;
+	}
+
+	public Date getCreateDate_gte() {
+		return sqlMap.getWhere().getValue("create_date", QueryType.GTE);
+	}
+
+	public void setCreateDate_gte(Date createDate) {
+		sqlMap.getWhere().and("create_date", QueryType.GTE, createDate);
+	}
+
+	public Date getCreateDate_lte() {
+		return sqlMap.getWhere().getValue("create_date", QueryType.LTE);
+	}
+
+	public void setCreateDate_lte(Date createDate) {
+		sqlMap.getWhere().and("create_date", QueryType.LTE, createDate);
+	}
+}

+ 105 - 0
src/main/java/com/jeesite/modules/mes/service/MesProductRivetService.java

@@ -0,0 +1,105 @@
+package com.jeesite.modules.mes.service;
+
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.service.CrudService;
+import com.jeesite.common.service.ServiceException;
+import com.jeesite.modules.mes.dao.MesProductRivetDao;
+import com.jeesite.modules.mes.entity.MesLineProcess;
+import com.jeesite.modules.mes.entity.MesProductRivet;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.ObjectUtils;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 套筒压铆过程参数Service
+ */
+@Service
+public class MesProductRivetService extends CrudService<MesProductRivetDao, MesProductRivet> {
+
+	@Autowired
+	private MesProductRivetDao mesProductRivetDao;
+
+	@Autowired
+	private MesLineProcessService mesLineProcessService;
+
+	@Override
+	public MesProductRivet get(MesProductRivet mesProductRivet) {
+		return super.get(mesProductRivet);
+	}
+
+	@Override
+	public Page<MesProductRivet> findPage(MesProductRivet mesProductRivet) {
+		return super.findPage(mesProductRivet);
+	}
+
+	@Override
+	public List<MesProductRivet> findList(MesProductRivet mesProductRivet) {
+		return super.findList(mesProductRivet);
+	}
+
+	@Override
+	@Transactional
+	public void save(MesProductRivet mesProductRivet) {
+		super.save(mesProductRivet);
+	}
+
+	@Override
+	@Transactional
+	public void delete(MesProductRivet mesProductRivet) {
+		super.delete(mesProductRivet);
+	}
+
+	@Transactional
+	public void batchUpload(String sn, String lineSn, String storageOprno, String oldOprno,
+	                        String userCode, JSONArray paramsArr) {
+		MesLineProcess lineProcess = mesLineProcessService.findInfoByOprnoLineSn(oldOprno, lineSn);
+		if (ObjectUtils.isEmpty(lineProcess)) {
+			throw new ServiceException(text("子工位未配置!"));
+		}
+
+		for (int i = 0; i < paramsArr.size(); i++) {
+			JSONObject obj = paramsArr.getJSONObject(i);
+			int pointNo = obj.getIntValue("pointNo");
+			String result = obj.getString("result");
+			Double stroke = obj.containsKey("stroke") ? obj.getDouble("stroke") : null;
+			Double pressure = obj.containsKey("pressure") ? obj.getDouble("pressure") : null;
+			Integer holdTime = obj.containsKey("holdTime") ? obj.getIntValue("holdTime") : null;
+			saveOrUpdatePoint(sn, lineSn, storageOprno, pointNo, result, stroke, pressure, holdTime, userCode);
+		}
+	}
+
+	private void saveOrUpdatePoint(String sn, String lineSn, String oprno, int pointNo, String result,
+	                               Double stroke, Double pressure, Integer holdTime, String userCode) {
+		MesProductRivet query = new MesProductRivet();
+		query.setSn(sn);
+		query.setLineSn(lineSn);
+		query.setOprno(oprno);
+		query.setPointNo(pointNo);
+		MesProductRivet exist = mesProductRivetDao.findByUniqueKey(query);
+
+		MesProductRivet entity = exist != null ? exist : new MesProductRivet();
+		entity.setSn(sn);
+		entity.setLineSn(lineSn);
+		entity.setOprno(oprno);
+		entity.setPointNo(pointNo);
+		entity.setResult(result);
+		entity.setStroke(stroke);
+		entity.setPressure(pressure);
+		entity.setHoldTime(holdTime);
+		entity.setRecordTime(new Date());
+		if (StringUtils.isNotEmpty(userCode)) {
+			entity.setUpdateBy(userCode);
+			if (exist == null) {
+				entity.setCreateBy(userCode);
+			}
+		}
+		super.save(entity);
+	}
+}

+ 163 - 0
src/main/java/com/jeesite/modules/mes/web/MesProductRivetController.java

@@ -0,0 +1,163 @@
+package com.jeesite.modules.mes.web;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.jeesite.common.config.Global;
+import com.jeesite.common.entity.Page;
+import com.jeesite.common.lang.DateUtils;
+import com.jeesite.common.lang.StringUtils;
+import com.jeesite.common.service.ServiceException;
+import com.jeesite.common.utils.excel.ExcelExport;
+import com.jeesite.common.web.BaseController;
+import com.jeesite.modules.mes.entity.MesProductRivet;
+import com.jeesite.modules.mes.req.MesSearchDateReq;
+import com.jeesite.modules.mes.service.MesLogService;
+import com.jeesite.modules.mes.service.MesProductRivetService;
+import com.jeesite.modules.mes.util.CommonUitl;
+import org.apache.shiro.authz.annotation.RequiresPermissions;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.interceptor.TransactionAspectSupport;
+import org.springframework.ui.Model;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.ModelAttribute;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 套筒压铆过程参数Controller
+ */
+@Controller
+@RequestMapping(value = "${adminPath}/mes/mesProductRivet")
+public class MesProductRivetController extends BaseController {
+
+	@Autowired
+	private MesProductRivetService mesProductRivetService;
+
+	@Autowired
+	private MesLogService mesLogService;
+
+	@ModelAttribute
+	public MesProductRivet get(String id, boolean isNewRecord) {
+		return mesProductRivetService.get(id, isNewRecord);
+	}
+
+	@RequiresPermissions("mes:mesProductRivet:view")
+	@RequestMapping(value = {"list", ""})
+	public String list(MesProductRivet mesProductRivet, Model model) {
+		MesSearchDateReq dateMap = CommonUitl.getSearchDate();
+		mesProductRivet.setCreateDate_gte(dateMap.getStartDate());
+		mesProductRivet.setCreateDate_lte(dateMap.getEndtDate());
+		model.addAttribute("mesProductRivet", mesProductRivet);
+		return "modules/mes/mesProductRivetList";
+	}
+
+	@RequiresPermissions("mes:mesProductRivet:view")
+	@RequestMapping(value = "listData")
+	@ResponseBody
+	public Page<MesProductRivet> listData(MesProductRivet mesProductRivet, HttpServletRequest request,
+	                                      HttpServletResponse response) {
+		mesProductRivet.setPage(new Page<>(request, response));
+		return mesProductRivetService.findPage(mesProductRivet);
+	}
+
+	@RequiresPermissions("mes:mesProductRivet:view")
+	@RequestMapping(value = "form")
+	public String form(MesProductRivet mesProductRivet, Model model) {
+		model.addAttribute("mesProductRivet", mesProductRivet);
+		return "modules/mes/mesProductRivetForm";
+	}
+
+	@RequiresPermissions("mes:mesProductRivet:edit")
+	@PostMapping(value = "save")
+	@ResponseBody
+	public String save(@Validated MesProductRivet mesProductRivet) {
+		mesProductRivetService.save(mesProductRivet);
+		return renderResult(Global.TRUE, text("保存套筒压铆过程参数成功!"));
+	}
+
+	@RequiresPermissions("mes:mesProductRivet:view")
+	@RequestMapping(value = "exportData")
+	public void exportData(MesProductRivet mesProductRivet, HttpServletResponse response) {
+		List<MesProductRivet> list = mesProductRivetService.findList(mesProductRivet);
+		String fileName = "套筒压铆过程参数" + DateUtils.getDate("yyyyMMddHHmmss") + ".xlsx";
+		try (ExcelExport ee = new ExcelExport("套筒压铆过程参数", MesProductRivet.class)) {
+			ee.setDataList(list).write(response, fileName);
+		}
+	}
+
+	@RequiresPermissions("mes:mesProductRivet:edit")
+	@RequestMapping(value = "delete")
+	@ResponseBody
+	public String delete(MesProductRivet mesProductRivet) {
+		mesProductRivetService.delete(mesProductRivet);
+		return renderResult(Global.TRUE, text("删除套筒压铆过程参数成功!"));
+	}
+
+	/**
+	 * OP200 客户端批量上传压铆参数
+	 */
+	@PostMapping(value = "batchUpload")
+	@ResponseBody
+	@Transactional
+	public String batchUpload(HttpServletRequest request) {
+		mesLogService.apisave(request);
+
+		String sn = request.getParameter("sn");
+		String lineSn = request.getParameter("lineSn");
+		String oprno = request.getParameter("oprno");
+		String userCode = request.getParameter("ucode");
+		String params = request.getParameter("params");
+
+		if (StringUtils.isEmpty(sn) || StringUtils.isEmpty(lineSn)
+				|| StringUtils.isEmpty(oprno) || StringUtils.isEmpty(params)) {
+			return renderResult(Global.FALSE, text("参数错误!"));
+		}
+
+		JSONArray jsonArr;
+		try {
+			jsonArr = JSON.parseArray(params);
+		} catch (Exception e) {
+			return renderResult(Global.FALSE, text("参数格式错误!"));
+		}
+		if (jsonArr == null || jsonArr.isEmpty()) {
+			return renderResult(Global.FALSE, text("参数错误!"));
+		}
+
+		String storageOprno = oprno.trim();
+		String oldOprno = storageOprno;
+		if (oldOprno.length() == 5) {
+			oldOprno += "A";
+		}
+
+		for (int i = 0; i < jsonArr.size(); i++) {
+			JSONObject obj = jsonArr.getJSONObject(i);
+			int pointNo = obj.getIntValue("pointNo");
+			String result = obj.getString("result");
+			if (pointNo < 1 || pointNo > 7) {
+				return renderResult(Global.FALSE, text("点位序号错误!"));
+			}
+			if (!"OK".equals(result) && !"NG".equals(result)) {
+				return renderResult(Global.FALSE, text("结果错误!"));
+			}
+		}
+
+		try {
+			mesProductRivetService.batchUpload(sn.trim(), lineSn.trim(), storageOprno, oldOprno, userCode, jsonArr);
+			return renderResult(Global.TRUE, text("操作成功!"));
+		} catch (ServiceException e) {
+			return renderResult(Global.FALSE, text(e.getMessage()));
+		} catch (Exception e) {
+			logger.error("batchUpload failed", e);
+			TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
+			return renderResult(Global.FALSE, text("操作失败!"));
+		}
+	}
+}

+ 1 - 0
src/main/resources/config/application.yml

@@ -654,6 +654,7 @@ shiro:
     ${adminPath}/mes/mesElectricTorque/batchSave = anon
     ${adminPath}/mes/mesProductYgsl/batchsave = anon
     ${adminPath}/mes/mesProductHb/upload = anon
+    ${adminPath}/mes/mesProductRivet/batchUpload = anon
     ${adminPath}/mes/mesProductCmt/batchsave = anon
     ${adminPath}/mes/mesDeviceState/add = anon
     ${adminPath}/mes/mesDeviceTime/list2 = anon

+ 18 - 0
src/main/resources/mappings/modules/mes/MesProductRivetDao.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeesite.modules.mes.dao.MesProductRivetDao">
+
+	<select id="findByUniqueKey" resultType="com.jeesite.modules.mes.entity.MesProductRivet">
+		SELECT
+			id, sn, line_sn, oprno, point_no, result,
+			stroke, pressure, hold_time, record_time,
+			create_by, create_date, update_by, update_date
+		FROM mes_product_rivet
+		WHERE sn = #{sn}
+		  AND line_sn = #{lineSn}
+		  AND oprno = #{oprno}
+		  AND point_no = #{pointNo}
+		LIMIT 1
+	</select>
+
+</mapper>

+ 128 - 0
src/main/resources/views/modules/mes/mesProductRivetForm.html

@@ -0,0 +1,128 @@
+<% layout('/layouts/default.html', {title: '套筒压铆过程参数管理', libs: ['validate']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header with-border">
+			<div class="box-title">
+				<i class="fa icon-note"></i> ${text(mesProductRivet.isNewRecord ? '新增套筒压铆过程参数' : '编辑套筒压铆过程参数')}
+			</div>
+			<div class="box-tools pull-right">
+				<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
+			</div>
+		</div>
+		<#form:form id="inputForm" model="${mesProductRivet}" action="${ctx}/mes/mesProductRivet/save" method="post" class="form-horizontal">
+			<div class="box-body">
+				<div class="form-unit">${text('基本信息')}</div>
+				<#form:hidden path="id"/>
+				<div class="row">
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required ">*</span> ${text('工件码')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="sn" maxlength="50" class="form-control required"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required ">*</span> ${text('产线编号')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="lineSn" maxlength="30" class="form-control required"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required ">*</span> ${text('工位号')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="oprno" maxlength="20" class="form-control required"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required ">*</span> ${text('铆接点序号')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="pointNo" maxlength="9" class="form-control required digits"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required ">*</span> ${text('OK/NG')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="result" maxlength="4" class="form-control required"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('行程值')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="stroke" maxlength="8" class="form-control number"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('压力值')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="pressure" maxlength="8" class="form-control number"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('保压时间')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="holdTime" maxlength="9" class="form-control digits"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('采集时间')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="recordTime" readonly="true" maxlength="20" class="form-control laydate"
+									dataFormat="datetime" data-type="datetime" data-format="yyyy-MM-dd HH:mm"/>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="box-footer">
+				<div class="row">
+					<div class="col-sm-offset-2 col-sm-10">
+						<% if (hasPermi('mes:mesProductRivet:edit')){ %>
+							<button type="submit" class="btn btn-sm btn-primary" id="btnSubmit"><i class="fa fa-check"></i> ${text('保 存')}</button>&nbsp;
+						<% } %>
+						<button type="button" class="btn btn-sm btn-default" id="btnCancel" onclick="js.closeCurrentTabPage()"><i class="fa fa-reply-all"></i> ${text('关 闭')}</button>
+					</div>
+				</div>
+			</div>
+		</#form:form>
+	</div>
+</div>
+<% } %>
+<script>
+$("#inputForm").validate({
+	submitHandler: function(form){
+		js.ajaxSubmitForm($(form), function(data){
+			js.showMessage(data.message);
+			if(data.result == Global.TRUE){
+				js.closeCurrentTabPage(function(contentWindow){
+					contentWindow.page();
+				});
+			}
+		}, "json");
+    }
+});
+</script>

+ 100 - 0
src/main/resources/views/modules/mes/mesProductRivetList.html

@@ -0,0 +1,100 @@
+<% layout('/layouts/default.html', {title: '套筒压铆过程参数管理', libs: ['dataGrid']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa icon-notebook"></i> ${text('套筒压铆过程参数管理')}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
+				<a href="#" class="btn btn-default" id="btnExport"><i class="glyphicon glyphicon-export"></i> 导出</a>
+				<a href="#" class="btn btn-default" id="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
+			</div>
+		</div>
+		<div class="box-body">
+			<#form:form id="searchForm" model="${mesProductRivet}" action="${ctx}/mes/mesProductRivet/listData" method="post" class="form-inline hide"
+					data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
+				<div class="form-group">
+					<label class="control-label">${text('工件码')}:</label>
+					<div class="control-inline">
+						<#form:input path="sn" maxlength="50" class="form-control width-260"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('产线编号')}:</label>
+					<div class="control-inline">
+						<#form:input path="lineSn" maxlength="30" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('工位号')}:</label>
+					<div class="control-inline">
+						<#form:input path="oprno" maxlength="20" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('铆接点')}:</label>
+					<div class="control-inline">
+						<#form:input path="pointNo" maxlength="2" class="form-control width-80"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('结果')}:</label>
+					<div class="control-inline">
+						<#form:input path="result" maxlength="4" class="form-control width-80"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('上传人')}:</label>
+					<div class="control-inline">
+						<#form:input path="createBy" maxlength="64" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('上传时间')}:</label>
+					<div class="control-inline">
+						<#form:input path="createDate_gte" readonly="true" maxlength="20" class="form-control laydate width-datetime2"
+							dataFormat="datetime2" data-type="datetime" data-format="yyyy-MM-dd HH:mm:ss" data-done="createDate_lte.click()"/>
+						&nbsp;-&nbsp;
+						<#form:input path="createDate_lte" readonly="true" maxlength="20" class="form-control laydate width-datetime2"
+							dataFormat="datetime2" data-type="datetime" data-format="yyyy-MM-dd HH:mm:ss"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<button type="submit" class="btn btn-primary btn-sm">${text('查询')}</button>
+					<button type="reset" class="btn btn-default btn-sm">${text('重置')}</button>
+				</div>
+			</#form:form>
+			<table id="dataGrid"></table>
+			<div id="dataGridPage"></div>
+		</div>
+	</div>
+</div>
+<% } %>
+<script>
+$('#dataGrid').dataGrid({
+	searchForm: $("#searchForm"),
+	columnModel: [
+		{header:'${text("工件码")}', name:'sn', index:'a.sn', width:320, align:"center", frozen:true},
+		{header:'${text("产线编号")}', name:'lineSn', index:'a.line_sn', width:100, align:"center"},
+		{header:'${text("工位号")}', name:'oprno', index:'a.oprno', width:100, align:"center"},
+		{header:'${text("铆接点")}', name:'pointNo', index:'a.point_no', width:80, align:"center"},
+		{header:'${text("结果")}', name:'result', index:'a.result', width:80, align:"center"},
+		{header:'${text("行程值")}', name:'stroke', index:'a.stroke', width:100, align:"center"},
+		{header:'${text("压力值")}', name:'pressure', index:'a.pressure', width:100, align:"center"},
+		{header:'${text("保压时间")}', name:'holdTime', index:'a.hold_time', width:100, align:"center"},
+		{header:'${text("上传人")}', name:'createBy', index:'a.create_by', width:120, align:"center"},
+		{header:'${text("上传时间")}', name:'createDate', index:'a.create_date', width:150, align:"center"}
+	],
+	frozenCols: true,
+	ajaxSuccess: function(data){}
+});
+</script>
+<script>
+$('#btnExport').click(function(){
+	js.ajaxSubmitForm($('#searchForm'), {
+		url:'${ctx}/mes/mesProductRivet/exportData',
+		downloadFile:true
+	});
+});
+</script>

二進制
src/main/webapp/WEB-INF/classes/com/jeesite/modules/mes/dao/MesProductRivetDao.class


二進制
src/main/webapp/WEB-INF/classes/com/jeesite/modules/mes/entity/MesProductRivet.class


二進制
src/main/webapp/WEB-INF/classes/com/jeesite/modules/mes/service/MesProductRivetService.class


二進制
src/main/webapp/WEB-INF/classes/com/jeesite/modules/mes/web/MesProductRivetController.class


+ 2 - 1
src/main/webapp/WEB-INF/classes/config/application.yml

@@ -567,7 +567,7 @@ shiro:
   # 记住我密钥设置,你可以通过 com.jeesite.test.RememberMeKeyGen 类快速生成一个秘钥。
   # 若不设置,则每次启动系统后自动生成一个新秘钥,这样会导致每次重启后,客户端记录的用户信息将失效。
   rememberMe:
-    secretKey: +w9srzXus5LhjBoOzEI5SQ==
+    secretKey: gIGwjNiyB+V3hS5CuhZwOw==
 
 #  # 指定获取客户端IP的Header名称,防止IP伪造。指定为空,则使用原生方法获取IP。
 #  remoteAddrHeaderName: X-Forwarded-For
@@ -654,6 +654,7 @@ shiro:
     ${adminPath}/mes/mesElectricTorque/batchSave = anon
     ${adminPath}/mes/mesProductYgsl/batchsave = anon
     ${adminPath}/mes/mesProductHb/upload = anon
+    ${adminPath}/mes/mesProductRivet/batchUpload = anon
     ${adminPath}/mes/mesProductCmt/batchsave = anon
     ${adminPath}/mes/mesDeviceState/add = anon
     ${adminPath}/mes/mesDeviceTime/list2 = anon

二進制
src/main/webapp/WEB-INF/classes/logs/debug.2026-06-08.0.log.zip


二進制
src/main/webapp/WEB-INF/classes/logs/debug.2026-06-09.0.log.zip


二進制
src/main/webapp/WEB-INF/classes/logs/debug.2026-06-10.0.log.zip


文件差異過大導致無法顯示
+ 671 - 11565
src/main/webapp/WEB-INF/classes/logs/debug.log


+ 18 - 0
src/main/webapp/WEB-INF/classes/mappings/modules/mes/MesProductRivetDao.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.jeesite.modules.mes.dao.MesProductRivetDao">
+
+	<select id="findByUniqueKey" resultType="com.jeesite.modules.mes.entity.MesProductRivet">
+		SELECT
+			id, sn, line_sn, oprno, point_no, result,
+			stroke, pressure, hold_time, record_time,
+			create_by, create_date, update_by, update_date
+		FROM mes_product_rivet
+		WHERE sn = #{sn}
+		  AND line_sn = #{lineSn}
+		  AND oprno = #{oprno}
+		  AND point_no = #{pointNo}
+		LIMIT 1
+	</select>
+
+</mapper>

+ 128 - 0
src/main/webapp/WEB-INF/classes/views/modules/mes/mesProductRivetForm.html

@@ -0,0 +1,128 @@
+<% layout('/layouts/default.html', {title: '套筒压铆过程参数管理', libs: ['validate']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header with-border">
+			<div class="box-title">
+				<i class="fa icon-note"></i> ${text(mesProductRivet.isNewRecord ? '新增套筒压铆过程参数' : '编辑套筒压铆过程参数')}
+			</div>
+			<div class="box-tools pull-right">
+				<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
+			</div>
+		</div>
+		<#form:form id="inputForm" model="${mesProductRivet}" action="${ctx}/mes/mesProductRivet/save" method="post" class="form-horizontal">
+			<div class="box-body">
+				<div class="form-unit">${text('基本信息')}</div>
+				<#form:hidden path="id"/>
+				<div class="row">
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required ">*</span> ${text('工件码')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="sn" maxlength="50" class="form-control required"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required ">*</span> ${text('产线编号')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="lineSn" maxlength="30" class="form-control required"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required ">*</span> ${text('工位号')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="oprno" maxlength="20" class="form-control required"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required ">*</span> ${text('铆接点序号')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="pointNo" maxlength="9" class="form-control required digits"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required ">*</span> ${text('OK/NG')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="result" maxlength="4" class="form-control required"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('行程值')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="stroke" maxlength="8" class="form-control number"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('压力值')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="pressure" maxlength="8" class="form-control number"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('保压时间')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="holdTime" maxlength="9" class="form-control digits"/>
+							</div>
+						</div>
+					</div>
+					<div class="col-xs-6">
+						<div class="form-group">
+							<label class="control-label col-sm-4" title="">
+								<span class="required hide">*</span> ${text('采集时间')}:<i class="fa icon-question hide"></i></label>
+							<div class="col-sm-8">
+								<#form:input path="recordTime" readonly="true" maxlength="20" class="form-control laydate"
+									dataFormat="datetime" data-type="datetime" data-format="yyyy-MM-dd HH:mm"/>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+			<div class="box-footer">
+				<div class="row">
+					<div class="col-sm-offset-2 col-sm-10">
+						<% if (hasPermi('mes:mesProductRivet:edit')){ %>
+							<button type="submit" class="btn btn-sm btn-primary" id="btnSubmit"><i class="fa fa-check"></i> ${text('保 存')}</button>&nbsp;
+						<% } %>
+						<button type="button" class="btn btn-sm btn-default" id="btnCancel" onclick="js.closeCurrentTabPage()"><i class="fa fa-reply-all"></i> ${text('关 闭')}</button>
+					</div>
+				</div>
+			</div>
+		</#form:form>
+	</div>
+</div>
+<% } %>
+<script>
+$("#inputForm").validate({
+	submitHandler: function(form){
+		js.ajaxSubmitForm($(form), function(data){
+			js.showMessage(data.message);
+			if(data.result == Global.TRUE){
+				js.closeCurrentTabPage(function(contentWindow){
+					contentWindow.page();
+				});
+			}
+		}, "json");
+    }
+});
+</script>

+ 100 - 0
src/main/webapp/WEB-INF/classes/views/modules/mes/mesProductRivetList.html

@@ -0,0 +1,100 @@
+<% layout('/layouts/default.html', {title: '套筒压铆过程参数管理', libs: ['dataGrid']}){ %>
+<div class="main-content">
+	<div class="box box-main">
+		<div class="box-header">
+			<div class="box-title">
+				<i class="fa icon-notebook"></i> ${text('套筒压铆过程参数管理')}
+			</div>
+			<div class="box-tools pull-right">
+				<a href="#" class="btn btn-default" id="btnSearch" title="${text('查询')}"><i class="fa fa-filter"></i> ${text('查询')}</a>
+				<a href="#" class="btn btn-default" id="btnExport"><i class="glyphicon glyphicon-export"></i> 导出</a>
+				<a href="#" class="btn btn-default" id="btnSetting" title="${text('设置')}"><i class="fa fa-navicon"></i></a>
+			</div>
+		</div>
+		<div class="box-body">
+			<#form:form id="searchForm" model="${mesProductRivet}" action="${ctx}/mes/mesProductRivet/listData" method="post" class="form-inline hide"
+					data-page-no="${parameter.pageNo}" data-page-size="${parameter.pageSize}" data-order-by="${parameter.orderBy}">
+				<div class="form-group">
+					<label class="control-label">${text('工件码')}:</label>
+					<div class="control-inline">
+						<#form:input path="sn" maxlength="50" class="form-control width-260"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('产线编号')}:</label>
+					<div class="control-inline">
+						<#form:input path="lineSn" maxlength="30" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('工位号')}:</label>
+					<div class="control-inline">
+						<#form:input path="oprno" maxlength="20" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('铆接点')}:</label>
+					<div class="control-inline">
+						<#form:input path="pointNo" maxlength="2" class="form-control width-80"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('结果')}:</label>
+					<div class="control-inline">
+						<#form:input path="result" maxlength="4" class="form-control width-80"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('上传人')}:</label>
+					<div class="control-inline">
+						<#form:input path="createBy" maxlength="64" class="form-control width-120"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label">${text('上传时间')}:</label>
+					<div class="control-inline">
+						<#form:input path="createDate_gte" readonly="true" maxlength="20" class="form-control laydate width-datetime2"
+							dataFormat="datetime2" data-type="datetime" data-format="yyyy-MM-dd HH:mm:ss" data-done="createDate_lte.click()"/>
+						&nbsp;-&nbsp;
+						<#form:input path="createDate_lte" readonly="true" maxlength="20" class="form-control laydate width-datetime2"
+							dataFormat="datetime2" data-type="datetime" data-format="yyyy-MM-dd HH:mm:ss"/>
+					</div>
+				</div>
+				<div class="form-group">
+					<button type="submit" class="btn btn-primary btn-sm">${text('查询')}</button>
+					<button type="reset" class="btn btn-default btn-sm">${text('重置')}</button>
+				</div>
+			</#form:form>
+			<table id="dataGrid"></table>
+			<div id="dataGridPage"></div>
+		</div>
+	</div>
+</div>
+<% } %>
+<script>
+$('#dataGrid').dataGrid({
+	searchForm: $("#searchForm"),
+	columnModel: [
+		{header:'${text("工件码")}', name:'sn', index:'a.sn', width:320, align:"center", frozen:true},
+		{header:'${text("产线编号")}', name:'lineSn', index:'a.line_sn', width:100, align:"center"},
+		{header:'${text("工位号")}', name:'oprno', index:'a.oprno', width:100, align:"center"},
+		{header:'${text("铆接点")}', name:'pointNo', index:'a.point_no', width:80, align:"center"},
+		{header:'${text("结果")}', name:'result', index:'a.result', width:80, align:"center"},
+		{header:'${text("行程值")}', name:'stroke', index:'a.stroke', width:100, align:"center"},
+		{header:'${text("压力值")}', name:'pressure', index:'a.pressure', width:100, align:"center"},
+		{header:'${text("保压时间")}', name:'holdTime', index:'a.hold_time', width:100, align:"center"},
+		{header:'${text("上传人")}', name:'createBy', index:'a.create_by', width:120, align:"center"},
+		{header:'${text("上传时间")}', name:'createDate', index:'a.create_date', width:150, align:"center"}
+	],
+	frozenCols: true,
+	ajaxSuccess: function(data){}
+});
+</script>
+<script>
+$('#btnExport').click(function(){
+	js.ajaxSubmitForm($('#searchForm'), {
+		url:'${ctx}/mes/mesProductRivet/exportData',
+		downloadFile:true
+	});
+});
+</script>

文件差異過大導致無法顯示
+ 341 - 341
target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst


二進制
target/test-classes/com/jeesite/test/InitData.class


二進制
target/test-classes/com/jeesite/test/InsertBatchTest.class


二進制
target/test-classes/com/jeesite/test/MultiDataSourceTest$1.class


二進制
target/test-classes/com/jeesite/test/MultiDataSourceTest.class


二進制
target/test-classes/com/jeesite/test/RememberMeKeyGen.class