1. 项目概述与核心价值
这个基于SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0的游戏销售平台,是一个典型的全栈式Java Web应用。我在实际开发中发现,这种技术组合特别适合需要快速迭代的中小型电商项目。后端采用SpringBoot2提供的自动配置和起步依赖,前端用Vue3的Composition API实现响应式界面,MyBatis-Plus的ActiveRecord模式让数据库操作变得异常简单,而MySQL8.0的JSON支持和窗口函数则为复杂业务逻辑提供了便利。
提示:这套技术栈的版本选择很有讲究 - SpringBoot2保持了对Java8的兼容,Vue3提供了更好的TypeScript支持,MyBatis-Plus3.x完美适配SpringBoot2,MySQL8.0则是首个支持原子DDL的版本。
2. 技术架构深度解析
2.1 后端技术栈实现细节
SpringBoot2的后端架构我采用了经典的三层模式:
- Controller层处理HTTP请求,用
@Validated做参数校验 - Service层用
@Transactional保证事务,这里有个坑要注意:默认只对RuntimeException回滚 - DAO层用MyBatis-Plus的BaseMapper,它的Wrapper条件构造器比原生MyBatis方便太多
数据库连接池配置示例:
java复制spring:
datasource:
url: jdbc:mysql://localhost:3306/game_store?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
hikari:
maximum-pool-size: 20
connection-timeout: 30000
2.2 前端工程化实践
Vue3项目我用Vite作为构建工具,比Webpack快一个数量级。目录结构这样组织:
code复制src/
├── api/ # Axios封装
├── assets/ # 静态资源
├── components/ # 通用组件
├── composables/ # 组合式函数
├── router/ # 路由配置
├── stores/ # Pinia状态管理
└── views/ # 页面组件
关键配置技巧:
- 在vite.config.js中配置@路径别名
- 使用Pinia替代Vuex进行状态管理
- 用VueUse库的useIntersectionObserver实现图片懒加载
3. 核心业务模块实现
3.1 商品管理系统
商品表设计我增加了几个优化字段:
sql复制CREATE TABLE `product` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '商品名称',
`price` decimal(10,2) NOT NULL COMMENT '现价',
`original_price` decimal(10,2) DEFAULT NULL COMMENT '原价',
`stock` int NOT NULL DEFAULT '0' COMMENT '库存',
`tags` json DEFAULT NULL COMMENT '标签数组',
`specs` json DEFAULT NULL COMMENT '规格JSON',
`sales` int DEFAULT '0' COMMENT '销量',
`is_hot` tinyint DEFAULT '0' COMMENT '是否热销',
PRIMARY KEY (`id`),
FULLTEXT KEY `ft_name` (`name`) /*!50100 WITH PARSER `ngram` */
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
商品搜索我用Elasticsearch做了全文检索,但小项目可以先用MySQL8的全文索引过渡。MyBatis-Plus的查询构造器这样用:
java复制LambdaQueryWrapper<Product> wrapper = new LambdaQueryWrapper<>();
wrapper.like(Product::getName, keyword)
.ge(Product::getPrice, minPrice)
.orderByDesc(Product::getSales);
return productMapper.selectPage(page, wrapper);
3.2 订单支付流程
支付状态机设计:
mermaid复制stateDiagram
[*] --> 待支付
待支付 --> 已支付: 支付成功
待支付 --> 已取消: 超时未支付
已支付 --> 已发货: 商家发货
已发货 --> 已完成: 用户确认
已发货 --> 退款中: 申请退款
退款中 --> 已退款: 退款成功
支付回调处理要注意幂等性:
java复制@Transactional
public String handlePayNotify(Map<String,String> params){
String orderNo = params.get("out_trade_no");
Order order = orderMapper.selectById(orderNo);
if(order.getStatus() != OrderStatus.UNPAID){
return "success"; // 已处理过的直接返回
}
// 更新订单状态
order.setStatus(OrderStatus.PAID);
orderMapper.updateById(order);
// 记录支付日志
PaymentLog log = new PaymentLog();
// ...设置日志属性
paymentLogMapper.insert(log);
return "success";
}
4. 性能优化实战
4.1 缓存策略设计
我用Redis实现了多级缓存:
- 热点数据缓存:商品详情用
product:{id}作为key - 分布式锁:用SETNX实现秒杀库存锁定
- 页面缓存:将渲染好的商品页HTML存Redis
缓存击穿解决方案:
java复制public Product getProduct(Long id) {
String key = "product:" + id;
// 1. 先查缓存
Product product = redisTemplate.opsForValue().get(key);
if(product == null){
// 2. 获取分布式锁
String lockKey = "lock:product:" + id;
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if(locked){
try {
// 3. 再次检查缓存(双重检查)
product = redisTemplate.opsForValue().get(key);
if(product == null){
// 4. 查数据库
product = productMapper.selectById(id);
// 5. 写入缓存
redisTemplate.opsForValue().set(key, product, 1, TimeUnit.HOURS);
}
} finally {
// 释放锁
redisTemplate.delete(lockKey);
}
} else {
// 未获取到锁,短暂休眠后重试
Thread.sleep(100);
return getProduct(id);
}
}
return product;
}
4.2 MySQL优化实践
我针对游戏销售场景做了这些优化:
- 索引优化:为订单表的user_id和create_time创建联合索引
- 分表策略:按月份对订单日志表进行水平分表
- 查询优化:用EXPLAIN分析慢查询,避免全表扫描
配置示例:
sql复制-- 创建覆盖索引
ALTER TABLE order_item
ADD INDEX idx_order_product (order_id, product_id);
-- 使用窗口函数优化统计查询
SELECT
user_id,
SUM(amount) OVER(PARTITION BY user_id) AS total_amount,
RANK() OVER(ORDER BY SUM(amount) DESC) AS rank
FROM orders
GROUP BY user_id;
5. 安全防护体系
5.1 认证与授权
我用Spring Security + JWT实现认证:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
JWT工具类关键代码:
java复制public class JwtUtil {
private static final String SECRET = "your-secret-key";
private static final long EXPIRATION = 864000000L; // 10天
public static String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles", userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList()));
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
// 验证和解析Token的方法...
}
5.2 常见攻击防护
- XSS防护:前端用vue-dompurify-html处理富文本
- CSRF防护:虽然用了JWT,但还是加了SameSite Cookie属性
- SQL注入:MyBatis-Plus的Wrapper会自动处理参数转义
- 频率限制:用Guava的RateLimiter做API限流
6. 部署与监控
6.1 Docker化部署
后端Dockerfile示例:
dockerfile复制FROM openjdk:8-jdk-alpine
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
docker-compose.yml配置:
yaml复制version: '3'
services:
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: game_store
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6-alpine
ports:
- "6379:6379"
backend:
build: ./backend
ports:
- "8080:8080"
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "80:80"
volumes:
mysql_data:
6.2 监控方案
- Spring Boot Actuator暴露健康检查端点
- Prometheus + Grafana监控系统指标
- ELK收集和分析日志
- 业务指标埋点:用Micrometer统计订单量等业务指标
配置示例:
java复制@Bean
public MeterRegistryCustomizer<PrometheusMeterRegistry> metricsCommonTags() {
return registry -> registry.config().commonTags(
"application", "game-store-platform"
);
}
// 在业务代码中记录指标
@GetMapping("/products/{id}")
public Product getProduct(@PathVariable Long id) {
Metrics.counter("product.view", "productId", id.toString()).increment();
return productService.getById(id);
}
7. 开发中的经验教训
- 版本兼容性问题:Vue3的某些插件还不稳定,要仔细测试
- 事务失效场景:同类内方法调用不会走代理,事务会失效
- 缓存一致性问题:先更新数据库再删缓存,用消息队列保证最终一致
- 前端性能优化:路由懒加载能显著提升首屏速度
一个真实的踩坑案例:有次用MyBatis-Plus的updateById方法更新商品库存,在高并发下出现了超卖。后来改用乐观锁解决:
java复制// 商品表增加version字段
@Version
private Integer version;
// 更新时带上版本号
boolean updateStock(Long productId, Integer quantity) {
Product product = productMapper.selectById(productId);
product.setStock(product.getStock() - quantity);
int rows = productMapper.updateById(product);
return rows > 0;
}
8. 扩展与二次开发建议
- 增加推荐系统:用协同过滤算法实现"猜你喜欢"
- 接入支付渠道:微信支付、支付宝的国际版接口
- 实现分销功能:多级分销佣金计算
- 增加游戏社区:玩家论坛和UGC内容管理
- 多语言支持:用vue-i18n实现前端国际化
对于想基于此项目二次开发的同行,我的建议是从小功能开始迭代。比如先实现一个简单的优惠券系统:
java复制public class CouponService {
@Transactional
public void applyCoupon(Long userId, Long orderId, String couponCode) {
// 1. 验证优惠券有效性
Coupon coupon = couponMapper.selectValidCoupon(couponCode);
if(coupon == null) {
throw new BusinessException("优惠券无效");
}
// 2. 检查使用限制
Order order = orderMapper.selectById(orderId);
if(order.getTotalAmount().compareTo(coupon.getMinAmount()) < 0) {
throw new BusinessException("未达到使用金额门槛");
}
// 3. 计算优惠金额
BigDecimal discountAmount = calculateDiscount(order.getTotalAmount(), coupon);
// 4. 更新订单
order.setCouponId(coupon.getId());
order.setDiscountAmount(discountAmount);
order.setPayAmount(order.getTotalAmount().subtract(discountAmount));
orderMapper.updateById(order);
// 5. 标记优惠券为已使用
couponMapper.useCoupon(couponCode, userId, orderId);
}
private BigDecimal calculateDiscount(BigDecimal amount, Coupon coupon) {
switch (coupon.getType()) {
case FIXED: // 固定金额
return coupon.getValue();
case PERCENT: // 百分比
return amount.multiply(coupon.getValue().divide(BigDecimal.valueOf(100)));
default:
throw new IllegalArgumentException("未知优惠券类型");
}
}
}
