每年到毕业季,总有一批人被同一个问题卡住:要技术栈有java、vue、springboot,要业务场景还得是能落地、不假大空的东西。如果你正在发愁或者想找个全栈项目练手,二手旧物回收商城这个方向确实值得认真做一遍。我这次完整梳理一下从需求、表结构、后端核心模块到前端页面和部署调试的整个实现思路,里面每一段都是实际写代码时会遇到的真实问题,不只是把系统跑起来那么简单。
这个项目的名义是“二手旧物回收商城”,本质上要做成两套业务闭环:一是二手闲置商品的展示、下单、交易,二是旧物回收预约的上门回收流程。两头都打通,系统才算完整,而不是做一个只挂着“回收”两个字但实际只有商品CRUD的假项目。适合的人群也很明确:准备用SpringBoot+Vue做毕设的同学、想练全栈项目经验的初级开发,以及想系统了解订单状态机、权限模型、文件上传这些高频面试点的朋友。
1. 需求梳理:这个二手旧物回收商城到底包含哪些功能
1.1 两条核心业务线:商品交易与回收预约
拿到这种项目题目,第一件事不是建SpringBoot工程,而是把业务角色和流程画清楚。我见过太多人上来先写实体类,写到订单表的时候就开始纠结“买家卖家怎么区分”“回收单和订单要不要放一张表”,本质都是需求没理清。
这个系统我拆成两条线来看:
商品交易线:用户A发布一件闲置二手商品,管理员审核通过后上架,用户B浏览、搜索、加入购物车、下单、支付,A收到订单后确认发货,B确认收货,订单完成。如果B不想买了,支付前可以取消订单,支付后发起退款申请。
回收预约线:用户有一些旧衣物、旧书、旧家电、手机数码之类的东西,不想发布成商品慢慢卖,直接填一个回收预约单,选择品类、预约时间段、填写地址和物品描述。回收员在后台看到待接单的预约,抢单/接单,按时间上门,当面确认物品后录入实际回收价格,用户确认回收完成,回收款转入用户余额,用户可申请提现。
两条线的用户角色都是同一套账号体系,普通用户既能在商城买东西,也能预约回收旧物,还能在个人中心看到自己的商品订单和回收记录。管理员负责商品审核、分类管理、回收单分配、用户管理、公告管理。回收员是第三个独立角色,专门处理回收流程。
1.2 功能模块清单:不该少的和容易被忽略的
按上面的业务线拆模块,最少需要这些功能:
- 用户端:注册登录、个人信息维护、收货地址管理、浏览商品、搜索/分类筛选、商品详情、购物车、下单支付、订单列表、订单详情、取消/确认收货、发布闲置商品、回收预约、回收记录、余额与提现、收藏商品、公告查看
- 管理员端:登录、用户管理、分类管理、商品审核/上下架、商品管理、订单管理、回收预约管理、回收员管理、公告管理、数据统计
- 回收员端:登录、待接单列表、我的回收任务、录入回收价格、完成回收、回收收益记录
容易被忽略但有亮点的功能我特意加上了三个:商品审核流程、回收员抢单机制、订单超时自动取消。这三个功能在数据库设计和后端逻辑上都能体现技术含量,面试时也是很好的展开点。尤其是商品审核,很多同学的毕设项目是发布即上架,这其实不太合理,二手平台必须有审核环节来控制违规商品,加上这个流程,系统的完整度立刻不一样。
1.3 系统角色与权限边界
整个系统至少需要三种角色:USER(普通用户)、ADMIN(管理员)、COLLECTOR(回收员)。我建议在多用户登录的设计上就直接用角色字段区分,而不拆成三套独立的登录表,这样体检中心和个人中心逻辑都统一。JWT令牌里存userId和role,后端用拦截器校验登录状态,再用注解做角色权限控制,比如只有ADMIN能调用商品审核接口,只有COLLECTOR能接回收单。
权限这块很容易被忽略,但确实很重要。我在做的时候遇到过一个实际场景:商品审核接口忘记加权限校验,结果普通用户直接调接口把自己的商品强制上架了。后来我把所有接口按角色过了一遍,给管理端接口统一加了@RequireRole("ADMIN")注解,才算踏实。项目答辩的时候可以重点讲一下这里的设计思路,比单纯说“我用了Spring Security”更有说服力。
2. 技术选型:为什么是SpringBoot+Vue这套组合,以及对应的替代方案
2.1 后端选型的理由
后端用SpringBoot几乎是这类项目的标准答案。原因很直接:SpringBoot帮我们把Spring的配置自动化,内嵌Tomcat,不需要额外部署WAR包,开发阶段一个main方法就能启服务。对于我这种要快速实现项目的场景,省下的时间可以全花在业务逻辑上。
具体技术栈我选的是:
- Spring Boot 2.7.x / 3.x都行,关键是JDK版本匹配。3.x要求JDK17,如果本机是JDK8就老老实实用2.7。
- MyBatis Plus:单表CRUD可以完全不写SQL,分页插件、逻辑删除、自动填充这些功能都是现成的,比纯MyBatis省太多事。
- MySQL 8.x:存业务数据,字符集统一utf8mb4。
- Redis:做验证码缓存、Token黑名单、首页轮播图缓存。这一个就能在项目亮点里占一条。
- JWT:无状态登录方案,用户登录后生成token,前端每次请求带在Header里。
- Lombok:省掉getter/setter,实体类清爽很多。
- Hutool:生成验证码、ID生成这些工具类非常方便。
为什么不用Spring Security?真要硬塞也能塞进去,但学习成本和配置成本都会高不少。对这种商城项目,用拦截器做登录校验、自己写角色注解反而更可控,面试的时候被问到底层原理也不心虚。Spring Security可以放在嘴边的“我还了解”这一层,没必要为了用而用。
2.2 前端选型的理由
前端我选的是Vue 3 + Vite + Element Plus + Pinia + Axios + Vue Router。Vue 3的组合式API写起来逻辑内聚性更好,同一个功能的响应式变量和方法放在一起,而不是像Vue 2的选项式那样分散在data、methods、computed里。Vite开发服务器启动速度快,热更新比webpack时代的体验好太多。
状态管理用Pinia而不是Vuex,因为Pinia更轻、API更简洁,没有mutations那一层的冗余写法,而且它对TypeScript的支持更好。UI库用Element Plus,表格、表单、弹窗、分页这些管理后台需要的组件都有现成的,风格也比较统一。Axios封装一下请求和响应拦截器,统一处理token注入、401跳转、错误消息提示,这是每个Vue项目都要做的基建工作。
2.3 为什么不建议换成其他方案的几个考虑
有的同学喜欢用关系型数据库的H2或者SQLite图省事,我强烈不建议。MySQL就多一个安装配置的事,但对后续的面试解释、代码审查、部署上线都有帮助。H2做毕设演示确实轻便,但面试官一问“你的数据最终存在哪”,答H2会显得项目很玩具。
后端换成Node.js或Python Django当然也能做,但问题在于这类毕设题目的关键词已经把技术栈限定成java、vue、springboot了,直接用最匹配的方案是最稳妥的。如果换成SSM(Spring MVC + Spring + MyBatis),又会陷入一堆XML配置里,纯粹自找麻烦。SpringBoot就是把SSM的繁琐配置默认化,这也是它能成为主流的原因。
前端不用Vue CLI而用Vite,是因为Vite基于ES Module的处理方式让冷启动和热更新都更快。不过我提醒一句,Vite要求Node.js 14.18+,你自己电脑上如果是老版本Node,记得先升级。还有人用Ant Design Vue,也不错,但我用Element Plus是因为后台管理场景它组件更全,表格和表单的交互更顺手。
3. 数据库设计:核心表结构、订单状态和回收单状态机
3.1 标准库表结构清单
这个项目我设计了12张表左右,核心几张列出来供参考:
| 表名 | 说明 | 关键字段 |
|---|---|---|
| user | 用户表 | id, username, password, nickname, avatar, phone, role, balance, status |
| category | 商品分类表 | id, name, sort, icon, status |
| product | 闲置商品表 | id, user_id, category_id, title, description, cover, images, price, original_price, status, view_count |
| cart | 购物车表 | id, user_id, product_id, quantity |
| product_order | 商品订单表 | id, order_no, user_id, seller_id, product_id, amount, status, address_snapshot, pay_time, ship_time, finish_time |
| address | 收货地址表 | id, user_id, receiver_name, receiver_phone, province, city, district, detail, is_default |
| recycle_appointment | 回收预约表 | id, appointment_no, user_id, collector_id, category, description, appointment_time, address, status, estimated_amount, actual_amount |
| recycle_record | 回收完成记录表 | id, appointment_id, user_id, collector_id, amount, balance_before, balance_after, create_time |
| withdraw_record | 提现记录表 | id, user_id, amount, account, status, reject_reason |
| banner | 轮播图表 | id, image, url, sort, status |
| notice | 公告表 | id, title, content, status, create_time |
| collection | 收藏表 | id, user_id, product_id, create_time |
有些字段我额外做了冗余设计,比如订单表里的seller_id和address_snapshot。地址快照的意思是下单时把买家填的收货地址整段存进订单,而不是关联地址表ID,因为地址以后可能被修改,但订单的收货信息必须保持下单时的样子。这个设计在电商系统里非常重要,面试被问“为什么订单表不直接关联地址表”的时候,这就是标准答案。
商品表里的images字段我建议存JSON数组格式的字符串,比如["https://xxx/1.jpg","https://xxx/2.jpg"],前端请求详情页时直接JSON.parse就能轮播展示。用独立的商品图片表也能做,但查询要多一次联表,对这个规模的项目反而没必要。
3.2 商品订单状态机设计
商品订单状态我设计成六个状态:待支付、待发货、待收货、已完成、已取消、退款中。数据库里用int类型存状态码,0待支付、1待发货、2待收货、3已完成、4已取消、5退款中,代码里定义常量类避免魔法数字。
完整流转是这样:
- 创建订单:状态0待支付,同时创建订单项快照
- 用户支付:状态0 -> 状态1,记录支付时间
- 卖家发货:状态1 -> 状态2,记录发货时间(二手交易里可以简化成“卖家确认发货”)
- 买家确认收货:状态2 -> 状态3,记录完成时间,卖家余额增加
- 超时未支付:状态0 -> 状态4,由定时任务统一处理
- 退款申请:状态2 -> 状态5,管理员确认后退款给买家,状态回滚并关闭订单
状态机最大的价值在于让非法状态跳转无处藏身。我在Service层写了一个OrderStatusTransition工具类,每次状态变更前先校验当前状态是否允许跳到目标状态,不允许就直接抛业务异常。比如订单在“已完成”状态下又调用“取消”接口,就会被拦截,不会产生脏数据。
3.3 回收单状态机设计
回收预约单的状态我也用数字存的:0待审核、1待接单、2待上门、3已确认(待打款)、4已完成、5已取消、6已驳回。流程是:
用户提交预约 -> 管理员/系统审核(0 -> 1,审核不通过直接6)-> 回收员接单(1 -> 2)-> 回收员上门确认物品并录入实际金额(2 -> 3)-> 用户确认回收完成,钱款打入余额(3 -> 4)-> 完成。如果用户临时有事,在2之前可以取消,状态置为5。
这里有个容易踩坑的点:回收金额录入后不能马上打到用户余额,必须等用户确认才能入账,防止回收员乱填价格。我在回收员录入金额后会让系统自动给用户发一条通知,用户确认后才调余额变更逻辑,并且用事务保证“更新预约单状态”和“增加用户余额”两个操作要么都成功,要么都失败。开发时曾出现过用户余额加了但预约单状态没更新的情况,后来加了@Transactional注解解决了。
3.4 Redis在这个项目里的真实用途
Redis最实在的三个用途,我建议大家一定写上:一是图形验证码的存储和校验,验证码5分钟过期,用Redis过期时间天然实现;二是登录Token的“记住我”语义管理,虽然JWT本身无状态,但退出登录场景下可以把token加入黑名单,Redis里存一个key为token、值为1、过期时间和token一致的黑名单;三是首页轮播图和热门商品的数据缓存,后台修改轮播图时删除缓存,下次请求自动回源数据库。
有同学问我为什么不用Redis存购物车,不是不行,但购物车数据在数据库里更好做列表分页和持久化,毕设项目没必要把所有东西都塞Redis,反而会被追问“Redis宕机了购物车怎么办”这种问题。
4. 后端核心实现:从登录鉴权到订单状态流转的代码思路
4.1 项目目录结构和统一结果集
我建议后端包结构按功能模块分,不按技术类型硬分。service、mapper、controller、entity、dto、vo、config、common、utils。模块之间用DTO/VO隔离,不要直接把Entity返回给前端。
统一结果集Result<T>是一个必须自己封装的基础类,包含code、message、data三个字段。成功返回Result.success(data),失败返回Result.error("参数错误"),配合全局异常处理器,controller里的代码就能非常干净。比如查询用户信息的方法只需要写业务代码,不用每个接口都手动try-catch。
4.2 JWT登录与接口权限控制
登录流程是这样的:用户提交用户名密码,校验通过后生成JWT,payload里放userId和role,secret密钥放在配置文件中;响应给前端,前端存在localStorage。Axios请求拦截器在每个请求Header里加Authorization: Bearer token,后端拦截器解析token,把userId和role放进ThreadLocal。
这里有个细节:JWT天然无状态,所以不能手动让某个token失效。退出登录时把token加入Redis黑名单。我见过很多学生的项目退出登录只是前端删了localStorage,后端token还能用,这就留下了安全隐患。
角色权限控制我用自定义注解实现。定义@RequireRole("ADMIN")注解,拦截器里校验当前用户角色是否匹配。比如商品发布接口需要USER,商品审核接口需要ADMIN,回收员接单接口需要COLLECTOR。这样controller方法上标一个注解就行,权限逻辑集中在拦截器,代码可读性很高。
4.3 商品发布与文件上传
商品发布接口接收表单数据,包括标题、描述、分类、价格、封面图、详情图。图片上传我建议直接传到服务器本地磁盘,再把访问路径存数据库。本地磁盘存储要考虑一个关键问题:前端要能直接通过URL访问到图片,所以SpringBoot需要配置静态资源映射,把自己指定的上传目录映射到/upload/**这个URL路径。
application.yml里大致这么配:
spring: servlet: multipart: max-file-size: 10MB max-request-size: 100MB file: upload-path: /data/upload/然后写一个配置类把/data/upload/映射为/upload/**的静态资源。上传后返回的路径就是/upload/2025/03/12/uuid.jpg。
常见的坑是文件上传大小限制。默认只有1MB,传一张手机拍的大图就直接报错。必须把max-file-size调大。还有文件名别用用户原始文件名,会乱码、会重名冲突,一定要用UUID重命名。我这里用Hutool的IdUtil.simpleUUID()生成文件名,再拼上原始扩展名。
4.4 下单流程中的超卖与重复提交处理
下单我用的思路是:校验商品状态为上架 -> 校验用户不是商品本人(不能买自己的商品)-> 生成唯一订单号 -> 扣减商品库存(如果这个商品限量1件就直接把状态改成“已被下单锁定”)-> 创建订单。这里我用数据库唯一约束或者乐观锁来处理并发。二手商品单件库存,其实可以用一个update语句把商品状态从“上架”改成“已锁定”,影响行数为1才下单成功,影响行数为0说明商品被别人抢了,直接返回“手慢了”。
重复提交问题我在前端做了按钮loading限制,后端再加一道幂等校验,使用Redis的setIfAbsent保存一个orderToken,第一次提交成功,第二次提交发现key已存在就报“不能重复提交”。
4.5 订单超时取消的定时任务实现
用@Scheduled(cron = "0 */5 * * * ?")每5分钟扫一次订单表,把“创建时间超过30分钟且状态为待支付”的订单批量改为已取消。注意批量更新时update语句要加上status = 0条件,防止把已经支付成功的订单也取消掉。这就是典型的条件更新防覆盖问题。
如果要把这个方案说得更高级一点,可以提RabbitMQ延迟队列方案,但毕设阶段定时任务足够满足需求。面试时被问“定时任务有延迟怎么办”,可以解释:业务上30分钟超时容忍几秒延迟完全没问题,如果要求精确到秒级,才需要引入延迟队列。
5. 前端Vue实现:商城、回收预约与后台管理三端页面怎么搭
5.1 前端工程初始化与实际目录结构
用Vite创建Vue3项目一条命令:npm create vite@latest second-hand-mall -- --template vue,然后安装依赖。我自己的项目结构是:
src/ api/ // 按模块拆分的接口文件 assets/ components/ layout/ // 前台布局和后台布局 router/ store/ // Pinia utils/ // axios封装、工具函数 views/ front/ // 商城前台页面 admin/ // 管理后台页面 collector/ // 回收员端页面5.2 Axios请求封装与登录状态管理
Axios封装成request.js,核心逻辑是请求拦截器从localStorage里取token加到Header上,响应拦截器判断code:400跳登录页、403提示无权限、500提示服务异常。与后端约定的code规则要前后端一致:200成功、400参数错误、401未登录、403无权限、500系统异常。
Pinia里建一个useUserStore,存用户信息、token、角色。每次页面刷新后重新调用/user/info获取登录用户信息。路由守卫里判断:没有token且访问需要登录的页面就跳登录页;有token但访问登录页就跳回首页;角色不匹配的页面直接提示无权限。这些逻辑在router/index.js的beforeEach里统一拦截。
5.3 商城首页与商品列表的实现细节
首页我做成轮播图+分类导航+最新商品+热门回收分类四个区块。商品列表页支持分类筛选、关键词搜索、价格排序。列表数据用分页组件配合后端Page对象,前端每页12条或8条,图片懒加载用Vue的v-lazy指令(Element Plus或自定义)。
商品详情页的核心是展示商品图片轮播、价格、描述、卖家的其他商品、以及“立即购买”和“加入购物车”两个按钮。发布商品页面用el-upload组件传图片到后端,action指向后端的/api/file/upload接口。上传后把返回的URL收集进表单数组,提交时和后端的数据结构保持一致。
5.4 回收预约页面的表单与状态展示
回收预约页面是一个分类明确的表单:选择回收品类(旧衣物、旧书、家电、手机数码、其他)、填预约时间(el-date-picker选日期和时段)、填详细地址、物品描述。提交后跳转到“我的回收预约”列表页,按状态显示标签:待审核、待接单、待上门、已确认、已完成、已取消。
回收员端页面我用了一套独立的侧边栏布局,核心是“可接单大厅”和“我的任务”。可接单大厅用卡片列表展示待接单的预约,点击接单调用/recycle/accept。我的任务里,回收员可以查看自己接下的单,点击“确认上门”后填写实际回收金额,提交后流程进入用户确认阶段。
5.5 管理后台的关键页面
管理后台我用经典的上左右布局:侧边栏菜单、顶部面包屑、主内容区域。核心页面包括:
- 商品审核页:待审核商品列表,每行有“通过”“驳回”按钮,驳回要填原因。
- 商品管理页:可以强制下架违规商品,列表带状态筛选。
- 回收预约管理页:管理员可以查看所有预约单,分配回收员或直接驳回。
- 用户管理页:启用/禁用账号。
- 数据统计页:用
v-charts或者ECharts画柱状图/饼图,统计每日订单量、回收分类占比等。
管理后台核心是表格+弹窗+表单的组合。Element Plus的el-table、el-dialog、el-form组件都能直接满足,重点在于写清楚每行的操作按钮对应哪个接口。
6. 联调与部署阶段遭遇的实际问题:跨域、时间格式、图片回显和前端代理
6.1 跨域问题:CORS配置与Vite代理双重方案
前后端分离开发时跨域是第一道坎。前端跑在http://localhost:5173,后端跑在http://localhost:8080,端口不同必然跨域。我做了两层保险:后端配置CORS全局允许指定来源;前端Vite开发服务器配置server.proxy,把/api开头的请求代理到后端。
实际开发中,我建议以Vite代理为主,后端CORS为辅。因为代理模式下前端请求的URL还是同源的,不会出现会话和Cookie层面的问题;后端CORS则对部署后的生产环境有兜底作用。具体Vite配置是这样:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }这样前端请求/api/user/login,开发服务器会把它转发到http://localhost:8080/api/user/login。后端Controller统一加@RequestMapping("/api")前缀,接口风格清晰。
6.2 LocalDateTime序列化问题
后端返回Java 8的时间类型,如果不做处理,前端拿到的是一串数组或者ISO字符串,显示很丑。处理方式两种:一是在application.yml里全局配置Jackson的日期格式;二是在实体类时间字段上加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")。我用的全局配置,这样不用每个字段都加注解。
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8这个配置特别容易漏,漏了之后前端表格里全是时间戳,排查半天还以为是后端代码问题。
6.3 MyBatis Plus逻辑删除与字段自动填充
逻辑删除是一个很实用、但很多资料不会讲透的点。我在application.yml里配置逻辑删除的全局值,实体类字段上加@TableLogic注解。这样执行delete操作时,MyBatis Plus自动将其转为update语句,设置deleted字段为1。所有查询操作也会自动带上deleted=0条件。这样实施的好处是数据不真实丢失,用户订单和商品关联还能查回来,面试时解释清楚这一层,能体现你对数据安全的考虑。
create_time和update_time两个字段可以用MyBatis Plus的MetaObjectHandler自动填充,实体类上标注@TableField(fill = FieldFill.INSERT),插入时自动写创建时间,更新时自动写更新时间。这个方案相比数据库DEFAULT CURRENT_TIMESTAMP好的一点是:Java层可控,像订单快照这种写入场景可以在逻辑里灵活处理。
6.4 图片上传后前端无法访问
这个问题我调试过很久。后端图片上传成功,路径也存到数据库了,但前端访问http://localhost:8080/upload/xxx.jpg返回404。原因就是SpringBoot没有把磁盘的upload目录映射成静态资源URL。解决方式是在配置类里加一个WebMvcConfigurer,重写addResourceHandlers方法,把/upload/**映射到磁盘路径。
部署之后还会遇到第二个坑:如果把项目部署到云服务器,图片路径不能写死成localhost,要换成服务器的公网IP或者域名。所以上传接口返回的URL,我建议后端在返回时动态拼接当前请求的域名,而不是存一个写死的地址。具体可以用ServletUriComponentsBuilder.fromCurrentContextPath()拿到当前服务的基础地址。
6.5 打包部署:jar包与前端dist的托管
后端部署很简单,Maven执行mvn clean package,生成target目录下的jar,用java -jar xxx.jar运行。前端运行npm run build生成dist目录,里面是静态文件。
生产环境我建议用Nginx托管前端dist,并把/api请求反向代理到后端的8080端口。Nginx配置核心就两大块:
server { listen 80; server_name your_domain; root /var/www/second-hand-mall/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; } location /upload/ { proxy_pass http://127.0.0.1:8080; } }这样整个系统的外部访问入口只有一个80端口,不需要把8080暴露到公网。同时Vue Router要用createWebHistory模式时,Nginx还要加一个try_files配置把所有路由都回退到index.html,否则刷新页面会404,这也是高频坑。
7. 答辩和面试环节的高频追问与回答思路
7.1 为什么订单表要有订单号,为什么不用自增ID当订单号
这是一个很典型的追问。订单号不是数据库主键,但为什么单独设计一个order_no字段?核心原因是业务展示和安全性。自增ID能猜,比如你下单ID是100,别人抢在你前面下单ID是99,平台能通过ID推测出单量;而订单号是带有业务规则的编码值,比如时间戳+随机串,不可推测、展示也好看。我用的生成规则是yyyyMMddHHmmss+ 6位随机数,唯一性由MySQL唯一索引兜底。
7.2 商品状态机是如何防止非法操作的
面试官喜欢问“如果前端绕过按钮直接调用后端接口,把已完成的订单取消掉怎么办”,这其实是状态机设计要解决的问题。我会回答:每个状态流转都在Service层做合法性校验,比如取消订单必须先判断当前状态是0待支付;确认收货必须先判断当前状态是2待收货。这种硬校验放在后端,前端按钮只是UI层面的引导,真正的数据安全之后端一层。
7.3 回收员抢单怎么避免两个人同时接同一单
这是一个典型的并发问题。我的方案是使用update recycle_appointment set collector_id = #{collectorId}, status = 2 where id = #{id} and status = 1这样的乐观锁更新,数据库行锁保证同时只能有一个回收员更新成功。影响行数为1表示抢单成功,为0表示已经被别人抢走。这种方案比先查询再更新要可靠得多,也解释了为什么不能让前端“先看再抢”。
7.4 从用户浏览到下单,整个请求链路是怎么串联的
面试官问这类问题,是看你有没有完整的调用链意识。我会顺着说:用户在商品列表点击商品 -> 前端路由跳到详情页 -> 详情页onMounted时调用getProductDetail接口 -> Axios拦截器自动带上token -> 后端Controller接收请求 -> Service调用Mapper查询商品信息和卖家信息 -> 返回Result对象 -> Axios响应拦截器解包 -> 前端把数据渲染到页面上。下单流程类似,但多了一层创建订单和支付。能够把这个链路讲清楚,基本证明你不是只复制了一个项目。
7.5 项目里你自认为最复杂、最值得说的部分是什么
这一个问题直接决定面试的走向。我会选回收预约的状态流转+回收员并发抢单来展开。因为它不只是CRUD,还涉及状态机约束、并发控制、事务边界、用户余额变更一致性。可以先讲表结构设计,再讲状态机合法流转,再讲乐观锁抢单,最后讲事务保证余额和状态的同步更新。整个过程有设计有细节,比干巴巴说“我做了商品CRUD”强得多。
最后再分享一个实打实的经验:这个项目从零开始写,先把ER图和接口文档定下来,再动手敲代码,整体开发周期能压缩不少。所有涉及金额的地方一律用BigDecimal,别用double;所有涉及状态变更的接口,响应里都返回当前最新状态,前端拿到之后立即刷新页面,体验会好很多。我在写这套系统的时候,最大的体会是:代码只是业务逻辑的载体,真正花时间的是把流程想清楚、把状态定义清楚,这些前期工作做得越扎实,后期写起来越顺利,答辩的时候也越有底气。