SpringBoot + Vue工作流管理系统设计与毕业设计实战解析
这套 SpringBoot Vue 工作流程管理系统是我前阵子整理的一套 Java Web 毕业设计项目完整内容包括前端和后端源码、MySQL 初始化 SQL 脚本以及一份可以直接照着对接的接口文档。整体功能不花哨但把业务系统最常见的那条链路做全了发起申请、流程审批、待办已办、流程追踪、权限管理、用户管理。如果你正打算做一个 Java Web 毕设或者刚进公司就要独立上手一套前后端分离的业务系统这套项目值得照着拆一遍。下面我把项目怎么设计、数据库怎么建、前端怎么对接、有哪些坑按实际踩过的顺序一条条写清楚。1. 项目定位这套工作流程管理系统到底做了什么1.1 功能模块与适用场景这套系统说白了就是让“申请、审批”这件事在线化。比如企业里的请假、加班、报销、用印申请以前要打印纸质单子找好几个领导签字现在全部在系统里流转。我按最实用的场景把系统拆成了六大块系统管理、流程定义、申请发起、审批处理、待办已办、流程查询。系统管理管用户、角色、菜单权限流程定义管有哪些审批模板申请发起是员工填表单审批处理是上级批同意或驳回待办已办是当前用户要看什么、自己处理过什么流程查询则是看某个单子现在到了哪一步。功能模块对照表模块核心功能涉及的前端页面登录认证账号密码登录、JWT 签发、退出登录登录页系统管理用户管理、角色管理、菜单管理用户列表、角色分配流程定义流程模板配置、表单字段配置流程模板管理页申请发起选择流程、填写表单、提交发起申请页审批处理同意、驳回、审批意见填写待办列表、审批详情页流程查询查看流程进度、审批历史流程追踪页这套功能覆盖了大多数中小型系统的典型需求。做毕业设计答辩的时候评委基本会围绕“权限怎么控制”“流程状态怎么转”“数据怎么查”三个方向追问后面我会把对应实现逻辑拆开讲方便你边改边准备答辨稿。1.2 技术选型为什么是 SpringBoot Vue 而不是别的选 SpringBoot Vue不是因为它新而是因为它“平衡”。SpringBoot 把 Spring 家族那些繁琐的 XML 配置全部自动化了内置 Tomcat打一个 jar 包就能跑对毕设和中小型项目来说学习成本可控Vue 作为前端框架组件化开发、响应式数据、路由、状态管理都有成熟方案前后端通过 JSON 对接做管理后台非常顺手。这套项目里我采用的版本组合是SpringBoot 2.7.14 JDK 8 MyBatis-Plus 3.5.x MySQL 5.7/8.0前端用 Vue 3.2 Vite 4 Element Plus Pinia。有人可能问SpringBoot 都出到 3.x 了为什么不直接用最新版这个我后面讲到“版本坑”时会细说。简单讲毕设和内部项目追求的是稳定可复现教程多、组件兼容好、遇到问题能搜到答案往往比“最新”更重要。JDK 8 现在还足够跑绝大多数 Java Web 项目换 JDK 17 反而会有一堆第三方依赖适配问题。1.3 流程引擎直接用 Flowable 还是自研状态机工作流系统最关键的就是“流程怎么流转”。市面上有 Flowable、Activiti 这种重型工作流引擎功能很强能画 BPMN 流程图、支持会签、网关、定时器这些高级特性但学习成本高和业务表结构耦合也深。这套项目最终我选了自研状态机方案而不是引入 Flowable。原因有三点第一毕设重点在于把业务逻辑讲清楚自研状态机能展示你对流程设计的理解第二审批场景大多是“单线审批”用状态机完全够用第三引入 Flowable 后光引擎表就够答辩时解释半天反而分散了重点。自研状态机的核心是定义好“当前状态”和“可执行操作”。请假流程我定义了 5 种状态草稿、已提交、审批中、已通过、已驳回。操作有提交、同意、驳回。每次状态变更都写入一条审批记录相当于流水账随时能追溯是谁在什么时候批了什么意见。这套设计的扩展性也不错之后想加“组会签”或者“多级审批”只需要在状态机里增加状态和操作即可。2. 后端设计SpringBoot 核心模块与数据访问2.1 后端工程结构分清楚再动手写后端代码如果全堆在 controller 里后面改一个字段能让人崩溃。我习惯按“接口层-业务层-数据层”分层同时把公共逻辑抽出来。项目结构大致这样src/main/java/com/example/workflow ├── common // 返回结果、异常处理、常量、工具类 ├── config // 拦截器、全局配置、跨域配置 ├── controller // 接收前端请求 ├── service // 业务逻辑接口与实现 ├── mapper // MyBatis-Plus 的 Mapper 接口 ├── entity // 数据库实体类 ├── dto // 请求/响应参数封装 └── security // JWT 相关逻辑controller 里只做参数接收和简单校验service 层写业务mapper 层只写数据库操作。entity 和数据库表一一对应dto 则是把“前端传给我的”和“我要返回前端的”区分开不直接暴露数据库结构。很多新手喜欢直接把 entity 返回给前端短时间没问题但表结构调整或者要隐藏字段时就很痛苦。为什么强调这套结构因为毕设代码量和真实项目比不算大但仍然要养成这种分层习惯。答辩时如果你能说清楚“controller 为什么不写 SQL”“返回值为什么要封装 Result”印象分会明显不同。2.2 数据库表设计表结构如何支撑流程审批数据库设计决定了这套系统能走多远。我总共设计了 12 张表核心的几张如下。系统管理部分sys_user、sys_role、sys_menu、sys_user_role。用户表存账号、密码、昵称、部门 ID、状态角色表存角色编码和名称菜单表存前端路由和按钮权限标识用户角色关联表解决多对多关系。流程业务部分biz_process_definition流程定义表、biz_apply申请单主表、biz_approval_record审批记录表。流程定义表存流程编码、流程名称、当前版本、是否启用申请单主表存流程实例 ID、申请人 ID、申请类型、申请事由、表单 JSON、当前审批节点、流程状态审批记录表存审批人、审批动作、审批意见、操作时间。关键设计细节我特别提醒一句审批记录表不要只存最终结果而是要“每次动作都落一条记录”。我见过有同学只更新主表状态不记录审批历史结果被问“怎么看这个单子经历了哪些人”时直接愣住。审批历史本质上是流水审计必须有。字段类型方面状态字段用 tinyint金额字段用 decimal(10,2)时间字段用 datetime文本字段用 varchar 或 text。所有表都加 create_by、create_time、update_by、update_time、deleted 这几个通用字段deleted 是逻辑删除标记。为什么用逻辑删除而不是物理删除因为审批数据要考虑追溯删掉就没了逻辑删除只是把 deleted 置为 1查询时统一过滤既保留数据又不影响列表。关于外键这套项目所有表之间都没有建数据库物理外键关联关系全部靠 service 层编码维护。也许有老师要求建外键但从实际工程角度看物理外键会导致插入更新性能下降而且删除顺序错误时会抛异常反而麻烦。你可以在答辩时解释这一点外键约束适合强一致性要求极高的场景工作流系统更依赖代码层面的校验和补偿。2.3 MyBatis-Plus 数据访问实体类与 SQL 的对应关系数据访问这块我用的是 MyBatis-Plus。它最大的价值是帮你把单表 CRUD 写掉大半不需要为每个表都写 XML 和重复 SQL。一个实体类对应一张表实体类上通过注解声明表名、主键策略、逻辑删除字段。TableName(biz_apply) public class BizApply { TableId(type IdType.AUTO) private Long id; private String applyNo; private Long processDefinitionId; private Long applicantId; private Integer status; private String formJson; private LocalDateTime submitTime; TableLogic private Integer deleted; }实体类写清楚后Mapper 接口继承 BaseMapper 就能用内置的 selectById、insert、updateById、selectPage 等方法。复杂查询主要用 LambdaQueryWrapper 构造条件比如查当前用户待办列表LambdaQueryWrapperBizApply wrapper new LambdaQueryWrapper(); wrapper.eq(BizApply::getApplicantId, userId) .eq(BizApply::getStatus, WorkflowStatus.APPROVING.getCode()) .orderByDesc(BizApply::getSubmitTime);再说一下“根据实体类生成建表 SQL”这个问题它其实是 MyBatis-Plus 的反向操作。MyBatis-Plus 本身只支持根据数据库表生成实体类不存在官方“根据实体类生成建表 SQL”的功能。如果你的需求是先用 Java 类定义字段再生成 DDL可以借助通用建表工具比如 pdman、Chat2DB或者干脆自己写 SQL。对于本项目我是先用 SQL 脚本建表再写实体类顺序是“先库后代码”字段类型和注释都可控。2.4 审批状态机与事务控制关键代码怎么落地状态机听起来玄乎落地时就是几段普通的 service 方法。核心逻辑是把状态变更收敛到同一个事务里先插入审批记录再更新申请单状态任何一步失败都回滚保证不会出现“审批记录写了单子状态没变”这种脏数据。审批通过的核心逻辑大致如下Transactional(rollbackFor Exception.class) public boolean approve(Long applyId, Long approverId, String comment) { BizApply apply applyMapper.selectById(applyId); if (apply null) { throw new BusinessException(申请单不存在); } if (!WorkflowStatus.APPROVING.getCode().equals(apply.getStatus())) { throw new BusinessException(当前状态不允许审批); } // 更新主表状态为已通过 apply.setStatus(WorkflowStatus.APPROVED.getCode()); apply.setApproverId(approverId); apply.setAuditTime(LocalDateTime.now()); applyMapper.updateById(apply); // 插入审批记录 BizApprovalRecord record new BizApprovalRecord(); record.setApplyId(applyId); record.setApproverId(approverId); record.setAction(APPROVE); record.setComment(comment); record.setCreateTime(LocalDateTime.now()); approvalRecordMapper.insert(record); return true; }这里有个细节容易被忽略更新主表时没有乐观锁保护。并发场景下两个审批人同时点“通过”可能都读到 APPROVING然后都更新成功。解决办法是加 version 字段或者用带条件更新update biz_apply set status 已通过 where id ? and status 审批中影响行数为 0 说明被其他人抢先处理。这套项目里我采用的是带状态条件更新实现简单且不用额外字段。驳回逻辑类似但要额外判断“驳回后是回到上一级还是退回发起人”。按实际业务我设计为“驳回即终止流程”也就是状态变为已驳回申请人修改后可以重新提交。如果业务流程要求逐级退回那状态机里需要记录历史节点栈每次驳回到上一级。这个区别在答辩时最好说明白体现你对业务细节的理解。2.5 接口文档与权限设计前后端怎么约定这套项目的接口文档既是给前端开发看的也是毕设资料的一部分。接口设计上我统一了返回结构{ code: 200, message: 操作成功, data: {} }code 为 200 代表成功非 200 代表失败前端 axios 拦截器只看 code 即可。分页接口返回固定结构total、pages、records、current、size。这样前端列表页接收数据不用每种接口单独适配。系统采用 JWT 做身份认证。登录接口校验用户名密码通过后签发 token后续请求在 header 里带Authorization: Bearer token后端拦截器统一校验。用户密码存储不是明文而是使用 BCrypt 加密后的字符串。为什么不能用 MD5因为 MD5 加不加盐都容易被彩虹表撞库BCrypt 自带盐且计算成本可调是目前比较稳妥的方案。接口文档里我把这一点也单独标注了。主要接口清单大概这样接口路径方法功能权限/api/auth/loginPOST登录匿名/api/auth/logoutPOST退出登录登录用户/api/user/pageGET用户分页管理员/api/process-definition/listGET流程模板列表登录用户/api/apply/submitPOST提交申请登录用户/api/apply/todoGET待办列表登录用户/api/apply/doneGET已办列表登录用户/api/apply/approvePOST审批通过审批人/api/apply/rejectPOST审批驳回审批人/api/apply/traceGET流程追踪登录用户接口文档里除了路径和参数说明还要给出成功返回示例、失败返回示例、字段说明。这些内容前端联调时需要反复对照特别是枚举值说明比如 status 字段 1 是草稿、2 是已提交、3 是审批中、4 是已通过、5 是已驳回。我最初没在文档里写状态枚举前端自己猜把“审批中”当成“已提交”列表一直出不来后来补上枚举说明才解决。文档这种东西写得越细后面省的事越多。3. 前端实现Vue 项目搭建与页面联动3.1 从环境配置到项目初始化给新手的几条建议前端部分技术栈是 Vue 3 Vite Element Plus Pinia。先说环境很多人在第一步就卡住。Node.js 建议装 18 或 20 的 LTS 版本装完在命令行执行node -v和npm -v确认生效。npm 默认源在部分网络环境下载依赖很慢可以临时配置镜像源npm config set registry https://registry.npmmirror.com创建项目用 Vitenpm create vitelatest workflow-web -- --template vue cd workflow-web npm install npm run dev项目跑起来后目录结构主要是 src/api请求接口封装、src/router路由、src/stores全局状态、src/views页面、src/components公共组件。这里提醒一句npm install 后如果报 ERESOLVE 错误通常是依赖版本冲突可以把 node_modules 删除重新安装或检查 package.json 里是否手工改动了版本号。源码交给别人时千万不要带 node_modules体积巨大且换台电脑没意义。正确做法是保留 package.json 和 package-lock.json别人拿到后执行npm install即可。另一个常见问题是 .env 文件被 gitignore 忽略导致对方运行后没有后端接口地址我在项目里专门写了一个 .env.example 模板里面注明VITE_API_BASE_URL/api交接时复制成 .env 就能用。3.2 路由、拦截器与动态菜单Vue Router 是前端的“指路牌”。本项目不是把所有路由都写死而是登录后根据后端返回的菜单权限动态生成。登录页、404 页等公开路由直接注册业务页面路由在用户信息加载后调用router.addRoute动态加入。导航守卫是鉴权核心router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else if (token to.path /login) { next(/) } else { next() } })这只是最基础的 token 判断真正完整项目里还要校验当前用户是否有访问该路由的权限否则用户手动输入 URL 就能跳过菜单。菜单权限的校验方式后端返回的菜单列表包含唯一标识比如apply:list前端把权限按钮存到 Pinia按钮上通过自定义指令 v-permission 判断没有权限直接移除 DOM。app.directive(permission, { mounted(el, binding) { const permValue binding.value if (!authStore().permissions.includes(permValue)) { el.parentNode?.removeChild(el) } } })有些毕设只做了“登录后才能进系统”这一层没有做“登录后只能看到他该看的功能”这一层。这是权限设计完整度的分水岭建议至少把菜单级权限做上按钮权限能加更好。3.3 审批核心页面发起申请与审批流转前端页面里其实最考验细节的是发起申请和审批详情。发起申请页面我按流程模板动态渲染表单。比如请假流程表单字段是“请假类型、开始时间、结束时间、请假事由”用印申请是“用印类型、文件名称、用印份数”。因为不同流程字段不同所以流程定义表里存的是 JSON Schema前端拿到后动态生成表单控件。这个做法比每种流程单独写死一个页面灵活得多后续加流程只需在后台配置不用改前端代码。审批详情页我用了时间线组件把审批记录按时间倒序展示。每一条记录显示审批人姓名、动作、意见、时间。这个页面同时承担流程追踪功能会读取当前申请单状态、当前处理人、各节点时间。做这个页面时注意后端返回的审批记录一定要按时间排序最好再带上状态变更前后的值这样前端能画出一条完整的流转线。与后端联调时最容易出问题的是日期格式。Java 后端返回的 LocalDateTime 默认是2024-05-18T10:30:00和前端 Element Plus 组件期望的格式可能不一致。我在项目里统一配置了 Jackson 序列化格式为yyyy-MM-dd HH:mm:ss前端显示就不需要额外转换。3.4 前端对接后端接口跨域与封装开发环境下前端地址是localhost:5173后端是localhost:8080直接请求会跨域。我不建议在前端代码里写死http://localhost:8080因为部署后又要改一遍。正确做法是开发环境用 Vite 代理// vite.config.js server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端代码里请求/api/xxx即可。生产环境则把打包后的 dist 文件放到 Nginx 下再由 Nginx 把/api反向代理到 Java 服务前后端地址一致不存在跨域。axios 请求封装方面我在 src/api/request.js 里统一做了三件事请求前从 localStorage 取 token 放到 header响应后判断 code不是 200 时弹出 messagetoken 失效比如 code 为 401时跳转登录页并清空本地信息。这个封装做完页面里就只写接口地址和参数不用每个页面重复处理错误代码清爽很多。4. SQL 脚本、初始化数据与部署细节4.1 能直接跑的 SQL 脚本是怎么组织的做毕设资料包时SQL 脚本是很多人最容易忽略又最容易出问题的一部分。我提供的是单独一个workflow.sql要求执行前数据库为空或全新 MySQL。脚本开头包了建库语句和字符集设置CREATE DATABASE IF NOT EXISTS workflow DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE workflow;字符集用 utf8mb4 是为了兼容用户填写的内容里可能有特殊符号和 emoji如果用旧版 utf8 存不进去。建表语句里每个字段都有 COMMENT因为数据库注释不仅是给人看的也是生成接口文档字段说明的重要来源。初始化数据除了默认管理员账号还预置了两条流程模板和几个测试账号方便演示时直接登录操作。这里回应一个很多人搜索过的问题怎么根据 Java 实体类自动生成建表 SQL如果你已经在代码里写了实体类想反推建表 SQLMyBatis-Plus 没有现成官方命令可以用以下方式解决一是用数据库设计工具比如 pdman、Navicat 的模型转换手工建模后导出二是先用项目跑一次自动建表。MyBatis-Plus 对ddl-auto的支持有限不通用。更可靠的方式是先设计表结构再生成实体类因为数据库才是数据模型的最终载体。本项目核心表执行顺序按此顺序初始化避免逻辑依赖问题sys_user、sys_role、sys_menu、sys_user_rolebiz_process_definitionbiz_applybiz_approval_record账号和菜单属于基础数据先建流程定义是业务主数据申请单依赖流程定义和用户审批记录依赖申请单。4.2 索引设计与慢 SQL 优化实录系统功能不复杂但数据量上来后我就开始遇到列表查询变慢的问题。比如待办列表一开始查询条件是applicant_id或approver_id加status但当时只在主键上建了索引全表扫描几千条数据时还能忍到几万条时接口耗时明显增加。解决方案是给高频查询字段建联合索引。我在 biz_apply 表上加了ALTER TABLE biz_apply ADD INDEX idx_applicant_status (applicant_id, status); ALTER TABLE biz_apply ADD INDEX idx_approver_status (approver_id, status); ALTER TABLE biz_apply ADD INDEX idx_create_time (create_time);联合索引为什么有用因为 MySQL 索引遵循“最左前缀”原则这两个查询条件分别是“申请人状态”和“审批人状态”建这两个联合索引后索引命中的概率很高覆盖了按申请人和按审批人查询的场景。如果运行中还是慢要先定位。MySQL 开启慢查询日志的通用操作是SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1;大于 1 秒的 SQL 会被记录到日志文件再配合 EXPLAIN 看执行计划EXPLAIN SELECT id, apply_no FROM biz_apply WHERE applicant_id 1 AND status 3 ORDER BY create_time DESC;看 key 字段是否用到了 idx_applicant_statusrows 扫描行数是否很低。如果发现 type 是 ALL说明没走索引需要检查字段类型是否匹配、索引是否建错。另外还要注意查询申请列表时前端表格只展示前 20 条但后端如果select *会把整行的 text 字段也查出来浪费内存和流量。我在列表页 SQL 里只查必要的字段详情页再查全部字段。很多慢查询没那么玄就是一次查太多东西导致的。4.3 前后端打包部署从代码到可访问部署这套项目我总结成三步后端打包、前端打包、Nginx 反向代理。后端在项目根目录执行mvn clean package -DskipTests打完包后在 target 目录会生成workflow-server.jar上传到服务器用下面命令启动nohup java -jar workflow-server.jar --spring.profiles.activeprod logs/workflow.log 21 生产环境配置我单独放在application-prod.yml里数据库地址、密码、日志级别都从开发环境剥离避免开发配置泄漏。数据库如果是远程的记得修改密码不要直接沿用初始化脚本里的弱密码。前端执行npm run build生成的 dist 目录上传到服务器某个静态目录比如/opt/workflow-web。Nginx 配置主要做两部分一是把/指向 dist二是把/api反向代理到http://127.0.0.1:8080。核心配置片段如下server { listen 80; server_name your-domain.com; location / { root /opt/workflow-web; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里try_files $uri $uri/ /index.html;必须有否则 Vue Router 使用 history 模式时刷新页面会 404。这个问题我第一次部署时踩过页面不刷新没事一按 F5 就白屏排查半天才发现是 Nginx 没配 try_files。如果你的系统用了 hash 路由改成 hash 也可以规避但从用户体验和 URL 美观度上讲history try_files 是更常见的选择。5. 常见问题排查与避坑实录5.1 SpringBoot 版本太高引发的连锁问题现在很多同学下载 SpringBoot 项目时喜欢选最新版结果照着老教程用第一步就报错。SpringBoot 3.x 包名从javax.*换成了jakarta.*JDK 最低要求 17很多旧版本的 MyBatis-Plus、PageHelper、Shiro 都不兼容。常见报错像ClassNotFoundException: javax.servlet.Filter其实不一定是缺依赖是包名整个变了。如果只做毕设我真心建议固定用 SpringBoot 2.7.x JDK 8。不是说新版本不好而是教程生态、组件兼容性、学校环境都更偏向这套组合。选择技术栈的第一原则是“让你能把精力放在业务逻辑上”而不是跟框架版本打架。当然如果导师明确要求用 SpringBoot 3也可以做但依赖版本要及时更新MyBatis-Plus 用 3.5.3JWT 库选择兼容 jakarta 的拦截器注册从WebMvcConfigurerAdapter换成WebMvcConfigurer同时确认 JDK 环境是 17 而非 8。5.2 Vue 项目源码交接时的常见坑很多同学把源码通过压缩包发给别人结果对方跑不起来八成是下面几个原因。第一node_modules 目录没删或删不干净。完整源码一般不放 node_modules而是提供 package-lock.json。接收方没有锁文件npm 会重新解析依赖不同时间的版本差异会导致无法预料的报错。第二环境变量文件被忽略。.env 文件经常在 .gitignore 里压缩时要注意手动带上 .env.example或干脆在 README 里写明VITE_API_BASE_URL要配成什么。第三TS 项目里常见的报错Failed to load tsconfig vue/tsconfig/tsconfig.web.json。这通常是 tsconfig 文件里extends引用了vue/tsconfig这个包但接收方没有安装解决办法是执行npm install -D vue/tsconfig或者检查 node_modules 里是否存在该包。如果项目本身没有用 TypeScript直接新建一个 tsconfig.json 或者删除相关 extends 配置也可以。第四前端构建后接口地址没改。部署后页面能打开但登录失败十有八九是前端请求地址还指向后端调试地址或留空。用 Vite 代理的生产环境必须由 Nginx 代理否则请求会落到前端开发服务器上。5.3 SQL 注入与查询安全防护再来聊一个面试和答辩都爱问的点SQL 注入。工作流系统里用户输入最多的地方是查询条件、审批意见、表单 JSON。如果查询条件里的值直接拼接进 SQL攻击者输入 or 11 --这种内容时查询条件就会被绕过甚至可以通过 union 查询拖出用户表。网上流传的万能密码绕过的本质就是利用字符串拼接把密码校验变成永真条件。MyBatis-Plus 的 QueryWrapper 默认是参数化查询用#{value}传给预编译 SQL这是安全的。真正要命的是在 XML 或注解里使用${value}比如ORDER BY ${sortField}因为排序字段无法预编译容易被注入。如果确实要支持动态排序正确做法是让前端传白名单字段名后端枚举校验后再拼进去。比如前端只能传create_time、submit_time两个字段名其他一律拒绝而不是把参数原样拼进 SQL。另外审批意见这种文本字段保存前也要做长度校验和内容过滤避免存储型 XSS。前端展示审批意见时用 Vue 的插值表达式而不是 v-html后者会把 HTML 直接渲染成页面如果意见里带了脚本就有风险。这些在接口文档里都值得写明能展示出你的安全意识。5.4 接口文档更新跟不上代码怎么办接口文档最大的敌人不是没写而是“写完就过期”。我在项目开发过程中经常改字段比如把分页参数从page改成pageNum如果文档不同步前端对接完才发现接口实际返回的pages和文档里写的totalPage对不上。现在的做法是用接口管理工具维护合集中的接口比如 Apifox 或 Postman把每个接口的请求示例、响应示例、枚举说明都维护在工具里再配合 Mock 数据。后端代码更新后直接同步更新工具里的文档前端根据最新的集合联调。要是项目允许还可以在代码里集成 knife4j启动后自动生成在线接口文档缺点是 UI 自定义程度有限但作为毕设和内部联调完全够用。给接口文档加一个“版本”字段也很有用。每次接口变更在文档里标注变更时间和变更内容前端联调时一眼能看出自己看的是不是最新版避免两个人争论“我之前看的不是这样”。最后分享一个小技巧也是我这次整理源码时印象最深的体会接口文档里写清楚“分页返回结构”这一条就能避免很多前后端扯皮。MyBatis-Plus 分页对象默认字段名是 records但很多前端习惯用 list。后端要么保持 records 并在文档里说明要么在后端再封装一层统一字段。遇到这种命名差异不要觉得是小事等到联调时再改返工成本是写文档时的好几倍。把这套项目跑通的关键其实不在于某个框架用得多熟而在于这些细节有没有提前想清楚。

