From 2eceb44a7f55cc8135181e6b91ea903f85f37585 Mon Sep 17 00:00:00 2001 From: ouhaolan Date: Mon, 2 Mar 2026 09:23:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-car/yudao-module-car-biz/pom.xml | 6 + .../renewalorder/RenewalOrderController.java | 21 +++ .../vo/RenewalOrderSaveReqVO.java | 3 +- .../vo/RenewalProductPageReqVO.java | 3 + .../vo/RenewalProductRespVO.java | 5 + .../vo/RenewalProductSaveReqVO.java | 3 + .../admin/sign/OnlineSignController.java | 158 ++++++++++++++++++ .../renewalproduct/RenewalProductDO.java | 4 + .../car/dal/redis/OnlineSignRedisDAO.java | 54 ++++++ .../car/dal/redis/RedisKeyConstants.java | 18 ++ .../service/contract/ContractServiceImpl.java | 54 +++++- .../renewalorder/RenewalOrderService.java | 23 +++ .../renewalorder/RenewalOrderServiceImpl.java | 59 +++++++ .../templates/contract/renewal-contract.html | 21 ++- .../templates/sign/sign-expired.html | 15 ++ .../resources/templates/sign/sign-page.html | 151 +++++++++++++++++ .../templates/sign/sign-success.html | 32 ++++ .../src/main/resources/application-local.yaml | 16 +- .../src/main/resources/application.yaml | 2 + 19 files changed, 622 insertions(+), 26 deletions(-) create mode 100644 yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/sign/OnlineSignController.java create mode 100644 yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/redis/OnlineSignRedisDAO.java create mode 100644 yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/redis/RedisKeyConstants.java create mode 100644 yudao-module-car/yudao-module-car-biz/src/main/resources/templates/sign/sign-expired.html create mode 100644 yudao-module-car/yudao-module-car-biz/src/main/resources/templates/sign/sign-page.html create mode 100644 yudao-module-car/yudao-module-car-biz/src/main/resources/templates/sign/sign-success.html diff --git a/yudao-module-car/yudao-module-car-biz/pom.xml b/yudao-module-car/yudao-module-car-biz/pom.xml index dc9305e..79ba97b 100644 --- a/yudao-module-car/yudao-module-car-biz/pom.xml +++ b/yudao-module-car/yudao-module-car-biz/pom.xml @@ -93,5 +93,11 @@ yudao-module-infra-biz ${revision} + + + + cn.iocoder.boot + yudao-spring-boot-starter-redis + diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalorder/RenewalOrderController.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalorder/RenewalOrderController.java index a29686f..526291c 100644 --- a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalorder/RenewalOrderController.java +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalorder/RenewalOrderController.java @@ -77,6 +77,27 @@ public class RenewalOrderController { return success(renewalOrderService.generateContractHtml(id)); } + @PostMapping("/create-sign-token") + @Operation(summary = "创建线上签名令牌") + @Parameter(name = "id", description = "订单编号", required = true) + @PreAuthorize("@ss.hasPermission('car:renewal-order:query')") + public CommonResult> createSignToken(@RequestParam("id") Long id) { + String uuid = renewalOrderService.createSignToken(id); + Map result = new HashMap<>(); + result.put("uuid", uuid); + result.put("signUrl", "/admin-api/car/sign/" + uuid); + return success(result); + } + + @PostMapping("/clear-contract-sign") + @Operation(summary = "清空订单合同与客户签名") + @Parameter(name = "id", description = "订单编号", required = true) + @PreAuthorize("@ss.hasPermission('car:renewal-order:update')") + public CommonResult clearContractAndSignature(@RequestParam("id") Long id) { + renewalOrderService.clearContractAndSignature(id); + return success(true); + } + @DeleteMapping("/delete") @Operation(summary = "删除车辆续保订单") @Parameter(name = "id", description = "编号", required = true) diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalorder/vo/RenewalOrderSaveReqVO.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalorder/vo/RenewalOrderSaveReqVO.java index af48cb1..7b82e6f 100644 --- a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalorder/vo/RenewalOrderSaveReqVO.java +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalorder/vo/RenewalOrderSaveReqVO.java @@ -74,7 +74,8 @@ public class RenewalOrderSaveReqVO { @Schema(description = "产品时效") private String productValidity; - @Schema(description = "原厂质保时长") + @Schema(description = "产品年限(原厂质保时长)", requiredMode = Schema.RequiredMode.REQUIRED, example = "3") + @NotBlank(message = "产品年限不能为空") private String originalWarrantyYears; @Schema(description = "原厂质保里程") diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductPageReqVO.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductPageReqVO.java index b03992e..63a545b 100644 --- a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductPageReqVO.java +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductPageReqVO.java @@ -24,6 +24,9 @@ public class RenewalProductPageReqVO extends PageParam { @Schema(description = "产品类别(car_renewal_product_type:00 无忧,01 延保)", example = "2") private String productType; + @Schema(description = "生效年限(car_renewal_year)", example = "1") + private String effectiveYear; + @Schema(description = "备注", example = "随便") private String remark; diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductRespVO.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductRespVO.java index e845429..9808fe3 100644 --- a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductRespVO.java +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductRespVO.java @@ -31,6 +31,11 @@ public class RenewalProductRespVO { @DictFormat("car_renewal_product_type") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中 private String productType; + @Schema(description = "生效年限(car_renewal_year)", example = "1") + @ExcelProperty(value = "生效年限", converter = DictConvert.class) + @DictFormat("car_renewal_year") + private String effectiveYear; + @Schema(description = "备注", example = "随便") @ExcelProperty("备注") private String remark; diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductSaveReqVO.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductSaveReqVO.java index 86a1a73..47a9fe3 100644 --- a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductSaveReqVO.java +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/renewalproduct/vo/RenewalProductSaveReqVO.java @@ -23,6 +23,9 @@ public class RenewalProductSaveReqVO { @NotEmpty(message = "产品类别(car_renewal_product_type:00 无忧,01 延保)不能为空") private String productType; + @Schema(description = "生效年限(car_renewal_year)", example = "1") + private String effectiveYear; + @Schema(description = "备注", example = "随便") private String remark; diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/sign/OnlineSignController.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/sign/OnlineSignController.java new file mode 100644 index 0000000..3f091f9 --- /dev/null +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/controller/admin/sign/OnlineSignController.java @@ -0,0 +1,158 @@ +package cn.iocoder.yudao.module.car.controller.admin.sign; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; +import cn.iocoder.yudao.module.car.dal.redis.OnlineSignRedisDAO; +import cn.iocoder.yudao.module.car.service.renewalorder.RenewalOrderService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import javax.annotation.security.PermitAll; +import java.util.HashMap; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +/** + * 线上签名控制器(公开接口,无需登录) + * + * @author 芋道源码 + */ +@Tag(name = "管理后台 - 线上签名(公开)") +@Controller +@RequestMapping("/admin-api/car/sign") +@Slf4j +@PermitAll +public class OnlineSignController { + + @Resource + private OnlineSignRedisDAO onlineSignRedisDAO; + + @Resource + private RenewalOrderService renewalOrderService; + + /** + * 签名成功页(GET 返回 HTML,可选 contractUrl 用于“查看合同”链接) + */ + @GetMapping("/success") + @Operation(summary = "签名成功页") + public String signSuccess(@RequestParam(value = "contractUrl", required = false) String contractUrl, Model model) { + model.addAttribute("contractUrl", contractUrl != null && !contractUrl.isEmpty() ? contractUrl : null); + return "sign/sign-success"; + } + + /** + * 签名页面(GET 返回 HTML) + */ + @GetMapping("/{uuid}") + @Operation(summary = "线上签名页面") + @Parameter(name = "uuid", description = "签名令牌", required = true) + public String signPage(@PathVariable("uuid") String uuid, Model model) { + String value = onlineSignRedisDAO.get(uuid); + if (value == null || value.isEmpty()) { + model.addAttribute("valid", false); + model.addAttribute("message", "当前在线签名实效已过期,请重新分享在线签名"); + return "sign/sign-expired"; + } + String[] parts = value.split(":", 2); + if (parts.length != 2) { + model.addAttribute("valid", false); + model.addAttribute("message", "当前在线签名实效已过期,请重新分享在线签名"); + return "sign/sign-expired"; + } + model.addAttribute("valid", true); + model.addAttribute("uuid", uuid); + return "sign/sign-page"; + } + + /** + * 获取合同 HTML(用于签名页预览) + */ + @GetMapping("/{uuid}/contract") + @Operation(summary = "获取合同HTML") + @ResponseBody + @Parameter(name = "uuid", description = "签名令牌", required = true) + public CommonResult getContract(@PathVariable("uuid") String uuid) { + String value = onlineSignRedisDAO.get(uuid); + if (value == null || value.isEmpty()) { + return CommonResult.error(404, "当前在线签名实效已过期,请重新分享在线签名"); + } + String[] parts = value.split(":", 2); + if (parts.length != 2) { + return CommonResult.error(404, "当前在线签名实效已过期,请重新分享在线签名"); + } + try { + Long tenantId = Long.parseLong(parts[0]); + Long orderId = Long.parseLong(parts[1]); + String html = TenantUtils.execute(tenantId, () -> renewalOrderService.generateContractHtml(orderId)); + return success(html); + } catch (Exception e) { + log.error("获取合同HTML失败, uuid={}", uuid, e); + return CommonResult.error(500, "获取合同失败"); + } + } + + /** + * 校验令牌是否有效 + */ + @GetMapping("/{uuid}/validate") + @Operation(summary = "校验签名令牌") + @ResponseBody + @Parameter(name = "uuid", description = "签名令牌", required = true) + public CommonResult validate(@PathVariable("uuid") String uuid) { + String value = onlineSignRedisDAO.get(uuid); + boolean valid = value != null && !value.isEmpty() && value.contains(":"); + return success(valid); + } + + /** + * 提交签名并生成合同 + */ + @PostMapping("/{uuid}/submit") + @Operation(summary = "提交签名并生成合同") + @ResponseBody + @Parameter(name = "uuid", description = "签名令牌", required = true) + public CommonResult> submitSignature( + @PathVariable("uuid") String uuid, + @RequestBody Map body) { + String signatureBase64 = body != null ? body.get("signature") : null; + if (signatureBase64 == null || signatureBase64.isEmpty()) { + return CommonResult.error(400, "签名不能为空"); + } + + String value = onlineSignRedisDAO.get(uuid); + if (value == null || value.isEmpty()) { + return CommonResult.error(404, "当前在线签名实效已过期,请重新分享在线签名"); + } + String[] parts = value.split(":", 2); + if (parts.length != 2) { + return CommonResult.error(404, "当前在线签名实效已过期,请重新分享在线签名"); + } + + try { + Long tenantId = Long.parseLong(parts[0]); + Long orderId = Long.parseLong(parts[1]); + String contractUrl = TenantUtils.execute(tenantId, () -> { + renewalOrderService.submitOnlineSignature(orderId, signatureBase64); + return renewalOrderService.generateContract(orderId); + }); + // 签名成功后删除令牌,防止重复使用 + onlineSignRedisDAO.delete(uuid); + + Map result = new HashMap<>(); + result.put("success", true); + result.put("contractUrl", contractUrl); + result.put("message", "签名成功,合同已生成"); + return success(result); + } catch (Exception e) { + log.error("提交签名失败, uuid={}", uuid, e); + return CommonResult.error(500, "提交签名失败: " + e.getMessage()); + } + } +} diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/dataobject/renewalproduct/RenewalProductDO.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/dataobject/renewalproduct/RenewalProductDO.java index de3f59d..40687ff 100644 --- a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/dataobject/renewalproduct/RenewalProductDO.java +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/dataobject/renewalproduct/RenewalProductDO.java @@ -41,6 +41,10 @@ public class RenewalProductDO extends BaseDO { * 枚举 {@link TODO car_renewal_product_type 对应的类} */ private String productType; + /** + * 生效年限(car_renewal_year) + */ + private String effectiveYear; /** * 备注 */ diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/redis/OnlineSignRedisDAO.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/redis/OnlineSignRedisDAO.java new file mode 100644 index 0000000..486cec7 --- /dev/null +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/redis/OnlineSignRedisDAO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.car.dal.redis; + +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import javax.annotation.Resource; +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +/** + * 线上签名 Redis DAO + * + * @author 芋道源码 + */ +@Repository +public class OnlineSignRedisDAO { + + private static final Duration EXPIRE = Duration.ofHours(12); + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 存储签名令牌(uuid -> tenantId:orderId) + * + * @param uuid 唯一序列码 + * @param value 租户ID:订单ID + */ + public void set(String uuid, String value) { + String key = RedisKeyConstants.SIGN_TOKEN + uuid; + stringRedisTemplate.opsForValue().set(key, value, EXPIRE); + } + + /** + * 获取签名令牌对应的值 + * + * @param uuid 唯一序列码 + * @return 租户ID:订单ID,不存在返回 null + */ + public String get(String uuid) { + String key = RedisKeyConstants.SIGN_TOKEN + uuid; + return stringRedisTemplate.opsForValue().get(key); + } + + /** + * 删除签名令牌(签名成功后删除,防止重复使用) + * + * @param uuid 唯一序列码 + */ + public void delete(String uuid) { + String key = RedisKeyConstants.SIGN_TOKEN + uuid; + stringRedisTemplate.delete(key); + } +} diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/redis/RedisKeyConstants.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/redis/RedisKeyConstants.java new file mode 100644 index 0000000..2a02e95 --- /dev/null +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/dal/redis/RedisKeyConstants.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.car.dal.redis; + +/** + * 车辆模块 Redis Key 常量 + * + * @author 芋道源码 + */ +public interface RedisKeyConstants { + + /** + * 线上签名令牌 + * KEY 格式:car:sign:token:{uuid} + * VALUE 格式:tenantId:orderId(加密后的订单ID) + * 过期时间:12小时 + */ + String SIGN_TOKEN = "car:sign:token:"; + +} diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/contract/ContractServiceImpl.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/contract/ContractServiceImpl.java index 651222d..5c639f6 100644 --- a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/contract/ContractServiceImpl.java +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/contract/ContractServiceImpl.java @@ -1,6 +1,8 @@ package cn.iocoder.yudao.module.car.service.contract; import cn.iocoder.yudao.module.car.dal.dataobject.renewalorder.RenewalOrderDO; +import cn.iocoder.yudao.module.car.dal.dataobject.renewalproduct.RenewalProductDO; +import cn.iocoder.yudao.module.car.service.renewalproduct.RenewalProductService; import cn.iocoder.yudao.module.infra.api.file.FileApi; import cn.iocoder.yudao.module.infra.service.file.FileService; import cn.hutool.core.util.StrUtil; @@ -38,6 +40,9 @@ public class ContractServiceImpl implements ContractService { @Resource private FileService fileService; + + @Resource + private RenewalProductService renewalProductService; private static final String CONTRACT_TEMPLATE = "contract/renewal-contract"; private static final String SEAL_IMAGE_PATH = "static/seal.png"; @@ -160,9 +165,43 @@ public class ContractServiceImpl implements ContractService { String certificateNo = generateCertificateNo(renewalOrder); context.setVariable("certificateNo", certificateNo); - // 服务期限 + // 服务期限:根据产品生效年限计算 LocalDate startDate = LocalDate.now(); - LocalDate endDate = startDate.plusYears(3); + int effectiveYears = 3; // 默认3年 + + // 根据订单的产品ID查询产品信息,获取生效年限 + if (renewalOrder.getProductId() != null) { + try { + RenewalProductDO product = renewalProductService.getRenewalProduct(renewalOrder.getProductId()); + if (product != null && product.getEffectiveYear() != null && !product.getEffectiveYear().isEmpty()) { + try { + // 将字典值转换为年数(字典值可能是 "1", "2", "3" 等) + effectiveYears = Integer.parseInt(product.getEffectiveYear()); + log.info("订单 {} 的产品 {} 生效年限: {} 年", renewalOrder.getId(), renewalOrder.getProductId(), effectiveYears); + } catch (NumberFormatException e) { + log.warn("产品生效年限格式不正确: {}, 使用默认值3年", product.getEffectiveYear(), e); + effectiveYears = 3; + } + } else { + log.warn("订单 {} 的产品 {} 未设置生效年限,使用默认值3年", renewalOrder.getId(), renewalOrder.getProductId()); + } + } catch (Exception e) { + log.error("查询产品信息失败,订单ID: {}, 产品ID: {}, 使用默认值3年", renewalOrder.getId(), renewalOrder.getProductId(), e); + effectiveYears = 3; + } + } else { + log.warn("订单 {} 未关联产品,使用默认值3年", renewalOrder.getId()); + } + + // 计算权益一、权益二的结束日期(最多3年) + int benefit12Years = Math.min(effectiveYears, 3); + LocalDate benefit12EndDate = startDate.plusYears(benefit12Years); + + // 计算权益三的结束日期(使用实际年限) + LocalDate benefit3EndDate = startDate.plusYears(effectiveYears); + + // 服务期限(用于合同主体部分) + LocalDate endDate = benefit3EndDate; // 使用实际年限作为合同总期限 context.setVariable("serviceStartDate", startDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); context.setVariable("serviceEndDate", endDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); @@ -170,6 +209,17 @@ public class ContractServiceImpl implements ContractService { context.setVariable("serviceStartDateText", startDate.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))); context.setVariable("serviceEndDateText", endDate.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))); + // 权益一、权益二的日期(最多3年) + context.setVariable("benefit12StartDateText", startDate.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))); + context.setVariable("benefit12EndDateText", benefit12EndDate.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))); + + // 权益三的日期(使用实际年限) + context.setVariable("benefit3StartDateText", startDate.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))); + context.setVariable("benefit3EndDateText", benefit3EndDate.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"))); + + // 服务年限(用于模板中显示年限) + context.setVariable("serviceYears", effectiveYears); + // 签章图片路径(转换为 base64) // 注意:openhtmltopdf 可能不支持 WebP 格式,如果出错会跳过签章显示 try { diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/renewalorder/RenewalOrderService.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/renewalorder/RenewalOrderService.java index 14e9bc6..96cea1a 100644 --- a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/renewalorder/RenewalOrderService.java +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/renewalorder/RenewalOrderService.java @@ -68,4 +68,27 @@ public interface RenewalOrderService { */ String generateContractHtml(Long id); + /** + * 创建线上签名令牌 + * + * @param orderId 订单编号 + * @return uuid 签名链接序列码 + */ + String createSignToken(Long orderId); + + /** + * 提交线上签名(上传签名图片并更新订单) + * + * @param orderId 订单编号 + * @param signatureBase64 签名 base64 数据 + */ + void submitOnlineSignature(Long orderId, String signatureBase64); + + /** + * 清空订单的合同与客户签名(用于重新生成合同时先清空再让客户扫码签名) + * + * @param id 订单编号 + */ + void clearContractAndSignature(Long id); + } \ No newline at end of file diff --git a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/renewalorder/RenewalOrderServiceImpl.java b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/renewalorder/RenewalOrderServiceImpl.java index 43648c9..0dbd216 100644 --- a/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/renewalorder/RenewalOrderServiceImpl.java +++ b/yudao-module-car/yudao-module-car-biz/src/main/java/cn/iocoder/yudao/module/car/service/renewalorder/RenewalOrderServiceImpl.java @@ -14,7 +14,13 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.car.dal.mysql.renewalorder.RenewalOrderMapper; +import cn.iocoder.yudao.module.car.dal.redis.OnlineSignRedisDAO; import cn.iocoder.yudao.module.car.service.contract.ContractService; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; +import cn.iocoder.yudao.module.infra.api.file.FileApi; + +import java.util.Base64; +import java.util.UUID; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.car.enums.ErrorCodeConstants.*; @@ -35,6 +41,12 @@ public class RenewalOrderServiceImpl implements RenewalOrderService { @Resource private ContractService contractService; + @Resource + private OnlineSignRedisDAO onlineSignRedisDAO; + + @Resource + private FileApi fileApi; + @Override public Long createRenewalOrder(RenewalOrderSaveReqVO createReqVO) { // 插入 @@ -107,4 +119,51 @@ public class RenewalOrderServiceImpl implements RenewalOrderService { } return contractService.generateContractHtml(order); } + + @Override + public String createSignToken(Long orderId) { + validateRenewalOrderExists(orderId); + String uuid = UUID.randomUUID().toString().replace("-", ""); + Long tenantId = TenantContextHolder.getTenantId(); + String value = (tenantId != null ? tenantId : 0) + ":" + orderId; + onlineSignRedisDAO.set(uuid, value); + log.info("创建线上签名令牌 - orderId: {}, uuid: {}", orderId, uuid); + return uuid; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void submitOnlineSignature(Long orderId, String signatureBase64) { + RenewalOrderDO existOrder = renewalOrderMapper.selectById(orderId); + if (existOrder == null) { + throw exception(RENEWAL_ORDER_NOT_EXISTS); + } + try { + byte[] imageBytes = Base64.getDecoder().decode(signatureBase64.replaceAll("^data:image/\\w+;base64,", "")); + String path = "signature/" + orderId + "_" + System.currentTimeMillis() + ".png"; + String url = fileApi.createFile("signature.png", path, imageBytes); + RenewalOrderDO update = new RenewalOrderDO(); + update.setId(orderId); + update.setCustomerSignatureUrl(url); + renewalOrderMapper.updateById(update); + log.info("线上签名已保存 - orderId: {}, url: {}", orderId, url); + } catch (Exception e) { + log.error("上传签名图片失败", e); + throw new RuntimeException("上传签名失败: " + e.getMessage(), e); + } + } + + @Override + public void clearContractAndSignature(Long id) { + RenewalOrderDO order = renewalOrderMapper.selectById(id); + if (order == null) { + throw exception(RENEWAL_ORDER_NOT_EXISTS); + } + RenewalOrderDO update = new RenewalOrderDO(); + update.setId(id); + update.setContractUrl(null); + update.setCustomerSignatureUrl(null); + renewalOrderMapper.updateById(update); + log.info("已清空订单合同与签名 - orderId: {}", id); + } } \ No newline at end of file diff --git a/yudao-module-car/yudao-module-car-biz/src/main/resources/templates/contract/renewal-contract.html b/yudao-module-car/yudao-module-car-biz/src/main/resources/templates/contract/renewal-contract.html index b1fa548..8efb2dc 100644 --- a/yudao-module-car/yudao-module-car-biz/src/main/resources/templates/contract/renewal-contract.html +++ b/yudao-module-car/yudao-module-car-biz/src/main/resources/templates/contract/renewal-contract.html @@ -2,7 +2,7 @@ - 途安出行+保障服务凭证 + 途安出行保障服务凭证 + + +
当前在线签名实效已过期,请重新分享在线签名
+ + diff --git a/yudao-module-car/yudao-module-car-biz/src/main/resources/templates/sign/sign-page.html b/yudao-module-car/yudao-module-car-biz/src/main/resources/templates/sign/sign-page.html new file mode 100644 index 0000000..99eb545 --- /dev/null +++ b/yudao-module-car/yudao-module-car-biz/src/main/resources/templates/sign/sign-page.html @@ -0,0 +1,151 @@ + + + + + + 线上签名 - 途安出行保障服务 + + + +
+

途安出行保障服务 - 线上签名

+
正在加载合同内容...
+
+ + + +
+
+
+ +
+
+ + + + diff --git a/yudao-module-car/yudao-module-car-biz/src/main/resources/templates/sign/sign-success.html b/yudao-module-car/yudao-module-car-biz/src/main/resources/templates/sign/sign-success.html new file mode 100644 index 0000000..752b8bf --- /dev/null +++ b/yudao-module-car/yudao-module-car-biz/src/main/resources/templates/sign/sign-success.html @@ -0,0 +1,32 @@ + + + + + + 签名成功 - 途安出行保障服务 + + + +
+
+

签名成功

+

合同已生成,您可关闭本页面。

+
+ 查看合同 + +
+
+ + diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 0257ba2..3bdbc4c 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -54,17 +54,9 @@ spring: primary: master datasource: master: - # MySQL Connector/J 8.X 连接的示例 - # url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai # MySQL Connector/J 5.X 连接的示例 - # url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 连接的示例 - # url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 连接的示例 - # url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 连接的示例 - # url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 连接的示例 - # url: jdbc:kingbase8://127.0.0.1:54321/test # 人大金仓 KingbaseES 连接的示例 - # url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 连接的示例 - url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true + url: jdbc:mysql://47.98.192.5:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 username: root - password: root + password: mysql_BcMnw5 #url: jdbc:mysql://47.98.192.5:3306/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例 #username: root @@ -86,9 +78,9 @@ spring: # Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优 redis: - host: 123.57.150.179 # 地址 + host: 47.98.192.5 # 地址 port: 6379 # 端口 - database: 0 # 数据库索引 + database: 1 # 数据库索引 password: redis_pd2R8B # 密码,建议生产环境开启 --- #################### 定时任务相关配置 #################### diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 953b947..44e1a72 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -209,6 +209,7 @@ yudao: security: permit-all_urls: - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录 + - /admin-api/car/sign/** # 线上签名页面及接口,无需登录 websocket: enable: true # websocket的开关 path: /infra/ws # 路径 @@ -246,6 +247,7 @@ yudao: - /admin-api/pay/notify/** # 支付回调通知,不携带租户编号 - /jmreport/* # 积木报表,无法携带租户编号 - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,无法携带租户编号 + - /admin-api/car/sign/** # 线上签名页面及接口,无法携带租户编号 ignore-tables: - system_tenant - system_tenant_package