
最早拿到“基于SpringBootVue的宠物交易管理平台”这个题目时我脑子里想的很简单宠物无非是一种特殊商品用户登录、发布宠物、下单购买一套常规电商流程不就行了。结果真到了设计订单状态和数据表那天我发现自己错得离谱——宠物是活体它有疫苗记录、有健康状态、需要线下看货、要交定金、要当面交接交接完还有观察期。这些业务逻辑随便哪一条都比普通商城的“发货-收货-确认”复杂得多。后来我的做法是把整个系统先拆成“信息可信度、交易状态、售后追踪”三块来设计再决定用 SpringBoot 做什么、Vue 写什么才没有走更多弯路。这套方案最终落地为一个前后端分离的管理平台面向用户端提供宠物浏览、搜索、预约看宠、下单支付、健康档案查看面向运营方提供宠物审核、订单仲裁、回访管理和数据统计。如果你正准备拿这个题目做课设、毕设或者所在团队刚接了一个宠物行业信息化项目这篇文章应该能帮你提前填掉不少暗坑。我会按照业务设计、技术选型、数据库建模、核心链路、权限隔离、宠物特有业务、部署调优这条顺序展开结合我实际踩坑的细节来讲尽量少说空话。1. 设计这类平台前先想清楚活体交易和普通电商到底差在哪1.1 宠物不是商品信息会“变”也会“造假”普通商品发布之后参数基本固定颜色、尺寸、价格写死就写死了。宠物完全不是这么回事。这只猫今天没打疫苗下周补打了再下个月又做了驱虫它的“商品信息”是一直在变的。更麻烦的是健康信息很容易被夸大甚至造假——随便拍一张疫苗本的照片传上去平台根本判断不了真假。所以宠物交易平台的信息模块本质不是“发布信息”而是“建立一套相对可信的宠物档案”。这个思路会直接影响后面的表结构和审核流程设计。我一开始也偷懒把宠物表设计得跟商品表差不多只是多加了几个字段。后来发现完全不够宠物相关信息至少要分三个层面来维护。第一层是基础信息品种、年龄、性别、地区、价格这部分跟普通商品差不多第二层是健康凭证疫苗记录、驱虫记录、体检报告必须支持“图片上传关键字段填写后台复核”的组合第三层是交易状态上架、已预定、已售出、售后中状态之间还必须跟订单流程联动。这里有个很容易被忽略的设计健康凭证不能只靠文字描述也不能只靠图片。文字描述方便搜索检索图片作为证据留存后台复核负责兜底三者缺一不可。我在宠物表里加了一个health_cert_url字段存体检报告图片地址同时单独建了一张vaccine_record表存结构化疫苗数据。买家翻详情页时能看到文字记录对应的疫苗凭证图片信任感完全不一样。如果你只做一个“疫苗状态已打”的布尔字段后面想加疫苗明细、到期提醒时就得返工。1.2 订单流程是“线下线上”混合博弈状态机必须重设计普通电商的订单状态有现成套路待付款、待发货、待收货、待评价、退款售后。宠物交易里这套流程基本套不进去因为买一只宠物通常不是线上下单就完事的。实际交易链路通常是这样的买家在平台上看中一只宠物线上先付一笔定金锁定它随后双方约定线下看宠物或者视频看宠物买家确认健康状态后支付尾款然后约定时间当面交接交接完成后还会有一个保障观察期通常是一周到一个月。如果这期间发现宠物有隐瞒疾病或者与描述严重不符平台需要介入协调甚至走退换或赔付。这套流程光状态枚举就比普通电商多出好几档而且普通商城里“自动确认收货”的逻辑在这里完全不成立——没人会自动确认收到一只活猫活狗。我最后定义的订单状态是一个自定义枚举没有用商城模板里的默认值状态值状态说明触发场景0待付定金买家下单成功但未支付1已付定金定金支付成功宠物被锁定2待交接双方确认交接时间等待线下完成3保障观察期宠物已交接售后观察期内4已完成观察期结束订单完结5已取消超时取消、协商取消或违规关闭6退款中平台仲裁介入资金处理中这套状态机是整个系统最核心的字段之一后面所有的回访提醒、佣金结算、报表统计都要靠它来驱动。如果项目前期没有把状态流转图画清楚写到订单管理模块时基本必返工。还有一个容易漏的业务细节定金和尾款的资金逻辑。普通电商是买家付款直接进卖家账户宠物交易最好做平台托管。所以我实际引入了“担保交易”的概念买家支付定金后资金进平台账户冻结交接完成时平台把大部分资金结算给卖家但扣留一部分作为售后期保证金观察期结束再全部释放。数据库层面体现为订单表里的amount、deposit、platform_frozen、settled等字段而不是简单记一个order_total。如果不这样设计售后期出现问题平台一点约束力都没有。2. 技术选型复盘为什么是SpringBootVue而不是别的2.1 后端选型的真实逻辑生态成熟比炫技重要接手这个项目时团队内部有过讨论业务规模不大如果换 Node.js 或 Go启动更快、部署更轻是不是更合适最后权衡下来还是选了 SpringBoot。核心原因有三条。第一整个团队对 JVM 技术栈最熟后续接其他系统、找人维护都容易。做一个偏管理的系统代码的可维护性比运行峰值性能重要得多。第二Spring 生态里现成能力太多Spring Security 做认证授权MyBatis-Plus 做数据访问Spring Cache 配合 Redis 做缓存Spring Task 写定时任务基本不用自己造轮子。哪怕遇到格式校验、文件上传、参数绑定这类琐碎需求也能找到大量现成组件。第三这个系统的形态就是典型的中后台业务系统跑的是表单、列表、权限、审核、订单流转没有高并发也不涉及实时计算SpringBoot 的单体架构完全覆盖得住。这里我要特别说一句不要一上来就拆微服务。很多人看这个题目里有“平台”两个字就忍不住把用户服务、订单服务、商品服务拆成三个工程再加注册中心、网关、配置中心。结果业务没多少服务间调用就绕晕了。我的经验是初始开发只做一个完整的大单体模块内部用包名做边界等业务边界真的清晰了再拆分。这个项目直到上线单体结构都完全够用而且调试问题比微服务省心太多。技术栈整体如下分层技术选型选型理由后端SpringBoot 2.7 MyBatis-Plus Spring Security生态成熟、组件齐全、团队熟练前端Vue3 Vite Element Plus Pinia中后台表单场景效率高热更新快数据库MySQL 8.0 Redis关系型核心数据为主Redis 做缓存部署Nginx 独立 Java 进程前后端分离部署直观可控2.2 前端技术栈的使用细节组件复用比写一堆页面更重要前端用 Vue3 配合 Vite 构建UI 组件库选 Element Plus状态管理用 Pinia。选它的理由很简单管理后台和用户端页面里绝大多数是标准表格、搜索表单和详情页这些组件开箱即用能省掉大量造轮子的时间。Vite 相比老的构建工具启动速度和热更新体感快很多开发期舒适度高不少。但技术选型只是第一步真正决定开发效率的是请求层封装和组件复用。我在 axios 基础上做了一层统一封装请求拦截器里自动从本地存储取出 token 加到请求头响应拦截器里遇到 401 自动清登录状态并跳转登录页业务错误码统一提示。核心简化如下import axios from axios const service axios.create({ baseURL: /api, timeout: 10000 }) service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) config.headers.Authorization Bearer ${token} return config }) service.interceptors.response.use( res res.data, err { if (err.response?.status 401) { localStorage.removeItem(token) window.location.href /login } return Promise.reject(err) } ) export default service这个封装看着基础但作用很大。后端返回结构调整时只需要改这个文件而不是改全项目几十个请求文件。宠物卡片、状态标签、轮播图这类高频组件我也单独抽成了公共组件。比如宠物状态标签全局定义一个STATUS_TEXT映射和对应的颜色映射任何页面想展示状态都能直接复用避免不同页面写法不一致。实际开发中这类细节才是影响交付速度的关键。3. 数据库往细了设计宠物、订单、健康档案三块主表怎么落地3.1 宠物基础表别把“信息表”和“状态表”混在一起宠物主表是系统里最重要的表它不仅是商品表还是一个业务状态容器。我的核心建表思路是基础信息字段、健康信息字段、交易状态字段、审核状态字段四类拆开管理。下面这段是我简化后的宠物表结构实际项目里还要加更多冗余字段但骨架就是这样的CREATE TABLE pet ( id BIGINT PRIMARY KEY AUTO_INCREMENT, seller_id BIGINT NOT NULL, name VARCHAR(50) NOT NULL, category VARCHAR(20), -- 猫/狗/仓鼠等 breed VARCHAR(50), -- 品种 age_value INT, -- 年龄数值 age_unit VARCHAR(10), -- 月/岁 gender TINYINT, -- 公/母 price DECIMAL(10,2) NOT NULL, -- 售价 deposit DECIMAL(10,2), -- 定金 region VARCHAR(100), -- 所在区域 description TEXT, vaccine_status TINYINT DEFAULT 0, -- 0未填 1部分完成 2已完成 health_cert_url VARCHAR(255), -- 体检报告附件 status TINYINT DEFAULT 0, -- 0上架 1已预定 2已售出 3下架 audit_status TINYINT DEFAULT 0, -- 0待审核 1通过 2驳回 audit_reason VARCHAR(255), create_time DATETIME, update_time DATETIME, KEY idx_seller (seller_id), KEY idx_status_audit_price (status, audit_status, price) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;为什么price和deposit要分开写这是业务决定的。定金是锁定宠物的费用通常远小于总价如果只存一个总价下单时还要通过比例计算定金既麻烦又容易出错。特殊宠物可能不收定金或者收取高额定金直接存字段最灵活。audit_status和status也是两个概念。前者表示平台是否审核通过后者表示交易状态。一个宠物可以先通过审核但处于下架状态也可以审核通过后是上架状态。当初我把两个字段混在一个字段里导致运营后台逻辑绕了很久最后才拆开。另外vaccine_status单纯表示健康记录是否完整具体疫苗明细放在单独的vaccine_record表里这里只是冗余一个快速筛选用的汇总状态。3.2 订单表状态机、金额拆分、渠道快照一起考虑订单表比宠物表更容易被低估。很多人以为有了订单状态字段就万事大吉实际上宠物交易订单比标准商品订单复杂在资金拆分上。我的表结构里关键字段包括订单号、宠物 id、买卖双方 id、总金额、定金、尾款、平台冻结金额、状态、支付渠道流水号等简化如下CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, pet_id BIGINT NOT NULL, buyer_id BIGINT NOT NULL, seller_id BIGINT NOT NULL, amount DECIMAL(10,2) NOT NULL, -- 宠物总价 deposit DECIMAL(10,2), -- 定金 balance DECIMAL(10,2), -- 尾款 platform_frozen DECIMAL(10,2), -- 平台冻结金额保障期后释放 status TINYINT NOT NULL, -- 参照章节1.2的状态枚举 payment_type TINYINT, -- 1定金 2尾款 3退款 transaction_id VARCHAR(64), -- 渠道流水号 create_time DATETIME, update_time DATETIME, UNIQUE KEY uk_order_no (order_no), KEY idx_buyer (buyer_id), KEY idx_seller (seller_id), KEY idx_status_time (status, update_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;order_no必须用唯一键约束不能用自增 id 作为订单号直接给用户看。用户会复制订单号去投诉、查询自增 id 不仅泄露业务量还容易被人遍历。我生成订单号的规则是日期时间 用户 id 后四位 随机数保证并发下不重复。订单表里冗余了buyer_id和seller_id而不是通过pet_id再去关联宠物表查卖家。原因是订单形成后宠物信息甚至卖家账号都可能变化订单必须保存一份交易当时的关系快照。如果后面需要展示宠物名称和图片建议在订单表再冗余pet_name、pet_image字段查询时就不用每次去 join 宠物表了。这类冗余在中小项目里完全值得能明显减少联表查询导致的慢 SQL。3.3 健康凭证与回访记录平台增值模块的数据基础健康凭证和售后回访是宠物平台差异化的关键。vaccine_record表记录每只宠物的疫苗明细字段为宠物 id、疫苗类型、接种时间、下次接种时间、凭证图片地址、录入人。follow_up_record表记录订单完成后的回访任务。回访不是拍脑袋设计出来的它直接服务于订单状态机里的保障观察期。订单进入“保障观察期”状态后系统会在第 7 天和第 30 天生成回访任务运营人员据此联系买家询问宠物健康状况。如果发现异常可以发起售后流程甚至仲裁。这张表给运营后台提供了明确的工作列表而不是让运营每天茫然地翻订单。CREATE TABLE follow_up_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, follow_time DATETIME, operator_id BIGINT, content VARCHAR(500), pet_health_status TINYINT, -- 0正常 1异常 need_refund TINYINT DEFAULT 0, create_time DATETIME )这种设计在普通电商里是锦上添花但在宠物平台里几乎算刚需。我见过很多项目把售后回访做成一个可有可无的备注字段最后运营根本不会用因为没有一个待办工作列表去引导他们行动。4. 核心功能实现与关键逻辑发布、筛选、下单、交接的完整链路4.1 宠物发布与多图上传文件校验和路径管理是重灾区发布宠物的核心不只是往数据库插一条记录还包括多图上传。前端在表单里用文件上传组件选择多张图片后和后端表单数据一起提交。我这里的接口设计如下pet表单数据用RequestPart接收图片数组用MultipartFile[]接收单个图片限制 5MB总数量限制 10 张。PostMapping(/pet) public ResultString create(RequestPart(pet) PetSaveDTO dto, RequestPart(value images, required false) MultipartFile[] images) { if (images ! null images.length 10) { return Result.error(图片数量不能超过10张); } for (MultipartFile file : images) { if (file.getSize() 5 * 1024 * 1024) { return Result.error(单个图片不能超过5MB); } } petService.savePet(dto, images); return Result.success(); }但接口校验只是第一层真正要操心的在 service 里图片不能直接存原图手机拍出来的照片经常两三兆甚至更大直接存服务器会把磁盘和带宽都拖垮。我这里的处理是先生成缩略图再保存原图。缩略图用于列表页展示原图用于详情页放大查看。文件命名我统一改成 UUID 后缀名避免中文名和空格造成访问乱码。存储路径也要提前规划。我吃过一个亏开发时把图片直接保存到了当前项目的static/uploads目录上线后才发现打包部署后这个目录会被覆盖而且不好备份。后来改成用配置项维护一个独立上传目录比如/data/pet-platform/uploads在 Nginx 里单独映射一个/uploads/访问路径。文件系统按年月分子目录存放即/uploads/2025/03/xxx.jpg避免单个目录文件数过多导致查找变慢。4.2 多条件组合搜索动态 SQL 和索引的配合方式宠物列表页是一个典型的高频接口用户会按品种、地区、价格区间、性别、疫苗状态等条件组合筛选。这里最容易犯的错是把所有查询条件都拼到一个大 SQL 里然后盲目加索引。我用的方案是 MyBatis-Plus 的LambdaQueryWrapper动态拼接条件核心逻辑如下LambdaQueryWrapperPet wrapper Wrappers.lambdaQuery(Pet.class) .eq(StringUtils.hasText(category), Pet::getCategory, category) .eq(StringUtils.hasText(breed), Pet::getBreed, breed) .eq(gender ! null, Pet::getGender, gender) .ge(minPrice ! null, Pet::getPrice, minPrice) .le(maxPrice ! null, Pet::getPrice, maxPrice) .eq(StringUtils.hasText(region), Pet::getRegion, region) .eq(Pet::getStatus, 0) .eq(Pet::getAuditStatus, 1) .orderByDesc(Pet::getCreateTime);这里有个很关键的细节查询条件一定要把status 0上架和audit_status 1审核通过固定带上否则买家可能看到已售出的宠物或未审核通过的内容。对买家来说这两个条件是不可绕过的硬条件不是用户可选项。索引策略上我建的是组合索引(status, audit_status, price)因为查询里status和audit_status是等值条件price是范围条件。MySQL 会先通过前两个字段定位到满足状态的行再按价格范围过滤。如果用户还按品种筛选品种字段单独建普通索引就够了。开发时我通过EXPLAIN检查执行计划发现如果没有组合索引这个查询会做全表扫描数据量一大页面就明显变慢。关键词搜索部分如果只是LIKE %关键词%索引基本失效这个项目初期数据量不大还能接受后续想优化可以引入全文索引或外接搜索引擎。4.3 下单到交接的完整闭环事务、行锁、幂等处理从买家点击“立即购买”到订单进入待交接状态中间涉及几个关键动作检查宠物状态、锁定宠物、生成订单、支付定金。这些操作必须在一个事务里完成否则会出现“订单生成了但宠物没锁定”这种对不上账的情况。我用的核心逻辑是Transactional public Order createOrder(Long petId, Long buyerId) { // 1. 行锁查询宠物防止并发重复下单 Pet pet petMapper.selectByIdForUpdate(petId); if (pet.getStatus() ! 0) { throw new BizException(宠物已预定或已下架); } // 2. 生成订单保存定金和状态 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setPetId(petId); order.setBuyerId(buyerId); order.setSellerId(pet.getSellerId()); order.setDeposit(pet.getDeposit()); order.setAmount(pet.getPrice()); order.setStatus(0); // 待付定金 orderMapper.insert(order); // 3. 宠物状态改为已预定 petMapper.updateStatus(petId, 0, 1); return order; }selectByIdForUpdate是 MyBatis 里执行SELECT ... FOR UPDATE的写法它会对这行数据加排他锁直到事务提交才释放。没有了这行锁两个买家同时下单同一只宠物很可能两个订单都生成成功宠物被卖两次。这是宠物交易里最典型的并发问题。支付回调也要做幂等处理。支付渠道回调通知可能重试多次如果回调接口直接处理订单就会把定金状态重复修改。我的做法是保存transaction_id每次回调先查这个流水号是否已经处理过处理过就直接返回成功不做任何重复操作。订单状态更新时也尽量用条件更新比如UPDATE orders SET status 1 WHERE id ? AND status 0这样能防止异常逻辑把状态覆盖成错误值。5. 三端权限与数据隔离卖家不能改别人家的宠物代码怎么实现5.1 登录认证JWT 配合 Spring Security 的落地方式这个平台至少有三类角色买家、卖家、管理员。有的用户既是买家又是卖家所以用户表里有一个角色字段可以是多个角色的组合。认证方式我用的是 JWT 加 Spring Security用户登录成功后后端签发一个 token里面包含用户 id 和角色列表前端每次请求都在 Authorization 头里带上这个 token。在后端我基于 Security 的过滤器链做了一次无状态改造。核心配置很简单关闭 session、禁用 csrf、设置接口放行规则、添加 JWT 认证过滤器。放行接口包括登录、注册、宠物列表和宠物详情其他接口全部需要认证。Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/login, /api/auth/register).permitAll() .requestMatchers(/api/pet/list, /api/pet/detail).permitAll() .requestMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() ) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }// jwt filter 核心逻辑 public class JwtAuthenticationFilter extends OncePerRequestFilter { protected void doFilterInternal(...) { String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { Claims claims jwtUtil.parseToken(token.replace(Bearer , )); // 解析出 userId, roles // 构造 Authentication 放入 SecurityContext } chain.doFilter(request, response); } }这段逻辑里最需要注意的是 token 过期处理。JWT 本身是无状态的token 过期后后端如果不主动拦截前端会拿着过期 token 一直请求直到后端报 401。所以我在前端拦截器里统一做了 401 跳转登录页同时在登录页存下用户要跳转的原始路径登录成功后自动回到原页面这个体验细节很实用。5.2 接口权限与数据越权后端必须做数据级校验很多项目把权限只做在路由和按钮上觉得前端隐藏了入口就安全了。这是大忌。一个卖家如果手动发一个 PUT 请求payload 里带着别人的宠物 id接口是否能正确处理如果后端不做校验就会产生横向越权卖家 A 修改甚至删除卖家 B 的宠物。我这里的做法分两层。第一层是方法级权限比如发布宠物、修改宠物、删除宠物这些操作用PreAuthorize限制只有卖家角色可以访问。第二层是数据范围校验在业务层里强制校验当前登录用户与资源所有者是否一致PreAuthorize(hasAnyRole(SELLER,ADMIN)) PutMapping(/pet/{id}) public ResultVoid updatePet(PathVariable Long id, RequestBody PetUpdateDTO dto) { Long currentUserId SecurityUtils.getCurrentUserId(); petService.checkOwner(id, currentUserId); petService.updatePet(id, dto); return Result.success(); }public void checkOwner(Long petId, Long userId) { Pet pet petMapper.selectById(petId); if (pet null) { throw new BizException(宠物不存在); } if (!pet.getSellerId().equals(userId)) { throw new BizException(无权操作此宠物); } }这种校验看着啰嗦但它是数据安全的地板。我之前做过一个项目前端隐藏按钮后以为万事大吉结果上线半个月就出现用户修改他人商品数据的投诉就是因为后端没有校验归属关系。宠物交易平台涉及金钱和活体这种低级错误绝对不能犯。5.3 管理后台的边界管理员能看能审但不能代替买卖方交易管理员的权限也要明确边界。管理员可以审核宠物、处理举报、查看订单详情、发起退款仲裁但不能代替买家支付、代替卖家改价格、随意修改订单状态。正常情况下订单状态的每次变化都应该有对应的业务方触发管理员介入只发生在仲裁场景而且每一步都要留操作日志。我专门建了一张operation_log表记录谁在什么时间对哪个订单做了哪种操作操作前后的状态是什么。这张表在复盘纠纷时作用非常大否则买卖双方各执一词时平台只能跟着和稀泥谁先改状态、谁半夜改价格根本说不清楚。6. 宠物平台独有的业务难点健康凭证、押金、售后回访怎么设计6.1 健康凭证的“证据链”图片、文字、审核三层保障健康凭证模块是我觉得整个平台和普通商城拉开差距的关键功能。前面提到疫苗信息存结构化表但只有结构化字段还不够必须把“凭证图片”也纳入设计。一个疫苗记录至少要包含疫苗名称、接种日期、接种医院、下次接种日期、凭证图片。买家看到的不只是一个状态标签而是一条完整可追溯的记录。卖家填写的健康信息必须经过后台审核。我在管理后台专门做了一个“宠物审核”工作台运营人员逐条查看新增宠物核对照片和健康记录。审核通过后宠物才能在前台列表曝光。这个逻辑在数据库里体现为audit_status字段前台列表固定过滤audit_status 1。很多做宠物平台的项目会忽略这个审核环节导致平台充斥着虚假信息最后买家流失这是非常不值得的。另外疫苗到期提醒很有价值。宠物疫苗是周期性需要如果平台能在疫苗到期前提醒卖家卖家会觉得自己被平台真正照顾到了。我实现的方式是定时任务每天扫描vaccine_record表把未来 7 天内到期且没有“已接种”标记的记录找出来生成一张待办通知。这个功能在普通电商里是完全不存在的。6.2 押金、佣金与保障期资金释放逻辑押金机制逻辑不复杂但涉及钱就必须严谨。我用一个具体例子说明假设宠物标价 2000 元定金 200 元尾款 1800 元。买家下单后支付 200 元进入平台冻结双方交接时买家支付尾款 1800 元平台对其中的 100 元继续冻结作为售后期押金观察期结束后平台把冻结的资金按规则结算给卖家同时平台按成交价 5% 抽取佣金。也就是说卖家实际到账金额为200 1800 - 100 - 100其中 100 元为平台佣金100 元为保障期押金在观察期满后释放。这笔账必须在代码里拆清楚。我的经验是不要只用一个字段存金额否则对账时会想死。订单表里amount、deposit、balance、platform_frozen、settled各管各的字段每一笔资金变动都写一条支付流水记录。支付流水表payment_log至少包含订单号、流水类型、金额、状态、渠道流水号、创建时间。这样即使出现账实不符也能通过流水表一笔一笔追溯。6.3 售后回访用定时任务生成待办而不是靠人工翻订单订单进入保障观察期后系统会在第 7 天和第 30 天自动生成回访任务。这里的技术核心是一个简单的定时任务每天扫描一遍订单表并结合回访记录判断是否已经生成过任务避免重复生成Scheduled(cron 0 30 8 * * ?) public void generateFollowUpTasks() { // 1. 查询所有状态为 3保障观察期的订单 // 2. 根据订单创建时间计算第7天、第30天 // 3. 检查 follow_up_record 是否已存在对应任务 // 4. 不存在则插入新回访任务 }这里我踩过一个坑直接写死只在订单完成后的第 7 天生成结果有些订单处于观察期还没满 7 天任务已经生成了。后来改成先判断订单创建时间距离今天的天数再根据天数决定生成哪种类型任务。定时任务一定要保证幂等否则重复执行会生成一堆重复回访任务。我对“是否已生成”的检查就是查follow_up_record表里是否已有相同order_id加上指定回访类型的记录。7. 上线部署与性能调优不算难但一定会反复踩的坑7.1 跨域、路径、环境问题本地一切正常一到服务器就挂这个项目我部署时踩的第一个坑就是图片访问路径问题。本地测试时图片地址存的是D:/pet_project/uploads/xxx.jpg换到服务器后路径完全失效。最后的解决办法是把上传目录做成一个可配置项写入application.yml前端展示时统一用/uploads/xxx.jpg这种相对路径由 Nginx 把/uploads/映射到实际目录。这样换机器只需要改配置不用改代码。第二个坑是跨域。开发环境里前端跑在localhost:5173后端跑在8080直接发请求会有跨域问题。我开发时在前端vite.config.js配置了 proxy所有/api请求转发到后端生产环境用 Nginx 统一反向代理这样前后端访问同源不需要后端单独配置跨域。如果后端代码里再开一个CorsFilter反而容易造成混乱这里建议开发阶段靠 proxy生产阶段靠 Nginx。server { listen 80; server_name your-domain; root /var/www/pet-platform/dist; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /uploads/ { alias /data/pet-platform/uploads/; expires 30d; } location / { try_files $uri $uri/ /index.html; } }try_files $uri $uri/ /index.html这行是 SPA 路由的关键不能少。没有它前端页面刷新时就会 404。这是我当时漏掉的一个典型细节。7.2 数据库连接、时区、连接池三个不起眼但致命的参数MySQL 数据库连接串里有几个参数容易让人头疼。serverTimezoneAsia/Shanghai不配时间字段会出现整整 8 小时的偏差useSSLfalse不配部分环境启动时会一直警告甚至报错。如果连接池用的 HikariCP初始化连接数设成 5最大连接数设成 20 就够了千万别设成 100一个小项目开 100 个数据库连接纯粹浪费资源。还有一个容易忽略的问题服务器内存。SpringBoot 应用默认堆内存可能跟着机器内存走我在一个 2G 内存的小服务器上部署时直接把内存耗爆过。后来在启动脚本里显式设置了-Xms256m -Xmx512m服务稳定很多。前端打包产物也用了 Gzip 压缩Nginx 开启后静态资源体积减少一半以上页面加载速度明显提升。7.3 事务与缓存热点数据加速但不能盲目加缓存首页的宠物推荐列表、品种分类列表是访问频率最高的接口我把它放到了 Redis 里设置 10 分钟过期过期后重新查库再写入。这个策略效果很明显数据库压力降了一个量级。缓存更新策略要考虑清楚后台宠物审核通过后要主动删除对应热点的缓存而不是等 10 分钟后自然过期否则运营会觉得前台更新不及时。事务使用上我在下单方法上加了Transactional但也有一个认知要纠正事务越大锁的时间越长。下单逻辑里尽量只把“检查宠物状态、生成订单、更新宠物状态”放在事务里支付回调的处理另起一个事务不要把整个 HTTP 请求都包在事务里。否则回调频繁时可能因为长事务把数据库连接池耗尽。结尾整个项目从最初原型到稳定运行我前后改了三轮订单状态设计。第一轮直接套商城模板加了简单的待付款和待发货第二轮补了定金和尾款第三轮才加上保障观察期和仲裁状态。每一轮改动都牵动数据库、前端表单、管理后台一大片代码改到后面我算是彻底想明白了宠物交易平台这类系统前期在业务流程设计上多花两天比后期改代码省十天。如果以后还有机会在这个基础上升级我会优先做用户端小程序和疫苗到期主动提醒这两个功能对用户粘性的提升可能比继续美化列表页大得多。做平台就是这样业务规则梳理清楚了技术只是实现它的手段。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。