contract-lifecycle-part2.md 16 KB

签约功能生命周期详解 - 第二部分

2. 服务层 (Service) (续)

2.1.2 提交合同签署流程 submitForSigning()

@Override
@Transactional(rollbackFor = Exception.class)
public ApiResult<ContractSubmitResponse> submitForSigning(String contractId) {
    log.info("开始提交合同签署, 合同ID: {}", contractId);
    
    // 1. 获取当前用户ID
    String currentUserId = securityService.getCurrentUserId();
    
    // 2. 查询合同信息
    Contract contract = contractMapper.selectById(contractId);
    if (contract == null) {
        return ApiResult.failed("合同不存在");
    }
    
    // 3. 检查合同状态
    if (contract.getContractStatus() != ContractStatus.DRAFT) {
        return ApiResult.failed("只有草稿状态的合同可以提交签署");
    }
    
    // 4. 检查用户是否有权限提交合同(是否属于甲方企业)
    if (!enterpriseService.isUserBelongToEnterprise(currentUserId, contract.getPartyAId())) {
        return ApiResult.forbidden("您没有权限提交该合同");
    }
    
    // 5. 更新合同状态为待签署
    contract.setContractStatus(ContractStatus.PENDING_SIGNING);
    contract.setUpdateUser(currentUserId);
    contract.setUpdateTime(new Date());
    contractMapper.updateStatus(contract);
    
    // 6. 创建签署任务
    ContractSignTask taskA = new ContractSignTask();
    taskA.setTaskId(IdGenerator.generateId());
    taskA.setContractId(contractId);
    taskA.setEnterpriseId(contract.getPartyAId());
    taskA.setEnterpriseName(contract.getPartyAName());
    taskA.setSignStatus(SignStatus.PENDING);
    taskA.setCreateTime(new Date());
    contractSignTaskMapper.insert(taskA);
    
    ContractSignTask taskB = new ContractSignTask();
    taskB.setTaskId(IdGenerator.generateId());
    taskB.setContractId(contractId);
    taskB.setEnterpriseId(contract.getPartyBId());
    taskB.setEnterpriseName(contract.getPartyBName());
    taskB.setSignStatus(SignStatus.PENDING);
    taskB.setCreateTime(new Date());
    contractSignTaskMapper.insert(taskB);
    
    // 7. 发送签署通知
    notificationService.sendContractSigningNotification(contract.getPartyAId(), contract);
    notificationService.sendContractSigningNotification(contract.getPartyBId(), contract);
    
    // 8. 记录合同操作日志
    contractLogService.logContractOperation(
            contractId,
            ContractOperationType.SUBMIT,
            "提交合同签署",
            currentUserId);
    
    // 9. 构建返回结果
    ContractSubmitResponse response = new ContractSubmitResponse();
    response.setContractId(contractId);
    response.setContractStatus(ContractStatus.PENDING_SIGNING);
    response.setSubmitTime(contract.getUpdateTime());
    response.setSigningUrl("/api/v1/contract/sign/" + contractId);
    
    log.info("合同提交签署成功, 合同ID: {}", contractId);
    return ApiResult.success("合同已提交签署", response);
}

处理逻辑

  1. 获取当前用户ID
  2. 查询合同信息
  3. 检查合同状态
  4. 检查用户权限
  5. 更新合同状态为待签署
  6. 创建签署任务(甲方和乙方)
  7. 发送签署通知
  8. 记录合同操作日志
  9. 构建返回结果

2.1.3 合同签署流程 signContract()

