基于SpringBoot+Vue的宠物交易管理平台设计与实现
前后端分离架构在校园毕设和中小型项目中几乎成了标配,SpringBoot负责后端接口,Vue负责前端页面,两者配合能快速搭建一个可直接演示、可扩展的完整系统。这篇文章就围绕“宠物交易管理平台”这个具体场景,把从技术选型、数据库设计、后端接口实现到前端页面联调的完整过程拆开讲清楚。如果你正在做类似的SpringBoot+Vue项目,或者想了解一个真实的交易类系统是怎么落地的,这篇文章应该能给你一份可以直接参考的路线图。
这个平台的核心需求很明确:让卖家能发布宠物信息,让买家能浏览、搜索、下单购买,平台管理员负责审核信息、管理订单和用户。听起来不复杂,但真正动手做的时候,会发现里面涉及权限控制、状态流转、文件上传、搜索排序、订单超时处理等一系列细节问题。我会把每个环节的关键决策和踩坑点都写出来,方便你照着做或者改进。
1. 项目整体设计与思路拆解
1.1 为什么选择SpringBoot+Vue这套组合
先说技术选型。SpringBoot在Java后端领域基本是事实标准,内置Tomcat、自动配置、起步依赖这些特性让项目初始化成本降到极低。以前用SSH或SSM搭建项目,光配置文件就能写半天,SpringBoot用spring-boot-starter-web一个依赖就把Web环境带起来了,这对快速开发一个管理平台来说效率提升非常明显。
Vue这边,渐进式框架的特性让前端开发可以按需引入,不需要一开始就把全家桶全上。配合Vue Router做页面跳转、Vuex或Pinia做状态管理、Axios做HTTP请求,基本覆盖了一个管理后台的所有需求。再加上Element UI或者Element Plus这种组件库,表格、表单、弹窗、分页这些常见界面元素可以直接复用,开发速度比手写DOM操作快一个量级。
前后端分离带来的另一个好处是部署灵活。后端打包成Jar包跑在服务器上,前端构建成静态文件扔到Nginx里,两个服务可以独立扩容、独立维护。对于毕设或者个人项目来说,这种架构即使后续要加功能、换部署方式,改动成本也相对可控。
1.2 平台功能模块怎么划分才合理
在动手写代码之前,先把功能模块理清楚。我建议按角色来划分,这样权限设计和接口设计都会自然很多。
- 买家端:注册登录、浏览宠物列表、按品种/价格/地区筛选、查看宠物详情、下单购买、查看订单状态、确认收货、评价(可选)。
- 卖家端:发布宠物信息、管理自己发布的宠物(上下架、修改、删除)、查看收到的订单、处理订单(发货/标记已售)。
- 管理后台:用户管理(禁用/启用账号)、宠物信息审核(避免违规内容)、订单管理(查看全部订单、处理纠纷)、品类管理(宠物品种分类维护)。
这个划分是基于实际交易场景反推出来的。做交易类平台,订单状态和宠物上下架状态是整个系统的核心,谁在什么阶段可以做什么操作,必须在设计阶段就定清楚,否则后面联调的时候会到处打架。
1.3 数据库设计的关键点
数据库设计我直接说结论:至少需要五张核心表,外加一张图片表。
- user 用户表:id、username、password(BCrypt加密存储)、phone、avatar、role(BUYER/SELLER/ADMIN)、status(0禁用 1正常)、create_time。
- pet 宠物信息表:id、seller_id、category_id、name、breed、age、gender、price、description、cover_image、status(0待审核 1在售 2已下架 3已售出)、create_time、update_time。
- orders 订单表:id、order_no(唯一订单编号)、pet_id、buyer_id、seller_id、amount、status(0待付款 1待发货 2待收货 3已完成 4已取消)、create_time、pay_time、finish_time。
- category 宠物品类表:id、name、sort_order。
- pet_image 宠物图片表:id、pet_id、image_url、sort_order。
设计的时候有几个容易踩的坑:一是宠物价格必须用Decimal而不是Float,否则浮点误差会让你对账对到怀疑人生;二是订单编号不要用数据库自增ID直接暴露给前端,很容易被遍历,建议用时间戳加随机数生成;三是所有表都加上创建时间和更新时间字段,排查问题的时候会省很多事。
2. 后端核心实现:从零搭建SpringBoot服务
2.1 项目初始化和依赖配置
后端工程建议直接用Spring Initializr生成,地址是start.spring.io。选Java 8或者Java 11都行,SpringBoot版本建议用2.7.x,这个版本比较稳定,网上资料也多。当然如果时间充裕,直接用SpringBoot 3.x也没问题,只是要注意javax包名变成jakarta,一些老教程里的代码要自己转换一下。
核心依赖就这几个:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>ORM这边我用了Spring Data JPA,不用MyBatis的原因很简单:这个项目的表结构和关联关系不算复杂,JPA的自动建表和Repository抽象能省掉大量CRUD代码,而且写起来非常快。如果你更习惯写SQL,换成MyBatis-Plus也完全没问题,接口设计上没有本质区别。
这里要检查一下版本对应关系。SpringBoot 2.7.x对应的是Java 8+,如果本机装了高版本JDK,需要在pom.xml里明确指定<java.version>。我遇到过最典型的场景就是本机JDK是17,结果工程还是按Java 8编译,启动直接报UnsupportedClassVersionError。这个坑很基础,但确实会卡人半天。
2.2 配置文件里的几个关键项
application.yml里有几个配置直接决定了项目能不能跑起来,值得逐项说清楚。首先是数据源配置:
spring: datasource: url: jdbc:mysql://localhost:3306/pet_trade?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: trueddl-auto设置为update适合开发阶段,实体类改了字段会自动同步到数据库表结构。但上线前一定要改成validate或者直接用数据库脚本初始化,否则生产环境里执行DDL语句风险很大。
JWT配置需要自己加一段:
jwt: secret: pet-trade-secret-key-please-change-in-production expire: 604800这里的secret是签名秘钥,生产环境必须换成一长串随机字符串,否则Token很容易被伪造。expire单位是秒,7天有效期对大部分交易场景够用了。
文件上传配置也别漏掉,宠物平台图片是最核心的内容载体,上传大小必须调大:
spring: servlet: multipart: max-file-size: 10MB max-request-size: 50MB不配置这个的话,默认1MB的限制会让你传一张宠物照片都报错,属于新手最容易踩的坑之一。
2.3 实体类设计的一个参考写法
JPA的实体类设计直接影响后面所有业务代码,这里给一个宠物信息表的实体示例:
@Entity @Table(name = "pet") @Data public class Pet { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private Long sellerId; private Long categoryId; private String name; private String breed; private Integer age; private String gender; @Column(precision = 10, scale = 2) private BigDecimal price; @Column(columnDefinition = "TEXT") private String description; private String coverImage; private Integer status; @Column(name = "create_time") private LocalDateTime createTime; @Column(name = "update_time") private LocalDateTime updateTime; }用Lombok的@Data注解省掉getter/setter,这个在项目里已经是常规操作了。BigDecimal对应数据库的DECIMAL(10,2),这个精度足够覆盖宠物交易的价格范围。status字段用Integer而不是枚举类型,主要是为了方便扩展状态,比如以后要加“已预约”状态,只需要新增一个数字映射就行,不用改表结构。
这里有个细节:JPA的驼峰命名自动映射到数据库下划线字段,靠的是spring.jpa.hibernate.naming.physical-strategy的默认策略。如果你没配这个属性,实体里写了createTime,数据库里自动生成的就是create_time,这正好符合数据库规范。如果用MyBatis反而要额外配置驼峰映射,这是JPA的一个隐性优势。
2.4 核心接口实现:宠物发布与交易流程
后端接口设计要围绕业务流程来,不能只做简单的增删改查。这个平台最核心的处理逻辑有两个:宠物发布的状态流转和订单的状态流转。
发布宠物接口大概是这样的逻辑:
@PostMapping("/api/pet/publish") public Result publishPet(@RequestBody PetDTO petDTO, @RequestAttribute("userId") Long userId) { // 1. 校验当前用户是否卖家角色 User user = userRepository.findById(userId).orElse(null); if (user == null || !RoleEnum.SELLER.name().equals(user.getRole())) { return Result.error("只有卖家才能发布宠物"); } // 2. 组装实体,初始状态为待审核 Pet pet = new Pet(); BeanUtils.copyProperties(petDTO, pet); pet.setSellerId(userId); pet.setStatus(PetStatusEnum.PENDING.getCode()); pet.setCreateTime(LocalDateTime.now()); // 3. 保存 petRepository.save(pet); return Result.success("发布成功,等待平台审核"); }这里用@RequestAttribute获取当前登录用户ID,是因为在JWT拦截器里已经把Token解析出来的用户信息放进了Request上下文,接口里直接取即可。这个做法比在每个接口里手动解析Token要干净很多。后面我们会讲到拦截器的写法。
订单创建接口的要点在于:同一只宠物不能被重复下单。这个校验放在应用层是不够的,必须依赖数据库层面的唯一约束。当时我用的是在orders表里给pet_id加唯一索引,然后在代码里捕获DataIntegrityViolationException异常并给出“该宠物已被下单”的提示。如果只在代码里校验,高并发场景下两个买家同时看到同一只宠物并且同时下单,可能出现重复订单。这种业务细节面试的时候讲出来很加分,说明你真的考虑过并发场景。
订单超时未支付的处理,我只用了最简单的方案:下单时记录create_time,查询接口里判断超过30分钟且状态为待付款,就自动把它改成已取消,同时把宠物状态恢复为在售。这个方案在数据量小的时候够用,但如果要做生产级处理,应该引入延迟队列或者定时任务来兜底。毕设阶段用查询时判断的方案也能说得通,重点是你知道这个方案有什么局限。
2.5 JWT登录认证与拦截器的实现
登录认证这块,我直接采用JWT方案,不是因为它是完美的,而是它在前后端分离场景下足够简单,无状态,不需要在服务端维护会话。写一个拦截器统一处理Token校验:
@Component public class JwtInterceptor implements HandlerInterceptor { @Autowired private JwtUtils jwtUtils; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录注册等白名单接口 String uri = request.getRequestURI(); if (uri.startsWith("/api/auth/")) { return true; } // 从Header获取Token String token = request.getHeader("Authorization"); if (StringUtils.hasText(token) && token.startsWith("Bearer ")) { token = token.substring(7); } // 解析Token Long userId = jwtUtils.parseToken(token); if (userId == null) { response.setStatus(401); response.getWriter().write("{\"code\":401,\"message\":\"未登录或Token已过期\"}"); return false; } request.setAttribute("userId", userId); return true; } }这个拦截器需要在WebMvcConfig里注册,同时配置好放行路径。注意前端发的请求要带上Authorization: Bearer <token>这个Header,这个约定要提前定好,前后端联调的时候才不会因为字段名不一致而扯皮。我见过很多团队在Authorization、token、access_token这几个命名之间来回横跳,纯属浪费时间。
2.6 文件上传与静态资源映射
宠物平台必然涉及图片上传,我的处理方式是:前端上传图片到后端的/api/file/upload接口,后端把文件保存到本地磁盘的/uploads/目录,然后把访问URL返回给前端。这里有个容易吃亏的地方:本地路径不能直接当图片URL用,需要配置静态资源映射。
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { String uploadPath = System.getProperty("user.dir") + "/uploads/"; registry.addResourceHandler("/uploads/**") .addResourceLocations("file:" + uploadPath); } }这样配置之后,前端拿到/uploads/xxx.jpg这个地址就能直接访问图片。真正上线的时候一般会把文件传到OSS或者云存储,但只要本地方案能跑通,换云端方案只是改一个Service实现类的事。
3. 前端核心实现:Vue页面与全链路联调
3.1 Vue工程初始化与依赖安装
前端用Vue CLI创建工程是最稳的方案:
npm install -g @vue/cli vue create pet-trade-frontend创建的时候选Manually select features,勾上Router和Vuex(或者Pinia),再加一下Axios和Element Plus。Node版本要注意,Vue CLI比较挑Node环境,我当时用Node 16是最省事的,后面升级到Node 18也没出乱子,但Node 20在一些老项目上会有openssl兼容性问题,如果报错就退回16。
依赖安装完,一个比较隐蔽的坑是npm install速度太慢或者卡住。建议用国内镜像源:
npm config set registry https://registry.npmmirror.com这个操作能让你少等至少五分钟。之前有同学用默认源,装到一半网络抖动,依赖装残了,项目直接起不来,最后只能删掉node_modules重新装,白折腾大半天。
3.2 路由设计里的小技巧
路由这块,我建议按功能模块划分组件:
const routes = [ { path: '/', component: HomeView }, { path: '/pets', component: PetListView }, { path: '/pets/:id', component: PetDetailView }, { path: '/publish', component: PublishView, meta: { requireAuth: true, role: 'SELLER' } }, { path: '/orders', component: OrderListView, meta: { requireAuth: true } }, { path: '/admin', component: AdminView, meta: { requireAuth: true, role: 'ADMIN' } }, { path: '/login', component: LoginView }, { path: '/register', component: RegisterView } ]这里要注意meta字段的用法。前端路由守卫根据meta.requireAuth决定是否需要登录,根据meta.role决定是否校验角色权限:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requireAuth && !token) { next('/login') return } // 角色校验可以后端再兜底,前端只是提升体验 next() })前端权限校验不要当真,它只能防止误操作,真正的安全防线在后端。如果用户绕过前端直接调接口,后端必须能鉴权拦住,否则就是重大安全隐患。
路由懒加载我也建议全部用上:
component: () => import('../views/PublishView.vue')这样首屏只加载必要的JS,其他页面按需加载,体验差别还挺明显的。项目小的时候无所谓,但养成分块习惯对以后做商业项目有帮助。
3.3 Axios请求封装与统一错误处理
Axios封装是前端工程质量的关键,不要在每个组件里直接axios.get(url),一定要统一封装一个请求模块:
import axios from 'axios' import { ElMessage } from 'element-plus' import router from '../router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动附加Token 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) { ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response?.status === 401) { localStorage.removeItem('token') router.push('/login') } else { ElMessage.error('网络异常,请稍后重试') } return Promise.reject(error) } ) export default request这套封装的好处是:Token附加和401跳转逻辑只写一遍,全局生效。最重要的是和后端约定好响应格式是{code, message, data},这样前端拦截器才能统一处理。这个约定最好在项目一开始就定死,后面两边都不会跑偏。
开发时还要配一下Vite的代理,这样后端启动在8080、前端Vite跑在5173时,请求可以顺利转发:
// vite.config.js server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }不配置这个的话,前端所有请求都会因为跨域被浏览器拦下来,后端写了@CrossOrigin也可能在携带自定义Header时遇到预检请求问题。
3.4 核心页面:宠物列表、详情与发布表单
宠物列表页是整个平台的流量入口。我用Element Plus的卡片布局来展示宠物信息,配合分页组件el-pagination。这里有个细节:分页参数要同时传page和size给后端,后端用Pageable接收,返回{records, total}这样的结构,前端才能把分页做完整。
搜索筛选这块,我用一个searchForm对象绑定搜索条件:关键词(宠物名/品种)、品类ID、价格区间、状态(在售/已卖)。每次搜索条件变化就重新请求列表接口:
const loadPets = async () => { const res = await request.get('/pet/list', { params: searchForm.value }) petList.value = res.data.records total.value = res.data.total }宠物详情页要展示多张图片、宠物基本信息、卖家联系方式。这里建议用el-carousel做图片轮播,因为一张主图往往表达不清楚宠物的状态,多图轮播是交易平台的标配。
发布页面是表单校验的重点,因为发布信息的质量直接决定平台的整体内容质量。我用el-form的rules做了必填和格式校验,价格字段用数字输入框并加上精准度校验,描述字段限制长度并实时显示剩余字数。发布成功后自动跳到“我的发布”页面,方便卖家继续修改或下架。
3.5 前端权限控制:路由守卫 + 菜单显隐 + 接口拦截
前端的权限控制是三层组合拳:路由守卫控制页面访问,菜单根据角色动态渲染,接口请求靠后端Token校验兜底。
菜单显隐是最常见的按钮级权限控制。后台管理页只在用户角色是ADMIN时显示,卖家中心只在角色是SELLER时显示。我用的方法是在Layout组件的mounted里读取localStorage里的用户信息,然后v-if判断:
<el-menu-item v-if="userInfo.role === 'ADMIN'" index="/admin">平台管理</el-menu-item> <el-menu-item v-if="userInfo.role === 'SELLER'" index="/publish">发布宠物</el-menu-item>这层控制纯粹是UI层面的优化,真正受到保护的是后端的接口校验。后端在接口上标了@PreAuthorize或者拦截器校验角色,前端怎么做都不会影响数据安全。这个主次关系要拎清楚。
4. 常见问题与排查技巧实录
4.1 跨域报错:前后端联调的第一只拦路虎
跨域问题在前端项目里遇到概率极高,浏览器控制台会报CORS error或者类似信息。前后端分离后,前端端口5173、后端端口8080,天然跨域。解决办法有两个,我建议两个都配置上。
后端用CORS配置类统一处理:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }前端开发时通过Vite代理去解决跨域,这比后端CORS配置更稳妥,因为真实部署时前端静态资源和后端API如果在同一个域名下,就不会有跨域问题。我的建议是:开发环境用Vite代理,生产环境用Nginx反向代理,后端不要开allowedOriginPatterns("*")这种全放行的配置,有安全风险。
配置完还报跨域的话,优先检查是不是OPTIONS预请求没放行。浏览器在发POST/PUT/DELETE请求时会先发一个OPTIONS预请求确认服务器允许,拦截器或Spring Security如果拦了这个预请求就会报错。把OPTIONS请求直接放行是最常见的解法。
4.2 图片上传后访问404是什么原因
上传成功但图片URL访问404,这个问题的根源通常是静态资源映射没配置对。我遇到过一种情况:文件成功保存到了/Users/xxx/project/uploads/目录,但前端访问http://localhost:8080/uploads/xxx.jpg返回404,最后发现是WebConfig里的资源映射路径写错了,少了file:前缀或者绝对路径没写对。
registry.addResourceHandler("/uploads/**") .addResourceLocations("file:" + uploadPath);file:这个前缀不能丢,不加的话Spring会把它当成classpath路径去解析,那自然找不到。还有一种场景是保存到本地磁盘的文件路径里有中文或空格,访问的时候URL编码不对也会404,建议文件名统一用UUID重命名,顺带还能防重名。
4.3 后端接口返回的时间格式为什么有8小时偏差
实体里有LocalDateTime字段,返回给前端后,发现时间比实际时间差了8小时。这个问题的根源是Jackjson序列化时没有指定时区。解决办法是在application.yml里加配置:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8同样的问题也会出现在数据库连接URL上,所以MySQL连接串里一定要带serverTimezone=Asia/Shanghai。这两处都设置好了,时间就不会再漂移。还有一个容易忽略的细节:数据库连接串里的时区参数在MySQL 8.x之前可能不会有问题,但MySQL 8.x之后不明确指定时区会直接报错,所以这行参数在新版本下是必填项,别省。
4.4 宠物列表分页数据总对不上
分页显示不对基本是前端传参和后端接收参数对不上。前端用params: { page: 1, size: 10 },后端DAO层参数名要对应上。Spring Data JPA的Pageable默认从0开始计数,前端分页组件一般从1开始,这里差一个1,导致第一页数据总是不对。解法有两种:后端把PageRequest.of(page - 1, size)传入Repository;或者前端传page时从0开始。我建议后端处理,因为前端分页组件通常是1-based,后端统一处理更符合常规开发直觉。
4.5 订单状态被“覆盖”导致数据不一致
这个场景挺典型的:买家下了单但没付款,卖家在后台把自己的宠物下架了。如果订单状态和宠物状态没有联动,就会出现“订单存在但宠物已下架”这种矛盾状态。这个坑本质上是业务状态一致性没考虑好,不是bug,是设计缺陷。
我的处理方案很直接:订单一旦创建,就锁定宠物信息快照。orders表里除了pet_id外,还冗余保存宠物名称、图片、价格等关键信息,这样即使宠物后续被修改或者删除,订单展示和交易记录不受影响。这是交易系统常见的“快照”思想,能极大避免订单数据和商品数据不一致的麻烦。
4.6 前后端字段命名不一致导致的牵头皮
前后端联调时最常见的低级错误就是字段名对不上。后端返回coverImage,前端写成了cover_image,查半天查不出问题。要避免这个,一方面后端全局统一用驼峰命名,前端也统一按驼峰处理,两边不要混用。另一方面,可以用TypeScript定义接口类型,前端写代码的时候有类型提示,拼错字段名会立刻报编译错误。如果是个人项目,后端用JPA时注意一下命名策略,尽量保持实体字段和前端变量的命名习惯兼容。如果实在不一致,就在后端DTO里加@JsonProperty做映射,别在前端到处打补丁。
5. 项目扩展方向与性能优化建议
5.1 从毕设项目到生产系统的差距在哪
这个项目做完,如果你只是想完成一个毕设或者课程设计,那到这一步已经可以写论文和答辩了。但如果真想把它做大做强,有几个方向是可以继续深入的。
第一,搜索能力。现在用的WHERE name LIKE '%关键词%',数据量上千条还能接受,上万条可能就开始卡了。可以引入Elasticsearch做全文搜索,把宠物名称、品种、描述等字段建索引,搜索速度和准确性都会大幅提升。
第二,支付系统。当前订单只做到“待付款”状态就停了,实际交易平台需要接入支付宝或微信支付,买家付款成功后再触发后续流程。这个需要商户资质,个人开发者后期可以接沙箱环境练手。支付回调、对账、退款这些逻辑才是交易系统的核心难点。
第三,消息通知。买家下单后通知卖家,宠物审核结果通知买家,这些可以通过引入消息队列(RocketMQ或RabbitMQ)或者WebSocket来做实时推送。这会让平台交互体验更接近商业产品。
5.2 性能优化从哪几个维度入手
性能优化不用上来就堆缓存,先分清优先级。最廉价的优化是SQL层面,看一下慢查询日志,给高频筛选字段加上索引。pet表上的status、category_id、seller_id都是索引的候选人,orders表上的buyer_id和seller_id也一样。
第二步是加Redis缓存。宠物列表页通常是热点数据,可以用@Cacheable把列表接口缓存起来,缓存时间设为1分钟,能明显减轻数据库压力。订单相关的数据不建议随便缓存,一致性风险太高,除非你能接受一定的延迟。
最后是部署层面。前端用Nginx托管静态资源,开启Gzip压缩和HTTP缓存;后端用java -jar启动,服务器内存至少2G起步。如果有条件,用Docker把前后端分别容器化,配合docker-compose一键部署,以后迁移服务器只需要跑一条命令,体验会好很多。数据库用什么版本也会有差异,个人项目用MySQL 8.0就好,别折腾分布式数据库。
5.3 日志与监控:别等出问题了才后悔没做
项目开发期间可以靠debug解决问题,但一旦部署到服务器上,日志就是你的唯一线索。我建议在关键业务节点打上日志,比如下单、支付回调、宠物上下架,通过Logback或Log4j2输出到独立的日志文件里。以后线上出问题,先看日志再猜原因,能节省大量排查时间。
logging: file: name: logs/pet-trade.log level: com.example.pettrade: DEBUG项目上线后如果需要简单的监控,可以用Spring Boot Actuator暴露健康检查接口,然后用Prometheus和Grafana去采集展示指标。这些工具配置成本不高,但对长期运维帮助很大。哪怕你的项目只是跑在一台小服务器上,把健康检查接口建好,也是一种好的工程习惯。
5.4 我对这个项目的整体感受和复盘
这个宠物交易管理平台做下来,最大的心得不是掌握了哪个框架的API,而是理解了“业务逻辑”和“技术实现”之间的边界在哪里。用户只看到“买家下单、卖家发货”这个简单流程,但背后是订单状态机、权限控制、数据一致性、图片处理等多个维度的技术决策。每一个看似简单的功能,拆开都是有纵深的设计空间。
如果你正在做类似的系统,我的建议是:先别急着写代码,把角色、状态、权限这三件事想清楚,一张纸一支笔就能画明白。状态搞清楚,所有接口设计顺着状态流转走,后面就顺了。另外,项目里不要怕遇到Bug,能踩的坑早点踩,对理解框架原理反而帮助很大。我到现在还记得第一次配置跨域时对着浏览器报错日志发呆的场景,现在回头看,那恰恰是对HTTP协议理解最深的一段经历。
最后分享一个小技巧:前后端分离项目联调时,后端尽量提供一份Swagger文档。SpringBoot工程里引入springfox-boot-starter或者springdoc-openapi,然后访问http://localhost:8080/swagger-ui/index.html就能看到所有接口的参数说明和响应结构。前端照着文档联调,能少走很多弯路,也少一些“你接口到底返回什么字段”的沟通成本。这可能是这个项目里性价比最高的一个附加配置,值得花10分钟加上。