安评报告修改
parent
9342ab42d3
commit
3ec4e15e6b
Binary file not shown.
|
|
@ -0,0 +1,87 @@
|
|||
# 本地库 vs 线上库 结构差异分析报告
|
||||
|
||||
| 项目 | 信息 |
|
||||
| --- | --- |
|
||||
| 本地库 | `192.168.20.100:33080` / `safety-eval-service` |
|
||||
| 线上库 | `nlb-kd2xz70qhllfet2koj.cn-beijing.nlb.aliyuncsslb.com:33068` / `safety-eval-service` |
|
||||
| 分析时间 | 2026-08-11 |
|
||||
| 执行动作 | 仅 `SELECT information_schema` 只读查询,**未对线上做任何变更** |
|
||||
|
||||
## 一、总体结论
|
||||
|
||||
| 检查项 | 结果 |
|
||||
| --- | --- |
|
||||
| 本地表数 / 线上表数 | 60 / 60 |
|
||||
| 线上缺表 | **0** |
|
||||
| 线上多表 | 0 |
|
||||
| 线上缺字段 | **8**(全部集中在 `org_info`) |
|
||||
| 线上多字段 | 0 |
|
||||
| 字段定义不一致 | **3** |
|
||||
| 线上缺索引 | 0 |
|
||||
|
||||
整体差异很小,核心问题是 `org_info` 表缺少一批「原型 V1.6」新增字段。
|
||||
|
||||
## 二、线上缺失字段(`org_info`,8 个)
|
||||
|
||||
均为可空、无默认值,补齐对存量数据无影响。
|
||||
|
||||
| 字段 | 类型 | 可空 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `qualification_cert_no` | varchar(100) | 是 | 资质证书编号 |
|
||||
| `fax` | varchar(20) | 是 | 传真 |
|
||||
| `contact_name` | varchar(50) | 是 | 联系人(由 `contact_name_phone` 拆分) |
|
||||
| `contact_phone` | varchar(20) | 是 | 联系电话(由 `contact_name_phone` 拆分) |
|
||||
| `contact_name_phone` | varchar(100) | 是 | 联系人及电话 |
|
||||
| `fixed_assets_total` | decimal(16,4) | 是 | 固定资产总值(万元) |
|
||||
| `apply_business_scope` | varchar(500) | 是 | 拟申请的法定安全评价业务范围 |
|
||||
| `org_intro` | text | 是 | 单位基本情况介绍 |
|
||||
|
||||
> 注:本地同时存在拆分后的 `contact_name` / `contact_phone` 与合并的 `contact_name_phone`,属于过渡期并存。是否三者全部上线,建议结合业务代码确认后再定。
|
||||
|
||||
## 三、字段定义不一致(3 处)
|
||||
|
||||
### 1. `eval_process_control.reviewer_signature_task_status` — 可安全对齐
|
||||
|
||||
仅注释文案不同,类型 `varchar(64)`、`NOT NULL`、默认值 `non_dispatched` 均一致。
|
||||
|
||||
- 线上:`审核签字任务状态 ...`
|
||||
- 本地:`过程控制审核签字任务状态 ...`
|
||||
|
||||
### 2 & 3. `org_info` 两个行业类别字段 — 存在数据截断风险,不建议改线上
|
||||
|
||||
| 字段 | 线上 | 本地 |
|
||||
| --- | --- | --- |
|
||||
| `safety_industry_category_code` | varchar(512) | varchar(32) |
|
||||
| `safety_industry_category_name` | varchar(1024) | varchar(50) |
|
||||
|
||||
**线上字段比本地更宽**,而非缺失或落后。实测线上存量数据:
|
||||
|
||||
| 字段 | 最大长度 | 超出本地长度的行数 |
|
||||
| --- | --- | --- |
|
||||
| `safety_industry_category_code` | 110 | 2 行 > 32 |
|
||||
| `safety_industry_category_name` | 57 | 1 行 > 50 |
|
||||
|
||||
本地对应数据最大长度仅 25 / 14,说明本地只是从未触发过加宽需求。
|
||||
|
||||
**结论:若按本地定义收窄线上,会直接截断线上真实数据,造成不可逆丢失。**
|
||||
正确做法是反向对齐——把本地改宽到与线上一致,相关语句已在 SQL 文件第五节给出。
|
||||
|
||||
## 四、索引
|
||||
|
||||
线上不存在缺失索引,无需处理。
|
||||
|
||||
## 五、交付物
|
||||
|
||||
| 文件 | 用途 |
|
||||
| --- | --- |
|
||||
| `online_schema_upgrade.sql` | 升级 SQL(含备份、执行、校验,风险项已注释隔离) |
|
||||
| `schema_diff.json` | 结构化差异明细 |
|
||||
| `compare_schema.py` | 只读对比脚本,可重复执行复核 |
|
||||
|
||||
## 六、执行建议
|
||||
|
||||
1. 先备份 `org_info`(SQL 第一节)。
|
||||
2. 执行第二节补字段、第三节对齐注释——这两项无数据风险。
|
||||
3. 第四节保持注释状态,**不要在线上执行**。
|
||||
4. 第五节在本地库执行,消除最后的长度差异。
|
||||
5. 用第六节语句校验结果。
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
只读对比:本地库 vs 线上库 结构差异,并生成升级 SQL。
|
||||
本脚本仅对 information_schema 执行 SELECT 查询,绝不修改线上数据库。
|
||||
"""
|
||||
import json
|
||||
import pymysql
|
||||
|
||||
SCHEMA = "safety-eval-service"
|
||||
|
||||
LOCAL = dict(host="192.168.20.100", port=33080,
|
||||
user="root", password="Mysql@zcloud33080")
|
||||
ONLINE = dict(host="nlb-kd2xz70qhllfet2koj.cn-beijing.nlb.aliyuncsslb.com",
|
||||
port=33068, user="root", password="5tS3owZ7w8Uk1egv")
|
||||
|
||||
COL_SQL = """
|
||||
SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_TYPE, IS_NULLABLE,
|
||||
COLUMN_DEFAULT, EXTRA, COLUMN_COMMENT, CHARACTER_SET_NAME, COLLATION_NAME
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA=%s
|
||||
ORDER BY TABLE_NAME, ORDINAL_POSITION
|
||||
"""
|
||||
|
||||
TBL_SQL = """
|
||||
SELECT TABLE_NAME, ENGINE, TABLE_COLLATION, TABLE_COMMENT
|
||||
FROM information_schema.TABLES
|
||||
WHERE TABLE_SCHEMA=%s AND TABLE_TYPE='BASE TABLE'
|
||||
ORDER BY TABLE_NAME
|
||||
"""
|
||||
|
||||
IDX_SQL = """
|
||||
SELECT TABLE_NAME, INDEX_NAME, NON_UNIQUE, SEQ_IN_INDEX, COLUMN_NAME, INDEX_TYPE
|
||||
FROM information_schema.STATISTICS
|
||||
WHERE TABLE_SCHEMA=%s
|
||||
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX
|
||||
"""
|
||||
|
||||
|
||||
def fetch(conn_args):
|
||||
conn = pymysql.connect(charset="utf8mb4", **conn_args)
|
||||
try:
|
||||
with conn.cursor(pymysql.cursors.DictCursor) as cur:
|
||||
cur.execute(TBL_SQL, (SCHEMA,))
|
||||
tables = {r["TABLE_NAME"]: r for r in cur.fetchall()}
|
||||
cur.execute(COL_SQL, (SCHEMA,))
|
||||
cols = {}
|
||||
for r in cur.fetchall():
|
||||
cols.setdefault(r["TABLE_NAME"], {})[r["COLUMN_NAME"]] = r
|
||||
cur.execute(IDX_SQL, (SCHEMA,))
|
||||
idx = {}
|
||||
for r in cur.fetchall():
|
||||
idx.setdefault(r["TABLE_NAME"], {}).setdefault(
|
||||
r["INDEX_NAME"], []).append(r)
|
||||
finally:
|
||||
conn.close()
|
||||
return tables, cols, idx
|
||||
|
||||
|
||||
def q(name):
|
||||
return "`%s`" % name
|
||||
|
||||
|
||||
def col_def(c):
|
||||
"""根据本地列定义拼出 DDL 片段。"""
|
||||
parts = [q(c["COLUMN_NAME"]), c["COLUMN_TYPE"]]
|
||||
if c["CHARACTER_SET_NAME"] and c["COLLATION_NAME"]:
|
||||
# 保持与表默认字符集一致,通常无需显式声明,这里省略以免冲突
|
||||
pass
|
||||
parts.append("NOT NULL" if c["IS_NULLABLE"] == "NO" else "NULL")
|
||||
default = c["COLUMN_DEFAULT"]
|
||||
extra = (c["EXTRA"] or "")
|
||||
if default is not None:
|
||||
up = str(default).upper()
|
||||
if up in ("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP()") or \
|
||||
up.startswith("CURRENT_TIMESTAMP(") or extra.upper().startswith("DEFAULT_GENERATED"):
|
||||
parts.append("DEFAULT %s" % default)
|
||||
else:
|
||||
parts.append("DEFAULT '%s'" % str(default).replace("'", "''"))
|
||||
elif c["IS_NULLABLE"] == "YES":
|
||||
parts.append("DEFAULT NULL")
|
||||
extra_clean = extra.replace("DEFAULT_GENERATED", "").strip()
|
||||
if extra_clean:
|
||||
parts.append(extra_clean.upper())
|
||||
if c["COLUMN_COMMENT"]:
|
||||
parts.append("COMMENT '%s'" % c["COLUMN_COMMENT"].replace("'", "''"))
|
||||
return " ".join(parts)
|
||||
|
||||
|
||||
def main():
|
||||
l_tbl, l_col, l_idx = fetch(LOCAL)
|
||||
o_tbl, o_col, o_idx = fetch(ONLINE)
|
||||
|
||||
report = {
|
||||
"local_table_count": len(l_tbl),
|
||||
"online_table_count": len(o_tbl),
|
||||
"missing_tables": sorted(set(l_tbl) - set(o_tbl)),
|
||||
"extra_tables": sorted(set(o_tbl) - set(l_tbl)),
|
||||
"missing_columns": [],
|
||||
"extra_columns": [],
|
||||
"diff_columns": [],
|
||||
"missing_indexes": [],
|
||||
}
|
||||
|
||||
for t in sorted(set(l_tbl) & set(o_tbl)):
|
||||
lc, oc = l_col.get(t, {}), o_col.get(t, {})
|
||||
for name in lc:
|
||||
if name not in oc:
|
||||
report["missing_columns"].append(
|
||||
{"table": t, "column": name, "def": col_def(lc[name]),
|
||||
"pos": lc[name]["ORDINAL_POSITION"]})
|
||||
for name in oc:
|
||||
if name not in lc:
|
||||
report["extra_columns"].append({"table": t, "column": name,
|
||||
"type": oc[name]["COLUMN_TYPE"]})
|
||||
for name in lc:
|
||||
if name not in oc:
|
||||
continue
|
||||
a, b = lc[name], oc[name]
|
||||
diffs = {}
|
||||
if a["COLUMN_TYPE"] != b["COLUMN_TYPE"]:
|
||||
diffs["type"] = [b["COLUMN_TYPE"], a["COLUMN_TYPE"]]
|
||||
if a["IS_NULLABLE"] != b["IS_NULLABLE"]:
|
||||
diffs["nullable"] = [b["IS_NULLABLE"], a["IS_NULLABLE"]]
|
||||
if (a["COLUMN_DEFAULT"] or "") != (b["COLUMN_DEFAULT"] or ""):
|
||||
diffs["default"] = [b["COLUMN_DEFAULT"], a["COLUMN_DEFAULT"]]
|
||||
if (a["EXTRA"] or "") != (b["EXTRA"] or ""):
|
||||
diffs["extra"] = [b["EXTRA"], a["EXTRA"]]
|
||||
if (a["COLUMN_COMMENT"] or "") != (b["COLUMN_COMMENT"] or ""):
|
||||
diffs["comment"] = [b["COLUMN_COMMENT"], a["COLUMN_COMMENT"]]
|
||||
if diffs:
|
||||
report["diff_columns"].append(
|
||||
{"table": t, "column": name, "diffs": diffs,
|
||||
"def": col_def(a)})
|
||||
|
||||
li, oi = l_idx.get(t, {}), o_idx.get(t, {})
|
||||
for iname, rows in li.items():
|
||||
if iname == "PRIMARY":
|
||||
continue
|
||||
if iname not in oi:
|
||||
report["missing_indexes"].append({
|
||||
"table": t, "index": iname,
|
||||
"unique": rows[0]["NON_UNIQUE"] == 0,
|
||||
"columns": [r["COLUMN_NAME"] for r in rows]})
|
||||
|
||||
with open("schema_diff.json", "w", encoding="utf-8") as f:
|
||||
json.dump(report, f, ensure_ascii=False, indent=2, default=str)
|
||||
|
||||
print("本地表数:", report["local_table_count"],
|
||||
" 线上表数:", report["online_table_count"])
|
||||
print("线上缺表:", len(report["missing_tables"]), report["missing_tables"])
|
||||
print("线上多表:", len(report["extra_tables"]), report["extra_tables"])
|
||||
print("线上缺字段:", len(report["missing_columns"]))
|
||||
print("线上多字段:", len(report["extra_columns"]))
|
||||
print("字段定义不一致:", len(report["diff_columns"]))
|
||||
print("线上缺索引:", len(report["missing_indexes"]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
-- =============================================================================
|
||||
-- 线上数据库结构升级 SQL(safety-eval-service)
|
||||
--
|
||||
-- 对比基准:本地库 192.168.20.100:33080 / safety-eval-service
|
||||
-- 目标库 :nlb-kd2xz70qhllfet2koj.cn-beijing.nlb.aliyuncsslb.com:33068 / safety-eval-service
|
||||
-- 生成时间:2026-08-11
|
||||
--
|
||||
-- 【重要】本文件仅供人工评审,尚未在任何环境执行。
|
||||
-- 执行前请务必先备份 org_info 表。
|
||||
-- =============================================================================
|
||||
|
||||
USE `safety-eval-service`;
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 一、备份(强烈建议,执行 DDL 前先跑)
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- CREATE TABLE `org_info_bak_20260811` LIKE `org_info`;
|
||||
-- INSERT INTO `org_info_bak_20260811` SELECT * FROM `org_info`;
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 二、补齐线上缺失字段(org_info,共 8 个,均为原型 V1.6 新增)
|
||||
-- 全部为可空且无默认值,对存量数据无影响,可安全执行。
|
||||
-- -----------------------------------------------------------------------------
|
||||
ALTER TABLE `org_info`
|
||||
ADD COLUMN `qualification_cert_no` varchar(100) NULL DEFAULT NULL COMMENT '资质证书编号(2026-08-08 原型V1.6新增,非必填)' AFTER `env`,
|
||||
ADD COLUMN `fax` varchar(20) NULL DEFAULT NULL COMMENT '传真(2026-08-08 原型V1.6新增)' AFTER `qualification_cert_no`,
|
||||
ADD COLUMN `contact_name` varchar(50) NULL DEFAULT NULL COMMENT '联系人(2026-08-08 由 contact_name_phone 拆分)' AFTER `fax`,
|
||||
ADD COLUMN `contact_phone` varchar(20) NULL DEFAULT NULL COMMENT '联系电话(2026-08-08 由 contact_name_phone 拆分)' AFTER `contact_name`,
|
||||
ADD COLUMN `contact_name_phone` varchar(100) NULL DEFAULT NULL COMMENT '联系人及电话(2026-08-08 原型V1.6新增)' AFTER `contact_phone`,
|
||||
ADD COLUMN `fixed_assets_total` decimal(16,4) NULL DEFAULT NULL COMMENT '固定资产总值(万元,非负,2026-08-08 原型V1.6新增)' AFTER `contact_name_phone`,
|
||||
ADD COLUMN `apply_business_scope` varchar(500) NULL DEFAULT NULL COMMENT '拟申请的法定安全评价业务范围(多选逗号分隔,2026-08-08 原型V1.6新增)' AFTER `fixed_assets_total`,
|
||||
ADD COLUMN `org_intro` text NULL COMMENT '单位基本情况介绍(长文本,2026-08-08 原型V1.6新增)' AFTER `apply_business_scope`;
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 三、字段注释对齐(eval_process_control)
|
||||
-- 仅注释文案不同,类型/长度/可空性均一致,无数据风险。
|
||||
-- -----------------------------------------------------------------------------
|
||||
ALTER TABLE `eval_process_control`
|
||||
MODIFY COLUMN `reviewer_signature_task_status` varchar(64) NOT NULL DEFAULT 'non_dispatched'
|
||||
COMMENT '过程控制审核签字任务状态 dispatched 下发,non_dispatched 未下发 , completed 已完成';
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 四、【不建议执行】org_info 两个字段的长度差异
|
||||
--
|
||||
-- safety_industry_category_code:线上 varchar(512) 本地 varchar(32)
|
||||
-- safety_industry_category_name:线上 varchar(1024) 本地 varchar(50)
|
||||
--
|
||||
-- 线上字段比本地更宽。实测线上存量数据:
|
||||
-- - code 最大长度 110,其中 2 条 > 32
|
||||
-- - name 最大长度 57, 其中 1 条 > 50
|
||||
-- 若按本地定义收窄,将直接截断线上真实数据,造成不可逆的数据丢失。
|
||||
--
|
||||
-- 结论:应反向以线上为准,把【本地】改宽以与线上保持一致,
|
||||
-- 而不是把线上改窄。以下语句仅作留档,禁止在线上执行。
|
||||
--
|
||||
-- ALTER TABLE `org_info`
|
||||
-- MODIFY COLUMN `safety_industry_category_code` varchar(32) NULL DEFAULT NULL COMMENT '安全生产监管行业类别编码',
|
||||
-- MODIFY COLUMN `safety_industry_category_name` varchar(50) NULL DEFAULT NULL COMMENT '安全生产监管行业类别名称';
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
-- =============================================================================
|
||||
-- 五、建议在【本地库】执行(使本地向线上看齐,消除该项差异)
|
||||
--
|
||||
-- ALTER TABLE `org_info`
|
||||
-- MODIFY COLUMN `safety_industry_category_code` varchar(512) NULL DEFAULT NULL COMMENT '安全生产监管行业类别编码',
|
||||
-- MODIFY COLUMN `safety_industry_category_name` varchar(1024) NULL DEFAULT NULL COMMENT '安全生产监管行业类别名称';
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- 六、执行后校验
|
||||
-- -----------------------------------------------------------------------------
|
||||
-- SELECT COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_COMMENT
|
||||
-- FROM information_schema.COLUMNS
|
||||
-- WHERE TABLE_SCHEMA='safety-eval-service' AND TABLE_NAME='org_info'
|
||||
-- ORDER BY ORDINAL_POSITION;
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"local_table_count": 60,
|
||||
"online_table_count": 60,
|
||||
"missing_tables": [],
|
||||
"extra_tables": [],
|
||||
"missing_columns": [
|
||||
{
|
||||
"table": "org_info",
|
||||
"column": "qualification_cert_no",
|
||||
"def": "`qualification_cert_no` varchar(100) NULL DEFAULT NULL COMMENT '资质证书编号(2026-08-08 原型V1.6新增,非必填)'",
|
||||
"pos": 57
|
||||
},
|
||||
{
|
||||
"table": "org_info",
|
||||
"column": "fax",
|
||||
"def": "`fax` varchar(20) NULL DEFAULT NULL COMMENT '传真(2026-08-08 原型V1.6新增)'",
|
||||
"pos": 58
|
||||
},
|
||||
{
|
||||
"table": "org_info",
|
||||
"column": "contact_name",
|
||||
"def": "`contact_name` varchar(50) NULL DEFAULT NULL COMMENT '联系人(2026-08-08 由 contact_name_phone 拆分)'",
|
||||
"pos": 59
|
||||
},
|
||||
{
|
||||
"table": "org_info",
|
||||
"column": "contact_phone",
|
||||
"def": "`contact_phone` varchar(20) NULL DEFAULT NULL COMMENT '联系电话(2026-08-08 由 contact_name_phone 拆分)'",
|
||||
"pos": 60
|
||||
},
|
||||
{
|
||||
"table": "org_info",
|
||||
"column": "contact_name_phone",
|
||||
"def": "`contact_name_phone` varchar(100) NULL DEFAULT NULL COMMENT '联系人及电话(2026-08-08 原型V1.6新增)'",
|
||||
"pos": 61
|
||||
},
|
||||
{
|
||||
"table": "org_info",
|
||||
"column": "fixed_assets_total",
|
||||
"def": "`fixed_assets_total` decimal(16,4) NULL DEFAULT NULL COMMENT '固定资产总值(万元,非负,2026-08-08 原型V1.6新增)'",
|
||||
"pos": 62
|
||||
},
|
||||
{
|
||||
"table": "org_info",
|
||||
"column": "apply_business_scope",
|
||||
"def": "`apply_business_scope` varchar(500) NULL DEFAULT NULL COMMENT '拟申请的法定安全评价业务范围(多选逗号分隔,2026-08-08 原型V1.6新增)'",
|
||||
"pos": 63
|
||||
},
|
||||
{
|
||||
"table": "org_info",
|
||||
"column": "org_intro",
|
||||
"def": "`org_intro` text NULL DEFAULT NULL COMMENT '单位基本情况介绍(长文本,2026-08-08 原型V1.6新增)'",
|
||||
"pos": 64
|
||||
}
|
||||
],
|
||||
"extra_columns": [],
|
||||
"diff_columns": [
|
||||
{
|
||||
"table": "eval_process_control",
|
||||
"column": "reviewer_signature_task_status",
|
||||
"diffs": {
|
||||
"comment": [
|
||||
"审核签字任务状态 dispatched 下发,non_dispatched 未下发 , completed 已完成",
|
||||
"过程控制审核签字任务状态 dispatched 下发,non_dispatched 未下发 , completed 已完成"
|
||||
]
|
||||
},
|
||||
"def": "`reviewer_signature_task_status` varchar(64) NOT NULL DEFAULT 'non_dispatched' COMMENT '过程控制审核签字任务状态 dispatched 下发,non_dispatched 未下发 , completed 已完成'"
|
||||
},
|
||||
{
|
||||
"table": "org_info",
|
||||
"column": "safety_industry_category_code",
|
||||
"diffs": {
|
||||
"type": [
|
||||
"varchar(512)",
|
||||
"varchar(32)"
|
||||
]
|
||||
},
|
||||
"def": "`safety_industry_category_code` varchar(32) NULL DEFAULT NULL COMMENT '安全生产监管行业类别编码'"
|
||||
},
|
||||
{
|
||||
"table": "org_info",
|
||||
"column": "safety_industry_category_name",
|
||||
"diffs": {
|
||||
"type": [
|
||||
"varchar(1024)",
|
||||
"varchar(50)"
|
||||
]
|
||||
},
|
||||
"def": "`safety_industry_category_name` varchar(50) NULL DEFAULT NULL COMMENT '安全生产监管行业类别名称'"
|
||||
}
|
||||
],
|
||||
"missing_indexes": []
|
||||
}
|
||||
|
|
@ -5,10 +5,12 @@ import io.swagger.annotations.ApiOperation;
|
|||
import io.swagger.annotations.ApiParam;
|
||||
import org.qinan.safetyeval.client.api.institution.InstitutionEvalReportApi;
|
||||
import org.qinan.safetyeval.client.co.report.EvalReportLibraryCO;
|
||||
import org.qinan.safetyeval.client.co.report.InstitutionEvalReportBatchSubmitResultCO;
|
||||
import org.qinan.safetyeval.client.co.report.InstitutionEvalReportSummaryCO;
|
||||
import org.qinan.safetyeval.client.dto.PageResponse;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportAddCmd;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportBatchSubmitCmd;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportPageQuery;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportSubmitCmd;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
|
@ -53,4 +55,11 @@ public class InstitutionEvalReportController {
|
|||
public SingleResponse<EvalReportLibraryCO> submit(@Validated @RequestBody InstitutionEvalReportSubmitCmd cmd) {
|
||||
return institutionEvalReportApi.submit(cmd);
|
||||
}
|
||||
|
||||
@ApiOperation("批量报送监管(未报送 → 已报送,支持部分成功)")
|
||||
@PostMapping("/submit/batch")
|
||||
public SingleResponse<InstitutionEvalReportBatchSubmitResultCO> batchSubmit(
|
||||
@Validated @RequestBody InstitutionEvalReportBatchSubmitCmd cmd) {
|
||||
return institutionEvalReportApi.batchSubmit(cmd);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,8 +8,11 @@ import org.qinan.safetyeval.client.dto.PageResponse;
|
|||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportAddCmd;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportPageQuery;
|
||||
import org.qinan.safetyeval.client.co.report.InstitutionEvalReportBatchSubmitResultCO;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportSubmitCmd;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportBatchSubmitCmd;
|
||||
import org.qinan.safetyeval.domain.entity.EvalReportEntity;
|
||||
import org.qinan.safetyeval.domain.exception.BizException;
|
||||
import org.qinan.safetyeval.domain.exception.ErrorCode;
|
||||
import org.qinan.safetyeval.domain.query.EvalReportQuery;
|
||||
import org.qinan.safetyeval.domain.query.PageResult;
|
||||
|
|
@ -21,6 +24,8 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
|
|
@ -108,6 +113,46 @@ public class InstitutionEvalReportExecutor implements InstitutionEvalReportApi {
|
|||
return SingleResponse.success(ReportProfileCoConverter.toReportCO(result));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量报送监管端(变更时间:2026-08-11)
|
||||
* 变更原因:机构端报告库需支持一次性报送多份「未报送」报告,原仅支持单条 submit。
|
||||
* 变更逻辑:
|
||||
* 1) 对入参 id 去重,逐条复用 submitToRegulator 的报送规则(未报送→已报送、已报送抛 EVAL_REPORT_ALREADY_SUBMITTED);
|
||||
* 2) 单条失败(已报送/无权限/不存在/其他异常)不中断整体,收集失败原因到 failList;
|
||||
* 3) 返回成功与失败明细,便于前端精确展示报送结果。
|
||||
*/
|
||||
@Override
|
||||
public SingleResponse<InstitutionEvalReportBatchSubmitResultCO> batchSubmit(InstitutionEvalReportBatchSubmitCmd cmd) {
|
||||
InstitutionEvalReportBatchSubmitResultCO result = new InstitutionEvalReportBatchSubmitResultCO();
|
||||
if (cmd == null || cmd.getIds() == null || cmd.getIds().isEmpty()) {
|
||||
throw new BizException(ErrorCode.EVAL_REPORT_BATCH_EMPTY);
|
||||
}
|
||||
// 变更时间:2026-08-11 入参去重,避免同一报告被提交两次
|
||||
Set<Long> distinctIds = new LinkedHashSet<>(cmd.getIds());
|
||||
for (Long id : distinctIds) {
|
||||
try {
|
||||
EvalReportEntity existing = evalReportDomainService.get(id);
|
||||
institutionOrgSupport.assertOrgAccess(existing.getOrgId(), ErrorCode.EVAL_REPORT_ORG_MISMATCH);
|
||||
EvalReportEntity submitted = evalReportDomainService.submitToRegulator(id);
|
||||
result.getSuccessList().add(ReportProfileCoConverter.toReportCO(submitted));
|
||||
} catch (BizException be) {
|
||||
addFail(result, id, be.getMessage());
|
||||
} catch (Exception e) {
|
||||
addFail(result, id, "报送失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
result.setSuccessCount(result.getSuccessList().size());
|
||||
result.setFailCount(result.getFailList().size());
|
||||
return SingleResponse.success(result);
|
||||
}
|
||||
|
||||
private void addFail(InstitutionEvalReportBatchSubmitResultCO result, Long id, String reason) {
|
||||
InstitutionEvalReportBatchSubmitResultCO.FailItem item = new InstitutionEvalReportBatchSubmitResultCO.FailItem();
|
||||
item.setId(id);
|
||||
item.setReason(reason);
|
||||
result.getFailList().add(item);
|
||||
}
|
||||
|
||||
private void applyDisplayStatus(EvalReportQuery query, String displayStatus, Integer reportStatusCode) {
|
||||
if (StringUtils.hasText(displayStatus)) {
|
||||
String status = displayStatus.trim().toUpperCase();
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package org.qinan.safetyeval.client.api.institution;
|
||||
|
||||
import org.qinan.safetyeval.client.co.report.EvalReportLibraryCO;
|
||||
import org.qinan.safetyeval.client.co.report.InstitutionEvalReportBatchSubmitResultCO;
|
||||
import org.qinan.safetyeval.client.co.report.InstitutionEvalReportSummaryCO;
|
||||
import org.qinan.safetyeval.client.dto.PageResponse;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportAddCmd;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportBatchSubmitCmd;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportPageQuery;
|
||||
import org.qinan.safetyeval.client.dto.institution.InstitutionEvalReportSubmitCmd;
|
||||
|
||||
|
|
@ -19,4 +21,10 @@ public interface InstitutionEvalReportApi {
|
|||
SingleResponse<EvalReportLibraryCO> add(InstitutionEvalReportAddCmd cmd);
|
||||
|
||||
SingleResponse<EvalReportLibraryCO> submit(InstitutionEvalReportSubmitCmd cmd);
|
||||
|
||||
/**
|
||||
* 批量报送监管端(变更时间:2026-08-11)
|
||||
* 复用单条 submit 的报送规则,逐条报送并收集失败明细,不因单条失败中断整体。
|
||||
*/
|
||||
SingleResponse<InstitutionEvalReportBatchSubmitResultCO> batchSubmit(InstitutionEvalReportBatchSubmitCmd cmd);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
package org.qinan.safetyeval.client.co.report;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 机构端批量报送结果
|
||||
*
|
||||
* <p>变更时间:2026-08-11</p>
|
||||
* <p>变更原因:批量报送需要向调用方返回「成功/失败」明细,便于前端展示哪些报告报送成功、
|
||||
* 哪些因已报送/无权限/不存在等原因失败,避免单条失败导致整体不可知。</p>
|
||||
*/
|
||||
@Data
|
||||
public class InstitutionEvalReportBatchSubmitResultCO {
|
||||
|
||||
@ApiModelProperty(value = "成功报送条数")
|
||||
private int successCount;
|
||||
|
||||
@ApiModelProperty(value = "失败条数")
|
||||
private int failCount;
|
||||
|
||||
@ApiModelProperty(value = "成功报送的报告视图列表")
|
||||
private List<EvalReportLibraryCO> successList = new ArrayList<>();
|
||||
|
||||
@ApiModelProperty(value = "失败明细:报告ID -> 失败原因")
|
||||
private List<FailItem> failList = new ArrayList<>();
|
||||
|
||||
@Data
|
||||
public static class FailItem {
|
||||
@ApiModelProperty(value = "失败的报告ID")
|
||||
private Long id;
|
||||
@ApiModelProperty(value = "失败原因")
|
||||
private String reason;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package org.qinan.safetyeval.client.dto.institution;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 机构端批量报送监管端命令
|
||||
*
|
||||
* <p>变更时间:2026-08-11</p>
|
||||
* <p>变更原因:报告库需支持一次性把多份「未报送」报告报送至监管端,原仅提供单条 submit,
|
||||
* 逐条点击效率低且无法给出统一的批量结果反馈。新增批量报送命令,入参为报告主键集合。</p>
|
||||
* <p>变更逻辑:复用 EvalReportDomainService.submitToRegulator 的单条报送规则(未报送→已报送、
|
||||
* 已报送抛 EVAL_REPORT_ALREADY_SUBMITTED),对失败项收集原因,不因单条失败中断整体。</p>
|
||||
*/
|
||||
@Data
|
||||
public class InstitutionEvalReportBatchSubmitCmd {
|
||||
|
||||
@NotEmpty(message = "报告ID集合不能为空")
|
||||
@ApiModelProperty(value = "待报送报告主键集合(仅支持「未报送」报告)", required = true)
|
||||
private List<@NotNull(message = "报告ID不能为空") Long> ids;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<!doctype html><html lang="zh"><head data-built-info="@cqsjjb/scripts@2.0.0-rspack.2 Frontend_Env[production] Build_Date[2026/8/5 14:13:14] App_Identifier[safetyEval-h5]"><meta charset="UTF-8"/><meta name="renderer" content="webkit"/><meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1"/><meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><meta charset="UTF-8" name="referrer" content="strict-origin-when-cross-origin"/><title>注册</title><script>(function () {
|
||||
<!doctype html><html lang="zh"><head data-built-info="@cqsjjb/scripts@2.0.0-rspack.2 Frontend_Env[production] Build_Date[2026/8/11 14:10:31] App_Identifier[safetyEval-h5]"><meta charset="UTF-8"/><meta name="renderer" content="webkit"/><meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1"/><meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><meta charset="UTF-8" name="referrer" content="strict-origin-when-cross-origin"/><title>注册</title><script>(function () {
|
||||
const APP_ENV = {
|
||||
antd: {
|
||||
'ant-prefix': 'micro-temp',
|
||||
|
|
@ -126,4 +126,4 @@
|
|||
}
|
||||
mergeParams();
|
||||
})();
|
||||
})();</script><script defer="defer" src="/safetyEval-h5/static/js/325.73c909fb4b7ba287.js"></script><script defer="defer" src="/safetyEval-h5/static/js/930.c071827086472787.js"></script><script defer="defer" src="/safetyEval-h5/static/js/997.9a421c4109ce8ab7.js"></script><script defer="defer" src="/safetyEval-h5/static/js/main.83bb2462d6099cf3.js"></script><link href="/safetyEval-h5/static/css/main.485b414ca5d6de25.css" rel="stylesheet"></head><body><noscript>此网页需要开启JavaScript功能。</noscript><div id="root" style="width: 100%; height: 100%; position: relative;overflow-y: auto;"></div><script type="text/javascript">/* @cqsjjb/script 输出当前应用基本信息 */console.log("%c@cqsjjb/scripts@2.0.0-rspack.2 Frontend_Env[production] Build_Date[2026/8/5 14:13:14] App_Identifier[safetyEval-h5] Frontend_Branch[dev] Backend_Branch[<branch-name>]", "color: #1890ff; border-radius: 2px; padding: 0 4px; border: 1px solid #1890ff; background: #f9fcff")</script></body><script src="https://cesium.com/downloads/cesiumjs/releases/1.91/Build/Cesium/Cesium.js"></script><link href="https://cesium.com/downloads/cesiumjs/releases/1.91/Build/Cesium/Widgets/widgets.css" rel="stylesheet"></html>
|
||||
})();</script><script defer="defer" src="/safetyEval-h5/static/js/33.826ba7bce17f692b.js"></script><script defer="defer" src="/safetyEval-h5/static/js/509.37fefdea880bd79c.js"></script><script defer="defer" src="/safetyEval-h5/static/js/554.dce5d48939bcb600.js"></script><script defer="defer" src="/safetyEval-h5/static/js/main.cac46dc57d12de0f.js"></script><link href="/safetyEval-h5/static/css/main.aeb5d93e210e3449.css" rel="stylesheet"></head><body><noscript>此网页需要开启JavaScript功能。</noscript><div id="root" style="width: 100%; height: 100%; position: relative;overflow-y: auto;"></div><script type="text/javascript">/* @cqsjjb/script 输出当前应用基本信息 */console.log("%c@cqsjjb/scripts@2.0.0-rspack.2 Frontend_Env[production] Build_Date[2026/8/11 14:10:31] App_Identifier[safetyEval-h5] Frontend_Branch[dev-tmp1] Backend_Branch[<branch-name>]", "color: #1890ff; border-radius: 2px; padding: 0 4px; border: 1px solid #1890ff; background: #f9fcff")</script></body><script src="https://cesium.com/downloads/cesiumjs/releases/1.91/Build/Cesium/Cesium.js"></script><link href="https://cesium.com/downloads/cesiumjs/releases/1.91/Build/Cesium/Widgets/widgets.css" rel="stylesheet"></html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 154 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,4 +1,4 @@
|
|||
<!doctype html><html lang="zh"><head data-built-info="@cqsjjb/scripts@2.0.0-rspack.3 Frontend_Env[production] Build_Date[2026/8/7 10:54:09] App_Identifier[safetyEval]"><meta charset="UTF-8"/><meta name="renderer" content="webkit"/><meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1"/><meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><title>--</title><script>(function () {
|
||||
<!doctype html><html lang="zh"><head data-built-info="@cqsjjb/scripts@2.0.0-rspack.3 Frontend_Env[production] Build_Date[2026/8/11 14:09:28] App_Identifier[safetyEval]"><meta charset="UTF-8"/><meta name="renderer" content="webkit"/><meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1"/><meta name="viewport" content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><title>--</title><script>(function () {
|
||||
const APP_ENV = {
|
||||
antd: {
|
||||
'ant-prefix': 'micro-temp',
|
||||
|
|
@ -89,4 +89,4 @@
|
|||
},
|
||||
|
||||
}
|
||||
}</script><script defer="defer" src="/safetyEval/static/js/839.7be94e9f9e3a7d36.js"></script><script defer="defer" src="/safetyEval/static/js/101.3b5adf63ead4365d.js"></script><script defer="defer" src="/safetyEval/static/js/479.74f05548bf67e219.js"></script><script defer="defer" src="/safetyEval/static/js/main.3f3dd26de476266b.js"></script><link href="/safetyEval/static/css/main.5ca34e4215ba28a7.css" rel="stylesheet"></head><body><noscript>此网页需要开启JavaScript功能。</noscript><div id="root" style="width: 100%; height: 100%; position: relative;overflow-y: auto"></div><script type="text/javascript">/* @cqsjjb/script 输出当前应用基本信息 */console.log("%c@cqsjjb/scripts@2.0.0-rspack.3 Frontend_Env[production] Build_Date[2026/8/7 10:54:09] App_Identifier[safetyEval] Frontend_Branch[dev] Backend_Branch[dev]", "color: #1890ff; border-radius: 2px; padding: 0 4px; border: 1px solid #1890ff; background: #f9fcff")</script></body></html>
|
||||
}</script><script defer="defer" src="/safetyEval/static/js/839.bcb7133530b5a949.js"></script><script defer="defer" src="/safetyEval/static/js/376.edf49837adea7bf5.js"></script><script defer="defer" src="/safetyEval/static/js/419.9f92da006b7be853.js"></script><script defer="defer" src="/safetyEval/static/js/479.ada5b7b837d8d37e.js"></script><script defer="defer" src="/safetyEval/static/js/main.ad05d9b885be96e9.js"></script><link href="/safetyEval/static/css/main.a70edbd2a3f67acc.css" rel="stylesheet"></head><body><noscript>此网页需要开启JavaScript功能。</noscript><div id="root" style="width: 100%; height: 100%; position: relative;overflow-y: auto"></div><script type="text/javascript">/* @cqsjjb/script 输出当前应用基本信息 */console.log("%c@cqsjjb/scripts@2.0.0-rspack.3 Frontend_Env[production] Build_Date[2026/8/11 14:09:28] App_Identifier[safetyEval] Frontend_Branch[dev-tmp1] Backend_Branch[dev]", "color: #1890ff; border-radius: 2px; padding: 0 4px; border: 1px solid #1890ff; background: #f9fcff")</script></body></html>
|
||||
|
|
@ -1 +1 @@
|
|||
module.exports={javaGit:"http://47.92.113.182:3000/cq_anquan/safety-eval-service.git",javaGitName:"safety-eval-service",environment:{development:{javaGitBranch:"dev",API_HOST:"http://192.168.0.103"},production:{javaGitBranch:"dev",API_HOST:""}},appIdentifier:"safetyEval",contextInject:{appKey:"",fileUrl:"https://skqhdg.porthebei.com:9004/file/uploadFiles2/"},windowInject:{title:"微应用模板",links:[],element:{root:{id:"root"}},scripts:[]},server:{port:"8081",host:"192.168.0.187",open:!1},framework:{antd:{"ant-prefix":"micro-temp",fontFamily:"PingFangSC-Regular",colorPrimary:"#1677ff",borderRadius:2}},webpackConfig:{htmlWebpackPluginOption:{inject:!0},resolve:{fallback:{stream:!1}}}};
|
||||
module.exports={javaGit:"http://47.92.113.182:3000/cq_anquan/safety-eval-service.git",javaGitName:"safety-eval-service",environment:{development:{javaGitBranch:"dev",API_HOST:"https://gbs-gateway.qhdsafety.com"},production:{javaGitBranch:"dev",API_HOST:""}},appIdentifier:"safetyEval",contextInject:{appKey:"",fileUrl:"https://skqhdg.porthebei.com:9004/file/uploadFiles2/"},windowInject:{title:"微应用模板",links:[],element:{root:{id:"root"}},scripts:[]},server:{port:"8081",host:"192.168.0.187",open:!1},framework:{antd:{"ant-prefix":"micro-temp",fontFamily:"PingFangSC-Regular",colorPrimary:"#1677ff",borderRadius:2}},webpackConfig:{htmlWebpackPluginOption:{inject:!0},resolve:{fallback:{stream:!1}}}};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -33,6 +33,8 @@
|
|||
"dayjs": "^1.11.7",
|
||||
"echarts": "^6.1.0",
|
||||
"history": "^4.10.1",
|
||||
"html2canvas": "^1.4.1",
|
||||
"jspdf": "^2.5.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
|
|
|
|||
Loading…
Reference in New Issue