@Override
@Transactional(rollbackFor = Exception.class)
public ApiResult<ContractSignResponse> signContract(String contractId, ContractSignRequest request) {
    log.info("开始签署合同, 合同ID: {}", contractId);
    
    // 1. 获取当前用户ID
    String currentUserId = securityService.getCurrentUserId();
    
    // 2. 查询合同信息
    Contract contract = contractMapper.selectById(contractId);
    if (contract == null) {
        return ApiResult.failed("合同不存在");
    }
    
    // 3. 检查合同状态
    if (contract.getContractStatus() != ContractStatus.PENDING_SIGNING) {
        return ApiResult.failed("当前合同状态不允许签署");
    }
    
    // 4. 获取用户所属企业
    String userEnterpriseId = enterpriseService.getUserEnterpriseId(currentUserId);
    if (userEnterpriseId == null) {
        return ApiResult.failed("用户未关联企业,无法签署合同");
    }
    
    // 5. 检查用户企业是否为合同相关方
    boolean isPartyA = userEnterpriseId.equals(contract.getPartyAId());
    boolean isPartyB = userEnterpriseId.equals(contract.getPartyBId());
    
    if (!isPartyA && !isPartyB) {
        return ApiResult.forbidden("您的企业不是合同签署方,无权签署");
    }
    
    // 6. 查询签署任务
    ContractSignTask signTask = contractSignTaskMapper.selectByContractAndEnterprise(
            contractId, userEnterpriseId);
    
    if (signTask == null) {
        return ApiResult.failed("未找到对应的签署任务");
    }
    
    if (signTask.getSignStatus() == SignStatus.SIGNED) {
        return ApiResult.failed("您已经签署过该合同");
    }
    
    // 7. 验证短信验证码
    if (!verificationCodeService.verify(currentUserId, request.getVerificationCode())) {
        return ApiResult.failed("验证码错误或已过期");
    }
    
    // 8. 创建数字签名
    String signature = digitalSignatureService.createSignature(
            contractId,
            userEnterpriseId,
            request.getSignerName(),
            request.getSignerPosition());
    
    // 9. 更新签署任务状态
    signTask.setSignStatus(SignStatus.SIGNED);
    signTask.setSignerName(request.getSignerName());
    signTask.setSignerPosition(request.getSignerPosition());
    signTask.setSignMethod(request.getSignMethod());
    signTask.setSignature(signature);
    signTask.setSignTime(new Date());
    signTask.setUpdateTime(new Date());
    contractSignTaskMapper.update(signTask);
    
    // 10. 检查所有签署任务是否完成
    boolean allSigned = contractSignTaskMapper.checkAllSigned(contractId);
    
    // 11. 如果所有方都已签署,更新合同状态为已签署
    if (allSigned) {
        contract.setContractStatus(ContractStatus.SIGNED);
        contract.setUpdateUser(currentUserId);
        contract.setUpdateTime(new Date());
        contractMapper.updateStatus(contract);
        
        // 12. 生成最终签署版合同
        String signedContractFileId = contractFileService.generateSignedContractFile(contract, contractSignTaskMapper.selectByContractId(contractId));
        contractMapper.updateSignedContractFile(contractId, signedContractFileId);
        
        // 13. 发送合同签署完成通知
        notificationService.sendContractSignedNotification(contract.getPartyAId(), contract);
        notificationService.sendContractSignedNotification(contract.getPartyBId(), contract);
    }
    
    // 14. 记录合同操作日志
    contractLogService.logContractOperation(
            contractId,
            ContractOperationType.SIGN,
            (isPartyA ? "甲方" : "乙方") + "签署合同",
            currentUserId);
    
    // 15. 构建返回结果
    ContractSignResponse response = new ContractSignResponse();
    response.setContractId(contractId);
    response.setContractStatus(allSigned ? ContractStatus.SIGNED : ContractStatus.PENDING_SIGNING);
    response.setSignTime(signTask.getSignTime());
    
    if (allSigned) {
        response.setSignedUrl("/api/v1/contract/download/signed/" + contractId);
    }
    
    log.info("合同签署成功, 合同ID: {}, 企业ID: {}", contractId, userEnterpriseId);
    return ApiResult.success("合同签署成功", response);
}

