ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java毕设实战:SSM框架构建个人任务管理系统APP全解析

Java毕设实战:SSM框架构建个人任务管理系统APP全解析 做Java方向毕业设计“个人任务管理系统APP”这个题第一眼看上去不起眼但我反而觉得它是性价比很高的一类题目业务贴近真实生活场景接口颗粒度恰好适合练手还能把SSM框架和移动端整合完整地展示出来。这套系统说白了就是围绕“任务”这条主线用Java写一套个人任务管理服务把任务的创建、查询、状态流转、提醒、统计这一整条链路打通再让APP端去消费这些后端接口。适合正在准备毕设、想自己动手走一遍后端服务开发流程的同学参考也适合想快速搭建一个带APP端的完整项目用于面试展示的开发者。我接触过不少做类似题目的学生很多人一开始会把精力放在“功能多不多”上实际上答辩和面试更看重的是你对整条链路的理解为什么这么设计状态、为什么用这个框架、任务状态流转时怎么保证数据一致。下面把这类项目的完整设计思路、核心实现和踩坑记录写出来照着这个思路做至少能让你少走两周弯路。1. 项目整体设计与思路拆解1.1 毕设选SSM而不是Spring Boot其实是为了“看得懂”这两年大家起步基本都是Spring BootSSM框架乍一看像是“老古董”。但毕设这个场景里选SSM反而有它的优势。Spring Boot把自动配置、内嵌Tomcat、依赖管理都封装好了你写的时候很爽但答辩时老师问一句“Spring MVC的核心流程是什么”“Spring容器是怎么管理Bean的”如果平时没碰过XML配置源码就容易答不上来。SSM是Spring Spring MVC MyBatis三件套这三个组件各管一段Spring管理Bean的创建和依赖注入Spring MVC负责HTTP请求的接收、参数绑定和响应返回MyBatis负责数据库的增删改查。因为一切都是显式配置的你能直观看到请求从DispatcherServlet进来之后经过了哪些组件SQL是怎么和Mapper接口绑定的。这些东西搞明白了再回头看Spring Boot就是一层“约定大于配置”的封装而已。还有一点是现实层面的考量很多学校毕设要求里明确写了“基于SSM框架”或者说项目里要体现出传统三层架构。我见过一些学生拿着Spring Boot项目去答辩老师直接问“你的控制层、业务层、持久层边界在哪”虽然Spring Boot也有三层但很多新手会把业务逻辑全部堆在Controller里导致分层不清晰。SSM因为配置繁琐反而逼着你把包结构分清楚这种训练对后续工作帮助很大。1.2 功能模块规划任务“全流程”到底指什么标题里有个词值得琢磨——“全流程”。个人任务管理系统不是简单的增删改查它要覆盖一个任务从产生到完结的完整生命周期。我在设计时把功能拆成了六个模块每个模块承担一个阶段的任务模块核心功能解决的问题用户模块注册、登录、密码加密、个人信息维护每个用户管理自己的任务数据隔离任务模块新建、编辑、删除、查询任务任务基本CRUD操作状态流转待开始、进行中、已完成、已取消、已归档任务生命周期的核心控制分类标签任务分类、标签关联、按分类筛选任务组织维度方便检索提醒通知截止日期提醒、状态变更通知任务“管理”的闭环否则和记事本没区别统计报表完成任务数、每日新增、分类占比让用户感知效率提升系统价值这里我要强调的是任务模块本身并不难写难点全在“状态流转”和“提醒通知”这两个设计上。状态流转是业务的核心约束提醒通知是整个系统从“记录工具”升级为“管理工具”的关键。一个个人任务管理系统如果只是能存数据、查数据那它和Excel表格没什么区别但加上“这个任务明天截止”“这个任务已经逾期”——它才真正帮用户管理了任务。1.3 整体架构分层与APP端技术选型系统采用前后端分离架构后端基于SSM构建RESTful APIAPP端通过HTTP接口消费数据。整体分层如下表现层Controller接收HTTP请求参数校验调用业务层返回统一格式的JSON业务层Service处理核心业务逻辑事务管理状态流转控制持久层Mapper/DAOMyBatis封装数据库操作动态SQL处理复杂查询关于APP端很多人的第一反应是“要用Android原生写”实际上毕设时间有限我更推荐用UniApp或H5打包的方式来做的原因有三点第一UniApp可以同时输出Android和iOS的安装包演示时拿手机装个APK就行第二前端代码用Vue写比原生Android简单得多能把精力留到后端业务上第三接口联调方便H5页面可以直接在浏览器里调试不用每次打包安装。当然如果你的题目明确要求“原生APP实现”那就用Android Studio Retrofit来对接后端接口核心逻辑是一样的只是网络请求和页面渲染的写法不同。2. 核心功能拆解与数据库设计2.1 任务状态机的设计是这类系统的灵魂个人任务管理系统最容易翻车的地方就是任务状态设计得太随意。我看到过很多项目直接把状态设计成前端的一个字段想改成什么就改成什么结果用户把“已完成”的任务又能改成“待开始”整个数据就没法看了。正确的做法是把任务状态当成一个有约束的状态机。我在项目中定义了五个状态分别是待开始0、进行中1、已完成2、已取消3、已归档4。每个状态之间的流转是有规律的当前状态允许流转到的状态操作场景待开始进行中、已取消、已归档用户开始做任务/放弃任务进行中已完成、待开始、已取消完成任务/回退到待开始/放弃已完成进行中、已归档可重新打开任务/归档已取消待开始、已归档重新激活任务/确认取消已归档已完成、已取消取消归档状态机的好处是你把业务规则固化在代码里而不是靠前端页面上那个下拉框来“自觉”遵守。用户无论如何点击后端都能保证状态变更合法。这个思想我强烈建议在答辩时着重讲它展示了你对业务约束的理解比多说十个CRUD接口都管用。另外我还加了一个字段task_status_time用来记录状态变更时间每次流转时更新这样出了问题时能追踪操作路径。2.2 数据表设计五张核心表就够了数据库设计要克制不要上来就搞十几张表很多表其实是凑数的。我这套系统的核心表只有五张用户表、分类表、任务表、标签表、任务标签关联表。下面是我实际用的表结构字段命名和类型都是可以直接落地的。用户表(user)字段类型说明idBIGINT PK AUTO_INCREMENT用户IDusernameVARCHAR(50) UNIQUE登录名passwordVARCHAR(100)加密后的密码nicknameVARCHAR(50)昵称avatarVARCHAR(255)头像URLcreate_timeDATETIME注册时间任务表(task)字段类型说明idBIGINT PK AUTO_INCREMENT任务IDuser_idBIGINT所属用户索引category_idBIGINT所属分类titleVARCHAR(100)任务标题contentTEXT任务详情statusTINYINT状态0待开始 1进行中 2已完成 3已取消 4已归档priorityTINYINT优先级0低 1中 2高due_timeDATETIME截止时间completed_timeDATETIME实际完成时间create_timeDATETIME创建时间update_timeDATETIME更新时间这里有两个设计细节我要单独说一下。一个是user_id必须加索引因为所有查询都是按用户隔离的另一个是due_time我强烈建议用DATETIME而不是时间戳因为MySQL的DATETIME在展示和写入时都直观配合Jackson统一格式化前后端处理都方便。任务表里这个priority字段虽然简单但在提醒逻辑里有用——高优先级任务提前两天提醒普通任务提前一天提醒。分类表(category)和标签表(tag)两表结构类似id、user_id、name。分类是一对多的关系一个任务属于一个分类标签是多对多的关系通过task_tag关联表维护。分类主要用于任务列表的分组统计标签则更适合做跨分类检索比如给任务打上“学习”“工作”“生活”的标签。注意用户ID的隔离每个用户只能看到自己的分类和标签这个条件一定要在SQL里带上否则就是越权查询。2.3 接口规划前后端先谈好契约再做RESTful接口设计这块我的习惯是先定好接口文档再做代码否则APP端写完页面才发现后端字段对不上联调时哭都来不及。这套系统我把接口分成四组接口路径方法功能请求参数响应说明/api/user/registerPOST用户注册username, password, nickname注册成功后返回用户ID/api/user/loginPOST用户登录username, password返回用户信息和token/api/task/listGET任务列表status, keyword, page, size返回分页数据/api/task/detail/{id}GET任务详情路径参数id返回任务完整信息/api/task/addPOST新建任务任务对象JSON返回创建结果/api/task/updatePOST更新任务任务对象JSON返回更新结果/api/task/changeStatusPOST状态流转id, targetStatus返回流转结果/api/task/delete/{id}DELETE删除任务路径参数id返回删除结果/api/category/listGET分类列表无返回当前用户全部分类/api/stats/indexGET统计数据type, dateRange返回统计结果统一响应格式这事很多新手会忽略导致APP端每个接口都要单独解析。我在项目里定义了一个Result类所有接口都返回这样的结构{ code: 200, message: success, data: { } }code为200表示成功非200表示业务异常message带回原因data是真正的数据。APP端只需要判断一次code即可。这个类代码很简单就是三个字段加上成功和失败的静态工厂方法但它的作用非常大——有了统一格式前端就可以封装一个通用的请求拦截器统一处理错误提示和登录失效跳转。3. 实操过程与核心环节实现3.1 SSM框架整合配置手把手过一遍SSM整合的难点不在代码而在配置文件的配合。很多人一上来就写代码结果Tomcat启动就报错查半天发现是某个XML写错了。我按照实际搭建顺序把关键配置拆出来讲。**第一步pom.xml引入依赖。**核心依赖是spring-webmvc、mybatis、mybatis-spring、mysql-connector-java、druid、jackson-databind。需要注意版本兼容问题我实测过spring 5.2.x搭配mybatis-spring 2.0.x是稳定的不要再往上乱升版本。**第二步web.xml配置前端控制器和编码过滤器。**DispatcherServlet拦截所有以/api开头的请求同时记得把前端静态资源排除否则会拦截到页面资源导致404servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern/api/*/url-pattern /servlet-mapping这里有个容易被忽略的点url-pattern我用的是/api/*而不是/这样做的目的很直接——APP端的请求全部走/api前缀而那些后台管理的JSP页面或者静态资源则不受DispatcherServlet干扰两种请求方式的边界非常清晰。**第三步spring-mvc.xml开启注解驱动和包扫描。**开启了mvc:annotation-driven之后Controller、RequestMapping这些注解才能生效包扫描范围要精确到controller层。同时配置Jackson的消息转换器把LocalDateTime序列化成yyyy-MM-dd HH:mm:ss格式这一步在前后端联调时极其重要默认的Jackson会把LocalDateTime序列化成数组APP端解析会非常痛苦。**第四步spring-context.xml配置数据源和MyBatis。**数据源我用Druid连接池配置了初始化连接数和最大连接数MyBatis这边最重要的是mapper-locations指向XML文件目录同时开启驼峰映射map-underscore-to-camel-casetrue这样数据库的create_time就能自动映射到Java的createTime字段不用每个字段都手动写resultMap。**第五步事务管理。**在spring-context.xml里配置DataSourceTransactionManager并且在service层使用Transactional注解。个人任务系统的事务需求不算复杂但“新建任务并更新分类统计”这种操作如果不用事务中间出错会导致数据不一致。3.2 任务模块的核心代码状态流转如何在Service层实现任务模块的后端实现我不打算把所有CRUD贴出来那些代码照着MyBatis的模板写就行。真正有含金量的是状态流转和动态查询这两个点。状态流转的核心逻辑在Service层。我在设计TaskService时专门写了一个changeStatus方法它接收任务ID和目标状态先查询出任务的当前状态再从状态机配置里判断是否允许流转Override Transactional public ResultVoid changeStatus(Long taskId, Integer targetStatus) { Task task taskMapper.selectById(taskId); if (task null) { return Result.error(任务不存在); } Integer currentStatus task.getStatus(); SetInteger allowedTargets STATUS_TRANSITIONS.get(currentStatus); if (allowedTargets null || !allowedTargets.contains(targetStatus)) { return Result.error(当前状态不允许流转到目标状态); } task.setStatus(targetStatus); if (targetStatus TaskStatus.COMPLETED) { task.setCompletedTime(new Date()); } task.setStatusTime(new Date()); task.setUpdateTime(new Date()); taskMapper.updateById(task); return Result.success(null); }这里的核心点在于STATUS_TRANSITIONS这个Map它就是状态机的代码化表达private static final MapInteger, SetInteger STATUS_TRANSITIONS new HashMap(); static { STATUS_TRANSITIONS.put(TaskStatus.TODO, new HashSet(Arrays.asList( TaskStatus.IN_PROGRESS, TaskStatus.CANCELLED, TaskStatus.ARCHIVED))); STATUS_TRANSITIONS.put(TaskStatus.IN_PROGRESS, new HashSet(Arrays.asList( TaskStatus.COMPLETED, TaskStatus.TODO, TaskStatus.CANCELLED))); STATUS_TRANSITIONS.put(TaskStatus.COMPLETED, new HashSet(Arrays.asList( TaskStatus.IN_PROGRESS, TaskStatus.ARCHIVED))); STATUS_TRANSITIONS.put(TaskStatus.CANCELLED, new HashSet(Arrays.asList( TaskStatus.TODO, TaskStatus.ARCHIVED))); STATUS_TRANSITIONS.put(TaskStatus.ARCHIVED, new HashSet(Arrays.asList( TaskStatus.COMPLETED, TaskStatus.CANCELLED))); }这个写法的好处是把业务规则集中管理后续如果要扩展“待开始”可以直接跳到“已完成”只需要改这个Map业务代码完全不用动。另外Transactional在这里也很关键更新状态和更新时间两步必须保证原子性否则状态改了、时间没更新或者系统异常导致状态丢失都会让用户数据错乱。动态查询这块用的是MyBatis的动态SQL。任务列表需要支持按状态过滤、按关键字搜索、按时间段查询、分页如果每个条件写一个方法组合爆炸。我在Mapper XML里用了where配合if标签select idselectTaskList resultTypecom.example.entity.Task SELECT * FROM task where if testuserId ! null AND user_id #{userId} /if if teststatus ! null AND status #{status} /if if testkeyword ! null and keyword ! AND (title LIKE CONCAT(%, #{keyword}, %) OR content LIKE CONCAT(%, #{keyword}, %)) /if if teststartDate ! null AND create_time gt; #{startDate} /if if testendDate ! null AND create_time lt; #{endDate} /if /where ORDER BY create_time DESC /select注意里面那个gt;和lt;是因为XML格式限制直接写会报错这个细节让不少人查了半天。动态SQL的威力就是用户不选择的条件自动不拼接前端只管传条件参数组合查询的全部逻辑由MyBatis完成。3.3 APP端对接后端接口联调的正确姿势APP端我以UniApp为例讲下对接思路。先封装一个公共的request工具统一处理BaseURL、请求头、token和响应拦截。这里BaseURL要特别注意Android模拟器访问本机后端时不能用localhost得用10.0.2.2真机调试时得用电脑的局域网IP比如http://192.168.1.100:8080。// api/request.js const BASE_URL http://10.0.2.2:8080/api; export function request(url, method GET, data {}) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL url, method, data, header: { Content-Type: application/json, token: uni.getStorageSync(token) }, success: (res) { if (res.data.code 200) { resolve(res.data.data); } else if (res.data.code 401) { uni.navigateTo({ url: /pages/login/login }); } else { uni.showToast({ title: res.data.message, icon: none }); reject(res.data); } }, fail: (err) { uni.showToast({ title: 网络连接失败, icon: none }); reject(err); } }); }); }响应拦截里判断code是不是401是为了处理“token过期”的情况。这个问题我在测试时经常遇到用户登录后token有效期为24小时过期后任何接口都返回401APP端如果不在拦截层统一处理用户会看到一堆莫名其妙的报错。后端对应要提供一个登录拦截器Interceptor在preHandle里校验token如果token不存在或已过期直接返回401状态码。这里建议用拦截器而不是Filter因为拦截器可以拿到Spring容器里的Bean方便做token的数据库验证。拦截器配置里要排除注册、登录、静态资源这三个路径否则用户还没登录就被拦在外面了。任务列表页面的渲染逻辑其实很简单页面加载时调用task/list接口拿到数据后用v-for循环渲染卡片。但这里有一个体验细节下拉刷新和上拉加载要配合分页参数page和size。我在实践中的做法是page从1开始每次请求成功后page加1触底时再请求下一页。后端的分页返回值要包含total字段这样前端能算出还有没有更多数据避免“没有下一页了还一直请求”的尴尬。3.4 提醒通知和统计报表让系统从“记账本”变成“管理器”提醒功能是所有个人任务管理系统里最容易被做成“摆设”的模块。很多人只是在数据库里加了一个提醒时间字段前端到了时间弹个Toast这其实没做到真正的提醒。我的实现思路是后端定时扫描加APP端本地通知配合。后端用Spring的Scheduled写一个定时任务每小时扫描一次任务表找出满足两个条件的数据状态不是已完成/已取消/已归档且截止时间在24小时以内。对于这些任务系统会把提醒记录写入remind_log表。为什么不直接推送因为推送功能涉及第三方推送SDK的接入个人毕设项目没必要做那么重记录提醒日志等用户打开APP时拉取未读提醒列表是一种性价比很高的替代方案。Component public class RemindScheduler { private static final Logger logger LoggerFactory.getLogger(RemindScheduler.class); Autowired private TaskMapper taskMapper; Autowired private RemindLogMapper remindLogMapper; Scheduled(cron 0 0 * * * ?) public void scanDueTasks() { Date now new Date(); Date threshold DateUtils.addHours(now, 24); ListTask dueTasks taskMapper.selectTasksByDueTimeBetween(now, threshold); for (Task task : dueTasks) { RemindLog log new RemindLog(); log.setTaskId(task.getId()); log.setUserId(task.getUserId()); log.setRemindTime(new Date()); log.setStatus(0); remindLogMapper.insert(log); } logger.info(定时任务扫描完成共生成{}条提醒, dueTasks.size()); } }定时任务的启动要在spring-context.xml里配置task:annotation-driven/否则Scheduled不生效。这个坑我记忆犹新毕业设计答辩前调了两天最后发现少了一行注解开关。统计报表这部分后端提供统计接口返回三组数据每日新增任务数、每日完成任务数、按分类统计的任务数量。SQL写法上有讲究直接按天分组可以用DATE_FORMAT(create_time, %Y-%m-%d)SELECT DATE_FORMAT(create_time, %Y-%m-%d) AS day, COUNT(*) AS count FROM task WHERE user_id #{userId} AND create_time #{startDate} GROUP BY DATE_FORMAT(create_time, %Y-%m-%d) ORDER BY dayAPP端拿到这些数据之后用ECharts或者ucharts画折线图和饼图。个人任务管理系统的统计报表不用做得很复杂“七天内每日新增/完成任务折线图”加“分类占比饼图”就已经足够在答辩时展示数据可视化的能力了。4. 常见问题与排查技巧实录4.1 跨域问题前后端联调的第一道坎APP端如果用的是H5页面包在WebView里或者你在浏览器里测试H5页面十有八九会遇到跨域报错。表现是浏览器控制台提示CORS error后端日志却没任何异常。因为浏览器处于安全策略拦截了跨域请求请求根本没到后端。解决办法是在Spring MVC里配置全局跨域支持。用一个WebMvcConfigurer配置类addCorsMappings方法里允许所有来源或者在controller上加CrossOrigin注解。两种方式都行我推荐全局配置这样每个接口都不用重复加注解Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }注意允许的路径范围写成/api/不要用/保持后端接口和外界的边界清晰。另外一个细节是allowCredentials和allowedOriginPatterns要搭配使用如果用老版本的allowedOrigins(*)同时开allowCredentials(true)在Spring 5.3里是被Spring拒绝的。4.2 时间字段的时间差问题你以为是玄学其实是时区前后端联调时最容易出“神秘Bug”的场景就是时间差8小时。比如前端传了一个“2024-06-01 10:00:00”的截止时间第二天一看变成了“2024-06-01 02:00:00”或者后端存进去是对的查出来给前端时少了8个小时。这个问题的根因是时区不一致。MySQL连接串里没有指定serverTimezone时驱动默认用JVM时区JVM默认用操作系统时区而Tomcat和数据库可能在同一个机器上还好一旦部署环境变了就会出错。我在jdbc.properties里强制指定jdbc.urljdbc:mysql://localhost:3306/task_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse同时Spring MVC的Jackson配置里我也固定了LocalDateTime的格式和时区Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder - { builder.simpleDateFormat(yyyy-MM-dd HH:mm:ss); builder.serializers(new LocalDateTimeSerializer( DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); builder.deserializers(new LocalDateTimeDeserializer( DateTimeFormatter.ofPattern(yyyy-MM-dd HH:mm:ss))); }; }这样一个原则就解决了所有接口的时间参数和返回值统一用yyyy-MM-dd HH:mm:ss字符串传递Java后端用LocalDateTime接收和存储数据库用DATETIME存。三层统一就不会出现莫名其妙的时间偏移。4.3 数据库设计的坑都是血泪教训任务表这个表名在MySQL里其实是个坑。如果你用task命名没问题但如果你定义了order、group、status这类字段需要小心因为有些是MySQL的保留字。我的建议是字段命名时给status这类字段不加前缀倒是没事但表名如果叫task_task太丑就用task。还有一个更隐蔽的坑中文乱码。MySQL版本如果是5.7以下的建库时如果不指定字符集默认是latin1存中文直接变成问号。建库记得用CREATE DATABASE task_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;utf8mb4比utf8更优因为手机上用户会输入emojiutf8不支持4字节字符。这个问题我是在测试“任务标题里加表情”时发现的存进去就是????后来把连接串里的characterEncoding、数据库字符集、表字符集全部统一成utf8mb4才解决。还有一对多查询的坑。任务表和标签表是多对多关系查询任务列表时如果用join查标签一个任务带三个标签就会产生三行重复数据。我一开始用SELECT DISTINCT去重结果分页total算出来比实际多后来学乖了主查询只查任务表然后用taskIds集合批量查询标签在Service层做内存组装。这样可以多查一次数据库但避免了分页数据错乱。4.4 并发和接口性能答辩时能加分的点个人任务管理系统本身并发量不会很高但答辩老师喜欢问“你有考虑过并发吗”。任务状态流转这个接口确实存在并发问题用户在两个设备上同时操作同一个任务一个是改为完成另一个是改为取消如果后端的changeStatus方法没有做并发控制最后的结果取决于哪个请求后到达导致状态错乱。解决办法可以简单粗暴就是乐观锁。在task表加一个version字段更新时带上version条件UPDATE task SET status #{targetStatus}, version version 1 WHERE id #{taskId} AND version #{oldVersion}如果影响行数为0说明version已经变了这次更新失败告诉客户端需要刷新后重试。这个方案改动量小逻辑清晰而且能在答辩时体现你思考过数据一致性。实际项目里个人任务系统的并发压力不大不这么做也不会出事但做了之后方案档次明显不一样。接口性能方面我做了两个小优化。第一个是任务列表查询默认只查当前用户的数据SQL里强制带user_id条件加了联合索引(user_id, status, create_time)第二个是列表接口的查询结果做了简单的缓存用户在一个小时内重复查看相同条件的任务列表直接返回缓存数据。不过缓存这块毕设可以不做做了反而可能被追问缓存一致性的问题除非你把“更新任务时主动删除缓存”的逻辑一并写清楚。最后再分享几个实操层面的体会这套系统做完我自己最大的感受是任务管理系统虽然业务看起来简单但把它做“干净”很不容易。所谓干净就是状态流转有约束查询有边界接口有契约数据有隔离。毕设做完之后我不建议直接停手还有两个可以快速扩展的方向一个是通过手机APP的本地通知把提醒真正推送给用户另一个是接入数据导出的功能把任务列表导出成Excel。这两个功能都不复杂但都能让项目的完整度和答辩的展示效果提升一个台阶。如果你正在做这个题目或者类似项目我建议踏踏实实把状态机和接口设计想清楚再动手写代码这比多写一百行CRUD有价值得多。遇到接口联调不通的时候先看两边的时间格式和JSON格式是不是一致再看认证拦截器是不是漏掉了这个路径八成问题都出在这两处。
返回列表