From 174f720f65497a5b751fbd0d9a244a2843551543 Mon Sep 17 00:00:00 2001 From: FCL Date: Mon, 10 Nov 2025 18:04:12 +0800 Subject: [PATCH] =?UTF-8?q?doc:=E6=96=87=E6=A1=A3=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/中铜技术文档/Seata分布式事务集成参考.md | 158 +++++++++++++ doc/中铜技术文档/计量单位转换使用文档.md | 212 ++++++++++++++++++ .../admin/ReportDocumentMainController.java | 56 ++++- 3 files changed, 425 insertions(+), 1 deletion(-) create mode 100644 doc/中铜技术文档/Seata分布式事务集成参考.md create mode 100644 doc/中铜技术文档/计量单位转换使用文档.md diff --git a/doc/中铜技术文档/Seata分布式事务集成参考.md b/doc/中铜技术文档/Seata分布式事务集成参考.md new file mode 100644 index 0000000..f847a51 --- /dev/null +++ b/doc/中铜技术文档/Seata分布式事务集成参考.md @@ -0,0 +1,158 @@ +[Seata分布式事务集成参考.md](Seata%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E9%9B%86%E6%88%90%E5%8F%82%E8%80%83.md)# Seata 分布式事务集成参考 + +## 一、架构说明 + +### 多环境 Seata 配置管理 + +所有环境的 Seata 配置统一通过 Nacos 管理,本地配置文件不包含 Seata 配置。 + +``` +├── dev 环境: base-server-dev.yaml (dev_tx_group, namespace: dev) +├── test 环境: base-server-test.yaml (test_tx_group, namespace: test) +└── prod 环境: base-server-prod.yaml (prod_tx_group, namespace: prod) + +Seata Server: 172.16.46.63:30088 +``` + +### 为什么使用 IP 直连? + +Nacos 的 namespace 隔离机制导致不同命名空间的应用无法跨 namespace 发现服务。Seata Server 通过 IP 直连(`registry.type=file`)避免此限制,所有环境共享同一个 Seata Server,通过不同的 `tx-service-group` 实现逻辑隔离。 + +--- + +## 二、Nacos 配置 + +在对应环境的 Nacos 配置中心创建配置文件。以 `base-server-dev.yaml` 为例(其他环境只需修改 `tx-service-group`): + +```yaml +seata: + enabled: true + application-id: base-server + tx-service-group: dev_tx_group + enable-auto-data-source-proxy: true + data-source-proxy-mode: AT + registry: + type: file + config: + type: file + service: + vgroupMapping: + default_tx_group: default + dev_tx_group: default + test_tx_group: default + prod_tx_group: default + default: + grouplist: 172.16.46.63:30088 + client: + tm: + defaultGlobalTransactionTimeout: 60000 + undo: + logTable: undo_log + dataValidation: true + logSerialization: jackson +``` + +--- + +## 三、业务集成步骤 + +### 步骤 1:在 pom.xml 中添加依赖 + +Seata 2.4.0 版本已在 `zt-dependencies` 中统一管理。业务模块只需在 `pom.xml` 中添加: + +```xml + + io.seata + seata-spring-boot-starter + +``` + +版本号会从 `zt-dependencies` 继承。 + +### 步骤 2:创建 undo_log 表 + +在业务数据库执行: + +```sql +CREATE TABLE "UNDO_LOG" ( + "BRANCH_ID" BIGINT NOT NULL, + "XID" VARCHAR(128) NOT NULL, + "CONTEXT" VARCHAR(128) NOT NULL, + "ROLLBACK_INFO" BLOB NOT NULL, + "LOG_STATUS" INT NOT NULL, + "LOG_CREATED" DATETIME DEFAULT SYSDATE, + "LOG_MODIFIED" DATETIME DEFAULT SYSDATE, + PRIMARY KEY ("BRANCH_ID") +); + +CREATE UNIQUE INDEX "UX_UNDO_LOG" ON "UNDO_LOG" ("XID", "BRANCH_ID"); +``` + +### 步骤 3:在 Service 方法上添加 @GlobalTransactional 注解 + +```java +import io.seata.spring.annotation.GlobalTransactional; + +@Service +public class OrderServiceImpl implements OrderService { + + @GlobalTransactional(name = "create-order", rollbackFor = Exception.class) + @Override + public Long createOrder(OrderCreateReqVO req) { + // 本地操作 + orderMapper.insert(order); + + // 跨服务调用自动参与分布式事务 + inventoryApi.deduct(productId, quantity); + + return order.getId(); + } +} +``` + +**说明**: +- 只在事务发起方添加注解 +- 被调用的其他服务自动参与,无需额外配置 +- Seata 自动通过 HTTP Header 传递事务 ID + +--- + +## 四、配置汇总 + +| 配置项 | 开发环境 | 测试环境 | 生产环境 | +|--------|---------|---------|---------| +| **tx-service-group** | `dev_tx_group` | `test_tx_group` | `prod_tx_group` | +| **Nacos 命名空间** | `hwc` | `test` | `prod` | +| **Seata Server** | `172.16.46.63:30088` | `172.16.46.63:30088` | `172.16.46.63:30088` | + +--- + +## 五、其他事务模式 + +当前配置默认使用 **AT 模式**(自动事务模式)。Seata 还支持其他事务模式,需要业务自己实现: + +### TCC 模式 + +需要实现 Try、Confirm、Cancel 三个业务方法,使用 `@TwoPhaseBusinessAction` 注解标记。详见官方文档。 + +### Saga 模式 + +需要定义 Saga 流程和状态机。详见官方文档。 + +### XA 模式 + +需要数据库支持 XA 事务,配置中修改 `data-source-proxy-mode: XA`。 + +**详见**: https://seata.apache.org/zh-cn/docs/overview/what-is-seata + +--- + +## 六、Seata 控制台 + +**地址**: `http://172.16.46.63:30087` + +可查看全局事务、分支事务、全局锁等监控信息。 + +--- + +**官方文档**: https://seata.apache.org/zh-cn/docs/overview/what-is-seata diff --git a/doc/中铜技术文档/计量单位转换使用文档.md b/doc/中铜技术文档/计量单位转换使用文档.md new file mode 100644 index 0000000..afee935 --- /dev/null +++ b/doc/中铜技术文档/计量单位转换使用文档.md @@ -0,0 +1,212 @@ +# 计量单位转换业务使用文档 + +## 一、系统概述 + +计量单位转换提供统一的计量单位转换服务,支持同一量纲内的单位自动转换。采用**单向配置、双向生效**机制,只需配置"非基准单位 → 基准单位"的转换规则,自动推导反向和间接转换。 + +**核心特性**: +- 单向配置、双向生效的转换机制 +- 支持按单位ID、符号、名称进行转换 +- 高精度计算,支持批量操作 +- 跨模块统一服务 + +## 二、内容配置 + +### 2.1 管理菜单路径 +后台管理 → 基础管理 → 计量单位 → 计量单位管理 + +### 2.2 配置功能 + +#### 计量量纲管理 +- **功能**:创建和管理不同的量纲类型(如重量、长度、体积等) +- **操作**:新增量纲、编辑量纲信息、删除量纲 +- **每个量纲只能设置一个基准单位** + +#### 计量单位管理 +- **功能**:创建和管理具体的计量单位 +- **操作**:新增单位、编辑单位信息、删除单位 +- **关联量纲**:将单位归属到具体的量纲下 + +#### 转换规则配置 +- **功能**:配置单位间的转换规则 +- **配置原则**:只需配置"非基准单位 → 基准单位" +- **自动推导**:系统自动推导反向转换和间接转换 + +### 2.3 配置建议 + +1. **量纲规划**:提前规划好业务需要的量纲类型 +2. **基准单位选择**:选择业务中最常用、最稳定的单位作为基准 +3. **转换规则**:优先使用整数转换系数,提高计算精度 +4. **定期校验**:使用转换路径校验功能确保配置正确性 +5. **转换完整性校验**:系统提供同一量纲内所有单位能否互相转换的校验功能,确保转换配置的完整性 + +## 三、API接口清单 + +### 3.1 单位管理接口 + +| 接口 | 方法 | 路径 | 说明 | API文档 | +|------|------|------|------|---------| +| 获取量纲树 | GET | `/admin-api/base/unit-management/unit-quantity/tree` | 获取量纲和单位树形结构 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E8%AE%A1%E9%87%8F%E5%8D%95%E4%BD%8D%E9%87%8F/getUnitQuantityTree) | +| 获取单位列表 | GET | `/admin-api/base/unit-management/unt-info/page` | 获取单位列表(用于下拉选择) | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E8%AE%A1%E9%87%8F%E5%8D%95%E4%BD%8D/getUntInfoPage) | + +### 3.2 单位转换接口 + +| 接口 | 方法 | 路径 | 说明 | API文档 | +|------|------|------|------|---------| +| 按ID转换单位 | POST | `/admin-api/base/unit-management/unit-conversion/convert` | 通过单位ID转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/convert) | +| 按符号转换单位 | POST | `/admin-api/base/unit-management/unit-conversion/convert-by-symbol` | 通过单位符号转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/convertBySymbol) | +| 按名称转换单位 | POST | `/admin-api/base/unit-management/unit-conversion/convert-by-name` | 通过单位名称转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/convertByName) | +| 批量ID转换 | POST | `/admin-api/base/unit-management/unit-conversion/batch-convert` | 按ID批量转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/batchConvert) | +| 批量符号转换 | POST | `/admin-api/base/unit-management/unit-conversion/batch-convert-by-symbol` | 按符号批量转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/batchConvertBySymbol) | +| 批量名称转换 | POST | `/admin-api/base/unit-management/unit-conversion/batch-convert-by-name` | 按名称批量转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/batchConvertByName) | + + +--- + +## 三、后端业务调用示例 + +### base模块内使用 + +```java +@Service +public class PurchaseOrderServiceImpl { + + @Resource + private UnitConversionService unitConversionService; + + /** + * 处理采购订单,统一转换为千克计算 + */ + public void processPurchaseOrder(PurchaseOrderSaveReqVO orderVO) { + for (PurchaseOrderDetailVO detail : orderVO.getDetails()) { + // 方式1:按符号转换 + UnitConvertBySymbolReqVO convertReq = new UnitConvertBySymbolReqVO(); + convertReq.setSrcUnitSymbol(detail.getUnt()); + convertReq.setTgtUnitSymbol("kg"); + convertReq.setValue(detail.getQty()); + convertReq.setPrecision(6); + + UnitConvertRespVO result = unitConversionService.convertBySymbol(convertReq); + BigDecimal standardQuantity = result.getConvertedValue(); + + // 方式2:按ID转换(如果有单位ID) + // UnitConvertReqVO convertReq = new UnitConvertReqVO(); + // convertReq.setSrcUntId(detail.getUntId()); + // convertReq.setTgtUntId(kgUnitId); + // ... + } + } +} +``` + +--- + +## 四、跨模块调用 + +### 4.1 直接Service调用(推荐) + +在同一服务内直接注入使用: +```java +@Resource +private UnitConversionService unitConversionService; +``` + +### 4.2 跨服务调用(按需使用) + +**1. 在 API 模块中定义 Feign 接口:** + +```java +package com.zt.plat.module.base.api; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.base.enums.ApiConstants; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@FeignClient(name = ApiConstants.NAME) +@Tag(name = "RPC 服务 - 单位转换") +public interface UnitConversionApi { + + String PREFIX = ApiConstants.PREFIX + "/unit-conversion"; + + @PostMapping(PREFIX + "/convert") + @Operation(summary = "按ID转换单位") + CommonResult convert(@RequestBody UnitConvertReqVO reqVO); + + @PostMapping(PREFIX + "/convert-by-symbol") + @Operation(summary = "按符号转换单位") + CommonResult convertBySymbol(@RequestBody UnitConvertBySymbolReqVO reqVO); +} +``` + +**2. 在其他服务中调用:** + +```java +@Service +public class PurchaseServiceImpl { + + @Resource + private UnitConversionApi unitConversionApi; + + public void processPurchase(PurchaseVO purchase) { + UnitConvertBySymbolReqVO convertReq = new UnitConvertBySymbolReqVO(); + convertReq.setSrcUnitSymbol(purchase.getUnit()); + convertReq.setTgtUnitSymbol("kg"); + convertReq.setValue(purchase.getQuantity()); + convertReq.setPrecision(6); + + CommonResult result = unitConversionApi.convertBySymbol(convertReq); + if (result.isSuccess()) { + BigDecimal standardQty = result.getData().getConvertedValue(); + // 业务处理 + } + } +} +``` + +--- + +## 五、前端使用 + +### 5.1 基本API调用 + +```typescript +// 获取量纲树 +export const getUnitQuantityTree = () => { + return request.get('/admin-api/base/unit-management/unit-quantity/tree') +} + +// 获取单位列表 +export const getUntInfoPage = (params: any) => { + return request.get('/admin-api/base/unit-management/unt-info/page', { params }) +} + +// 单位转换 +export const convertUnitBySymbol = (data: any) => { + return request.post('/admin-api/base/unit-management/unit-conversion/convert-by-symbol', data) +} +``` + +--- + +## 六、常见问题 + +**Q1: 前端如何获取单位选项?** + +A: 使用 `/admin-api/base/unit-management/unit-quantity/tree` 获取量纲树,然后根据选择的量纲调用 `/admin-api/base/unit-management/unt-info/page` 获取单位列表。 + +**Q2: 按ID转换和按符号转换哪个更好?** + +A: 按ID转换更稳定,因为数据库ID不会变化。建议在前端保存单位ID,在业务转换时使用ID调用。 + +**Q3: 跨服务调用需要特殊配置吗?** + +A: 不需要,项目已经统一配置好。所有Feign客户端都使用 `name = "base-server"`,路径使用 `/rpc-api` 前缀。 + +**Q4: 批量转换性能问题?** + +A: 使用批量接口,设置ignoreErrors=true。 + diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java index 7c39b56..b9b3a0a 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java @@ -14,8 +14,10 @@ import com.zt.plat.framework.excel.core.util.ExcelUtils; import com.zt.plat.module.qms.business.config.dal.dataobject.ConfigUserSignatureDO; import com.zt.plat.module.qms.business.config.service.ConfigUserSignatureService; import com.zt.plat.module.qms.business.reportdoc.controller.vo.*; +import com.zt.plat.module.qms.business.reportdoc.dal.dataobject.ReportDocumentDataDO; import com.zt.plat.module.qms.business.reportdoc.dal.dataobject.ReportDocumentMainDO; import com.zt.plat.module.qms.business.reportdoc.dal.dataobject.ReportDocumentTypeDO; +import com.zt.plat.module.qms.business.reportdoc.service.ReportDocumentDataService; import com.zt.plat.module.qms.business.reportdoc.service.ReportDocumentMainService; import com.zt.plat.module.qms.business.reportdoc.service.ReportDocumentTypeService; import com.zt.plat.module.qms.enums.QmsCommonConstant; @@ -32,7 +34,9 @@ import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static com.zt.plat.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static com.zt.plat.framework.common.pojo.CommonResult.error; @@ -54,6 +58,7 @@ public class ReportDocumentMainController extends AbstractFileUploadController i } @Resource private ReportDocumentMainService reportDocumentMainService; + @Resource private ReportDocumentDataService reportDocumentDataService; @Resource private ReportDocumentTypeService reportDocumentTypeService; @Resource private ConfigUserSignatureService configUserSignatureService; @@ -149,7 +154,7 @@ public class ReportDocumentMainController extends AbstractFileUploadController i @Operation(summary = "获得检测报告业务") @Parameter(name = "id", description = "编号", required = true, example = "1024") //@PreAuthorize("@ss.hasPermission('qms:report-document-main:query')") - public CommonResult getReportDocumentMain(@RequestParam("id") Long id) { + public CommonResult getReportDocumentMain(@RequestParam("id") Long id, @RequestParam(value = "editFlag", required = false, defaultValue = "false") String editFlag) { ReportDocumentMainDO reportDocumentMain = reportDocumentMainService.getReportDocumentMain(id); ReportDocumentMainRespVO vo = BeanUtils.toBean(reportDocumentMain, ReportDocumentMainRespVO.class); @@ -183,9 +188,58 @@ public class ReportDocumentMainController extends AbstractFileUploadController i vo.setDocumentSignature(docSigJson.toJSONString()); } + //处理抬头数据 + + if("true".equals(editFlag)){ + String formData = vo.getFormData(); + JSONObject formDataJson = new JSONObject(); + if(!ObjectUtils.isEmpty(formData)) + formDataJson = JSONObject.parseObject(formData); + List dataList = reportDocumentDataService.listByMainDataId(id).getData(); + ReportDocumentTypeDO typeDO = reportDocumentTypeService.getReportDocumentType(reportDocumentMain.getReportDocumentTypeId()); + String customConfig = typeDO.getCustomConfig(); + String defaultConclusion = ""; + if(!ObjectUtils.isEmpty(customConfig)){ + JSONObject config = JSONObject.parseObject(customConfig); + defaultConclusion = config.getString("defaultConclusion"); + } + formDataJson.put("conclusion", defaultConclusion); + if(!dataList.isEmpty()) + formDataJson.put("sampleName", dataList.get(0).getSampleName()); + //处理检测标准 + String standard = assembleStandard(dataList); + formDataJson.put("standard", standard); + vo.setFormData(formDataJson.toJSONString()); +// ReportDocumentMainSaveReqVO updateVO = new ReportDocumentMainSaveReqVO(); +// updateVO.setId(reportDocumentMain.getId()); +// updateVO.setFormData(formDataJson.toJSONString()); +// reportDocumentMainService.updateReportDocumentMain(updateVO); + } return success(vo); } + private String assembleStandard(List dataList){ + + Set standardSet = new HashSet<>(); + for(ReportDocumentDataDO data : dataList){ + String content = data.getDocumentContent(); + if(ObjectUtils.isEmpty( content)) + continue; + JSONObject json = JSONObject.parseObject(content); + for(String key : json.keySet()){ + JSONObject obj = json.getJSONObject(key); + String methodName = obj.getString("methodName"); + if(ObjectUtils.isEmpty(methodName)) + continue; + standardSet.add(methodName); + } + } + if(standardSet.isEmpty()) + return ""; + return String.join(",", standardSet); + } + + @GetMapping("/page") @Operation(summary = "获得检测报告业务分页") //@PreAuthorize("@ss.hasPermission('qms:report-document-main:query')")