简介本资源是一套基于Spring Boot与Vue技术栈开发的企业级合同管理系统完整源码包面向Java全栈初学者、毕业设计学生及中小型团队开发者旨在解决合同全生命周期管理中的起草、审批、签署、履行与归档等核心业务痛点。压缩包共728个文件涵盖93个Java后端逻辑文件、46个Vue前端组件、156个JS交互脚本、162个SVG图标资源、49个CSS样式文件及1个SQL建库脚本辅以bat部署脚本与yml配置文件结构清晰、模块解耦便于学习分层架构与前后端联调。包体大小18.85MB已通过Tomcat7与MySQL 5.7环境严格调试开箱即用。目前已有2715人学习下载资源包含可直接运行的项目源码、配套数据库脚本、功能说明文档及多套静态资源Bootstrap/Element UI等特别适合用于毕设实战、技术栈整合训练与企业轻量级合同管理原型搭建。1. 合同管理系统不是电子文档归档工具而是业务流驱动的契约执行中枢很多团队拿到「SpringBoot Vue 合同管理系统」这个标题第一反应是又一个 CRUD 表单项目但实际落地时才发现合同生命周期远比增删改查复杂——它要对接法务审核节点、触发用印审批流、关联供应商主数据、校验签约主体资质、生成带时间戳的 PDF 签章文件、同步归档至 OA 或档案系统甚至要支持多版本比对和条款变更追溯。本项目正是围绕这些真实业务断点设计后端用 SpringBoot 构建领域模型Contract、Party、Clause、ApprovalRecord前端用 Vue 实现动态表单渲染支持条款模板拖拽配置、PDF 预览与签署状态联动、以及基于路由守卫的权限粒度控制如法务仅见审核中合同财务仅见已付款条款。适合正在从 Excel 管理转向数字化契约治理的中小型企业 IT 团队或需要快速交付合同模块的 Java/Vue 全栈开发者——它不提供 SaaS 服务但给出可嵌入现有系统的标准接口与可复用组件。2. 搭建合同管理基础骨架SpringBoot 后端核心结构与关键依赖选型2.1 为什么选择 SpringBoot 2.7.x 而非 3.x当前主流企业级项目仍以 JDK 8/11 为主而 SpringBoot 3.x 强制要求 JDK 17 且移除了 Servlet API 的部分兼容层。合同管理系统需对接老旧 OA 系统如泛微 e-cology的 WebService 接口其 WSDL 文件依赖 JAX-WS 运行时该组件在 SpringBoot 3.x 中已被弃用。因此本项目采用 SpringBoot 2.7.182023 年 LTS 版本既满足 Java 8 兼容性又获得安全补丁支持。若你环境已升级至 JDK 17可将spring-boot-starter-parent替换为3.2.5但需同步替换spring-boot-starter-web为spring-boot-starter-webflux并重写 Controller 层。!-- pom.xml 关键依赖 -- parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies !-- 合同核心JPA 持久化 Lombok 减少样板代码 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- PDF 生成iText 7.2.5支持数字签名与条款高亮 -- dependency groupIdcom.itextpdf/groupId artifactIditext7-core-pdfhtml/artifactId version4.0.2/version typepom/type /dependency !-- 审批流轻量级 Flowable 6.8.0避免 Activiti 复杂配置 -- dependency groupIdorg.flowable/groupId artifactIdflowable-spring-boot-starter-process/artifactId version6.8.0/version /dependency /dependencies提示iText 7 商业授权较严格若项目为内部系统且无对外分发需求可使用 AGPLv3 版本若需商用请购买官方许可。替代方案是 Apache PDFBox但其数字签名支持弱于 iText。2.2 合同实体建模从「一份文档」到「可执行契约」合同不是静态 PDF而是由多个聚合根组成的业务对象。本项目定义以下核心实体关系实体关键字段业务含义ContractcontractNo,status(DRAFT/APPROVING/SIGNED/EXPIRED),signDate,expireDate主合同头信息status驱动整个生命周期PartypartyType(CLIENT/SUPPLIER),name,creditCode,legalRepresentative签约主体支持同一主体多次出现如甲方母公司子公司ClausetemplateId,content,isMandatory,version条款内容templateId关联预设模板库version支持历史回溯ApprovalRecordnodeId,approverId,status(PENDING/APPROVED/REJECTED),comment审批记录与 Flowable 的ACT_HI_PROCINST表双向同步// Contract.java - 使用 EntityGraph 预加载关联数据避免 N1 查询 Entity Table(name t_contract) NamedEntityGraph( name Contract.withPartiesAndClauses, attributeNodes { NamedAttributeNode(parties), NamedAttributeNode(clauses) } ) public class Contract { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(name contract_no, unique true, nullable false) private String contractNo; // 格式CT-2024-0001 Enumerated(EnumType.STRING) Column(name status) private ContractStatus status ContractStatus.DRAFT; OneToMany(mappedBy contract, cascade CascadeType.ALL, fetch FetchType.LAZY) private ListParty parties new ArrayList(); OneToMany(mappedBy contract, cascade CascadeType.ALL, fetch FetchType.LAZY) private ListClause clauses new ArrayList(); }2.2.1 动态条款模板引擎用 Thymeleaf 渲染合同正文合同正文不是纯文本而是含变量占位符的模板如${client.name}、${totalAmount}。系统在生成 PDF 前先用 Thymeleaf 解析模板并注入实时数据Service public class ContractTemplateService { private final TemplateEngine templateEngine; public ContractTemplateService(TemplateEngine templateEngine) { this.templateEngine templateEngine; } public String renderHtml(Contract contract) { Context context new Context(); context.setVariable(contract, contract); context.setVariable(parties, contract.getParties()); context.setVariable(clauses, contract.getClauses()); // 注入当前日期、审批人等上下文变量 context.setVariable(now, LocalDateTime.now()); return templateEngine.process(contract-template, context); } }注意contract-template.html存放于src/main/resources/templates/需启用 Thymeleaf 自动配置spring-boot-starter-thymeleaf已包含。此设计使法务人员可直接编辑 HTML 模板无需修改 Java 代码。2.3 REST API 设计面向 Vue 前端的资源契约API 不按传统 CRUD 命名而是按业务动作设计降低前端理解成本URLMethod说明响应示例POST /api/contracts/draft创建草稿合同返回Contract对象及draftId{ id: 101, contractNo: CT-2024-0001, status: DRAFT }PUT /api/contracts/{id}/submit提交审核触发 Flowable 流程实例{ processInstanceId: a1b2c3d4 }GET /api/contracts/{id}/pdf生成 PDF返回application/pdf流二进制 PDF 内容GET /api/contracts/{id}/diff?fromVersion2toVersion3版本比对返回 HTML 格式差异报告ins新增条款/insdel删除条款/delRestController RequestMapping(/api/contracts) public class ContractController { PostMapping(/draft) public ResponseEntityContract createDraft(RequestBody ContractDraftRequest request) { Contract contract contractService.createDraft(request); return ResponseEntity.status(HttpStatus.CREATED).body(contract); } PutMapping(/{id}/submit) public ResponseEntityMapString, String submitForApproval( PathVariable Long id, RequestBody ApprovalSubmitRequest request) { String processId approvalService.startProcess(id, request.getApproverIds()); return ResponseEntity.ok(Map.of(processInstanceId, processId)); } }3. Vue 前端实现动态表单、PDF 预览与审批状态驱动 UI3.1 项目初始化与依赖安装Vue 3 Composition API Element Plus本项目采用 Vue 3.4Composition API而非 Options API因其更契合合同管理的复杂状态管理需求。使用create-vue脚手架初始化并安装以下核心依赖# 创建项目选择 TypeScript、Router、Pinia、ESLint npm create vuelatest # 安装 UI 组件库与工具 npm install element-plus2.3.12 npm install axios1.6.7 npm install pdf-lib3.1.0 # 用于前端 PDF 编辑如添加水印 npm install vueuse/core10.7.2 # 提供 useStorage、useAsyncState 等组合式函数提示pdf-lib仅用于前端轻量操作如添加公司水印正式签署必须调用后端 iText 生成带数字签名的 PDF因浏览器环境无法安全存储私钥。3.2 合同创建页拖拽式条款模板配置合同创建页不是固定表单而是基于 JSON Schema 的动态渲染器。法务管理员预先配置条款模板库存于数据库t_clause_template表前端通过v-for渲染可拖拽区块!-- ContractCreate.vue -- template el-container el-aside width300px h3条款模板库/h3 draggable :listtemplates item-keyid ghost-classghost endonDragEnd template #item{ element } div classtemplate-item clickaddClause(element) {{ element.name }} ({{ element.category }}) /div /template /draggable /el-aside el-main clause-editor v-model:clausescurrentClauses :readonlyfalse / el-button clicksaveDraft保存草稿/el-button /el-main /el-container /template script setup import { ref, onMounted } from vue import draggable from vuedraggable const templates ref([]) const currentClauses ref([]) // 加载模板库调用 /api/clause-templates onMounted(async () { const res await axios.get(/api/clause-templates) templates.value res.data }) const addClause (template) { currentClauses.value.push({ templateId: template.id, content: template.defaultContent, isMandatory: template.isMandatory, version: 1 }) } /script3.2.1 条款编辑器组件支持富文本与变量插入clause-editor组件封装了 Quill 富文本编辑器并内置变量插入按钮如「客户名称」「签约日期」点击后自动插入${client.name}占位符后端渲染时替换为真实值// components/ClauseEditor.vue import { onMounted, ref } from vue import Quill from quill import quill/dist/quill.snow.css export default { props: [modelValue, readonly], emits: [update:modelValue], setup(props, { emit }) { const editor ref(null) onMounted(() { const quill new Quill(editor.value, { theme: snow, readOnly: props.readonly, modules: { toolbar: [ [{ header: [1, 2, 3, false] }], [bold, italic, underline], [{ color: [] }, { background: [] }], [{ variable: [${client.name}, ${totalAmount}, ${now}] }] // 自定义变量按钮 ] } }) quill.on(text-change, () { emit(update:modelValue, quill.root.innerHTML) }) }) return { editor } } }3.3 PDF 预览与签署状态同步WebSocket 实时更新合同 PDF 不直接返回二进制流而是生成临时访问链接如/pdf/preview/101?tokenabc前端用iframe嵌入。但关键在于当法务在后台完成审批需实时通知前端更新 UI。本项目采用 SpringBoot 的spring-boot-starter-websocket Vue 的stomp/stompjs实现// WebSocketConfig.java Configuration EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { Override public void configureMessageBroker(MessageBrokerRegistry registry) { registry.enableSimpleBroker(/topic); // 订阅主题 registry.setApplicationDestinationPrefixes(/app); // 发送前缀 } }// utils/websocket.js import { Client } from stomp/stompjs const stompClient new Client({ brokerURL: ws://localhost:8080/ws, onConnect: () { stompClient.subscribe(/topic/contract-status, (message) { const data JSON.parse(message.body) if (data.contractId currentContractId.value) { // 更新当前合同状态徽标 contractStatus.value data.status } }) } }) export default stompClient注意WebSocket 连接需在用户进入合同详情页时建立离开时断开避免长连接泄漏。/topic/contract-status主题由后端在审批完成时广播simpMessagingTemplate.convertAndSend(/topic/contract-status, statusUpdate);4. 合同 PDF 生成与数字签名iText 7 的安全实践4.1 生成带时间戳与条款高亮的 PDF合同 PDF 不是简单导出 HTML而是用 iText 7 将 Thymeleaf 渲染的 HTML 转为 PDF并添加业务必需元素时间戳调用国家授时中心 APIhttp://www.ntp.org.cn/获取权威时间写入 PDF 元数据条款高亮对Clause实体中标记为isMandatorytrue的段落用黄色背景突出显示页眉页脚每页显示合同编号、页码、生成时间如「CT-2024-0001 · 第 1 页 · 2024-06-15 14:22:33」Service public class PdfGenerationService { public byte[] generatePdf(Long contractId) throws IOException { Contract contract contractRepository.findById(contractId).orElseThrow(); String html templateService.renderHtml(contract); // 使用 XMLWorker 解析 HTMLiText 7.2 推荐使用 HtmlConverter ConverterProperties properties new ConverterProperties(); properties.setBaseUri(https://example.com/static/); Document document HtmlConverter.convertToDocument(html, properties); // 添加时间戳元数据 document.getDocumentInfo().setCreationDate(Calendar.getInstance()); document.getDocumentInfo().setAuthor(Contract System v1.0); // 高亮强制条款遍历 Paragraph匹配 ${clause.id} 占位符 for (IBlockElement element : document.getChildren()) { if (element instanceof Paragraph) { Paragraph p (Paragraph) element; if (p.getText().contains(${clause.mandatory})) { p.setBackgroundColor(ColorConstants.YELLOW); } } } ByteArrayOutputStream baos new ByteArrayOutputStream(); PdfWriter writer new PdfWriter(baos); PdfDocument pdfDoc new PdfDocument(writer); document.copyTo(pdfDoc); document.close(); pdfDoc.close(); return baos.toByteArray(); } }4.2 数字签名集成对接 CFCA 或上海 CA 的国密 SM2 签名国内合同签署必须符合《电子签名法》要求使用国家认可的 CA 机构颁发的数字证书。本项目预留 SM2 签名接口实际部署时需替换为合作 CA 的 SDK// SignatureService.java - 抽象签名策略 public interface SignatureService { byte[] sign(byte[] pdfBytes, String certPath, String password) throws Exception; } // CFCASignatureService.java - CFCA 实现需引入 cfca-sdk.jar Component public class CFCASignatureService implements SignatureService { Override public byte[] sign(byte[] pdfBytes, String certPath, String password) { // 调用 CFCA SDK 的 Signer.sign() 方法 // 此处省略具体 SDK 调用代码实际需配置 CFCA 服务地址、证书路径 return cfcaSigner.sign(pdfBytes, certPath, password); } }提示CFCA SDK 需向中国金融认证中心申请获取cfca-sdk.jar及测试证书。生产环境必须使用硬件 USBKey 存储私钥禁止明文存储密码。SM2 签名后PDF 的AcroForm字段会嵌入数字签名可用 Adobe Reader 验证「签名有效」与「证书可信」。5. 部署与生产调优Nginx 反向代理、PDF 生成性能与敏感信息防护5.1 Nginx 配置分离静态资源与 API 请求Vue 打包后的dist/目录由 Nginx 直接托管API 请求反向代理至 SpringBoot默认 8080# /etc/nginx/conf.d/contract.conf server { listen 80; server_name contract.example.com; # 前端静态资源 location / { root /var/www/contract-frontend; try_files $uri $uri/ /index.html; } # API 接口代理 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # PDF 预览临时链接需设置过期时间 location /pdf/preview/ { proxy_pass http://127.0.0.1:8080/; proxy_cache_valid 200 302 10m; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; } }注意/pdf/preview/路径需在 SpringBoot 中配置GetMapping(/pdf/preview/{id})并校验token参数有效性防止未授权访问。建议使用 JWT 签名 token有效期设为 5 分钟。5.2 PDF 生成性能优化异步队列与缓存策略PDF 生成是 CPU 密集型操作直接同步处理会导致接口超时。本项目采用Async Redis 缓存Service public class AsyncPdfService { Async(taskExecutor) // 配置独立线程池避免阻塞 Web 线程 public void generateAndCachePdf(Long contractId) { try { byte[] pdfBytes pdfGenerationService.generatePdf(contractId); // 存入 RedisKey: pdf:contract:{id}, TTL: 24h redisTemplate.opsForValue() .set(pdf:contract: contractId, pdfBytes, Duration.ofHours(24)); } catch (Exception e) { log.error(PDF generation failed for contract {}, contractId, e); } } }// Controller 中改为异步触发 GetMapping(/pdf/preview/{id}) public ResponseEntityResource previewPdf(PathVariable Long id) { // 先查 Redis 缓存 byte[] cachedPdf (byte[]) redisTemplate.opsForValue().get(pdf:contract: id); if (cachedPdf ! null) { return ResponseEntity.ok() .contentType(MediaType.APPLICATION_PDF) .body(new ByteArrayResource(cachedPdf)); } // 缓存未命中触发异步生成并返回 loading 页面 asyncPdfService.generateAndCachePdf(id); return ResponseEntity.ok() .contentType(MediaType.TEXT_HTML) .body(new ByteArrayResource(h3PDF 正在生成请稍候.../h3.getBytes())); }5.3 敏感信息防护HeapDump 与日志脱敏SpringBoot 默认开启 HeapDump 功能/actuator/heapdump可能泄露合同中的客户名称、金额等敏感数据。必须禁用或加权# application-prod.yml management: endpoint: heapdump: show-details: NEVER # 禁止暴露堆转储 endpoints: web: exposure: include: health,info,metrics,prometheus # 仅暴露必要端点 endpoint: health: show-details: WHEN_AUTHORIZED # 健康检查详情需认证同时所有日志输出需脱敏// Logback 配置 logback-spring.xml appender nameCONSOLE classch.qos.logback.core.ConsoleAppender encoder pattern%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%ex{10}/pattern charsetUTF-8/charset /encoder /appender !-- 自定义脱敏转换器 -- conversionRule conversionWordsensitive converterClasscom.example.contract.util.SensitiveLogConverter/// SensitiveLogConverter.java public class SensitiveLogConverter extends ClassicConverter { Override public String convert(ILoggingEvent event) { String message event.getFormattedMessage(); // 脱敏手机号、身份证号、银行账号 message message.replaceAll((?!\\d)\\d{3}(?\\d{4}), ****); message message.replaceAll(\\d{17}[\\dXx], *****************); return message; } }提示SensitiveLogConverter仅处理日志消息体数据库 SQL 日志需在application.yml中关闭logging.level.org.hibernate.SQLOFF或使用p6spy代理 JDBC 进行字段级脱敏。本文还有配套的精品资源点击获取