1. 项目概述与背景
这个积分制零食自选超市商城销售平台是一个典型的B2C电商系统,采用前后端分离架构。我在实际开发中发现,积分机制的设计是这类项目的核心难点之一。不同于传统电商,积分系统需要处理复杂的业务逻辑:既要作为促销手段刺激消费,又要避免积分滥发导致的成本失控。
技术栈选择上,SpringBoot+Vue的组合确实能覆盖大部分中小型电商场景。最近帮一个校园创业团队实施类似项目时,他们最初考虑用PHP快速开发,但考虑到后期扩展性和团队技术栈,最终选择了这个方案。Maven的依赖管理在整合第三方SDK(比如微信支付)时特别省心,避免了常见的jar包冲突问题。
2. 技术架构设计
2.1 后端分层架构
典型的四层结构在实践中需要做些调整:
- Controller层:建议统一返回ResultDTO包装类,包含code/message/data三要素。我们项目里还加了traceId用于链路追踪
java复制public class ResultDTO<T> {
private String code; // 如"200"
private String message;
private T data;
private String traceId;
}
- Service层:积分操作必须加@Transactional,我们遇到过用户积分更新了但订单没创建成功的bug。建议这样设计:
java复制@Transactional(rollbackFor = Exception.class)
public void deductPoints(Long userId, Integer points) {
// 先检查积分余额
User user = checkUserPoints(userId, points);
// 再扣减积分
user.setPoints(user.getPoints() - points);
userRepository.save(user);
// 最后记录积分日志
savePointLog(userId, points, "ORDER_PAY");
}
2.2 前端Vue架构
推荐使用Vue3+TypeScript组合,比JavaScript更利于维护。几个关键点:
- 路由懒加载能显著提升首屏速度
javascript复制const routes = [
{
path: '/products',
component: () => import('@/views/ProductList.vue')
}
]
- Vuex状态管理要合理划分module,我们把用户、购物车、积分分成独立模块
javascript复制// store/modules/points.js
export default {
namespaced: true,
state: () => ({
balance: 0
}),
mutations: {
SET_BALANCE(state, value) {
state.balance = value
}
}
}
3. 核心功能实现细节
3.1 积分系统设计
积分规则需要灵活配置,我们最终采用策略模式:
java复制public interface PointsStrategy {
int calculatePoints(Order order);
}
@Component
public class FoodPointsStrategy implements PointsStrategy {
@Override
public int calculatePoints(Order order) {
// 食品类商品10元积1分
return (int)(order.getAmount() / 10);
}
}
积分流水表设计建议包含这些字段:
sql复制CREATE TABLE point_log (
id BIGINT PRIMARY KEY,
user_id BIGINT NOT NULL,
points INT NOT NULL COMMENT '正数表示增加,负数表示减少',
type VARCHAR(20) NOT NULL COMMENT 'ORDER/REFUND/ADMIN等',
biz_id VARCHAR(64) COMMENT '关联业务ID',
balance INT NOT NULL COMMENT '操作后余额',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
3.2 商品管理模块
分页查询接口要注意缓存策略,我们采用两级缓存:
- 本地Caffeine缓存热点商品
- Redis缓存全量商品基础信息
接口示例:
java复制@GetMapping("/products")
public PageResult<ProductVO> listProducts(
@RequestParam(required = false) String keyword,
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size) {
// 先查缓存
String cacheKey = "products:" + keyword + ":" + page;
PageResult<ProductVO> cached = cacheService.get(cacheKey);
if (cached != null) {
return cached;
}
// 缓存没有再查数据库
Page<Product> paged = productService.search(keyword, page, size);
PageResult<ProductVO> result = convertToPageResult(paged);
// 写入缓存
cacheService.set(cacheKey, result, 5, TimeUnit.MINUTES);
return result;
}
4. 开发环境配置
4.1 IDEA高效配置
几个必装的插件:
- Lombok Plugin - 自动生成getter/setter
- MyBatisX - MyBatis映射文件跳转
- Vue.js - 前端开发支持
推荐配置Live Template提高编码效率:
- 输入
psvm快速生成main方法 - 输入
logi生成日志语句:
java复制private static final Logger log = LoggerFactory.getLogger($CLASS$.class);
4.2 Maven多环境配置
在pom.xml中配置profile:
xml复制<profiles>
<profile>
<id>dev</id>
<properties>
<env>dev</env>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<env>prod</env>
</properties>
</profile>
</profiles>
配合application-${env}.yml实现环境隔离。
5. 部署与性能优化
5.1 后端部署要点
- JVM参数调优(2C4G服务器示例):
bash复制java -jar -Xms2g -Xmx2g -XX:MaxMetaspaceSize=512m \
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
-Dspring.profiles.active=prod \
your-application.jar
- 使用Nginx反向代理时注意配置:
nginx复制location /api {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_connect_timeout 60s;
proxy_read_timeout 300s;
}
5.2 前端性能优化
- 配置gzip压缩(vue.config.js):
javascript复制module.exports = {
chainWebpack: (config) => {
config.plugin('compression').use(CompressionPlugin, [{
algorithm: 'gzip',
test: /\.(js|css)$/,
threshold: 10240
}])
}
}
- 使用CDN加速静态资源:
javascript复制// vue.config.js
externals: {
vue: 'Vue',
'vue-router': 'VueRouter',
axios: 'axios'
}
6. 踩坑实录与解决方案
6.1 积分并发问题
我们遇到过用户同时兑换商品导致的积分超扣问题。最终方案:
- 数据库加乐观锁:
java复制@Version
private Integer version;
- 使用Redis分布式锁:
java复制public boolean deductPointsWithLock(Long userId, int points) {
String lockKey = "point:lock:" + userId;
try {
// 尝试获取锁,有效期10秒
boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("操作太频繁");
}
return pointService.deductPoints(userId, points);
} finally {
redisTemplate.delete(lockKey);
}
}
6.2 跨域问题处理
前后端分离时常见的坑,推荐两种方案:
- 后端全局配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.maxAge(3600);
}
}
- Nginx层解决:
nginx复制location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
}
7. 扩展功能建议
7.1 实时库存提醒
使用WebSocket实现低库存预警:
java复制@RestController
@RequestMapping("/ws")
public class StockWebSocket {
@Autowired
private SimpMessagingTemplate messagingTemplate;
@Scheduled(fixedRate = 60000)
public void checkLowStock() {
List<Product> lowStockProducts = productService.getLowStockProducts();
if (!lowStockProducts.isEmpty()) {
messagingTemplate.convertAndSend(
"/topic/stock-alert",
new StockAlert(lowStockProducts)
);
}
}
}
前端订阅:
javascript复制this.sock = new SockJS('/ws');
this.stompClient = Stomp.over(this.sock);
this.stompClient.connect({}, () => {
this.stompClient.subscribe('/topic/stock-alert', (msg) => {
alert(`以下商品库存不足:${JSON.parse(msg.body).productNames}`);
});
});
7.2 智能推荐系统
基于用户积分消费习惯的简单推荐算法:
java复制public List<Product> recommendProducts(Long userId) {
// 1. 获取用户常买品类
List<Category> favoriteCategories = orderService.getFavoriteCategories(userId);
// 2. 获取同品类热销商品
List<Product> hotProducts = productService.getHotProducts(favoriteCategories);
// 3. 按用户积分等级过滤
int userLevel = userService.getUserLevel(userId);
return hotProducts.stream()
.filter(p -> p.getRequiredLevel() <= userLevel)
.limit(10)
.collect(Collectors.toList());
}
这个项目从技术选型到具体实现都有很多值得深入探讨的地方,特别是在积分系统的设计上,需要平衡用户体验和系统安全性。我们在实际开发中总结的经验是:前期把积分流水表设计完善,后期做数据分析时会轻松很多;对于高并发场景,Redis的原子操作比数据库事务更可靠。
