ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

校园二手物品置换系统:SpringBoot+Vue前后端分离实战全解析

校园二手物品置换系统:SpringBoot+Vue前后端分离实战全解析 每年毕业季的宿舍楼下总会上演同一幕成箱的考研教材、还能用的台灯、小风扇被当作废品处理而另一边刚入学的学弟学妹又在花几百块钱重新买一遍。我做校园二手物品置换系统这个项目的出发点就是这种最真实的校园场景——让闲置物品在校园内部完成流转而不是照搬一个普通的商城模板交差。这篇文章整理了我从需求分析、技术选型、数据库设计到SpringBoot后端和Vue前端完整实现的全过程重点会放在那些跑通之后回头看才发现的细节上。如果你正在做类似的毕设或者想从头完整搭一个前后端分离项目这套思路可以直接借鉴。1. 需求梳理校园二手置换和普通商城到底差在哪1.1 校园场景决定了系统的独特逻辑很多人在做校园二手系统时第一反应是这不就是个简化版闲鱼吗然后照着电商模子开始建表商品表、购物车、订单、支付。做到一半才发现校园二手场景和电商有几个本质区别第一没有支付环节。学生之间的交易基本是线下见面扫码转账或者直接以物换物系统根本不需要接入支付渠道。这在简化开发难度的同时也意味着订单状态不能照搬电商的待付款-已付款-已发货-已收货得重新设计一套适合当面交易的流程。第二置换二字不只是买卖。系统里得有可交换这个概念。学生发一本书可能标注想换一个U盘或者可议价也可交换。所以物品数据模型里要有一个标识位表示是否接受交换这样订单类型也要区分购买和交换。第三交易半径极短。校园交易的交付方式以约在校门口为主平台不需要物流。这带来一个好处订单生命周期短当天发起、当天确认、当天完成都很正常。所以状态机的流转应当简单直接不要让用户在页面里点来点去。搞清楚这三点之后功能模块就很清晰了——核心不是支付链路而是物品信息的管理、供需双方的撮合、线下交易的确认。1.2 角色、业务流和功能清单这个系统只有两种角色普通学生用户和管理员。学生用户的核心操作流是发布闲置物品填写标题、描述、分类、期望价格、是否接受交换上传图片浏览首页物品列表按分类筛选、按关键词搜索进入物品详情页查看发布者信息进行留言咨询或直接发起购买/交换在我的发布我的订单里管理自己发布的物品和处理收到的请求收藏感兴趣的物品方便后续查看管理员的核心操作则是用户管理、物品管理审核违规物品、下架、数据统计发布量、成交量的概况。模块划分之后我再把每个模块拆成接口清单。这一步看着琐碎但做毕设的时候特别有用——很多同学代码写到一半发现Controller和Service对不上就是因为没在动手前把接口定好。我建议你打开一个备忘录按模块把接口列表写出来比如发布物品、修改物品、删除物品、分页查询物品、获取物品详情、创建订单、确认订单、完成订单、取消订单、留言、收藏。每个接口标注请求方式、请求参数、返回数据结构。前后端都按这份清单开发联调时效率会高很多。2. 技术选型为什么SpringBootVue是最稳的组合2.1 前后端分离的收益和代价先说实话如果只图把功能做完用SpringBoot写模板引擎Thymeleaf其实更快一个工程搞定所有页面不用处理跨域不用关心两个端口怎么联调。但我做这个项目时还是选了前后端分离理由很实际一是思路清晰。前端只关心页面交互通过Axios调接口后端只关心业务逻辑不掺和HTML渲染。出了Bug排查范围直接缩小一半。二是展示效果好。Vue配合Element Plus做出来的界面无论在毕设答辩还是实际使用中颜值都明显高于服务端渲染的页面。三是可迁移性强。这套前后端分离的架构学会一次以后做任何管理系统都能复用值得投入成本。代价就是前面说的那些需要处理跨域、需要维护两套工程、打包部署时要考虑静态资源怎么放。但这些坑都在可控范围内下文会逐一说明。2.2 版本选型是第一个坑SpringBoot用哪个版本、Vue用2还是3这是我在各种技术群里被问得最多的问题也是热搜词里springboot版本太高vue安装及环境配置的根源。先说结论SpringBoot用2.7.x不要用3.x前端用Vue3加Vite组件库用Element Plus。我见过太多人一上来就下载最新的SpringBoot 3.x然后发现JDK版本要17、原来的javax包全变成了jakarta、网上查到的资料全是旧写法光环境就折腾了一星期。SpringBoot 2.7.x是2.x的最终版本稳定、兼容JDK8、资料最全对毕设和中小型项目完全够用。JDK我用的是1.8没别的原因就是省心。如果你将来想接触更新的技术栈再单独学SpringBoot 3也不迟但别在项目起步时给自己设置这层障碍。前端我选Vue3而不是Vue2是因为Element Plus、Vite这些配套生态已经是主流了。Vite相比Webpack最大的感受就是快冷启动几乎秒开热更新即时生效。但它有个小门槛环境要求Node.js版本不能太低建议装16.x以上。安装完Node.js之后用npm create vitelatest初始化项目选择vue模板然后依次安装vue-router、pinia、axios、element-plus。这套流程跑通之后基础环境就算定了。2.3 核心依赖清单和工程结构我习惯把依赖记录成一份清单避免装一半漏一半后端pom.xml核心依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.18/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /dependencyMyBatis-Plus能省掉大部分单表CRUD的XML内置分页插件代码量直接砍半。Hutool用来处理日期、生成随机文件名、复制Bean这些杂活。Lombok用来省掉getter/setter。后端包结构com.campus.secondhand ├── controller ├── service ├── mapper ├── entity ├── common │ ├── Result │ ├── ResultCode │ └── JwtUtil ├── config │ ├── CorsConfig │ ├── MybatisPlusConfig │ └── WebMvcConfig └── interceptor └── JwtInterceptor前端目录结构src ├── api // 接口请求封装 ├── assets ├── components // 通用组件 ├── router // 路由配置 ├── store // Pinia状态管理 ├── views // 页面 ├── utils // 工具类axios封装 App.vue main.js3. 数据库设计四张核心表把业务状态跑通3.1 核心表结构与字段解释设计数据库时我给自己定的原则是虽然功能模块看起来多但核心表不超过六张字段宁缺毋滥。复杂度过高写Service时会把自己绕晕。我的表结构是这样的用户表 userCREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, phone varchar(20) DEFAULT NULL COMMENT 联系电话, college varchar(100) DEFAULT NULL COMMENT 学院专业信息, role tinyint DEFAULT 0 COMMENT 0学生 1管理员, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, deleted tinyint DEFAULT 0 COMMENT 逻辑删除, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;物品表 itemCREATE TABLE item ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 发布者ID, title varchar(100) NOT NULL COMMENT 标题, description text COMMENT 详细描述, category_id bigint DEFAULT NULL COMMENT 分类ID, price decimal(10,2) DEFAULT NULL COMMENT 期望价格, original_price decimal(10,2) DEFAULT NULL COMMENT 原价用于展示成色参考, is_exchange tinyint DEFAULT 0 COMMENT 是否接受交换 0否 1是, status tinyint DEFAULT 0 COMMENT 0在售 1已下架 2已成交, view_count int DEFAULT 0 COMMENT 浏览量, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, deleted tinyint DEFAULT 0 COMMENT 逻辑删除, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;物品图片表 item_image独立出来是为了方便上传多图一个物品对应多条图片记录id, item_id, url, sort_order置换订单表 trade_orderCREATE TABLE trade_order ( id bigint NOT NULL AUTO_INCREMENT, item_id bigint NOT NULL COMMENT 物品ID, from_user_id bigint NOT NULL COMMENT 发起方用户ID, to_user_id bigint NOT NULL COMMENT 物品发布者ID, order_type tinyint DEFAULT 0 COMMENT 0购买 1交换, message varchar(255) DEFAULT NULL COMMENT 附言比如希望交换的物品, status tinyint DEFAULT 0 COMMENT 0待确认 1已确认 2已完成 3已取消 4已拒绝, create_time datetime DEFAULT NULL, update_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;再加上分类表 category 和收藏表 favorite六张表就齐了。留言功能可以复用订单里的附言或者单独加一张comment表看细分程度。我建议做物品详情页的留言区这能明显提升交互感加一张item_comment表即可字段是 id、item_id、user_id、content、create_time。3.2 置换订单的状态机设计这是整个项目里最需要想清楚的地方。我设计的订单状态流转如下学生B在物品详情页发起购买或交换请求生成订单状态为0待确认学生A发布者在我收到的订单里看到请求可以选择确认、拒绝买家确认后双方线下见面完成交易买家在系统里点击确认完成订单变为2已完成如果中途任何一方反悔可以取消订单状态变为3已取消这一段代码逻辑不复杂但要注意物品状态要与订单状态联动。我的规则是订单待确认时物品仍然显示为在售因为此时还没有谈拢订单已确认时立刻把物品状态改为1已下架避免被其他人重复下单订单已取消或已拒绝时把物品状态恢复为0在售订单已完成时物品状态改为2已成交永久下架如果不做这套联动就会出现一个Bug物品还在页面展示两个人同时下单最后根本不知道谁成交了。我当时就是写完订单之后忘了同步物品状态测试时用两个账号同时抢同一件物品数据就乱了。后来在Service层单独封装了一个updateItemStatusByOrder方法所有状态变更都走这个方法问题才彻底解决。3.3 几个容易被忽略的字段细节逻辑删除字段。用户删除物品时不应该物理删除记录否则历史订单关联就断了。MyBatis-Plus的逻辑删除功能配置一下就能用deleted字段默认值为0删除操作变成update。时间字段的默认值。建议在实体类里用TableField(fill FieldFill.INSERT)配合MetaObjectHandler自动填充创建时间和更新时间省得每个Service里手动set一遍。view_count字段。这个字段看着不起眼但物品列表页排序常常会用浏览量作为热度指标。在查询详情接口时做个update view_count view_count 1。索引。小项目很多人不建索引但查询量大起来还是很明显的。我建议在item表的status、category_id、create_time上建索引在trade_order表的item_id、from_user_id、to_user_id上建索引。4. 后端实现从鉴权到订单流转的关键代码4.1 JWT登录鉴权拦截器加注解登录模块我选了JWT方案流程是用户登录成功之后后端生成一个包含用户id和角色的Token返回给前端前端存在localStorage里每次请求在Header里带上后端拦截器校验Token解析出当前用户信息放到request上下文中。JWT工具类核心代码如下public class JwtUtil { private static final String SECRET campus-secondhand-secret-key; public static String generateToken(User user) { return Jwts.builder() .setSubject(user.getId().toString()) .claim(role, user.getRole()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }拦截器里要做三件事放行登录接口、校验Token存在且有效、把用户信息塞进request。我用了一个自定义注解RequireLogin标记需要登录才能访问的接口拦截器里判断HandlerMethod上有没有这个注解没有就直接放行。这样不需要在WebMvcConfig里写一长串排除路径维护起来更直观。Component public class JwtInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod method (HandlerMethod) handler; if (!method.hasMethodAnnotation(RequireLogin.class)) { return true; } String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { throw new BusinessException(401, 未登录); } try { Claims claims JwtUtil.parseToken(token.substring(7)); request.setAttribute(userId, Long.parseLong(claims.getSubject())); } catch (Exception e) { throw new BusinessException(401, 登录状态已过期); } return true; } }统一返回结果类ResultT很关键它保证前后端接口风格一致{ code: 200, message: success, data: ... }。配合全局异常处理器RestControllerAdvice把业务异常和参数校验异常都转成统一结构返回前端处理起来轻松很多。4.2 物品发布图片上传的两种方案图片上传是毕设答辩时最容易演示的功能也是网上资料最杂的地方。我的做法是本地存储不引OSS。原因很简单你手头没有阿里云账号就算有调试和部署都多一层依赖。本地存储的思路是前端把图片文件传到后端的/api/upload接口后端把文件保存到服务器的某个目录下然后把访问路径返回给前端。要解决的核心问题是上传的图片怎么被访问到。SpringBoot默认的静态资源目录是classpath:/static/如果我把图片写到磁盘某个路径前端是访问不到的。解决办法是在WebMvcConfig里配置资源映射Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath System.getProperty(user.dir) /upload/; registry.addResourceHandler(/upload/**) .addResourceLocations(file: uploadPath); } }这样前端访问http://localhost:8080/upload/xxx.jpg就能直接看到图片。上传时注意文件名不要用用户原始文件名时间戳加随机数拼接防止重名。上传Controller核心代码PostMapping(/api/upload) RequireLogin public ResultString upload(RequestParam(file) MultipartFile file) { if (file.isEmpty()) { throw new BusinessException(400, 文件不能为空); } String originalFilename file.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName System.currentTimeMillis() _ RandomUtil.randomString(8) ext; Path path Paths.get(uploadDir, fileName); file.transferTo(path.toFile()); return Result.success(/upload/ fileName); }4.3 订单状态流转Service层怎么控制订单模块是业务的核心我单独写了TradeOrderService状态变更逻辑全部收敛在这里。买家发起订单时有一个很容易漏掉的检查目标物品必须是在售状态。如果物品已经被人下架或成交前端页面还没刷新用户还是能点立即购买按钮后端必须兜住这层校验。public void createOrder(TradeOrder order) { Item item itemMapper.selectById(order.getItemId()); if (item null) { throw new BusinessException(400, 物品不存在); } if (item.getStatus() ! 0) { throw new BusinessException(400, 该物品当前不可下单); } if (item.getUserId().equals(order.getFromUserId())) { throw new BusinessException(400, 不能购买自己发布的物品); } order.setToUserId(item.getUserId()); order.setStatus(0); order.setCreateTime(new Date()); tradeOrderMapper.insert(order); }确认订单和完成订单的处理类似确认订单时除了更新订单状态还要同步物品状态。这里我把更新物品状态封装成了一个受控方法Transactional public void confirmOrder(Long orderId, Long userId) { TradeOrder order getOrderById(orderId); // 校验操作者必须是物品发布者 if (!order.getToUserId().equals(userId)) { throw new BusinessException(403, 无权限操作该订单); } if (order.getStatus() ! 0) { throw new BusinessException(400, 当前状态不允许确认); } order.setStatus(1); tradeOrderMapper.updateById(order); Item item itemMapper.selectById(order.getItemId()); item.setStatus(1); // 已下架 itemMapper.updateById(item); }注意这里必须加Transactional因为订单状态和物品状态要一起变更任何一个失败都要回滚。关于MyBatis-Plus的分页插件这里有个很多人踩的坑3.5.3版本的分页插件不再通过PaginationInnerInterceptor自动生效需要在配置类里手动声明Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }不配这个Page分页查询返回的总记录数永远是0。我当初排查了很久才发现是版本升级后的配置遗漏。4.4 后端最容易踩的版本坑我在热搜词里看到springboot默认使用cglib代理这个确实值得说一下。从SpringBoot 2.x开始Spring对接口代理和类代理的默认策略发生了变化。很多人写自定义注解做权限校验时发现注解在类上生效、在方法上不生效或者Controller里拿不到request属性多半和代理模式有关。我的建议是拦截器用HandlerInterceptor处理不用AOP做登录校验这样能避开这个坑。另一个是跨域配置。前端开发服务器跑在5173端口后端跑在8080端口浏览器会拦截跨域请求。在配置类里写一个CorsConfig即可Configuration public class CorsConfig { Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedMethod(*); config.addAllowedHeader(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); } }如果你是带着Cookie做会话管理setAllowCredentials(true)必须和addAllowedOriginPattern(*)配合使用不能直接addAllowedOrigin(*)否则浏览器会报错。5. 前端实现Vue3项目落地与页面拆解5.1 初始化项目Vite、Element Plus、Pinia前端项目我用Vite创建npm create vitelatest frontend -- --template vue cd frontend npm install npm install vue-router4 pinia axios element-plus npm install element-plus/icons-vue主入口main.js里需要完整引入Element Plusimport { createApp } from vue import App from ./App.vue import router from ./router import { createPinia } from pinia import ElementPlus from element-plus import element-plus/dist/index.css const app createApp(App) app.use(router) app.use(createPinia()) app.use(ElementPlus) app.mount(#app)Element Plus的按需引入可以有效减小包体积但毕设项目图省事完整引入完全没问题。5.2 路由权限和Axios拦截器路由配置里我只区分了两种页面需要登录后访问的发布页、订单管理页、个人信息页和公开页面首页、物品详情页。实现方式用路由守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requireAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) } else { next() } })Axios封装是我比较在意的一部分。我在utils/request.js里统一做了三件事请求头自动带Token、响应拦截统一处理业务码、401跳转登录页。import axios from axios import { ElMessage } from element-plus import router from ../router const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response { const res response.data if (res.code 200) { return res.data } ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(error.message || 网络错误) return Promise.reject(error) } ) export default request需要注意的一点是baseURL。开发环境下我在vite.config.js里配了代理export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端代码里所有请求都写/api/xxx不需要写完整地址。部署阶段还可以直接把前端dist放进SpringBoot的static目录接口路径完全一致不用改任何代码。5.3 核心页面和组件怎么写首页是用户第一眼看到的东西我用Element Plus的el-card加栅格布局展示物品卡片。每张卡片包含物品图片、标题、价格、分类标签和发布时间。页面数据通过el-pagination分页加载。发布页的表单用el-form包含标题、分类下拉框、价格、原价、是否接受交换的开关、描述文本域和图片上传。图片上传这块Element Plus的el-upload组件配合自定义上传方法el-upload v-model:file-listfileList action/api/upload namefile :headersuploadHeaders list-typepicture-card :limit6 :on-successhandleUploadSuccess /el-upload物品详情页的订单发起按钮做了两个入口如果物品允许交换显示发起交换否则显示购买。点击后弹出一个对话框填写附言——这个附言对交换场景特别重要因为用户需要写明我想用XX换你的XX。我的订单列表页是整个系统里最能体现状态驱动的页面。用el-tabs区分我发布的和我收到的两类订单每张订单卡片显示物品信息、对方昵称、状态标签。对不同状态显示对应操作按钮待确认的订单显示确认和拒绝已确认的订单显示完成交易。从组件拆分角度我把OrderActionButtons抽成了一个独立组件根据订单状态动态渲染按钮父组件监听点击事件调用接口。这样我的发布页面和我的购买页面可以复用同一套逻辑。5.4 前后端联调时的高频报错前后端联调时我遇到的三个最典型的问题第一个是时间格式不一致。Java后端默认返回的时间格式是2025-01-05T12:30:00.00008:00前端直接显示很难看。解决方式是在后端的application.yml里配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8第二个是字段命名。数据库字段是user_id这种下划线风格Java实体类用驼峰userIdMyBatis-Plus默认开启了驼峰映射前端返回的就是userId。这个没问题。但如果你字段命名不统一前端拿到的数据对不上号排查起来非常痛苦。建议尽早约定统一。第三个是图片路径。开发环境图片URL是/upload/xxx.jpg代理模式下前端能正常访问。但如果后端直接返回了绝对路径http://localhost:8080/upload/xxx.jpg部署到服务器时IP一换就会失效。所以我统一返回相对路径让前端用当前域名拼接。这样本地开发和服务器部署都不用改代码。6. 打包部署从本地跑通到上线发布6.1 前端打包放进SpringBoot程序写完最重要的一件事是能给别人演示。很多人的毕设还停留在本地能跑答辩时换一台电脑就起不来。我建议最终把前端打包塞进后端工程做成一个可以直接运行的jar包。操作很简单前端执行npm run build生成dist目录把里面的文件复制到后端项目的src/main/resources/static下。然后用Maven打包mvn clean package生成的后端jar包自带页面运行java -jar second-hand-0.0.1.jar浏览器直接访问http://localhost:8080就能看到完整网站。这个方案也顺带解决了跨域问题因为前端页面和后端接口同源。这里有个细节需要注意Vue项目默认的路由模式是createWebHistory直出模式下刷新非首页路由会报404因为后端找不到对应路径。两个解决办法一是后端写一个转发Controller把非/api开头的路径都转发到index.html二是前端改用createWebHashHistoryURL带上#/符号。演示项目用hash模式最省事一行代码改完不用动后端。6.2 IDEA运行配置和端口问题热搜词里有一条idea 2026怎么配置springboot服务编辑配置数据比如启动端口这确实是新手高频问题。其实不用在IDEA里做什么复杂配置SpringBoot的端口在application.yml里改server: port: 8080 servlet: context-path: /IDEA的Run Configuration里只需要指定Main类带有SpringBootApplication的那个类和JRE版本即可。如果你改了端口注意前端代理的目标端口也要跟着改。另外Maven在IDEA里首次加载依赖需要时间如果看到红色报错先点一下Maven侧栏的刷新按钮绝大多数情况是依赖还没下载完。6.3 我建议的部署顺序整个项目从零到部署我建议按下面的顺序推进而不是想到哪写到哪先建数据库和表结构写入几条测试数据搭后端工程骨架把统一返回、异常处理、登录注册跑通完成物品模块的增删改查和分页查询用Postman验证接口搭前端工程登录注册页面接上后端做首页列表和详情页把物品链路打通做发布功能、图片上传做订单模块这是最复杂的部分前后端同步联调做留言、收藏等辅助模块收尾前端打包、塞进后端、生成jar包、整体演示走一遍这套顺序的核心思路是先打通主链路再完善分支功能。如果一上来就抠收藏、留言这些边角功能核心的订单流转反而容易写崩。最后分享一点个人体会校园二手置换系统这类题目做出来不难但想做得像样还是需要花心思的。我做完之后最大的感受是业务状态的设计比技术本身更重要——订单和物品的状态联动、角色权限的边界、各类异常情况的兜底这些东西想清楚了代码写起来行云流水不想清楚写完了也是在修修补补打补丁。如果你正在做类似的系统我建议你在写代码之前先花一整天时间把需求文档和数据库设计完整写出来哪怕只是自己看得懂的表格。这一步做完后面能省一半的返工时间。祝你也能顺利把项目跑起来。
返回列表