JavaWeb合同管理系统实战:从环境搭建到审批流与部署避坑
简介这是一套基于Java Web技术栈开发的合同管理系统源码包面向需要学习企业级Web应用开发的学生、初级开发者及小型企业信息化选型人员帮助理解合同全生命周期管理的实现思路涵盖合同创建、审批、变更、到期提醒与审计追踪等核心业务场景。压缩包共941个文件约16.18MB以html、css、png、js等前端资源为主配合jsp页面、java源码、class字节码、jar依赖包及xml、json配置文件构成完整的前后端工程结构另有少量db数据文件与工程元数据文件便于直接导入IDE运行调试。目前已有1010人学习下载。资源包含登录鉴权、合同操作、用户信息管理、导出Excel、验证码生成等模块的实现代码读者可据此掌握Servlet、JSP与MVC分层架构的落地方式并参考DAO数据访问与工具类的封装技巧适合作为课程设计或二次开发的参考底本。1. 合同管理系统新版拆包一套 JavaWeb 项目能解决哪些实际业务很多团队在合同管理上还在用共享文件夹加 Excel 台账法务改一版、销售改一版最后谁手里的是终稿都说不清。这套 JavaWeb 合同管理系统免费版核心解决的就是合同从起草、审批、签署到归档的全流程线上化问题。它基于 Spring Boot MyBatis 技术栈构建前端走 JSP 或模板引擎渲染数据库用 MySQL属于典型的 Java 工程师能快速上手二次开发的 Web 项目。适合谁一是中小型企业想低成本搭一套内部合同流转工具二是 Java 学习者需要一个业务逻辑完整、模块划分清晰的实战项目来练手三是接私活的开发者拿它当基础框架改造成客户交付版。它不是什么高精尖的微服务架构但胜在业务闭环完整、代码结构规整能让你把精力放在合同业务本身而不是环境折腾上。2. 环境搭建与数据库初始化从零把项目跑起来2.1 技术栈确认与依赖版本选择拿到一个 JavaWeb 项目包第一件事不是急着导入 IDE而是先看pom.xml或build.gradle确认技术栈版本。这套合同管理系统常见配置是 Spring Boot 2.x 搭配 MyBatis-PlusJDK 要求 1.8 或 11MySQL 5.7 或 8.0。为什么强调版本因为 Spring Boot 2.7 和 3.x 在 Jakarta 包名上不兼容MyBatis-Plus 3.5 前后 API 也有差异。我一般会先扫一眼依赖树确认没有引入奇怪的商业授权库再决定用哪个 JDK 跑。常见做法是本地装两个 JDK一个 8 用于老项目维护一个 17 用于新项目。这套系统如果pom.xml里写的是java.version1.8/java.version那就老老实实用 JDK 8别硬上 17否则编译期就可能报Unsupported class file major version。MySQL 驱动注意mysql-connector-java和mysql-connector-j的区别8.0 以上用后者URL 里要带serverTimezoneAsia/Shanghai不然时间字段存进去会差 8 小时。2.2 数据库建表与初始数据导入项目包里通常有个sql目录里面是建表脚本和初始数据。导入之前先建库字符集用utf8mb4排序规则utf8mb4_general_ci。合同管理系统涉及合同主体、审批流、附件、操作日志等表字段里会有TEXT和DATETIME字符集不对会导致中文乱码或表情符号插入失败。-- 创建数据库字符集必须 utf8mb4 CREATE DATABASE contract_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 导入建表脚本在命令行执行 -- mysql -u root -p contract_db contract_schema.sql -- mysql -u root -p contract_db contract_data.sql建表脚本里如果有外键约束导入顺序要注意先导主表再导从表。初始数据里一般会有一个 admin 账号密码可能是 MD5 或 BCrypt 加密的登录不上就去看sys_user表里的password字段用对应加密方式重新生成一个。别直接改明文登录逻辑里会做加密比对。2.3 application.yml 关键配置项配置文件是项目能不能跑起来的命门。数据库连接、端口、文件上传路径、日志级别都在这里。下面是一份典型配置我按参数含义逐项说明。server: port: 8080 # 如果被占用改成 8081别和本地其他服务冲突 spring: datasource: url: jdbc:mysql://localhost:3306/contract_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password # 改成你自己的 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 50MB # 合同附件可能较大默认 1MB 不够用 max-request-size: 100MB mybatis-plus: mapper-locations: classpath:mapper/*.xml # XML 映射文件位置 configuration: map-underscore-to-camel-case: true # 数据库下划线转 Java 驼峰 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发期打印 SQLmap-underscore-to-camel-case这个参数特别关键。数据库字段叫contract_nameJava 实体里叫contractName不开这个映射查出来就是 null。开发期把 SQL 日志打开能省很多排查时间上线再关掉。文件上传大小限制也要调合同 PDF 加附件很容易超过默认值不调的话上传直接抛MaxUploadSizeExceededException。2.4 启动类与常见启动失败排查启动类上一般有SpringBootApplication和MapperScan(com.xxx.mapper)。如果忘了加MapperScanMyBatis 找不到 Mapper 接口启动时报Consider defining a bean of type xxxMapper。端口冲突报Port 8080 was already in use改端口或杀掉占用进程。数据库连不上先看 URL 里的库名、时区、账号密码再看 MySQL 服务有没有起。启动成功后访问http://localhost:8080能看到登录页就说明前后端通了。如果页面 404检查application.yml里有没有配spring.mvc.view.prefix和suffixJSP 项目还要确认pom.xml里引入了tomcat-embed-jasper依赖否则 JSP 不会被解析。3. 合同核心业务模块拆解从实体设计到审批流实现3.1 合同实体与 MyBatis-Plus 映射合同表是整个系统的核心字段设计直接决定后续功能好不好扩展。典型字段包括合同编号、名称、甲方乙方、金额、签订日期、生效日期、到期日期、状态、创建人、审批人。用 MyBatis-Plus 的话实体类加TableName和TableId注解主键策略选ASSIGN_ID或AUTO。Data TableName(contract_info) public class ContractInfo { TableId(type IdType.AUTO) private Long id; private String contractNo; // 合同编号业务唯一 private String contractName; private String partyA; private String partyB; private BigDecimal amount; // 金额用 BigDecimal别用 double private Date signDate; private Date expireDate; private Integer status; // 0 草稿 1 审批中 2 已生效 3 已归档 private Long createBy; private Date createTime; }金额字段用BigDecimal是血泪经验double做加减会出现0.1 0.2 0.30000000000000004这种精度问题合同金额算错是要出事的。状态字段用整数枚举别用字符串查询和索引效率更高。合同编号建议加唯一索引防止并发插入重复。3.2 合同增删改查接口与分页MyBatis-Plus 的BaseMapper已经提供了单表 CRUD分页需要配一个PaginationInnerInterceptor拦截器。下面是一个合同列表查询的 Service 方法带条件筛选和分页。public PageContractInfo pageQuery(int pageNum, int pageSize, String keyword, Integer status) { LambdaQueryWrapperContractInfo wrapper new LambdaQueryWrapper(); // 关键字模糊匹配合同名称或编号 if (StringUtils.hasText(keyword)) { wrapper.and(w - w.like(ContractInfo::getContractName, keyword) .or().like(ContractInfo::getContractNo, keyword)); } if (status ! null) { wrapper.eq(ContractInfo::getStatus, status); } wrapper.orderByDesc(ContractInfo::getCreateTime); return contractMapper.selectPage(new Page(pageNum, pageSize), wrapper); }LambdaQueryWrapper的好处是字段名用方法引用编译期就能发现拼写错误比手写字符串强。分页拦截器要在配置类里注册否则selectPage返回的是全量数据页码参数不生效。关键字查询用and包一层避免or条件和其他eq条件优先级混乱导致查错数据。3.3 审批流的状态机实现合同审批是这套系统的业务重心。简单实现是一个状态机草稿提交后变审批中审批通过变已生效驳回退回草稿。每次状态变更要记录操作日志谁在什么时间把状态从 A 改成了 B。Transactional public void approve(Long contractId, Long approverId, boolean pass, String remark) { ContractInfo contract contractMapper.selectById(contractId); if (contract.getStatus() ! 1) { throw new BizException(当前合同不在审批中无法操作); } contract.setStatus(pass ? 2 : 0); contractMapper.updateById(contract); // 写审批日志 ApprovalLog log new ApprovalLog(); log.setContractId(contractId); log.setApproverId(approverId); log.setResult(pass ? 通过 : 驳回); log.setRemark(remark); log.setCreateTime(new Date()); approvalLogMapper.insert(log); }Transactional保证状态更新和日志写入要么都成功要么都回滚。状态判断前置防止重复审批。审批日志单独建表不要塞进合同主表否则一条合同多次审批会撑爆字段。如果业务复杂到多级审批常见做法是引入approval_flow和approval_node两张表按节点顺序流转这套免费版一般只做了单级审批二次开发时可以扩展。3.4 文件上传与合同附件管理合同附件上传要处理文件存储路径、文件名冲突、类型校验。别把文件存到项目目录里打成 jar 包后路径会变。我一般配一个绝对路径比如/data/contract/upload/在application.yml里可配。PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) throws IOException { String originalName file.getOriginalFilename(); String suffix originalName.substring(originalName.lastIndexOf(.)); // 只允许 pdf、doc、docx if (!Arrays.asList(.pdf, .doc, .docx).contains(suffix.toLowerCase())) { return Result.fail(不支持的文件类型); } String newName UUID.randomUUID().toString() suffix; File dest new File(uploadPath newName); if (!dest.getParentFile().exists()) { dest.getParentFile().mkdirs(); } file.transferTo(dest); return Result.ok(newName); }文件名用 UUID 重命名避免中文名和特殊字符导致存储失败。类型校验用后缀白名单别信前端传来的Content-Type那个可以伪造。上传目录要提前建好并给写权限Linux 下注意运行用户的权限否则报Permission denied。4. 避坑与常见问题排查那些让你加班到凌晨的坑4.1 中文乱码从数据库到页面全链路排查现象合同名称存进去是问号或者页面显示乱码。原因通常有三层数据库字符集不是utf8mb4、连接 URL 没带characterEncodingutf8、Tomcat 的URIEncoding没配。解决顺序是从底往上查先SHOW CREATE DATABASE contract_db看字符集再检查 JDBC URL最后看server.xml里 Connector 有没有加URIEncodingUTF-8。三层都对了乱码基本消失。4.2 文件上传大小超限与临时目录问题现象上传大合同报MaxUploadSizeExceededException或The temporary upload location is not valid。原因是 Spring Boot 默认上传限制 1MB且临时目录在系统重启后可能被清理。解决在application.yml里调大max-file-size和max-request-size同时显式配置spring.servlet.multipart.location指向一个存在的目录。Linux 下/tmp可能被定时清理建议指到应用自己的数据目录。4.3 MyBatis 字段映射为 null 的三种情况现象查询出来某些字段是 null但数据库里明明有值。原因一map-underscore-to-camel-case没开contract_name映射不到contractName。原因二实体类字段名和数据库列名完全对不上比如数据库叫c_name实体叫contractName需要TableField(c_name)显式指定。原因三写了自定义 XML 查询但resultMap没配全漏了字段。排查时先开 SQL 日志看查出来的列名再对照实体字段。4.4 事务不生效的典型场景现象审批方法加了Transactional但状态更新了日志没写进去。原因一方法不是public的Spring AOP 代理不生效。原因二同类内部方法直接调用绕过了代理对象。原因三异常被 catch 了没往外抛事务管理器认为没出错。解决确保方法是public、通过注入的代理对象调用、异常要么不 catch 要么 catch 后throw new RuntimeException(e)。这个坑我踩过不止一次后来养成习惯事务方法里 catch 块必须手动回滚或重新抛出。4.5 合同编号并发生成重复现象两个用户同时创建合同生成了相同的合同编号。原因是编号生成逻辑用了「查最大值加一」这种非原子操作。解决用数据库序列或 Redis 原子自增或者给合同编号字段加唯一索引插入冲突时捕获异常重试。最省事的做法是唯一索引兜底业务层做一次重试别指望应用层锁能完全防住。5. 二次开发与部署上线的几个关键动作5.1 从免费版到可交付版本的改造清单免费版能跑通流程但离交付还有距离。我一般按这个顺序改先加操作日志切面记录每个接口的调用人和参数再把硬编码的审批人改成从角色表动态查然后补数据权限销售只能看自己的合同法务能看全部最后加导出 Excel 功能领导最喜欢这个。改造时别动核心表结构用扩展表或新增字段的方式做方便后续升级。5.2 打包部署与生产环境配置差异开发用mvn spring-boot:run生产要打成可执行 jar。mvn clean package -DskipTests之后target目录下会有个几十 MB 的 jarjava -jar xxx.jar --spring.profiles.activeprod启动。生产配置和开发配置分文件application-prod.yml里数据库密码用环境变量注入别写死在文件里。日志级别从DEBUG调到INFOSQL 日志关掉不然日志文件几天就撑满磁盘。# 生产启动示例堆内存按服务器配置调整 java -Xms512m -Xmx1024m -jar contract-system.jar --spring.profiles.activeprod app.log 21 -Xms和-Xmx设成一样大避免运行期频繁扩容。 app.log 21 把标准输出和错误都重定向到日志文件并后台运行。别忘了配logback-spring.xml做日志滚动按天切割保留 30 天。5.3 验证部署是否成功的检查项部署完别只看进程在不在。先curl http://localhost:8080/login看返回码是不是 200再登录进去创建一条合同走完审批流最后检查上传附件能不能下载。数据库连接池监控如果有配 Druid看活跃连接数是否正常。这套流程走一遍基本能确认系统可用。从那以后我每次部署完都强制走一遍「登录、建合同、审批、上传、下载」五步验证少一步都可能漏掉问题。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

