1. 项目概述:全栈水果电商平台的技术架构
这套"精品水果线上销售系统"采用当前主流的前后端分离架构,后端基于SpringBoot2框架构建RESTful API服务,前端使用Vue3实现响应式用户界面,数据持久层选用MyBatis-Plus操作MySQL8.0数据库。系统完整实现了商品展示、购物车管理、订单处理、支付对接等电商核心功能模块,配套的开发文档和数据库设计文档降低了二次开发门槛。
技术选型亮点:Vue3的Composition API大幅提升代码复用性,SpringBoot2的内嵌Tomcat简化部署,MyBatis-Plus的ActiveRecord模式让数据库操作更直观,MySQL8.0的窗口函数优化了数据分析查询效率。
2. 核心模块设计与实现
2.1 后端工程结构解析
code复制src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── fruitshop/
│ │ ├── config/ # 跨域/安全等配置
│ │ ├── controller/ # 订单/商品等控制器
│ │ ├── entity/ # 数据库实体类
│ │ ├── mapper/ # MyBatis-Plus接口
│ │ ├── service/ # 业务逻辑层
│ │ └── util/ # 工具类包
│ └── resources/
│ ├── mapper/ # XML映射文件
│ ├── application.yml # 多环境配置
│ └── banner.txt # 启动LOGO
2.2 前端工程关键配置
javascript复制// vite.config.js 优化示例
export default defineConfig({
plugins: [
vue(),
// 自动导入Element-Plus组件
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
build: {
chunkSizeWarningLimit: 1500, // 调整chunk大小警告阈值
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor'
}
}
}
}
}
})
3. 数据库设计与优化
3.1 MySQL8.0表结构设计
sql复制CREATE TABLE `product` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`price` decimal(10,2) NOT NULL,
`stock` int NOT NULL DEFAULT '0',
`category_id` int DEFAULT NULL,
`cover_url` varchar(255) DEFAULT NULL,
`detail_html` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci,
`sales` int DEFAULT '0',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`),
FULLTEXT KEY `ft_idx_name_detail` (`name`,`detail_html`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
3.2 性能优化实践
- 利用MySQL8.0的倒序索引优化新品查询:
sql复制ALTER TABLE product ADD INDEX idx_create_time_desc (create_time DESC); - 使用窗口函数实现销量排行:
sql复制SELECT id, name, sales, RANK() OVER(ORDER BY sales DESC) as sales_rank FROM product WHERE category_id = 1; - JSON字段存储商品规格:
sql复制ALTER TABLE product ADD specs JSON DEFAULT NULL;
4. 典型业务逻辑实现
4.1 购物车合并策略
java复制// CartServiceImpl.java
@Transactional
public void mergeCart(Long userId, List<CartItemDTO> tempItems) {
// 1. 查询用户已有购物车项
LambdaQueryWrapper<Cart> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Cart::getUserId, userId);
List<Cart> existItems = cartMapper.selectList(queryWrapper);
// 2. 转换为Map便于查找
Map<Long, Cart> existMap = existItems.stream()
.collect(Collectors.toMap(Cart::getProductId, Function.identity()));
// 3. 遍历临时购物车项
for (CartItemDTO item : tempItems) {
if (existMap.containsKey(item.getProductId())) {
// 4. 存在则更新数量
Cart cart = existMap.get(item.getProductId());
cart.setQuantity(cart.getQuantity() + item.getQuantity());
cartMapper.updateById(cart);
} else {
// 5. 不存在则新增记录
Cart newCart = new Cart();
BeanUtils.copyProperties(item, newCart);
newCart.setUserId(userId);
cartMapper.insert(newCart);
}
}
}
4.2 订单状态机设计
java复制// OrderStatusEnum.java
public enum OrderStatusEnum {
UNPAID(0, "待支付") {
@Override
public boolean canChangeTo(OrderStatusEnum newStatus) {
return newStatus == PAID || newStatus == CANCELLED;
}
},
PAID(1, "已支付") {
@Override
public boolean canChangeTo(OrderStatusEnum newStatus) {
return newStatus == SHIPPED || newStatus == REFUNDING;
}
},
// ...其他状态
public abstract boolean canChangeTo(OrderStatusEnum newStatus);
// 状态检查工具方法
public static void checkStatusChange(OrderStatusEnum oldStatus, OrderStatusEnum newStatus) {
if (!oldStatus.canChangeTo(newStatus)) {
throw new BusinessException("非法状态变更:" + oldStatus.desc + "->" + newStatus.desc);
}
}
}
5. 部署与监控方案
5.1 多环境打包配置
yaml复制# application-prod.yml
spring:
datasource:
url: jdbc:mysql://mysql-prod:3306/fruit_shop?useSSL=false&serverTimezone=Asia/Shanghai
username: prod_user
password: ${DB_PASSWORD}
redis:
host: redis-prod
port: 6379
password: ${REDIS_PASSWORD}
# 启用监控端点
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
5.2 前端性能优化
- 路由懒加载配置:
javascript复制const routes = [ { path: '/product/:id', component: () => import('@/views/ProductDetail.vue'), meta: { keepAlive: true } } ] - Webpack分包策略:
javascript复制// vite.config.js export default { build: { rollupOptions: { output: { manualChunks: { echarts: ['echarts'], element: ['element-plus'] } } } } }
6. 开发中的典型问题解决
6.1 MyBatis-Plus批量更新陷阱
java复制// 错误示例:批量更新返回行数不准确
List<User> users = userService.listByIds(ids);
users.forEach(u -> u.setStatus(1));
userService.updateBatchById(users); // 即使数据未变化也会返回成功
// 正确做法:添加更新条件
boolean success = userService.lambdaUpdate()
.in(User::getId, ids)
.set(User::getStatus, 1)
.update();
6.2 Vue3路由状态保持
javascript复制// 使用keep-alive配合路由meta
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" v-if="$route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!$route.meta.keepAlive" />
</router-view>
// 列表页离开时保存查询条件
onBeforeRouteLeave((to, from, next) => {
if (to.name === 'ProductDetail') {
sessionStorage.setItem('queryParams', JSON.stringify(queryParams.value));
}
next();
});
7. 安全防护措施
7.1 接口防刷策略
java复制@RestController
@RequestMapping("/api")
public class ApiController {
@RateLimiter(value = 10, key = "'sms_' + #phone") // 10秒内限流
@PostMapping("/sms-code")
public Result sendSmsCode(@RequestParam String phone) {
// 发送验证码逻辑
}
}
7.2 SQL注入防护
- 始终使用MyBatis-Plus的条件构造器:
java复制// 安全写法 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.like("username", param).eq("status", 1); // 危险写法(绝对避免) String sql = "SELECT * FROM user WHERE username LIKE '%" + param + "%'"; - 启用MyBatis的拦截器进行语句检查:
java复制@Intercepts(@Signature(type= StatementHandler.class, method="prepare", args={Connection.class, Integer.class})) public class SqlInjectInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { // 检查SQL语句是否包含危险关键词 } }
8. 扩展功能实现思路
8.1 秒杀功能设计
java复制// 分布式锁实现秒杀
public boolean seckill(Long productId, Long userId) {
String lockKey = "seckill:" + productId;
String requestId = UUID.randomUUID().toString();
try {
// 获取分布式锁
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, requestId, 10, TimeUnit.SECONDS);
if (!locked) return false;
// 1. 校验库存
Product product = productMapper.selectById(productId);
if (product.getStock() <= 0) return false;
// 2. 扣减库存(乐观锁)
int updated = productMapper.updateStock(productId, product.getVersion());
if (updated == 0) return false;
// 3. 创建订单
createSeckillOrder(product, userId);
return true;
} finally {
// 释放锁
if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
redisTemplate.delete(lockKey);
}
}
}
8.2 推荐算法集成
python复制# Python推荐服务示例(通过HTTP接口调用)
@app.route('/recommend', methods=['POST'])
def recommend():
user_id = request.json.get('user_id')
# 1. 从MySQL读取用户行为数据
history = mysql.query(
"SELECT product_id FROM orders WHERE user_id = %s", (user_id,))
# 2. 使用协同过滤算法
model = load_model('cf_model.pkl')
recommendations = model.predict(history)
# 3. 返回推荐结果
return jsonify({
'code': 200,
'data': recommendations[:5]
})
项目部署建议:使用Docker Compose编排服务,包含MySQL8.0、Redis、Nginx和SpringBoot应用容器,通过Prometheus+Grafana实现监控。前端项目建议开启gzip压缩和HTTP/2提升加载速度。
