1. 项目背景与需求分析
作为一名长期从事电商系统开发的工程师,我最近完成了一个基于SpringBoot和Vue的仙剑七主题游戏商城项目。这个毕业设计选题源于当前游戏周边市场的快速增长趋势,以及传统实体店在商品展示和销售渠道上的局限性。
仙剑奇侠传作为国产单机游戏的经典IP,拥有庞大的粉丝群体和周边商品需求。但现有的销售渠道存在几个痛点:
- 商品展示不直观,粉丝难以及时获取最新周边信息
- 购买流程繁琐,特别是对于跨地域的粉丝
- 缺乏统一的会员体系和积分机制
- 管理员对商品和订单的管理效率低下
针对这些问题,我们设计的商城平台需要实现以下核心功能:
- 用户端:商品浏览、分类检索、购物车、订单管理、个人信息维护
- 管理端:用户管理、商品CRUD、分类管理、订单处理、数据统计
- 系统层:权限控制、安全认证、数据持久化、前后端分离架构
2. 技术选型与架构设计
2.1 技术栈决策
经过对多个技术方案的评估,最终确定的技术栈如下:
后端技术栈:
- SpringBoot 2.7.x:提供快速应用开发能力,内置Tomcat简化部署
- Spring Security:处理认证授权,实现RBAC权限模型
- MyBatis-Plus:简化数据库操作,提供强大的CRUD接口
- Redis:缓存热点数据,提升系统响应速度
- MySQL 8.0:关系型数据库,存储核心业务数据
- Swagger:API文档自动生成,便于前后端协作
前端技术栈:
- Vue 3.x:采用Composition API,提升代码组织性
- Element Plus:UI组件库,加速界面开发
- Axios:处理HTTP请求,实现前后端通信
- Vue Router:管理前端路由,实现SPA体验
- Pinia:状态管理,替代Vuex的轻量级方案
2.2 系统架构设计
系统采用经典的三层架构,但针对电商特点做了优化:
code复制┌───────────────────────────────────────┐
│ 客户端层 │
│ ┌───────────┐ ┌─────────────┐ │
│ │ PC Web │ │ Mobile H5 │ │
│ └───────────┘ └─────────────┘ │
└───────────────────┬───────────────────┘
│ HTTP/HTTPS
▼
┌───────────────────────────────────────┐
│ 网关层 │
│ ┌─────────────────────────────────┐ │
│ │ Nginx反向代理 │ │
│ └─────────────────────────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Spring Cloud Gateway │ │
│ └─────────────────────────────────┘ │
└───────────────────┬───────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 应用层 │
│ ┌───────────┐ ┌─────────────┐ │
│ │ 用户服务 │ │ 商品服务 │ │
│ └───────────┘ └─────────────┘ │
│ ┌───────────┐ ┌─────────────┐ │
│ │ 订单服务 │ │ 支付服务 │ │
│ └───────────┘ └─────────────┘ │
└───────────────────┬───────────────────┘
│
▼
┌───────────────────────────────────────┐
│ 数据层 │
│ ┌───────────┐ ┌─────────────┐ │
│ │ MySQL │ │ Redis │ │
│ └───────────┘ └─────────────┘ │
│ ┌─────────────────────────────────┐ │
│ │ Elasticsearch │ │
│ └─────────────────────────────────┘ │
└───────────────────────────────────────┘
提示:虽然毕业设计通常不需要完整的微服务架构,但采用清晰的服务划分有利于后续扩展。实际开发中可根据团队规模适当简化。
3. 核心功能实现细节
3.1 用户权限系统实现
权限控制采用RBAC(基于角色的访问控制)模型,数据库设计如下:
sql复制CREATE TABLE `sys_user` (
`id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL COMMENT '登录名',
`password` varchar(100) NOT NULL COMMENT '密码',
`salt` varchar(20) DEFAULT NULL COMMENT '盐',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`mobile` varchar(20) DEFAULT NULL COMMENT '手机号',
`status` tinyint DEFAULT '1' COMMENT '状态 0:禁用 1:正常',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
CREATE TABLE `sys_role` (
`role_id` bigint NOT NULL AUTO_INCREMENT,
`role_name` varchar(100) DEFAULT NULL COMMENT '角色名称',
`remark` varchar(100) DEFAULT NULL COMMENT '备注',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
CREATE TABLE `sys_user_role` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint DEFAULT NULL COMMENT '用户ID',
`role_id` bigint DEFAULT NULL COMMENT '角色ID',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户与角色对应关系表';
CREATE TABLE `sys_menu` (
`menu_id` bigint NOT NULL AUTO_INCREMENT,
`parent_id` bigint DEFAULT NULL COMMENT '父菜单ID',
`name` varchar(50) DEFAULT NULL COMMENT '菜单名称',
`url` varchar(200) DEFAULT NULL COMMENT '菜单URL',
`perms` varchar(500) DEFAULT NULL COMMENT '授权标识',
`type` int DEFAULT NULL COMMENT '类型 0:目录 1:菜单 2:按钮',
`icon` varchar(50) DEFAULT NULL COMMENT '菜单图标',
`order_num` int DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜单管理表';
权限校验通过Spring Security的过滤器链实现,核心配置类如下:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/", "/home", "/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/auth/login")
.defaultSuccessUrl("/")
.permitAll()
.and()
.logout()
.logoutUrl("/auth/logout")
.logoutSuccessUrl("/")
.permitAll()
.and()
.csrf().disable();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
3.2 商品管理模块
商品模块采用树形分类结构,支持多级分类。核心实体关系如下:
java复制@Data
@TableName("product_category")
public class ProductCategory {
@TableId(type = IdType.AUTO)
private Long id;
private Long parentId; // 父分类ID
private String name; // 分类名称
private Integer level; // 分类层级
private Integer sort; // 排序
private String icon; // 图标
}
@Data
@TableName("product")
public class Product {
@TableId(type = IdType.AUTO)
private Long id;
private Long categoryId; // 分类ID
private String name; // 商品名称
private String subtitle; // 副标题
private String mainImage; // 主图
private String subImages; // 子图,JSON数组
private String detail; // 详情HTML
private BigDecimal price; // 价格
private Integer stock; // 库存
private Integer status; // 状态
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
商品搜索功能结合Elasticsearch实现,索引配置如下:
json复制PUT /product
{
"mappings": {
"properties": {
"id": {"type": "keyword"},
"name": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
},
"subtitle": {"type": "text"},
"categoryId": {"type": "keyword"},
"price": {"type": "double"},
"status": {"type": "integer"}
}
}
}
3.3 购物车与订单系统
购物车设计考虑两种场景:登录用户持久化存储,未登录用户Cookie存储。核心逻辑如下:
java复制@Service
public class CartServiceImpl implements CartService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private String getCartKey(Long userId) {
return "cart:" + userId;
}
@Override
public void addToCart(Long userId, CartItem cartItem) {
String key = getCartKey(userId);
Boolean hasKey = redisTemplate.hasKey(key);
if (hasKey != null && hasKey) {
// 检查是否已存在相同商品
String hashKey = cartItem.getProductId().toString();
CartItem existItem = (CartItem) redisTemplate.opsForHash().get(key, hashKey);
if (existItem != null) {
existItem.setQuantity(existItem.getQuantity() + cartItem.getQuantity());
} else {
existItem = cartItem;
}
redisTemplate.opsForHash().put(key, hashKey, existItem);
} else {
Map<String, CartItem> map = new HashMap<>();
map.put(cartItem.getProductId().toString(), cartItem);
redisTemplate.opsForHash().putAll(key, map);
}
}
@Override
public List<CartItem> getCartList(Long userId) {
String key = getCartKey(userId);
Map<Object, Object> entries = redisTemplate.opsForHash().entries(key);
return entries.values().stream()
.map(obj -> (CartItem) obj)
.collect(Collectors.toList());
}
}
订单生成采用状态机模式,核心状态流转如下:
code复制┌───────────┐ ┌───────────┐ ┌────────────┐ ┌───────────┐
│ 待支付 │───▶│ 已支付 │───▶│ 已发货 │───▶│ 已完成 │
└───────────┘ └───────────┘ └────────────┘ └───────────┘
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌────────────┐
│ 已取消 │ │ 退款中 │ │ 已退款 │
└───────────┘ └───────────┘ └────────────┘
4. 前端实现关键点
4.1 Vue组件化开发
商品列表组件采用组合式API实现:
vue复制<template>
<div class="product-list">
<div v-for="product in products" :key="product.id" class="product-item">
<router-link :to="`/product/${product.id}`">
<el-image :src="product.mainImage" fit="cover"></el-image>
<div class="product-info">
<h3>{{ product.name }}</h3>
<p class="subtitle">{{ product.subtitle }}</p>
<p class="price">¥{{ product.price.toFixed(2) }}</p>
</div>
</router-link>
<el-button type="primary" @click="addToCart(product)">加入购物车</el-button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { useCartStore } from '@/stores/cart'
import { getProductList } from '@/api/product'
const cartStore = useCartStore()
const products = ref([])
const fetchProducts = async () => {
try {
const res = await getProductList()
products.value = res.data
} catch (error) {
console.error('获取商品列表失败:', error)
}
}
const addToCart = (product) => {
cartStore.addItem({
productId: product.id,
name: product.name,
mainImage: product.mainImage,
price: product.price,
quantity: 1
})
}
onMounted(() => {
fetchProducts()
})
</script>
4.2 状态管理优化
使用Pinia管理全局状态,购物车store示例:
javascript复制import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { addCartItem, getCartList } from '@/api/cart'
export const useCartStore = defineStore('cart', () => {
const items = ref([])
const loading = ref(false)
const totalCount = computed(() => {
return items.value.reduce((sum, item) => sum + item.quantity, 0)
})
const totalAmount = computed(() => {
return items.value.reduce((sum, item) => sum + (item.price * item.quantity), 0)
})
const fetchCart = async () => {
loading.value = true
try {
const res = await getCartList()
items.value = res.data
} finally {
loading.value = false
}
}
const addItem = async (item) => {
try {
await addCartItem(item)
await fetchCart()
} catch (error) {
console.error('添加购物车失败:', error)
throw error
}
}
return { items, loading, totalCount, totalAmount, fetchCart, addItem }
})
5. 部署与性能优化
5.1 多环境配置
SpringBoot通过profile实现环境隔离:
yaml复制# application.yml
spring:
profiles:
active: @activatedProperties@
---
# application-dev.yml
server:
port: 8080
spring:
datasource:
url: jdbc:mysql://localhost:3306/mall_dev?useSSL=false
username: devuser
password: devpass
redis:
host: localhost
port: 6379
---
# application-prod.yml
server:
port: 80
servlet:
context-path: /api
spring:
datasource:
url: jdbc:mysql://prod-db:3306/mall_prod?useSSL=true
username: ${DB_USER}
password: ${DB_PASS}
redis:
host: redis-prod
port: 6379
password: ${REDIS_PASS}
5.2 前端部署优化
通过Nginx配置实现静态资源缓存和Gzip压缩:
nginx复制server {
listen 80;
server_name mall.example.com;
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
# 静态资源缓存1年
location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
expires 1y;
add_header Cache-Control "public";
}
}
location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
6. 开发经验与避坑指南
6.1 跨域问题解决方案
开发阶段常见跨域问题,推荐三种解决方案:
- SpringBoot配置CORS(适合后端主导项目)
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
}
- Nginx反向代理(生产环境推荐)
nginx复制location /api {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
- Vue开发代理(前端开发时使用)
javascript复制// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
6.2 数据库设计常见问题
- 字符集问题:务必使用utf8mb4字符集,支持emoji和所有Unicode字符
sql复制CREATE DATABASE mall CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
- 索引缺失:高频查询字段必须建立索引,如:
sql复制ALTER TABLE `product` ADD INDEX `idx_category_status` (`category_id`, `status`);
ALTER TABLE `order` ADD INDEX `idx_user_status` (`user_id`, `status`);
- 事务处理:订单相关操作必须使用事务
java复制@Transactional
public Order createOrder(OrderCreateParam param) {
// 1. 扣减库存
productService.reduceStock(param.getProductId(), param.getQuantity());
// 2. 生成订单
Order order = new Order();
// ...设置订单属性
orderMapper.insert(order);
// 3. 清空购物车
cartService.clearCart(param.getUserId());
return order;
}
6.3 性能优化实践
- 缓存策略:
- 商品详情:Redis缓存 + 本地缓存二级架构
- 分类数据:定时缓存,变更时主动刷新
- 热点数据:使用Redis的zset实现自动热点发现
- SQL优化:
- 避免SELECT *,只查询需要的字段
- 复杂查询使用EXPLAIN分析执行计划
- 批量操作使用MyBatis的foreach标签
- 前端性能:
- 图片懒加载:使用Intersection Observer API
- 组件懒加载:Vue的defineAsyncComponent
- 代码分割:基于路由的组件拆分
7. 项目扩展方向
完成基础功能后,可以考虑以下扩展方向提升项目完整度:
- 支付系统集成:
- 微信支付Native API实现
- 支付宝当面付接入
- 模拟支付功能(适合毕业设计演示)
- 数据分析看板:
- 使用ECharts实现销售数据可视化
- 商品销量排行榜
- 用户购买行为分析
- 推荐系统:
- 基于用户行为的协同过滤推荐
- 基于商品标签的内容推荐
- 混合推荐策略实现
- 消息通知:
- 订单状态变更短信通知
- 站内信系统
- WebSocket实时通知
- 秒杀系统:
- Redis预减库存
- 消息队列削峰填谷
- 接口限流防刷
在实现这些扩展功能时,建议先画出时序图和架构图,明确系统边界和数据流向。对于毕业设计而言,可以选择1-2个方向进行深入,不必追求大而全。