commit 3b70da2f0b7c4c2d0fc47da22daf0ba34b1a1d92 Author: zhouxiaofeng <2946471396@qq.com> Date: Tue May 26 10:08:21 2026 +0800 首次提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8368a --- /dev/null +++ b/.gitignore @@ -0,0 +1,47 @@ +###################################################################### +# Build Tools + +.gradle +/build/ +!gradle/wrapper/gradle-wrapper.jar + +target/ +!.mvn/wrapper/maven-wrapper.jar + +###################################################################### +# IDE + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### JRebel ### +rebel.xml + +### NetBeans ### +nbproject/private/ +build/* +nbbuild/ +dist/ +nbdist/ +.nb-gradle/ + +###################################################################### +# Others +*.log +*.xml.versionsBackup +*.swp + +!*/build/*.java +!*/build/*.html +!*/build/*.xml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ba835b9 --- /dev/null +++ b/pom.xml @@ -0,0 +1,123 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + + com.nbport + zgwl-end + 1.0.0-SNAPSHOT + zgwl-end + zgwl-end single module demo + jar + + + 17 + 3.5.11 + UTF-8 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-websocket + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.2.0 + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-jsqlparser + ${mybatis-plus.version} + + + com.mysql + mysql-connector-j + runtime + + + com.oracle.database.jdbc + ojdbc11 + runtime + + + cn.hutool + hutool-all + 5.8.16 + + + com.alibaba + easyexcel + 3.3.2 + + + org.apache.xmlbeans + xmlbeans + 3.1.0 + + + com.alibaba.fastjson2 + fastjson2 + 2.0.47 + + + net.coobird + thumbnailator + 0.4.8 + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + 1.18.32 + + + + + + + diff --git a/src/main/java/com/nbport/zgwl/ZgwlEndApplication.java b/src/main/java/com/nbport/zgwl/ZgwlEndApplication.java new file mode 100644 index 0000000..d1efab7 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/ZgwlEndApplication.java @@ -0,0 +1,18 @@ +package com.nbport.zgwl; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@MapperScan({ + "com.nbport.zgwl.mapper", + "com.nbport.zgwl.partner.mapper", + "com.nbport.zgwl.manifest.mapper" +}) +@SpringBootApplication +public class ZgwlEndApplication { + + public static void main(String[] args) { + SpringApplication.run(ZgwlEndApplication.class, args); + } +} diff --git a/src/main/java/com/nbport/zgwl/common/ErrorCode.java b/src/main/java/com/nbport/zgwl/common/ErrorCode.java new file mode 100644 index 0000000..25910df --- /dev/null +++ b/src/main/java/com/nbport/zgwl/common/ErrorCode.java @@ -0,0 +1,21 @@ +package com.nbport.zgwl.common; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ErrorCode implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String code; + + private String msg; +} diff --git a/src/main/java/com/nbport/zgwl/common/GlobalErrorCodeConstants.java b/src/main/java/com/nbport/zgwl/common/GlobalErrorCodeConstants.java new file mode 100644 index 0000000..53ecac6 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/common/GlobalErrorCodeConstants.java @@ -0,0 +1,14 @@ +package com.nbport.zgwl.common; + +import com.nbport.zgwl.exception.ServiceErrorCodeRange; + +public interface GlobalErrorCodeConstants { + + ErrorCode SUCCESS = new ErrorCode("0000", "成功"); + ErrorCode ERROR = new ErrorCode("9999", "失败"); + ErrorCode USER_NOT_FOUND = new ErrorCode(ServiceErrorCodeRange.USER_NOT_FOUND, "用户不存在"); + ErrorCode USERNAME_OR_PWD_ERROR = new ErrorCode(ServiceErrorCodeRange.USERNAME_OR_PWD_ERROR, "用户名或密码错误"); + ErrorCode USER_NOT_FOUND_OR_NO_DEFAULT = new ErrorCode(ServiceErrorCodeRange.USER_NOT_FOUND, "用户不存在或多租户未配置"); + ErrorCode UNKNOWN = new ErrorCode(ServiceErrorCodeRange.DEFAULT, "未知错误"); + ErrorCode PARAMS_ERROR = new ErrorCode(ServiceErrorCodeRange.DEFAULT, "参数不正确"); +} diff --git a/src/main/java/com/nbport/zgwl/common/PageResult.java b/src/main/java/com/nbport/zgwl/common/PageResult.java new file mode 100644 index 0000000..3b5c5d1 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/common/PageResult.java @@ -0,0 +1,36 @@ +package com.nbport.zgwl.common; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +public class PageResult implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long pageNo; + + private Long pageSize; + + private Long total; + + private Long totalPage; + + private List records = new ArrayList<>(); + + public PageResult(IPage page) { + this.pageNo = page.getCurrent(); + this.pageSize = page.getSize(); + this.total = page.getTotal(); + this.totalPage = page.getPages(); + this.records = page.getRecords(); + } +} diff --git a/src/main/java/com/nbport/zgwl/common/R.java b/src/main/java/com/nbport/zgwl/common/R.java new file mode 100644 index 0000000..618994d --- /dev/null +++ b/src/main/java/com/nbport/zgwl/common/R.java @@ -0,0 +1,158 @@ +package com.nbport.zgwl.common; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonView; +import com.nbport.zgwl.exception.ServiceErrorCodeRange; +import com.nbport.zgwl.exception.ServiceException; +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Objects; + +public class R implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @Schema(description = "响应状态码", example = "0000") + @JsonView(Views.Public.class) + private String code; + + @Schema(description = "响应数据") + @JsonView(Views.Public.class) + private T result; + + @Schema(description = "响应消息", example = "操作成功") + @JsonView(Views.Public.class) + private String message; + + public static R error(R result) { + return error(result.getCode(), result.getMessage()); + } + + public static R error(String code, String message) { + if (Objects.equals(GlobalErrorCodeConstants.SUCCESS.getCode(), code)) { + throw new ServiceException(ServiceErrorCodeRange.DEFAULT, "code 必须是错误的!"); + } + R result = new R<>(); + result.code = code; + result.message = message; + return result; + } + + public static R error(ErrorCode errorCode) { + return error(errorCode.getCode(), errorCode.getMsg()); + } + + public static R success(T data) { + R result = new R<>(); + result.code = GlobalErrorCodeConstants.SUCCESS.getCode(); + result.result = data; + result.message = ""; + return result; + } + + public static R success(String message) { + R result = new R<>(); + result.code = GlobalErrorCodeConstants.SUCCESS.getCode(); + result.result = null; + result.message = message; + return result; + } + + public static R success(String code, String message) { + R result = new R<>(); + result.code = code; + result.result = null; + result.message = message; + return result; + } + + public static R error(String message) { + R result = new R<>(); + result.code = GlobalErrorCodeConstants.ERROR.getCode(); + result.result = null; + result.message = message; + return result; + } + + public static R success() { + R result = new R<>(); + result.code = GlobalErrorCodeConstants.SUCCESS.getCode(); + result.result = null; + result.message = ""; + return result; + } + + public static R data(String message, T data) { + R result = new R<>(); + result.code = GlobalErrorCodeConstants.SUCCESS.getCode(); + result.result = data; + result.message = message; + return result; + } + + public static R data(T data) { + R result = new R<>(); + result.code = GlobalErrorCodeConstants.SUCCESS.getCode(); + result.result = data; + result.message = null; + return result; + } + + public static boolean isSuccess(String code) { + return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode()); + } + + @JsonIgnore + public boolean isSuccess() { + return isSuccess(code); + } + + @JsonIgnore + public boolean isError() { + return !isSuccess(); + } + + public void checkError() throws ServiceException { + if (isSuccess()) { + return; + } + throw new ServiceException(code, message); + } + + @JsonIgnore + public T getCheckedData() { + checkError(); + return result; + } + + public static R error(ServiceException serviceException) { + return error(serviceException.getCode(), serviceException.getMessage()); + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/src/main/java/com/nbport/zgwl/common/Views.java b/src/main/java/com/nbport/zgwl/common/Views.java new file mode 100644 index 0000000..dbabad3 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/common/Views.java @@ -0,0 +1,10 @@ +package com.nbport.zgwl.common; + +public class Views { + + private Views() { + } + + public interface Public { + } +} diff --git a/src/main/java/com/nbport/zgwl/config/MybatisPlusConfig.java b/src/main/java/com/nbport/zgwl/config/MybatisPlusConfig.java new file mode 100644 index 0000000..372b152 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/config/MybatisPlusConfig.java @@ -0,0 +1,39 @@ +package com.nbport.zgwl.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MybatisPlusConfig { + + @Value("${spring.datasource.url:}") + private String datasourceUrl; + + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + PaginationInnerInterceptor paginationInnerInterceptor = + new PaginationInnerInterceptor(resolveDbType(datasourceUrl)); + paginationInnerInterceptor.setMaxLimit(500L); + interceptor.addInnerInterceptor(paginationInnerInterceptor); + return interceptor; + } + + private DbType resolveDbType(String jdbcUrl) { + if (jdbcUrl == null) { + return DbType.MYSQL; + } + String url = jdbcUrl.toLowerCase(); + if (url.startsWith("jdbc:oracle:")) { + return DbType.ORACLE; + } + if (url.startsWith("jdbc:mysql:")) { + return DbType.MYSQL; + } + return DbType.MYSQL; + } +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/config/ContractPushConfiguration.java b/src/main/java/com/nbport/zgwl/contractpush/config/ContractPushConfiguration.java new file mode 100644 index 0000000..c19deeb --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/config/ContractPushConfiguration.java @@ -0,0 +1,19 @@ +package com.nbport.zgwl.contractpush.config; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestTemplate; + +/** + * 推送模块基础配置 + */ +@Configuration +@EnableConfigurationProperties(ContractPushProperties.class) +public class ContractPushConfiguration { + + @Bean + public RestTemplate contractPushRestTemplate() { + return new RestTemplate(); + } +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/config/ContractPushProperties.java b/src/main/java/com/nbport/zgwl/contractpush/config/ContractPushProperties.java new file mode 100644 index 0000000..f1c2ba1 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/config/ContractPushProperties.java @@ -0,0 +1,66 @@ +package com.nbport.zgwl.contractpush.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 合同推送相关配置 + * 默认从 application.yml 读取测试地址 + * 生产环境可以通过 application-prod.yml 覆盖 + */ +@Data +@ConfigurationProperties(prefix = "contract-push") +public class ContractPushProperties { + + private Settlement settlement = new Settlement(); + + private DataCollection dataCollection = new DataCollection(); + + @Data + public static class Settlement { + + private String systemKey; + + private String secretKey; + + private String tokenUrl; + + private String pushContractUrl; + + private String approveQueryUrl; + + private String archiveUrl; + + private String approveViewBaseUrl; + } + + @Data + public static class DataCollection { + + private String systemKey; + + private String secretKey; + + private String sysCode; + + private String tokenUrl; + + private String uploadUrl; + + private String collectOppositesUrl; + + private String collectZtbsUrl; + + private String collectContractUrl; + + private String collectPlanFeedbackUrl; + + private String collectContractCloseUrl; + + private String collectContractArchiveUrl; + + private String queryApproveInfoUrl; + + private String approveViewBaseUrl; + } +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/controller/DataCollectionPushController.java b/src/main/java/com/nbport/zgwl/contractpush/controller/DataCollectionPushController.java new file mode 100644 index 0000000..dc12d8e --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/controller/DataCollectionPushController.java @@ -0,0 +1,84 @@ +package com.nbport.zgwl.contractpush.controller; + +import com.nbport.zgwl.common.R; +import com.nbport.zgwl.contractpush.dto.ApproveQueryRequestDTO; +import com.nbport.zgwl.contractpush.dto.ApproveViewUrlRequestDTO; +import com.nbport.zgwl.contractpush.dto.PushPayloadDTO; +import com.nbport.zgwl.contractpush.dto.PushTokenRequestDTO; +import com.nbport.zgwl.contractpush.service.DataCollectionPushService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.Map; + +/** + * 数据采集推送相关接口 + * 对应《数据采集接口规范V3.0》 + */ +@RestController +@RequestMapping("/contract/push/data-collection") +public class DataCollectionPushController { + + private final DataCollectionPushService dataCollectionPushService; + + public DataCollectionPushController(DataCollectionPushService dataCollectionPushService) { + this.dataCollectionPushService = dataCollectionPushService; + } + + @GetMapping("/token") + public R> getToken(PushTokenRequestDTO requestDTO) { + return R.success(dataCollectionPushService.getToken(requestDTO)); + } + + @PostMapping("/upload") + public R> upload(@RequestPart("file") MultipartFile file, + @RequestParam(value = "token", required = false) String token) { + return R.success(dataCollectionPushService.upload(file, token)); + } + + @PostMapping("/opposites") + public R> collectOpposites(@RequestBody PushPayloadDTO requestDTO) { + return R.success(dataCollectionPushService.collectOpposites(requestDTO)); + } + + @PostMapping("/ztbs") + public R> collectZtbs(@RequestBody PushPayloadDTO requestDTO) { + return R.success(dataCollectionPushService.collectZtbs(requestDTO)); + } + + @PostMapping("/contract") + public R> collectContract(@RequestBody PushPayloadDTO requestDTO) { + return R.success(dataCollectionPushService.collectContract(requestDTO)); + } + + @PostMapping("/plan-feedback") + public R> collectPlanFeedback(@RequestBody PushPayloadDTO requestDTO) { + return R.success(dataCollectionPushService.collectPlanFeedback(requestDTO)); + } + + @PostMapping("/close") + public R> collectContractClose(@RequestBody PushPayloadDTO requestDTO) { + return R.success(dataCollectionPushService.collectContractClose(requestDTO)); + } + + @PostMapping("/archive") + public R> collectContractArchive(@RequestBody PushPayloadDTO requestDTO) { + return R.success(dataCollectionPushService.collectContractArchive(requestDTO)); + } + + @PostMapping("/approve/query") + public R> queryApproveInfo(@RequestBody ApproveQueryRequestDTO requestDTO) { + return R.success(dataCollectionPushService.queryApproveInfo(requestDTO)); + } + + @PostMapping("/approve/view-url") + public R> buildApproveViewUrl(@RequestBody ApproveViewUrlRequestDTO requestDTO) { + return R.success(dataCollectionPushService.buildApproveViewUrl(requestDTO)); + } +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/controller/SettlementPushController.java b/src/main/java/com/nbport/zgwl/contractpush/controller/SettlementPushController.java new file mode 100644 index 0000000..0c69abc --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/controller/SettlementPushController.java @@ -0,0 +1,55 @@ +package com.nbport.zgwl.contractpush.controller; + +import com.nbport.zgwl.common.R; +import com.nbport.zgwl.contractpush.dto.ApproveQueryRequestDTO; +import com.nbport.zgwl.contractpush.dto.ApproveViewUrlRequestDTO; +import com.nbport.zgwl.contractpush.dto.PushPayloadDTO; +import com.nbport.zgwl.contractpush.dto.PushTokenRequestDTO; +import com.nbport.zgwl.contractpush.service.SettlementPushService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * 结算平台推送相关接口 + * 对应《结算平台推送合同接口文档V9.5》 + */ +@RestController +@RequestMapping("/contract/push/settlement") +public class SettlementPushController { + + private final SettlementPushService settlementPushService; + + public SettlementPushController(SettlementPushService settlementPushService) { + this.settlementPushService = settlementPushService; + } + + @GetMapping("/token") + public R> getToken(PushTokenRequestDTO requestDTO) { + return R.success(settlementPushService.getToken(requestDTO)); + } + + @PostMapping("/contract") + public R> pushContract(@RequestBody PushPayloadDTO requestDTO) { + return R.success(settlementPushService.pushContract(requestDTO)); + } + + @PostMapping("/approve/query") + public R> queryApproveInfo(@RequestBody ApproveQueryRequestDTO requestDTO) { + return R.success(settlementPushService.queryApproveInfo(requestDTO)); + } + + @PostMapping("/archive") + public R> pushArchive(@RequestBody PushPayloadDTO requestDTO) { + return R.success(settlementPushService.pushArchive(requestDTO)); + } + + @PostMapping("/approve/view-url") + public R> buildApproveViewUrl(@RequestBody ApproveViewUrlRequestDTO requestDTO) { + return R.success(settlementPushService.buildApproveViewUrl(requestDTO)); + } +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/dto/ApproveQueryRequestDTO.java b/src/main/java/com/nbport/zgwl/contractpush/dto/ApproveQueryRequestDTO.java new file mode 100644 index 0000000..f63fbce --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/dto/ApproveQueryRequestDTO.java @@ -0,0 +1,24 @@ +package com.nbport.zgwl.contractpush.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 审批信息查询 / 单点查看地址拼装请求 + */ +@Data +public class ApproveQueryRequestDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + private String token; + + private String phoneNum; + + private String contractSysId; + + private String contractCode; + + private String contractId; +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/dto/ApproveViewUrlRequestDTO.java b/src/main/java/com/nbport/zgwl/contractpush/dto/ApproveViewUrlRequestDTO.java new file mode 100644 index 0000000..95157a0 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/dto/ApproveViewUrlRequestDTO.java @@ -0,0 +1,18 @@ +package com.nbport.zgwl.contractpush.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 审批查看地址拼装参数 + */ +@Data +public class ApproveViewUrlRequestDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + private String contractId; + + private String ivUser; +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/dto/PushPayloadDTO.java b/src/main/java/com/nbport/zgwl/contractpush/dto/PushPayloadDTO.java new file mode 100644 index 0000000..73fb279 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/dto/PushPayloadDTO.java @@ -0,0 +1,22 @@ +package com.nbport.zgwl.contractpush.dto; + +import lombok.Data; + +import java.io.Serializable; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 通用推送请求体 + * token 不传时,Service 内部会自动获取 + * payload 直接放文档原始 JSON 结构即可 + */ +@Data +public class PushPayloadDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + private String token; + + private Map payload = new LinkedHashMap<>(); +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/dto/PushTokenRequestDTO.java b/src/main/java/com/nbport/zgwl/contractpush/dto/PushTokenRequestDTO.java new file mode 100644 index 0000000..6dfb3aa --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/dto/PushTokenRequestDTO.java @@ -0,0 +1,18 @@ +package com.nbport.zgwl.contractpush.dto; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 获取 token 时可选覆盖默认 systemKey / secretKey + */ +@Data +public class PushTokenRequestDTO implements Serializable { + + private static final long serialVersionUID = 1L; + + private String systemKey; + + private String secretKey; +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/handler/ContractPushRequestHandler.java b/src/main/java/com/nbport/zgwl/contractpush/handler/ContractPushRequestHandler.java new file mode 100644 index 0000000..1d904ef --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/handler/ContractPushRequestHandler.java @@ -0,0 +1,51 @@ +package com.nbport.zgwl.contractpush.handler; + +import cn.hutool.core.util.StrUtil; +import com.nbport.zgwl.exception.ServiceException; +import org.springframework.stereotype.Component; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 推送请求加工器 + * 这里只做最基础的 token、sys_code、时间戳补全,保持逻辑简单 + */ +@Component +public class ContractPushRequestHandler { + + public Map copyPayload(Map payload) { + return payload == null ? new LinkedHashMap<>() : new LinkedHashMap<>(payload); + } + + public Map prepareSettlementPayload(Map payload, String token) { + Map requestBody = copyPayload(payload); + if (StrUtil.isBlank(token)) { + throw new ServiceException("结算平台 token 不能为空"); + } + requestBody.put("token", token); + return requestBody; + } + + public Map prepareDataCollectionPayload(Map payload, String sysCode) { + Map requestBody = copyPayload(payload); + if (StrUtil.isBlank(sysCode)) { + throw new ServiceException("数据采集 sysCode 未配置"); + } + requestBody.put("sys_code", sysCode); + requestBody.putIfAbsent("timeStamp", String.valueOf(System.currentTimeMillis())); + return requestBody; + } + + public Map buildDataCollectionHeaders(String token, String systemKey, boolean includeSystemKey) { + if (StrUtil.isBlank(token)) { + throw new ServiceException("数据采集 cj_token 不能为空"); + } + Map headers = new LinkedHashMap<>(); + headers.put("cj_token", token); + if (includeSystemKey) { + headers.put("systemKey", systemKey); + } + return headers; + } +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/service/DataCollectionPushService.java b/src/main/java/com/nbport/zgwl/contractpush/service/DataCollectionPushService.java new file mode 100644 index 0000000..1497073 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/service/DataCollectionPushService.java @@ -0,0 +1,147 @@ +package com.nbport.zgwl.contractpush.service; + +import cn.hutool.core.util.StrUtil; +import com.nbport.zgwl.contractpush.config.ContractPushProperties; +import com.nbport.zgwl.contractpush.dto.ApproveQueryRequestDTO; +import com.nbport.zgwl.contractpush.dto.ApproveViewUrlRequestDTO; +import com.nbport.zgwl.contractpush.dto.PushPayloadDTO; +import com.nbport.zgwl.contractpush.dto.PushTokenRequestDTO; +import com.nbport.zgwl.contractpush.handler.ContractPushRequestHandler; +import com.nbport.zgwl.contractpush.utils.ContractPushHttpUtils; +import com.nbport.zgwl.contractpush.utils.ContractPushUrlUtils; +import com.nbport.zgwl.exception.ServiceException; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 数据采集推送服务 + * 对应《数据采集接口规范V3.0》 + */ +@Service +public class DataCollectionPushService { + + private final ContractPushProperties properties; + private final ContractPushHttpUtils httpUtils; + private final ContractPushRequestHandler requestHandler; + + public DataCollectionPushService(ContractPushProperties properties, + ContractPushHttpUtils httpUtils, + ContractPushRequestHandler requestHandler) { + this.properties = properties; + this.httpUtils = httpUtils; + this.requestHandler = requestHandler; + } + + public Map getToken(PushTokenRequestDTO requestDTO) { + String systemKey = requestDTO != null && StrUtil.isNotBlank(requestDTO.getSystemKey()) + ? requestDTO.getSystemKey() + : properties.getDataCollection().getSystemKey(); + String secretKey = requestDTO != null && StrUtil.isNotBlank(requestDTO.getSecretKey()) + ? requestDTO.getSecretKey() + : properties.getDataCollection().getSecretKey(); + + Map query = new LinkedHashMap<>(); + query.put("systemKey", systemKey); + query.put("secretKey", secretKey); + return httpUtils.get(properties.getDataCollection().getTokenUrl(), query); + } + + public Map upload(MultipartFile file, String token) { + Map headers = requestHandler.buildDataCollectionHeaders( + resolveCollectionToken(token), + properties.getDataCollection().getSystemKey(), + true + ); + return httpUtils.postMultipart(properties.getDataCollection().getUploadUrl(), file, headers); + } + + public Map collectOpposites(PushPayloadDTO requestDTO) { + return postCollectionJson(properties.getDataCollection().getCollectOppositesUrl(), requestDTO, false); + } + + public Map collectZtbs(PushPayloadDTO requestDTO) { + return postCollectionJson(properties.getDataCollection().getCollectZtbsUrl(), requestDTO, false); + } + + public Map collectContract(PushPayloadDTO requestDTO) { + return postCollectionJson(properties.getDataCollection().getCollectContractUrl(), requestDTO, false); + } + + public Map collectPlanFeedback(PushPayloadDTO requestDTO) { + return postCollectionJson(properties.getDataCollection().getCollectPlanFeedbackUrl(), requestDTO, false); + } + + public Map collectContractClose(PushPayloadDTO requestDTO) { + return postCollectionJson(properties.getDataCollection().getCollectContractCloseUrl(), requestDTO, false); + } + + public Map collectContractArchive(PushPayloadDTO requestDTO) { + return postCollectionJson(properties.getDataCollection().getCollectContractArchiveUrl(), requestDTO, false); + } + + public Map queryApproveInfo(ApproveQueryRequestDTO requestDTO) { + Map body = new LinkedHashMap<>(); + body.put("contractCode", requestDTO == null ? null : requestDTO.getContractCode()); + body.put("contractId", requestDTO == null ? null : requestDTO.getContractId()); + body.put("phoneNum", requestDTO == null ? null : requestDTO.getPhoneNum()); + + Map headers = requestHandler.buildDataCollectionHeaders( + resolveCollectionToken(requestDTO == null ? null : requestDTO.getToken()), + properties.getDataCollection().getSystemKey(), + true + ); + Map requestBody = requestHandler.prepareDataCollectionPayload( + body, + properties.getDataCollection().getSysCode() + ); + return httpUtils.postJson(properties.getDataCollection().getQueryApproveInfoUrl(), requestBody, headers); + } + + public Map buildApproveViewUrl(ApproveViewUrlRequestDTO requestDTO) { + if (requestDTO == null || StrUtil.isBlank(requestDTO.getContractId()) || StrUtil.isBlank(requestDTO.getIvUser())) { + throw new ServiceException("contractId 和 ivUser 不能为空"); + } + Map result = new LinkedHashMap<>(); + result.put( + "approveViewUrl", + ContractPushUrlUtils.buildApproveViewUrl( + properties.getDataCollection().getApproveViewBaseUrl(), + requestDTO.getContractId(), + requestDTO.getIvUser() + ) + ); + return result; + } + + private Map postCollectionJson(String url, PushPayloadDTO requestDTO, boolean includeSystemKeyHeader) { + String token = resolveCollectionToken(requestDTO == null ? null : requestDTO.getToken()); + Map headers = requestHandler.buildDataCollectionHeaders( + token, + properties.getDataCollection().getSystemKey(), + includeSystemKeyHeader + ); + Map requestBody = requestHandler.prepareDataCollectionPayload( + requestDTO == null ? null : requestDTO.getPayload(), + properties.getDataCollection().getSysCode() + ); + return httpUtils.postJson(url, requestBody, headers); + } + + private String resolveCollectionToken(String inputToken) { + if (StrUtil.isNotBlank(inputToken)) { + return inputToken; + } + Map tokenResult = getToken(new PushTokenRequestDTO()); + Object data = tokenResult.get("data"); + if (data instanceof Map) { + Object accessToken = ((Map) data).get("access_token"); + if (accessToken != null) { + return String.valueOf(accessToken); + } + } + throw new ServiceException("获取数据采集 token 失败"); + } +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/service/SettlementPushService.java b/src/main/java/com/nbport/zgwl/contractpush/service/SettlementPushService.java new file mode 100644 index 0000000..2761420 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/service/SettlementPushService.java @@ -0,0 +1,103 @@ +package com.nbport.zgwl.contractpush.service; + +import cn.hutool.core.util.StrUtil; +import com.nbport.zgwl.contractpush.config.ContractPushProperties; +import com.nbport.zgwl.contractpush.dto.ApproveQueryRequestDTO; +import com.nbport.zgwl.contractpush.dto.ApproveViewUrlRequestDTO; +import com.nbport.zgwl.contractpush.dto.PushPayloadDTO; +import com.nbport.zgwl.contractpush.dto.PushTokenRequestDTO; +import com.nbport.zgwl.contractpush.handler.ContractPushRequestHandler; +import com.nbport.zgwl.contractpush.utils.ContractPushHttpUtils; +import com.nbport.zgwl.contractpush.utils.ContractPushUrlUtils; +import com.nbport.zgwl.exception.ServiceException; +import org.springframework.stereotype.Service; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 结算平台推送服务 + * 对应《结算平台推送合同接口文档V9.5》 + */ +@Service +public class SettlementPushService { + + private final ContractPushProperties properties; + private final ContractPushHttpUtils httpUtils; + private final ContractPushRequestHandler requestHandler; + + public SettlementPushService(ContractPushProperties properties, + ContractPushHttpUtils httpUtils, + ContractPushRequestHandler requestHandler) { + this.properties = properties; + this.httpUtils = httpUtils; + this.requestHandler = requestHandler; + } + + public Map getToken(PushTokenRequestDTO requestDTO) { + String systemKey = requestDTO != null && StrUtil.isNotBlank(requestDTO.getSystemKey()) + ? requestDTO.getSystemKey() + : properties.getSettlement().getSystemKey(); + + Map query = new LinkedHashMap<>(); + query.put("systemKey", systemKey); + return httpUtils.get(properties.getSettlement().getTokenUrl(), query); + } + + public Map pushContract(PushPayloadDTO requestDTO) { + String token = resolveSettlementToken(requestDTO == null ? null : requestDTO.getToken()); + Map body = requestHandler.prepareSettlementPayload( + requestDTO == null ? null : requestDTO.getPayload(), + token + ); + return httpUtils.postJson(properties.getSettlement().getPushContractUrl(), body, null); + } + + public Map queryApproveInfo(ApproveQueryRequestDTO requestDTO) { + Map body = new LinkedHashMap<>(); + body.put("token", resolveSettlementToken(requestDTO == null ? null : requestDTO.getToken())); + body.put("phoneNum", requestDTO == null ? null : requestDTO.getPhoneNum()); + body.put("contractSysId", requestDTO == null ? null : requestDTO.getContractSysId()); + return httpUtils.postJson(properties.getSettlement().getApproveQueryUrl(), body, null); + } + + public Map pushArchive(PushPayloadDTO requestDTO) { + String token = resolveSettlementToken(requestDTO == null ? null : requestDTO.getToken()); + Map body = requestHandler.prepareSettlementPayload( + requestDTO == null ? null : requestDTO.getPayload(), + token + ); + return httpUtils.postJson(properties.getSettlement().getArchiveUrl(), body, null); + } + + public Map buildApproveViewUrl(ApproveViewUrlRequestDTO requestDTO) { + if (requestDTO == null || StrUtil.isBlank(requestDTO.getContractId()) || StrUtil.isBlank(requestDTO.getIvUser())) { + throw new ServiceException("contractId 和 ivUser 不能为空"); + } + Map result = new LinkedHashMap<>(); + result.put( + "approveViewUrl", + ContractPushUrlUtils.buildApproveViewUrl( + properties.getSettlement().getApproveViewBaseUrl(), + requestDTO.getContractId(), + requestDTO.getIvUser() + ) + ); + return result; + } + + private String resolveSettlementToken(String inputToken) { + if (StrUtil.isNotBlank(inputToken)) { + return inputToken; + } + Map tokenResult = getToken(new PushTokenRequestDTO()); + Object data = tokenResult.get("data"); + if (data instanceof Map) { + Object accessToken = ((Map) data).get("access_token"); + if (accessToken != null) { + return String.valueOf(accessToken); + } + } + throw new ServiceException("获取结算平台 token 失败"); + } +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/utils/ContractPushHttpUtils.java b/src/main/java/com/nbport/zgwl/contractpush/utils/ContractPushHttpUtils.java new file mode 100644 index 0000000..fae4a8b --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/utils/ContractPushHttpUtils.java @@ -0,0 +1,101 @@ +package com.nbport.zgwl.contractpush.utils; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.TypeReference; +import com.nbport.zgwl.exception.ServiceException; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * 最简单的 HTTP 调用工具 + * 当前只做 GET / POST JSON / POST MULTIPART 三种 + */ +@Component +public class ContractPushHttpUtils { + + private final RestTemplate restTemplate; + + public ContractPushHttpUtils(RestTemplate contractPushRestTemplate) { + this.restTemplate = contractPushRestTemplate; + } + + public Map get(String url, Map queryParams) { + String finalUrl = ContractPushUrlUtils.appendQuery(url, queryParams); + ResponseEntity response = restTemplate.getForEntity(finalUrl, String.class); + return parseResponse(response.getBody()); + } + + public Map postJson(String url, Map body, Map headers) { + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.APPLICATION_JSON); + addHeaders(httpHeaders, headers); + HttpEntity> entity = new HttpEntity<>(body, httpHeaders); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); + return parseResponse(response.getBody()); + } + + public Map postMultipart(String url, MultipartFile file, Map headers) { + if (file == null || file.isEmpty()) { + throw new ServiceException("上传文件不能为空"); + } + + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA); + addHeaders(httpHeaders, headers); + + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("file", toByteArrayResource(file)); + + HttpEntity> entity = new HttpEntity<>(formData, httpHeaders); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class); + return parseResponse(response.getBody()); + } + + private ByteArrayResource toByteArrayResource(MultipartFile file) { + try { + return new ByteArrayResource(file.getBytes()) { + @Override + public String getFilename() { + return file.getOriginalFilename(); + } + }; + } catch (IOException e) { + throw new ServiceException("读取上传文件失败: " + e.getMessage()); + } + } + + private void addHeaders(HttpHeaders httpHeaders, Map headers) { + if (headers == null || headers.isEmpty()) { + return; + } + headers.forEach(httpHeaders::add); + } + + private Map parseResponse(String responseBody) { + if (responseBody == null || responseBody.trim().isEmpty()) { + return Collections.emptyMap(); + } + try { + return JSON.parseObject(responseBody, new TypeReference>() { + }); + } catch (Exception ex) { + Map fallback = new LinkedHashMap<>(); + fallback.put("raw", responseBody); + return fallback; + } + } +} diff --git a/src/main/java/com/nbport/zgwl/contractpush/utils/ContractPushUrlUtils.java b/src/main/java/com/nbport/zgwl/contractpush/utils/ContractPushUrlUtils.java new file mode 100644 index 0000000..66dc340 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/contractpush/utils/ContractPushUrlUtils.java @@ -0,0 +1,50 @@ +package com.nbport.zgwl.contractpush.utils; + +import cn.hutool.core.util.StrUtil; +import com.nbport.zgwl.exception.ServiceException; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.StringJoiner; + +/** + * URL 拼装工具 + */ +public final class ContractPushUrlUtils { + + private ContractPushUrlUtils() { + } + + public static String appendQuery(String url, Map queryParams) { + if (queryParams == null || queryParams.isEmpty()) { + return url; + } + StringJoiner joiner = new StringJoiner("&"); + queryParams.forEach((key, value) -> { + if (StrUtil.isNotBlank(value)) { + joiner.add(encode(key) + "=" + encode(value)); + } + }); + return url + (url.contains("?") ? "&" : "?") + joiner; + } + + public static String buildApproveViewUrl(String baseUrl, String contractId, String ivUser) { + if (StrUtil.isBlank(baseUrl)) { + throw new ServiceException("审批查看地址未配置"); + } + Map params = new LinkedHashMap<>(); + params.put("id", contractId); + params.put("ssoToken", ivUser); + return appendQuery(baseUrl, params); + } + + private static String encode(String value) { + try { + return URLEncoder.encode(value, "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new ServiceException("URL 编码失败: " + e.getMessage()); + } + } +} diff --git a/src/main/java/com/nbport/zgwl/controller/MinioFileController.java b/src/main/java/com/nbport/zgwl/controller/MinioFileController.java new file mode 100644 index 0000000..ad2eb8b --- /dev/null +++ b/src/main/java/com/nbport/zgwl/controller/MinioFileController.java @@ -0,0 +1,49 @@ +package com.nbport.zgwl.controller; + +import com.nbport.zgwl.common.R; +import com.nbport.zgwl.exception.ServiceException; +import com.nbport.zgwl.service.LocalFileService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.util.CollectionUtils; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import java.util.Map; + +@RestController +@RequestMapping("/trans/minio/file") +@Slf4j +@RequiredArgsConstructor +@Validated +public class MinioFileController { + + private final LocalFileService localFileService; + + @PostMapping(value = "/uploadOutie", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public R uploadOutie(MultipartHttpServletRequest request) { + Map fileMap = request.getFileMap(); + if (CollectionUtils.isEmpty(fileMap)) { + throw new ServiceException("请上传文件"); + } + MultipartFile file = fileMap.values().iterator().next(); + log.info("接收到上传文件请求,fileName={}", file.getOriginalFilename()); + String relativePath = localFileService.upload(file); + return R.success(relativePath); + } + + @GetMapping("/download") + public ResponseEntity download(@RequestParam("fileName") String fileName) { + log.info("接收到文件下载请求,fileName={}", fileName); + return localFileService.download(fileName); + } +} diff --git a/src/main/java/com/nbport/zgwl/controller/StudentController.java b/src/main/java/com/nbport/zgwl/controller/StudentController.java new file mode 100644 index 0000000..5df3e14 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/controller/StudentController.java @@ -0,0 +1,65 @@ +package com.nbport.zgwl.controller; + +import com.nbport.zgwl.common.PageResult; +import com.nbport.zgwl.common.R; +import com.nbport.zgwl.dto.StudentQueryDto; +import com.nbport.zgwl.dto.StudentSaveDto; +import com.nbport.zgwl.entity.StudentEntity; +import com.nbport.zgwl.entity.SysUserEntity; +import com.nbport.zgwl.service.StudentService; +import com.nbport.zgwl.utils.MockSecurityManager; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +@RequestMapping("/student") +@Slf4j +@RequiredArgsConstructor +@Validated +public class StudentController { + + private final StudentService studentService; + private final MockSecurityManager securityManager; + + @PostMapping("/list") + public R>> queryList(@RequestBody StudentQueryDto queryDto) { + SysUserEntity user = securityManager.getLoginUser(); + PageResult> pageResult = studentService.queryList(queryDto, user); + return R.success(pageResult); + } + + @GetMapping("/detail/{id}") + public R detail(@PathVariable("id") Long id) { + return R.success(studentService.detail(id)); + } + + @PostMapping("/add") + public R add(@RequestBody @Valid StudentSaveDto dto) { + log.info("新增学生请求:{}", dto); + studentService.add(dto); + return R.success("新增成功"); + } + + @PostMapping("/update") + public R update(@RequestBody @Valid StudentSaveDto dto) { + log.info("修改学生请求:{}", dto); + studentService.update(dto); + return R.success("修改成功"); + } + + @PostMapping("/delete/{id}") + public R delete(@PathVariable("id") Long id) { + studentService.delete(id); + return R.success("删除成功"); + } +} diff --git a/src/main/java/com/nbport/zgwl/dto/BasePageDto.java b/src/main/java/com/nbport/zgwl/dto/BasePageDto.java new file mode 100644 index 0000000..bb880a6 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/dto/BasePageDto.java @@ -0,0 +1,43 @@ +package com.nbport.zgwl.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class BasePageDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long pageNo = 1L; + + private Long pageSize = 15L; + + private Sorter sorter; + + @Data + @AllArgsConstructor + @NoArgsConstructor + @Builder + public static class Sorter implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + public static final String ASC = "asc"; + + public static final String DESC = "desc"; + + private String field; + + @Builder.Default + private String order = ASC; + } +} diff --git a/src/main/java/com/nbport/zgwl/dto/StudentQueryDto.java b/src/main/java/com/nbport/zgwl/dto/StudentQueryDto.java new file mode 100644 index 0000000..7ad399e --- /dev/null +++ b/src/main/java/com/nbport/zgwl/dto/StudentQueryDto.java @@ -0,0 +1,21 @@ +package com.nbport.zgwl.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; + +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentQueryDto extends BasePageDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String studentNo; + + private String studentName; + + private String gender; +} diff --git a/src/main/java/com/nbport/zgwl/dto/StudentSaveDto.java b/src/main/java/com/nbport/zgwl/dto/StudentSaveDto.java new file mode 100644 index 0000000..68aedd7 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/dto/StudentSaveDto.java @@ -0,0 +1,37 @@ +package com.nbport.zgwl.dto; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +@Data +public class StudentSaveDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + @NotBlank(message = "学号不能为空") + private String studentNo; + + @NotBlank(message = "姓名不能为空") + private String studentName; + + @NotBlank(message = "性别不能为空") + private String gender; + + @NotNull(message = "年龄不能为空") + @Min(value = 1, message = "年龄不能小于 1") + @Max(value = 120, message = "年龄不能大于 120") + private Integer age; + + private String phone; + + private String address; +} diff --git a/src/main/java/com/nbport/zgwl/entity/BaseEntity.java b/src/main/java/com/nbport/zgwl/entity/BaseEntity.java new file mode 100644 index 0000000..f68972f --- /dev/null +++ b/src/main/java/com/nbport/zgwl/entity/BaseEntity.java @@ -0,0 +1,40 @@ +package com.nbport.zgwl.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.extension.activerecord.Model; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +@Data +@ToString +@NoArgsConstructor +@EqualsAndHashCode(callSuper = false) +@Accessors(chain = true) +public class BaseEntity> extends Model implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @TableField(value = "create_dt", fill = FieldFill.INSERT) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createDt; + + @TableField(value = "update_dt", fill = FieldFill.UPDATE) + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateDt; + + @TableField(value = "create_by", fill = FieldFill.INSERT) + private String createBy; + + @TableField(value = "update_by", fill = FieldFill.UPDATE) + private String updateBy; +} diff --git a/src/main/java/com/nbport/zgwl/entity/StudentEntity.java b/src/main/java/com/nbport/zgwl/entity/StudentEntity.java new file mode 100644 index 0000000..2daaa6f --- /dev/null +++ b/src/main/java/com/nbport/zgwl/entity/StudentEntity.java @@ -0,0 +1,43 @@ +package com.nbport.zgwl.entity; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +@Data +@TableName(value = "BIZ_STUDENT") +@EqualsAndHashCode(callSuper = true) +@KeySequence(value = "SEQ_BIZ_STUDENT", dbType = DbType.ORACLE) +public class StudentEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.INPUT) + private Long id; + + @TableField("student_no") + private String studentNo; + + @TableField("student_name") + private String studentName; + + @TableField("gender") + private String gender; + + @TableField("age") + private Integer age; + + @TableField("phone") + private String phone; + + @TableField("address") + private String address; +} diff --git a/src/main/java/com/nbport/zgwl/entity/SysUserEntity.java b/src/main/java/com/nbport/zgwl/entity/SysUserEntity.java new file mode 100644 index 0000000..6577d67 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/entity/SysUserEntity.java @@ -0,0 +1,31 @@ +package com.nbport.zgwl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; + +@Data +@TableName(value = "sys_user") +@EqualsAndHashCode(callSuper = true) +public class SysUserEntity extends BaseEntity { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "id", type = IdType.INPUT) + private Long id; + + @TableField("username") + private String username; + + @TableField("mobile_phone") + private String mobilePhone; + + @TableField("email") + private String email; +} diff --git a/src/main/java/com/nbport/zgwl/exception/ServiceErrorCodeRange.java b/src/main/java/com/nbport/zgwl/exception/ServiceErrorCodeRange.java new file mode 100644 index 0000000..a69c92d --- /dev/null +++ b/src/main/java/com/nbport/zgwl/exception/ServiceErrorCodeRange.java @@ -0,0 +1,14 @@ +package com.nbport.zgwl.exception; + +public interface ServiceErrorCodeRange { + + String DEFAULT = "A0001"; + + String USER_NOT_FOUND = "A0002"; + + String USERNAME_OR_PWD_ERROR = "A0003"; + + String DATA_NOT_FOUND = "A0004"; + + String FILE_NOT_FOUND = "A0005"; +} diff --git a/src/main/java/com/nbport/zgwl/exception/ServiceException.java b/src/main/java/com/nbport/zgwl/exception/ServiceException.java new file mode 100644 index 0000000..9c924f5 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/exception/ServiceException.java @@ -0,0 +1,19 @@ +package com.nbport.zgwl.exception; + +import lombok.Getter; + +@Getter +public class ServiceException extends RuntimeException { + + private final String code; + + public ServiceException(String code, String message) { + super(message); + this.code = code; + } + + public ServiceException(String message) { + super(message); + this.code = ServiceErrorCodeRange.DEFAULT; + } +} diff --git a/src/main/java/com/nbport/zgwl/handler/GlobalExceptionHandler.java b/src/main/java/com/nbport/zgwl/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..ea9a5c3 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/handler/GlobalExceptionHandler.java @@ -0,0 +1,58 @@ +package com.nbport.zgwl.handler; + +import com.nbport.zgwl.common.GlobalErrorCodeConstants; +import com.nbport.zgwl.common.R; +import com.nbport.zgwl.exception.ServiceException; +import jakarta.validation.ConstraintViolationException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.stream.Collectors; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ServiceException.class) + public R handleServiceException(ServiceException e) { + log.error("业务异常", e); + return R.error(e); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public R handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(";")); + return R.error(GlobalErrorCodeConstants.PARAMS_ERROR.getCode(), message); + } + + @ExceptionHandler(BindException.class) + public R handleBindException(BindException e) { + String message = e.getBindingResult().getFieldErrors().stream() + .map(FieldError::getDefaultMessage) + .collect(Collectors.joining(";")); + return R.error(GlobalErrorCodeConstants.PARAMS_ERROR.getCode(), message); + } + + @ExceptionHandler(ConstraintViolationException.class) + public R handleConstraintViolationException(ConstraintViolationException e) { + return R.error(GlobalErrorCodeConstants.PARAMS_ERROR.getCode(), e.getMessage()); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public R handleHttpMessageNotReadableException(HttpMessageNotReadableException e) { + return R.error(GlobalErrorCodeConstants.PARAMS_ERROR.getCode(), "请求体格式不正确"); + } + + @ExceptionHandler(Exception.class) + public R handleException(Exception e) { + log.error("系统异常", e); + return R.error(GlobalErrorCodeConstants.ERROR.getCode(), e.getMessage()); + } +} diff --git a/src/main/java/com/nbport/zgwl/handler/MyMetaObjectHandler.java b/src/main/java/com/nbport/zgwl/handler/MyMetaObjectHandler.java new file mode 100644 index 0000000..577b8a0 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/handler/MyMetaObjectHandler.java @@ -0,0 +1,26 @@ +package com.nbport.zgwl.handler; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + LocalDateTime now = LocalDateTime.now(); + this.strictInsertFill(metaObject, "createDt", LocalDateTime.class, now); + this.strictInsertFill(metaObject, "updateDt", LocalDateTime.class, now); + this.strictInsertFill(metaObject, "createBy", String.class, "system"); + this.strictInsertFill(metaObject, "updateBy", String.class, "system"); + } + + @Override + public void updateFill(MetaObject metaObject) { + this.strictUpdateFill(metaObject, "updateDt", LocalDateTime.class, LocalDateTime.now()); + this.strictUpdateFill(metaObject, "updateBy", String.class, "system"); + } +} diff --git a/src/main/java/com/nbport/zgwl/manifest/controller/ContractTemplateController.java b/src/main/java/com/nbport/zgwl/manifest/controller/ContractTemplateController.java new file mode 100644 index 0000000..ba37e0d --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/controller/ContractTemplateController.java @@ -0,0 +1,74 @@ +package com.nbport.zgwl.manifest.controller; + +import com.nbport.zgwl.common.PageResult; +import com.nbport.zgwl.common.R; +import com.nbport.zgwl.manifest.dto.ContractTemplateDto; +import com.nbport.zgwl.manifest.dto.ContractTemplateQueryDto; +import com.nbport.zgwl.manifest.dto.TemplateOrgOptionDto; +import com.nbport.zgwl.manifest.service.ContractTemplateService; +import com.nbport.zgwl.manifest.utils.ManifestManageConstants; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * 模板管理 Controller + * + * 路由尽量保持简单: + * - /list 分页 + * - /published 已发布列表 + * - /org/options 假组织选项 + * - /{id} 详情 + * - POST 新增 + * - PUT 编辑 + * - PUT /{id}/status/{status} 状态切换 + */ +@RestController +@RequiredArgsConstructor +@RequestMapping(ManifestManageConstants.REQUEST_PREFIX) +public class ContractTemplateController { + + private final ContractTemplateService contractTemplateService; + + @PostMapping("/list") + public R> queryList(@RequestBody ContractTemplateQueryDto queryDto) { + return R.success(contractTemplateService.queryTemplateList(queryDto)); + } + + @GetMapping("/published") + public R> queryPublishedList() { + return R.success(contractTemplateService.queryPublishedTemplates()); + } + + @GetMapping("/org/options") + public R> queryOrgOptions() { + return R.success(contractTemplateService.queryOrgOptions()); + } + + @GetMapping("/{id}") + public R getDetail(@PathVariable("id") Long id) { + return R.success(contractTemplateService.getTemplateDetail(id)); + } + + @PostMapping + public R add(@RequestBody ContractTemplateDto template) { + return R.success(contractTemplateService.createTemplate(template)); + } + + @PutMapping + public R edit(@RequestBody ContractTemplateDto template) { + return R.success(contractTemplateService.updateTemplate(template)); + } + + @PutMapping("/{id}/status/{status}") + public R changeStatus(@PathVariable("id") Long id, @PathVariable("status") String status) { + return R.success(contractTemplateService.changeTemplateStatus(id, status)); + } +} diff --git a/src/main/java/com/nbport/zgwl/manifest/domain/ContractTemplateManage.java b/src/main/java/com/nbport/zgwl/manifest/domain/ContractTemplateManage.java new file mode 100644 index 0000000..8bc0b36 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/domain/ContractTemplateManage.java @@ -0,0 +1,87 @@ +package com.nbport.zgwl.manifest.domain; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +@TableName(value = "CONTRACT_TEMPLATE_MANAGE") +@Data +@EqualsAndHashCode(callSuper = false) +@KeySequence(value = "SEQ_CONTRACT_TEMPLATE_MANAGE", dbType = DbType.ORACLE) +public class ContractTemplateManage implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "ID", type = IdType.INPUT) + private Long id; + + @TableField("TEMPLATE_CODE") + private String templateCode; + + @TableField("TEMPLATE_NAME") + private String templateName; + + @TableField("SOURCE") + private String source; + + @TableField("CONTRACT_TYPE") + private String contractType; + + @TableField("SELF_PARTY_ROLE") + private String selfPartyRole; + + @TableField("VERSION") + private String version; + + @TableField("STATUS") + private String status; + + @TableField("VARIABLE_COUNT") + private Integer variableCount; + + @TableField("DESCRIPTION") + private String description; + + @TableField("USAGE_TIP") + private String usageTip; + + @TableField("TEMPLATE_FILE_NAME") + private String templateFileName; + + @TableField("TEMPLATE_FILE_PATH") + private String templateFilePath; + + @TableField("PUBLISH_ORG_IDS_JSON") + private String publishOrgIdsJson; + + @TableField("PUBLISH_ORG_NAMES_JSON") + private String publishOrgNamesJson; + + @TableField("VARIABLE_MAPPINGS_JSON") + private String variableMappingsJson; + + @TableField("CREATE_BY") + private String createBy; + + @TableField("CREATED_TIME") + private LocalDateTime createdTime; + + @TableField("UPDATED_BY") + private String updatedBy; + + @TableField("UPDATED_TIME") + private LocalDateTime updatedTime; + + @TableField("DEL_FLAG") + private String delFlag; +} diff --git a/src/main/java/com/nbport/zgwl/manifest/dto/ContractTemplateDto.java b/src/main/java/com/nbport/zgwl/manifest/dto/ContractTemplateDto.java new file mode 100644 index 0000000..c7d0a79 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/dto/ContractTemplateDto.java @@ -0,0 +1,113 @@ +package com.nbport.zgwl.manifest.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 合同模板对象 + * + * 这里直接用 DTO 承载页面需要的字段,方便前后端对接。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class ContractTemplateDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + /** + * 范本编号,保存时后端自动生成。 + */ + private String templateCode; + + private String templateName; + + /** + * 范本类型:标准模板 / 推荐文本 / 示范文本 / 对方文本 + */ + private String source; + + private String contractType; + + /** + * 我方地位:甲方 / 乙方 / 丙方 + */ + private String selfPartyRole; + + private String version; + + /** + * 状态:draft / published / disabled + */ + private String status; + + private Integer variableCount; + + private String description; + + private String usageTip; + + private String templateFileName; + + /** + * 模板文件相对路径 + */ + private String templateFilePath; + + /** + * 发布范围 orgId 集合 + */ + @Builder.Default + private List publishOrgIds = new ArrayList<>(); + + /** + * 发布范围 orgName 集合 + */ + @Builder.Default + private List publishOrgNames = new ArrayList<>(); + + /** + * 为列表直接展示准备的文本。 + */ + private String publishScopeText; + + @Builder.Default + private List variableMappings = new ArrayList<>(); + + @Builder.Default + private List variables = new ArrayList<>(); + + private String createBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createdTime; + + private String updatedBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updatedTime; + + private String delFlag; + + /** + * 以下两个字段只在数据库层临时承接 JSON 文本,不给页面直接使用。 + */ + private String publishOrgIdsJson; + + private String publishOrgNamesJson; + + private String variableMappingsJson; +} diff --git a/src/main/java/com/nbport/zgwl/manifest/dto/ContractTemplateQueryDto.java b/src/main/java/com/nbport/zgwl/manifest/dto/ContractTemplateQueryDto.java new file mode 100644 index 0000000..a77b1a8 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/dto/ContractTemplateQueryDto.java @@ -0,0 +1,27 @@ +package com.nbport.zgwl.manifest.dto; + +import com.nbport.zgwl.dto.BasePageDto; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 模板分页查询参数 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ContractTemplateQueryDto extends BasePageDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String templateName; + + private String contractType; + + private String source; + + private String status; +} diff --git a/src/main/java/com/nbport/zgwl/manifest/dto/TemplateOrgOptionDto.java b/src/main/java/com/nbport/zgwl/manifest/dto/TemplateOrgOptionDto.java new file mode 100644 index 0000000..55be4c1 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/dto/TemplateOrgOptionDto.java @@ -0,0 +1,26 @@ +package com.nbport.zgwl.manifest.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 发布范围组织选项 + * + * 当前先用假数据代替真实组织表,后续内网联调时再替换成真实组织接口。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TemplateOrgOptionDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String orgId; + + private String orgName; +} diff --git a/src/main/java/com/nbport/zgwl/manifest/dto/TemplateVariableMappingDto.java b/src/main/java/com/nbport/zgwl/manifest/dto/TemplateVariableMappingDto.java new file mode 100644 index 0000000..e73a98c --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/dto/TemplateVariableMappingDto.java @@ -0,0 +1,29 @@ +package com.nbport.zgwl.manifest.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 模板变量映射 + * + * variableKey:Word 模板里的变量名 + * bindField:业务字段名 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TemplateVariableMappingDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String id; + + private String variableKey; + + private String bindField; +} diff --git a/src/main/java/com/nbport/zgwl/manifest/handler/ContractTemplateDataHandler.java b/src/main/java/com/nbport/zgwl/manifest/handler/ContractTemplateDataHandler.java new file mode 100644 index 0000000..02f500d --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/handler/ContractTemplateDataHandler.java @@ -0,0 +1,161 @@ +package com.nbport.zgwl.manifest.handler; + +import cn.hutool.core.util.StrUtil; +import com.nbport.zgwl.exception.ServiceException; +import com.nbport.zgwl.manifest.dto.ContractTemplateDto; +import com.nbport.zgwl.manifest.dto.TemplateOrgOptionDto; +import com.nbport.zgwl.manifest.dto.TemplateVariableMappingDto; +import com.nbport.zgwl.manifest.utils.ManifestJsonUtils; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 模板数据处理器 + * + * 负责: + * 1. 保存前清洗数据 + * 2. 保存前校验数据 + * 3. 查询后把 JSON 文本转成前端需要的列表 + */ +@Component +public class ContractTemplateDataHandler { + + public void normalizeBeforeSave(ContractTemplateDto template) { + template.setTemplateName(StrUtil.trim(template.getTemplateName())); + template.setSource(StrUtil.trim(template.getSource())); + template.setContractType(StrUtil.trim(template.getContractType())); + template.setSelfPartyRole(StrUtil.trim(template.getSelfPartyRole())); + template.setVersion(StrUtil.trim(template.getVersion())); + template.setDescription(StrUtil.trim(template.getDescription())); + template.setUsageTip(StrUtil.trim(template.getUsageTip())); + template.setTemplateFileName(StrUtil.trim(template.getTemplateFileName())); + template.setTemplateFilePath(StrUtil.trim(template.getTemplateFilePath())); + + List publishOrgIds = template.getPublishOrgIds() == null ? new ArrayList<>() : template.getPublishOrgIds(); + List publishOrgNames = template.getPublishOrgNames() == null ? new ArrayList<>() : template.getPublishOrgNames(); + template.setPublishOrgIds( + publishOrgIds.stream() + .filter(StrUtil::isNotBlank) + .map(StrUtil::trim) + .distinct() + .collect(Collectors.toList()) + ); + template.setPublishOrgNames( + publishOrgNames.stream() + .filter(StrUtil::isNotBlank) + .map(StrUtil::trim) + .distinct() + .collect(Collectors.toList()) + ); + + List mappings = template.getVariableMappings() == null ? new ArrayList<>() : template.getVariableMappings(); + mappings = mappings.stream() + .filter(Objects::nonNull) + .map(item -> new TemplateVariableMappingDto( + StrUtil.blankToDefault(item.getId(), "var_" + System.nanoTime()), + StrUtil.trim(item.getVariableKey()), + StrUtil.trim(item.getBindField()) + )) + .filter(item -> StrUtil.isNotBlank(item.getVariableKey()) || StrUtil.isNotBlank(item.getBindField())) + .collect(Collectors.toList()); + template.setVariableMappings(mappings); + template.setVariables( + mappings.stream() + .map(TemplateVariableMappingDto::getVariableKey) + .filter(StrUtil::isNotBlank) + .distinct() + .collect(Collectors.toList()) + ); + template.setVariableCount(template.getVariables().size()); + template.setPublishScopeText(String.join("、", template.getPublishOrgNames())); + } + + public void validateForSave(ContractTemplateDto template) { + if (StrUtil.isBlank(template.getTemplateName())) { + throw new ServiceException("范本名称不能为空"); + } + if (StrUtil.isBlank(template.getSource())) { + throw new ServiceException("范本类型不能为空"); + } + if (StrUtil.isBlank(template.getContractType())) { + throw new ServiceException("合同类型不能为空"); + } + if (StrUtil.isBlank(template.getSelfPartyRole())) { + throw new ServiceException("我方地位不能为空"); + } + if (StrUtil.isBlank(template.getVersion())) { + throw new ServiceException("版本号不能为空"); + } + if (StrUtil.isBlank(template.getTemplateFileName()) || StrUtil.isBlank(template.getTemplateFilePath())) { + throw new ServiceException("请先上传 Word 模板文件"); + } + if (template.getPublishOrgIds() == null || template.getPublishOrgIds().isEmpty()) { + throw new ServiceException("发布范围不能为空"); + } + if (template.getVariableMappings() == null || template.getVariableMappings().isEmpty()) { + throw new ServiceException("请至少维护一个变量映射"); + } + for (TemplateVariableMappingDto mapping : template.getVariableMappings()) { + if (StrUtil.isBlank(mapping.getVariableKey())) { + throw new ServiceException("模板变量名称不能为空"); + } + if (StrUtil.isBlank(mapping.getBindField())) { + throw new ServiceException("模板变量【" + mapping.getVariableKey() + "】尚未绑定业务字段"); + } + } + } + + public void fillUiFields(ContractTemplateDto template) { + if (template == null) { + return; + } + template.setPublishOrgIds(ManifestJsonUtils.parseStringList(template.getPublishOrgIdsJson())); + template.setPublishOrgNames(ManifestJsonUtils.parseStringList(template.getPublishOrgNamesJson())); + template.setVariableMappings( + ManifestJsonUtils.parseList( + template.getVariableMappingsJson(), + new com.alibaba.fastjson2.TypeReference>() { + } + ) + ); + template.setVariables( + template.getVariableMappings().stream() + .map(TemplateVariableMappingDto::getVariableKey) + .filter(StrUtil::isNotBlank) + .distinct() + .collect(Collectors.toList()) + ); + template.setVariableCount(template.getVariables().size()); + template.setPublishScopeText(String.join("、", template.getPublishOrgNames())); + } + + public void fillUiFields(List templates) { + if (templates == null) { + return; + } + templates.forEach(this::fillUiFields); + } + + public void fillOrgNamesByIds(ContractTemplateDto template, List orgOptions) { + if (template.getPublishOrgIds() == null) { + template.setPublishOrgNames(new ArrayList<>()); + return; + } + List names = new ArrayList<>(); + for (String orgId : template.getPublishOrgIds()) { + TemplateOrgOptionDto option = orgOptions.stream() + .filter(item -> Objects.equals(item.getOrgId(), orgId)) + .findFirst() + .orElse(null); + if (option != null) { + names.add(option.getOrgName()); + } + } + template.setPublishOrgNames(names); + template.setPublishScopeText(String.join("、", names)); + } +} diff --git a/src/main/java/com/nbport/zgwl/manifest/mapper/ContractTemplateMapper.java b/src/main/java/com/nbport/zgwl/manifest/mapper/ContractTemplateMapper.java new file mode 100644 index 0000000..36e9218 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/mapper/ContractTemplateMapper.java @@ -0,0 +1,38 @@ +package com.nbport.zgwl.manifest.mapper; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.nbport.zgwl.manifest.dto.ContractTemplateDto; +import com.nbport.zgwl.manifest.dto.ContractTemplateQueryDto; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 模板管理 Mapper + */ +public interface ContractTemplateMapper { + + Long selectTemplateSeqNextVal(); + + IPage selectTemplatePage(Page page, @Param("query") ContractTemplateQueryDto queryDto); + + List selectPublishedTemplates(); + + ContractTemplateDto selectTemplateById(@Param("id") Long id); + + int insertTemplate(ContractTemplateDto template); + + int updateTemplate(ContractTemplateDto template); + + int updateTemplateStatus(@Param("id") Long id, + @Param("status") String status, + @Param("updateBy") String updateBy, + @Param("updateTime") LocalDateTime updateTime); + + int deleteTemplateById(@Param("id") Long id, + @Param("updateBy") String updateBy, + @Param("updateTime") LocalDateTime updateTime, + @Param("delFlag") String delFlag); +} diff --git a/src/main/java/com/nbport/zgwl/manifest/service/ContractTemplateService.java b/src/main/java/com/nbport/zgwl/manifest/service/ContractTemplateService.java new file mode 100644 index 0000000..25af9bc --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/service/ContractTemplateService.java @@ -0,0 +1,28 @@ +package com.nbport.zgwl.manifest.service; + +import com.nbport.zgwl.common.PageResult; +import com.nbport.zgwl.manifest.dto.ContractTemplateDto; +import com.nbport.zgwl.manifest.dto.ContractTemplateQueryDto; +import com.nbport.zgwl.manifest.dto.TemplateOrgOptionDto; + +import java.util.List; + +/** + * 模板管理 Service + */ +public interface ContractTemplateService { + + PageResult queryTemplateList(ContractTemplateQueryDto queryDto); + + List queryPublishedTemplates(); + + ContractTemplateDto getTemplateDetail(Long id); + + ContractTemplateDto createTemplate(ContractTemplateDto template); + + ContractTemplateDto updateTemplate(ContractTemplateDto template); + + ContractTemplateDto changeTemplateStatus(Long id, String status); + + List queryOrgOptions(); +} diff --git a/src/main/java/com/nbport/zgwl/manifest/service/ContractTemplateServiceImpl.java b/src/main/java/com/nbport/zgwl/manifest/service/ContractTemplateServiceImpl.java new file mode 100644 index 0000000..2072173 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/service/ContractTemplateServiceImpl.java @@ -0,0 +1,146 @@ +package com.nbport.zgwl.manifest.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.nbport.zgwl.common.PageResult; +import com.nbport.zgwl.exception.ServiceException; +import com.nbport.zgwl.manifest.dto.ContractTemplateDto; +import com.nbport.zgwl.manifest.dto.ContractTemplateQueryDto; +import com.nbport.zgwl.manifest.dto.TemplateOrgOptionDto; +import com.nbport.zgwl.manifest.handler.ContractTemplateDataHandler; +import com.nbport.zgwl.manifest.mapper.ContractTemplateMapper; +import com.nbport.zgwl.manifest.utils.ManifestCodeUtils; +import com.nbport.zgwl.manifest.utils.ManifestJsonUtils; +import com.nbport.zgwl.manifest.utils.ManifestManageConstants; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 模板管理 Service 实现 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class ContractTemplateServiceImpl implements ContractTemplateService { + + private final ContractTemplateMapper contractTemplateMapper; + + private final ContractTemplateDataHandler contractTemplateDataHandler; + + @Override + public PageResult queryTemplateList(ContractTemplateQueryDto queryDto) { + Page page = new Page<>(queryDto.getPageNo(), queryDto.getPageSize()); + PageResult pageResult = new PageResult<>(contractTemplateMapper.selectTemplatePage(page, queryDto)); + contractTemplateDataHandler.fillUiFields(pageResult.getRecords()); + return pageResult; + } + + @Override + public List queryPublishedTemplates() { + List templates = contractTemplateMapper.selectPublishedTemplates(); + contractTemplateDataHandler.fillUiFields(templates); + return templates; + } + + @Override + public ContractTemplateDto getTemplateDetail(Long id) { + ContractTemplateDto template = contractTemplateMapper.selectTemplateById(id); + if (template == null) { + throw new ServiceException("模板不存在"); + } + contractTemplateDataHandler.fillUiFields(template); + return template; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ContractTemplateDto createTemplate(ContractTemplateDto template) { + contractTemplateDataHandler.normalizeBeforeSave(template); + contractTemplateDataHandler.fillOrgNamesByIds(template, queryOrgOptions()); + contractTemplateDataHandler.validateForSave(template); + + LocalDateTime now = LocalDateTime.now(); + template.setId(contractTemplateMapper.selectTemplateSeqNextVal()); + template.setTemplateCode(ManifestCodeUtils.buildTemplateCode(template.getContractType())); + template.setCreateBy(ManifestManageConstants.DEFAULT_OPERATOR); + template.setCreatedTime(now); + template.setUpdatedBy(ManifestManageConstants.DEFAULT_OPERATOR); + template.setUpdatedTime(now); + template.setDelFlag(ManifestManageConstants.DEL_FLAG_NORMAL); + saveJsonFields(template); + + contractTemplateMapper.insertTemplate(template); + return getTemplateDetail(template.getId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ContractTemplateDto updateTemplate(ContractTemplateDto template) { + if (template.getId() == null) { + throw new ServiceException("修改模板时 id 不能为空"); + } + + ContractTemplateDto dbTemplate = contractTemplateMapper.selectTemplateById(template.getId()); + if (dbTemplate == null) { + throw new ServiceException("模板不存在"); + } + + contractTemplateDataHandler.normalizeBeforeSave(template); + contractTemplateDataHandler.fillOrgNamesByIds(template, queryOrgOptions()); + contractTemplateDataHandler.validateForSave(template); + + template.setTemplateCode(dbTemplate.getTemplateCode()); + template.setCreateBy(dbTemplate.getCreateBy()); + template.setCreatedTime(dbTemplate.getCreatedTime()); + template.setUpdatedBy(ManifestManageConstants.DEFAULT_OPERATOR); + template.setUpdatedTime(LocalDateTime.now()); + template.setDelFlag(dbTemplate.getDelFlag()); + saveJsonFields(template); + + contractTemplateMapper.updateTemplate(template); + return getTemplateDetail(template.getId()); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public ContractTemplateDto changeTemplateStatus(Long id, String status) { + if (!ManifestManageConstants.STATUS_DRAFT.equals(status) + && !ManifestManageConstants.STATUS_PUBLISHED.equals(status) + && !ManifestManageConstants.STATUS_DISABLED.equals(status)) { + throw new ServiceException("模板状态不合法"); + } + + ContractTemplateDto dbTemplate = contractTemplateMapper.selectTemplateById(id); + if (dbTemplate == null) { + throw new ServiceException("模板不存在"); + } + + contractTemplateMapper.updateTemplateStatus( + id, + status, + ManifestManageConstants.DEFAULT_OPERATOR, + LocalDateTime.now() + ); + return getTemplateDetail(id); + } + + @Override + public List queryOrgOptions() { + return List.of( + new TemplateOrgOptionDto("ORG001", "浙港物流平台有限公司"), + new TemplateOrgOptionDto("ORG002", "宁波舟山港集团有限公司"), + new TemplateOrgOptionDto("ORG003", "浙江省海港投资运营集团有限公司"), + new TemplateOrgOptionDto("ORG004", "宁波港东南物流集团有限公司") + ); + } + + private void saveJsonFields(ContractTemplateDto template) { + template.setPublishOrgIdsJson(ManifestJsonUtils.toJson(template.getPublishOrgIds())); + template.setPublishOrgNamesJson(ManifestJsonUtils.toJson(template.getPublishOrgNames())); + template.setVariableMappingsJson(ManifestJsonUtils.toJson(template.getVariableMappings())); + } +} diff --git a/src/main/java/com/nbport/zgwl/manifest/utils/ManifestCodeUtils.java b/src/main/java/com/nbport/zgwl/manifest/utils/ManifestCodeUtils.java new file mode 100644 index 0000000..4936af5 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/utils/ManifestCodeUtils.java @@ -0,0 +1,56 @@ +package com.nbport.zgwl.manifest.utils; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 模板编号生成工具 + * + * 需求里要求“范本编号自动生成”,这里采用: + * TPL-合同类型编码-时间戳-随机数 + * + * 例子:TPL-MM-20260522113045-128 + */ +public final class ManifestCodeUtils { + + private static final Map CONTRACT_TYPE_CODE_MAP = Map.ofEntries( + Map.entry("买卖合同", "MM"), + Map.entry("采购合同", "CG"), + Map.entry("销售合同", "XS"), + Map.entry("服务合同", "FW"), + Map.entry("框架合同", "FJ"), + Map.entry("供用电、水、气、热力合同", "GY"), + Map.entry("赠与合同", "ZY"), + Map.entry("借款合同", "JK"), + Map.entry("保证合同", "BZ"), + Map.entry("租赁合同", "ZL"), + Map.entry("融资租赁合同", "RZ"), + Map.entry("保理合同", "BL"), + Map.entry("承揽合同", "CL"), + Map.entry("建设工程合同", "GC"), + Map.entry("运输合同", "YS"), + Map.entry("技术合同", "JS"), + Map.entry("保管合同", "BG"), + Map.entry("仓储合同", "CC"), + Map.entry("委托合同", "WT"), + Map.entry("物业服务合同", "WY"), + Map.entry("行纪合同", "HJ"), + Map.entry("中介合同", "ZJ"), + Map.entry("合伙合同", "HH"), + Map.entry("用工合同", "YG"), + Map.entry("业务合同", "YW"), + Map.entry("其他合同", "QT") + ); + + private ManifestCodeUtils() { + } + + public static String buildTemplateCode(String contractType) { + String typeCode = CONTRACT_TYPE_CODE_MAP.getOrDefault(contractType, "QT"); + String now = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); + int random = ThreadLocalRandom.current().nextInt(100, 1000); + return "TPL-" + typeCode + "-" + now + "-" + random; + } +} diff --git a/src/main/java/com/nbport/zgwl/manifest/utils/ManifestJsonUtils.java b/src/main/java/com/nbport/zgwl/manifest/utils/ManifestJsonUtils.java new file mode 100644 index 0000000..62df948 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/utils/ManifestJsonUtils.java @@ -0,0 +1,41 @@ +package com.nbport.zgwl.manifest.utils; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.TypeReference; + +import java.util.Collections; +import java.util.List; + +/** + * 模板管理 JSON 工具 + * + * 模板的“发布范围、变量映射”等字段更适合存成 JSON 文本。 + * 这里统一做转换,避免 Service 里反复写解析代码。 + */ +public final class ManifestJsonUtils { + + private ManifestJsonUtils() { + } + + public static String toJson(Object value) { + if (value == null) { + return null; + } + return JSON.toJSONString(value); + } + + public static List parseStringList(String json) { + if (json == null || json.isBlank()) { + return Collections.emptyList(); + } + return JSON.parseObject(json, new TypeReference>() { + }); + } + + public static List parseList(String json, TypeReference> typeReference) { + if (json == null || json.isBlank()) { + return Collections.emptyList(); + } + return JSON.parseObject(json, typeReference); + } +} diff --git a/src/main/java/com/nbport/zgwl/manifest/utils/ManifestManageConstants.java b/src/main/java/com/nbport/zgwl/manifest/utils/ManifestManageConstants.java new file mode 100644 index 0000000..b4e4dea --- /dev/null +++ b/src/main/java/com/nbport/zgwl/manifest/utils/ManifestManageConstants.java @@ -0,0 +1,26 @@ +package com.nbport.zgwl.manifest.utils; + +/** + * 模板管理模块常量 + * + * 把常量单独放这里,是为了后续迁移到内网时只改一个地方。 + */ +public final class ManifestManageConstants { + + private ManifestManageConstants() { + } + + public static final String REQUEST_PREFIX = "/manifest/manage"; + + public static final String DEFAULT_OPERATOR = "local_test"; + + public static final String DEL_FLAG_NORMAL = "0"; + + public static final String DEL_FLAG_DELETED = "2"; + + public static final String STATUS_DRAFT = "draft"; + + public static final String STATUS_PUBLISHED = "published"; + + public static final String STATUS_DISABLED = "disabled"; +} diff --git a/src/main/java/com/nbport/zgwl/mapper/StudentMapper.java b/src/main/java/com/nbport/zgwl/mapper/StudentMapper.java new file mode 100644 index 0000000..88e9b75 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/mapper/StudentMapper.java @@ -0,0 +1,15 @@ +package com.nbport.zgwl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.nbport.zgwl.dto.StudentQueryDto; +import com.nbport.zgwl.entity.StudentEntity; +import org.apache.ibatis.annotations.Param; + +import java.util.Map; + +public interface StudentMapper extends BaseMapper { + + IPage> queryList(Page> page, @Param("query") StudentQueryDto queryDto); +} diff --git a/src/main/java/com/nbport/zgwl/partner/controller/PartnerManageController.java b/src/main/java/com/nbport/zgwl/partner/controller/PartnerManageController.java new file mode 100644 index 0000000..1916313 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/controller/PartnerManageController.java @@ -0,0 +1,68 @@ +package com.nbport.zgwl.partner.controller; + +import com.nbport.zgwl.common.PageResult; +import com.nbport.zgwl.common.R; +import com.nbport.zgwl.partner.dto.PartnerManageDto; +import com.nbport.zgwl.partner.dto.PartnerQueryDto; +import com.nbport.zgwl.partner.service.PartnerManageService; +import com.nbport.zgwl.partner.utils.PartnerManageConstants; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 相对方管理 Controller + */ +@RestController +@RequiredArgsConstructor +@RequestMapping(PartnerManageConstants.REQUEST_PREFIX) +public class PartnerManageController { + + private final PartnerManageService partnerManageService; + + @PostMapping("/list") + public R> queryList(@RequestBody PartnerQueryDto queryDto) { + return R.success(partnerManageService.queryPartnerList(queryDto)); + } + + @GetMapping("/{customerSysid}") + public R getInfo(@PathVariable("customerSysid") Long customerSysid) { + return R.success(partnerManageService.selectPartnerById(customerSysid)); + } + + @PostMapping + public R add(@RequestBody PartnerManageDto partner) { + return R.success(partnerManageService.insertPartner(partner)); + } + + @PutMapping + public R edit(@RequestBody PartnerManageDto partner) { + partnerManageService.updatePartner(partner); + return R.success("保存成功"); + } + + @DeleteMapping("/{customerSysids}") + public R remove(@PathVariable("customerSysids") String customerSysids) { + List idList = Arrays.stream(customerSysids.split(",")) + .filter(item -> item != null && !item.isBlank()) + .map(Long::valueOf) + .collect(Collectors.toList()); + partnerManageService.deletePartnerByIds(idList); + return R.success("删除成功"); + } + + @PostMapping("/{customerSysid}/sync") + public R mockSync(@PathVariable("customerSysid") Long customerSysid) { + return R.success(partnerManageService.mockSyncPartner(customerSysid)); + } +} diff --git a/src/main/java/com/nbport/zgwl/partner/domain/IldmFeeCustomerInfo.java b/src/main/java/com/nbport/zgwl/partner/domain/IldmFeeCustomerInfo.java new file mode 100644 index 0000000..a530f9d --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/domain/IldmFeeCustomerInfo.java @@ -0,0 +1,224 @@ +package com.nbport.zgwl.partner.domain; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; + +@TableName(value = "ILDM_FEE_CUSTOMER_INFO") +@Data +@EqualsAndHashCode(callSuper = false) +@KeySequence(value = "SEQ_ILDM_FEE_CUSTOMER_INFO", dbType = DbType.ORACLE) +public class IldmFeeCustomerInfo implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "CUSTOMER_SYSID", type = IdType.INPUT) + private Long customerSysid; + + @TableField("CUSTOMER_REGNO") + private String customerRegno; + + @TableField("CUSTOMER_NO") + private String customerNo; + + @TableField("CUSTOMER_NAME_C") + private String customerNameC; + + @TableField("CUSTOMER_NAME_EN") + private String customerNameEn; + + @TableField("CUSTOMER_SHORT_NAME") + private String customerShortName; + + @TableField("CUSTOMER_SHORT_NAME_EN") + private String customerShortNameEn; + + @TableField("CUSTOMER_TAXNO") + private String customerTaxno; + + @TableField("CUSTOMER_ADDRESS") + private String customerAddress; + + @TableField("OFFICEPHONE") + private String officephone; + + @TableField("CUSTOMER_BANK") + private String customerBank; + + @TableField("CUSTOMER_BANKACCOUNT") + private String customerBankaccount; + + @TableField("CUSTOMER_CHARACTER") + private String customerCharacter; + + @TableField("CUSTOMER_REGADDR") + private String customerRegaddr; + + @TableField("CUSTOMER_REGDATE") + private LocalDate customerRegdate; + + @TableField("CUSTOMER_NATURE") + private String customerNature; + + @TableField("CUSTOMER_ZIP") + private String customerZip; + + @TableField("CUSTOMER_1_TYPEFLAG") + private String customer1Typeflag; + + @TableField("CUSTOMER_2_TYPEFLAG") + private String customer2Typeflag; + + @TableField("CUSTOMER_3_TYPEFLAG") + private String customer3Typeflag; + + @TableField("CUSTOMER_4_TYPEFLAG") + private String customer4Typeflag; + + @TableField("PLACE_TYPE") + private String placeType; + + @TableField("ACTIVEFLAG") + private String activeflag; + + @TableField("CUSTOMER_SYSID_HT") + private String customerSysidHt; + + @TableField("DOWN_DATE") + private LocalDate downDate; + + @TableField("OPEN_FLAG") + private String openFlag; + + @TableField("CREATE_BY") + private String createBy; + + @TableField("CREATE_TIME") + private LocalDateTime createTime; + + @TableField("UPDATE_BY") + private String updateBy; + + @TableField("UPDATE_TIME") + private LocalDateTime updateTime; + + @TableField("SYSTEM_TYPE") + private String systemType; + + @TableField("REMARK") + private String remark; + + @TableField("ORG_ID") + private String orgId; + + @TableField("EMAIL_ADDRESS") + private String emailAddress; + + @TableField("OPPOSITE_ID") + private String oppositeId; + + @TableField("OPPOSITE_CHARACTER") + private String oppositeCharacter; + + @TableField("GAT_FLAG") + private String gatFlag; + + @TableField("GROUP_FLAG") + private String groupFlag; + + @TableField("COUNTRY") + private String country; + + @TableField("COUNTRY_NAME") + private String countryName; + + @TableField("CITY") + private String city; + + @TableField("POSTCODE") + private String postcode; + + @TableField("LEGAL_PERSON_ID") + private String legalPersonId; + + @TableField("LEGAL_PERSON_NAME") + private String legalPersonName; + + @TableField("LEGAL_PERSON_ACCOUNT") + private String legalPersonAccount; + + @TableField("LEGAL_PERSON_CARD_ID") + private String legalPersonCardId; + + @TableField("WEBSITE") + private String website; + + @TableField("CERTIFICATE_TYPE") + private String certificateType; + + @TableField("CERTIFICATE_NUM") + private String certificateNum; + + @TableField("TIN_CODE") + private String tinCode; + + @TableField("ABROAD_CODE") + private String abroadCode; + + @TableField("REGISTERED_CAPITAL") + private BigDecimal registeredCapital; + + @TableField("REG_CAPITAL") + private String regCapital; + + @TableField("BUS_SCOPE") + private String busScope; + + @TableField("OPPOSITE_CODE") + private String oppositeCode; + + @TableField("OPPOSITE_STATUS") + private String oppositeStatus; + + @TableField("IS_SGAT") + private String isSgat; + + @TableField("IS_JTN") + private String isJtn; + + @TableField("NATURE_OF_ENTERPRISE_ORG") + private String natureOfEnterpriseOrg; + + @TableField("NATURE_OF_ENTERPRISE_ORG_LEV") + private String natureOfEnterpriseOrgLev; + + @TableField("RELATIVE_TYPE") + private String relativeType; + + @TableField("IS_DELETED") + private String isDeleted; + + @TableField("COMMENTS") + private String comments; + + @TableField("SYNC_STATUS") + private String syncStatus; + + @TableField("SYNC_TIME") + private LocalDateTime syncTime; + + @TableField("SYNC_MESSAGE") + private String syncMessage; +} diff --git a/src/main/java/com/nbport/zgwl/partner/domain/SealPartnerContact.java b/src/main/java/com/nbport/zgwl/partner/domain/SealPartnerContact.java new file mode 100644 index 0000000..485df13 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/domain/SealPartnerContact.java @@ -0,0 +1,63 @@ +package com.nbport.zgwl.partner.domain; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +@TableName(value = "SEAL_PARTNER_CONTACT") +@Data +@EqualsAndHashCode(callSuper = false) +@KeySequence(value = "SEQ_SEAL_PARTNER_CONTACT", dbType = DbType.ORACLE) +public class SealPartnerContact implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + @TableId(value = "ID", type = IdType.INPUT) + private Long id; + + @TableField("PARTNER_RECORD_ID") + private Long partnerRecordId; + + @TableField("CONTACT_NAME") + private String contactName; + + @TableField("CONTACT_PHONE") + private String contactPhone; + + @TableField("POSTAL_ADDRESS") + private String postalAddress; + + @TableField("EMAIL") + private String email; + + @TableField("SORT_ORDER") + private Integer sortOrder; + + @TableField("DEL_FLAG") + private String delFlag; + + @TableField("CREATE_BY") + private String createBy; + + @TableField("CREATE_TIME") + private LocalDateTime createTime; + + @TableField("UPDATE_BY") + private String updateBy; + + @TableField("UPDATE_TIME") + private LocalDateTime updateTime; + + @TableField("REMARK") + private String remark; +} diff --git a/src/main/java/com/nbport/zgwl/partner/dto/PartnerContactDto.java b/src/main/java/com/nbport/zgwl/partner/dto/PartnerContactDto.java new file mode 100644 index 0000000..ea71e0b --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/dto/PartnerContactDto.java @@ -0,0 +1,47 @@ +package com.nbport.zgwl.partner.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 相对方联系人信息 + * + * 字段命名直接采用 seal_partner_contact 的驼峰写法。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PartnerContactDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private Long id; + + /** + * 前端渲染联系人列表时使用的临时唯一键。 + */ + private String uid; + + private Long partnerRecordId; + + private String contactName; + + private String contactPhone; + + private String postalAddress; + + private String email; + + private Integer sortOrder; + + private String delFlag; + + private String remark; +} diff --git a/src/main/java/com/nbport/zgwl/partner/dto/PartnerManageDto.java b/src/main/java/com/nbport/zgwl/partner/dto/PartnerManageDto.java new file mode 100644 index 0000000..8e6089b --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/dto/PartnerManageDto.java @@ -0,0 +1,243 @@ +package com.nbport.zgwl.partner.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 相对方主对象 + * + * 字段命名直接采用 ILDM_FEE_CUSTOMER_INFO 的驼峰写法, + * 避免再维护一套“业务语义字段”和“数据库字段”的双重命名。 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PartnerManageDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + // ============== 客户基本信息字段 ============== + + /** 客户系统ID */ + private Long customerSysid; // 注意:数据库中是VARCHAR(64),不是Long + + /** 客户注册号 */ + private String customerRegno; + + /** 客户编号 */ + private String customerNo; + + /** 公司全称 */ + private String customerNameC; + + /** 公司英文名称 */ + private String customerNameEn; + + /** 公司简称 */ + private String customerShortName; + + /** 公司英文简称 */ + private String customerShortNameEn; + + /** 统一社会信用代码 */ + private String customerTaxno; + + /** 详细地址 */ + private String customerAddress; + + /** 联系电话 */ + private String officephone; + + /** 开户银行 */ + private String customerBank; + + /** 银行账户 */ + private String customerBankaccount; + + /** 客户性质 */ + private String customerCharacter; + + /** 注册地址 */ + private String customerRegaddr; + + /** 注册日期 */ + private LocalDate customerRegdate; + + /** 客户性质分类 */ + private String customerNature; + + /** 邮编 */ + private String customerZip; + + /** 客户类型标识1 */ + private String customer1Typeflag; + + /** 客户类型标识2 */ + private String customer2Typeflag; + + /** 客户类型标识3 */ + private String customer3Typeflag; + + /** 客户类型标识4 */ + private String customer4Typeflag; + + /** 境内外 */ + private String placeType; + + /** 启用标识 */ + private String activeflag; + + /** 海铁系统客户ID */ + private String customerSysidHt; + + /** 下载日期 */ + private LocalDate downDate; + + /** 开户标识 */ + private String openFlag; + + /** 业务系统类型(ht:海铁联运系统;xg:箱管系统;qc:全程物流) */ + private String systemType; + + /** 组织体系对应的公司编码 */ + private String orgId; + + /** 邮箱地址 */ + private String emailAddress; + + // ============== 法务系统相关字段 ============== + + /** 法务系统相对方ID(唯一标识) */ + private String oppositeId; + + /** 相对方性质分类(1:境内组织,2:境内个人,3:境外组织,4:境外个人) */ + private String oppositeCharacter; + + /** 是否涉港澳台标识(Y:是,N:否,法务业务分类用) */ + private String gatFlag; + + /** 是否集团内(Y:是,N:否,法务业务分类用) */ + private String groupFlag; + + /** 国家编码 */ + private String country; + + /** 国家名称 */ + private String countryName; + + /** 城市 */ + private String city; + + /** 邮编 */ + private String postcode; + + /** 法定代表人或负责人ID */ + private String legalPersonId; + + /** 法定代表人或负责人姓名 */ + private String legalPersonName; + + /** 法定代表人或负责人账户 */ + private String legalPersonAccount; + + /** 法定代表人身份证号(加密存储) */ + private String legalPersonCardId; + + /** 网站地址 */ + private String website; + + /** 自然人证件类型(idCard:身份证,passport:护照,other:其他) */ + private String certificateType; + + /** 自然人证件号码(加密存储) */ + private String certificateNum; + + /** TIN码(税务识别号) */ + private String tinCode; + + /** 境外公司活动证明编码 */ + private String abroadCode; + + /** 注册资金(单位:元) */ + private BigDecimal registeredCapital; + + /** 注册资本(文本描述,如:1000万人民币) */ + private String regCapital; + + /** 经营范围 */ + private String busScope; + + /** 相对方编码(业务分类编码) */ + private String oppositeCode; + + /** 状态(EFFECTUAL:有效,INVALID:无效) */ + private String oppositeStatus; + + /** 是否SGAT(1:是,0:否) */ + private String isSgat; + + /** 是否JTN(1:是,0:否) */ + private String isJtn; + + /** 组织性质(ZY:政府,QY:企业,QT:其他组织) */ + private String natureOfEnterpriseOrg; + + /** 组织性质子级(GNQY:国有企业,MYQY:民营企业,WZQY:外资企业,HZQY:合资企业,GYQZHKGQY:国有全资和控股企业,QT:其他) */ + private String natureOfEnterpriseOrgLev; + + /** 类型(client:客户,clientAndSup:客户及供应商,other:其他,Partner:合作伙伴,supplier:供应商) */ + private String relativeType; + + /** 法务系统删除标记(Y:是,N:否,逻辑删除) */ + private String isDeleted; + + /** 备注【法务】 */ + private String comments; + + /** 备注 */ + private String remark; + + // ============== 系统通用字段 ============== + + /** 创建人 */ + private String createBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime createTime; + + /** 更新人 */ + private String updateBy; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime updateTime; + + // ============== 同步状态字段 ============== + + /** 同步状态:unsynced/synced/failed */ + private String syncStatus; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime syncTime; + + /** 同步说明 */ + private String syncMessage; + + // ============== 非数据库字段 ============== + + /** 联系人列表 */ + @Builder.Default + private List contacts = new ArrayList<>(); +} \ No newline at end of file diff --git a/src/main/java/com/nbport/zgwl/partner/dto/PartnerQueryDto.java b/src/main/java/com/nbport/zgwl/partner/dto/PartnerQueryDto.java new file mode 100644 index 0000000..dbe99a3 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/dto/PartnerQueryDto.java @@ -0,0 +1,29 @@ +package com.nbport.zgwl.partner.dto; + +import com.nbport.zgwl.dto.BasePageDto; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 相对方列表查询参数 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PartnerQueryDto extends BasePageDto implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + private String customerNameC; + + private String relativeType; + + private String oppositeCharacter; + + private String groupFlag; + + private String syncStatus; +} diff --git a/src/main/java/com/nbport/zgwl/partner/handler/PartnerDataHandler.java b/src/main/java/com/nbport/zgwl/partner/handler/PartnerDataHandler.java new file mode 100644 index 0000000..39db79e --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/handler/PartnerDataHandler.java @@ -0,0 +1,219 @@ +package com.nbport.zgwl.partner.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.StrUtil; +import com.nbport.zgwl.exception.ServiceException; +import com.nbport.zgwl.partner.dto.PartnerContactDto; +import com.nbport.zgwl.partner.dto.PartnerManageDto; +import com.nbport.zgwl.partner.utils.PartnerManageConstants; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 相对方数据处理器 + */ +@Component +public class PartnerDataHandler { + + public void normalizeBeforeSave(PartnerManageDto partner) { + partner.setCustomerNameC(StrUtil.trim(partner.getCustomerNameC())); + partner.setCustomerTaxno(StrUtil.trim(partner.getCustomerTaxno())); + partner.setCustomerRegaddr(StrUtil.trim(partner.getCustomerRegaddr())); + partner.setEmailAddress(StrUtil.trim(partner.getEmailAddress())); + partner.setCountry(StrUtil.trim(partner.getCountry())); + partner.setCountryName(StrUtil.trim(partner.getCountryName())); + partner.setLegalPersonName(StrUtil.trim(partner.getLegalPersonName())); + partner.setCertificateNum(StrUtil.trim(partner.getCertificateNum())); + partner.setTinCode(StrUtil.trim(partner.getTinCode())); + partner.setRegCapital(StrUtil.trim(partner.getRegCapital())); + partner.setOppositeCode(StrUtil.trim(partner.getOppositeCode())); + partner.setOppositeStatus(StrUtil.trim(partner.getOppositeStatus())); + partner.setComments(StrUtil.trim(partner.getComments())); + partner.setRemark(StrUtil.trim(partner.getRemark())); + partner.setSyncMessage(StrUtil.trim(partner.getSyncMessage())); + + if (!"QY".equals(partner.getNatureOfEnterpriseOrg())) { + partner.setNatureOfEnterpriseOrgLev(null); + } + + if (!isDomesticOrg(partner)) { + partner.setCustomerTaxno(null); + partner.setLegalPersonName(null); + } + + if (!isDomesticPerson(partner) && !isForeignPerson(partner)) { + partner.setCertificateType(null); + partner.setCertificateNum(null); + } + + if (!isForeignOrg(partner)) { + partner.setTinCode(null); + } + + if (!isForeignOrg(partner) && !isForeignPerson(partner)) { + partner.setCountry(null); + partner.setCountryName(null); + } + + if (!isDomesticOrg(partner) && !isForeignOrg(partner)) { + partner.setCustomerRegaddr(null); + partner.setRegCapital(null); + partner.setRegisteredCapital(null); + } + + if (!showGatFlag(partner)) { + partner.setGatFlag(PartnerManageConstants.GAT_FLAG_NO); + } + + partner.setGroupFlag(StrUtil.blankToDefault(partner.getGroupFlag(), PartnerManageConstants.GROUP_FLAG_NO)); + partner.setGatFlag(StrUtil.blankToDefault(partner.getGatFlag(), PartnerManageConstants.GAT_FLAG_NO)); + partner.setSyncStatus(StrUtil.blankToDefault(partner.getSyncStatus(), PartnerManageConstants.SYNC_STATUS_UNSYNCED)); + partner.setOppositeStatus(StrUtil.blankToDefault(partner.getOppositeStatus(), PartnerManageConstants.OPPOSITE_STATUS_EFFECTUAL)); + partner.setIsDeleted(StrUtil.blankToDefault(partner.getIsDeleted(), PartnerManageConstants.PARTNER_IS_DELETED_NORMAL)); + partner.setIsSgat(PartnerManageConstants.GAT_FLAG_YES.equals(partner.getGatFlag()) + ? PartnerManageConstants.FLAG_ONE + : PartnerManageConstants.FLAG_ZERO); + partner.setIsJtn(PartnerManageConstants.GROUP_FLAG_YES.equals(partner.getGroupFlag()) + ? PartnerManageConstants.FLAG_ONE + : PartnerManageConstants.FLAG_ZERO); + + List contacts = partner.getContacts() == null ? new ArrayList<>() : partner.getContacts(); + contacts = contacts.stream() + .filter(Objects::nonNull) + .peek(this::normalizeContact) + .filter(this::hasAnyContactValue) + .collect(Collectors.toList()); + partner.setContacts(contacts); + } + + public void validateForSave(PartnerManageDto partner) { + if (StrUtil.isBlank(partner.getCustomerNameC())) { + throw new ServiceException("公司名称不能为空"); + } + if (StrUtil.isBlank(partner.getRelativeType())) { + throw new ServiceException("公司类型不能为空"); + } + if (StrUtil.isBlank(partner.getOppositeCharacter())) { + throw new ServiceException("公司性质不能为空"); + } + if (StrUtil.isBlank(partner.getNatureOfEnterpriseOrg())) { + throw new ServiceException("组织性质不能为空"); + } + if ("QY".equals(partner.getNatureOfEnterpriseOrg()) && StrUtil.isBlank(partner.getNatureOfEnterpriseOrgLev())) { + throw new ServiceException("组织性质选择企业时,组织性质企业二级不能为空"); + } + if (StrUtil.isBlank(partner.getGroupFlag())) { + throw new ServiceException("是否集团内不能为空"); + } + if (isDomesticOrg(partner) && StrUtil.isBlank(partner.getCustomerTaxno())) { + throw new ServiceException("境内组织必须填写统一社会信用代码"); + } + if (isDomesticPerson(partner) && StrUtil.isBlank(partner.getCertificateType())) { + throw new ServiceException("境内个人必须选择自然人证件类型"); + } + if (isDomesticPerson(partner) && StrUtil.isBlank(partner.getCertificateNum())) { + throw new ServiceException("境内个人必须填写自然人证件号码"); + } + if (isForeignOrg(partner) && StrUtil.isBlank(partner.getTinCode())) { + throw new ServiceException("境外组织必须填写TIN号"); + } + if (isForeignOrg(partner) && StrUtil.isBlank(partner.getCountry())) { + throw new ServiceException("境外组织必须选择国家"); + } + if (isForeignPerson(partner) && StrUtil.isBlank(partner.getCountry())) { + throw new ServiceException("境外个人必须选择国家"); + } + if (isDomesticOrg(partner) && StrUtil.isBlank(partner.getLegalPersonName())) { + throw new ServiceException("境内组织必须填写法定代表人或负责人"); + } + if ((isDomesticOrg(partner) || isForeignOrg(partner)) && StrUtil.isBlank(partner.getCustomerRegaddr())) { + throw new ServiceException("组织类型必须填写注册地址"); + } + if ((isDomesticOrg(partner) || isForeignOrg(partner)) && StrUtil.isBlank(partner.getRegCapital())) { + throw new ServiceException("组织类型必须填写注册资本"); + } + validateContacts(partner); + } + + public void fillContactUiKeys(List contacts) { + if (contacts == null) { + return; + } + contacts.forEach(contact -> { + if (StrUtil.isBlank(contact.getUid())) { + contact.setUid(contact.getId() == null ? null : String.valueOf(contact.getId())); + } + }); + } + + private void validateContacts(PartnerManageDto partner) { + if (!needContacts(partner)) { + return; + } + if (CollUtil.isEmpty(partner.getContacts())) { + throw new ServiceException("组织类型至少需要维护一个联系人"); + } + for (PartnerContactDto contact : partner.getContacts()) { + if (StrUtil.isBlank(contact.getContactName())) { + throw new ServiceException("联系人姓名不能为空"); + } + if (StrUtil.isBlank(contact.getContactPhone())) { + throw new ServiceException("联系人联系电话不能为空"); + } + if (StrUtil.isBlank(contact.getPostalAddress())) { + throw new ServiceException("联系人通讯地址不能为空"); + } + if (StrUtil.isBlank(contact.getEmail())) { + throw new ServiceException("联系人电子邮箱不能为空"); + } + if (!Validator.isEmail(contact.getEmail())) { + throw new ServiceException("联系人电子邮箱格式不正确"); + } + } + } + + private void normalizeContact(PartnerContactDto contact) { + contact.setContactName(StrUtil.trim(contact.getContactName())); + contact.setContactPhone(StrUtil.trim(contact.getContactPhone())); + contact.setPostalAddress(StrUtil.trim(contact.getPostalAddress())); + contact.setEmail(StrUtil.trim(contact.getEmail())); + contact.setRemark(StrUtil.trim(contact.getRemark())); + contact.setDelFlag(PartnerManageConstants.CONTACT_DEL_FLAG_NORMAL); + } + + private boolean hasAnyContactValue(PartnerContactDto contact) { + return StrUtil.isNotBlank(contact.getContactName()) + || StrUtil.isNotBlank(contact.getContactPhone()) + || StrUtil.isNotBlank(contact.getPostalAddress()) + || StrUtil.isNotBlank(contact.getEmail()); + } + + private boolean needContacts(PartnerManageDto partner) { + return isDomesticOrg(partner) || isForeignOrg(partner); + } + + private boolean showGatFlag(PartnerManageDto partner) { + return isDomesticOrg(partner) || isDomesticPerson(partner); + } + + private boolean isDomesticOrg(PartnerManageDto partner) { + return "1".equals(partner.getOppositeCharacter()); + } + + private boolean isDomesticPerson(PartnerManageDto partner) { + return "2".equals(partner.getOppositeCharacter()); + } + + private boolean isForeignOrg(PartnerManageDto partner) { + return "3".equals(partner.getOppositeCharacter()); + } + + private boolean isForeignPerson(PartnerManageDto partner) { + return "4".equals(partner.getOppositeCharacter()); + } +} diff --git a/src/main/java/com/nbport/zgwl/partner/mapper/PartnerManageMapper.java b/src/main/java/com/nbport/zgwl/partner/mapper/PartnerManageMapper.java new file mode 100644 index 0000000..0afdafe --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/mapper/PartnerManageMapper.java @@ -0,0 +1,61 @@ +package com.nbport.zgwl.partner.mapper; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.nbport.zgwl.partner.dto.PartnerContactDto; +import com.nbport.zgwl.partner.dto.PartnerManageDto; +import com.nbport.zgwl.partner.dto.PartnerQueryDto; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 相对方管理 Mapper + */ +public interface PartnerManageMapper { + + Long selectPartnerSeqNextVal(); + + Long selectPartnerContactSeqNextVal(); + + IPage selectPartnerPage(Page page, @Param("query") PartnerQueryDto queryDto); + + PartnerManageDto selectPartnerById(@Param("customerSysid") Long customerSysid); + + int insertPartner(PartnerManageDto partner); + + int updatePartner(PartnerManageDto partner); + + int deletePartnerByIds(@Param("customerSysids") List customerSysids, + @Param("updateBy") String updateBy, + @Param("updateTime") LocalDateTime updateTime, + @Param("isDeleted") String isDeleted); + + List selectPartnerContactsByPartnerIds(@Param("partnerRecordIds") List partnerRecordIds); + + int markPartnerContactsDeletedByPartnerId(@Param("partnerRecordId") Long partnerRecordId, + @Param("updateBy") String updateBy, + @Param("updateTime") LocalDateTime updateTime, + @Param("delFlag") String delFlag); + + int markPartnerContactsDeletedByPartnerIds(@Param("partnerRecordIds") List partnerRecordIds, + @Param("updateBy") String updateBy, + @Param("updateTime") LocalDateTime updateTime, + @Param("delFlag") String delFlag); + + int batchInsertPartnerContacts(@Param("contacts") List contacts, + @Param("createBy") String createBy, + @Param("createTime") LocalDateTime createTime, + @Param("updateBy") String updateBy, + @Param("updateTime") LocalDateTime updateTime, + @Param("delFlag") String delFlag); + + int updatePartnerSyncInfo(@Param("customerSysid") Long customerSysid, + @Param("oppositeId") String oppositeId, + @Param("syncStatus") String syncStatus, + @Param("syncTime") LocalDateTime syncTime, + @Param("syncMessage") String syncMessage, + @Param("updateBy") String updateBy, + @Param("updateTime") LocalDateTime updateTime); +} diff --git a/src/main/java/com/nbport/zgwl/partner/service/PartnerManageService.java b/src/main/java/com/nbport/zgwl/partner/service/PartnerManageService.java new file mode 100644 index 0000000..fb46263 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/service/PartnerManageService.java @@ -0,0 +1,25 @@ +package com.nbport.zgwl.partner.service; + +import com.nbport.zgwl.common.PageResult; +import com.nbport.zgwl.partner.dto.PartnerManageDto; +import com.nbport.zgwl.partner.dto.PartnerQueryDto; + +import java.util.List; + +/** + * 相对方管理 Service + */ +public interface PartnerManageService { + + PageResult queryPartnerList(PartnerQueryDto queryDto); + + PartnerManageDto selectPartnerById(Long customerSysid); + + Long insertPartner(PartnerManageDto partner); + + void updatePartner(PartnerManageDto partner); + + void deletePartnerByIds(List customerSysids); + + PartnerManageDto mockSyncPartner(Long customerSysid); +} diff --git a/src/main/java/com/nbport/zgwl/partner/service/PartnerManageServiceImpl.java b/src/main/java/com/nbport/zgwl/partner/service/PartnerManageServiceImpl.java new file mode 100644 index 0000000..eb9d837 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/service/PartnerManageServiceImpl.java @@ -0,0 +1,260 @@ +package com.nbport.zgwl.partner.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.nbport.zgwl.common.PageResult; +import com.nbport.zgwl.contractpush.dto.PushPayloadDTO; +import com.nbport.zgwl.exception.ServiceException; +import com.nbport.zgwl.partner.dto.PartnerContactDto; +import com.nbport.zgwl.partner.dto.PartnerManageDto; +import com.nbport.zgwl.partner.dto.PartnerQueryDto; +import com.nbport.zgwl.partner.handler.PartnerDataHandler; +import com.nbport.zgwl.partner.mapper.PartnerManageMapper; +import com.nbport.zgwl.partner.utils.PartnerIdUtils; +import com.nbport.zgwl.partner.utils.PartnerManageConstants; +import com.nbport.zgwl.partner.utils.PartnerSyncPayloadBuilder; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * 相对方管理 Service 实现 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class PartnerManageServiceImpl implements PartnerManageService { + + private final PartnerManageMapper partnerManageMapper; + + private final PartnerDataHandler partnerDataHandler; + + @Override + public PageResult queryPartnerList(PartnerQueryDto queryDto) { + Page page = new Page<>(queryDto.getPageNo(), queryDto.getPageSize()); + PageResult pageResult = new PageResult<>( + partnerManageMapper.selectPartnerPage(page, queryDto)); + attachContacts(pageResult.getRecords()); + return pageResult; + } + + @Override + public PartnerManageDto selectPartnerById(Long customerSysid) { + PartnerManageDto partner = partnerManageMapper.selectPartnerById(customerSysid); + if (partner == null) { + throw new ServiceException("相对方不存在"); + } + attachContacts(Collections.singletonList(partner)); + return partner; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public Long insertPartner(PartnerManageDto partner) { + partnerDataHandler.normalizeBeforeSave(partner); + partnerDataHandler.validateForSave(partner); + + LocalDateTime now = LocalDateTime.now(); + partner.setCustomerSysid(partnerManageMapper.selectPartnerSeqNextVal()); + partner.setOppositeCode(ensureOppositeCode(partner.getOppositeCode())); + partner.setOppositeId(null); + partner.setSyncStatus(PartnerManageConstants.SYNC_STATUS_UNSYNCED); + partner.setSyncTime(null); + partner.setSyncMessage("新增后尚未同步"); + partner.setOppositeStatus(PartnerManageConstants.OPPOSITE_STATUS_EFFECTUAL); + partner.setCreateBy(PartnerManageConstants.DEFAULT_OPERATOR); + partner.setCreateTime(now); + partner.setUpdateBy(PartnerManageConstants.DEFAULT_OPERATOR); + partner.setUpdateTime(now); + partner.setIsDeleted(PartnerManageConstants.PARTNER_IS_DELETED_NORMAL); + + partnerManageMapper.insertPartner(partner); + saveContacts(partner.getCustomerSysid(), partner.getContacts(), now); + return partner.getCustomerSysid(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePartner(PartnerManageDto partner) { + if (partner.getCustomerSysid() == null) { + throw new ServiceException("修改时 customerSysid 不能为空"); + } + + PartnerManageDto dbPartner = partnerManageMapper.selectPartnerById(partner.getCustomerSysid()); + if (dbPartner == null) { + throw new ServiceException("相对方不存在或已删除"); + } + + partnerDataHandler.normalizeBeforeSave(partner); + partnerDataHandler.validateForSave(partner); + + LocalDateTime now = LocalDateTime.now(); + partner.setOppositeCode(ensureOppositeCode(partner.getOppositeCode(), dbPartner.getOppositeCode())); + partner.setOppositeId(dbPartner.getOppositeId()); + partner.setOppositeStatus(ensureValue(partner.getOppositeStatus(), dbPartner.getOppositeStatus(), + PartnerManageConstants.OPPOSITE_STATUS_EFFECTUAL)); + partner.setSyncStatus(PartnerManageConstants.SYNC_STATUS_UNSYNCED); + partner.setSyncTime(null); + partner.setSyncMessage("信息已修改,待重新同步"); + partner.setUpdateBy(PartnerManageConstants.DEFAULT_OPERATOR); + partner.setUpdateTime(now); + partner.setIsDeleted(PartnerManageConstants.PARTNER_IS_DELETED_NORMAL); + + partnerManageMapper.updatePartner(partner); + partnerManageMapper.markPartnerContactsDeletedByPartnerId( + partner.getCustomerSysid(), + PartnerManageConstants.DEFAULT_OPERATOR, + now, + PartnerManageConstants.CONTACT_DEL_FLAG_DELETED); + saveContacts(partner.getCustomerSysid(), partner.getContacts(), now); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deletePartnerByIds(List customerSysids) { + if (customerSysids == null || customerSysids.isEmpty()) { + return; + } + LocalDateTime now = LocalDateTime.now(); + partnerManageMapper.markPartnerContactsDeletedByPartnerIds( + customerSysids, + PartnerManageConstants.DEFAULT_OPERATOR, + now, + PartnerManageConstants.CONTACT_DEL_FLAG_DELETED); + partnerManageMapper.deletePartnerByIds( + customerSysids, + PartnerManageConstants.DEFAULT_OPERATOR, + now, + PartnerManageConstants.PARTNER_IS_DELETED_DELETED); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public PartnerManageDto mockSyncPartner(Long customerSysid) { + PartnerManageDto partner = partnerManageMapper.selectPartnerById(customerSysid); + if (partner == null) { + throw new ServiceException("相对方不存在或已删除"); + } + + attachContacts(Collections.singletonList(partner)); + + LocalDateTime now = LocalDateTime.now(); + String oppositeId = StrUtilHelper.hasText(partner.getOppositeId()) + ? partner.getOppositeId() + : PartnerIdUtils.buildGroupPartnerId(); + + PartnerSyncPayloadBuilder.buildCollectOppositesPayload(partner); + // PushPayloadDTO payloadDTO = + // PartnerSyncPayloadBuilder.buildCollectOppositesPayload(partner); + // Map response = + // dataCollectionPushService.collectOpposites(payloadDTO); + String syncMessage = "本地测试模拟同步成功。TODO:后续在这里调用相对方信息采集接口 collectOpposites。"; + // // 从法务系统响应中取出返回的 oppositeId(比如 response 里有个 "jtId" 字段) + // String oppositeIdFromLawSystem = ...; // 具体 key 看法务返回的字段名 + // String syncMessage = response.containsKey("msg") + // ? String.valueOf(response.get("msg")) + // : "相对方信息采集同步成功"; + + // // 回写法务返回的 oppositeId,而不是本地生成的 + partnerManageMapper.updatePartnerSyncInfo( + customerSysid, + oppositeId, + PartnerManageConstants.SYNC_STATUS_SYNCED, + now, + syncMessage, + PartnerManageConstants.DEFAULT_OPERATOR, + now); + return selectPartnerById(customerSysid); + } + + private void attachContacts(List partnerList) { + if (partnerList == null || partnerList.isEmpty()) { + return; + } + + List customerSysids = partnerList.stream() + .map(PartnerManageDto::getCustomerSysid) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (customerSysids.isEmpty()) { + return; + } + + List contacts = partnerManageMapper.selectPartnerContactsByPartnerIds(customerSysids); + Map> contactMap = contacts.stream() + .collect(Collectors.groupingBy(PartnerContactDto::getPartnerRecordId)); + + partnerList.forEach(item -> { + List currentContacts = contactMap.getOrDefault(item.getCustomerSysid(), + Collections.emptyList()); + partnerDataHandler.fillContactUiKeys(currentContacts); + item.setContacts(currentContacts); + }); + } + + private void saveContacts(Long customerSysid, List contacts, LocalDateTime now) { + if (customerSysid == null || contacts == null || contacts.isEmpty()) { + return; + } + + int sortOrder = 1; + for (PartnerContactDto contact : contacts) { + contact.setId(partnerManageMapper.selectPartnerContactSeqNextVal()); + contact.setPartnerRecordId(customerSysid); + contact.setSortOrder(sortOrder++); + contact.setDelFlag(PartnerManageConstants.CONTACT_DEL_FLAG_NORMAL); + } + + partnerManageMapper.batchInsertPartnerContacts( + contacts, + PartnerManageConstants.DEFAULT_OPERATOR, + now, + PartnerManageConstants.DEFAULT_OPERATOR, + now, + PartnerManageConstants.CONTACT_DEL_FLAG_NORMAL); + } + + private String ensureOppositeCode(String oppositeCode) { + return ensureOppositeCode(oppositeCode, null); + } + + private String ensureOppositeCode(String oppositeCode, String fallback) { + if (StrUtilHelper.hasText(oppositeCode)) { + return oppositeCode; + } + if (StrUtilHelper.hasText(fallback)) { + return fallback; + } + return PartnerIdUtils.buildPartnerId(); + } + + private String ensureValue(String value, String fallback, String defaultValue) { + if (StrUtilHelper.hasText(value)) { + return value; + } + if (StrUtilHelper.hasText(fallback)) { + return fallback; + } + return defaultValue; + } + + /** + * 避免为了一个判空再引入新依赖,这里保留一个最小工具。 + */ + private static final class StrUtilHelper { + + private StrUtilHelper() { + } + + private static boolean hasText(String value) { + return value != null && !value.isBlank(); + } + } +} diff --git a/src/main/java/com/nbport/zgwl/partner/utils/PartnerIdUtils.java b/src/main/java/com/nbport/zgwl/partner/utils/PartnerIdUtils.java new file mode 100644 index 0000000..1f43903 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/utils/PartnerIdUtils.java @@ -0,0 +1,41 @@ +package com.nbport.zgwl.partner.utils; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.concurrent.ThreadLocalRandom; + +/** + * 相关方ID生成工具 + * + * 本地相关方ID:自己系统生成,后续同步给法务系统时对应 o_id。 + * 集团相对方ID:这里先做模拟生成,后续真实联调时用法务系统回传值覆盖。 + */ +public final class PartnerIdUtils { + + private static final String PARTNER_PREFIX = "OPP"; + + private static final String GROUP_PREFIX = "JT"; + + private PartnerIdUtils() { + } + + public static String buildPartnerId() { + return PARTNER_PREFIX + "-" + formatNow("yyyyMMddHHmmss") + "-" + randomDigits(4); + } + + public static String buildGroupPartnerId() { + return GROUP_PREFIX + "-" + formatNow("yyyyMMddHHmmss") + "-" + randomDigits(4); + } + + private static String formatNow(String pattern) { + return new SimpleDateFormat(pattern).format(new Date()); + } + + private static String randomDigits(int length) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < length; i++) { + builder.append(ThreadLocalRandom.current().nextInt(10)); + } + return builder.toString(); + } +} diff --git a/src/main/java/com/nbport/zgwl/partner/utils/PartnerManageConstants.java b/src/main/java/com/nbport/zgwl/partner/utils/PartnerManageConstants.java new file mode 100644 index 0000000..f4652d6 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/utils/PartnerManageConstants.java @@ -0,0 +1,42 @@ +package com.nbport.zgwl.partner.utils; + +/** + * 相对方模块常量 + */ +public final class PartnerManageConstants { + + private PartnerManageConstants() { + } + + public static final String REQUEST_PREFIX = "/partner/manage"; + + public static final String DEFAULT_OPERATOR = "local_test"; + + public static final String PARTNER_IS_DELETED_NORMAL = "N"; + + public static final String PARTNER_IS_DELETED_DELETED = "Y"; + + public static final String CONTACT_DEL_FLAG_NORMAL = "0"; + + public static final String CONTACT_DEL_FLAG_DELETED = "2"; + + public static final String OPPOSITE_STATUS_EFFECTUAL = "EFFECTUAL"; + + public static final String GAT_FLAG_YES = "Y"; + + public static final String GAT_FLAG_NO = "N"; + + public static final String GROUP_FLAG_YES = "Y"; + + public static final String GROUP_FLAG_NO = "N"; + + public static final String FLAG_ONE = "1"; + + public static final String FLAG_ZERO = "0"; + + public static final String SYNC_STATUS_UNSYNCED = "unsynced"; + + public static final String SYNC_STATUS_SYNCED = "synced"; + + public static final String SYNC_STATUS_FAILED = "failed"; +} diff --git a/src/main/java/com/nbport/zgwl/partner/utils/PartnerSyncPayloadBuilder.java b/src/main/java/com/nbport/zgwl/partner/utils/PartnerSyncPayloadBuilder.java new file mode 100644 index 0000000..4c2ff35 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/partner/utils/PartnerSyncPayloadBuilder.java @@ -0,0 +1,71 @@ +package com.nbport.zgwl.partner.utils; + +import com.nbport.zgwl.contractpush.dto.PushPayloadDTO; +import com.nbport.zgwl.partner.dto.PartnerContactDto; +import com.nbport.zgwl.partner.dto.PartnerManageDto; + +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 相对方同步报文构造工具 + */ +public final class PartnerSyncPayloadBuilder { + + private PartnerSyncPayloadBuilder() { + } + + public static PushPayloadDTO buildCollectOppositesPayload(PartnerManageDto partner) { + Map payload = new LinkedHashMap<>(); + payload.put("o_id", partner.getCustomerSysid() == null ? null : String.valueOf(partner.getCustomerSysid())); + payload.put("o_name", partner.getCustomerNameC()); + payload.put("o_type", partner.getRelativeType()); + payload.put("o_nature", partner.getOppositeCharacter()); + payload.put("o_orgNature", convertOrgNature(partner.getNatureOfEnterpriseOrg())); + payload.put("o_orgNature_2", partner.getNatureOfEnterpriseOrgLev()); + payload.put("o_GAT", PartnerManageConstants.GAT_FLAG_YES.equals(partner.getGatFlag()) + ? PartnerManageConstants.FLAG_ONE + : PartnerManageConstants.FLAG_ZERO); + payload.put("o_isGroup", PartnerManageConstants.GROUP_FLAG_YES.equals(partner.getGroupFlag()) + ? PartnerManageConstants.FLAG_ONE + : PartnerManageConstants.FLAG_ZERO); + payload.put("o_creditCode", partner.getCustomerTaxno()); + payload.put("o_zjType", partner.getCertificateType()); + payload.put("o_zjNo", partner.getCertificateNum()); + payload.put("o_tin", partner.getTinCode()); + payload.put("o_country", partner.getCountry()); + payload.put("o_legalPerson", partner.getLegalPersonName()); + payload.put("o_address", partner.getCustomerRegaddr()); + payload.put("o_capital", partner.getRegCapital()); + payload.put("o_creator", partner.getCreateBy()); + payload.put("o_creationTime", partner.getCreateTime() == null ? null : partner.getCreateTime().toString().replace("T", " ")); + payload.put("o_contacts", buildContactList(partner.getContacts())); + + PushPayloadDTO requestDTO = new PushPayloadDTO(); + requestDTO.setPayload(payload); + return requestDTO; + } + + private static List> buildContactList(List contacts) { + if (contacts == null) { + return List.of(); + } + return contacts.stream().map(item -> { + Map contact = new LinkedHashMap<>(); + contact.put("name", item.getContactName()); + contact.put("phone", item.getContactPhone()); + contact.put("postalAddress", item.getPostalAddress()); + contact.put("email", item.getEmail()); + return contact; + }).collect(Collectors.toList()); + } + + private static String convertOrgNature(String natureOfEnterpriseOrg) { + if ("ZY".equals(natureOfEnterpriseOrg)) { + return "ZF"; + } + return natureOfEnterpriseOrg; + } +} diff --git a/src/main/java/com/nbport/zgwl/seal/config/SealConfig.java b/src/main/java/com/nbport/zgwl/seal/config/SealConfig.java new file mode 100644 index 0000000..1119424 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/seal/config/SealConfig.java @@ -0,0 +1,54 @@ +package com.nbport.zgwl.seal.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 签章平台配置类 + * 从application.yml中读取签章平台相关配置 + * + * 在application.yml中添加如下配置: + * seal: + * appKey: A7C4FD32BE454B5 + * appSecret: 你的appSecret(联系浙江CA获取) + * baseUrl: https://saasapi.certca.cn + * + * @author xiaofengzhou + */ +@Component +@ConfigurationProperties(prefix = "seal") +public class SealConfig { + + /** 应用Key,由浙江CA提供,用于获取访问令牌 */ + private String appKey; + + /** 应用密钥,由浙江CA提供,用于计算HMAC签名 */ + private String appSecret; + + /** 签章平台API基础地址,例如 https://saasapi.certca.cn */ + private String baseUrl; + + public String getAppKey() { + return appKey; + } + + public void setAppKey(String appKey) { + this.appKey = appKey; + } + + public String getAppSecret() { + return appSecret; + } + + public void setAppSecret(String appSecret) { + this.appSecret = appSecret; + } + + public String getBaseUrl() { + return baseUrl; + } + + public void setBaseUrl(String baseUrl) { + this.baseUrl = baseUrl; + } +} diff --git a/src/main/java/com/nbport/zgwl/seal/config/SealWebSocketConfig.java b/src/main/java/com/nbport/zgwl/seal/config/SealWebSocketConfig.java new file mode 100644 index 0000000..2813133 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/seal/config/SealWebSocketConfig.java @@ -0,0 +1,34 @@ +package com.nbport.zgwl.seal.config; + +import com.nbport.zgwl.seal.handler.SealWebSocketHandler; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.config.annotation.WebSocketConfigurer; +import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; + +/** + * WebSocket配置类 + * + * 配置签章平台回调的WebSocket连接端点 + * 前端通过 ws://localhost:8080/seal/ws/callback 建立连接 + * + * 注意: + * 1. setAllowedOrigins("*") 允许所有来源,生产环境应限制为具体域名 + * 2. WebSocket不走Spring Security的HTTP认证,需要额外考虑安全性 + * + * @author xiaofengzhou + */ +@Configuration +@EnableWebSocket +public class SealWebSocketConfig implements WebSocketConfigurer { + + @Autowired + private SealWebSocketHandler sealWebSocketHandler; + + @Override + public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { + registry.addHandler(sealWebSocketHandler, "/seal/ws/callback") + .setAllowedOrigins("*"); + } +} diff --git a/src/main/java/com/nbport/zgwl/seal/controller/SealCallbackController.java b/src/main/java/com/nbport/zgwl/seal/controller/SealCallbackController.java new file mode 100644 index 0000000..cafc714 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/seal/controller/SealCallbackController.java @@ -0,0 +1,111 @@ +package com.nbport.zgwl.seal.controller; + +import com.alibaba.fastjson2.JSON; +import com.nbport.zgwl.common.R; +import com.nbport.zgwl.seal.handler.SealWebSocketHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * 签章平台回调接口控制器 + * + * @author xiaofengzhou + */ +@RestController +@RequestMapping("/seal/callback") +public class SealCallbackController { + + private static final Logger log = LoggerFactory.getLogger(SealCallbackController.class); + + @Autowired + private SealWebSocketHandler webSocketHandler; + + @PostMapping("/login") + public R loginCallback(@RequestBody Map params) { + log.info("收到身份认证回调: {}", JSON.toJSONString(params)); + + try { + String requestId = (String) params.get("requestId"); + webSocketHandler.sendCallbackMessage(requestId, "login", params); + return R.success("回调接收成功"); + } catch (Exception e) { + log.error("处理身份认证回调失败", e); + return R.error("回调处理失败: " + e.getMessage()); + } + } + + @PostMapping("/sign") + public R signCallback(@RequestBody Map params) { + log.info("收到签署回调: {}", JSON.toJSONString(params)); + + try { + String contractId = (String) params.get("contractId"); + String requestId = (String) params.get("requestId"); + webSocketHandler.sendCallbackMessage(requestId, "sign", params); + webSocketHandler.sendCallbackMessage(contractId, "contract_status", params); + return R.success("回调接收成功"); + } catch (Exception e) { + log.error("处理签署回调失败", e); + return R.error("回调处理失败: " + e.getMessage()); + } + } + + @PostMapping("/presign") + public R presignCallback(@RequestBody Map params) { + log.info("收到预盖章回调: {}", JSON.toJSONString(params)); + + try { + String contractId = (String) params.get("contractId"); + String requestId = (String) params.get("requestId"); + webSocketHandler.sendCallbackMessage(requestId, "presign", params); + webSocketHandler.sendCallbackMessage(contractId, "contract_status", params); + return R.success("回调接收成功"); + } catch (Exception e) { + log.error("处理预盖章回调失败", e); + return R.error("回调处理失败: " + e.getMessage()); + } + } + + @PostMapping("/trusteeship") + public R trusteeshipCallback(@RequestBody Map params) { + log.info("收到托管章回调: {}", JSON.toJSONString(params)); + + try { + String requestId = (String) params.get("requestId"); + webSocketHandler.sendCallbackMessage(requestId, "trusteeship", params); + return R.success("回调接收成功"); + } catch (Exception e) { + log.error("处理托管章回调失败", e); + return R.error("回调处理失败: " + e.getMessage()); + } + } + + @PostMapping("/signQrCode") + public R signQrCodeCallback(@RequestBody Map params) { + log.info("收到签名二维码回调: {}", JSON.toJSONString(params)); + + try { + String requestId = (String) params.get("requestId"); + webSocketHandler.sendCallbackMessage(requestId, "signQrCode", params); + return R.success("回调接收成功"); + } catch (Exception e) { + log.error("处理签名二维码回调失败", e); + return R.error("回调处理失败: " + e.getMessage()); + } + } + + @GetMapping("/poll/{requestId}") + public R> pollCallback(@PathVariable String requestId) { + Map callbackData = webSocketHandler.getCallbackData(requestId); + return R.success(callbackData); + } +} diff --git a/src/main/java/com/nbport/zgwl/seal/controller/SealProxyController.java b/src/main/java/com/nbport/zgwl/seal/controller/SealProxyController.java new file mode 100644 index 0000000..2c658c1 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/seal/controller/SealProxyController.java @@ -0,0 +1,353 @@ +package com.nbport.zgwl.seal.controller; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.nbport.zgwl.common.R; +import com.nbport.zgwl.seal.config.SealConfig; +import com.nbport.zgwl.seal.service.SealTokenService; +import com.nbport.zgwl.seal.utils.SealHttpClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.util.HashMap; +import java.util.Map; + +/** + * 签章平台代理控制器 + * + * @author xiaofengzhou + */ +@RestController +@RequestMapping("/seal/api") +public class SealProxyController { + + private static final Logger log = LoggerFactory.getLogger(SealProxyController.class); + + @Autowired + private SealConfig sealConfig; + + @Autowired + private SealTokenService sealTokenService; + + private String proxyPost(String apiPath, String requestBody) { + String accessToken = sealTokenService.getAccessToken(); + String url = sealConfig.getBaseUrl() + apiPath; + log.info("代理请求签章平台: {} body: {}", url, requestBody); + return SealHttpClient.post(url, requestBody, accessToken, MediaType.APPLICATION_JSON_VALUE); + } + + private String proxyPost(String apiPath) { + return proxyPost(apiPath, "{}"); + } + + private R parseSealResponse(String responseJson) { + try { + JSONObject result = JSON.parseObject(responseJson); + if (result.getBooleanValue("isSuccess")) { + return R.success(result.get("data")); + } else { + return R.error(result.getString("msg")); + } + } catch (Exception e) { + log.error("解析签章平台响应失败: {}", responseJson, e); + return R.error("解析签章平台响应失败: " + e.getMessage()); + } + } + + @PostMapping("/token/getAccessToken") + public R getAccessToken() { + try { + String token = sealTokenService.forceRefreshToken(); + Map data = new HashMap<>(); + data.put("access_token", token); + data.put("expires_in", 7200); + return R.success(data); + } catch (Exception e) { + log.error("获取Token失败", e); + return R.error("获取Token失败: " + e.getMessage()); + } + } + + @PostMapping("/token/clear") + public R clearToken() { + sealTokenService.clearToken(); + return R.success("Token已清除"); + } + + @PostMapping("/qrcode/getLoginQrCode") + public R getLoginQrCode(@RequestBody Map params) { + try { + String notifyUrl = params.get("notifyUrl"); + JSONObject body = new JSONObject(); + body.put("notifyUrl", notifyUrl); + String response = proxyPost("/saas/api/qrcode/getLoginQrCode", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取身份认证二维码失败", e); + return R.error("获取身份认证二维码失败: " + e.getMessage()); + } + } + + @PostMapping("/contract/start") + public R startContract(@RequestParam("file") MultipartFile file, + @RequestParam("contractName") String contractName, + @RequestParam(value = "description", required = false) String description) { + try { + String accessToken = sealTokenService.getAccessToken(); + String url = sealConfig.getBaseUrl() + "/saas/api/contract/start"; + String response = SealHttpClient.postMultipart(url, file, contractName, description, accessToken); + return parseSealResponse(response); + } catch (Exception e) { + log.error("创建签署流程失败", e); + return R.error("创建签署流程失败: " + e.getMessage()); + } + } + + @PostMapping("/qrcode/getStandardSignQrCode") + public R getStandardSignQrCode(@RequestBody Map params) { + try { + JSONObject body = buildSignQrCodeBody(params); + String response = proxyPost("/saas/api/qrcode/getStandardSignQrCode", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取标准签章二维码失败", e); + return R.error("获取标准签章二维码失败: " + e.getMessage()); + } + } + + @PostMapping("/qrcode/getPreSignQrCode") + public R getPreSignQrCode(@RequestBody Map params) { + try { + JSONObject body = buildPreSignBody(params); + String response = proxyPost("/saas/api/qrcode/getPreSignQrCode", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取预盖章二维码失败", e); + return R.error("获取预盖章二维码失败: " + e.getMessage()); + } + } + + @PostMapping("/qrcode/getTrusteeshipSealQrCode") + public R getTrusteeshipSealQrCode(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("notifyUrl", params.get("notifyUrl")); + String response = proxyPost("/saas/api/qrcode/getTrusteeshipSealQrCode", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取托管章二维码失败", e); + return R.error("获取托管章二维码失败: " + e.getMessage()); + } + } + + @PostMapping("/contract/confirmSign") + public R confirmSign(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(params); + String response = proxyPost("/saas/api/contract/confirmSign", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("托管签署失败", e); + return R.error("托管签署失败: " + e.getMessage()); + } + } + + @PostMapping("/seal/getTimestamp") + public R getTimestamp(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("formatPattern", params.getOrDefault("formatPattern", "yyyy-MM-dd")); + String response = proxyPost("/saas/api/seal/getTimestamp", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取时间戳印章失败", e); + return R.error("获取时间戳印章失败: " + e.getMessage()); + } + } + + @PostMapping("/contract/signTimestamp") + public R signTimestamp(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(params); + String response = proxyPost("/saas/api/contract/signTimestamp", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("签署时间签章失败", e); + return R.error("签署时间签章失败: " + e.getMessage()); + } + } + + @PostMapping("/seal/getCommentSeal") + public R getCommentSeal(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("text", params.get("text")); + body.put("fontSize", params.get("fontSize")); + String response = proxyPost("/saas/api/seal/getCommentSeal", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取批注章预览失败", e); + return R.error("获取批注章预览失败: " + e.getMessage()); + } + } + + @PostMapping("/contract/signCommentSeal") + public R signCommentSeal(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(params); + String response = proxyPost("/saas/api/contract/signCommentSeal", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("签署批注签章失败", e); + return R.error("签署批注签章失败: " + e.getMessage()); + } + } + + @PostMapping("/contract/complete") + public R completeContract(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("contractId", params.get("contractId")); + String response = proxyPost("/saas/api/contract/complete", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("完成签署流程失败", e); + return R.error("完成签署流程失败: " + e.getMessage()); + } + } + + @PostMapping("/contract/cancel") + public R cancelContract(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("contractId", params.get("contractId")); + String response = proxyPost("/saas/api/contract/cancel", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("取消签署流程失败", e); + return R.error("取消签署流程失败: " + e.getMessage()); + } + } + + @PostMapping("/user/getUserUniqueId") + public R getUserUniqueId(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("name", params.get("name")); + body.put("mobile", params.get("mobile")); + body.put("idCode", params.get("idCode")); + String response = proxyPost("/saas/api/user/getUserUniqueId ", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取用户身份编码失败", e); + return R.error("获取用户身份编码失败: " + e.getMessage()); + } + } + + @PostMapping("/seal/getSealBySignerId") + public R getSealBySignerId(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(params); + String response = proxyPost("/saas/api/seal/getSealBySignerId", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取用户印章失败", e); + return R.error("获取用户印章失败: " + e.getMessage()); + } + } + + @PostMapping("/contract/getDetail") + public R getContractDetail(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("contractId", params.get("contractId")); + String response = proxyPost("/saas/api/contract/getDetail", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取合约详情失败", e); + return R.error("获取合约详情失败: " + e.getMessage()); + } + } + + @PostMapping("/contract/preview") + public R previewContract(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("contractId", params.get("contractId")); + String response = proxyPost("/saas/api/contract/preview", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("预览合约文件失败", e); + return R.error("预览合约文件失败: " + e.getMessage()); + } + } + + @PostMapping("/contract/download") + public R downloadContract(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("contractId", params.get("contractId")); + String response = proxyPost("/saas/api/contract/download", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("下载合约文件失败", e); + return R.error("下载合约文件失败: " + e.getMessage()); + } + } + + @PostMapping("/user/getUserInfos") + public R getUserInfos(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("requestId", params.get("requestId")); + String response = proxyPost("/api/user/getUserInfos", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取用户隐私数据失败", e); + return R.error("获取用户隐私数据失败: " + e.getMessage()); + } + } + + @PostMapping("/qrcode/getSignQrCode") + public R getSignQrCode(@RequestBody Map params) { + try { + JSONObject body = new JSONObject(); + body.put("notifyUrl", params.get("notifyUrl")); + body.put("originalBase64Str", params.get("originalBase64Str")); + String response = proxyPost("/saas/api/qrcode/getSignQrCode", body.toJSONString()); + return parseSealResponse(response); + } catch (Exception e) { + log.error("获取签名二维码失败", e); + return R.error("获取签名二维码失败: " + e.getMessage()); + } + } + + private JSONObject buildSignQrCodeBody(Map params) { + JSONObject body = new JSONObject(); + body.put("contractId", params.get("contractId")); + body.put("notifyUrl", params.get("notifyUrl")); + body.put("isNeedUrlLink", params.getOrDefault("isNeedUrlLink", false)); + body.put("sealConfigs", params.get("sealConfigs")); + body.put("pagingSealConfig", params.get("pagingSealConfig")); + return body; + } + + private JSONObject buildPreSignBody(Map params) { + JSONObject body = new JSONObject(); + body.put("contractId", params.get("contractId")); + body.put("notifyUrl", params.get("notifyUrl")); + body.put("isNeedUrlLink", params.getOrDefault("isNeedUrlLink", false)); + body.put("sealConfigs", params.get("sealConfigs")); + body.put("pagingSealConfigs", params.get("pagingSealConfigs")); + body.put("timestampSealConfigs", params.get("timestampSealConfigs")); + return body; + } +} diff --git a/src/main/java/com/nbport/zgwl/seal/dto/SealApiDTO.java b/src/main/java/com/nbport/zgwl/seal/dto/SealApiDTO.java new file mode 100644 index 0000000..4872afa --- /dev/null +++ b/src/main/java/com/nbport/zgwl/seal/dto/SealApiDTO.java @@ -0,0 +1,923 @@ +package com.nbport.zgwl.seal.dto; + +import java.util.List; + +/** + * 签章平台API数据传输对象(DTO) + * + * 包含所有签章平台API的请求和响应数据结构 + * 统一管理在一个文件中,方便后续迁移和维护 + * + * @author xiaofengzhou + */ +public class SealApiDTO { + + public static class SealResponse { + private boolean isSuccess; + private String msg; + private int code; + private T data; + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + } + + public static class TokenResponseData { + private String access_token; + private Integer expires_in; + + public String getAccess_token() { + return access_token; + } + + public void setAccess_token(String access_token) { + this.access_token = access_token; + } + + public Integer getExpires_in() { + return expires_in; + } + + public void setExpires_in(Integer expires_in) { + this.expires_in = expires_in; + } + } + + public static class ContractStartData { + private String contractId; + private Long fileSize; + private Integer imageWidth; + private Integer imageHeight; + private String pdfUrl; + private List pdfToImageList; + + public String getContractId() { + return contractId; + } + + public void setContractId(String contractId) { + this.contractId = contractId; + } + + public Long getFileSize() { + return fileSize; + } + + public void setFileSize(Long fileSize) { + this.fileSize = fileSize; + } + + public Integer getImageWidth() { + return imageWidth; + } + + public void setImageWidth(Integer imageWidth) { + this.imageWidth = imageWidth; + } + + public Integer getImageHeight() { + return imageHeight; + } + + public void setImageHeight(Integer imageHeight) { + this.imageHeight = imageHeight; + } + + public String getPdfUrl() { + return pdfUrl; + } + + public void setPdfUrl(String pdfUrl) { + this.pdfUrl = pdfUrl; + } + + public List getPdfToImageList() { + return pdfToImageList; + } + + public void setPdfToImageList(List pdfToImageList) { + this.pdfToImageList = pdfToImageList; + } + } + + public static class QrCodeResponseData { + private String requestId; + private Integer expiresIn; + private String qrCode; + private String urlLink; + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public Integer getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(Integer expiresIn) { + this.expiresIn = expiresIn; + } + + public String getQrCode() { + return qrCode; + } + + public void setQrCode(String qrCode) { + this.qrCode = qrCode; + } + + public String getUrlLink() { + return urlLink; + } + + public void setUrlLink(String urlLink) { + this.urlLink = urlLink; + } + } + + public static class SealConfigItem { + private String sealId; + private Float x; + private Float y; + private Double scale; + private Integer pageNum; + + public String getSealId() { + return sealId; + } + + public void setSealId(String sealId) { + this.sealId = sealId; + } + + public Float getX() { + return x; + } + + public void setX(Float x) { + this.x = x; + } + + public Float getY() { + return y; + } + + public void setY(Float y) { + this.y = y; + } + + public Double getScale() { + return scale; + } + + public void setScale(Double scale) { + this.scale = scale; + } + + public Integer getPageNum() { + return pageNum; + } + + public void setPageNum(Integer pageNum) { + this.pageNum = pageNum; + } + } + + public static class PagingSealConfig { + private String sealId; + private Double scale; + private Integer startPage; + private Integer endPage; + private Integer coordinateY; + + public String getSealId() { + return sealId; + } + + public void setSealId(String sealId) { + this.sealId = sealId; + } + + public Double getScale() { + return scale; + } + + public void setScale(Double scale) { + this.scale = scale; + } + + public Integer getStartPage() { + return startPage; + } + + public void setStartPage(Integer startPage) { + this.startPage = startPage; + } + + public Integer getEndPage() { + return endPage; + } + + public void setEndPage(Integer endPage) { + this.endPage = endPage; + } + + public Integer getCoordinateY() { + return coordinateY; + } + + public void setCoordinateY(Integer coordinateY) { + this.coordinateY = coordinateY; + } + } + + public static class TimestampSealConfig { + private String formatPattern; + private Float x; + private Float y; + private Integer pageNum; + private Double scale; + private Integer width; + private Integer height; + + public String getFormatPattern() { + return formatPattern; + } + + public void setFormatPattern(String formatPattern) { + this.formatPattern = formatPattern; + } + + public Float getX() { + return x; + } + + public void setX(Float x) { + this.x = x; + } + + public Float getY() { + return y; + } + + public void setY(Float y) { + this.y = y; + } + + public Integer getPageNum() { + return pageNum; + } + + public void setPageNum(Integer pageNum) { + this.pageNum = pageNum; + } + + public Double getScale() { + return scale; + } + + public void setScale(Double scale) { + this.scale = scale; + } + + public Integer getWidth() { + return width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + } + + public static class SignatureResult { + private String contractId; + private String contractName; + private Integer contractStatus; + private String signatureId; + private String signer; + private String signatureTime; + private List signatureList; + + public String getContractId() { + return contractId; + } + + public void setContractId(String contractId) { + this.contractId = contractId; + } + + public String getContractName() { + return contractName; + } + + public void setContractName(String contractName) { + this.contractName = contractName; + } + + public Integer getContractStatus() { + return contractStatus; + } + + public void setContractStatus(Integer contractStatus) { + this.contractStatus = contractStatus; + } + + public String getSignatureId() { + return signatureId; + } + + public void setSignatureId(String signatureId) { + this.signatureId = signatureId; + } + + public String getSigner() { + return signer; + } + + public void setSigner(String signer) { + this.signer = signer; + } + + public String getSignatureTime() { + return signatureTime; + } + + public void setSignatureTime(String signatureTime) { + this.signatureTime = signatureTime; + } + + public List getSignatureList() { + return signatureList; + } + + public void setSignatureList(List signatureList) { + this.signatureList = signatureList; + } + } + + public static class SignatureItem { + private String owner; + private String signatureId; + private String signer; + private String signatureTime; + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String getSignatureId() { + return signatureId; + } + + public void setSignatureId(String signatureId) { + this.signatureId = signatureId; + } + + public String getSigner() { + return signer; + } + + public void setSigner(String signer) { + this.signer = signer; + } + + public String getSignatureTime() { + return signatureTime; + } + + public void setSignatureTime(String signatureTime) { + this.signatureTime = signatureTime; + } + } + + public static class SealInfo { + private String sealId; + private String sealName; + private String pictureUrl; + private Integer sealType; + private String owner; + + public String getSealId() { + return sealId; + } + + public void setSealId(String sealId) { + this.sealId = sealId; + } + + public String getSealName() { + return sealName; + } + + public void setSealName(String sealName) { + this.sealName = sealName; + } + + public String getPictureUrl() { + return pictureUrl; + } + + public void setPictureUrl(String pictureUrl) { + this.pictureUrl = pictureUrl; + } + + public Integer getSealType() { + return sealType; + } + + public void setSealType(Integer sealType) { + this.sealType = sealType; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + } + + public static class ContractDetail { + private String contractId; + private String contractName; + private Integer status; + private String statusName; + private String startTime; + private Integer fileSize; + private List signatureList; + private Integer imageWidth; + private Integer imageHeight; + private String pdfUrl; + private List pdfToImageList; + private String downloadUrl; + + public String getContractId() { + return contractId; + } + + public void setContractId(String contractId) { + this.contractId = contractId; + } + + public String getContractName() { + return contractName; + } + + public void setContractName(String contractName) { + this.contractName = contractName; + } + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getStatusName() { + return statusName; + } + + public void setStatusName(String statusName) { + this.statusName = statusName; + } + + public String getStartTime() { + return startTime; + } + + public void setStartTime(String startTime) { + this.startTime = startTime; + } + + public Integer getFileSize() { + return fileSize; + } + + public void setFileSize(Integer fileSize) { + this.fileSize = fileSize; + } + + public List getSignatureList() { + return signatureList; + } + + public void setSignatureList(List signatureList) { + this.signatureList = signatureList; + } + + public Integer getImageWidth() { + return imageWidth; + } + + public void setImageWidth(Integer imageWidth) { + this.imageWidth = imageWidth; + } + + public Integer getImageHeight() { + return imageHeight; + } + + public void setImageHeight(Integer imageHeight) { + this.imageHeight = imageHeight; + } + + public String getPdfUrl() { + return pdfUrl; + } + + public void setPdfUrl(String pdfUrl) { + this.pdfUrl = pdfUrl; + } + + public List getPdfToImageList() { + return pdfToImageList; + } + + public void setPdfToImageList(List pdfToImageList) { + this.pdfToImageList = pdfToImageList; + } + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + } + + public static class SignerIdData { + private String signerId; + + public String getSignerId() { + return signerId; + } + + public void setSignerId(String signerId) { + this.signerId = signerId; + } + } + + public static class UserInfoData { + private String name; + private String mobile; + private String code; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMobile() { + return mobile; + } + + public void setMobile(String mobile) { + this.mobile = mobile; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + } + + public static class LoginCallbackData { + private String requestId; + private String accountId; + private String accountType; + private String username; + private String entName; + private String entCode; + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getAccountType() { + return accountType; + } + + public void setAccountType(String accountType) { + this.accountType = accountType; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEntName() { + return entName; + } + + public void setEntName(String entName) { + this.entName = entName; + } + + public String getEntCode() { + return entCode; + } + + public void setEntCode(String entCode) { + this.entCode = entCode; + } + } + + public static class SignCallbackData { + private String contractId; + private String contractName; + private Integer contractStatus; + private String signer; + private String signerId; + private String requestId; + + public String getContractId() { + return contractId; + } + + public void setContractId(String contractId) { + this.contractId = contractId; + } + + public String getContractName() { + return contractName; + } + + public void setContractName(String contractName) { + this.contractName = contractName; + } + + public Integer getContractStatus() { + return contractStatus; + } + + public void setContractStatus(Integer contractStatus) { + this.contractStatus = contractStatus; + } + + public String getSigner() { + return signer; + } + + public void setSigner(String signer) { + this.signer = signer; + } + + public String getSignerId() { + return signerId; + } + + public void setSignerId(String signerId) { + this.signerId = signerId; + } + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + } + + public static class TrusteeshipCallbackData { + private String requestId; + private List sealList; + private String signer; + private String signerId; + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public List getSealList() { + return sealList; + } + + public void setSealList(List sealList) { + this.sealList = sealList; + } + + public String getSigner() { + return signer; + } + + public void setSigner(String signer) { + this.signer = signer; + } + + public String getSignerId() { + return signerId; + } + + public void setSignerId(String signerId) { + this.signerId = signerId; + } + } + + public static class TrusteeshipSealItem { + private String sealId; + private String sealName; + private String entName; + private String pictureUrl; + private String owner; + private String ownerSubAccountId; + private Integer sealType; + + public String getSealId() { + return sealId; + } + + public void setSealId(String sealId) { + this.sealId = sealId; + } + + public String getSealName() { + return sealName; + } + + public void setSealName(String sealName) { + this.sealName = sealName; + } + + public String getEntName() { + return entName; + } + + public void setEntName(String entName) { + this.entName = entName; + } + + public String getPictureUrl() { + return pictureUrl; + } + + public void setPictureUrl(String pictureUrl) { + this.pictureUrl = pictureUrl; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public String getOwnerSubAccountId() { + return ownerSubAccountId; + } + + public void setOwnerSubAccountId(String ownerSubAccountId) { + this.ownerSubAccountId = ownerSubAccountId; + } + + public Integer getSealType() { + return sealType; + } + + public void setSealType(Integer sealType) { + this.sealType = sealType; + } + } + + public static class SignQrCodeCallbackData { + private String requestId; + private String accountId; + private String accountType; + private String username; + private String entName; + private String entCode; + private String signatureValue; + + public String getRequestId() { + return requestId; + } + + public void setRequestId(String requestId) { + this.requestId = requestId; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getAccountType() { + return accountType; + } + + public void setAccountType(String accountType) { + this.accountType = accountType; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEntName() { + return entName; + } + + public void setEntName(String entName) { + this.entName = entName; + } + + public String getEntCode() { + return entCode; + } + + public void setEntCode(String entCode) { + this.entCode = entCode; + } + + public String getSignatureValue() { + return signatureValue; + } + + public void setSignatureValue(String signatureValue) { + this.signatureValue = signatureValue; + } + } +} diff --git a/src/main/java/com/nbport/zgwl/seal/handler/SealWebSocketHandler.java b/src/main/java/com/nbport/zgwl/seal/handler/SealWebSocketHandler.java new file mode 100644 index 0000000..f93d788 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/seal/handler/SealWebSocketHandler.java @@ -0,0 +1,151 @@ +package com.nbport.zgwl.seal.handler; + +import com.alibaba.fastjson2.JSON; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import org.springframework.web.socket.CloseStatus; +import org.springframework.web.socket.TextMessage; +import org.springframework.web.socket.WebSocketSession; +import org.springframework.web.socket.handler.TextWebSocketHandler; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 签章平台WebSocket处理器 + * + * 职责: + * 1. 管理WebSocket连接 + * 2. 接收签章平台回调后,实时推送给对应的前端页面 + * 3. 提供轮询兜底机制 + * + * @author xiaofengzhou + */ +@Component +public class SealWebSocketHandler extends TextWebSocketHandler { + + private static final Logger log = LoggerFactory.getLogger(SealWebSocketHandler.class); + + private static final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); + + private static final ConcurrentHashMap requestSessionMap = new ConcurrentHashMap<>(); + + private static final ConcurrentHashMap> callbackCache = new ConcurrentHashMap<>(); + + @Override + public void afterConnectionEstablished(WebSocketSession session) throws Exception { + String sessionId = session.getId(); + sessions.put(sessionId, session); + log.info("WebSocket连接建立: sessionId={}", sessionId); + sendMessage(session, "{\"type\":\"connected\",\"sessionId\":\"" + sessionId + "\"}"); + } + + @Override + protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { + String payload = message.getPayload(); + log.debug("收到WebSocket消息: sessionId={}, payload={}", session.getId(), payload); + + try { + Map msgMap = JSON.parseObject(payload, Map.class); + String action = (String) msgMap.get("action"); + + if ("subscribe".equals(action)) { + String requestId = (String) msgMap.get("requestId"); + if (requestId != null) { + requestSessionMap.put(requestId, session.getId()); + log.info("WebSocket订阅: requestId={}, sessionId={}", requestId, session.getId()); + + Map cached = callbackCache.get(requestId); + if (cached != null) { + sendCallbackToSession(session, requestId, (String) cached.get("callbackType"), cached); + callbackCache.remove(requestId); + } + } + } else if ("unsubscribe".equals(action)) { + String requestId = (String) msgMap.get("requestId"); + if (requestId != null) { + requestSessionMap.remove(requestId); + log.info("WebSocket取消订阅: requestId={}", requestId); + } + } else if ("ping".equals(action)) { + sendMessage(session, "{\"type\":\"pong\"}"); + } + } catch (Exception e) { + log.error("处理WebSocket消息失败", e); + } + } + + @Override + public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { + String sessionId = session.getId(); + sessions.remove(sessionId); + requestSessionMap.entrySet().removeIf(entry -> sessionId.equals(entry.getValue())); + log.info("WebSocket连接关闭: sessionId={}, status={}", sessionId, status); + } + + @Override + public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { + log.error("WebSocket传输错误: sessionId={}", session.getId(), exception); + sessions.remove(session.getId()); + } + + public void sendCallbackMessage(String requestId, String callbackType, Map data) { + if (requestId == null) { + return; + } + + Map cacheEntry = new ConcurrentHashMap<>(); + cacheEntry.put("callbackType", callbackType); + cacheEntry.putAll(data); + callbackCache.put(requestId, cacheEntry); + + String sessionId = requestSessionMap.get(requestId); + if (sessionId != null) { + WebSocketSession session = sessions.get(sessionId); + if (session != null && session.isOpen()) { + sendCallbackToSession(session, requestId, callbackType, data); + log.info("WebSocket推送回调成功: requestId={}, callbackType={}", requestId, callbackType); + } else { + log.warn("WebSocket session不存在或已关闭: sessionId={}, requestId={}", sessionId, requestId); + requestSessionMap.remove(requestId); + } + } else { + log.info("没有前端订阅此requestId的回调: requestId={}, 数据已缓存供轮询使用", requestId); + } + } + + private void sendCallbackToSession(WebSocketSession session, String requestId, String callbackType, Map data) { + try { + Map message = new ConcurrentHashMap<>(); + message.put("type", "callback"); + message.put("requestId", requestId); + message.put("callbackType", callbackType); + message.put("data", data); + sendMessage(session, JSON.toJSONString(message)); + } catch (Exception e) { + log.error("WebSocket推送失败: sessionId={}", session.getId(), e); + } + } + + private void sendMessage(WebSocketSession session, String message) { + try { + if (session.isOpen()) { + synchronized (session) { + session.sendMessage(new TextMessage(message)); + } + } + } catch (IOException e) { + log.error("WebSocket发送消息失败", e); + } + } + + public Map getCallbackData(String requestId) { + return callbackCache.remove(requestId); + } + + public int getOnlineCount() { + return sessions.size(); + } +} diff --git a/src/main/java/com/nbport/zgwl/seal/service/SealTokenService.java b/src/main/java/com/nbport/zgwl/seal/service/SealTokenService.java new file mode 100644 index 0000000..480c011 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/seal/service/SealTokenService.java @@ -0,0 +1,119 @@ +package com.nbport.zgwl.seal.service; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.nbport.zgwl.seal.config.SealConfig; +import com.nbport.zgwl.seal.utils.HMacSHA256Util; +import com.nbport.zgwl.seal.utils.SeaHttpUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; + +import java.util.concurrent.locks.ReentrantLock; + +/** + * 签章平台Token管理服务 + * + * @author xiaofengzhou + */ +@Service +public class SealTokenService { + + private static final Logger log = LoggerFactory.getLogger(SealTokenService.class); + + @Autowired + private SealConfig sealConfig; + + private volatile String cachedToken; + + private volatile long tokenExpireTime = 0; + + private final ReentrantLock lock = new ReentrantLock(); + + private static final long TOKEN_EXPIRE_SECONDS = 7200; + + private static final long REFRESH_BEFORE_SECONDS = 300; + + public String getAccessToken() { + long now = System.currentTimeMillis(); + if (cachedToken != null && now < tokenExpireTime - REFRESH_BEFORE_SECONDS * 1000) { + return cachedToken; + } + + try { + lock.lock(); + if (cachedToken != null && now < tokenExpireTime - REFRESH_BEFORE_SECONDS * 1000) { + return cachedToken; + } + refreshToken(); + return cachedToken; + } finally { + lock.unlock(); + } + } + + public String forceRefreshToken() { + try { + lock.lock(); + refreshToken(); + return cachedToken; + } finally { + lock.unlock(); + } + } + + private void refreshToken() { + try { + String appKey = sealConfig.getAppKey(); + String appSecret = sealConfig.getAppSecret(); + long timestamp = System.currentTimeMillis(); + + JSONObject signData = new JSONObject(); + signData.put("appKey", appKey); + signData.put("timestamp", timestamp); + String signatureStr = JSON.toJSONString(signData); + + String signature = HMacSHA256Util.encrypt(signatureStr, appSecret); + log.debug("签章平台Token请求 - 签名原文: {}, 签名值: {}", signatureStr, signature); + + JSONObject requestBody = new JSONObject(); + requestBody.put("appKey", appKey); + requestBody.put("timestamp", timestamp); + requestBody.put("signature", signature); + + String url = sealConfig.getBaseUrl() + "/saas/api/token/getAccessToken"; + log.info("请求签章平台获取Token, URL: {}", url); + String response = SeaHttpUtils.sendPost(url, requestBody.toJSONString(), MediaType.APPLICATION_JSON_VALUE); + + JSONObject result = JSON.parseObject(response); + if (result.getBooleanValue("isSuccess")) { + JSONObject data = result.getJSONObject("data"); + cachedToken = data.getString("access_token"); + long expiresIn = data.getLongValue("expires_in", TOKEN_EXPIRE_SECONDS); + tokenExpireTime = System.currentTimeMillis() + expiresIn * 1000; + log.info("签章平台Token获取成功, 有效期: {}秒", expiresIn); + } else { + String msg = result.getString("msg"); + log.error("签章平台Token获取失败: {}", msg); + throw new RuntimeException("获取签章平台Token失败: " + msg); + } + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + log.error("获取签章平台Token异常", e); + throw new RuntimeException("获取签章平台Token异常: " + e.getMessage(), e); + } + } + + public void clearToken() { + try { + lock.lock(); + cachedToken = null; + tokenExpireTime = 0; + } finally { + lock.unlock(); + } + } +} diff --git a/src/main/java/com/nbport/zgwl/seal/utils/HMacSHA256Util.java b/src/main/java/com/nbport/zgwl/seal/utils/HMacSHA256Util.java new file mode 100644 index 0000000..dfb0a83 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/seal/utils/HMacSHA256Util.java @@ -0,0 +1,40 @@ +package com.nbport.zgwl.seal.utils; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; + +/** + * HMacSHA256签名算法工具类 + * + * @author xiaofengzhou + */ +public class HMacSHA256Util { + + public static String encrypt(String message, String appSecret) { + final String algorithm = "HmacSHA256"; + try { + Mac hmacSha256 = Mac.getInstance(algorithm); + byte[] keyBytes = appSecret.getBytes(StandardCharsets.UTF_8); + byte[] messageBytes = message.getBytes(StandardCharsets.UTF_8); + hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, algorithm)); + byte[] digestBytes = hmacSha256.doFinal(messageBytes); + return byteArrayToHexString(digestBytes); + } catch (Exception e) { + throw new RuntimeException("HMacSHA256签名计算失败: " + e.getMessage(), e); + } + } + + private static String byteArrayToHexString(byte[] bytes) { + StringBuilder hs = new StringBuilder(); + String stmp; + for (int n = 0; (bytes != null) && (n < bytes.length); n++) { + stmp = Integer.toHexString(bytes[n] & 0xFF); + if (stmp.length() == 1) { + hs.append('0'); + } + hs.append(stmp); + } + return hs.toString().toLowerCase(); + } +} diff --git a/src/main/java/com/nbport/zgwl/seal/utils/SeaHttpUtils.java b/src/main/java/com/nbport/zgwl/seal/utils/SeaHttpUtils.java new file mode 100644 index 0000000..4b6fd94 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/seal/utils/SeaHttpUtils.java @@ -0,0 +1,16 @@ +package com.nbport.zgwl.seal.utils; + +/** + * 签章平台基础HTTP工具 + * + * 保留单独工具类名称,便于和原项目调用习惯保持一致。 + */ +public class SeaHttpUtils { + + private SeaHttpUtils() { + } + + public static String sendPost(String url, String jsonBody, String contentType) { + return SealHttpClient.post(url, jsonBody, null, contentType); + } +} diff --git a/src/main/java/com/nbport/zgwl/seal/utils/SealHttpClient.java b/src/main/java/com/nbport/zgwl/seal/utils/SealHttpClient.java new file mode 100644 index 0000000..19876da --- /dev/null +++ b/src/main/java/com/nbport/zgwl/seal/utils/SealHttpClient.java @@ -0,0 +1,179 @@ +package com.nbport.zgwl.seal.utils; + +import cn.hutool.core.util.StrUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.multipart.MultipartFile; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; +import java.util.UUID; + +/** + * 签章平台HTTP客户端工具类 + * + * @author xiaofengzhou + */ +public class SealHttpClient { + + private static final Logger log = LoggerFactory.getLogger(SealHttpClient.class); + + private static final int CONNECT_TIMEOUT = 30000; + + private static final int READ_TIMEOUT = 60000; + + public static String post(String url, String jsonBody, String accessToken, String contentType) { + HttpURLConnection conn = null; + try { + conn = createConnection(url); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setRequestProperty("Content-Type", contentType); + conn.setRequestProperty("Accept", "application/json"); + if (StrUtil.isNotBlank(accessToken)) { + conn.setRequestProperty("access_token", accessToken); + } + conn.setRequestProperty("User-Agent", "Mozilla/5.0"); + + if (jsonBody != null && !jsonBody.isEmpty()) { + try (OutputStream os = conn.getOutputStream()) { + byte[] input = jsonBody.getBytes(StandardCharsets.UTF_8); + os.write(input, 0, input.length); + } + } + + return readResponse(conn); + } catch (Exception e) { + log.error("HTTP POST请求失败, URL: {}", url, e); + throw new RuntimeException("请求签章平台失败: " + e.getMessage(), e); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + public static String postMultipart(String url, MultipartFile file, String contractName, String description, String accessToken) { + HttpURLConnection conn = null; + String boundary = "----WebKitFormBoundary" + UUID.randomUUID().toString().replace("-", ""); + + try { + conn = createConnection(url); + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + conn.setDoInput(true); + conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); + if (StrUtil.isNotBlank(accessToken)) { + conn.setRequestProperty("access_token", accessToken); + } + conn.setRequestProperty("User-Agent", "Mozilla/5.0"); + + try (OutputStream os = conn.getOutputStream(); + PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8), true)) { + addFormField(writer, boundary, "contractName", contractName); + + if (description != null && !description.isEmpty()) { + addFormField(writer, boundary, "description", description); + } + + writer.append("--").append(boundary).append("\r\n"); + writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"") + .append(file.getOriginalFilename()).append("\"\r\n"); + writer.append("Content-Type: ").append(file.getContentType()).append("\r\n\r\n"); + writer.flush(); + os.write(file.getBytes()); + os.flush(); + writer.append("\r\n"); + + writer.append("--").append(boundary).append("--\r\n"); + writer.flush(); + } + + return readResponse(conn); + } catch (Exception e) { + log.error("文件上传请求失败, URL: {}", url, e); + throw new RuntimeException("文件上传失败: " + e.getMessage(), e); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + private static void addFormField(PrintWriter writer, String boundary, String name, String value) { + writer.append("--").append(boundary).append("\r\n"); + writer.append("Content-Disposition: form-data; name=\"") + .append(name).append("\"\r\n\r\n"); + writer.append(value).append("\r\n"); + writer.flush(); + } + + private static HttpURLConnection createConnection(String urlStr) throws Exception { + URL url = new URL(urlStr); + HttpURLConnection conn; + + if ("https".equalsIgnoreCase(url.getProtocol())) { + HttpsURLConnection httpsConn = (HttpsURLConnection) url.openConnection(); + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{new TrustAllManager()}, new java.security.SecureRandom()); + httpsConn.setSSLSocketFactory(sslContext.getSocketFactory()); + httpsConn.setHostnameVerifier((hostname, session) -> true); + conn = httpsConn; + } else { + conn = (HttpURLConnection) url.openConnection(); + } + + conn.setConnectTimeout(CONNECT_TIMEOUT); + conn.setReadTimeout(READ_TIMEOUT); + return conn; + } + + private static String readResponse(HttpURLConnection conn) throws IOException { + int responseCode = conn.getResponseCode(); + InputStream inputStream = responseCode >= 200 && responseCode < 300 ? conn.getInputStream() : conn.getErrorStream(); + + if (inputStream == null) { + return "{}"; + } + + StringBuilder response = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + response.append(line); + } + } + + log.debug("签章平台响应 [{}]: {}", responseCode, response); + return response.toString(); + } + + private static class TrustAllManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } +} diff --git a/src/main/java/com/nbport/zgwl/service/LocalFileService.java b/src/main/java/com/nbport/zgwl/service/LocalFileService.java new file mode 100644 index 0000000..f77c3f1 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/service/LocalFileService.java @@ -0,0 +1,81 @@ +package com.nbport.zgwl.service; + +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.util.IdUtil; +import com.nbport.zgwl.exception.ServiceErrorCodeRange; +import com.nbport.zgwl.exception.ServiceException; +import com.nbport.zgwl.utils.FileStorageUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Objects; + +@Service +@Slf4j +@RequiredArgsConstructor +public class LocalFileService { + + @Value("${file.local.path:${user.dir}/upload-files}") + private String localPath; + + public String upload(MultipartFile file) { + if (file == null || file.isEmpty()) { + throw new ServiceException("上传文件不能为空"); + } + try { + Path rootPath = Paths.get(localPath).toAbsolutePath().normalize(); + Files.createDirectories(rootPath); + + String dateFolder = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); + String suffix = FileStorageUtils.getFileSuffix(Objects.requireNonNullElse(file.getOriginalFilename(), "")); + String fileName = IdUtil.simpleUUID() + suffix; + String relativePath = dateFolder + "/" + fileName; + Path targetPath = FileStorageUtils.resolveFilePath(rootPath, relativePath); + Files.createDirectories(targetPath.getParent()); + file.transferTo(targetPath); + log.info("文件上传成功,targetPath={}", targetPath); + return relativePath; + } catch (IOException e) { + log.error("文件上传失败", e); + throw new ServiceException("文件上传失败"); + } + } + + public ResponseEntity download(String fileName) { + try { + Path rootPath = Paths.get(localPath).toAbsolutePath().normalize(); + Path filePath = FileStorageUtils.resolveFilePath(rootPath, fileName); + if (!Files.exists(filePath) || Files.isDirectory(filePath)) { + throw new ServiceException(ServiceErrorCodeRange.FILE_NOT_FOUND, "文件不存在"); + } + byte[] bytes = Files.readAllBytes(filePath); + String contentType = Files.probeContentType(filePath); + MediaType mediaType = contentType == null ? MediaType.APPLICATION_OCTET_STREAM : MediaType.parseMediaType(contentType); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(mediaType); + headers.setContentLength(bytes.length); + headers.setContentDisposition(ContentDisposition.inline().filename(filePath.getFileName().toString()).build()); + return ResponseEntity.ok() + .headers(headers) + .body(new ByteArrayResource(bytes)); + } catch (IOException e) { + log.error("文件下载失败", e); + throw new ServiceException("文件下载失败"); + } + } +} diff --git a/src/main/java/com/nbport/zgwl/service/StudentService.java b/src/main/java/com/nbport/zgwl/service/StudentService.java new file mode 100644 index 0000000..f5ca6e7 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/service/StudentService.java @@ -0,0 +1,79 @@ +package com.nbport.zgwl.service; + +import cn.hutool.core.bean.BeanUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.nbport.zgwl.common.PageResult; +import com.nbport.zgwl.dto.StudentQueryDto; +import com.nbport.zgwl.dto.StudentSaveDto; +import com.nbport.zgwl.entity.StudentEntity; +import com.nbport.zgwl.entity.SysUserEntity; +import com.nbport.zgwl.exception.ServiceErrorCodeRange; +import com.nbport.zgwl.exception.ServiceException; +import com.nbport.zgwl.mapper.StudentMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Map; + +@Service +@Slf4j +@RequiredArgsConstructor +public class StudentService { + + private final StudentMapper studentMapper; + + public PageResult> queryList(StudentQueryDto queryDto, SysUserEntity user) { + try { + log.info("学生分页查询,user={}, queryDto={}", user, queryDto); + Page> page = new Page<>(queryDto.getPageNo(), queryDto.getPageSize()); + return new PageResult<>(studentMapper.queryList(page, queryDto)); + } catch (Exception e) { + log.error("分页查询学生失败", e); + throw new ServiceException("分页查询失败"); + } + } + + public StudentEntity detail(Long id) { + StudentEntity studentEntity = studentMapper.selectById(id); + if (studentEntity == null) { + throw new ServiceException(ServiceErrorCodeRange.DATA_NOT_FOUND, "学生信息不存在"); + } + return studentEntity; + } + + @Transactional(rollbackFor = Exception.class) + public void add(StudentSaveDto dto) { + boolean exists = studentMapper.exists(new LambdaQueryWrapper() + .eq(StudentEntity::getStudentNo, dto.getStudentNo())); + if (exists) { + throw new ServiceException("学号已存在"); + } + StudentEntity entity = BeanUtil.copyProperties(dto, StudentEntity.class); + studentMapper.insert(entity); + } + + @Transactional(rollbackFor = Exception.class) + public void update(StudentSaveDto dto) { + if (dto.getId() == null) { + throw new ServiceException("id 不能为空"); + } + StudentEntity dbEntity = detail(dto.getId()); + boolean exists = studentMapper.exists(new LambdaQueryWrapper() + .eq(StudentEntity::getStudentNo, dto.getStudentNo()) + .ne(StudentEntity::getId, dto.getId())); + if (exists) { + throw new ServiceException("学号已存在"); + } + BeanUtil.copyProperties(dto, dbEntity); + studentMapper.updateById(dbEntity); + } + + @Transactional(rollbackFor = Exception.class) + public void delete(Long id) { + detail(id); + studentMapper.deleteById(id); + } +} diff --git a/src/main/java/com/nbport/zgwl/utils/FileStorageUtils.java b/src/main/java/com/nbport/zgwl/utils/FileStorageUtils.java new file mode 100644 index 0000000..7f85d95 --- /dev/null +++ b/src/main/java/com/nbport/zgwl/utils/FileStorageUtils.java @@ -0,0 +1,28 @@ +package com.nbport.zgwl.utils; + +import cn.hutool.core.util.StrUtil; +import com.nbport.zgwl.exception.ServiceException; + +import java.nio.file.Path; + +public class FileStorageUtils { + + private FileStorageUtils() { + } + + public static String getFileSuffix(String originalFilename) { + if (StrUtil.isBlank(originalFilename) || !originalFilename.contains(".")) { + return ""; + } + return originalFilename.substring(originalFilename.lastIndexOf(".")); + } + + public static Path resolveFilePath(Path rootPath, String relativePath) { + String normalizedRelativePath = relativePath.replace("\\", "/"); + Path targetPath = rootPath.resolve(normalizedRelativePath).normalize(); + if (!targetPath.startsWith(rootPath)) { + throw new ServiceException("非法文件路径"); + } + return targetPath; + } +} diff --git a/src/main/java/com/nbport/zgwl/utils/MockSecurityManager.java b/src/main/java/com/nbport/zgwl/utils/MockSecurityManager.java new file mode 100644 index 0000000..e5b836a --- /dev/null +++ b/src/main/java/com/nbport/zgwl/utils/MockSecurityManager.java @@ -0,0 +1,17 @@ +package com.nbport.zgwl.utils; + +import com.nbport.zgwl.entity.SysUserEntity; +import org.springframework.stereotype.Component; + +@Component +public class MockSecurityManager { + + public SysUserEntity getLoginUser() { + SysUserEntity user = new SysUserEntity(); + user.setId(1L); + user.setUsername("admin"); + user.setMobilePhone("13800000000"); + user.setEmail("admin@test.com"); + return user; + } +} diff --git a/src/main/resources/application-mysql.yml b/src/main/resources/application-mysql.yml new file mode 100644 index 0000000..10e79a1 --- /dev/null +++ b/src/main/resources/application-mysql.yml @@ -0,0 +1,6 @@ +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://127.0.0.1:3306/zgwl_end?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true + username: root + password: root diff --git a/src/main/resources/application-oracle.yml b/src/main/resources/application-oracle.yml new file mode 100644 index 0000000..a45f55d --- /dev/null +++ b/src/main/resources/application-oracle.yml @@ -0,0 +1,8 @@ +spring: + datasource: + driver-class-name: oracle.jdbc.OracleDriver + url: jdbc:oracle:thin:@//127.0.0.1:1521/XEPDB1 + username: developer + password: dev123 + hikari: + connection-test-query: SELECT 1 FROM DUAL diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..81ea3e1 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,64 @@ +server: + port: 8899 + +spring: + application: + name: zgwl-end + profiles: + active: ${SPRING_PROFILES_ACTIVE:oracle} + servlet: + multipart: + max-file-size: 20MB + max-request-size: 20MB + +mybatis-plus: + mapper-locations: classpath*:mapper/**/*.xml + type-aliases-package: com.nbport.zgwl.entity,com.nbport.zgwl.partner.domain,com.nbport.zgwl.manifest.domain + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + banner: false + +springdoc: + swagger-ui: + path: /swagger-ui.html + api-docs: + path: /v3/api-docs + +file: + local: + path: ${user.dir}/upload-files + +seal: + appKey: A7C4FD32BE454B5 + appSecret: 6c74358c28c149f18242bd614a101910 + baseUrl: https://saasapi.certca.cn + +contract-push: + settlement: + systemKey: stamp-settlement-test-key + secretKey: stamp-settlement-test-secret + tokenUrl: http://10.168.53.211:8888/grcv5/serverapi/restapi/other/v1/ecp/getToken + pushContractUrl: http://10.168.53.211:8888/grcv5/serverapi/restapi/other/v1/ecp/pushContract + approveQueryUrl: http://10.168.53.211:8888/grcv5/serverapi/restapi/other/v1/ecp/queryInfoByContractSysId + archiveUrl: http://10.168.53.211:8888/grcv5/serverapi/restapi/other/v1/ecp/collectContractArchive + approveViewBaseUrl: http://10.168.53.211:8888/wp/grcv5/contractView/G_1_viewContractApprove.htm + dataCollection: + systemKey: stamp-collection-test-key + secretKey: stamp-collection-test-secret + sysCode: stamp-end-demo + tokenUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/getToken + uploadUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/upload + collectOppositesUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectOpposites + collectZtbsUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectZtbs + collectContractUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectContract + collectPlanFeedbackUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectPlanFeedback + collectContractCloseUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectContractClose + collectContractArchiveUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/collectContractArchive + queryApproveInfoUrl: http://10.168.53.211:8888/grcv5/cj/rest/dataCenter/queryApproveInfo + approveViewBaseUrl: http://10.168.53.211:8888/grcv5/wp/contractView/G_1_viewContractApprove.htm + +logging: + level: + com.nbport.zgwl.mapper: debug diff --git a/src/main/resources/mapper/StudentMapper.xml b/src/main/resources/mapper/StudentMapper.xml new file mode 100644 index 0000000..dd0496a --- /dev/null +++ b/src/main/resources/mapper/StudentMapper.xml @@ -0,0 +1,59 @@ + + + + + + diff --git a/src/main/resources/mapper/manifest/ContractTemplateMapper.xml b/src/main/resources/mapper/manifest/ContractTemplateMapper.xml new file mode 100644 index 0000000..bfb749e --- /dev/null +++ b/src/main/resources/mapper/manifest/ContractTemplateMapper.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select id, template_code, template_name, source, contract_type, self_party_role, + version, status, variable_count, description, usage_tip, + template_file_name, template_file_path, + publish_org_ids_json, publish_org_names_json, variable_mappings_json, + create_by, created_time, updated_by, updated_time, del_flag + from contract_template_manage + + + + + + + + + + insert into contract_template_manage + ( + id, template_code, template_name, source, contract_type, self_party_role, + version, status, variable_count, description, usage_tip, + template_file_name, template_file_path, + publish_org_ids_json, publish_org_names_json, variable_mappings_json, + create_by, created_time, updated_by, updated_time, del_flag + ) + values + ( + #{id}, #{templateCode}, #{templateName}, #{source}, #{contractType}, #{selfPartyRole}, + #{version}, #{status}, #{variableCount}, #{description}, #{usageTip}, + #{templateFileName}, #{templateFilePath}, + #{publishOrgIdsJson}, #{publishOrgNamesJson}, #{variableMappingsJson}, + #{createBy}, #{createdTime}, #{updatedBy}, #{updatedTime}, #{delFlag} + ) + + + + update contract_template_manage + + template_name = #{templateName}, + source = #{source}, + contract_type = #{contractType}, + self_party_role = #{selfPartyRole}, + version = #{version}, + status = #{status}, + variable_count = #{variableCount}, + description = #{description}, + usage_tip = #{usageTip}, + template_file_name = #{templateFileName}, + template_file_path = #{templateFilePath}, + publish_org_ids_json = #{publishOrgIdsJson}, + publish_org_names_json = #{publishOrgNamesJson}, + variable_mappings_json = #{variableMappingsJson}, + updated_by = #{updatedBy}, + updated_time = #{updatedTime}, + + where id = #{id} and del_flag = '0' + + + + update contract_template_manage + set status = #{status}, + updated_by = #{updateBy}, + updated_time = #{updateTime} + where id = #{id} and del_flag = '0' + + + + update contract_template_manage + set del_flag = #{delFlag}, + updated_by = #{updateBy}, + updated_time = #{updateTime} + where id = #{id} and del_flag = '0' + + diff --git a/src/main/resources/mapper/partner/PartnerManageMapper.xml b/src/main/resources/mapper/partner/PartnerManageMapper.xml new file mode 100644 index 0000000..0807db2 --- /dev/null +++ b/src/main/resources/mapper/partner/PartnerManageMapper.xml @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select CUSTOMER_SYSID, CUSTOMER_NAME_C, CUSTOMER_TAXNO, CUSTOMER_REGADDR, + EMAIL_ADDRESS, OPPOSITE_ID, OPPOSITE_CHARACTER, GAT_FLAG, GROUP_FLAG, + COUNTRY, COUNTRY_NAME, LEGAL_PERSON_NAME, CERTIFICATE_TYPE, + CERTIFICATE_NUM, TIN_CODE, REGISTERED_CAPITAL, REG_CAPITAL, + OPPOSITE_CODE, OPPOSITE_STATUS, IS_SGAT, IS_JTN, + NATURE_OF_ENTERPRISE_ORG, NATURE_OF_ENTERPRISE_ORG_LEV, RELATIVE_TYPE, + IS_DELETED, COMMENTS, REMARK, CREATE_BY, CREATE_TIME, + UPDATE_BY, UPDATE_TIME, sync_status, sync_time, sync_message + from ILDM_FEE_CUSTOMER_INFO + + + + + + + + insert into ILDM_FEE_CUSTOMER_INFO + ( + CUSTOMER_SYSID, CUSTOMER_NAME_C, CUSTOMER_TAXNO, CUSTOMER_REGADDR, EMAIL_ADDRESS, + OPPOSITE_ID, OPPOSITE_CHARACTER, GAT_FLAG, GROUP_FLAG, COUNTRY, + COUNTRY_NAME, LEGAL_PERSON_NAME, CERTIFICATE_TYPE, CERTIFICATE_NUM, + TIN_CODE, REGISTERED_CAPITAL, REG_CAPITAL, OPPOSITE_CODE, + OPPOSITE_STATUS, IS_SGAT, IS_JTN, NATURE_OF_ENTERPRISE_ORG, + NATURE_OF_ENTERPRISE_ORG_LEV, RELATIVE_TYPE, IS_DELETED, COMMENTS, + REMARK, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME, + sync_status, sync_time, sync_message + ) + values + ( + #{customerSysid}, #{customerNameC}, #{customerTaxno}, #{customerRegaddr}, #{emailAddress}, + #{oppositeId}, #{oppositeCharacter}, #{gatFlag}, #{groupFlag}, #{country}, + #{countryName}, #{legalPersonName}, #{certificateType}, #{certificateNum}, + #{tinCode}, #{registeredCapital}, #{regCapital}, #{oppositeCode}, + #{oppositeStatus}, #{isSgat}, #{isJtn}, #{natureOfEnterpriseOrg}, + #{natureOfEnterpriseOrgLev}, #{relativeType}, #{isDeleted}, #{comments}, + #{remark}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, + #{syncStatus}, #{syncTime}, #{syncMessage} + ) + + + + update ILDM_FEE_CUSTOMER_INFO + + CUSTOMER_NAME_C = #{customerNameC}, + CUSTOMER_TAXNO = #{customerTaxno}, + CUSTOMER_REGADDR = #{customerRegaddr}, + EMAIL_ADDRESS = #{emailAddress}, + OPPOSITE_CHARACTER = #{oppositeCharacter}, + GAT_FLAG = #{gatFlag}, + GROUP_FLAG = #{groupFlag}, + COUNTRY = #{country}, + COUNTRY_NAME = #{countryName}, + LEGAL_PERSON_NAME = #{legalPersonName}, + CERTIFICATE_TYPE = #{certificateType}, + CERTIFICATE_NUM = #{certificateNum}, + TIN_CODE = #{tinCode}, + REGISTERED_CAPITAL = #{registeredCapital}, + REG_CAPITAL = #{regCapital}, + OPPOSITE_CODE = #{oppositeCode}, + OPPOSITE_STATUS = #{oppositeStatus}, + IS_SGAT = #{isSgat}, + IS_JTN = #{isJtn}, + NATURE_OF_ENTERPRISE_ORG = #{natureOfEnterpriseOrg}, + NATURE_OF_ENTERPRISE_ORG_LEV = #{natureOfEnterpriseOrgLev}, + RELATIVE_TYPE = #{relativeType}, + COMMENTS = #{comments}, + REMARK = #{remark}, + UPDATE_BY = #{updateBy}, + UPDATE_TIME = #{updateTime}, + sync_status = #{syncStatus}, + sync_time = #{syncTime}, + sync_message = #{syncMessage}, + + where CUSTOMER_SYSID = #{customerSysid} + and IS_DELETED = 'N' + + + + update ILDM_FEE_CUSTOMER_INFO + set IS_DELETED = #{isDeleted}, + UPDATE_BY = #{updateBy}, + UPDATE_TIME = #{updateTime} + where CUSTOMER_SYSID in + + #{customerSysid} + + + + + + + update SEAL_PARTNER_CONTACT + set del_flag = #{delFlag}, + update_by = #{updateBy}, + update_time = #{updateTime} + where partner_record_id = #{partnerRecordId} + and del_flag = '0' + + + + update SEAL_PARTNER_CONTACT + set del_flag = #{delFlag}, + update_by = #{updateBy}, + update_time = #{updateTime} + where partner_record_id in + + #{partnerRecordId} + + and del_flag = '0' + + + + INSERT ALL + + INTO SEAL_PARTNER_CONTACT + ( + id, partner_record_id, contact_name, contact_phone, postal_address, email, + sort_order, del_flag, create_by, create_time, update_by, update_time, remark + ) + VALUES + ( + #{item.id}, #{item.partnerRecordId}, #{item.contactName}, #{item.contactPhone}, + #{item.postalAddress}, #{item.email}, #{item.sortOrder}, + #{delFlag}, #{createBy}, #{createTime}, #{updateBy}, #{updateTime}, #{item.remark} + ) + + SELECT 1 FROM DUAL + + + + update ILDM_FEE_CUSTOMER_INFO + set OPPOSITE_ID = #{oppositeId}, + sync_status = #{syncStatus}, + sync_time = #{syncTime}, + sync_message = #{syncMessage}, + UPDATE_BY = #{updateBy}, + UPDATE_TIME = #{updateTime} + where CUSTOMER_SYSID = #{customerSysid} + and IS_DELETED = 'N' + + diff --git a/upload-files/20260522/2b9e71a7ef6b4c7d9da0ce936fde6b4f.docx b/upload-files/20260522/2b9e71a7ef6b4c7d9da0ce936fde6b4f.docx new file mode 100644 index 0000000..b7358a5 Binary files /dev/null and b/upload-files/20260522/2b9e71a7ef6b4c7d9da0ce936fde6b4f.docx differ diff --git a/upload-files/20260522/6a60ec1df60a44a792e27ed1d99ef7e0.docx b/upload-files/20260522/6a60ec1df60a44a792e27ed1d99ef7e0.docx new file mode 100644 index 0000000..b7358a5 Binary files /dev/null and b/upload-files/20260522/6a60ec1df60a44a792e27ed1d99ef7e0.docx differ diff --git a/upload-files/20260522/6f244579546f41b49345cc4a8621f222.png b/upload-files/20260522/6f244579546f41b49345cc4a8621f222.png new file mode 100644 index 0000000..1968008 Binary files /dev/null and b/upload-files/20260522/6f244579546f41b49345cc4a8621f222.png differ diff --git a/upload-files/20260522/c7fc19bb4699442e947155619638e444.docx b/upload-files/20260522/c7fc19bb4699442e947155619638e444.docx new file mode 100644 index 0000000..b7358a5 Binary files /dev/null and b/upload-files/20260522/c7fc19bb4699442e947155619638e444.docx differ diff --git a/upload-files/20260522/d956ff6687dd432199e1c68b81e5ecd3.docx b/upload-files/20260522/d956ff6687dd432199e1c68b81e5ecd3.docx new file mode 100644 index 0000000..b7358a5 Binary files /dev/null and b/upload-files/20260522/d956ff6687dd432199e1c68b81e5ecd3.docx differ diff --git a/zgwl_end.sql b/zgwl_end.sql new file mode 100644 index 0000000..32eec04 --- /dev/null +++ b/zgwl_end.sql @@ -0,0 +1,25 @@ +CREATE DATABASE IF NOT EXISTS zgwl_end DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; + +USE zgwl_end; + +DROP TABLE IF EXISTS BIZ_STUDENT; +CREATE TABLE BIZ_STUDENT ( + id BIGINT NOT NULL COMMENT '主键ID', + student_no VARCHAR(64) NOT NULL COMMENT '学号', + student_name VARCHAR(64) NOT NULL COMMENT '姓名', + gender VARCHAR(16) NOT NULL COMMENT '性别', + age INT NOT NULL COMMENT '年龄', + phone VARCHAR(32) DEFAULT NULL COMMENT '手机号', + address VARCHAR(255) DEFAULT NULL COMMENT '地址', + create_dt DATETIME DEFAULT NULL COMMENT '创建时间', + update_dt DATETIME DEFAULT NULL COMMENT '更新时间', + create_by VARCHAR(64) DEFAULT NULL COMMENT '创建人', + update_by VARCHAR(64) DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (id), + UNIQUE KEY uk_student_no (student_no) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='学生表'; + +INSERT INTO BIZ_STUDENT (id, student_no, student_name, gender, age, phone, address, create_dt, update_dt, create_by, update_by) VALUES +(1925800000000000001, 'S2024001', '张三', '男', 20, '13800001111', '宁波市鄞州区', NOW(), NOW(), 'system', 'system'), +(1925800000000000002, 'S2024002', '李四', '女', 21, '13800002222', '宁波市北仑区', NOW(), NOW(), 'system', 'system'), +(1925800000000000003, 'S2024003', '王五', '男', 22, '13800003333', '宁波市镇海区', NOW(), NOW(), 'system', 'system'); diff --git a/zgwl_end_oracle.sql b/zgwl_end_oracle.sql new file mode 100644 index 0000000..8c941b0 --- /dev/null +++ b/zgwl_end_oracle.sql @@ -0,0 +1,68 @@ +-- ============================================= +-- Oracle version for zgwl_end.sql +-- Execute with: sqlplus developer/dev123@localhost/XEPDB1 @zgwl_end_oracle.sql +-- ============================================= + +WHENEVER SQLERROR EXIT SQL.SQLCODE; + +BEGIN + EXECUTE IMMEDIATE 'DROP TABLE BIZ_STUDENT'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -942 THEN + RAISE; + END IF; +END; +/ + +BEGIN + EXECUTE IMMEDIATE 'DROP SEQUENCE SEQ_BIZ_STUDENT'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -2289 THEN + RAISE; + END IF; +END; +/ + +CREATE TABLE BIZ_STUDENT ( + ID NUMBER(19) NOT NULL, + STUDENT_NO VARCHAR2(64 CHAR) NOT NULL, + STUDENT_NAME VARCHAR2(64 CHAR) NOT NULL, + GENDER VARCHAR2(16 CHAR) NOT NULL, + AGE NUMBER(10) NOT NULL, + PHONE VARCHAR2(32 CHAR), + ADDRESS VARCHAR2(255 CHAR), + CREATE_DT TIMESTAMP(6), + UPDATE_DT TIMESTAMP(6), + CREATE_BY VARCHAR2(64 CHAR), + UPDATE_BY VARCHAR2(64 CHAR), + CONSTRAINT PK_BIZ_STUDENT PRIMARY KEY (ID), + CONSTRAINT UK_BIZ_STUDENT_NO UNIQUE (STUDENT_NO) +); + +COMMENT ON TABLE BIZ_STUDENT IS '学生表'; +COMMENT ON COLUMN BIZ_STUDENT.ID IS '主键ID'; +COMMENT ON COLUMN BIZ_STUDENT.STUDENT_NO IS '学号'; +COMMENT ON COLUMN BIZ_STUDENT.STUDENT_NAME IS '姓名'; +COMMENT ON COLUMN BIZ_STUDENT.GENDER IS '性别'; +COMMENT ON COLUMN BIZ_STUDENT.AGE IS '年龄'; +COMMENT ON COLUMN BIZ_STUDENT.PHONE IS '手机号'; +COMMENT ON COLUMN BIZ_STUDENT.ADDRESS IS '地址'; +COMMENT ON COLUMN BIZ_STUDENT.CREATE_DT IS '创建时间'; +COMMENT ON COLUMN BIZ_STUDENT.UPDATE_DT IS '更新时间'; +COMMENT ON COLUMN BIZ_STUDENT.CREATE_BY IS '创建人'; +COMMENT ON COLUMN BIZ_STUDENT.UPDATE_BY IS '更新人'; + +INSERT INTO BIZ_STUDENT (ID, STUDENT_NO, STUDENT_NAME, GENDER, AGE, PHONE, ADDRESS, CREATE_DT, UPDATE_DT, CREATE_BY, UPDATE_BY) VALUES +(1925800000000000001, 'S2024001', '张三', '男', 20, '13800001111', '宁波市鄞州区', SYSTIMESTAMP, SYSTIMESTAMP, 'system', 'system'); +INSERT INTO BIZ_STUDENT (ID, STUDENT_NO, STUDENT_NAME, GENDER, AGE, PHONE, ADDRESS, CREATE_DT, UPDATE_DT, CREATE_BY, UPDATE_BY) VALUES +(1925800000000000002, 'S2024002', '李四', '女', 21, '13800002222', '宁波市北仑区', SYSTIMESTAMP, SYSTIMESTAMP, 'system', 'system'); +INSERT INTO BIZ_STUDENT (ID, STUDENT_NO, STUDENT_NAME, GENDER, AGE, PHONE, ADDRESS, CREATE_DT, UPDATE_DT, CREATE_BY, UPDATE_BY) VALUES +(1925800000000000003, 'S2024003', '王五', '男', 22, '13800003333', '宁波市镇海区', SYSTIMESTAMP, SYSTIMESTAMP, 'system', 'system'); + +CREATE SEQUENCE SEQ_BIZ_STUDENT + START WITH 1925800000000000004 + INCREMENT BY 1 + NOCACHE + NOCYCLE; diff --git a/zgwl_manifest_tables.sql b/zgwl_manifest_tables.sql new file mode 100644 index 0000000..d375d60 --- /dev/null +++ b/zgwl_manifest_tables.sql @@ -0,0 +1,37 @@ +-- ============================================= +-- 合同模板管理模块数据表 +-- 执行方式:在 MySQL 中直接执行本文件 +-- ============================================= + +USE zgwl_end; + +DROP TABLE IF EXISTS `contract_template_manage`; + +CREATE TABLE `contract_template_manage` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `template_code` VARCHAR(64) NOT NULL COMMENT '范本编号,后端自动生成', + `template_name` VARCHAR(200) NOT NULL COMMENT '范本名称', + `source` VARCHAR(32) NOT NULL COMMENT '范本类型:标准模板/推荐文本/示范文本/对方文本', + `contract_type` VARCHAR(64) NOT NULL COMMENT '合同类型', + `self_party_role` VARCHAR(16) NOT NULL COMMENT '我方地位:甲方/乙方/丙方', + `version` VARCHAR(32) NOT NULL COMMENT '版本号', + `status` VARCHAR(20) NOT NULL DEFAULT 'draft' COMMENT '状态:draft/published/disabled', + `variable_count` INT NOT NULL DEFAULT 0 COMMENT '变量数量', + `description` VARCHAR(1000) DEFAULT NULL COMMENT '范本描述', + `usage_tip` VARCHAR(1000) DEFAULT NULL COMMENT '使用提示', + `template_file_name` VARCHAR(255) NOT NULL COMMENT '模板文件名称', + `template_file_path` VARCHAR(500) NOT NULL COMMENT '模板文件相对路径', + `publish_org_ids_json` TEXT DEFAULT NULL COMMENT '发布范围 orgId JSON 数组', + `publish_org_names_json` TEXT DEFAULT NULL COMMENT '发布范围 orgName JSON 数组', + `variable_mappings_json` LONGTEXT DEFAULT NULL COMMENT '变量映射 JSON 数组', + `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人', + `created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人', + `updated_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标记:0存在 2删除', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_template_code` (`template_code`), + KEY `idx_template_name` (`template_name`), + KEY `idx_contract_type` (`contract_type`), + KEY `idx_status` (`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='合同模板管理表'; diff --git a/zgwl_manifest_tables_oracle.sql b/zgwl_manifest_tables_oracle.sql new file mode 100644 index 0000000..cbdd54f --- /dev/null +++ b/zgwl_manifest_tables_oracle.sql @@ -0,0 +1,85 @@ +-- ============================================= +-- Oracle version for zgwl_manifest_tables.sql +-- Execute with: sqlplus developer/dev123@localhost/XEPDB1 @zgwl_manifest_tables_oracle.sql +-- ============================================= + +WHENEVER SQLERROR EXIT SQL.SQLCODE; + +BEGIN + EXECUTE IMMEDIATE 'DROP TABLE CONTRACT_TEMPLATE_MANAGE'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -942 THEN + RAISE; + END IF; +END; +/ + +BEGIN + EXECUTE IMMEDIATE 'DROP SEQUENCE SEQ_CONTRACT_TEMPLATE_MANAGE'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -2289 THEN + RAISE; + END IF; +END; +/ + +CREATE TABLE CONTRACT_TEMPLATE_MANAGE ( + ID NUMBER(19) NOT NULL, + TEMPLATE_CODE VARCHAR2(64 CHAR) NOT NULL, + TEMPLATE_NAME VARCHAR2(200 CHAR) NOT NULL, + SOURCE VARCHAR2(32 CHAR) NOT NULL, + CONTRACT_TYPE VARCHAR2(64 CHAR) NOT NULL, + SELF_PARTY_ROLE VARCHAR2(16 CHAR) NOT NULL, + VERSION VARCHAR2(32 CHAR) NOT NULL, + STATUS VARCHAR2(20 CHAR) DEFAULT 'draft' NOT NULL, + VARIABLE_COUNT NUMBER(10) DEFAULT 0 NOT NULL, + DESCRIPTION VARCHAR2(1000 CHAR), + USAGE_TIP VARCHAR2(1000 CHAR), + TEMPLATE_FILE_NAME VARCHAR2(255 CHAR) NOT NULL, + TEMPLATE_FILE_PATH VARCHAR2(500 CHAR) NOT NULL, + PUBLISH_ORG_IDS_JSON CLOB, + PUBLISH_ORG_NAMES_JSON CLOB, + VARIABLE_MAPPINGS_JSON CLOB, + CREATE_BY VARCHAR2(64 CHAR), + CREATED_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, + UPDATED_BY VARCHAR2(64 CHAR), + UPDATED_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, + DEL_FLAG CHAR(1 CHAR) DEFAULT '0' NOT NULL, + CONSTRAINT PK_CONTRACT_TEMPLATE_MANAGE PRIMARY KEY (ID), + CONSTRAINT UK_TEMPLATE_CODE UNIQUE (TEMPLATE_CODE) +); + +CREATE INDEX IDX_TEMPLATE_NAME ON CONTRACT_TEMPLATE_MANAGE (TEMPLATE_NAME); +CREATE INDEX IDX_CONTRACT_TYPE ON CONTRACT_TEMPLATE_MANAGE (CONTRACT_TYPE); +CREATE INDEX IDX_STATUS ON CONTRACT_TEMPLATE_MANAGE (STATUS); + +COMMENT ON TABLE CONTRACT_TEMPLATE_MANAGE IS '合同模板管理表'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.ID IS '主键ID'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.TEMPLATE_CODE IS '范本编号,后端自动生成'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.TEMPLATE_NAME IS '范本名称'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.SOURCE IS '范本类型:标准模板/推荐文本/示范文本/对方文本'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.CONTRACT_TYPE IS '合同类型'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.SELF_PARTY_ROLE IS '我方地位:甲方/乙方/丙方'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.VERSION IS '版本号'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.STATUS IS '状态:draft/published/disabled'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.VARIABLE_COUNT IS '变量数量'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.DESCRIPTION IS '范本描述'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.USAGE_TIP IS '使用提示'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.TEMPLATE_FILE_NAME IS '模板文件名称'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.TEMPLATE_FILE_PATH IS '模板文件相对路径'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.PUBLISH_ORG_IDS_JSON IS '发布范围 orgId JSON 数组'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.PUBLISH_ORG_NAMES_JSON IS '发布范围 orgName JSON 数组'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.VARIABLE_MAPPINGS_JSON IS '变量映射 JSON 数组'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.CREATE_BY IS '创建人'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.CREATED_TIME IS '创建时间'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.UPDATED_BY IS '更新人'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.UPDATED_TIME IS '更新时间'; +COMMENT ON COLUMN CONTRACT_TEMPLATE_MANAGE.DEL_FLAG IS '删除标记:0存在 2删除'; + +CREATE SEQUENCE SEQ_CONTRACT_TEMPLATE_MANAGE + START WITH 1 + INCREMENT BY 1 + NOCACHE + NOCYCLE; diff --git a/zgwl_partner_tables.sql b/zgwl_partner_tables.sql new file mode 100644 index 0000000..109b61f --- /dev/null +++ b/zgwl_partner_tables.sql @@ -0,0 +1,137 @@ +-- ============================================= +-- 相对方管理模块数据表 +-- 执行方式:在 MySQL 中直接执行本文件 +-- ============================================= + +USE zgwl_end; + +DROP TABLE IF EXISTS `seal_partner_contact`; +DROP TABLE IF EXISTS `ILDM_FEE_CUSTOMER_INFO`; + +CREATE TABLE `ILDM_FEE_CUSTOMER_INFO` ( + `CUSTOMER_SYSID` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '客户系统ID', + `CUSTOMER_REGNO` VARCHAR(64) DEFAULT NULL COMMENT '客户注册号', + `CUSTOMER_NO` VARCHAR(64) DEFAULT NULL COMMENT '客户编号', + `CUSTOMER_NAME_C` VARCHAR(200) DEFAULT NULL COMMENT '公司全称', + `CUSTOMER_NAME_EN` VARCHAR(200) DEFAULT NULL COMMENT '公司英文名称', + `CUSTOMER_SHORT_NAME` VARCHAR(100) DEFAULT NULL COMMENT '公司简称', + `CUSTOMER_SHORT_NAME_EN` VARCHAR(100) DEFAULT NULL COMMENT '公司英文简称', + `CUSTOMER_TAXNO` VARCHAR(50) DEFAULT NULL COMMENT '统一社会信用代码', + `CUSTOMER_ADDRESS` VARCHAR(200) DEFAULT NULL COMMENT '详细地址', + `OFFICEPHONE` VARCHAR(20) DEFAULT NULL COMMENT '联系电话', + `CUSTOMER_BANK` VARCHAR(100) DEFAULT NULL COMMENT '开户银行', + `CUSTOMER_BANKACCOUNT` VARCHAR(50) DEFAULT NULL COMMENT '银行账户', + `CUSTOMER_CHARACTER` VARCHAR(50) DEFAULT NULL COMMENT '客户性质', + `CUSTOMER_REGADDR` VARCHAR(200) DEFAULT NULL COMMENT '注册地址', + `CUSTOMER_REGDATE` DATE DEFAULT NULL COMMENT '注册日期', + `CUSTOMER_NATURE` VARCHAR(50) DEFAULT NULL COMMENT '客户性质分类', + `CUSTOMER_ZIP` VARCHAR(20) DEFAULT NULL COMMENT '邮编', + `CUSTOMER_1_TYPEFLAG` VARCHAR(10) DEFAULT NULL COMMENT '客户类型标识1', + `CUSTOMER_2_TYPEFLAG` VARCHAR(10) DEFAULT NULL COMMENT '客户类型标识2', + `CUSTOMER_3_TYPEFLAG` VARCHAR(10) DEFAULT NULL COMMENT '客户类型标识3', + `CUSTOMER_4_TYPEFLAG` VARCHAR(10) DEFAULT NULL COMMENT '客户类型标识4', + `PLACE_TYPE` VARCHAR(10) DEFAULT NULL COMMENT '境内外', + `ACTIVEFLAG` VARCHAR(10) DEFAULT NULL COMMENT '启用标识', + `CUSTOMER_SYSID_HT` VARCHAR(64) DEFAULT NULL COMMENT '海铁系统客户ID', + `DOWN_DATE` DATE DEFAULT NULL COMMENT '下载日期', + `OPEN_FLAG` VARCHAR(10) DEFAULT NULL COMMENT '开户标识', + `CREATE_BY` VARCHAR(64) DEFAULT NULL COMMENT '创建人', + `CREATE_TIME` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `UPDATE_BY` VARCHAR(64) DEFAULT NULL COMMENT '更新人', + `UPDATE_TIME` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `SYSTEM_TYPE` VARCHAR(20) DEFAULT NULL COMMENT '业务系统类型(ht:海铁联运系统;xg:箱管系统;qc:全程物流)', + `REMARK` VARCHAR(500) DEFAULT NULL COMMENT '备注', + `ORG_ID` VARCHAR(64) DEFAULT NULL COMMENT '组织体系对应的公司编码', + `EMAIL_ADDRESS` VARCHAR(100) DEFAULT NULL COMMENT '邮箱地址', + `OPPOSITE_ID` VARCHAR(64) DEFAULT NULL COMMENT '法务系统相对方ID(唯一标识)', + `OPPOSITE_CHARACTER` VARCHAR(10) DEFAULT NULL COMMENT '相对方性质分类(1:境内组织,2:境内个人,3:境外组织,4:境外个人)', + `GAT_FLAG` VARCHAR(2) DEFAULT NULL COMMENT '是否涉港澳台标识(Y:是,N:否,法务业务分类用)', + `GROUP_FLAG` VARCHAR(2) DEFAULT NULL COMMENT '是否集团内(Y:是,N:否,法务业务分类用)', + `COUNTRY` VARCHAR(20) DEFAULT NULL COMMENT '国家编码', + `COUNTRY_NAME` VARCHAR(50) DEFAULT NULL COMMENT '国家名称', + `CITY` VARCHAR(50) DEFAULT NULL COMMENT '城市', + `POSTCODE` VARCHAR(20) DEFAULT NULL COMMENT '邮编', + `LEGAL_PERSON_ID` VARCHAR(64) DEFAULT NULL COMMENT '法定代表人或负责人ID', + `LEGAL_PERSON_NAME` VARCHAR(50) DEFAULT NULL COMMENT '法定代表人或负责人姓名', + `LEGAL_PERSON_ACCOUNT` VARCHAR(64) DEFAULT NULL COMMENT '法定代表人或负责人账户', + `LEGAL_PERSON_CARD_ID` VARCHAR(100) DEFAULT NULL COMMENT '法定代表人身份证号(加密存储)', + `WEBSITE` VARCHAR(200) DEFAULT NULL COMMENT '网站地址', + `CERTIFICATE_TYPE` VARCHAR(20) DEFAULT NULL COMMENT '自然人证件类型(idCard:身份证,passport:护照,other:其他)', + `CERTIFICATE_NUM` VARCHAR(100) DEFAULT NULL COMMENT '自然人证件号码(加密存储)', + `TIN_CODE` VARCHAR(50) DEFAULT NULL COMMENT 'TIN码(税务识别号)', + `ABROAD_CODE` VARCHAR(50) DEFAULT NULL COMMENT '境外公司活动证明编码', + `REGISTERED_CAPITAL` DECIMAL(20,2) DEFAULT NULL COMMENT '注册资金(单位:元)', + `REG_CAPITAL` VARCHAR(100) DEFAULT NULL COMMENT '注册资本(文本描述,如:1000万人民币)', + `BUS_SCOPE` TEXT COMMENT '经营范围', + `OPPOSITE_CODE` VARCHAR(64) DEFAULT NULL COMMENT '相对方编码(业务分类编码)', + `OPPOSITE_STATUS` VARCHAR(20) DEFAULT 'EFFECTUAL' COMMENT '状态(EFFECTUAL:有效,INVALID:无效)', + `IS_SGAT` VARCHAR(2) DEFAULT '0' COMMENT '是否SGAT(1:是,0:否)', + `IS_JTN` VARCHAR(2) DEFAULT '0' COMMENT '是否JTN(1:是,0:否)', + `NATURE_OF_ENTERPRISE_ORG` VARCHAR(10) DEFAULT NULL COMMENT '组织性质(ZY:政府,QY:企业,QT:其他组织)', + `NATURE_OF_ENTERPRISE_ORG_LEV` VARCHAR(20) DEFAULT NULL COMMENT '组织性质子级(GNQY:国有企业,MYQY:民营企业,WZQY:外资企业,HZQY:合资企业,GYQZHKGQY:国有全资和控股企业,QT:其他)', + `RELATIVE_TYPE` VARCHAR(30) DEFAULT NULL COMMENT '类型(client:客户,clientAndSup:客户及供应商,other:其他,Partner:合作伙伴,supplier:供应商)', + `IS_DELETED` VARCHAR(2) DEFAULT 'N' COMMENT '法务系统删除标记(Y:是,N:否,逻辑删除)', + `COMMENTS` VARCHAR(500) DEFAULT NULL COMMENT '备注【法务】', + `sync_status` VARCHAR(20) NOT NULL DEFAULT 'unsynced' COMMENT '同步状态:unsynced/synced/failed', + `sync_time` DATETIME DEFAULT NULL COMMENT '最后同步时间', + `sync_message` VARCHAR(500) DEFAULT NULL COMMENT '同步说明', + PRIMARY KEY (`CUSTOMER_SYSID`), + KEY `idx_customer_no` (`CUSTOMER_NO`), + KEY `idx_opposite_id` (`OPPOSITE_ID`), + KEY `idx_customer_taxno` (`CUSTOMER_TAXNO`), + KEY `idx_system_type` (`SYSTEM_TYPE`), + KEY `idx_create_time` (`CREATE_TIME`), + KEY `idx_sync_status` (`sync_status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户/相对方信息表'; + +CREATE TABLE `seal_partner_contact` ( + `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `partner_record_id` BIGINT(20) NOT NULL COMMENT '关联相对方主表 CUSTOMER_SYSID', + `contact_name` VARCHAR(50) NOT NULL COMMENT '联系人姓名', + `contact_phone` VARCHAR(20) NOT NULL COMMENT '联系电话', + `postal_address` VARCHAR(100) NOT NULL COMMENT '通讯地址', + `email` VARCHAR(50) NOT NULL COMMENT '电子邮箱', + `sort_order` INT(11) NOT NULL DEFAULT 1 COMMENT '排序号', + `del_flag` CHAR(1) NOT NULL DEFAULT '0' COMMENT '删除标记:0存在 2删除', + `create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建人', + `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新人', + `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注', + PRIMARY KEY (`id`), + KEY `idx_partner_record_id` (`partner_record_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='相对方联系人表'; + +INSERT INTO `ILDM_FEE_CUSTOMER_INFO` +(`CUSTOMER_SYSID`, `CUSTOMER_NAME_C`, `CUSTOMER_TAXNO`, `CUSTOMER_REGADDR`, `CREATE_BY`, `CREATE_TIME`, `UPDATE_BY`, `UPDATE_TIME`, + `EMAIL_ADDRESS`, `OPPOSITE_ID`, `OPPOSITE_CHARACTER`, `GAT_FLAG`, `GROUP_FLAG`, `COUNTRY`, `COUNTRY_NAME`, + `LEGAL_PERSON_NAME`, `REG_CAPITAL`, `OPPOSITE_CODE`, `OPPOSITE_STATUS`, `IS_SGAT`, `IS_JTN`, + `NATURE_OF_ENTERPRISE_ORG`, `NATURE_OF_ENTERPRISE_ORG_LEV`, `RELATIVE_TYPE`, `IS_DELETED`, + `COMMENTS`, `sync_status`, `sync_time`, `sync_message`) +VALUES +(1, '宁波港船务货运代理有限公司', '91330201MA2ABCDE9X', '宁波市北仑区明州西路188号', 'local_test', '2026-05-20 09:12:33', 'local_test', '2026-05-20 09:15:10', + 'zhoujingli@example.com', 'JT-10256', '1', 'N', 'N', NULL, NULL, + '李四', '5000万元', 'OPP-202605210001', 'EFFECTUAL', '0', '0', + 'QY', 'GNQY', 'supplier', 'N', + '本地初始化测试数据', 'synced', '2026-05-20 09:15:10', '已按数据采集接口规范同步到法务系统'), +(2, '王小明', NULL, NULL, 'local_test', '2026-05-20 14:20:05', 'local_test', '2026-05-20 14:20:05', + NULL, NULL, '2', 'N', 'N', NULL, NULL, + NULL, NULL, 'OPP-202605210002', 'EFFECTUAL', '0', '0', + 'QT', NULL, 'client', 'N', + '个人类型测试数据', 'unsynced', NULL, '新增后尚未同步'), +(3, 'Ocean Energy Pte. Ltd.', NULL, '88 Harbour Street, London', 'local_test', '2026-05-18 11:08:22', 'local_test', '2026-05-18 11:10:40', + 'alice.brown@ocean-energy.com', 'JT-10888', '3', 'N', 'N', 'GJ826', '英国', + NULL, 'USD 2,000,000', 'OPP-202605210003', 'EFFECTUAL', '0', '0', + 'QY', 'WZQY', 'Partner', 'N', + '境外组织测试数据', 'failed', '2026-05-18 11:10:40', '法务系统返回:注册地址长度超限,请修正后重试'); + +INSERT INTO `seal_partner_contact` +(`id`, `partner_record_id`, `contact_name`, `contact_phone`, `postal_address`, `email`, + `sort_order`, `del_flag`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) +VALUES +(1, 1, '周经理', '15757460062', '宁波市北仑区明州西路188号A座', 'zhoujingli@example.com', + 1, '0', 'local_test', '2026-05-20 09:12:33', 'local_test', '2026-05-20 09:12:33', '主联系人'), +(2, 3, 'Alice Brown', '+44-20-8899-0001', '88 Harbour Street, London', 'alice.brown@ocean-energy.com', + 1, '0', 'local_test', '2026-05-18 11:08:22', 'local_test', '2026-05-18 11:08:22', '海外主联系人'), +(3, 3, 'Tom Lee', '+44-20-8899-0002', '88 Harbour Street, London', 'tom.lee@ocean-energy.com', + 2, '0', 'local_test', '2026-05-18 11:08:22', 'local_test', '2026-05-18 11:08:22', '备用联系人'); diff --git a/zgwl_partner_tables_oracle.sql b/zgwl_partner_tables_oracle.sql new file mode 100644 index 0000000..1eeac13 --- /dev/null +++ b/zgwl_partner_tables_oracle.sql @@ -0,0 +1,264 @@ +-- ============================================= +-- Oracle version for zgwl_partner_tables.sql +-- Execute with: sqlplus developer/dev123@localhost/XEPDB1 @zgwl_partner_tables_oracle.sql +-- ============================================= + +WHENEVER SQLERROR EXIT SQL.SQLCODE; + +BEGIN + EXECUTE IMMEDIATE 'DROP TABLE SEAL_PARTNER_CONTACT'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -942 THEN + RAISE; + END IF; +END; +/ + +BEGIN + EXECUTE IMMEDIATE 'DROP TABLE ILDM_FEE_CUSTOMER_INFO'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -942 THEN + RAISE; + END IF; +END; +/ + +BEGIN + EXECUTE IMMEDIATE 'DROP SEQUENCE SEQ_SEAL_PARTNER_CONTACT'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -2289 THEN + RAISE; + END IF; +END; +/ + +BEGIN + EXECUTE IMMEDIATE 'DROP SEQUENCE SEQ_ILDM_FEE_CUSTOMER_INFO'; +EXCEPTION + WHEN OTHERS THEN + IF SQLCODE != -2289 THEN + RAISE; + END IF; +END; +/ + +CREATE TABLE ILDM_FEE_CUSTOMER_INFO ( + CUSTOMER_SYSID NUMBER(19) NOT NULL, + CUSTOMER_REGNO VARCHAR2(64 CHAR), + CUSTOMER_NO VARCHAR2(64 CHAR), + CUSTOMER_NAME_C VARCHAR2(200 CHAR), + CUSTOMER_NAME_EN VARCHAR2(200 CHAR), + CUSTOMER_SHORT_NAME VARCHAR2(100 CHAR), + CUSTOMER_SHORT_NAME_EN VARCHAR2(100 CHAR), + CUSTOMER_TAXNO VARCHAR2(50 CHAR), + CUSTOMER_ADDRESS VARCHAR2(200 CHAR), + OFFICEPHONE VARCHAR2(20 CHAR), + CUSTOMER_BANK VARCHAR2(100 CHAR), + CUSTOMER_BANKACCOUNT VARCHAR2(50 CHAR), + CUSTOMER_CHARACTER VARCHAR2(50 CHAR), + CUSTOMER_REGADDR VARCHAR2(200 CHAR), + CUSTOMER_REGDATE DATE, + CUSTOMER_NATURE VARCHAR2(50 CHAR), + CUSTOMER_ZIP VARCHAR2(20 CHAR), + CUSTOMER_1_TYPEFLAG VARCHAR2(10 CHAR), + CUSTOMER_2_TYPEFLAG VARCHAR2(10 CHAR), + CUSTOMER_3_TYPEFLAG VARCHAR2(10 CHAR), + CUSTOMER_4_TYPEFLAG VARCHAR2(10 CHAR), + PLACE_TYPE VARCHAR2(10 CHAR), + ACTIVEFLAG VARCHAR2(10 CHAR), + CUSTOMER_SYSID_HT VARCHAR2(64 CHAR), + DOWN_DATE DATE, + OPEN_FLAG VARCHAR2(10 CHAR), + CREATE_BY VARCHAR2(64 CHAR), + CREATE_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, + UPDATE_BY VARCHAR2(64 CHAR), + UPDATE_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, + SYSTEM_TYPE VARCHAR2(20 CHAR), + REMARK VARCHAR2(500 CHAR), + ORG_ID VARCHAR2(64 CHAR), + EMAIL_ADDRESS VARCHAR2(100 CHAR), + OPPOSITE_ID VARCHAR2(64 CHAR), + OPPOSITE_CHARACTER VARCHAR2(10 CHAR), + GAT_FLAG VARCHAR2(2 CHAR), + GROUP_FLAG VARCHAR2(2 CHAR), + COUNTRY VARCHAR2(20 CHAR), + COUNTRY_NAME VARCHAR2(50 CHAR), + CITY VARCHAR2(50 CHAR), + POSTCODE VARCHAR2(20 CHAR), + LEGAL_PERSON_ID VARCHAR2(64 CHAR), + LEGAL_PERSON_NAME VARCHAR2(50 CHAR), + LEGAL_PERSON_ACCOUNT VARCHAR2(64 CHAR), + LEGAL_PERSON_CARD_ID VARCHAR2(100 CHAR), + WEBSITE VARCHAR2(200 CHAR), + CERTIFICATE_TYPE VARCHAR2(20 CHAR), + CERTIFICATE_NUM VARCHAR2(100 CHAR), + TIN_CODE VARCHAR2(50 CHAR), + ABROAD_CODE VARCHAR2(50 CHAR), + REGISTERED_CAPITAL NUMBER(20, 2), + REG_CAPITAL VARCHAR2(100 CHAR), + BUS_SCOPE CLOB, + OPPOSITE_CODE VARCHAR2(64 CHAR), + OPPOSITE_STATUS VARCHAR2(20 CHAR) DEFAULT 'EFFECTUAL', + IS_SGAT VARCHAR2(2 CHAR) DEFAULT '0', + IS_JTN VARCHAR2(2 CHAR) DEFAULT '0', + NATURE_OF_ENTERPRISE_ORG VARCHAR2(10 CHAR), + NATURE_OF_ENTERPRISE_ORG_LEV VARCHAR2(20 CHAR), + RELATIVE_TYPE VARCHAR2(30 CHAR), + IS_DELETED VARCHAR2(2 CHAR) DEFAULT 'N', + COMMENTS VARCHAR2(500 CHAR), + SYNC_STATUS VARCHAR2(20 CHAR) DEFAULT 'unsynced' NOT NULL, + SYNC_TIME TIMESTAMP(6), + SYNC_MESSAGE VARCHAR2(500 CHAR), + CONSTRAINT PK_ILDM_FEE_CUSTOMER_INFO PRIMARY KEY (CUSTOMER_SYSID) +); + +CREATE INDEX IDX_CUSTOMER_NO ON ILDM_FEE_CUSTOMER_INFO (CUSTOMER_NO); +CREATE INDEX IDX_OPPOSITE_ID ON ILDM_FEE_CUSTOMER_INFO (OPPOSITE_ID); +CREATE INDEX IDX_CUSTOMER_TAXNO ON ILDM_FEE_CUSTOMER_INFO (CUSTOMER_TAXNO); +CREATE INDEX IDX_SYSTEM_TYPE ON ILDM_FEE_CUSTOMER_INFO (SYSTEM_TYPE); +CREATE INDEX IDX_CREATE_TIME ON ILDM_FEE_CUSTOMER_INFO (CREATE_TIME); +CREATE INDEX IDX_SYNC_STATUS ON ILDM_FEE_CUSTOMER_INFO (SYNC_STATUS); + +COMMENT ON TABLE ILDM_FEE_CUSTOMER_INFO IS '客户/相对方信息表'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CUSTOMER_SYSID IS '客户系统ID'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CUSTOMER_NAME_C IS '公司全称'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CUSTOMER_TAXNO IS '统一社会信用代码'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CUSTOMER_REGADDR IS '注册地址'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.EMAIL_ADDRESS IS '邮箱地址'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.OPPOSITE_ID IS '法务系统相对方ID(唯一标识)'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.OPPOSITE_CHARACTER IS '相对方性质分类'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.GAT_FLAG IS '是否涉港澳台标识'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.GROUP_FLAG IS '是否集团内'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.COUNTRY IS '国家编码'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.COUNTRY_NAME IS '国家名称'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.LEGAL_PERSON_NAME IS '法定代表人或负责人姓名'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CERTIFICATE_TYPE IS '自然人证件类型'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.CERTIFICATE_NUM IS '自然人证件号码'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.TIN_CODE IS 'TIN码(税务识别号)'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.REGISTERED_CAPITAL IS '注册资金(单位:元)'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.REG_CAPITAL IS '注册资本'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.OPPOSITE_CODE IS '相对方编码'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.OPPOSITE_STATUS IS '状态(EFFECTUAL:有效,INVALID:无效)'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.IS_SGAT IS '是否SGAT(1:是,0:否)'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.IS_JTN IS '是否JTN(1:是,0:否)'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.NATURE_OF_ENTERPRISE_ORG IS '组织性质'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.NATURE_OF_ENTERPRISE_ORG_LEV IS '组织性质子级'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.RELATIVE_TYPE IS '类型'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.IS_DELETED IS '法务系统删除标记'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.COMMENTS IS '备注【法务】'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.SYNC_STATUS IS '同步状态'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.SYNC_TIME IS '最后同步时间'; +COMMENT ON COLUMN ILDM_FEE_CUSTOMER_INFO.SYNC_MESSAGE IS '同步说明'; + +CREATE TABLE SEAL_PARTNER_CONTACT ( + ID NUMBER(19) NOT NULL, + PARTNER_RECORD_ID NUMBER(19) NOT NULL, + CONTACT_NAME VARCHAR2(50 CHAR) NOT NULL, + CONTACT_PHONE VARCHAR2(20 CHAR) NOT NULL, + POSTAL_ADDRESS VARCHAR2(100 CHAR) NOT NULL, + EMAIL VARCHAR2(50 CHAR) NOT NULL, + SORT_ORDER NUMBER(10) DEFAULT 1 NOT NULL, + DEL_FLAG CHAR(1 CHAR) DEFAULT '0' NOT NULL, + CREATE_BY VARCHAR2(64 CHAR), + CREATE_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, + UPDATE_BY VARCHAR2(64 CHAR), + UPDATE_TIME TIMESTAMP(6) DEFAULT CURRENT_TIMESTAMP, + REMARK VARCHAR2(500 CHAR), + CONSTRAINT PK_SEAL_PARTNER_CONTACT PRIMARY KEY (ID) +); + +CREATE INDEX IDX_PARTNER_RECORD_ID ON SEAL_PARTNER_CONTACT (PARTNER_RECORD_ID); + +COMMENT ON TABLE SEAL_PARTNER_CONTACT IS '相对方联系人表'; +COMMENT ON COLUMN SEAL_PARTNER_CONTACT.ID IS '主键ID'; +COMMENT ON COLUMN SEAL_PARTNER_CONTACT.PARTNER_RECORD_ID IS '关联相对方主表 CUSTOMER_SYSID'; +COMMENT ON COLUMN SEAL_PARTNER_CONTACT.CONTACT_NAME IS '联系人姓名'; +COMMENT ON COLUMN SEAL_PARTNER_CONTACT.CONTACT_PHONE IS '联系电话'; +COMMENT ON COLUMN SEAL_PARTNER_CONTACT.POSTAL_ADDRESS IS '通讯地址'; +COMMENT ON COLUMN SEAL_PARTNER_CONTACT.EMAIL IS '电子邮箱'; +COMMENT ON COLUMN SEAL_PARTNER_CONTACT.SORT_ORDER IS '排序号'; +COMMENT ON COLUMN SEAL_PARTNER_CONTACT.DEL_FLAG IS '删除标记:0存在 2删除'; + +INSERT INTO ILDM_FEE_CUSTOMER_INFO +(CUSTOMER_SYSID, CUSTOMER_NAME_C, CUSTOMER_TAXNO, CUSTOMER_REGADDR, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME, + EMAIL_ADDRESS, OPPOSITE_ID, OPPOSITE_CHARACTER, GAT_FLAG, GROUP_FLAG, COUNTRY, COUNTRY_NAME, + LEGAL_PERSON_NAME, REG_CAPITAL, OPPOSITE_CODE, OPPOSITE_STATUS, IS_SGAT, IS_JTN, + NATURE_OF_ENTERPRISE_ORG, NATURE_OF_ENTERPRISE_ORG_LEV, RELATIVE_TYPE, IS_DELETED, + COMMENTS, SYNC_STATUS, SYNC_TIME, SYNC_MESSAGE) +VALUES +(1, '宁波港船务货运代理有限公司', '91330201MA2ABCDE9X', '宁波市北仑区明州西路188号', 'local_test', + TO_TIMESTAMP('2026-05-20 09:12:33', 'YYYY-MM-DD HH24:MI:SS'), + 'local_test', TO_TIMESTAMP('2026-05-20 09:15:10', 'YYYY-MM-DD HH24:MI:SS'), + 'zhoujingli@example.com', 'JT-10256', '1', 'N', 'N', NULL, NULL, + '李四', '5000万元', 'OPP-202605210001', 'EFFECTUAL', '0', '0', + 'QY', 'GNQY', 'supplier', 'N', + '本地初始化测试数据', 'synced', TO_TIMESTAMP('2026-05-20 09:15:10', 'YYYY-MM-DD HH24:MI:SS'), '已按数据采集接口规范同步到法务系统'); + +INSERT INTO ILDM_FEE_CUSTOMER_INFO +(CUSTOMER_SYSID, CUSTOMER_NAME_C, CUSTOMER_TAXNO, CUSTOMER_REGADDR, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME, + EMAIL_ADDRESS, OPPOSITE_ID, OPPOSITE_CHARACTER, GAT_FLAG, GROUP_FLAG, COUNTRY, COUNTRY_NAME, + LEGAL_PERSON_NAME, REG_CAPITAL, OPPOSITE_CODE, OPPOSITE_STATUS, IS_SGAT, IS_JTN, + NATURE_OF_ENTERPRISE_ORG, NATURE_OF_ENTERPRISE_ORG_LEV, RELATIVE_TYPE, IS_DELETED, + COMMENTS, SYNC_STATUS, SYNC_TIME, SYNC_MESSAGE) +VALUES +(2, '王小明', NULL, NULL, 'local_test', + TO_TIMESTAMP('2026-05-20 14:20:05', 'YYYY-MM-DD HH24:MI:SS'), + 'local_test', TO_TIMESTAMP('2026-05-20 14:20:05', 'YYYY-MM-DD HH24:MI:SS'), + NULL, NULL, '2', 'N', 'N', NULL, NULL, + NULL, NULL, 'OPP-202605210002', 'EFFECTUAL', '0', '0', + 'QT', NULL, 'client', 'N', + '个人类型测试数据', 'unsynced', NULL, '新增后尚未同步'); + +INSERT INTO ILDM_FEE_CUSTOMER_INFO +(CUSTOMER_SYSID, CUSTOMER_NAME_C, CUSTOMER_TAXNO, CUSTOMER_REGADDR, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME, + EMAIL_ADDRESS, OPPOSITE_ID, OPPOSITE_CHARACTER, GAT_FLAG, GROUP_FLAG, COUNTRY, COUNTRY_NAME, + LEGAL_PERSON_NAME, REG_CAPITAL, OPPOSITE_CODE, OPPOSITE_STATUS, IS_SGAT, IS_JTN, + NATURE_OF_ENTERPRISE_ORG, NATURE_OF_ENTERPRISE_ORG_LEV, RELATIVE_TYPE, IS_DELETED, + COMMENTS, SYNC_STATUS, SYNC_TIME, SYNC_MESSAGE) +VALUES +(3, 'Ocean Energy Pte. Ltd.', NULL, '88 Harbour Street, London', 'local_test', + TO_TIMESTAMP('2026-05-18 11:08:22', 'YYYY-MM-DD HH24:MI:SS'), + 'local_test', TO_TIMESTAMP('2026-05-18 11:10:40', 'YYYY-MM-DD HH24:MI:SS'), + 'alice.brown@ocean-energy.com', 'JT-10888', '3', 'N', 'N', 'GJ826', '英国', + NULL, 'USD 2,000,000', 'OPP-202605210003', 'EFFECTUAL', '0', '0', + 'QY', 'WZQY', 'Partner', 'N', + '境外组织测试数据', 'failed', TO_TIMESTAMP('2026-05-18 11:10:40', 'YYYY-MM-DD HH24:MI:SS'), '法务系统返回:注册地址长度超限,请修正后重试'); + +INSERT INTO SEAL_PARTNER_CONTACT +(ID, PARTNER_RECORD_ID, CONTACT_NAME, CONTACT_PHONE, POSTAL_ADDRESS, EMAIL, + SORT_ORDER, DEL_FLAG, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME, REMARK) +VALUES +(1, 1, '周经理', '15757460062', '宁波市北仑区明州西路188号A座', 'zhoujingli@example.com', + 1, '0', 'local_test', TO_TIMESTAMP('2026-05-20 09:12:33', 'YYYY-MM-DD HH24:MI:SS'), + 'local_test', TO_TIMESTAMP('2026-05-20 09:12:33', 'YYYY-MM-DD HH24:MI:SS'), '主联系人'); + +INSERT INTO SEAL_PARTNER_CONTACT +(ID, PARTNER_RECORD_ID, CONTACT_NAME, CONTACT_PHONE, POSTAL_ADDRESS, EMAIL, + SORT_ORDER, DEL_FLAG, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME, REMARK) +VALUES +(2, 3, 'Alice Brown', '+44-20-8899-0001', '88 Harbour Street, London', 'alice.brown@ocean-energy.com', + 1, '0', 'local_test', TO_TIMESTAMP('2026-05-18 11:08:22', 'YYYY-MM-DD HH24:MI:SS'), + 'local_test', TO_TIMESTAMP('2026-05-18 11:08:22', 'YYYY-MM-DD HH24:MI:SS'), '海外主联系人'); + +INSERT INTO SEAL_PARTNER_CONTACT +(ID, PARTNER_RECORD_ID, CONTACT_NAME, CONTACT_PHONE, POSTAL_ADDRESS, EMAIL, + SORT_ORDER, DEL_FLAG, CREATE_BY, CREATE_TIME, UPDATE_BY, UPDATE_TIME, REMARK) +VALUES +(3, 3, 'Tom Lee', '+44-20-8899-0002', '88 Harbour Street, London', 'tom.lee@ocean-energy.com', + 2, '0', 'local_test', TO_TIMESTAMP('2026-05-18 11:08:22', 'YYYY-MM-DD HH24:MI:SS'), + 'local_test', TO_TIMESTAMP('2026-05-18 11:08:22', 'YYYY-MM-DD HH24:MI:SS'), '备用联系人'); + +CREATE SEQUENCE SEQ_ILDM_FEE_CUSTOMER_INFO + START WITH 4 + INCREMENT BY 1 + NOCACHE + NOCYCLE; + +CREATE SEQUENCE SEQ_SEAL_PARTNER_CONTACT + START WITH 4 + INCREMENT BY 1 + NOCACHE + NOCYCLE;