处理逻辑

  1. 获取当前用户ID
  2. 查询合同信息
  3. 检查合同状态
  4. 获取用户所属企业
  5. 检查用户企业是否为合同相关方
  6. 查询签署任务
  7. 验证短信验证码
  8. 创建数字签名
  9. 更新签署任务状态
  10. 检查所有签署任务是否完成
  11. 如果所有方都已签署,更新合同状态为已签署
  12. 生成最终签署版合同
  13. 发送合同签署完成通知
  14. 记录合同操作日志
  15. 构建返回结果

2.2 数字签名服务

主要实现类DigitalSignatureServiceImpl

@Service
@RequiredArgsConstructor
public class DigitalSignatureServiceImpl implements DigitalSignatureService {

    private final EnterpriseInfoMapper enterpriseInfoMapper;
    
    @Override
    public String createSignature(String contractId, String enterpriseId, String signerName, String signerPosition) {
        try {
            // 1. 获取企业信息
            EnterpriseInfo enterpriseInfo = enterpriseInfoMapper.selectById(enterpriseId);
            if (enterpriseInfo == null) {
                throw new BusinessException("企业信息不存在");
            }
            
            // 2. 构建签名数据
            Map<String, Object> signData = new HashMap<>();
            signData.put("contractId", contractId);
            signData.put("enterpriseId", enterpriseId);
            signData.put("enterpriseName", enterpriseInfo.getEnterpriseName());
            signData.put("signerName", signerName);
            signData.put("signerPosition", signerPosition);
            signData.put("signTime", System.currentTimeMillis());
            
            // 3. 创建数字签名
            String signDataStr = JsonUtil.toJson(signData);
            String signature = SignatureUtil.sign(signDataStr);
            
            return signature;
        } catch (Exception e) {
            throw new BusinessException("创建数字签名失败:" + e.getMessage());
        }
    }
    
    @Override
    public boolean verifySignature(String signature, String contractId, String enterpriseId) {
        try {
            // 1. 获取企业信息
            EnterpriseInfo enterpriseInfo = enterpriseInfoMapper.selectById(enterpriseId);
            if (enterpriseInfo == null) {
                return false;
            }
            
            // 2. 获取签署记录
            ContractSignTask signTask = contractSignTaskMapper.selectByContractAndEnterprise(contractId, enterpriseId);
            if (signTask == null) {
                return false;
            }
            
            // 3. 验证签名
            return SignatureUtil.verify(signature, signTask);
        } catch (Exception e) {
            return false;
        }
    }
}

2.3 合同文件服务

主要实现类ContractFileServiceImpl

@Service
@RequiredArgsConstructor
public class ContractFileServiceImpl implements ContractFileService {

    private final FileStorageService fileStorageService;
    private final ContractTemplateService contractTemplateService;
    
    @Override
    public String generateContractFile(Contract contract) {
        try {
            // 1. 获取合同模板
            String templateId = getTemplateIdByContractType(contract.getContractType());
            byte[] templateContent = contractTemplateService.getTemplateContent(templateId);
            
            // 2. 填充合同内容
            Map<String, Object> templateData = buildTemplateData(contract);
            byte[] contractContent = contractTemplateService.fillTemplate(templateContent, templateData);
            
            // 3. 保存合同文件
            String fileName = "合同_" + contract.getContractId() + ".pdf";
            String fileId = fileStorageService.storeFile(contractContent, "application/pdf", fileName);
            
            return fileId;
        } catch (Exception e) {
            throw new BusinessException("生成合同文件失败:" + e.getMessage());
        }
    }
    
    @Override
    public String generateSignedContractFile(Contract contract, List<ContractSignTask> signTasks) {
        try {
            // 1. 获取原合同文件
            byte[] originalContract = fileStorageService.getFileContent(contract.getContractFileId());
            
            // 2. 添加签名信息
            byte[] signedContract = addSignaturesToContract(originalContract, signTasks);
            
            // 3. 保存签署后的合同文件
            String fileName = "已签署合同_" + contract.getContractId() + ".pdf";
            String fileId = fileStorageService.storeFile(signedContract, "application/pdf", fileName);
            
            return fileId;
        } catch (Exception e) {
            throw new BusinessException("生成已签署合同文件失败:" + e.getMessage());
        }
    }
    