相关新闻

Vue组件通信全指南:从props到Pinia的实战选型

Vue组件通信全指南:从props到Pinia的实战选型

Vue组件通信这个话题,我几乎每次面试都会问,每次带新人也会讲。问一圈下来,大多数人都能把props和$emit背出来,但真到了项目里,什么样的数据该走 props,什么样的状态扔进 Pinia,兄弟之间偶尔传个…

2026/10/9 9:20:18 阅读更多 →
从URL编码到HTTPS证书链:网络通信安全层层递进

从URL编码到HTTPS证书链:网络通信安全层层递进

移动端日志里经常能看到这么一串东西:urlhttps%3a%2f%2fdev.coc.1008...,后面跟着一堆%加十六进制数字。不懂的人把它当乱码,懂的人知道这是一段被编码过的 URL。而这串字符背后,其实是整个网络通信安全体系的第一道入口。这篇文章…

2026/10/9 9:20:18 阅读更多 →
MacBook到底要不要关机?睡眠与关机的正确使用姿势

MacBook到底要不要关机?睡眠与关机的正确使用姿势

MacBook要不要关机、多久关一次机比较好?这个问题我几乎每隔几天就能在社区里看到一次,问的人从刚入坑的学生到用了五六年的老用户都有。有趣的是,答案永远两极分化:一边说"合盖就走,从不管关机"&#xff0c…

