每年六月和九月,大学宿舍楼下都会准时上演两场大型物资轮回:毕业生把带不走的台灯、书桌、专业书打包贱卖,新生们则拖着行李箱满校园找二手货。以前大家怎么交易?QQ群、微信群、贴吧、宿舍楼下的黑板。我在学校待了几年,见过太多次“消息发出去没人回”“约好交易被放鸽子”“付了钱发现东西是坏的”的情况。信息零散、没有评价机制、没有交易记录、更没有平台担保,这就是校园二手市场的真实困境。
所以当我动手做这个基于Spring Boot + Vue的校园二手交易系统时,目标很明确:把线下零散的信息流变成一套结构化的交易闭环,同时配好源码、数据库和文档,让课程设计也好、实际落地也好,都有个完整可参考的样板。这篇文章不打算写教科书式的功能列表,而是把整个项目从选型、库表设计、后端接口、前端页面到部署跑通的完整思路和踩坑记录都摊开来讲,希望对正在做类似系统的人有点实际帮助。
1. 校园二手交易的真实痛点与业务闭环定义
做系统之前先别急着写代码,把业务想清楚比什么都重要。一套校园二手交易系统,表面上是“发布商品+浏览+联系”,但真正落到细节里,你会发现很多看起来简单的功能,背后都对应着校园场景特有的痛点。
1.1 微信群和贴吧解决不了的三件事
第一是信息生命周期管理。微信群里的二手信息,被聊天记录一刷就沉底了,三天后有人想买,得翻几十页记录。贴吧帖子虽然能置顶,但交易完成后帖子还在,过期信息永远躺在那里。这套系统里我设计了商品上下架和下架后的状态清理,本质上就是把“信息流”变成“资产流”,每个商品是一个有状态的实体,而不是聊天记录里的一段语音。
第二是信任与评价机制。校园二手交易最大的问题是“对面是不是靠谱的人”。微信群里交易,买卖双方对彼此几乎一无所知。系统里用注册学号/工号、发货地址、历史交易记录、评价分数构建了一个基础信任链。买之前先看对方的信用情况,这比“群友推荐”靠谱得多。
第三是交易流程的可追溯性。线下交易出了纠纷,就是各执一词。系统设计了订单状态流转和申诉入口,每一个订单处于什么状态都有记录,谁在什么时间点了确认、完成了付款/交付,后台都能查。这不是过度设计,而是真实交易中“出了问题能说清楚”的底线需求。
1.2 用户角色与完整的业务流程
这套系统的用户角色分三类:普通用户(既可以是买家也可以是卖家)、管理员、游客。游客只能浏览和搜索商品,想要发布、下单、收藏、评论就必须登录。管理员负责审核商品、处理举报和申诉、管理用户状态。
核心业务闭环是这样的:用户注册登录后发布闲置商品,填写标题、描述、价格、图片、成色、交易地点;商品经管理员审核通过后上架;买家通过分类、关键词搜索浏览商品,发起购买请求生成订单;买卖双方在线下完成交易后,在订单里确认交付;最后双方互相评价,评价数据回写到用户信用分里。整个流程用状态机控制,不允许跳步。
这个闭环最大的价值在于:每个环节都有对应的数据落库,不会出现“商品卖了但系统里还挂着”这种脱节情况。很多课程设计项目只做了发布和列表,交易环节整个缺失,那其实只能算“商品展示系统”,不能叫“交易系统”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术栈为什么锁定Spring Boot + Vue
技术选型这件事,很多新手容易陷入两个极端:一个是“用什么无所谓,老师验收能过就行”,另一个是“什么火用什么,微服务、分布式全上”。这套系统没有走这两个极端,选Spring Boot + Vue是有明确理由的。
2.1 后端选Spring Boot的三个实打实的理由
第一,开箱即用的生态。Spring Boot内置了Tomcat,内置了自动配置,一个Spring Boot应用就是一个可直接运行的jar包。相比传统的SSM(Spring + SpringMVC + MyBatis)模式,省掉了一大堆XML配置,这对一个中型管理系统来说是决定性优势。我记得最早用SSM写项目时,光spring和mybatis的配置文件就能折腾一整天,而Spring Boot里一个spring-boot-starter-web依赖就解决了绝大多数问题。
第二,和MyBatis/MyBatis-Plus配合顺畅。Spring Boot + MyBatis-Plus是国内管理系统开发最主流的组合之一。生成代码、分页插件、逻辑删除、自动填充,这些功能正好命中校园二手交易系统这种CRUD占比很高的业务场景。
第三,学习资源丰富,排坑容易。从IDEA创建Spring Boot项目、Spring Boot配置、Spring Boot整合各类中间件,网上教程一抓一大把。做课程设计或者毕业设计,遇到问题搜解决方案几乎都能搜到,这对独立开发者来说是非常现实的因素。
2.2 前端选Vue而不是jQuery/React的原因
Vue在这类项目里的优势是渐进式开发和模板语法友好。jQuery时代你得手动操作DOM,页面一复杂代码就乱成一团;Vue的数据双向绑定把“数据变化自动更新视图”这件事解决掉了,开发体验提升非常明显。相比React,Vue的学习曲线更平缓,单文件组件(SFC)的写法让一个页面组件、样式、脚本都放在一个.vue文件里,对后端出身、前端功底一般的开发者来说更容易上手。
还有就是生态匹配度。Element UI / Element Plus组件库和Vue配合起来,后台管理界面基本是“搭积木”式开发,表格、表单、弹窗、分页这些高频组件现成可用,不需要自己从头写CSS布局。这套系统里商品卡片列表、订单管理表格、后台审核界面,都是基于组件库快速搭建的。
2.3 版本选择别追新:一次springboot版本过高的教训
这点我必须重点说,因为实际踩过坑。项目最初用了当时最新的Spring Boot 3.0版本,JDK要求17,但很多人的本机环境还是JDK 8,IDE还是老版本,跑都跑不起来。后来我把项目降到Spring Boot 2.7.x + JDK 8 + MyBatis-Plus 3.5.x的组合,一切才顺了。
版本太高除了带来的环境兼容问题,还有依赖之间的冲突。比如Spring Boot 3.0对javax到jakarta包的迁移,很多老教程里的代码直接报错,排错成本很高。课程设计和毕业设计追求的不是“最新”,而是“最稳”。Spring Boot 2.7.x到现在依然是国内教程覆盖最广、社区讨论最多的版本,遇到问题随便一搜就有答案。Vue也一样,Vue 3虽然已经成熟,但如果团队熟悉Vue 2,项目规模又不大的话,Vue 2 + Element UI依然是极其稳妥的选择。版本焦虑在项目里完全不必要,稳定可控才是第一位的。
技术栈最终选型参考:
| 分层 | 技术选型 | 版本建议 | 说明 |
|---|---|---|---|
| 后端框架 | Spring Boot | 2.7.x | 兼容JDK 8,生态稳定 |
| ORM | MyBatis-Plus | 3.5.x | 分页、逻辑删除开箱即用 |
| 鉴权方式 | JWT | 0.9.x/0.11.x | 前后端分离场景下无状态 |
| 前端框架 | Vue | 2.6 / 3.x均可 | 配套Element UI / Element Plus |
| 数据库 | MySQL | 5.7 / 8.0 | 5.7无坑,8.0注意驱动差异 |
| 构建工具 | Maven / npm | 任意稳定版 | 后端Maven,前端npm |
3. 数据库设计复盘:建表和字段的细节决定成败
数据库设计是整个系统最核心的环节,没有之一。标题里写“源码+数据库+文档”,数据库脚本往往是很多买家拿到手之后第一个打开的文件——因为只要把SQL一导入,系统就能跑起来。所以建表语句的质量直接决定第一印象。
3.1 四张核心表的字段设计
这套系统最核心的表有四张:用户表(tb_user)、商品表(tb_product)、订单表(tb_order)、评价表(tb_comment),再加上收藏表(tb_favorite)、分类表(tb_category)作为辅助。
用户表的核心字段除了常规的username、password、nickname、avatar、phone之外,还加了学校校区字段(campus)、学号/工号字段(student_no)和信用分字段(credit_score)。信用分是后续评价体系的落点,这也是区别于普通“注册登录demo”的关键设计。
商品表是信息量最大的表,我按实际交易场景设计了这些关键字段:
sql复制CREATE TABLE `tb_product` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) NOT NULL COMMENT '发布者ID',
`category_id` bigint(20) DEFAULT NULL COMMENT '分类ID',
`title` varchar(100) NOT NULL COMMENT '商品标题',
`description` varchar(1000) DEFAULT NULL COMMENT '商品描述',
`price` decimal(10,2) NOT NULL COMMENT '售价',
`original_price` decimal(10,2) DEFAULT NULL COMMENT '原价',
`cover_image` varchar(255) DEFAULT NULL COMMENT '封面图URL',
`images` text COMMENT '多图URL,逗号分隔',
`condition_level` tinyint(2) DEFAULT NULL COMMENT '成色(5成新/8成新/全新等)',
`trade_location` varchar(100) DEFAULT NULL COMMENT '交易地点',
`status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '状态:0待审核 1在售 2已下架 3已售出',
`view_count` int(11) DEFAULT '0' COMMENT '浏览量',
`deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除',
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_category_status` (`category_id`, `status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意几个细节:images字段用逗号分隔存储多张图片,这是中小项目的常见做法——不单独建商品图片表,查询时拆一下字符串就行,减少一次关联查询。trade_location是校园交易特有的字段,因为线下当面交易是校园二手交易的主要交付方式。view_count用于后续做商品排序,浏览量高的可以排在前面,这不复杂但很实用。
3.2 交易状态用整型枚举的出发点
订单状态我用了整型int存储,而不是字符串,原因有两个。一是空间的直观对比:字符串“WAIT_CONFIRM”比整数0占空间,更重要的是字符串状态一旦写错了大小写,查询结果就会出问题,整数靠数字映射枚举,不会出现这种低级错误。二是状态机控制更方便:后端代码里定义枚举类,状态流转时直接比较整数码,逻辑清晰不易出错。
订单状态枚举设计如下:
java复制public enum OrderStatus {
WAIT_CONFIRM(0, "待买家确认"),
WAIT_TRADE(1, "待线下交易"),
FINISHED(2, "交易完成"),
CANCELED(3, "已取消"),
COMPLAINT(4, "申诉中");
private final int code;
private final String desc;
OrderStatus(int code, String desc) {
this.code = code;
this.desc = desc;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
校园二手交易和电商平台不同,没有物流环节,也不搞在线支付,所以订单状态后续调整成了:买家下单(0)→ 卖家确认(1)→ 线下交易完成(2),中间任意一方可以取消(3)。如果有纠纷,可以进入申诉状态(4),由管理员介入处理。这个状态机不算复杂,但覆盖了校园二手交易的全部真实可能性。
3.3 软删除、自动填充时间与索引经验
第一个坑是delete字段的坑。我当时在订单表上做物理删除,结果一个订单被删除之后,第二天查统计报表发现金额对不上,找了半天才意识到是数据被物理删了。后面所有表统一改成逻辑删除,加一个deleted字段(0未删除,1已删除),所有查询都带条件deleted = 0。MyBatis-Plus有@TableLogic注解,配置之后自动帮你拼上这个条件,不用自己手写。
第二个细节是时间字段。所有表都有create_time和update_time,在MyBatis-Plus里用MetaObjectHandler实现自动填充,插入时自动填创建时间,更新时自动填修改时间。这样每次写SQL都不用手动维护时间字段,也避免了“忘了填导致时间为空”的问题。
第三个是索引设计。一开始为了省事,只给主键建了索引,结果商品量一大,按分类查询(category_id)和按用户查询(user_id)的时候明显变慢。后来补上了单列索引和联合索引,比如idx_category_status (category_id, status),就是针对“按分类查在售商品”这个高频查询设计的。数据库设计一定要想清楚“哪些查询是最频繁的”,再针对这些查询建索引,而不是把所有字段都建一遍索引。
4. 后端核心逻辑实现:鉴权、图片上传、订单状态机的落地
后端部分我挑三个最有代表性的模块讲:登录鉴权、图片上传、订单状态管理。这三个模块做好了,系统基本就立住了。
4.1 JWT登录鉴权:为什么不用Session
前后端分离架构下,Session方案要处理跨域携带Cookie、集群部署Session共享等问题,而且移动端对接也不方便。JWT(JSON Web Token)是无状态的,后端不保存会话状态,客户端每次请求把Token放在请求头里,后端只负责验签。用户登录成功后生成一个包含用户ID、角色、过期时间的Token返回给前端,前端存到localStorage里,每次请求带上Authorization: Bearer <token>。
JWT的核心实现思路:
java复制// 登录成功后生成Token
String token = Jwts.builder()
.setSubject(user.getId().toString())
.claim("role", user.getRole())
.setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600 * 1000))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
这里我建议过期时间设成7天,太短了用户隔几天再用就得重新登录,体验不好;太长了又容易泄露。实际项目里可以在用户操作时做“续期”或者用双Token机制,但课程设计阶段7天完全够用。
还需要写一个JwtInterceptor拦截器,在Spring Boot里实现HandlerInterceptor接口,在preHandle里校验Token有效性。注意放行登录接口、注册接口、商品搜索等公开接口,其余接口统一拦截。Token过期、签名错误都返回401状态码,前端路由守卫收到401就跳登录页。
4.2 图片上传的本地存储方案与路径坑
图片上传这个功能,看着简单,坑在于存储路径的规划。初学者最常见的做法是把图片存在前端项目的static目录下,但这是错的——前端打包之后,static目录会被重新生成,上传的图片会被覆盖或丢失。
正确的做法是在后端项目里配置一个独立的上传目录,然后通过静态资源映射把URL和目录关联起来。Spring Boot里这样配置:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/upload/**")
.addResourceHandler("file:" + uploadPath);
}
}
uploadPath配置在application.yml里,比如D:/campus-trade/upload/(Windows)或/usr/local/campus-trade/upload/(Linux)。上传时把图片文件写到这个目录,数据库里存/upload/xxx.jpg这个相对路径,前端直接用这个路径访问图片。这样图片和数据是分开的,打包部署的时候只要把上传目录单独备份,就不会和数据一起丢失。
实际踩过的坑是:Windows下文件路径分隔符是反斜杠\,Linux下是正斜杠/。处理文件名拼接时最好统一用File.separator,或者用URI拼接,避免部署到服务器上路径报错。
4.3 订单状态机的流转设计
订单状态机是整个后端逻辑里最需要谨慎的部分,因为状态不能乱跳。比如一笔订单处于“待买家确认”,卖家不能直接把它改成“交易完成”,必须等买家确认或者系统管理员介入。
我用的控制方式是在Service层写状态流转方法,而不是让前端传任意状态值:
java复制public void confirmOrder(Long orderId, Long userId) {
Order order = orderMapper.selectById(orderId);
if (order == null) {
throw new BusinessException("订单不存在");
}
// 校验当前状态允许流转
if (order.getStatus() != OrderStatus.WAIT_CONFIRM.getCode()) {
throw new BusinessException("当前订单状态不允许此操作");
}
// 校验操作人是否为买家
if (!order.getBuyerId().equals(userId)) {
throw new BusinessException("无权限操作该订单");
}
order.setStatus(OrderStatus.WAIT_TRADE.getCode());
orderMapper.updateById(order);
}
每个状态流转方法都要做“状态合法性校验”和“操作人权限校验”双重检查。这种写法能被普通CRUD多写一点代码,但避免了订单状态被随意篡改的问题。订单成交后还要顺手把对应商品的status改成“已售出”,这一步很容易漏,漏了就会造成“商品已经卖了但还挂在首页”。
另外一个容易忽略的点是并发操作。两个买家同时看到一件商品,同时下单,可能导致超卖。简单方案是在tb_product表加一个status字段判断,下单前用UPDATE tb_product SET status = 3 WHERE id = ? AND status = 1这种方式做原子性的状态变更,受影响行数为0就说明商品已经被别人下单了。比起加锁和事务,这种“乐观锁”思路在这个场景里最实用。
5. 前端Vue部分落地:路由守卫、页面复用与打包部署
前端这块,Vue项目从零搭建到打包上线,中间有几个环节是必踩的坑。我一个个说。
5.1 前端目录结构与页面划分
项目用Vue CLI创建,目录结构大致如下:
code复制src/
├── api/ # 接口请求封装
├── assets/ # 静态资源
├── components/ # 公共组件(商品卡片、分页、上传组件)
├── router/ # 路由配置
├── store/ # Vuex状态管理
├── views/ # 页面
│ ├── home/ # 首页(商品列表、搜索)
│ ├── product/ # 商品详情、发布商品
│ ├── order/ # 订单列表、订单详情
│ ├── user/ # 个人中心、我的发布、我的收藏
│ └── admin/ # 后台管理(用户管理、商品审核、举报处理)
└── utils/ # 工具函数(request封装、token处理)
api目录的封装很关键。统一用axios实例,配置baseURL为/api,并在request拦截器里统一加token,在response拦截器里统一处理401状态。这样每个页面调用接口时不需要重复写请求头逻辑,代码清爽很多。
页面复用方面,首页的商品卡片组件(商品图、标题、价格、成色标签)在首页、搜索结果页、我的收藏页、管理员审核页都会被使用。一个ProductCard.vue组件,通过props接收商品对象,通过@click事件向父组件传递跳转行为,一套代码四处复用。这种组件化思维,是Vue项目不变成“面条代码”的关键。
5.2 路由守卫与未登录拦截
前端路由守卫和后端JWT拦截是配套的。后端拦截保证接口安全,前端路由守卫保证“未登录用户看不到需要登录的页面”。路由表里通过meta.requiresAuth标记需要登录的页面,然后在全局前置守卫里统一判断:
javascript复制router.beforeEach((to, from, next) => {
const token = localStorage.getItem('token')
if (to.meta.requiresAuth && !token) {
next({ path: '/login', query: { redirect: to.fullPath } })
} else {
next()
}
})
登录成功之后,可以通过redirect参数把用户带回他之前想访问的页面,这个细节体验好很多。管理员页面还要再校验角色,只在用户是管理员时放行,否则跳转首页并提示“无权限访问”。
5.3 vue打包后布局异常与刷新404的排查
前端做完之后,npm run build打包出dist目录,扔到服务器上就出问题了。这个问题在热搜词里反复出现(“vue 打包后 布局异常”),我把完整的排查思路写一下。
第一个坑是静态资源404。页面能打开,但样式和JS都加载不出来,打开浏览器控制台全是404。原因很简单:Vue默认的publicPath是/,打包后引用的资源路径是绝对路径/js/app.js,如果你的项目部署在服务器根目录没问题,但部署在子目录(比如http://ip:8080/front/),路径就全错了。解决办法是在vue.config.js里设置:
javascript复制module.exports = {
publicPath: './',
// 其他配置
}
设置为./之后,打包出来的资源引用就变成了相对路径,部署在任意子目录都能正常加载。
第二个坑是history模式刷新404。Vue Router默认是hash模式,URL里带#号。为了好看,很多人切到history模式,结果部署上线后,从首页跳转没问题,一刷新或直接输入子路由地址就404。原因是history模式依赖后端做路由重写,把所有请求都指向index.html。如果是Nginx部署,需要加这条配置:
nginx复制location / {
try_files $uri $uri/ /index.html;
}
如果你用的是纯静态服务器,不具备重写条件,那用hash模式最省事。能用hash模式就别折腾history模式,除非你知道怎么配服务器。我后来部署方案就用了hash模式,彻底避开这个坑。
第三个坑是打包后布局错乱。个别样式和本地开发时不一致,检查后发现是CSS中使用了绝对路径引用背景图片,打包后路径不对导致样式失效。解决方法是把图片资源放在src/assets目录里通过import引入,而不是直接写在background-image的 url 字符串里。正则处理掉硬编码路径,让webpack统一处理资源引用。
6. 拿到源码后怎么跑起来:数据库初始化、必改配置与二次开发思路
标题写了“源码+数据库+文档”,那这部分就是对使用者最实用的内容。很多人拿到一套源码之后,不知道怎么把它跑起来,浪费一整天配置环境。我把这套系统从零跑通的步骤和会遇到的问题一次性说清楚。
6.1 从零到启动的完整步骤
前提条件是:本机装了JDK 8(或11)、Maven 3.6+、MySQL 5.7+、Node.js 14+。然后按这个顺序操作:
- 导入数据库:用Navicat或命令行执行项目根目录下的
sql/campus_trade.sql文件,建库建表并插入初始数据(管理员账号、测试分类、测试商品)。 - 改后端配置:打开
src/main/resources/application.yml,修改数据库连接的用户名和密码。 - 启动后端:用IDEA打开backend目录下的
pom.xml,加载Maven依赖后直接运行主类。看到Started Application in xx seconds就是启动成功。 - 安装前端依赖:在frontend目录执行
npm install,这一步如果有报错,八成是Node版本问题(Node 14以下或Node 18以上对老项目都可能不兼容)。 - 启动前端:执行
npm run dev,浏览器访问http://localhost:8081。 - 测试登录:用初始化的测试账号登录。第一种是管理员账号,进入后台审核界面;第二种是普通用户账号,走一遍“发布商品→搜索→下单→确认交易”的完整流程。
我建议在这个流程里每个步骤都验证一个最小结果:数据库导入了就查一下表数量对不对,后端启动了就访问一下/api/health之类的接口,前端起来了就访问一下首页。不要等所有步骤做完才一把梭,不然出错时不好定位。
6.2 配置文件里必改的三个位置
实际使用中,数据库脚本导入干净之后,90%的启动失败都是配置文件没改全。我列三个必改项:
第一,数据库账号密码。
yaml复制spring:
datasource:
url: jdbc:mysql://localhost:3306/campus_trade?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
MySQL 8.0以上版本的驱动名是com.mysql.cj.jdbc.Driver,MySQL 5.7是com.mysql.jdbc.Driver。如果用的是Spring Boot 2.7.x,驱动一般会自动适配,但如果连接报错ClassNotFound,手动指定驱动类即可。
第二,文件上传路径。
yaml复制file:
upload-path: D:/campus-trade/upload/
这个目录需要提前创建好。Windows下注意盘符存在,Linux下注意目录权限,否则图片上传会报“系统找不到指定的路径”或“Permission denied”。
第三,JWT密钥。 默认的secret是用来签名的,用默认值能跑通,但上线前一定要改成一串足够长的随机字符串,否则Token可以被预测伪造。这个参数往往被人忽略,但在真实环境里是很严重的安全漏洞。
6.3 课程设计之外的差距补全方向
如果这套系统只是用来做课程设计或者毕业设计,现在的功能其实已经够了。但如果你想让它真正“可用”,有几个方向值得继续补:
一是接入Redis做缓存和会话管理。目前是纯JWT无状态方案,如果把热门商品列表、分类信息缓存到Redis,并发性能会好很多,这也是面试时一个很加分的优化点。
二是消息通知。买家下单、卖家确认、交易完成,目前都需要用户主动刷新页面看状态。接入WebSocket或者简单的邮件通知,体验会提升一个档次。
三是支付和信用体系。校园场景虽然以线下交易为主,但很多学校内也有成熟的线上支付场景。接入校园卡支付或者微信/支付宝的校园版支付,再加上实名认证(绑定学号),系统的完整度会更高。
四是完善单元测试。大部分课程设计项目最薄弱的就是测试。即便不追求覆盖率,至少对订单状态流、商品发布、登录鉴权这几个核心模块写单元测试。我在实际改造中就从这里入手,把后端Service层的关键逻辑加了测试,改代码时心里踏实很多。
这套系统说到底,是一个覆盖了前后端分离、数据库设计、接口文档、部署运维的综合性案例。它没有高深的技术难点,但把每个环节都做扎实了。我最大的体会是:真正难的不是某个单独的技术点,而是把登录、商品、订单、评价这些模块串成一个完整闭环,还要保证别人拿到源码能顺利跑起来。如果你能把这套系统从头到尾独立做一遍,Spring Boot和Vue的实战水平基本就过关了。
