ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring Boot与Vue构建多端商城测评分享系统实战

Spring Boot与Vue构建多端商城测评分享系统实战 最近我接到一个需求用Spring Boot搭后端Vue做前端做出一个既能当化妆品购物商城、又能让用户发测评写分享的系统还要同时覆盖App和小程序两端。说实话单独做商城不难单独做UGC内容也不难难的是把两者缝在一起还让小程序、App、H5三端共用一套接口。这篇文章就把我整个落地过程里觉得有价值的部分拆开聊——从表结构怎么设计到测评热度怎么算再到客户端调试和打包部署里的坑按实际开发顺序串一遍。不管你是准备从零搭这类项目还是想给现有商城接测评分享模块看完应该能少走不少弯路。1. 需求没写明白的商城测评分享到底在要什么很多项目一看标题是商城就只想到商品、购物车、订单但客户挂上测评分享四个字背后真正要的是一个能自发产生内容的社区场。用户在买完一支口红之后写一段使用感受附上实拍图其他用户看到这篇测评后种草、加购、再买再评这就形成了内容驱动交易的闭环。所以我刚拿到这个标题时第一反应不是去敲代码而是把人、货、场的关系理清楚。1.1 从购物清单到种草社区两类角色的核心差异商城模式下系统只有两种主要角色买家和管理员。买家买东西管理员管商品。但测评分享加入之后买家就多了一层内容创作者身份管理员也多了一项审核职责。我在设计种子数据时就刻意区分了这两类行为普通用户可以收藏商品、下单、发表测评、点赞他人测评管理员既要维护商品池又要审核测评内容是否合规、是否涉及虚假宣传。这里有一个容易被忽略的点测评内容对化妆品类目特别敏感。化妆品涉及成分、功效宣称、使用前后对比图商家本身就有合规红线更别说普通用户写的测评。所以我在后端单独建了一张article_review表不仅存标题和正文还特意加了status字段默认待审核管理员过一遍才放出来。这个设计不是我拍脑袋想的而是看过太多做UGC的团队后面被内容合规折腾到返工才觉得一开始就必须把审核状态机设计进去。1.2 多端复用不是三套界面而是一套接口语义标题里同时出现App和小程序很多团队的第一反应是分两端各写一套接口甚至一个用Spring Boot、另一个用Node.js。我的建议是接口必须按业务域划分而不是按端划分。什么意思App下单和小程序下单底层都是创建订单这件事差异只在登录凭证获取方式。我在这套系统里只定义了一套POST /api/order/create用platform字段区分来源后端在回调或推送时再判断走微信订阅消息还是App推送。前端用uni-app一套Vue代码同时编译到小程序和App端页面上的轮播图、商品列表、测评列表全都复用同一套组件。至于小程序和App在不同系统上的原生差异比如微信登录、App内分享我会放进各自的平台专属SDK封装层。这样做的最大好处是后端服务只需要维护一套逻辑联调时不用对着三套文档前端人员也不用精神分裂地在两套代码里改同一个bug。项目能做到后期交付还保持清爽靠的就是这个从一开始就确立的分层意识。2. Spring Boot 后端别急着写Controller先把表和状态机画清楚我看到不少新手拿到这种项目第一件事就是建一个GoodsController、OrderController就开始写CRUD。真按这个节奏走到做测评功能时一定会卡住因为测评和商品、用户、订单之间是相互引用的关系。我自己的习惯是先用一周时间把数据库表和核心状态流转定义清楚再回过头写接口效率会高得多。2.1 商品、测评、图片、评论的数据表组织方式我把整张核心表的结构分成四块避免一张表扛所有业务商品相关product、product_sku、product_category主要存基础信息和规格库存。测评相关article_review、review_image、review_comment、review_like存用户创作内容、配图、评论和点赞关系。交易相关orders、order_item、shopping_cart和普通商城一致。用户相关user、user_address、user_favorite用户基础信息与收藏关系。这里有个细节review_like这张表我存的是like_user_id和review_id两列主键用自增id同时做了联合唯一索引。点赞在测评场景里还会被用来计算热度所以我会在这里冗余一个status字段方便实现再点一次取消赞的幂等操作。评论表同样要加上parent_id支持楼中楼不然用户想回复别人的测评时后台会很难受。review_image表我单独拆出来是因为测评里的图片和大促商品图不太一样。用户上传的图片没有水印、可能需要做敏感内容审核而且经常需要读取多张图拼成九宫格。与其在测评表里用逗号分隔图片URL不如拆个子表查询时按sort_order排序方便后续做图片懒加载和异地CDN替换。2.2 JWT登录态与游客可看、登录可评的最小实现这套系统不要求注册才能看商品所以我把接口分成两级商品轮播、商品列表这些查询接口直接放行但发测评、点赞、评论、下单必须带着登录凭证。我用的是Spring Boot JWT的经典组合。用户调用POST /api/user/login时后端用jjwt生成一个包含userId和role的token有效期设成7天小程序端每次请求都会在请求头里带Authorization: Bearer token。这里有一个关键选择token里只放最少必要信息不能把用户头像、昵称、手机号全塞进去否则一旦逻辑更新老token就全部作废了。拦截器方面我这个项目自定义了一个AuthInterceptor继承HandlerInterceptor在preHandle里解析token并放入ThreadLocal。登录接口、微信回调接口、静态资源路径全部用excludePathPatterns跳过。这样后面做管理员接口时只需在注解里加一个RequireRole(ADMIN)在拦截器里做二次校验就行不用每个Controller都写一遍判断逻辑。2.3 用户测评热度不该只数点赞数测评列表默认按发布时间倒序但如果一个用户发了一篇质量很高但时间很久的测评它会永远沉底这对内容社区不友好。我引入了一个简单的热度值hot_score 点赞数 * 2 评论数 * 3 浏览数 * 0.5再乘以一个时间衰减因子。具体实现我在article_review表里加了一个冗余字段hot_score每当点赞、评论、浏览发生时不是实时跑SQL汇总而是通过Spring Boot的Async异步方法给这个字段加权重。列表页排序先用hot_score desc再用create_time desc作为次级排序。做一个小定时任务Scheduled每半小时把超过7天的hot_score乘以0.9实现热度缓慢下降。这个不复杂但对一个测评分享系统来说热榜有变化比永远按时间排带来的用户留存效果明显好很多。3. Vue 这一层商城页和测评页的取舍别想着一次全做前端部分标题明确写了Vue。但纯Vue只能跑H5或者App的WebView要同时兼容微信小程序就必须在Vue语法之上套一层。我这边选的是uni-app因为它能用一套.vue文件同时编译到微信小程序和App端团队里的人只要会Vue就能上手不用再学一遍小程序原生语法。3.1 为什么用uni-app而不是直接用小程序原生写先不踩坑再谈性能。小程序原生开发最大的问题是同一套业务要写小程序的WXML/WXSS还要写App端的Vue组件两边都要各来一套。uni-app在编译阶段帮你把这层差异吃掉了你只管用template写页面结构script里用Vue生命周期管理状态底部标签栏和路由都可以用统一配置。当然这不是说uni-app没成本。个别原生API比如微信小程序的wx.login在uni-app里要通过uni.login封装后再转成微信codeApp端如果要唤起本机分享面板又要走plus.share这些HTML5 Plus API。我处理这些差异的方式是用一个platform.js工具类统一暴露方法内部用#ifdef条件编译区分平台业务页面只调login()、share()这些抽象方法不直接写wx.开头的代码。3.2 测评详情页的富文本与图片上传处理测评文章要支持用户粘贴带格式的内容这里不能直接上v-html就完事——因为用户上传的富文本里可能含有违规内容或样式攻击。我的做法是前端用rich-editor组件用户编辑输出HTML后端在收到内容后做一次清洗把script、style标签全部剥掉只保留p、img、blockquote等安全标签再存入数据库。图片上传单独封装成一个uploadImage方法流程是用户选图 - 用canvas做等比压缩 - 上传到后端POST /api/upload/image接口 - 后端返回URL和CDN路径。压不压缩差别很大不压缩的话一个用户手机拍出来的图可能是5MB多传几篇测评后端存储和带宽就爆了。我这边统一把最长边压到1200像素网络图片控制在300KB内实测下来清晰度在手机端完全够用。3.3 加载更多的正确手势别再做整页滚动加载标题相关词里不断出现微信小程序页面列表加载更多这个需求可见大家都在这块栽过跟头。我的做法是列表接口用page和pageSize分页每次返回{ list, total, hasNextPage }。前端滚动到底部时判断hasNextPage再触发下一页请求并追加到当前数组末尾这样不会覆盖掉用户已经浏览的内容又能保证滚动流畅。还有一个细节是并发控制。我用了一个loading布尔值在请求开始置为true结束后置为false如果用户快速滚动触发多次回调只有loading为false时才发请求避免重复请求同一页数据。实测在低端Android机上滑动时基本不会出现列表跳闪或者重复渲染的问题。4. App与小程序端的特有链路登录、发布、订阅消息三端共用一套业务接口但获取用户身份这件事必须分别处理。小程序不能直接用手机号密码登录App又不能直接用微信授权回调。这两个端我专门写了一个登录适配层。4.1 微信登录、App登录与用户表打通小程序端的流程是前端调uni.login拿到code传给后端POST /api/user/wxlogin后端拿code换微信openid再查user表里有没有该openid。如果不存在就创建默认用户如果存在则直接登录成功返回JWT。App端的登录我做了两套手机号验证码登录以及微信授权登录。手机号登录相对简单注意验证码不要直接存明文我用Redis存验证码5分钟过期校验成功立即删除。微信授权登录在App端返回的是access_token和openid逻辑和小程序相似只是后端换openid的接口地址不一样。这里有个坑同一个用户在小程序登录和App登录如果都用微信生成的openid是不同的因为小程序和App在微信开放平台里属于不同主体。如果不想让用户在两端的账号数据割裂就必须在数据库里加一张user_social_bind表用unionid作为唯一关联键。我当时一开始漏了这个后来发现用户在App下单的记录小程序端查不到项目差点推倒重来。4.2 发布测评时的本地压缩切片上传流程发布测评的前端体验很大程度上决定用户愿不愿意写。我处理用户提交的图文时第一件事是离线压缩第二件事是分片上传。压图片是为了省流量分片是为了弱网环境下不会因为一个大文件上传失败导致整篇测评白写。具体实现在uni-app里我用uni.compressImage把图片压缩到指定尺寸再通过uni.uploadFile分片上传。每片1MB后端用临时目录接收等所有分片传完后再合并。这样即使微信小程序单次请求有大小限制也不会触发超时问题。上传过程中要做进度展示给用户一个正在发布请勿中途退出的提示同时缓存正在编辑的草稿到本地Storage。4.3 小程序订阅消息与App推送的取舍测评被点赞、订单发货这类消息小程序端我用的是订阅消息机制。注意微信的订阅消息需要用户主动点一次允许且一次订阅只能触发一次推送。所以我在测评发布页里放了一个开关用户勾选有人评论时通知我时前端调一次uni.requestSubscribeMessage后端拿到订阅凭证后再存到notification_subscription表。App端我暂时没接厂商推送因为接入个推或极光需要额外依赖而且对测评系统来说不是核心价值。我的过渡方案是站内信表存未读消息用户在App打开消息中心时从接口拉取后续再按需接极光推送。这个计划可以省下不少联调时间也能保证第一版稳定上线。5. 联调、打包、部署里最磨人的五个大坑很多代码在本地跑没问题一到被人用真机测试就花式崩溃。下面这几个坑是我在这个项目里真实踩过、并且都在网上被反复讨论过的值得单独拿出来说。5.1 Vue打包进Spring Boot的静态资源路径配置生产环境我不打算单独为前端开Nginx直接把Vue打包后的dist目录放进Spring Boot的static文件夹里让后端一个端口服务前后端部署成本最低。但要小心两点。第一前端路由必须使用history模式并且Spring Boot端要配置forward把非以/api/开头的路径都转发到index.html这样用户在页面里刷新或者直接访问深链接时不会404。第二前端构建时publicPath要设置成相对路径./不然资源路径会带上绝对根路径前后端分离部署时没问题合到一起部署反而会找错资源。5.2 Charles抓包小程序接口的正确姿势做小程序开发你不抓包几乎没法排查线上问题。我之前常用的工具是Charles。流程分四步手机和电脑连同一个局域网手机WIFI代理指向电脑IP和Charles端口8888Charles上开启SSL Proxying并安装证书到手机微信小程序必须在开发工具里勾选不校验合法域名否则请求会被拦抓包时如果看到CONNECT请求直接过滤掉只看api开头的真实业务接口。抓的时候特别留意请求体和响应体。小程序有些错误不是后端报的而是前端传的参数类型不对比如把商品价格String传进去后端要求BigDecimal这种问题用Charles一眼就能看出来。抓到之后我用Postman重新请求一遍确认是否是后端问题再决定改哪边。5.3 Spring Boot版本过高引起的依赖冲突热词里提到springboot版本太高vue打包放进springboot中。这个问题我遇到过。Spring Boot 3.x把javax.servlet换成了jakarta.servlet同时要求JDK 17及以上。如果团队还在用Spring Boot 2.x的很多旧教程直接把spring-boot-starter-web升级到3.x很多依赖比如mybatis的旧版本就会起冲突运行时抛各种ClassNotFoundException。我的建议是这个项目锁Spring Boot 2.7.x JDK 8或11原因无他——生态成熟、网上资料多、第三方兼容性最好。等核心功能全部跑通后再规划是否升级。如果非要升级就用Spring Boot官方提供的spring-boot-properties-migrator插件自动检查旧配置项逐条迁移。5.4 接口跨域与线上域名白名单部署到云服务器后H5端访问接口会涉及跨域。我在Spring Boot里写了一个CorsConfig用WebMvcConfigurer实现addCorsMappings允许的来源列表只放自己前端的域名方法限定GET,POST,PUT,DELETE不放开所有来源。小程序端更特殊线上正式版不允许请求http明文地址必须在微信公众平台配置request合法域名并且要求HTTPS。我因此提前申请了SSL证书配置到Nginx或直接在Spring Boot里用server.ssl.*开启HTTPS。配置完后记得在微信后台重新提交审核不然无法访问。5.5 小程序Android端唤醒App的隐藏需求这个项目里有专属于App的功能小程序浏览测评时底部一个按钮引导用户下载App查看完整版。这里用的是微信小程序打开App能力需要在微信公众平台关联App应用。我卡过的问题是iOS端要求先在浏览器里唤起App而Android端可以直接跳转。我最终的处理方案是小程序端调用uni.navigateToMiniProgram或者wx.openApp的封装后端配置接口返回对应的App下载链接和唤起协议。查看微信官方文档发现iOS只支持通过universal link拉起所以我在后端额外暴露了一个redirect接口动态拼接链接再在小程序里引导用户复制链接跳浏览器。实际测试中这个功能在小程序和App两端都能正常拉起只是iOS确实多了一步浏览器确认动作。6. 复盘整个技术栈选型和演进的最终答案整个项目从立项到第一版上线前后端加起来大概六周。如果让我重新做一遍依然会先把后端表结构和状态机磨透再动前端页面。这个项目最大的收益不是写了几百个接口而是把电商交易和用户内容生产两件本身张力很大的事情用一套合理的架构串到了一起。我个人的体会是做这类全栈加多端项目最忌讳的是一上来就追求完美。你不需要第一版就同时支持评论楼中楼、热度衰减、订阅消息、App推送。我的落地顺序是第一周搭好Spring Boot骨架、设计全部核心表、跑通登录和商品查询。第二周实现购物车、订单、支付回调先用微信JSAPI模拟。第三至四周实现测评发布、列表、详情、点赞、评论并接上内容审核状态。第五周写uni-app页面把商城主流程和测评主流程串通。第六周打包调试、上线部署处理小程序审核和HTTPS。这套顺序我先保证了商城这个底盘不塌再让测评分享在底盘上生长。测评内容在后面自然成为商城的流量入口这也是标题里测评分享系统的真实价值所在。后端接口设计上我没有引入复杂的微服务或消息队列全项目就是Spring Boot单体加MySQL只在点赞和热度计算这种轻量异步场景里用了一个自定义线程池。对化妆品商城这个体量简单能维护的方案往往比复杂抽象的架构更有价值。等到用户量真正上来再根据热点数据拆Redis缓存或者把测评模块单独拆成服务也不迟。最后分享一个实战小技巧我给测评接口设计了一组debug开关通过请求头里的X-Debug-UserId模拟不同用户身份这样联调时不用频繁切换账号。上线前一定记得关掉这个开关否则任何人都能胡编身份后果不堪设想。这种不起眼的细节往往比多写几个接口更让我觉得项目是真的可交付的水平。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进