2026/10/9 9:20:18 阅读更多 →

最新新闻

等保2.0数据库测评通关指南:MySQL/Oracle/SQL Server/PostgreSQL/Redis五类数据库加固与自查

等保2.0数据库测评通关指南:MySQL/Oracle/SQL Server/PostgreSQL/Redis五类数据库加固与自查

简介:这份作业指导书面向数据库安全测评人员、等保合规工程师及运维人员,系统梳理了MySQL、Oracle、SQL Server、Postgres、Redis五类主流数据库在等保测评中的实操要点,帮助读者快速定位各数据库的测评项与查询方法。资源包内含1个docx文档&…

2026/10/9 11:09:59 阅读更多 →
基于LoRA微调的中文医疗问答机器人实战:从数据构造到量化部署

基于LoRA微调的中文医疗问答机器人实战:从数据构造到量化部署

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

2026/10/9 11:09:59 阅读更多 →
EtherCAT与FSoE协议栈深度解析:从报文结构到安全配置实战

EtherCAT与FSoE协议栈深度解析:从报文结构到安全配置实战

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

2026/10/9 11:09:59 阅读更多 →
pstack-claude:命令行级本地化Claude集成方案

pstack-claude:命令行级本地化Claude集成方案

1. 项目概述:pstack-claude 是什么,它解决的是哪类真实开发痛点?pstack-claude 这个名字乍看像一个工具组合词,但拆开来看,“pstack”是 Linux 系统中一个真实存在的诊断命令,用于打印指定进程的调用栈&…