AI Agent 工程化实战:七个核心要素与七个决策点

AI Agent 工程化实战:七个核心要素与七个决策点

AI Agent 这个词在过去一年里被反复提及,但真正动手搭过一套能跑起来的 Agent 系统的人都知道,从"知道它是什么"到"让它稳定干活"之间隔着一整条工程化的鸿沟。我前后参与过几个 Agent 项目的落地,从最初用几十行代码拼一…

2026/10/13 12:04:00 阅读更多 →
基于JSP的玩具销售系统毕业设计:从技术选型到并发防超卖的完整实现

基于JSP的玩具销售系统毕业设计:从技术选型到并发防超卖的完整实现

简介:这是一份面向高校计算机相关专业毕业设计的完整项目源码包,主题为基于JSP的网上玩具销售系统,适合正在准备毕设或需要Java Web实战案例的学生与开发者参考。项目采用JSP结合Servlet、JavaBeans实现服务端逻辑,配套SQL Server…

2026/10/12 16:03:57 阅读更多 →
Agent开发:从API调用到工程化系统的核心架构与实战

Agent开发:从API调用到工程化系统的核心架构与实战

1. 从“接个API就完事”说起:Agent开发的真实门槛在哪里“Agent?网页接个api就万事大吉?”——这句话我第一次听到的时候,差点把嘴里的咖啡喷出来。不是因为它离谱,而是因为它太真实了。过去一年多,我见过太…

