最近在捣鼓一套“化妆品购物商城 测评分享”的完整系统后端用的 SpringBoot管理后台和 H5 用 VueC 端用户端做成了微信小程序和 App。整套系统从需求梳理、表结构设计到前后端联调前前后后折腾了一个多月过程中踩了不少坑也沉淀了不少可以直接拿来就用的经验。这个项目最大的特点是把“购物”和“测评种草”放在同一个产品里用户进小程序之后可以先刷达人和普通用户的真实测评内容被种草了再去看商品详情、加入购物车、下单支付买完之后还可以在订单里发测评、评分、传图传视频。这种“内容驱动交易”的模式在化妆品类目里特别吃香因为化妆品决策高度依赖口碑和真实体验干巴巴的商品列表很难让用户下单。如果你正在做毕业设计、个人作品集或者小团队想快速验证一个美妆电商的业务闭环这套系统的架构思路和实现细节都很有参考价值。下面我把整个项目的关键设计、核心代码实现、常见问题排查思路完整拆一遍。1. 先把项目想清楚商城和测评分享到底怎么揉在一起1.1 核心业务闭环设计很多新手一上来就急着建表、写接口结果做到一半发现业务逻辑混乱。我建议先把用户路径画出来。这套系统的核心路径是浏览测评内容 → 被种草 → 查看商品详情 → 加入购物车 → 提交订单并支付 → 写测评/晒单 → 获得关注和点赞。注意这里有一个关键点测评不是和商城并列的两个模块而是嵌入在用户购物动线里的。用户在测评详情页看到推荐商品可以直接跳转到商品详情页下单完成之后用户的“待评价列表”里才会出现这条订单这就保证了测评内容的真实性。我在设计的时候把“是否购买后测评”做成了一个字段purchased_flag购买过的用户发布的测评会打上“已购”标签没有购买的用户也能发测评但权重排序会靠后而且不能带“购买建议”类的高引导性内容。1.2 功能模块划分从功能上看系统分成四个端C端小程序/App用户端微信登录、首页信息流、测评详情、商品列表与详情、购物车、下单支付、订单管理、个人中心、发布测评、点赞收藏关注。管理后台Vue PC端商品管理SPU/SKU、库存管理、测评审核、订单管理、用户管理、轮播图管理、分类管理。后端服务SpringBoot提供 RESTful API处理鉴权、业务逻辑、文件上传、订单状态流转、内容审核。存储与中间件MySQL 存业务数据Redis 做缓存和库存扣减MinIO 做图片和视频的对象存储。1.3 这套方案适合谁如果你只是做一个静态展示页面那用不着这么重的架构。这套方案适合有真实交易逻辑的项目用户要登录、要下单、要支付至少走模拟支付或微信支付、要有内容发布和审核。换句话说它不是一个简单的“商城 Demo”而是一个可运行的、能演示完整业务闭环的系统。也正因为如此用 SpringBoot Vue 小程序这套技术栈是最稳的个人能驾驭、社区资料多、部署成本低。2. 技术选型SpringBoot Vue 小程序为什么这么搭2.1 后端为什么选 SpringBootSpringBoot 在 Java 后端领域基本是事实标准了尤其适合单体应用快速落地。我选的是SpringBoot 2.7.x 版本为什么不直接上 3.x因为很多毕业设计和公司内部项目还在用 2.x而且 3.x 要求 JDK 17对不熟悉的新手来说有点门槛2.7.x 配合 JDK 8 就能跑得稳稳的。后续要升级也不是问题核心业务代码改动量不大。Maven 构建是这个项目的标配pom.xml里核心依赖就那几样spring-boot-starter-web、spring-boot-starter-data-jpa或者 MyBatis-Plus、spring-boot-starter-data-redis、mysql-connector-j、jjwtJWT 鉴权、minio对象存储 SDK。我在这套项目里用的是 MyBatis-Plus它和 SpringBoot 配合起来写 CRUD 极其省事特别是分页查询自带Page对象不用自己手写 PageHelper。2.2 前端和管理后台Vue 3 Vite管理后台我用的 Vue 3 Vite Element Plus。Vite 比 Webpack 快太多开发时热更新几乎秒开对于频繁调整页面的场景非常友好。Vue Router 用的history 模式但这里埋了一个坑打包之后部署到静态服务器或者 SpringBoot 的static目录时刷新二级页面会 404。我后来直接把管理后台改成了hash 模式省去一堆路由回退配置。如果你的项目要求必须用 history 模式那记住在服务器或 SpringBoot 里加一个forward到index.html的兜底处理就行。2.3 C端为什么不写两套原生而是用 uni-app小程序和 App 如果各写一套原生成本和维护量直接翻倍。我选择了uni-app一套 Vue 语法代码同时编译到微信小程序和 App离线打包或云打包。它在 C 端的好处非常明显页面结构用 Vue 单文件组件写生命周期和微信小程序的生命周期做了映射你可以在onLoad、onShow、onReachBottom这些钩子里写业务逻辑。这套代码后续如果要做抖音小程序、支付宝小程序也能复用一大半。2.4 存储方案MySQL Redis MinIO数据存储我分了三层MySQL 存用户、订单、商品、测评的内容数据Redis 存热点商品缓存、购物车临时数据、库存扣减的原子操作MinIO 存测评图片、商品主图、测评视频。MinIO 是一个开源的对象存储服务完全兼容亚马逊 S3 API你可以部署在自己服务器上几十 GB 的图片和视频完全没压力而且不像云厂商的 OSS 会产生外网流量费用。把 MinIO 加入 SpringBoot 也是很常规的操作加一个依赖、配置一个 client 就能用。3. 核心表结构设计这一步做稳后面少走一半弯路3.1 用户与登录相关表用户表我设计了字段id、openid、unionid、nickname、avatar、skin_type肤质类型干性、油性、混合、敏感、phone、status、create_time。这里重点说一下openid和unionid微信小程序登录拿到的code换回来后主要是openid同一个用户在不同小程序下openid不同只有绑定了开放平台账号才能拿到unionid。我设计了独立索引并且把openid设为唯一键避免同一个微信号反复插入多条用户记录。3.2 商品表SPU/SKU 分离化妆品商品有规格属性比如色号、容量所以我拆了productSPU和product_skuSKU两张表。SPU 表存商品标题、品牌、主图、详情图、分类 ID、功效标签SKU 表存具体的规格名、库存、价格、SKU 图片。查询商品列表时按 SPU 维度返回进入详情页后通过product_id查出所有 SKU前端做规格选择。库存我放在了 SKU 表上而不是 SPU 表因为用户下单时锁定的是具体 SKU 的库存比如一支口红可以有大牌色号和小样色号库存消耗完全独立。3.3 测评内容表测评表是整个系统的灵魂我设计了这些关键字段id、user_id、product_id、order_id关联订单判断是否已购、content、score评分1-5、skin_type用户肤质、effect_tags功效标签如保湿、控油、不致痘、imagesJSON 数组最多9张图、video_url、purchased_flag、status待审核/通过/驳回、like_count、view_count、create_time。这里有两个细节建议特别注意一是images用 JSON 数组存在前端渲染时直接JSON.parse比额外建一张图片子表简单得多管理后台展示也方便二是order_id字段要允许为空因为非购买用户也能发测评但通过purchased_flag区分展示层级。3.4 订单与购物车链路购物车表字段相对简单id、user_id、sku_id、quantity、checked是否选中、create_time。订单我拆了主表和子表order存订单号、用户 ID、总金额、实付金额、状态、收货地址快照order_item存订单下的每个商品 SKU、单价、数量、商品快照。为什么要做快照因为商品价格和标题随时可能调整如果不快照用户查看历史订单时看到的是当前价格会造成纠纷。订单状态我用status字段管理0待支付、1已支付/待发货、2已发货/待收货、3已完成、4已取消、5退款中/已退款。关于“测评和订单”的关联我特意用order_id把测评关联到订单项目上防止用户买了一次口红然后发十篇测评。表结构上就是在测评表里加一条唯一约束uk_user_order_product同一个用户同一个订单同一个商品只能发一条购买型测评。4. 后端 SpringBoot 实现登录、文件存储、库存防超卖这些硬骨头4.1 项目初始化和配置用 IDEA 新建 SpringBoot 项目的时候可以直接选择 Spring Initializr依赖选择 Web、MySQL Driver、Redis、Lombok、Validation。我习惯在pom.xml里再加MyBatis-Plus、jjwt、MinIO三个依赖版本号用 Maven 仓库里稳定版即可。application.yml里核心配置如下server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/beauty_mall?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 servlet: multipart: max-file-size: 50MB max-request-size: 100MB minio: endpoint: http://localhost:9000 access-key: minioadmin secret-key: minioadmin bucket: beauty-mall注意 MySQL 连接串里一定要写serverTimezoneAsia/Shanghai不写的话你会在时间字段上遇到 8 小时时差问题尤其是订单创建时间对不上排查起来很头疼。4.2 微信登录与 JWT 鉴权微信小程序登录的流程是小程序端调用wx.login()拿到临时code把这个code传到后端后端拿着codeappidsecret请求微信的接口换openid拿到openid后查用户表不存在就自动注册最后生成一个 JWT token 返回给前端。前端后续请求都把 token 放在Authorization请求头里后端用一个拦截器解析 token 并往ThreadLocal里塞当前用户 ID。核心接口逻辑大概是PostMapping(/wx/login) public Result login(RequestBody WxLoginDTO dto) { String url https://api.weixin.qq.com/sns/jscode2session?appid appId secret appSecret js_code dto.getCode() grant_typeauthorization_code; String response restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(response); String openid json.getString(openid); User user userMapper.selectOne(new LambdaQueryWrapperUser() .eq(User::getOpenid, openid)); if (user null) { user new User(); user.setOpenid(openid); user.setNickname(微信用户 openid.substring(openid.length() - 6)); userMapper.insert(user); } String token JwtUtil.createToken(user.getId()); return Result.success(token); }JWT 我用的jjwt库生成 token 时把用户 ID 写进subject过期时间设为 7 天。注意不要把openid写进 token因为 token 会暴露在客户端虽然 JWT 签名不可伪造但信息仍然可以解码敏感数据一律不放。4.3 MinIO 文件存储图片和视频统一托管化妆品测评里图片和视频是重头戏我用 MinIO 做了统一存储。把 MinIO 集成到 SpringBoot 其实就是三步写一个配置类读取application.yml里的参数并创建MinioClient封装 upload、getUrl、delete 方法写一个通用文件上传接口。Service public class MinioService { Autowired private MinioClient minioClient; public String upload(MultipartFile file, String objectName) throws Exception { PutObjectArgs args PutObjectArgs.builder() .bucket(beauty-mall) .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build(); minioClient.putObject(args); return minioClient.getPresignedObjectUrl( GetPresignedObjectUrlArgs.builder() .method(Method.GET) .bucket(beauty-mall) .object(objectName) .expiry(7 * 24 * 3600) .build()); } }这里有一个坑要重点提醒getPresignedObjectUrl生成的链接是有过期时间的默认 7 天超过时间图片就访问不了了。如果是商品主图这种长期展示的图片建议把存储桶权限设为公开读用http://ip:9000/beauty-mall/objectName这种固定地址拼接返回给前端否则用户过几天打开小程序会发现所有图片全部裂开。视频文件基本都是几百 MB 量级上传时注意multipart大小配置同时在前端做一个上传前压缩和进度条体验会好很多。4.4 商品库存的防超卖方案商城系统最怕的就是超卖也就是用户下单成功后库存变成负数。我用的方案是 Redis 预热 数据库扣减双重校验商品上架时把 SKU 库存同步到 Redis用户下单时先从 Redis 用decr原子操作扣减库存返回结果如果小于 0 说明没货了直接回滚Redis 扣减成功后再往订单表插入数据。为什么不能只依赖数据库的update product_sku set stock stock - 1 where id ? and stock 0因为这个操作在高并发下会锁行而且一旦数据库挂了就完全不可用。实际生产里电商系统通常两者结合Redis 扛住峰值流量数据库负责最终一致性对账。不过如果你做的只是一个演示项目单纯用数据库乐观锁实现完全可以UPDATE product_sku SET stock stock - #{count} WHERE id #{skuId} AND stock #{count}受影响行数为 0 就提示用户库存不足简单且不会超卖。5. Vue 端与小程序端管理后台和 C 端页面怎么落地5.1 管理后台Vue3 动态路由与权限控制管理后台我用 Vue3 Element Plus路由分成了静态路由和动态路由两部分。静态路由只有登录页登录成功后根据当前用户的角色管理员、运营、普通用户动态添加路由。项目里我用的是一个asyncRoutes数组里面包含商品管理、订单管理、测评审核、用户管理等页面路由每个路由对象都有meta.roles字段登录成功后遍历过滤出有权限的路由用router.addRoute()动态添加。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (!token to.path ! /login) { next(/login) } else if (token) { const userInfo JSON.parse(localStorage.getItem(userInfo)) if (userInfo userInfo.role ADMIN) { next() } else { next(/403) } } else { next() } })这里建议把动态路由做成一个小函数部署的时候直接执行一遍看是否有报错因为路由表一旦编译错误管理后台整个白屏而控制台里的报错往往要到跳转时才触发排查周期很长。5.2 小程序端uniapp 打包微信小程序的适配细节C 端我用的 uni-app核心页面包括首页信息流、测评详情页、商品详情页、购物车、订单确认、支付、个人中心、发布测评页。这里有几个微信小程序特有的适配点新手容易踩页面头部标题小程序页面的标题栏要在pages.json里配置navigationBarTitleText它是个静态配置没法在页面 onLoad 里随时改除非你用uni.setNavigationBarTitle()。顶部导航栏高度微信小程序的胶囊按钮高度在不同机型上不一样iPhone 老机型和新机型的statusBarHeight差别很大。我在公共样式里用了 CSS 变量var(--status-bar-height)uni-app 自己封装了这个变量适配刘海屏和挖孔屏时非常好用。列表加载更多首页信息流和测评列表是典型的瀑布流分页场景用onReachBottom触底加载onReachBottom() { if (this.page * this.limit this.total) { uni.showToast({ title: 没有更多了, icon: none }) return } this.page 1 this.loadData() }注意onReachBottom触发很频繁用户滚动到底部可能会连续触发两三次建议加一个isLoading锁请求没结束前丢弃后续触发。微信登录uni-app 里直接uni.login({ provider: weixin })拿到 code再调后端接口。这里有个细节如果你用uni.getUserProfile()去拿用户头像昵称微信官方已经限制了2022 年后新注册的小程序不让通过这个接口直接弹出授权头像昵称了项目里最好引导用户去个人中心手动上传头像、填写昵称。5.3 测评视频播放带 m3u8 的免安装方案测评内容里除了图片还能传短视频我当时顺便把视频播放也处理了。如果你的视频文件是 m3u8 分片格式在微信小程序里可以直接用官方video组件播放因为微信内置了解码器video :srcvideoUrl object-fitcontain autoplay playsinline controls /video但要注意 m3u8 的域名必须在小程序后台的“业务域名”白名单里否则就是黑屏加报错。如果是 H5 端你后续把同一套代码编译成浏览器版浏览器不能原生播放 m3u8需要用 hls.js 做封装这个项目里我在 H5 端加了一个 video 播放器组件内部集成 hls.js加载时判断Hls.isSupported()支持就用 Hls 对象挂载播放不支持就走原生 video。整套下来大约是几十行代码但解决了“免安装播放”的需求。5.4 小程序调试与抓包charles 和开发者工具的正确用法小程序联调阶段最大的痛点是看不到后端返回的真实接口数据。我推荐两种方式第一种最直接微信开发者工具里勾选“不校验合法域名”然后把本地后端接口地址填进去直接跑通联调第二种就是抓包我用的 charles 抓包工具。把手机和电脑连同一个 WiFi手机 HTTP 代理指向电脑 IP 的 8888 端口再安装 charles 的 SSL 证书就能看到小程序发出的所有 HTTPS 请求包括微信登录的 code、后端返回的 token。抓包对排查“小程序端收到了什么、到底发了什么”非常有用尤其是支付回调、token 过期这类前后端衔接问题五分钟就能定位是前端传参的问题还是后端返回的问题。6. 常见问题避坑速查表这些坑我替你踩过了6.1 后端编程中的高频问题问题原因解决方案时间字段差 8 小时数据库连接串没有设置时区url 里加serverTimezoneAsia/Shanghai上传大视频报 413Nginx 默认请求体太小client_max_body_size 100m;MinIO 图片地址过期用了预签名 URL 且过期时间短桶设置为公开读直接拼字符串地址JWT 解析失败 401token 过期或签名不一致登录时设置统一的密钥filter 里校验SpringBoot 启动报循环依赖不同 service 互相注入把被依赖的方法抽到单独类或加Lazy6.2 SpringBoot 版本和 Maven 构建的坑我遇到的一个典型问题是 Maven 构建时本地仓库缺少新依赖导致报编译错误。解决思路是确认 JDK 版本和 SpringBoot 版本配套2.7.x 对应 JDK 83.x 对应 JDK 17。如果你是 JDK 17 环境硬跑 SpringBoot 2.3.x 的老项目会报错。还有一个常见的坑是 Maven 打包后 jar 文件非常大里面包含了 SpringBoot 插件的 repackage 打包如果你在构建部署时用的是mvn package默认配置一定要注意spring-boot-maven-plugin的配置否则打出来的包里缺主类一启动就是no main manifest attribute。6.3 前端打包放进 SpringBoot 的路径问题这个项目最后要能一键部署我选择把 Vue 管理后台打包后的dist目录直接复制到 SpringBoot 的src/main/resources/static下面。这里有两个注意点一是 Vite 打包时要修改base: ./否则静态资源路径会在ip:8080这个根路径上带一层绝对路径导致 JS 和 CSS 加载不到二是前端路由要用 hash 模式否则在二级页面手动刷新就会 404。如果你非要 history 模式就在 SpringBoot 里写一个WebMvcConfigurer的addViewControllers把所有非 API 的路径都 forward 到index.html。6.4 小程序审核和上线准备小程序端做好之后提交微信审核前我提醒自己检查这几项类目选择要和商城测评匹配如果是个人主体千万不能选错类目个人主体小程序里“商城”类目基本不开放这也是为什么很多同类项目会挂到公司主体下页面必须完整不能留空的 tabBar 页面用户隐私协议、客服功能、用户反馈入口都要有。测评内容涉及化妆品功效类词汇比如“美白”“祛斑”“治疗”这类词很容易触发审核规则我做的处理是在发表测评接口里加入关键词过滤交给运营在后端人工二次审核避免内容直接上墙。6.5 我的一点实战心得最后说几条我个人觉得特别有用的经验。第一做这种商城测评项目不要一上来就写代码先把关键页面流程用小程序的页面草稿画一遍页面之间的联系理清楚之后表结构和接口都顺了。第二尽量提前把数据权限和内容审核做进去化妆品测评涉及功效宣称后期平台合规要求只会越来越严早做比晚做强。第三如果时间紧张优先跑通“微信登录-浏览测评-下单支付-发布测评”这条最小闭环其他功能比如优惠券、积分、分享裂变都可以迭代补上。这个项目的价值更多在于帮你完整掌握一套现代 Web 应用从零到一的构建流程SpringBoot 后端接口设计、SQL 数据建模、Redis 缓存使用、对象存储接入、Vue3 管理后台、uni-app 小程序端打包上线。美妆加测评这个业务方向本身也很有延展性后续完全可以在此基础上扩展社区问答、达人榜单、AI 肤质分析、商品对比等功能。希望这篇拆解能给你省下一些查资料和踩坑的时间。
阅读完成 · 觉得有帮助?