2026/10/9 11:09:59 阅读更多 →
pstack诊断Claude工具卡死:从调用栈定位Node.js阻塞问题

pstack诊断Claude工具卡死:从调用栈定位Node.js阻塞问题

1. “pstack-claude”不是工具名&#xff0c;而是调试现场的命名习惯你搜“pstack-claude”&#xff0c;大概率是在终端里敲下pstack <pid>后&#xff0c;突然发现进程名里带claude字样——比如claude-code-server、claude-desktop或某个本地部署的codex服务进程。这时候…

2026/10/9 11:09:59 阅读更多 →
力扣模拟题刷题指南:从拆解思路到经典题单与面试策略

力扣模拟题刷题指南:从拆解思路到经典题单与面试策略

做力扣模拟题&#xff0c;最容易被低估&#xff0c;也最容易翻车。我刷了三百多道题之后回头看&#xff0c;真正在面试现场把我救下来的&#xff0c;往往不是那些需要灵光一现的DP难题&#xff0c;而是老老实实按题目要求一步步模拟的“体力活”。今天这篇就把模拟题这件事聊透…

2026/10/9 11:08:57 阅读更多 →

日新闻

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API实战:LocalDate、Date与ZonedDateTime的转换与避坑指南

Java时间API这个话题&#xff0c;隔三差五就会在群里被翻出来讨论一次。上周还有个同事线上处理一个订单超时问题&#xff0c;排查到最后发现是ZonedDateTime序列化后时区丢了&#xff0c;用户在下单当天晚上看到的时间整整差了8个小时。这类问题几乎每个做Java开发的人都遇到过…

