news 2026/9/11 5:44:17

Spring Boot整合SSM与Vue构建美容院商城预约管理系统实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot整合SSM与Vue构建美容院商城预约管理系统实战

年前刚把一个美容院美妆化妆品商城管理系统从头到尾撸完,后端用 Spring Boot 整合 SSM(Spring + Spring MVC + MyBatis),前端用 Vue 做前后端分离,从数据库建模到权限控制再到订单流程全部自己搞定。整个项目做完之后最大的感受是:这类“管理系统”看起来到处都是,但真要把业务逻辑理清楚、把坑踩平、把代码写得能给别人看,还是有不少门道。

这套系统表面上是个电商商城,实际上揉进了两种完全不同的业务场景:一边是标准的美妆商品在线购买流程,另一边是美容院线下服务项目的预约与管理。这也让它比普通的商品管理系统多了不少可聊的点。这篇文章我就把整个项目的设计思路、核心模块实现、实操过程和踩坑记录完整分享出来。如果你正在做类似的毕设、接私活,或者想用这套技术栈练手,应该能省下不少时间。

1. 项目整体设计与业务模块拆解

1.1 项目定位:这个商城系统到底给谁用

在动手写代码之前,我先把项目的使用场景捋了一遍。市面上常见的商城系统大多是纯线上零售,比如卖衣服鞋子数码产品,这类系统的核心是商品、购物车、订单、支付、物流。但美容院场景有个特殊性:它不仅有实体美妆产品的销售,还有线下服务项目,比如面部护理、身体按摩、美甲美睫这类需要提前预约、分配美容师和工位的服务。

所以这套系统实际包含了三个端:

  • 前台商城端:给普通用户用,浏览商品、加购物车、下单、查看订单、预约美容服务、查看美妆资讯。
  • 后台管理端:给管理员和运营用,管理商品分类、商品上下架、库存、订单处理、用户管理、轮播图配置、优惠券设置。
  • 美容院运营端:给门店美容师或店长用,查看预约记录、管理服务项目、维护排班和技师信息。

我选择把它定位成“商品销售 + 服务预约”双核心模式,而不是单纯做个商品展示加购物车的demo。原因很简单:纯商品商城在网上随便一搜就有无数个,但能把服务预约和商品销售结合好的项目不多,这个差异点无论放在毕设答辩还是接私活的项目演示里,都更容易讲出东西。

1.2 功能模块拆解与角色权限划分

整个系统的功能模块我拆成了六大块,每一块再往下细化成具体功能点:

模块前端功能后台功能
用户中心注册登录、个人信息编辑、收货地址管理用户列表、用户状态管理(禁用/启用)
商品中心分类浏览、商品搜索、商品详情、多规格选择分类管理、商品管理、SKU管理、库存管理
购物车与订单购物车增删改查、结算、下单、订单列表、订单详情订单列表、发货、订单备注、退款处理
服务预约服务项目展示、选择门店/技师/时间、提交预约预约管理、排班设置、服务项目维护
营销模块优惠券领取、下单抵扣优惠券发放、核销统计
系统管理首页轮播图、公告展示轮播图配置、公告管理、管理员账号管理

角色权限这块,我没有简单用一张用户表加一个role字段糊弄过去,而是用了 RBAC(基于角色的访问控制)模型。系统里规划了四类角色:超级管理员、运营人员、美容师、普通用户。后端在做接口权限的时候,用的方式是自定义注解 + 拦截器,给需要鉴权的接口标注需要的角色,拦截器统一校验当前用户角色。

提示:很多新手做权限控制喜欢在每个Controller方法里手写if判断,写三五个接口还没什么感觉,接口一多就全是重复代码。用注解加拦截器的方案,后面扩展新接口只需要加一行注解,维护成本低很多。

2. 技术选型思路:为什么是 Spring Boot + SSM + Vue

2.1 先搞清 SSM 和 Spring Boot 的关系

标题里同时出现“springboot”和“ssm”,很多刚入门的人会困惑:这俩不是两套东西吗?其实不是。SSM 指的是 Spring、Spring MVC、MyBatis 这三个框架的整合使用,而 Spring Boot 可以理解成对 Spring 生态体系的一次大幅简化封装。用 Spring Boot 搭出来的项目,底层仍然是 Spring + Spring MVC + MyBatis 这套架构在跑。

