简介:面向高校计算机专业毕业生及Java开发学习者的企业报销管理系统完整毕业设计资源,基于Java与SQL Server 2000数据库开发,采用客户端/服务器架构,围绕个人工作、信息中心、日常工作、流转中心、维护中心五个子系统展开,适用于毕业设计参考、课程实践或企业信息化入门学习。压缩包共206个文件,约11.53MB,包含Java源程序(.java)及编译后的class文件、配套论文与答辩PPT文档、数据库数据文件(mdf/ldf)、界面图标与截图(png/gif等),结构划分清晰,便于按模块查阅,目前已有46人学习下载。资源内含可运行的完整源代码、毕业论文和PPT模板,具体实现了电话簿、总经理工作计划、电子公告、规章制度、新闻发布、资料管理、办公用品申领、公文流转及权限角色设置等功能,同时提供数据库文件与编译产物,具备Java环境即可运行。无论是毕业设计答辩准备,还是围绕报销与办公流程进行功能扩展,都能从中获得可落地的设计思路和项目基础。
1. 企业报销管理系统:Java毕设里能把业务讲完整的一块试验田
每年带毕设,我都会遇到同一个问题:图书管理系统太单薄,电商系统又烂大街,学生抱着一堆“项目源码.zip”不知道从哪下手。企业报销管理系统放在Java毕业设计的池子里,属于“看着不惊艳,内里却压着一整套业务线”的选择:一张报销单从员工填单、部门经理审批,到财务审核、打款归档,中间涉及状态流转、角色权限、金额精度、操作留痕。它有完整的数据库设计,有前后端交互,也有能在论文和答辩里讲清楚的技术点。适合的人是那些不想堆花哨框架,但想把一个系统做扎实、能上台讲明白的Java初学者和准毕业生。压缩包里的源代码、论文、PPT模板,恰好覆盖了从跑通到答辩的整条链路。
2. 把报销流程拆成状态机:角色、权限与三张核心表
2.1 报销单的生命周期:草稿、审批中、驳回、已打款的状态流转
拿到代码先别急着启动,应该先把业务边界弄清楚。企业报销系统的核心不是增删改查,而是一张报销单如何沿着状态往前走。很多毕设只做了“通过”和“不通过”两个状态,评委一问“员工填错了怎么办、审批到一半发现发票不对怎么办”,现场就卡住了。实际情况里单据需要六个状态:草稿、待部门审批、待财务审批、已打款、已驳回、已撤回。
草稿不是可有可无的。员工填单时可能还有发票没贴完、金额要再确认,需要先保存不提交,这个场景很常见。已撤回相当于给使用者的后悔药:提交之后发现报销事由写错了,只要审批人还没处理,允许员工自己撤回来改。驳回也不是简单回到最初,而是回到草稿状态,附带审批意见,让员工改完再重新走流程。已打款是终态,单子不能再被修改或撤回,只能留档备查。
状态推进是有方向性的,开发时最好先画一张状态转移表,再写代码。我常用的表格长这样:
| 当前状态 | 触发动作 | 目标状态 | 可操作角色 |
|---|---|---|---|
| 草稿 | 提交 | 待部门审批 | 报销本人 |
| 草稿 | 修改保存 | 草稿 | 报销本人 |
| 待部门审批 | 通过 | 待财务审批 | 部门经理 |
| 待部门审批 | 驳回 | 草稿 | 部门经理 |
| 待部门审批 | 撤回 | 已撤回 | 报销本人 |
| 待财务审批 | 通过 | 已打款 | 财务人员 |
| 待财务审批 | 驳回 | 草稿 | 财务人员 |
为什么不在待部门审批时直接跳到已打款?因为企业报销要分两级审,部门经理确认业务真实,财务确认票据合规和金额对不对。这个两级审批在论文里是需求分析的一部分,在答辩里也能解释清楚系统为什么不是一条线走到底。这六个状态别觉得多,Java面试里“设计一个审批状态机”是反复出现的白板题,你现在理清了,后面面试直接能用上。
2.2 行级权限与角色:员工、部门经理、财务各看各的数据
报销系统的角色权限,是整个项目里最容易讲出深度的地方。常见的设置为四类:普通员工、部门经理、财务人员、系统管理员。但要注意,权限控制不光是“谁能打开哪个页面”,更关键的是同一张报销单,不同角色能查到的数据范围不一样。
员工只能看自己提交的单子;部门经理能看本部门所有员工提交的单子,但只能审批状态为“待部门审批”的;财务人员能看全公司进入财务环节的单子;系统管理员只维护用户、部门和基础数据,不参与具体审批。这种按数据范围过滤的权限,在面试里常被叫作行级权限,和菜单权限是两回事。实现时不需要引入Shiro、Spring Security这种重型框架,后端查询条件里根据当前登录人拼上user_id或dept_id就够了。
如果你只按“是不是部门经理”控制列表页的访问,不管数据范围,那部门经理打开列表后就把全公司的单子都看光了,一张报销单的归属部门、报销人全都暴露。答辩时评委只要问一句“你的权限是到按钮级还是到数据级”,就很容易暴露设计缺陷。把行级权限写清楚,属于花小力气就能拿分的点。
2.3 建表SQL:报销主表、明细表和审批记录表为什么这么建
数据库设计直接决定后面写代码省不省事。企业报销管理系统至少要有三张表:报销单主表expense、报销明细表expense_item、审批记录表approval_record。很多人会问,为什么报销明细不直接在主表里加几个字段,比如交通费、餐费、住宿费?因为费用类型会变,今天有交通费,明天可能加一项“团建费”,每加一种费用就加字段,改表结构的成本太高。把明细拆成一张表,每种费用一行记录,扩展起来只需要加数据。
主表和明细表是一对多关系,主表里保留total_amount总额字段。这个字段看似冗余,但列表页和报表页按总额排序、统计时,不用每次SUM明细表,查询效率高很多。代价是保存明细时要在同一个事务里同步更新总额,两边对不上就是bug。建表SQL大体是下面这样:
CREATE TABLE `expense` ( `id` BIGINT NOT NULL COMMENT '主键,雪花ID', `expense_no` VARCHAR(32) NOT NULL COMMENT '报销单号', `user_id` BIGINT NOT NULL COMMENT '提交人ID', `dept_id` BIGINT NOT NULL COMMENT '提交人部门ID', `total_amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '报销总额,冗余字段', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0草稿 1待部门审批 2待财务审批 3已打款 4已驳回 5已撤回', `reason` VARCHAR(255) DEFAULT NULL COMMENT '报销事由', `version` INT NOT NULL DEFAULT 0 COMMENT '乐观锁版本号', `deleted` TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除:0未删 1已删', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_dept_id` (`dept_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报销单主表';主键推荐用雪花ID而不是自增ID。自增ID在单库里没问题,但以后做数据迁移、分库分表时容易撞车;雪花ID由MyBatis-Plus的ASSIGN_ID直接生成,插入时不用手动赋值。逻辑删除字段deleted也建议加上,审批记录需要留痕,不能物理删掉历史单据。
明细表字段就直白很多:id、expense_id、费用类型、金额、发票号、备注。金额同样用DECIMAL(10,2),绝对不用float和double。审批记录表用来存每一次操作的留痕:报销单ID、审批人ID、动作(通过/驳回/撤回)、审批意见、审批时间。这张表在论文里对应“操作可追溯”,后面做统计报表也靠它。我的习惯是,建表SQL写在db目录里,和项目源码一起提交,这样换电脑、换数据库时不用重新记表结构。
3. Spring Boot + MyBatis-Plus技术骨架:从zip解压到IDEA首次启动
3.1 为什么选Spring Boot + MyBatis-Plus而不是SSH或SSM
打开源码之前,先确认技术选型合理。企业报销管理系统可以用SSH、SSM、Spring Boot做,但现在的Java毕设主流方案已经是Spring Boot 2.x + MyBatis-Plus + Vue,理由不难理解。
Spring Boot解决了SSM项目里最磨人的配置文件问题。传统SSM要手写spring-mvc.xml、spring-mybatis.xml、web.xml,事务扫描路径、视图解析器写错一个,启动就白屏,对新手极不友好。Spring Boot把约定变成自动化,你只在application.yml里写真正和项目相关的数据源、端口、MyBatis-Plus参数就行,从解压到跑通的时间能压缩到两小时以内。MyBatis-Plus解决的则是单表CRUD的重复劳动。传统MyBatis要手写Mapper接口的XML和SQL,一个实体有十个字段,insert就要写十行;MyBatis-Plus让Mapper继承BaseMapper后,insert、updateById、selectById、selectPage直接用,单表操作不用写任何SQL。
这套组合的边界也要说清楚。如果涉及多表关联、复杂条件统计,MyBatis-Plus的LambdaQueryWrapper写起来会很难看,该写XML还是写XML。但报销系统的列表查询基本集中在“按状态查”“按部门和时间查”“按单号查”这几个维度,用Wrapper就能覆盖,没必要为了用XML而用XML。前端用Vue加Element UI,表格、表单、弹窗这些管理后台的常见组件都有现成封装,比JSP和jQuery好写得多。
3.2 解压zip到IDEA跑通:目录结构、JDK版本与Maven依赖
拿到压缩包之后,最常见的翻车点是解压到中文路径或带空格的目录里,然后IDEA把整个文件夹当成普通目录打开,代码全是红的。标准做法是:先把zip解压到纯英文路径,比如E:\projects\expense-system;打开IDEA时,通过File -> Open选中项目里的pom.xml来导入,而不是选外层文件夹。导入后等右下角Maven依赖下载完,pom.xml没有红色波浪线,再去看启动类。
Java环境变量和JDK版本也要提前统一。如果电脑上装了多个JDK,IDEA可能默认用JAVA_HOME指向的那个,而pom.xml里写的是java.version=1.8,版本不匹配就会报UnsupportedClassVersionError。处理方式是在Project Structure里把Project SDK、Language Level以及Modules里的Language level都改成一致。这一条对用Maven构建的Spring Boot项目尤其重要,我见过好几次“代码没问题,就是启动报错”的情况,最后都是JDK版本不统一惹的祸。
依赖下载慢是另一个常见问题。第一次导入时Maven要把Spring Boot、MyBatis-Plus、MySQL驱动等一堆jar包从中央仓库拉到本地,网络不好会一直卡在Dependency downloading。常见做法是修改Maven的settings.xml,配置阿里云镜像仓库。Maven 3.8以上默认禁止HTTP协议镜像,所以镜像URL要用https版本。依赖拉完的标志是IDEA右下角不再转圈,左侧目录能正常展开ExpenseApplication启动类。在这之前不要改任何业务代码,否则出了问题分不清是环境问题还是代码问题。
正常情况下,项目目录结构大概是这样的:
expense-system/ ├── pom.xml ├── src/ │ └── main/ │ ├── java/com/company/expense/ │ │ ├── ExpenseApplication.java │ │ ├── common/ # 返回结果封装、全局异常处理 │ │ ├── config/ # MyBatis-Plus分页、乐观锁拦截器 │ │ ├── controller/ # 报销单、登录、用户管理接口 │ │ ├── service/ # 业务逻辑层 │ │ ├── mapper/ # MyBatis-Plus的Mapper接口 │ │ ├── entity/ # 数据库实体类 │ │ └── dto/ # 前端请求参数封装 │ └── resources/ │ ├── application.yml # 数据源、MyBatis-Plus配置 │ ├── db/ # 建表SQL和初始化数据 │ └── mapper/ # 需要手写XML时的目录 └── frontend/ ├── package.json └── src/ ├── api/ # 前端调用后端的接口封装 ├── views/ # 登录、报销单列表、审批页面 └── components/ # 表格、表单等公共组件如果压缩包里没有frontend目录,那可能是一套后端JSP渲染的版本,目录里会多出src/main/webapp。两种方案我都带人做过,不影响后面的功能理解,只是联调方式不同。
3.3 唯一必改的配置:数据源与MyBatis-Plus参数
跑通项目前,唯一必须动的地方是application.yml里的数据源配置。数据库名称、账号、密码、时区,任何一项不对,启动都会报错。下面这份配置是典型的Spring Boot 2.x + MyBatis-Plus组合:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/expense_system?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml global-config: db-config: id-type: assign_id logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0 configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpldriver-class-name要跟着本地MySQL版本走。MySQL 8.x用com.mysql.cj.jdbc.Driver,MySQL 5.7及以下用com.mysql.jdbc.Driver。很多老项目源码里写的是后者,本地如果装的是MySQL 8,不改驱动就会启动失败。url里的serverTimezone=Asia/Shanghai也不是可有可无的,不加它,连接时会直接报时区错误。如果你打算把数据库换成SQL Server,驱动类、url前缀、方言全都要改,不要只改一个账号密码就当完了。
log-impl配置成StdOutImpl,作用是把每次执行的SQL打印在控制台。开发阶段建议全程开着,能看到MyBatis-Plus实际拼出的SQL条件和参数,排查“为什么查不到数据”这类问题很直观。把系统部署到正式环境之前再把它去掉,否则日志会刷得飞快。
4. 报销单CRUD与审批流转的落地:前端页面到后端接口的最小闭环
4.1 实体类与Mapper:MyBatis-Plus如何省掉八成CRUD
技术栈和配置都就位后,开始看实际代码。先拿实体类下手。Expense实体用@TableName注解映射表名,字段上用了MyBatis-Plus的几个关键注解:
@Data @TableName("expense") public class Expense { @TableId(type = IdType.ASSIGN_ID) private Long id; private String expenseNo; @TableField("user_id") private Long userId; private Long deptId; private BigDecimal totalAmount; private Integer status; private String reason; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @Version private Integer version; @TableLogic private Integer deleted; }这段代码里有三个注解值得在答辩时讲清楚。@TableId(type = IdType.ASSIGN_ID)让主键采用雪花算法生成,新增记录时不需要手动set id。@Version是乐观锁标记,配合后面的拦截器,更新时自动在SQL里加上version旧值条件,这是解决审批并发问题的关键。@TableLogic表示逻辑删除字段,执行deleteById时实际变成update deleted=1,历史审批记录还能查出来。
Mapper层就更简单了,继承BaseMapper之后就拥有了单表操作的全部基础方法:
@Mapper public interface ExpenseMapper extends BaseMapper<Expense> { }这里不用写任何XML和SQL语句。想一下用原生MyBatis时,一个实体类要手写多少东西:insert的字段列表、update的set部分、select的列映射。用MyBatis-Plus时这些都被BaseMapper封装好了,代码量少一半以上,而且字段和实体类天然对应。如果你需要根据实体类生成建表SQL,MyBatis-Plus的代码生成器也能输出,不过我在项目里更习惯把db目录下的建表SQL作为权威版本,生成器的东西只用来做临时参考。
4.2 Service与审批核心方法:状态校验与乐观锁
Service层是这套系统业务逻辑最密的地方,尤其是审批动作。一个常见的错误写法是:先查出来看状态对不对,然后直接update by id。这个写法在并发场景下必出问题。两个审批人同时打开同一张单,一个点通过,一个点驳回,后提交的那个人看到的是旧状态,把前面人的修改直接覆盖掉。
MyBatis-Plus的乐观锁拦截器会自动把@Version字段拼进update语句:update expense set status = ?, version = version + 1 where id = ? and version = 旧版本。更新影响行数是0,说明这张单在你读取之后已经被别人改过,直接拒绝。审批方法的核心逻辑可以这样写:
@Override @Transactional(rollbackFor = Exception.class) public void approve(Long expenseId, Long approverId, String action, String comment) { Expense expense = expenseMapper.selectById(expenseId); if (expense == null) { throw new BizException("报销单不存在或已被删除"); } Integer current = expense.getStatus(); Integer target; if ("pass".equals(action)) { if (current == STATUS_DEPT_PENDING) { target = STATUS_FINANCE_PENDING; } else if (current == STATUS_FINANCE_PENDING) { target = STATUS_PAID; } else { throw new BizException("当前状态不能执行通过操作"); } } else if ("reject".equals(action)) { target = STATUS_DRAFT; } else { throw new BizException("不支持的审批动作"); } expense.setStatus(target); int rows = expenseMapper.updateById(expense); if (rows == 0) { throw new BizException("操作冲突,数据已被其他人修改,请刷新后重试"); } ApprovalRecord record = new ApprovalRecord(); record.setExpenseId(expenseId); record.setApproverId(approverId); record.setAction(action); record.setComment(comment); approvalRecordMapper.insert(record); }几个细节值得展开。先查再改不是问题,问题在于“改的时候必须带条件”,乐观锁就是帮你自动带条件的机制。target状态的判断只允许从待部门审批走到待财务审批、从待财务审批走到已打款,其他路径全部抛异常。@Transactional保证状态更新和审批记录插入在同一个事务里,任何一个失败都一起回滚,不会出现“单子已经打款了但审批记录没存下来”的情况。
4.3 Controller与前端页面:接口约定和Element UI表格
后端接口这一层没有太多炫技空间,但接口规范要整齐。报销单相关的核心接口有三个:提交报销单、审批报销单、分页查询报销单列表。返回格式统一用一个Result 包装code、message和data。这样前端只需要处理一种返回结构,联调时不会出现这个人返回一个Map、那个人返回一串字符串的混乱局面。
Controller可以这样写:
@PostMapping("/expense/submit") public Result<Void> submit(@RequestBody ExpenseSubmitDTO dto) { expenseService.submit(dto); return Result.success(); } @PostMapping("/expense/approve") public Result<Void> approve(@RequestBody ApproveDTO dto) { expenseService.approve(dto.getExpenseId(), getCurrentUserId(), dto.getAction(), dto.getComment()); return Result.success(); } @GetMapping("/expense/page") public Result<Page<ExpenseVO>> page(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) Integer status) { return Result.success(expenseService.pageQuery(page, size, status)); }@RequestParam的defaultValue要特别说明。Element UI的表格分页组件默认从第1页开始、每页10条,和后端分页参数保持一致,否则点击第二页会出现重复数据。submit方法里,后端要负责生成expense_no,业务编号规则可以做成“BX”加年月日时分秒,形如BX20250614001,方便在搜索框里按单号查。
前端列表页用Element UI的el-table展示,状态列不要直接显示0、1这样的数字,渲染时要映射成中文标签,0对应草稿,1对应待部门审批,2对应待财务审批,3对应已打款,4对应已驳回,5对应已撤回。演示截图里显示中文状态,比显示裸数字更有说服力。核心片段如下:
<el-table :data="tableData" v-loading="loading" border> <el-table-column prop="expenseNo" label="报销单号" width="180" /> <el-table-column prop="totalAmount" label="金额" width="120" /> <el-table-column prop="statusText" label="状态" width="120" /> <el-table-column label="操作" width="200"> <template slot-scope="scope"> <el-button type="text" @click="handleDetail(scope.row)">查看</el-button> <el-button type="text" v-if="canApprove(scope.row)" @click="handleApprove(scope.row)">审批</el-button> </template> </el-table-column> </el-table>canApprove函数的作用是控制按钮显隐:只有当前登录人是部门经理或财务,且单据状态恰好处于自己能审批的那一步时,才显示审批按钮。这一层判断放在前端是为了避免用户反复点击无权限按钮,但后端同样要做校验,前端控制只是体验优化,真正拦人的是后端。
5. 从解压到答辩的避坑实录:5个常见的翻车现场与解决办法
5.1 SQL脚本导入报错或中文乱码
现象:把zip里的sql文件用记事本打开,复制到Navicat查询窗口执行,要么create table报语法错,要么表建好后中文全是问号。
原因:sql文件保存为utf8mb4编码,Windows记事本按ANSI解析,复制出来的内容已经错乱;另一种情况是MySQL服务端字符集不统一,表接到系统默认的latin1上。
解决:先重新建库并指定字符集:CREATE DATABASE expense_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci。然后通过Navicat的“运行SQL文件”功能直接选择文件导入,不要复制粘贴。导入后执行SHOW CREATE TABLE expense,确认表结构里CHARSET=utf8mb4。如果你用的是MySQL 8.0的zip解压版,初始化时同样要把my.ini里的character-set-server配置成utf8mb4,否则后面连接程序时还会出现乱码。
5.2 金额加起来差一分钱
现象:报销明细里有1.10、2.20、3.30三项,合计显示却是6.6000000000000005,或者保存后变成6.60,再次查询又变成6.599999。
原因:用double或float表示金额,浮点数在二进制里本身不精确,累加时误差就出来了。数据库字段如果也用float列,同样的误差会一路带到报表里。
解决:金额统一用BigDecimal,数据库列用DECIMAL(10,2)。构造BigDecimal时一定要传字符串,写成new BigDecimal("1.10"),不要直接传double。Service里累加金额用add方法,最终set到totalAmount时用setScale(2, RoundingMode.HALF_UP)保留两位小数。前端传来金额时如果是数字类型,也要先转成字符串再构造BigDecimal。这一条写进论文里,可以光明正大作为一个“系统健壮性处理”的亮点。
5.3 两个审批人同时操作同一张单
现象:部门经理和财务几乎同时打开一张报销单,一个点通过,一个点驳回,最后库里状态是驳回,但财务那边的页面还显示可以审批,再点一次又变成了已打款。
原因:审批逻辑没有版本控制,update语句直接set status,不关心当前状态是不是已经变了。后提交的人拿旧状态覆盖前面人的修改,形成脏写。
解决:给expense表加version字段,实体类加@Version注解,然后在MyBatis-Plus配置类里注册OptimisticLockerInnerInterceptor。这样update时自动带上version条件,影响行数为0就说明被别人动过了。如果不引入乐观锁,也可以手动写update expense set status=#{newStatus} where id=#{id} and status=#{expectStatus},影响行数为0就拒绝。两条路任选一条,不能再裸用updateById。
5.4 create_time和update_time一直是null
现象:数据库表里明明有默认值CURRENT_TIMESTAMP,但通过MyBatis-Plus插入后,查出来的create_time是null,update_time也不变。
原因:MyBatis-Plus执行insert时会把实体类里所有字段的完整值拼进SQL,如果实体类里的createTime是null,就往表里写NULL,把数据库默认值覆盖掉了。update时同样会把updateTime的null值写进去。
解决:实体类字段上配置@TableField(fill = FieldFill.INSERT)和@TableField(fill = FieldFill.INSERT_UPDATE),再写一个MetaObjectHandler的实现类,在insertFill方法里注入LocalDateTime.now()。注意用了自动填充后,数据库列本身也要保持DATETIME类型。一个容易忽略的小坑是,MetaObjectHandler要加上@Component注解交给Spring管理,不然fill配置不生效,时间字段照样为空。
5.5 IDEA启动报Lombok或依赖相关错误
现象:双击启动类,直接报Cannot resolve symbol 'SpringBootApplication',或者实体类上的@Data没生效,getter/setter在代码里标红。
原因:三个来源比较常见。一是Maven依赖没有下载完,jar包不完整;二是JDK版本和pom.xml里配置的java.version不一致;三是Lombok插件版本与IDEA版本冲突。
解决:先看本地Maven仓库里有没有对应jar包,没有就重新刷新Maven项目,并把镜像换成国内源。再检查Project Structure里Project SDK是不是pom.xml要求的版本。Lombok在新版IDEA已经内置,如果还报错,去插件市场禁用再重启,同时确认Annotation Processing没有被误关。处理完这三步,启动类能正常跨过去,就说明环境已经通了。还有一个小经验:别把项目文件夹放到桌面或网盘同步目录里跑,路径中的中文和特殊字符会让Tomcat和静态资源加载出一些奇怪问题,统一放纯英文路径能省很多事。
6. 论文和PPT怎么做:把代码亮点翻译成答辩能听懂的话
6.1 论文系统实现部分:不贴代码,画三张图
论文里最忌讳贴大段源码。评委不会一行行读你的代码,他关心的是这系统是不是你做的、遇到问题你怎么解决。系统实现这一章,三个技术点足够:状态机设计、金额精度控制、并发审批的一致性。状态机设计用第二章那张状态转移表,说明草稿、驳回、撤回这些分支是怎么来的;金额精度讲清楚为什么不用double而用BigDecimal,以及明细总额和主表冗余字段怎么保持一致;并发审批把乐观锁的update SQL原理说一遍。这三个点都是代码里真实存在的,也都接得住提问。ER图画expense、expense_item、approval_record三张表的关系就够了,别画一张二十多个表的巨图,打印出来连自己都看不清。
6.2 PPT的演示顺序:和你要讲的故事对齐
PPT模板如果直接拿来用,要小心把模板自带的内容漏删干净。建议按这个顺序组织:选题背景与研究意义、技术选型、功能模块、数据库设计、核心流程演示、系统展示、总结与展望。真正演示时,提前准备一条完整链路:用员工账号提交一条报销单,切到部门经理账号审批通过,再切到财务账号审批打款,最后回到列表页展示状态为“已打款”。这比任何口播都有说服力。
我自己带项目时吃过一次亏,学生演示现场临时填单,金额填错不说,还当着评委的面把本应通过的审批点成了驳回,只能硬着头皮说是“流程分支演示”。后来我养成了习惯:所有演示数据提前造好,截图也备一份在PPT里,万一现场页面加载不出来,就切到截图继续讲。再补一句实在话:这些状态机、乐观锁、BigDecimal的取舍,做好了不只是毕业设计,拿去当Java工程师的入门面试素材也完全够用。希望帮到你。
本文还有配套的精品资源,点击获取