2026/10/9 0:00:49 阅读更多 →
EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

EasyTier实践:从NAT穿透到子网代理的异地组网部署与排错

前几个月我手头有好几台机器需要互相访问&#xff1a;办公室台式机、家里 NAS、还有一台云主机。如果只是偶尔传个文件倒还好&#xff0c;问题是工作场景经常要在几处环境之间来回切换&#xff0c;每次都先登录跳板机再层层代理&#xff0c;实在折腾。我先后试过端口映射、自建…

2026/10/9 0:00:49 阅读更多 →
AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent工程实战:从七要素到七个决策点的系统设计指南

AI Agent 这个词在过去一年里被反复提及&#xff0c;但真正动手搭过一套能跑起来的 Agent 系统的人都知道&#xff0c;从"知道它是什么"到"让它稳定干活"之间隔着一整套工程决策。我前后参与过几个 Agent 项目的落地&#xff0c;从最初用现成框架拼装&…

2026/10/9 0:01:50 阅读更多 →

周新闻

KT148A语音芯片外挂8002D功放的工程实践指南

KT148A语音芯片外挂8002D功放的工程实践指南

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

2026/10/8 15:26:32 阅读更多 →
LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

LLC谐振变换器增益公式推导:从FHA等效到完整归一化表达式

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

2026/10/8 15:26:40 阅读更多 →
ARM架构深度解析:从RISC设计理念到交叉编译实战

ARM架构深度解析:从RISC设计理念到交叉编译实战

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

2026/10/9 10:11:06 阅读更多 →

月新闻

我发现了一个新思路:用 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/8 21:13:17 阅读更多 →
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/8 15:26:17 阅读更多 →
黑夜航拍船只数据集训练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/9 6:17:20 阅读更多 →