我打个比方:SSM 就像给你一堆食材和菜谱,需要自己开火、自己调火候、自己掌握下锅顺序;Spring Boot 就像给了你一台智能料理机,食材放进去、选好模式,它自己把火候和顺序都处理好了,但做出来的还是那道菜。

所以“springboot + ssm”这个组合,在真实项目里通常指的是:使用 Spring Boot 作为基础工程框架,整合 Spring MVC 处理前端请求,使用 MyBatis 操作数据库。这个组合的优势非常明显:

  • MyBatis 对 SQL 的控制力很强,商城系统里各种多表联查、动态条件查询、分页统计,用 MyBatis 写起来非常顺手。
  • Spring Boot 自动配置解决了传统 SSM 项目里大量 XML 配置的问题,开发效率提升明显。
  • 这套技术栈在国内中小型项目里的普及率极高,遇到问题搜索答案非常容易。

2.2 前后端分离架构下的核心机制

这套系统我采用了完全的前后端分离架构,前端 Vue 项目通过 HTTP 接口与后端交互。分离架构带来一个需要重点设计的环节:接口规范。

我定的统一返回结构是这样的:

{ "code": 200, "message": "操作成功", "data": { } }

code 用 200 表示成功,404 表示资源不存在,500 表示服务器异常,401 表示未登录或 token 失效,403 表示没有权限。前端 axios 响应拦截器会统一处理这些 code,遇到 401 自动跳转登录页,遇到 500 弹出错误提示。

接口风格用的是 RESTful 设计,比如:

  • GET /api/products 获取商品列表
  • POST /api/products 新增商品
  • PUT /api/products/{id} 修改商品
  • DELETE /api/products/{id} 删除商品

统一返回结构这件事看似简单,但必须从一开始就定好。我见过不少项目,有的接口返回 {code, msg, data},有的接口直接返回一个数组,有的返回布尔值,前端每次接数据都要看一遍后端代码,联调效率极低。

认证方案选择了 JWT(JSON Web Token)+ Token 拦截器。用户登录成功后后端生成一个 token 返回给前端,前端存在本地存储里,之后每次请求都在请求头里带上 Authorization 字段。后端拦截器从请求头取 token,解析出用户 ID 和角色信息放到请求上下文里,后面的接口直接用就行。

3. 数据库设计与核心业务实现

3.1 核心表结构设计

数据库设计是整个项目最重要的一步,我花的时间比写代码还多。核心表包括用户表、分类表、商品表、SKU表、购物车表、订单表、订单明细表、优惠券表、预约表。这里我把最关键的商品相关表结构和大家拆解一下。

用户表:

CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录用户名', `password` varchar(100) NOT NULL COMMENT '密码,BCrypt加密', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `phone` varchar(20) DEFAULT NULL COMMENT '手机号', `avatar` varchar(255) DEFAULT NULL COMMENT '头像地址', `role` tinyint(4) NOT NULL DEFAULT '1' COMMENT '角色:0超管 1用户 2运营 3美容师', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1正常 0禁用', `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

分类表:

CREATE TABLE `category` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `parent_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '父分类ID,0表示顶级', `name` varchar(50) NOT NULL COMMENT '分类名称', `sort` int(11) NOT NULL DEFAULT '0' COMMENT '排序值,越小越靠前', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1启用 0禁用', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品分类表';

商品表:

CREATE TABLE `product` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(200) NOT NULL COMMENT '商品名称', `category_id` bigint(20) NOT NULL COMMENT '分类ID', `main_image` varchar(255) DEFAULT NULL COMMENT '主图', `images` text COMMENT '商品轮播图,JSON数组', `detail` longtext COMMENT '商品详情', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1上架 0下架', `sales` int(11) NOT NULL DEFAULT '0' COMMENT '销量', `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';

SKU表:

CREATE TABLE `sku` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `product_id` bigint(20) NOT NULL COMMENT '商品ID', `specs` varchar(255) NOT NULL COMMENT '规格描述,如 颜色:红色;容量:100ml', `price` decimal(10,2) NOT NULL COMMENT '售价', `stock` int(11) NOT NULL DEFAULT '0' COMMENT '库存', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态:1启用 0禁用', PRIMARY KEY (`id`), KEY `idx_product` (`product_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='SKU表';