2026/10/12 16:04:01 阅读更多 →

最新新闻

C#高精度毫秒定时器:突破系统时钟限制的两种实现与工程避坑

C#高精度毫秒定时器:突破系统时钟限制的两种实现与工程避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:33:09 阅读更多 →
多标签分类破解无监督行人重识别:从聚类到训练全流程

多标签分类破解无监督行人重识别:从聚类到训练全流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:33:09 阅读更多 →
惠普DL388 G9升级E5-2600 v4 CPU:固件微码兼容性实战指南

惠普DL388 G9升级E5-2600 v4 CPU:固件微码兼容性实战指南

简介:本资源专为惠普DL388 Gen9服务器运维工程师及硬件升级技术人员设计,解决E5-2600 v3系列CPU升级至v4系列时因固件不兼容导致服务器无法点亮的核心问题。资源提供完整、开箱即用的BIOS/UEFI固件升级方案,含启动优盘制作工具、多阶段刷写脚…

2026/10/14 1:33:09 阅读更多 →
NPU与TPU硬件加速原理及推理部署实战指南

NPU与TPU硬件加速原理及推理部署实战指南

1. 从CPU到NPU:为什么我们需要专用硬件加速1.1 一个让人头疼的现实:通用处理器的天花板做过深度学习推理部署的朋友大概都有过这种体验:模型在服务器上跑得好好的,一放到边缘设备上就卡得不行。明明参数量也不算大,但帧…

2026/10/14 1:33:09 阅读更多 →
Python解析阿里云物联网透传数据:从二进制帧到物模型对齐

Python解析阿里云物联网透传数据:从二进制帧到物模型对齐

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:33:09 阅读更多 →
二维点云ICP算法原理推导与实战:从目标函数到闭式解

二维点云ICP算法原理推导与实战:从目标函数到闭式解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:32:09 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →