1. 项目背景与需求分析
太原学院作为一所拥有近万名师生的大型高校,校内商业生态丰富多样。从学生食堂周边的奶茶店、快餐店,到教学楼附近的文具店、打印店,再到宿舍区的生活超市,各类商铺总数超过50家。传统的纸质登记管理方式已经无法满足以下需求:
- 租赁管理混乱:商铺合同到期时间不统一,续约流程繁琐,经常出现漏签、错签情况
- 商品监管困难:无法实时掌握各商铺商品库存情况,特别是食品类商品的保质期管理
- 订单处理低效:学生订餐高峰期经常出现错单、漏单,人工对账耗时耗力
- 数据统计滞后:校方无法及时获取商铺经营数据,影响商业规划决策
针对这些痛点,我们设计开发了这套商铺管理系统,主要解决以下核心问题:
- 实现商铺信息的数字化统一管理
- 建立标准化的租赁合同电子流程
- 提供实时商品库存监控功能
- 构建高效的订单处理系统
- 实现多维度经营数据分析
2. 技术选型与架构设计
2.1 技术栈选择考量
后端选择SpringBoot的三大理由:
- 快速开发:自动配置特性大幅减少XML配置,特别适合高校场景下有限的技术支持资源
- 生态丰富:Spring Security提供完善的权限控制,Spring Data JPA简化数据库操作
- 易于部署:内嵌Tomcat服务器,打包成jar即可运行,降低运维难度
前端选择Vue.js的关键优势:
- 响应式设计:数据驱动视图的特性特别适合频繁更新的订单管理界面
- 组件化开发:可复用组件加速开发,如商品卡片、订单表格等
- 学习曲线平缓:相比React更易上手,适合学生团队维护
数据库选择MySQL的决策因素:
- 事务支持:ACID特性确保订单、库存等关键操作的原子性
- 成本效益:开源免费,符合高校项目预算限制
- 社区支持:丰富的文档和解决方案,便于问题排查
2.2 系统架构详解
采用经典的三层架构设计:
code复制表示层(Vue.js) ←HTTP→ 业务逻辑层(SpringBoot) ←JDBC→ 数据访问层(MySQL)
关键架构决策:
- 前后端完全分离:通过RESTful API交互,便于后期App扩展
- 按业务模块分包:controller/service/dao层次清晰,例如:
- com.tyu.store.controller
- com.tyu.store.service
- com.tyu.store.dao
- 异常统一处理:@ControllerAdvice捕获所有异常,返回标准JSON格式
实际开发中发现,校园网环境存在跨域问题,解决方案是在SpringBoot配置中添加:
java复制@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("*") .allowedMethods("GET", "POST", "PUT", "DELETE"); } }
3. 核心功能实现
3.1 商铺管理模块
数据库设计优化点:
- 使用TINYINT(1)替代布尔类型,兼容更多数据库版本
- 添加spatial index支持地理位置查询(用于后期扩展校内导航功能)
- 设置create_time默认值为CURRENT_TIMESTAMP
关键接口实现:
java复制@RestController
@RequestMapping("/api/store")
public class StoreController {
@Autowired
private StoreService storeService;
// 分页查询+多条件筛选
@GetMapping
public PageResult<Store> searchStores(
@RequestParam(required = false) String name,
@RequestParam(required = false) String type,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
return storeService.search(name, type, page, size);
}
// 商铺状态批量更新
@PutMapping("/status")
public Result batchUpdateStatus(@RequestBody Map<String, Object> params) {
List<Integer> ids = (List<Integer>) params.get("ids");
int status = (int) params.get("status");
return storeService.updateStatusBatch(ids, status);
}
}
前端实现技巧:
- 使用ElementUI的Table组件实现可分页、可排序的商铺列表
- 结合vue-router实现商铺详情页的动态路由
- 通过watch监听筛选条件变化,自动触发查询
3.2 商品库存管理
库存预警算法:
sql复制-- 每日凌晨执行的预警检查
CREATE EVENT check_inventory_warning
ON SCHEDULE EVERY 1 DAY STARTS '00:00:00'
DO
BEGIN
INSERT INTO warning_notices(product_id, store_id, notice_type)
SELECT product_id, store_id, 'stock'
FROM product_inventory
WHERE stock_quantity <= warning_level;
END;
并发控制方案:
- 乐观锁实现:
java复制@Transactional
public Result updateInventory(int productId, int quantity) {
Product product = productDao.selectById(productId);
if(product.getStock() < quantity) {
return Result.error("库存不足");
}
int rows = productDao.updateStock(productId, quantity, product.getVersion());
if(rows == 0) {
throw new OptimisticLockingFailureException("并发修改冲突");
}
return Result.success();
}
- Redis缓存热点商品:
java复制// 商品详情加入缓存
public Product getProductWithCache(int id) {
String key = "product:" + id;
String json = redisTemplate.opsForValue().get(key);
if(json != null) {
return JSON.parseObject(json, Product.class);
}
Product product = productDao.selectById(id);
redisTemplate.opsForValue().set(key, JSON.toJSONString(product), 30, TimeUnit.MINUTES);
return product;
}
3.3 订单系统设计
状态机设计:
java复制public enum OrderStatus {
UNPAID(0) {
@Override
public boolean canChangeTo(OrderStatus newStatus) {
return newStatus == PAID || newStatus == CANCELLED;
}
},
PAID(1) {
@Override
public boolean canChangeTo(OrderStatus newStatus) {
return newStatus == SHIPPED || newStatus == REFUNDING;
}
},
// 其他状态...
public abstract boolean canChangeTo(OrderStatus newStatus);
}
订单编号生成规则:
java复制public class OrderNoGenerator {
private static final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final AtomicInteger counter = new AtomicInteger(0);
public static String generate() {
String timePart = LocalDateTime.now().format(formatter);
String seqPart = String.format("%04d", counter.getAndIncrement() % 10000);
return "TY" + timePart + seqPart;
}
}
4. 权限系统实现
4.1 基于RBAC的权限模型
数据库关系设计:
code复制user - user_role - role - role_permission - permission
Spring Security配置要点:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/admin/**").hasRole("ADMIN")
.antMatchers("/api/store/**").hasAnyRole("ADMIN", "STORE_OWNER")
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()));
}
}
4.2 JWT令牌实现
令牌生成逻辑:
java复制public 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_TIME))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
前端存储方案:
javascript复制// 登录成功后
localStorage.setItem('token', response.data.token);
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
// 请求拦截器
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
5. 部署与运维实践
5.1 服务器环境配置
推荐配置:
- 阿里云ECS共享型n4
- 2核4G内存
- CentOS 7.6
- MySQL 5.7
关键优化参数:
ini复制# my.cnf配置
[mysqld]
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
max_connections = 200
query_cache_size = 64M
5.2 应用部署脚本
SpringBoot启动脚本:
bash复制#!/bin/bash
APP_NAME=store-system.jar
JVM_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC"
nohup java -jar $JVM_OPTS $APP_NAME --spring.profiles.active=prod > app.log 2>&1 &
echo $! > pid.file
Nginx前端配置:
nginx复制server {
listen 80;
server_name store.tyu.edu.cn;
location / {
root /var/www/store-front;
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
}
}
5.3 监控与日志
ELK日志收集方案:
- Filebeat收集SpringBoot日志
- Logstash解析日志格式
- Elasticsearch建立索引
- Kibana展示仪表盘
关键指标监控:
- 接口响应时间(P99 < 500ms)
- 数据库连接池使用率(<80%)
- JVM内存使用率(<70%)
- 订单创建成功率(>99.9%)
6. 典型问题解决方案
6.1 高并发下单问题
现象:
双十一活动期间,奶茶店商品出现超卖
解决方案:
- Redis分布式锁:
java复制public boolean tryLock(String key, long expire) {
return redisTemplate.opsForValue()
.setIfAbsent(key, "1", expire, TimeUnit.SECONDS);
}
- 数据库库存校验:
sql复制UPDATE product_inventory
SET stock_quantity = stock_quantity - 1
WHERE product_id = ? AND stock_quantity >= 1
6.2 文件上传性能优化
痛点:
商铺上传营业执照等大文件时超时
优化方案:
- 前端分片上传
- 后端使用阿里云OSS直传
- 配置Nginx上传大小限制:
nginx复制client_max_body_size 20M;
6.3 数据统计不准问题
场景:
商铺营业额报表与财务系统对不上
排查过程:
- 发现订单状态变更未记录操作日志
- 缺少版本控制机制
最终方案:
- 引入审计日志表
- 实现数据变更事件溯源
java复制@EntityListeners(AuditingEntityListener.class)
public class Order {
@CreatedBy
private String createdBy;
@LastModifiedBy
private String modifiedBy;
}
7. 项目演进方向
- 移动端扩展:开发微信小程序版本,支持扫码点餐
- 智能分析:基于历史数据预测商品销量,辅助进货决策
- 物联网集成:对接智能货架,实时监控库存
- 支付对接:接入校园一卡通支付系统
实际开发中我们发现,校园系统的特殊性在于:
- 需要兼顾行政管理需求和商业运营需求
- 用户群体固定但使用习惯差异大
- 寒暑假等特殊时段业务波动明显
这些特性需要在架构设计阶段就充分考虑扩展性和灵活性。比如我们预留的"学期配置"功能,可以快速调整系统参数适应不同学期的运营需求。