还有订单表、订单明细表、购物车表、优惠券表、预约表等,结构上遵循的原则是一样的:字段必加注释、主键用 bigint 自增、时间字段默认当前时间、需要查询的字段尽量建索引。

3.2 多级分类与 SKU 库存

商品分类我设计了 parent_id 自关联字段,这样就能支持无限层级分类。比如“美妆护肤”是顶级分类,下面可以有“面部护理”“身体护理”,“面部护理”下面还可以有“洁面”“面膜”等子分类。前端在展示分类时,通过递归方式把树状结构渲染出来。后端提供一个接口返回全部分类,前端一次性拿到后自己构建树,比每次展开都请求接口体验好很多。

SKU 这个概念对没接触过的读者需要解释一下。美妆商品有个特点,同一款产品往往有不同容量、不同色号,比如一瓶精华有 30ml 和 50ml 两种规格,价格和库存都不一样。如果每个规格都单独建一条商品记录,后台管理会非常痛苦,前台展示也很奇怪,全部堆在一起像重复商品。SKU 的方案就是商品信息只维护一份,规格相关的内容放到 sku 表里,每个规格对应一条 sku 记录,有自己的价格和库存。

前端商品详情页会展示规格选择器,用户选了某个规格后,页面动态切换到对应 sku 的价格和库存。用户加入购物车时,提交的是 sku_id 加数量,这样购物车里的每个条目都精确对应一个具体规格的商品。

3.3 订单流程与库存扣减

订单模块是整个系统业务逻辑最复杂的地方。我设计的状态流转是:待付款 -> 待发货 -> 已发货 -> 已完成,另外还有已取消和退款中两个分支状态。

下单接口的核心逻辑按顺序执行以下步骤:

  1. 校验用户传入的购物车条目是否为空、每个商品是否上架、sku 是否存在。
  2. 判断 sku 库存是否充足,不足的条目直接返回错误提示。
  3. 计算订单总金额,验算前端传过来的金额与后端重新计算的结果是否一致。这一点必须做,否则用户修改请求参数就可以用异常低价下单。
  4. 生成订单号和订单记录,批量插入订单明细。
  5. 更新 sku 库存。
  6. 删除对应的购物车记录。
  7. 如果使用了优惠券,同步核销优惠券并记录使用情况。

这整个流程必须放在同一个事务里,任何一步失败都要全部回滚。我在实现时直接用 @Transactional 注解,并指定回滚类型:

@Transactional(rollbackFor = Exception.class) public OrderVO createOrder(OrderCreateDTO dto) { // 业务逻辑 }

库存扣减这里有个经典的并发问题:两个用户同时买了同一个 sku,此时库存只剩 1 件,如果不做任何控制,两个请求都可能先查询到库存充足,然后都执行扣减,最终库存变成负数,出现超卖。

解决办法是在扣减库存时使用带条件的 update 语句:

UPDATE sku SET stock = stock - #{quantity} WHERE id = #{skuId} AND stock >= #{quantity}

这条语句执行后返回受影响行数,如果为 0,说明库存不足或商品已被其他请求扣完,此时抛出库存不足异常,事务回滚。这种方式效率高,也不用引入复杂的分布式锁。

4. 实操过程:从零搭建前后端项目

4.1 后端工程初始化与关键配置

后端工程我直接用 IDEA 创建 Spring Boot 项目。这里有个常见问题必须先说:直接用 IDEA 默认的 https://start.spring.io 创建项目经常超时,尤其是网络环境不稳定的情况下,等半天还是失败。

解决方法是把创建项目的地址换成阿里云镜像:

https://start.aliyun.com

在 IDEA 的 New Project 窗口,把 Spring Initializr 的 URL 改成这个地址,创建速度和成功率会明显提升。但要注意,阿里云镜像上支持的 Spring Boot 版本可能比官方更新的慢一点,我建议选择 2.7.x 系列,这既兼容性稳定,也符合绝大多数项目需求。Spring Boot 3.x 虽然已经出来了,但它基于 JDK 17,并且部分第三方 starter 的兼容性还需要时间沉淀,对于以学习和稳定交付为目标的项目,没必要追新版本。

项目依赖方面,我添加了 spring-boot-starter-web、spring-boot-starter-validation、mybatis-spring-boot-starter、mysql-connector-java、lombok、jjwt 等。这里有个细节:MyBatis 的 starter 在较新版本里是mybatis-spring-boot-starter,需要注意选择与 Spring Boot 版本匹配的版本号,我用的是 2.3.1。

application.yml 里的关键配置:

spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/beauty_mall?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的密码 mybatis: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

关于 map-underscore-to-camel-case 这个配置,一定要设置为 true。数据库字段都是下划线风格,比如 created_time,而 Java 实体类属性是驼峰风格,比如 createdTime,开启这个配置后 MyBatis 就能自动完成映射,省去在 XML 里一个一个写 resultMap 的麻烦。

还有个小细节可以提升开发体验,项目开发阶段开启 SQL 日志输出:

logging: level: com.example.mapper: debug

这样 MyBatis 执行的每一条 SQL 和参数都会在控制台输出,排查问题会方便很多。但上线前记得关掉。

4.2 前端 Vue 工程搭建与路由设计

前端我选的是 Vue 3 + Vite。创建项目的命令:

npm create vue@latest

这个命令会生成一个基于 Vite 的 Vue 3 项目。执行过程中可以选择是否安装 Vue Router、Pinia、ESLint 等,根据自己的需要勾选即可。

前端项目依赖安装是个容易卡住的环节。如果 npm install 速度极慢或者直接卡死,改成国内镜像源:

npm config set registry https://registry.npmmirror.com

改完之后再执行安装,速度会有质的提升。另外 npm 偶尔会出现缓存问题,安装到一半报各种奇怪的错,试试清理缓存:

npm cache clean --force

项目目录结构我按功能做了划分:

src/ api/ // 接口请求封装,按模块拆分 assets/ // 静态资源 components/ // 公共组件 router/ // 路由配置 store/ // Pinia 状态管理 utils/ // 工具函数,axios 封装在这里 views/ // 页面视图

路由这块有两个模式需要知道:createWebHistory 和 createWebHashHistory。history 模式的 URL 更美观,没有 # 号,但刷新页面时如果后端没配置路径重写,会出现 404。hash 模式 URL 里有 #,不用后端配合,但美观度差一些。

本地开发时,由于 Vite dev server 默认支持 history fallback,所以直接用 history 模式没问题。部署到线上时,如果用了 Nginx,需要加一段配置把未知路径都重写到 index.html:

location / { try_files $uri $uri/ /index.html; }

我把这个配置写在部署小节,方便线上部署时查阅。

4.3 登录注册与 token 权限控制的完整实现

登录逻辑是整个前后端联动最典型的模块,我完整梳理一遍。

后端部分:

用户注册时,密码不存明文,用 BCrypt 加密。Spring Security 里自带了 BCryptPasswordEncoder,但为了不引入整套 Spring Security,我直接使用了 spring-security-crypto 这个单独依赖,只用到它的加密能力。

注册接口核心代码:

@PostMapping("/register") public Result<Void> register(@RequestBody @Valid UserRegisterDTO dto) { // 检查用户名是否已存在 if (userMapper.findByUsername(dto.getUsername()) != null) { return Result.error("用户名已存在"); } // 密码加密 String encodedPassword = BCrypt.hashpw(dto.getPassword(), BCrypt.gensalt()); User user = new User(); user.setUsername(dto.getUsername()); user.setPassword(encodedPassword); user.setNickname(dto.getNickname()); userMapper.insert(user); return Result.success(); }

登录时用 BCrypt.checkpw 校验密码。登录成功生成 JWT token:

public String generateToken(Long userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }

token 有效期我设了 24 小时,对商城这种使用频率来说比较合适,太短了用户要频繁重新登录,太长了安全风险增加。

前端部分:

axios 封装里最关键的是请求拦截器和响应拦截器。请求拦截器负责从 Pinia store 里取 token 并放到请求头:

service.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = token } return config }, error => Promise.reject(error) )

响应拦截器统一处理业务状态码:

service.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res.data } if (res.code === 401) { // token 过期,跳转登录页 router.push('/login') return Promise.reject(new Error('登录已过期')) } ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) }, error => { ElMessage.error(error.message || '网络异常') return Promise.reject(error) } )

前端路由守卫用来控制页面访问权限。未登录用户只能访问登录页、注册页、商品浏览和详情页,进入购物车、订单中心、用户中心这些需要登录的页面时,会跳转到登录页:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next('/login') } else { next() } })

这套 token 方案实现起来快,也能满足大多数中小型系统的需求。它的局限性是 token 一旦在有效期内泄露,别人可以冒用你的身份,这就是为什么我强调生产环境必须使用 HTTPS。如果想更严谨,可以做成双 token 机制(accessToken + refreshToken),短 token 过期后通过刷新接口自动续期,体验和安全都能兼顾,但复杂度会上去一些,后面有空我再单独讲。

5. 常见问题与排查技巧实录

5.1 环境与工程初始化问题

这部分问题我在开发过程中全踩过一遍,直接整理成速查表:

问题现象原因解决方案
IDEA 创建 Spring Boot 项目超时默认 start.spring.io 访问不稳定改用阿里云镜像 https://start.aliyun.com
Maven/ npm 依赖下载极慢使用了国外仓库换成阿里云 Maven 镜像或 npmmirror
Spring Boot 版本太高导致依赖冲突starter 之间版本不兼容使用 Spring Boot 2.7.x 稳定版本,Maven 统一依赖版本管理
MySQL 连接报时区错误jar 包或 JDBC 连接串未指定时区连接串加 serverTimezone=Asia/Shanghai
MyBatis 映射 XML 找不到mapper-locations 路径写错确认 classpath:mapper/*.xml 与实际路径一致

这里重点提醒一下 Spring Boot 版本问题。Spring Boot 3.x 刚发布那阵子,我尝试把一个旧项目升级上去,结果因为个别第三方 starter 没有适配,折腾了一整天最后还是放弃了。所以做项目应该追求稳定交付,而不是新版本尝鲜。等一个版本出来一年以上,生态成熟了,再考虑升级。

5.2 前后端联调与数据交互问题

前后端分离之后的联调阶段,问题的出现频率比想象中高很多,而且很多问题看着像前端问题,仔细排查却发现是后端配置没弄对。

第一个高频问题是跨域。前端在 localhost:5173,后端在 localhost:8080,直接请求肯定被浏览器拦截。我后端的处理方式是通过配置类统一配置跨域:

@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); } }

生产环境建议把 allowedOriginPatterns 换成实际的域名,而不是用通配符,这样更安全。

第二个问题是请求参数解析失败。前端用 JSON 格式提交,后端用 @RequestBody 接收,如果后端没配置 JSON 序列化或者前端 content-type 不对,就会报 415 或 400。排查时我习惯先在浏览器开发者工具的网络面板里看下请求头,确认 content-type 是 application/json,然后看后端日志里具体是哪个字段解析失败。时间字段容易踩坑,比如前端传的 "2025-01-26 14:30:00" 要能被正确解析,需要在后端统一配置日期格式:

@Configuration public class JacksonConfig { @Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder -> builder .serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))) .deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } }

第三个问题是前端 Vue Router 用了 history 模式,部署到服务器后刷新页面就 404。这本质上是后端的静态资源服务器没有把前端路由的 URL 重写到 index.html。Nginx 加 try_files 就能解决,前面配置也提到过。本地开发不会出现这个问题,所以很多人是部署上线时才发现。

5.3 安全加固与线上部署经验

系统开发完成后,我专门做了一轮安全加固,有几个点很值得分享。

第一个是关于 Spring Boot Actuator 的安全配置。Actuator 是 Spring Boot 提供的监控组件,里面包含了一些敏感接口,其中 /actuator/heapdump 这个端点可以直接把 JVM 堆内存 dump 下来,而堆内存里可能包含数据库连接信息、用户会话、配置项等敏感内容。这在生产环境是非常危险的漏洞。我的处理方式是在配置文件中禁用这些敏感端点:

management: endpoints: web: exposure: include: health,info

只暴露 health 和 info 两个端点用于健康检查,其他全部关闭。如果你线上确实需要用到一些监控信息,建议配合 Spring Security 做访问限制,只允许内网或特定 IP 访问。

第二个是数据库密码等敏感配置的加密。application.yml 里直接写数据库明文密码,如果项目代码不小心上传到公共仓库,或者服务器被入侵拿到配置,密码就直接暴露了。我用的是 jasypt-spring-boot-starter,配置里写成密文:

jasypt: encryptor: password: ${JASYPT_PASSWORD} spring: datasource: password: ENC(加密后的密文)

解密密钥通过环境变量传入,不写死在代码里。这样即使配置泄露,没有密钥也无法反推出真实密码。

第三个是文件上传的安全限制。商城系统需要上传商品图片、用户头像,如果不对上传做限制,攻击者可以上传恶意文件。我在后端做了三个层面的控制:文件类型白名单校验、文件大小限制(单文件最大 5MB)、上传目录禁止执行脚本。文件路径使用 UUID 重命名,避免路径穿越攻击。

部署这块,我最终的方案是:

  • 后端:打包成 jar,直接运行java -jar xxx.jar,用 systemd 或宝塔面板做进程守护。
  • 前端:npm run build 生成静态文件,部署到 Nginx 的 html 目录。
  • 数据库:使用 MySQL 8.0,线上开启 binlog,定期自动备份。

用宝塔面板部署的话,整个过程可以在网页上点几下完成,对新手环境很友好。但我自己实际维护的时候更倾向用命令行加 systemd 的方式,启动日志、进程管理、故障恢复都更透明。

还有一个自动建表的小技巧值得单独说一下。有段时间项目在测试环境和生产环境之间频繁切换,手工维护 SQL 脚本会经常忘执行。后来我用 spring.sql.init 结合 schema.sql 做自动建表,但这个方式对已有表不会重复建,也不会动数据。如果团队用的是 MyBatis Plus,还可以考虑 MyBatis Plus 代码生成器先建好表再生成代码,路径更顺畅。注意生产环境我不建议让框架自动改表结构,改表是有风险的,最好通过专门的发布流程执行 SQL 脚本。

从数据库建模、角色权限设计,到前端路由守卫、SKU 库存扣减,再到最后的 token 认证、安全加固,整个项目做下来我最大的体会是:这类系统真正的难点从来不是某个功能写不出来,而是模块之间的耦合关系理不清。比如下单逻辑涉及用户、购物车、库存、订单、优惠券五个模块,如果前面表的关联设计不清晰,后面每一个接口都会写得很痛苦。

如果让我再做一遍,我会在项目启动初期把更多的精力放在接口文档上,哪怕是手写一个简单的接口清单,把所有接口的请求参数和返回结构提前定义好,前后端并行开发时能省掉大量来回确认的时间。这一点在团队协作和接私活的场景下,价值比多写几个功能还大。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 5:42:49

Arm-2D在Cortex-M上的静态图形加速工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 5:42:45

Windows平台OpenClaw AI框架安装与配置全攻略

1. OpenClaw在Windows平台的完整安装指南 OpenClaw作为一款新兴的AI智能体开发框架&#xff0c;在开发者社区中逐渐流行起来。不同于常规的AI工具&#xff0c;它提供了本地化部署、多模型接入和自定义技能扩展等特性。本文将详细演示Windows环境下从零开始部署OpenClaw的全过程…

作者头像 李华
网站建设 2026/9/11 5:42:08

初识USB:从概念到实践,解析ESP32-P4的USB枚举与调试

做嵌入式开发&#xff0c;尤其是这两年开始接触带USB OTG的高性能MCU之后&#xff0c;我越来越觉得USB是个“用得着、说不清”的东西。点灯、串口打印、I2C读个传感器都还好说&#xff0c;一旦涉及到USB&#xff0c;什么描述符、端点、枚举、类请求全冒出来了&#xff0c;光是“…

作者头像 李华
网站建设 2026/9/11 5:42:06

51单片机硬件底层原理与工程调试实战指南

1. 这不是“看视频学单片机”&#xff0c;而是带你亲手把51单片机从芯片手册里“抠”出来你搜“尚硅谷51单片机教程”&#xff0c;页面上跳出来的全是“零基础入门”“保姆级教学”“手把手带你点亮LED”。但现实是&#xff0c;很多同学跟着视频敲完代码、烧录进开发板、LED亮了…

作者头像 李华