1. 项目背景与核心需求
这个毕业设计选题瞄准了校园场景下的零食消费痛点。在大学宿舍、实验室或办公室环境中,学生们常常面临零食购买不便、选择有限的问题。传统的小卖部营业时间固定,商品种类单一,而外卖配送又存在等待时间长、配送费高等问题。
积分制零食自选平台的核心价值在于:
- 通过B/S架构实现随时随地的零食选购
- 积分兑换机制增加用户粘性(比如通过每日签到、分享推荐获得积分)
- 自助式服务减轻管理员负担
- 为计算机专业学生展示全栈开发能力提供完整案例
提示:选择SpringBoot框架的考虑在于其快速开发特性,特别适合毕业设计这类有时间限制的项目。相比传统的SSM框架,SpringBoot可以节省约40%的配置时间。
2. 技术选型与架构设计
2.1 技术栈组成
前端部分:
- 基础框架:Vue.js 2.x(考虑到学习曲线平缓)
- UI组件库:Element UI(提供丰富的表单和表格组件)
- 图表库:ECharts(用于展示销售数据可视化)
- 构建工具:Webpack 4.x
后端部分:
- 核心框架:SpringBoot 2.7.x(LTS版本)
- 持久层:MyBatis-Plus 3.5.x(简化CRUD操作)
- 数据库:MySQL 8.0(关系型)+ Redis 6.x(缓存积分数据)
- 安全框架:Spring Security + JWT
- 接口文档:Swagger UI 3.0
2.2 系统架构图解
code复制用户层
↓
表现层(Vue前端)
↓
API网关层(SpringBoot)
↓
业务逻辑层
↓
数据访问层(MyBatis-Plus)
↓
数据存储层(MySQL+Redis)
这种分层架构的优势在于:
- 前后端完全解耦,便于独立开发和部署
- 网关层统一处理鉴权和日志
- Redis缓存高频访问的积分数据,减轻数据库压力
- 使用MyBatis-Plus的代码生成器可快速创建基础CRUD接口
3. 核心功能模块实现
3.1 用户积分系统设计
积分规则表结构设计:
sql复制CREATE TABLE `point_rules` (
`id` int NOT NULL AUTO_INCREMENT,
`rule_name` varchar(50) NOT NULL COMMENT '规则名称',
`point_value` int NOT NULL COMMENT '积分值',
`daily_limit` int DEFAULT NULL COMMENT '每日上限',
`status` tinyint DEFAULT '1' COMMENT '是否启用',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
积分流水表关键字段:
java复制public class PointTransaction {
private Long id;
private Long userId;
private Integer pointValue; // 正数表示获得,负数表示消耗
private String sourceType; // 来源类型:SIGN_IN/ORDER/PROMOTION
private String description;
private LocalDateTime createTime;
}
积分计算服务核心逻辑:
java复制@Transactional
public void addUserPoints(Long userId, String sourceType) {
// 1. 查询积分规则
PointRule rule = pointRuleMapper.selectBySourceType(sourceType);
// 2. 检查当日是否已达上限
Integer todayPoints = pointLogMapper.sumTodayPoints(userId, sourceType);
if (rule.getDailyLimit() != null && todayPoints >= rule.getDailyLimit()) {
throw new BusinessException("今日该类型积分已达上限");
}
// 3. 记录积分流水
PointTransaction log = new PointTransaction();
log.setUserId(userId);
log.setPointValue(rule.getPointValue());
log.setSourceType(sourceType);
pointLogMapper.insert(log);
// 4. 更新用户总积分
userMapper.updateUserPoints(userId, rule.getPointValue());
}
3.2 商品库存管理实现
采用乐观锁解决并发问题:
java复制public boolean reduceStock(Long productId, Integer quantity) {
int retry = 0;
while (retry < 3) { // 重试机制
Product product = productMapper.selectById(productId);
if (product.getStock() < quantity) {
return false;
}
int rows = productMapper.updateStock(
productId,
quantity,
product.getVersion()
);
if (rows > 0) {
return true;
}
retry++;
}
throw new ConcurrentUpdateException("库存更新冲突");
}
注意:实际项目中应考虑使用Redis分布式锁或消息队列来更好地处理高并发场景,但考虑到毕业设计的复杂度,乐观锁方案已经足够。
4. 典型业务场景实现
4.1 订单创建流程
- 前端传参示例:
json复制{
"items": [
{
"productId": 101,
"quantity": 2
}
],
"usePoints": 50,
"deliveryInfo": {
"building": "3号楼",
"room": "502"
}
}
- 后端处理逻辑:
java复制@Transactional
public Order createOrder(OrderCreateDTO dto, Long userId) {
// 1. 验证积分是否足够
if (dto.getUsePoints() > 0) {
Integer availablePoints = userService.getAvailablePoints(userId);
if (availablePoints < dto.getUsePoints()) {
throw new BusinessException("积分不足");
}
}
// 2. 检查并扣减库存
for (OrderItemDTO item : dto.getItems()) {
if (!productService.reduceStock(item.getProductId(), item.getQuantity())) {
throw new BusinessException(item.getProductId() + "库存不足");
}
}
// 3. 创建订单主表
Order order = new Order();
order.setUserId(userId);
order.setTotalAmount(calculateTotal(dto.getItems()));
order.setActualAmount(order.getTotalAmount() - pointsToMoney(dto.getUsePoints()));
orderMapper.insert(order);
// 4. 创建订单明细
for (OrderItemDTO item : dto.getItems()) {
OrderDetail detail = new OrderDetail();
detail.setOrderId(order.getId());
detail.setProductId(item.getProductId());
detail.setQuantity(item.getQuantity());
orderDetailMapper.insert(detail);
}
// 5. 扣减积分
if (dto.getUsePoints() > 0) {
pointService.deductPoints(userId, dto.getUsePoints(), "ORDER_PAYMENT");
}
// 6. 发送订单创建事件
eventPublisher.publishEvent(new OrderCreatedEvent(order.getId()));
return order;
}
4.2 定时任务设计
处理超时未支付订单:
java复制@Scheduled(cron = "0 */5 * * * ?") // 每5分钟执行一次
public void cancelUnpaidOrders() {
// 查询30分钟前创建且未支付的订单
List<Order> unpaidOrders = orderMapper.selectUnpaidOrders(
LocalDateTime.now().minusMinutes(30));
for (Order order : unpaidOrders) {
try {
// 1. 恢复库存
List<OrderDetail> details = orderDetailMapper.selectByOrderId(order.getId());
for (OrderDetail detail : details) {
productService.increaseStock(detail.getProductId(), detail.getQuantity());
}
// 2. 恢复积分(如果使用了积分)
if (order.getPointUsed() > 0) {
pointService.refundPoints(order.getUserId(), order.getPointUsed());
}
// 3. 更新订单状态
order.setStatus(OrderStatus.CANCELLED);
order.setCancelReason("超时未支付");
orderMapper.updateById(order);
} catch (Exception e) {
log.error("取消订单{}失败:{}", order.getId(), e.getMessage());
}
}
}
5. 部署与性能优化
5.1 多环境配置
使用SpringBoot的Profile特性:
yaml复制# application-dev.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/snack_dev
username: dev_user
password: dev123
# application-prod.yml
spring:
datasource:
url: jdbc:mysql://prod-db:3306/snack_prod
username: ${DB_USER}
password: ${DB_PASS}
redis:
host: redis-cluster
启动时指定Profile:
bash复制java -jar snack-platform.jar --spring.profiles.active=prod
5.2 缓存策略实现
商品信息缓存示例:
java复制@Service
@CacheConfig(cacheNames = "products")
public class ProductServiceImpl implements ProductService {
@Cacheable(key = "#id")
public Product getById(Long id) {
return productMapper.selectById(id);
}
@CachePut(key = "#product.id")
public Product updateProduct(Product product) {
productMapper.updateById(product);
return product;
}
@CacheEvict(key = "#id")
public void deleteProduct(Long id) {
productMapper.deleteById(id);
}
}
5.3 日志收集方案
使用ELK Stack收集日志:
- 在SpringBoot中配置Logstash日志输出:
xml复制<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>6.6</version>
</dependency>
- logback-spring.xml配置:
xml复制<appender name="logstash" class="net.logstash.logback.appender.LogstashTcpSocketAppender">
<destination>logstash:5044</destination>
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
</appender>
- 在Kibana中创建仪表盘展示:
- 错误日志趋势
- 接口响应时间分布
- 用户活跃度分析
6. 毕业设计答辩要点
6.1 技术亮点展示
- 自动装配机制:
- 自定义Starter实现积分规则加载
java复制@AutoConfiguration
@ConditionalOnClass(PointService.class)
@EnableConfigurationProperties(PointProperties.class)
public class PointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public PointService pointService(PointProperties properties) {
return new PointServiceImpl(properties);
}
}
- 接口幂等性处理:
java复制@Idempotent
@PostMapping("/orders")
public Result createOrder(@RequestBody OrderCreateDTO dto) {
// ...
}
通过自定义注解和拦截器实现:
java复制@Aspect
@Component
public class IdempotentAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Around("@annotation(idempotent)")
public Object around(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("Idempotent-Token");
if (StringUtils.isEmpty(token)) {
throw new BusinessException("幂等令牌不能为空");
}
String key = "idempotent:" + token;
if (redisTemplate.opsForValue().setIfAbsent(key, "1", 24, TimeUnit.HOURS)) {
return joinPoint.proceed();
} else {
throw new BusinessException("请勿重复提交");
}
}
}
6.2 常见问题准备
- 为什么选择Vue+ElementUI而不是React+AntDesign?
- 更符合国内技术生态
- ElementUI文档完善,社区资源丰富
- 组件风格适合管理系统类项目
- 如何处理积分过期问题?
sql复制-- 定期任务清理过期积分
CREATE EVENT expire_points_event
ON SCHEDULE EVERY 1 DAY
DO
DELETE FROM user_points
WHERE expire_time < NOW() AND used = 0;
- 系统如何保证数据一致性?
- 分布式事务采用本地消息表
- 关键操作添加事务注解
- 重要业务数据变更记录操作日志
6.3 项目扩展方向
- 大数据分析模块:
- 使用Flink实时分析销售数据
- 基于用户购买记录实现协同过滤推荐
- 移动端适配:
- 开发微信小程序版本
- 集成扫码支付功能
- 智能货柜对接:
- 通过IoT协议连接实体货柜
- 实现线下扫码取货自动扣款
我在实际开发中发现,积分系统的并发控制是最大的挑战。最初版本没有考虑积分扣减的原子性操作,在压力测试时出现了积分超扣的情况。最终的解决方案是采用Redis的DECR命令配合Lua脚本实现原子操作:
lua复制-- 积分扣减脚本
local current = redis.call('GET', KEYS[1])
if not current or tonumber(current) < tonumber(ARGV[1]) then
return 0
end
return redis.call('DECRBY', KEYS[1], ARGV[1])
这个案例告诉我们,即使是毕业设计项目,也需要提前考虑并发场景下的数据一致性问题。建议在项目初期就进行压力测试,尽早发现并解决这类隐患。