    @Override
    public byte[] getContractFileContent(String fileId) {
        return fileStorageService.getFileContent(fileId);
    }
    
    private String getTemplateIdByContractType(ContractType contractType) {
        switch (contractType) {
            case PURCHASE:
                return "TEMPLATE_PURCHASE";
            case SALES:
                return "TEMPLATE_SALES";
            case FINANCING:
                return "TEMPLATE_FINANCING";
            default:
                return "TEMPLATE_GENERAL";
        }
    }
    
    private Map<String, Object> buildTemplateData(Contract contract) {
        Map<String, Object> data = new HashMap<>();
        data.put("contractId", contract.getContractId());
        data.put("contractName", contract.getContractName());
        data.put("partyA", contract.getPartyAName());
        data.put("partyB", contract.getPartyBName());
        data.put("contractAmount", contract.getContractAmount());
        data.put("startDate", DateUtil.formatDate(contract.getStartDate()));
        data.put("endDate", DateUtil.formatDate(contract.getEndDate()));
        data.put("contractPeriod", contract.getContractPeriod());
        data.put("description", contract.getDescription());
        data.put("createDate", DateUtil.formatDate(contract.getCreateTime()));
        return data;
    }
    
    private byte[] addSignaturesToContract(byte[] originalContract, List<ContractSignTask> signTasks) {
        // 使用PDF库添加数字签名、签章等信息
        // 此处省略具体PDF处理实现...
        return originalContract;
    }
}

3. 数据访问层 (Mapper/Repository)

3.1 合同数据访问

主要实现类ContractMapper

@Mapper
public interface ContractMapper {
    /**
     * 插入合同
     */
    int insert(Contract contract);
    
    /**
     * 根据ID查询
     */
    Contract selectById(String contractId);
    
    /**
     * 更新合同状态
     */
    int updateStatus(Contract contract);
    
    /**
     * 更新合同文件ID
     */
    int updateContractFile(String contractId, String contractFileId);
    
    /**
     * 更新已签署合同文件ID
     */
    int updateSignedContractFile(String contractId, String signedContractFileId);
    
    /**
     * 查询企业相关的合同列表
     */
    List<Contract> selectByEnterpriseId(String enterpriseId, ContractQueryParam param);
    
    /**
     * 统计企业相关的合同数量
     */
    int countByEnterpriseId(String enterpriseId, ContractQueryParam param);
    
    /**
     * 删除合同
     */
    int deleteById(String contractId);
}

3.2 合同签署任务数据访问

主要实现类ContractSignTaskMapper

@Mapper
public interface ContractSignTaskMapper {
    /**
     * 插入签署任务
     */
    int insert(ContractSignTask task);
    
    /**
     * 更新签署任务
     */
    int update(ContractSignTask task);
    
    /**
     * 根据合同ID和企业ID查询签署任务
     */
    ContractSignTask selectByContractAndEnterprise(String contractId, String enterpriseId);
    
    /**
     * 根据合同ID查询所有签署任务
     */
    List<ContractSignTask> selectByContractId(String contractId);
    
    /**
     * 检查合同是否已全部签署
     */
    boolean checkAllSigned(String contractId);
    
    /**
     * 删除签署任务
     */
    int deleteByContractId(String contractId);
}

3.3 合同操作日志数据访问

主要实现类ContractLogMapper

@Mapper
public interface ContractLogMapper {
    /**
     * 插入合同操作日志
     */
    int insert(ContractLog log);
    
    /**
     * 根据合同ID查询操作日志
     */
    List<ContractLog> selectByContractId(String contractId);
}