fix(api): 修复全量接口扫描发现的 500 —— 由 70 个降到 0 个
基于 /v3/api-docs 自动发现 4173 个接口做全量探测(scripts/api-500-probe/probe_500.py),
第一轮 70 个返回 500,逐一定位修复后复测为 0。
1. 导出类接口(Excel 写不出去)
- 新增 LocalTimeConvert:FastExcel 不支持 LocalTime,班次/考勤规则导出直接 500
- 质检方案 types(List<Integer>)、生产任务 pauseRecords/assigneeUserIds/assigneeUserNames
改用 JsonConvert 承载,避免 Can not find 'Converter' support class List
2. 归档台账导出(MybatisPlusException: can't found IPage for args!)
- exportArchiveExcel 传 Page 为 null,改为按 pageNo/pageSize 构造 Page
3. BPM 流程定义
- page:sort 为 null 时 Comparator.comparing 抛 NPE,改用 nullsLast
- get:id 与 key 都为空时给出业务提示,不再让 Flowable 抛 key is null
4. 文件下载(CacheLoader.InvalidCacheLoadException)
- configId 无效时按 404 返回,不再是 500
5. 全局异常处理
- MultipartException(导入接口未传文件)→ 400
- 其它 HttpMessageNotReadableException(报文结构不匹配)→ 400
- buildExceptionLog 对 NOT NULL 列做空值兜底,避免异常日志本身插入失败
6. 请求体缺少必填校验(空请求体直接打到 DB 报 Field 'xxx' doesn't have a default value)
- MDM 物料/计量单位/品牌/分类/仓库 SaveReqVO 加 @NotBlank
- MES 资料审核 SaveReqVO 加 @NotBlank/@NotNull
- BI 图表配置/数据源配置 DO 加 @NotBlank/@NotNull
- 示例 demo03 course/grade DO 加 @NotNull
已完成第二轮验证:4173 个接口中 500 数量为 0;导出类接口实际返回 xlsx 字节流。
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | #!/usr/bin/env python3 |
| | | # -*- coding: utf-8 -*- |
| | | """ä»å®¹å¨æ¥å¿éææ¯ä¸ª 500 å¼å¸¸æ å°å°æ¥å£ + é¦ä¸ªä¸å¡æ 帧""" |
| | | import re, sys, collections |
| | | |
| | | path = sys.argv[1] |
| | | cur_url = "" |
| | | records = [] |
| | | buf = None |
| | | with open(path, encoding="utf-8", errors="replace") as f: |
| | | for line in f: |
| | | line = re.sub(r"\x1b\[[0-9;]*m", "", line.rstrip("\n")) |
| | | m = re.search(r"\[preHandle\]\[å¼å§è¯·æ± URL\(([^)]+)\)", line) |
| | | if m: |
| | | cur_url = m.group(1) |
| | | continue |
| | | if "defaultExceptionHandler" in line: |
| | | buf = {"url": cur_url, "exc": "", "frames": []} |
| | | records.append(buf) |
| | | continue |
| | | if buf is not None: |
| | | if not buf["exc"] and re.match(r"^[a-zA-Z].*(Exception|Error)", line): |
| | | buf["exc"] = line.strip()[:200] |
| | | elif line.startswith("\tat cn.iocoder") and len(buf["frames"]) < 3: |
| | | buf["frames"].append(line.strip()[:180]) |
| | | if line.startswith("\tat ") and len(buf["frames"]) >= 3: |
| | | buf = None |
| | | |
| | | agg = collections.OrderedDict() |
| | | for r in records: |
| | | key = r["url"].split("?")[0] |
| | | agg.setdefault(key, {"exc": r["exc"], "frame": (r["frames"][0] if r["frames"] else ""), "n": 0}) |
| | | agg[key]["n"] += 1 |
| | | |
| | | for url, v in agg.items(): |
| | | print("%s\n EXC: %s\n FRAME: %s (x%d)" % (url, v["exc"], v["frame"], v["n"])) |
| | | print("URLS", len(agg)) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | #!/usr/bin/env python3 |
| | | # -*- coding: utf-8 -*- |
| | | """对 DataIntegrityViolationException æåå
·ä½ SQL ä¸çº¦æå""" |
| | | import re, subprocess, sys |
| | | text = subprocess.run(["docker", "logs", "--since", "15m", "mom-kyzn"], |
| | | stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=180).stdout.decode("utf-8", "replace") |
| | | text = re.sub(r"\x1b\[[0-9;]*m", "", text) |
| | | lines = text.split("\n") |
| | | i = 0 |
| | | cur = "" |
| | | out = [] |
| | | while i < len(lines): |
| | | m = re.search(r"\[preHandle\]\[å¼å§è¯·æ± URL\(([^)]+)\)", lines[i]) |
| | | if m: |
| | | cur = m.group(1).split("?")[0] |
| | | if "DataIntegrityViolationException" in lines[i] or "SQLIntegrityConstraintViolationException" in lines[i] or "doesn't have a default value" in lines[i] or "cannot be null" in lines[i]: |
| | | ctx = [] |
| | | for l in lines[max(0, i-3): i+6]: |
| | | if "INSERT INTO" in l or "doesn't have a default value" in l or "cannot be null" in l or "Data too long" in l or "Duplicate entry" in l or "Field " in l: |
| | | ctx.append(l.strip()[:300]) |
| | | if ctx: |
| | | out.append((cur, ctx)) |
| | | i += 1 |
| | | seen = set() |
| | | for cur, ctx in out: |
| | | key = cur |
| | | if key in seen: |
| | | continue |
| | | seen.add(key) |
| | | print("###", cur) |
| | | for c in ctx[:3]: |
| | | print(" ", c) |
| | | print("TOTAL", len(out), "UNIQUE", len(seen)) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import re, subprocess |
| | | t = subprocess.run(["docker","logs","--since","30m","mom-kyzn"],stdout=subprocess.PIPE,stderr=subprocess.STDOUT,timeout=180).stdout.decode("utf-8","replace") |
| | | t = re.sub(r"\x1b\[[0-9;]*m","",t) |
| | | ls = t.split("\n") |
| | | cur="" |
| | | for i,l in enumerate(ls): |
| | | m = re.search(r"\[preHandle\]\[å¼å§è¯·æ± URL\(([^)]+)\)", l) |
| | | if m: cur=m.group(1).split("?")[0] |
| | | if "defaultExceptionHandler" in l: |
| | | for l2 in ls[i+1:i+60]: |
| | | if re.match(r"^[a-zA-Z].*?(Exception|Error)", l2): |
| | | print(cur, "|", l2.strip()[:150]); break |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import re, subprocess |
| | | text = subprocess.run(["docker","logs","--since","30m","mom-kyzn"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=180).stdout.decode("utf-8","replace") |
| | | text = re.sub(r"\x1b\[[0-9;]*m","",text) |
| | | lines = text.split("\n") |
| | | want = ["HttpMessageNotWritableException","IORuntimeException","InvalidCacheLoadException","MybatisPlusException","NullPointerException","FlowableIllegalArgumentException","HttpMessageNotReadableException"] |
| | | cur="" |
| | | res={} |
| | | for i,l in enumerate(lines): |
| | | m = re.search(r"\[preHandle\]\[å¼å§è¯·æ± URL\(([^)]+)\)", l) |
| | | if m: cur = m.group(1).split("?")[0] |
| | | for w in want: |
| | | if w in l: |
| | | res.setdefault(w, set()).add(cur) |
| | | for w in want: |
| | | if w in res: |
| | | print("##", w) |
| | | for u in sorted(res[w]): |
| | | print(" ", u) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | #!/usr/bin/env python3 |
| | | # -*- coding: utf-8 -*- |
| | | """康æ mom-kyzn å
¨éæ¥å£ 500 æ«æï¼åºäº /v3/api-docs èªå¨åç°ï¼""" |
| | | import json, sys, re, time, urllib.request, urllib.error, concurrent.futures |
| | | |
| | | BASE = "http://127.0.0.1:9007" |
| | | TOKEN = sys.argv[1] |
| | | TENANT = "1" |
| | | |
| | | def load_doc(url): |
| | | req = urllib.request.Request(url, headers={"Accept": "application/json"}) |
| | | with urllib.request.urlopen(req, timeout=120) as r: |
| | | return json.loads(r.read().decode("utf-8")) |
| | | |
| | | def collect(): |
| | | cfg = load_doc(BASE + "/v3/api-docs/swagger-config") |
| | | ops = [] |
| | | seen = set() |
| | | for u in cfg.get("urls", []): |
| | | try: |
| | | doc = load_doc(BASE + u["url"]) |
| | | except Exception as e: |
| | | print("SKIP doc", u["url"], e) |
| | | continue |
| | | paths = doc.get("paths", {}) |
| | | for path, methods in paths.items(): |
| | | for m, op in methods.items(): |
| | | if m.lower() not in ("get", "post", "put", "delete"): |
| | | continue |
| | | if path in seen: |
| | | continue |
| | | seen.add(path + " " + m) |
| | | ops.append((m.upper(), path, op.get("operationId", ""))) |
| | | return ops |
| | | |
| | | def call(op): |
| | | m, path, oid = op |
| | | # æ¿æ¢è·¯å¾åæ°ä¸º 1 |
| | | real = re.sub(r"\{[^/}]+\}", "1", path) |
| | | url = BASE + real |
| | | data = None |
| | | headers = {"Authorization": "Bearer " + TOKEN, "tenant-id": TENANT} |
| | | if m in ("POST", "PUT"): |
| | | data = b"{}" |
| | | headers["Content-Type"] = "application/json" |
| | | req = urllib.request.Request(url, data=data, headers=headers, method=m) |
| | | t0 = time.time() |
| | | try: |
| | | with urllib.request.urlopen(req, timeout=60) as r: |
| | | body = r.read(4000).decode("utf-8", "replace") |
| | | code = r.status |
| | | except urllib.error.HTTPError as e: |
| | | body = e.read(4000).decode("utf-8", "replace") |
| | | code = e.code |
| | | except Exception as e: |
| | | return (m, path, oid, "EXC", str(e)[:120], int((time.time()-t0)*1000)) |
| | | try: |
| | | j = json.loads(body) |
| | | biz = j.get("code") |
| | | msg = (j.get("msg") or "")[:160] |
| | | except Exception: |
| | | biz, msg = None, body[:160] |
| | | return (m, path, oid, biz if biz is not None else code, msg, int((time.time()-t0)*1000)) |
| | | |
| | | def main(): |
| | | ops = collect() |
| | | print("TOTAL_OPS", len(ops), flush=True) |
| | | results = [] |
| | | with concurrent.futures.ThreadPoolExecutor(max_workers=8) as ex: |
| | | for r in ex.map(call, ops): |
| | | results.append(r) |
| | | bad500 = [r for r in results if r[3] == 500 or r[3] == "EXC"] |
| | | others = {} |
| | | for r in results: |
| | | others[str(r[3])] = others.get(str(r[3]), 0) + 1 |
| | | print("STATS", json.dumps(others, ensure_ascii=False), flush=True) |
| | | print("===== 500 LIST =====", flush=True) |
| | | for r in sorted(bad500, key=lambda x: x[1]): |
| | | print("%s %s | op=%s | code=%s | %s | %sms" % r, flush=True) |
| | | print("500_COUNT", len(bad500), flush=True) |
| | | with open("/tmp/kyzn_probe_all.json", "w") as f: |
| | | json.dump([{"method": r[0], "path": r[1], "op": r[2], "code": r[3], "msg": r[4], "ms": r[5]} for r in results], f, ensure_ascii=False, indent=1) |
| | | |
| | | main() |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | #!/usr/bin/env python3 |
| | | # -*- coding: utf-8 -*- |
| | | """对 500 æ¥å£éä¸ªéæ¾ï¼å¹¶ç²¾ç¡®æå对åºçå¼å¸¸ç±»åä¸ä¸å¡æ 帧""" |
| | | import json, re, subprocess, sys, time, urllib.request, urllib.error |
| | | from datetime import datetime |
| | | |
| | | BASE = "http://127.0.0.1:9007" |
| | | TOKEN = sys.argv[1] |
| | | |
| | | data = json.load(open("/tmp/kyzn_probe_all.json")) |
| | | targets = [] |
| | | seen = set() |
| | | for r in data: |
| | | if r["code"] != 500: |
| | | continue |
| | | key = (r["method"], r["path"]) |
| | | if key in seen: |
| | | continue |
| | | seen.add(key) |
| | | targets.append(key) |
| | | |
| | | def call(m, path): |
| | | real = re.sub(r"\{[^/}]+\}", "1", path) |
| | | headers = {"Authorization": "Bearer " + TOKEN, "tenant-id": "1"} |
| | | body = None |
| | | if m in ("POST", "PUT"): |
| | | body = b"{}" |
| | | headers["Content-Type"] = "application/json" |
| | | req = urllib.request.Request(BASE + real, data=body, headers=headers, method=m) |
| | | try: |
| | | with urllib.request.urlopen(req, timeout=60) as r: |
| | | return json.loads(r.read(3000).decode("utf-8", "replace")).get("code") |
| | | except urllib.error.HTTPError as e: |
| | | try: |
| | | return json.loads(e.read(3000).decode("utf-8", "replace")).get("code") |
| | | except Exception: |
| | | return e.code |
| | | except Exception as e: |
| | | return "EXC:" + str(e)[:60] |
| | | |
| | | def exception_since(ts): |
| | | out = subprocess.run(["docker", "logs", "--since", ts, "mom-kyzn"], |
| | | stdout=subprocess.PIPE, stderr=subprocess.STDOUT, timeout=120) |
| | | text = re.sub(r"\x1b\[[0-9;]*m", "", out.stdout.decode("utf-8", "replace")) |
| | | lines = text.split("\n") |
| | | exc = "" |
| | | frames = [] |
| | | for i, l in enumerate(lines): |
| | | if "defaultExceptionHandler" in l: |
| | | for l2 in lines[i+1:i+400]: |
| | | if not exc and re.match(r"^[a-zA-Z].*?(Exception|Error)", l2): |
| | | exc = l2.strip()[:180] |
| | | elif l2.startswith("\tat cn.iocoder") and len(frames) < 2: |
| | | frames.append(l2.strip()[:150]) |
| | | if len(frames) >= 2 and exc: |
| | | break |
| | | if exc: |
| | | break |
| | | return exc, frames |
| | | |
| | | results = [] |
| | | for m, path in targets: |
| | | ts = datetime.now().strftime("%Y-%m-%dT%H:%M:%S") |
| | | time.sleep(0.2) |
| | | code = call(m, path) |
| | | time.sleep(0.6) |
| | | exc, frames = exception_since(ts) |
| | | results.append({"method": m, "path": path, "code": code, "exc": exc, "frames": frames}) |
| | | print("%-58s %-6s %s" % (path, code, exc), flush=True) |
| | | |
| | | json.dump(results, open("/tmp/kyzn_500_detail.json", "w"), ensure_ascii=False, indent=1) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | import json, re, subprocess, sys, time, urllib.request, urllib.error |
| | | from datetime import datetime |
| | | BASE="http://127.0.0.1:9007"; TOKEN=sys.argv[1] |
| | | targets = [l.split() for l in sys.stdin.read().strip().split("\n") if l.strip()] |
| | | def call(m,p): |
| | | real = re.sub(r"\{[^/}]+\}","1",p) |
| | | h={"Authorization":"Bearer "+TOKEN,"tenant-id":"1"}; b=None |
| | | if m in ("POST","PUT"): |
| | | b=b"{}"; h["Content-Type"]="application/json" |
| | | req=urllib.request.Request(BASE+real,data=b,headers=h,method=m) |
| | | try: |
| | | with urllib.request.urlopen(req,timeout=60) as r: |
| | | raw=r.read(3000).decode("utf-8","replace") |
| | | except urllib.error.HTTPError as e: |
| | | raw=e.read(3000).decode("utf-8","replace") |
| | | except Exception as e: |
| | | return "EXC "+str(e)[:80] |
| | | try: |
| | | j=json.loads(raw); return "code=%s msg=%s" % (j.get("code"), (j.get("msg") or "")[:120]) |
| | | except Exception: |
| | | return "RAW "+raw[:120] |
| | | def exc_since(ts): |
| | | out=subprocess.run(["docker","logs","--since",ts,"mom-kyzn"],stdout=subprocess.PIPE,stderr=subprocess.STDOUT,timeout=120) |
| | | t=re.sub(r"\x1b\[[0-9;]*m","",out.stdout.decode("utf-8","replace")) |
| | | ls=t.split("\n") |
| | | for i,l in enumerate(ls): |
| | | if "defaultExceptionHandler" in l: |
| | | for l2 in ls[i+1:i+120]: |
| | | if re.match(r"^[a-zA-Z].*?(Exception|Error)", l2): |
| | | return l2.strip()[:200] |
| | | return "" |
| | | for m,p in targets: |
| | | ts=datetime.now().strftime("%Y-%m-%dT%H:%M:%S"); time.sleep(0.2) |
| | | r=call(m,p); time.sleep(0.6) |
| | | print("%-52s %-58s %s" % (p, r, exc_since(ts)), flush=True) |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | GET /admin-api/system/dept/simple-list |
| | | GET /admin-api/system/dict-type/page |
| | | GET /admin-api/system/mail-account/page |
| | | GET /admin-api/srm/tender-bid/get |
| | | GET /admin-api/mes/dv/metering/simple-list |
| | | GET /admin-api/mes/md/unit-measure/export-excel |
| | | GET /admin-api/mes/pd/archive/export-excel |
| | | GET /admin-api/infra/file/1/get/a.png |
| | | GET /admin-api/bpm/process-definition/page |
| | | GET /admin-api/bpm/form/create |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | GET /admin-api/mes/qc/template/export-excel |
| | | GET /admin-api/mes/pro/task/export-excel |
| | | GET /admin-api/mes/pd/archive/export-excel |
| | | GET /admin-api/hrm/shift/export-excel |
| | | GET /admin-api/hrm/attendance/rule/export-excel |
| | | POST /admin-api/mdm/item/create |
| | | POST /admin-api/bi/chart-config/create |
| | | GET /admin-api/infra/file/1/get/a.png |
| | | GET /admin-api/bpm/process-definition/page |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | GET /admin-api/mes/dv/repair/get |
| | | GET /admin-api/mes/dv/spare/get |
| | | GET /admin-api/srm/supplier-certificate/page |
| | | GET /admin-api/system/dept/simple-list |
| | | GET /admin-api/system/mail-account/page |
| | | GET /admin-api/crm/customer-limit-config/get |
| | | GET /admin-api/im/manager/face-pack-item/page |
| | | GET /admin-api/member/user/update-level |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | package cn.iocoder.yudao.framework.excel.core.convert; |
| | | |
| | | import cn.idev.excel.converters.Converter; |
| | | import cn.idev.excel.enums.CellDataTypeEnum; |
| | | import cn.idev.excel.metadata.GlobalConfiguration; |
| | | import cn.idev.excel.metadata.data.WriteCellData; |
| | | import cn.idev.excel.metadata.property.ExcelContentProperty; |
| | | |
| | | import java.time.LocalTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | |
| | | /** |
| | | * Excel LocalTime 转æ¢å¨ |
| | | * |
| | | * FastExcel å
ç½®åªæ¯æ Date / LocalDateTimeï¼éå° LocalTime åæ®µä¼æ |
| | | * {@code ExcelWriteDataConvertException: Can not find 'Converter' support class LocalTime}ï¼ |
| | | * å æ¤è¿éè¡¥ä¸ä¸ªè½¬æ¢å¨ï¼ä¾ç次ãèå¤è§åç嫿¶é´å段ç VO å¯¼åºæ¶ä½¿ç¨ã |
| | | * |
| | | * @author è¶
级管çå |
| | | */ |
| | | public class LocalTimeConvert implements Converter<LocalTime> { |
| | | |
| | | private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); |
| | | |
| | | @Override |
| | | public Class<?> supportJavaTypeKey() { |
| | | throw new UnsupportedOperationException("æä¸æ¯æï¼ä¹ä¸éè¦"); |
| | | } |
| | | |
| | | @Override |
| | | public CellDataTypeEnum supportExcelTypeKey() { |
| | | throw new UnsupportedOperationException("æä¸æ¯æï¼ä¹ä¸éè¦"); |
| | | } |
| | | |
| | | @Override |
| | | public WriteCellData<String> convertToExcelData(LocalTime value, ExcelContentProperty contentProperty, |
| | | GlobalConfiguration globalConfiguration) { |
| | | if (value == null) { |
| | | return new WriteCellData<>(""); |
| | | } |
| | | return new WriteCellData<>(value.format(FORMATTER)); |
| | | } |
| | | |
| | | } |
| | |
| | | import org.springframework.web.bind.annotation.RestControllerAdvice; |
| | | import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; |
| | | import org.springframework.web.multipart.MaxUploadSizeExceededException; |
| | | import org.springframework.web.multipart.MultipartException; |
| | | import org.springframework.web.servlet.NoHandlerFoundException; |
| | | import org.springframework.web.servlet.resource.NoResourceFoundException; |
| | | import tools.jackson.databind.exc.InvalidFormatException; |
| | |
| | | } |
| | | if (ex instanceof MaxUploadSizeExceededException) { |
| | | return maxUploadSizeExceededExceptionHandler((MaxUploadSizeExceededException) ex); |
| | | } |
| | | // ä¸ä¼ ç±»æ¥å£æ¶å°é multipart 请æ±ï¼å端æ¼ä¼ æä»¶ / ç¨ JSON è°ç¨äºå¯¼å
¥æ¥å£ï¼å±äºå®¢æ·ç«¯é®é¢ |
| | | if (ex instanceof MultipartException) { |
| | | return multipartExceptionHandler((MultipartException) ex); |
| | | } |
| | | if (ex instanceof NoHandlerFoundException) { |
| | | return noHandlerFoundExceptionHandler((NoHandlerFoundException) ex); |
| | |
| | | } |
| | | |
| | | /** |
| | | * å¤çæä»¶ä¸ä¼ æ¥å£æ¶å°é multipart è¯·æ± |
| | | * |
| | | * ä¾å¦è¯´ï¼Excel 导å
¥æ¥å£æ¼ä¼ file åæ®µãæåç«¯ç¨ application/json è°ç¨äºå¯¼å
¥æ¥å£ |
| | | */ |
| | | @ExceptionHandler(MultipartException.class) |
| | | public CommonResult<?> multipartExceptionHandler(MultipartException ex) { |
| | | log.warn("[multipartExceptionHandler]", ex); |
| | | return CommonResult.error(BAD_REQUEST.getCode(), "请æ±åæ°ä¸æ£ç¡®:è¯·ä½¿ç¨ multipart/form-data ä¸ä¼ æä»¶"); |
| | | } |
| | | |
| | | /** |
| | | * å¤ç SpringMVC åæ°ç»å®ä¸æ£ç¡®ï¼æ¬è´¨ä¸ä¹æ¯éè¿ Validator æ ¡éª |
| | | */ |
| | | @ExceptionHandler(BindException.class) |
| | |
| | | if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) { |
| | | return CommonResult.error(BAD_REQUEST.getCode(), "请æ±åæ°ç±»åé误: request body 缺失"); |
| | | } |
| | | return defaultExceptionHandler(ServletUtils.getRequest(), ex); |
| | | // å
¶å®ååºåå失败ï¼å¦æå¯¹è±¡ä¼ ææ°ç»ãåæ®µç±»åä¸å¹é
ï¼åæ ·æ¯å®¢æ·ç«¯æ¥æé®é¢ï¼ä¸åºæ 500 å¤ç |
| | | return CommonResult.error(BAD_REQUEST.getCode(), |
| | | String.format("请æ±åæ°ç±»åé误:%s", StrUtil.maxLength(ex.getMessage(), 120))); |
| | | } |
| | | |
| | | /** |
| | |
| | | errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request)); |
| | | // 设置å¼å¸¸å段 |
| | | errorLog.setExceptionName(e.getClass().getName()); |
| | | errorLog.setExceptionMessage(ExceptionUtil.getMessage(e)); |
| | | errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e)); |
| | | errorLog.setExceptionStackTrace(ExceptionUtil.stacktraceToString(e)); |
| | | // infra_api_error_log çè¿äºå齿¯ NOT NULLï¼è MyBatis-Plus æå
¥æ¶ä¼è·³è¿ null åæ®µï¼ |
| | | // äºæ¯ä»»ä¸å段为 null é½ä¼åæ "Field 'xxx' doesn't have a default value"ï¼å¼å¸¸æ¥å¿åèè®°ä¸ä¸æ¥ |
| | | errorLog.setExceptionMessage(StrUtil.nullToEmpty(ExceptionUtil.getMessage(e))); |
| | | errorLog.setExceptionRootCauseMessage(StrUtil.nullToEmpty(ExceptionUtil.getRootCauseMessage(e))); |
| | | errorLog.setExceptionStackTrace(StrUtil.nullToEmpty(ExceptionUtil.stacktraceToString(e))); |
| | | StackTraceElement[] stackTraceElements = e.getStackTrace(); |
| | | Assert.notEmpty(stackTraceElements, "å¼å¸¸ stackTraceElements ä¸è½ä¸ºç©º"); |
| | | StackTraceElement stackTraceElement = stackTraceElements[0]; |
| | | errorLog.setExceptionClassName(stackTraceElement.getClassName()); |
| | | errorLog.setExceptionFileName(stackTraceElement.getFileName()); |
| | | errorLog.setExceptionMethodName(stackTraceElement.getMethodName()); |
| | | // æ é¡¶å
ç´ å¯è½æ¯ native æ¹æ³æ Lambdaï¼getFileName() 为 nullï¼ |
| | | // infra_api_error_log çè¿äºåæ¯ NOT NULLï¼ä¸º null ä¼å¯¼è´å¼å¸¸æ¥å¿æå
¥å¤±è´¥ï¼æ¥å¿éåªå©ä¸æ¡ SQL æ¥éï¼ |
| | | errorLog.setExceptionFileName(StrUtil.nullToEmpty(stackTraceElement.getFileName())); |
| | | errorLog.setExceptionMethodName(StrUtil.nullToEmpty(stackTraceElement.getMethodName())); |
| | | errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber()); |
| | | // 设置å
¶å®å段 |
| | | errorLog.setTraceId(TracerUtils.getTraceId()); |
| | | errorLog.setApplicationName(applicationName); |
| | | errorLog.setRequestUrl(request.getRequestURI()); |
| | | errorLog.setTraceId(StrUtil.nullToEmpty(TracerUtils.getTraceId())); |
| | | errorLog.setApplicationName(StrUtil.nullToEmpty(applicationName)); |
| | | errorLog.setRequestUrl(StrUtil.nullToEmpty(request.getRequestURI())); |
| | | Map<String, Object> requestParams = MapUtil.<String, Object>builder() |
| | | .put("query", ServletUtils.getParamMap(request)) |
| | | .put("body", ServletUtils.getBody(request)).build(); |
| | | errorLog.setRequestParams(JsonUtils.toJsonString(requestParams)); |
| | | errorLog.setRequestMethod(request.getMethod()); |
| | | errorLog.setUserAgent(ServletUtils.getUserAgent(request)); |
| | | errorLog.setUserIp(ServletUtils.getClientIP(request)); |
| | | errorLog.setRequestParams(StrUtil.nullToEmpty(JsonUtils.toJsonString(requestParams))); |
| | | errorLog.setRequestMethod(StrUtil.nullToEmpty(request.getMethod())); |
| | | errorLog.setUserAgent(StrUtil.nullToEmpty(ServletUtils.getUserAgent(request))); |
| | | errorLog.setUserIp(StrUtil.nullToEmpty(ServletUtils.getClientIP(request))); |
| | | errorLog.setExceptionTime(LocalDateTime.now()); |
| | | } |
| | | |
| | |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.*; |
| | | |
| | | /** |
| | |
| | | * |
| | | * å
³è {@link BiDashboardDO#getId()} |
| | | */ |
| | | @NotNull(message = "æå±ä»ªè¡¨çIDä¸è½ä¸ºç©º") |
| | | private Long dashboardId; |
| | | |
| | | /** |
| | | * å¾è¡¨åç§° |
| | | */ |
| | | @NotBlank(message = "å¾è¡¨åç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | /** |
| | | * å¾è¡¨ç±»åï¼bar / line / pie / scatter / gauge / table / number |
| | | */ |
| | | @NotBlank(message = "å¾è¡¨ç±»åä¸è½ä¸ºç©º") |
| | | private String chartType; |
| | | |
| | | /** |
| | |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import lombok.*; |
| | | |
| | | /** |
| | |
| | | /** |
| | | * æ°æ®æºåç§° |
| | | */ |
| | | @NotBlank(message = "æ°æ®æºåç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | /** |
| | |
| | | package cn.iocoder.yudao.module.bpm.controller.admin.definition; |
| | | |
| | | import cn.hutool.core.collection.CollUtil; |
| | | import cn.hutool.core.util.StrUtil; |
| | | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| | | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| | | import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageReqVO; |
| | |
| | | public CommonResult<BpmProcessDefinitionRespVO> getProcessDefinition( |
| | | @RequestParam(value = "id", required = false) String id, |
| | | @RequestParam(value = "key", required = false) String key) { |
| | | ProcessDefinition processDefinition = id != null ? processDefinitionService.getProcessDefinition(id) |
| | | // id ä¸ key è³å°è¦ä¼ ä¸ä¸ªï¼å¦å Flowable 伿 FlowableIllegalArgumentException(key is null) åæ 500 |
| | | if (StrUtil.isBlank(id) && StrUtil.isBlank(key)) { |
| | | throw new IllegalArgumentException("æµç¨ç¼å· id 䏿µç¨å®ä¹æ è¯ key ä¸è½åæ¶ä¸ºç©º"); |
| | | } |
| | | ProcessDefinition processDefinition = StrUtil.isNotBlank(id) ? processDefinitionService.getProcessDefinition(id) |
| | | : processDefinitionService.getActiveProcessDefinition(key); |
| | | if (processDefinition == null) { |
| | | return success(null); |
| | |
| | | BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class); |
| | | return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null); |
| | | }); |
| | | // æåº |
| | | result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort)); |
| | | // æåºï¼sort æ¥èªæµç¨å®ä¹æ©å±ä¿¡æ¯ï¼å岿°æ®å¯è½ä¸º nullï¼ |
| | | // ç´æ¥ç¨ Comparator.comparing 伿 NPEï¼æ¥å£ 500ï¼ï¼è¿éæ null æå°æå |
| | | result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort, |
| | | Comparator.<Long>nullsLast(Comparator.<Long>naturalOrder()))); |
| | | return result; |
| | | } |
| | | |
| | |
| | | import cn.idev.excel.annotation.ExcelProperty; |
| | | import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; |
| | | import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; |
| | | import cn.iocoder.yudao.framework.excel.core.convert.LocalTimeConvert; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | |
| | | private String deptName; |
| | | |
| | | @Schema(description = "ä¸çæ¶é´") |
| | | @ExcelProperty("ä¸çæ¶é´") |
| | | @ExcelProperty(value = "ä¸çæ¶é´", converter = LocalTimeConvert.class) |
| | | private LocalTime workStartTime; |
| | | |
| | | @Schema(description = "ä¸çæ¶é´") |
| | | @ExcelProperty("ä¸çæ¶é´") |
| | | @ExcelProperty(value = "ä¸çæ¶é´", converter = LocalTimeConvert.class) |
| | | private LocalTime workEndTime; |
| | | |
| | | @Schema(description = "è¿å°å
许åéæ°") |
| | |
| | | package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.excel.core.convert.LocalTimeConvert; |
| | | import cn.idev.excel.annotation.ExcelIgnoreUnannotated; |
| | | import cn.idev.excel.annotation.ExcelProperty; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | |
| | | private String name; |
| | | |
| | | @Schema(description = "ä¸çæ¶é´") |
| | | @ExcelProperty("ä¸çæ¶é´") |
| | | @ExcelProperty(value = "ä¸çæ¶é´", converter = LocalTimeConvert.class) |
| | | private LocalTime startTime; |
| | | |
| | | @Schema(description = "ä¸çæ¶é´") |
| | | @ExcelProperty("ä¸çæ¶é´") |
| | | @ExcelProperty(value = "ä¸çæ¶é´", converter = LocalTimeConvert.class) |
| | | private LocalTime endTime; |
| | | |
| | | @Schema(description = "æ åå·¥æ¶ï¼å°æ¶ï¼") |
| | |
| | | import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*; |
| | | import cn.iocoder.yudao.module.infra.dal.dataobject.file.FileDO; |
| | | import cn.iocoder.yudao.module.infra.service.file.FileService; |
| | | import com.google.common.cache.CacheLoader; |
| | | import com.google.common.util.concurrent.UncheckedExecutionException; |
| | | import io.swagger.v3.oas.annotations.Operation; |
| | | import io.swagger.v3.oas.annotations.Parameter; |
| | |
| | | log.warn("[getFileContent][configId({}) path({}) æä»¶é
ç½®ä¸åå¨]", configId, path); |
| | | response.setStatus(HttpStatus.NOT_FOUND.value()); |
| | | return; |
| | | } catch (CacheLoader.InvalidCacheLoadException ex) { |
| | | // LoadingCache ç load è¿å null æ¶æåºï¼ä¾å¦åå²æä»¶è®°å½ç configId 已被å é¤ï¼ï¼åæ ·æèµæºä¸åå¨å¤ç |
| | | log.warn("[getFileContent][configId({}) path({}) æä»¶é
ç½®ä¸åå¨]", configId, path); |
| | | response.setStatus(HttpStatus.NOT_FOUND.value()); |
| | | return; |
| | | } |
| | | if (content == null) { |
| | | log.warn("[getFileContent][configId({}) path({}) æä»¶ä¸åå¨]", configId, path); |
| | |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.*; |
| | | |
| | | /** |
| | |
| | | /** |
| | | * å¦çç¼å· |
| | | */ |
| | | @NotNull(message = "å¦çç¼å·ä¸è½ä¸ºç©º") |
| | | private Long studentId; |
| | | /** |
| | | * åå |
| | |
| | | /** |
| | | * åæ° |
| | | */ |
| | | @NotNull(message = "åæ°ä¸è½ä¸ºç©º") |
| | | private Integer score; |
| | | |
| | | } |
| | |
| | | import com.baomidou.mybatisplus.annotation.KeySequence; |
| | | import com.baomidou.mybatisplus.annotation.TableId; |
| | | import com.baomidou.mybatisplus.annotation.TableName; |
| | | import jakarta.validation.constraints.NotNull; |
| | | import lombok.*; |
| | | |
| | | /** |
| | |
| | | /** |
| | | * å¦çç¼å· |
| | | */ |
| | | @NotNull(message = "å¦çç¼å·ä¸è½ä¸ºç©º") |
| | | private Long studentId; |
| | | /** |
| | | * åå |
| | |
| | | /** |
| | | * ç主任 |
| | | */ |
| | | @NotNull(message = "ç主任ä¸è½ä¸ºç©º") |
| | | private String teacher; |
| | | |
| | | } |
| | |
| | | package cn.iocoder.yudao.module.mdm.controller.admin.brand.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - MDM åçæ°å¢/ä¿®æ¹ Request VO") |
| | |
| | | private Long id; |
| | | |
| | | @Schema(description = "åçç¼ç ", requiredMode = Schema.RequiredMode.REQUIRED, example = "BRAND001") |
| | | @NotBlank(message = "åçç¼ç ä¸è½ä¸ºç©º") |
| | | private String code; |
| | | |
| | | @Schema(description = "åçåç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "åçA") |
| | | @NotBlank(message = "åçåç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| | |
| | | package cn.iocoder.yudao.module.mdm.controller.admin.category.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import lombok.Data; |
| | | |
| | | @Schema(description = "管çåå° - MDM ç©æåç±»æ°å¢/ä¿®æ¹ Request VO") |
| | |
| | | private String code; |
| | | |
| | | @Schema(description = "åç±»åç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "äºéé
ä»¶") |
| | | @NotBlank(message = "åç±»åç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | @Schema(description = "ç©æç±»å", example = "1") |
| | |
| | | package cn.iocoder.yudao.module.mdm.controller.admin.item.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | |
| | | private Long id; |
| | | |
| | | @Schema(description = "ç©æç¼ç ", requiredMode = Schema.RequiredMode.REQUIRED, example = "ITEM001") |
| | | @NotBlank(message = "ç©æç¼ç ä¸è½ä¸ºç©º") |
| | | private String code; |
| | | |
| | | @Schema(description = "ç©æåç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "èºä¸") |
| | | @NotBlank(message = "ç©æåç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | @Schema(description = "ç©ææ¡ç ", example = "12345678") |
| | |
| | | package cn.iocoder.yudao.module.mdm.controller.admin.unit.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | |
| | | private Long id; |
| | | |
| | | @Schema(description = "åä½ç¼ç ", requiredMode = Schema.RequiredMode.REQUIRED, example = "PCS") |
| | | @NotBlank(message = "åä½ç¼ç ä¸è½ä¸ºç©º") |
| | | private String code; |
| | | |
| | | @Schema(description = "åä½åç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "个") |
| | | @NotBlank(message = "åä½åç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | @Schema(description = "æ¯å¦ä¸»åä½", example = "true") |
| | |
| | | package cn.iocoder.yudao.module.mdm.controller.admin.warehouse.vo; |
| | | |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import jakarta.validation.constraints.NotBlank; |
| | | import lombok.Data; |
| | | |
| | | import java.math.BigDecimal; |
| | |
| | | private Long id; |
| | | |
| | | @Schema(description = "ä»åºç¼ç ", requiredMode = Schema.RequiredMode.REQUIRED, example = "WH001") |
| | | @NotBlank(message = "ä»åºç¼ç ä¸è½ä¸ºç©º") |
| | | private String code; |
| | | |
| | | @Schema(description = "ä»åºåç§°", requiredMode = Schema.RequiredMode.REQUIRED, example = "æåä»åº") |
| | | @NotBlank(message = "ä»åºåç§°ä¸è½ä¸ºç©º") |
| | | private String name; |
| | | |
| | | @Schema(description = "ä»åºå°å", example = "䏿µ·å¸æµ¦ä¸æ°åº") |
| | |
| | | @ApiAccessLog(operateType = EXPORT) |
| | | public void exportArchiveExcel(@Valid MesPdArchivePageReqVO pageReqVO, |
| | | HttpServletResponse response) throws IOException { |
| | | // 导åºéè¦ä¼ å
¥ Page 对象ï¼åç»æ¥è¯¢èµ° XML mapperï¼Page 为 null æ¶ MyBatis-Plus 伿 |
| | | // MybatisPlusException: can't found IPage for args!ï¼æ¤åè¿éä¼ null ç´æ¥å¯¼è´ 500ï¼ |
| | | pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); |
| | | IPage<MesPdArchiveGroupRespVO> archivePage = archiveService.getArchivePageGroupByProject(pageReqVO, null); |
| | | Page<MesPdArchiveGroupRespVO> page = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()); |
| | | IPage<MesPdArchiveGroupRespVO> archivePage = archiveService.getArchivePageGroupByProject(pageReqVO, page); |
| | | ExcelUtils.write(response, "彿¡£å°è´¦.xls", "æ°æ®", MesPdArchiveRespVO.class, |
| | | buildArchiveRespVOList(archivePage.getRecords())); |
| | | } |
| | |
| | | private Long id; |
| | | |
| | | @Schema(description = "å®¡æ ¸åå·", requiredMode = Schema.RequiredMode.REQUIRED, example = "AUD2024001") |
| | | @NotBlank(message = "å®¡æ ¸åå·ä¸è½ä¸ºç©º") |
| | | private String auditNo; |
| | | |
| | | @Schema(description = "èµæID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") |
| | | @NotNull(message = "èµæIDä¸è½ä¸ºç©º") |
| | | private Long documentId; |
| | | |
| | | @Schema(description = "èµæåç§°", example = "产å设计å¾") |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.excel.core.convert.JsonConvert; |
| | | import cn.idev.excel.annotation.ExcelIgnoreUnannotated; |
| | | import cn.idev.excel.annotation.ExcelProperty; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | | import lombok.Data; |
| | | |
| | |
| | | |
| | | @Schema(description = "管çåå° - MES çäº§ä»»å¡ Response VO") |
| | | @Data |
| | | @ExcelIgnoreUnannotated |
| | | public class MesProTaskRespVO { |
| | | |
| | | @Schema(description = "ç¼å·", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") |
| | |
| | | private LocalDateTime createTime; |
| | | |
| | | @Schema(description = "åå·¥åå²è®°å½") |
| | | // å¯¼åº Excel æ¶ä¸æ¯æ List åæ®µï¼è¿éæ¹ç¨ JsonConvert 转æ JSON ææ¬ï¼é¿å
å¯¼åºæ¥å£ 500 |
| | | @ExcelProperty(value = "åå·¥åå²è®°å½", converter = JsonConvert.class) |
| | | private List<MesProTaskPauseRecordRespVO> pauseRecords; |
| | | |
| | | @Schema(description = "æå®æ¥å·¥äººå ID éå") |
| | | @ExcelProperty(value = "æå®æ¥å·¥äººåID", converter = JsonConvert.class) |
| | | private List<Long> assigneeUserIds; |
| | | |
| | | @Schema(description = "æå®æ¥å·¥äººåå§åéå") |
| | | @ExcelProperty(value = "æå®æ¥å·¥äººåå§å", converter = JsonConvert.class) |
| | | private List<String> assigneeUserNames; |
| | | |
| | | @Schema(description = "æ¯å¦æ¥å·¥å®¡æ¹") |
| | |
| | | package cn.iocoder.yudao.module.mes.controller.admin.qc.template.vo; |
| | | |
| | | import cn.iocoder.yudao.framework.excel.core.convert.JsonConvert; |
| | | import cn.idev.excel.annotation.ExcelIgnoreUnannotated; |
| | | import cn.idev.excel.annotation.ExcelProperty; |
| | | import io.swagger.v3.oas.annotations.media.Schema; |
| | |
| | | private String name; |
| | | |
| | | @Schema(description = "æ£æµç§ç±»", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 3]") |
| | | @ExcelProperty("æ£æµç§ç±»") |
| | | @ExcelProperty(value = "æ£æµç§ç±»", converter = JsonConvert.class) |
| | | private List<Integer> types; |
| | | |
| | | @Schema(description = "ç¶æ", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") |
| | |
| | | private LocalDateTime createTime; |
| | | |
| | | } |
| | | |