1. 项目概述与核心架构设计
这个宠物商城系统采用前后端分离架构,前端使用Vue 3构建用户界面,后端基于SpringBoot框架提供RESTful API,数据持久层采用MyBatis操作MySQL数据库。整个系统实现了商品展示、购物车管理、订单处理等电商核心功能模块。
技术栈选型考量:
- SpringBoot:简化了传统Spring应用的初始搭建和开发过程,内置Tomcat服务器,通过starter依赖快速集成其他组件。选择2.7.x版本兼顾稳定性和新特性支持
- Vue 3:组合式API更适合复杂前端应用开发,配合Vue Router和Pinia状态管理,构建响应式用户界面
- MyBatis:相比JPA更灵活,适合需要复杂SQL优化的电商系统,通过XML配置实现对象关系映射
- MySQL 8.0:支持JSON数据类型和窗口函数,事务性能满足高并发订单处理需求
提示:实际开发中建议使用SpringBoot 2.7.18和Vue 3.3.x长期支持版本,避免使用最新版本可能存在的兼容性问题
2. 开发环境准备与工程搭建
2.1 后端工程初始化
使用IDEA创建SpringBoot项目时关键配置:
- 选择Maven构建工具和Java 17(LTS版本)
- 添加依赖:
xml复制<dependencies> <!-- Web支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis整合 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.1</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok简化代码 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
2.2 前端工程搭建
通过Vue CLI创建项目时注意:
bash复制npm init vue@latest pet-mall-frontend
选择配置:
- TypeScript支持
- Pinia状态管理
- Router路由
- ESLint代码规范
安装必要依赖:
bash复制cd pet-mall-frontend
npm install axios vue-axios element-plus --save
2.3 数据库设计
主要表结构设计示例:
sql复制CREATE TABLE `pet_product` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL COMMENT '商品名称',
`price` decimal(10,2) NOT NULL COMMENT '售价',
`stock` int NOT NULL DEFAULT '0' COMMENT '库存',
`category_id` int DEFAULT NULL COMMENT '分类ID',
`main_image` varchar(255) DEFAULT NULL COMMENT '主图URL',
`detail` text COMMENT '商品详情',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `order_info` (
`id` bigint NOT NULL AUTO_INCREMENT,
`order_no` varchar(50) NOT NULL COMMENT '订单编号',
`user_id` bigint NOT NULL,
`payment` decimal(20,2) DEFAULT NULL COMMENT '实付金额',
`status` int DEFAULT '10' COMMENT '订单状态',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `order_no_index` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3. 核心功能实现细节
3.1 后端API开发
商品服务接口示例:
java复制@RestController
@RequestMapping("/api/product")
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping("/list")
public Result<List<ProductVO>> listProducts(
@RequestParam(required = false) Integer categoryId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
PageInfo<ProductVO> pageInfo = productService
.getProductByCategoryId(categoryId, pageNum, pageSize);
return Result.success(pageInfo);
}
@GetMapping("/detail/{id}")
public Result<ProductDetailVO> getDetail(@PathVariable Long id) {
ProductDetailVO detail = productService.getProductDetail(id);
return Result.success(detail);
}
}
MyBatis映射文件示例:
xml复制<mapper namespace="com.petmall.mapper.ProductMapper">
<resultMap id="BaseResultMap" type="com.petmall.entity.Product">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="name" property="name" jdbcType="VARCHAR"/>
<result column="price" property="price" jdbcType="DECIMAL"/>
</resultMap>
<select id="selectByCategoryId" resultMap="BaseResultMap">
SELECT id, name, price, main_image
FROM pet_product
WHERE category_id = #{categoryId}
ORDER BY create_time DESC
LIMIT #{pageNum}, #{pageSize}
</select>
</mapper>
3.2 前端页面开发
商品列表页关键代码:
vue复制<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { getProductList } from '@/api/product'
import type { Product } from '@/types'
const products = ref<Product[]>([])
const loading = ref(false)
const fetchProducts = async () => {
loading.value = true
try {
const res = await getProductList({
pageNum: 1,
pageSize: 10
})
products.value = res.data.list
} finally {
loading.value = false
}
}
onMounted(() => {
fetchProducts()
})
</script>
<template>
<div class="product-list">
<el-skeleton :loading="loading" animated>
<template #default>
<el-row :gutter="20">
<el-col
v-for="product in products"
:key="product.id"
:span="6"
>
<product-card :data="product" />
</el-col>
</el-row>
</template>
</el-skeleton>
</div>
</template>
4. 系统部署方案
4.1 后端部署
- 打包SpringBoot应用:
bash复制mvn clean package -DskipTests
- 编写Dockerfile:
dockerfile复制FROM openjdk:17-jdk-slim
VOLUME /tmp
COPY target/pet-mall-backend-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
- 数据库配置:
yaml复制spring:
datasource:
url: jdbc:mysql://mysql-server:3306/petmall?useSSL=false
username: root
password: yourpassword
driver-class-name: com.mysql.cj.jdbc.Driver
redis:
host: redis-server
port: 6379
4.2 前端部署
- 构建生产环境代码:
bash复制npm run build
- Nginx配置示例:
nginx复制server {
listen 80;
server_name yourdomain.com;
location / {
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
location /api/ {
proxy_pass http://backend-server:8080;
proxy_set_header Host $host;
}
}
5. 常见问题与解决方案
5.1 跨域问题处理
SpringBoot后端配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
}
5.2 MyBatis查询结果映射异常
典型错误场景:
java复制// 实体类
@Data
public class Product {
private Long id;
private String name;
}
// Mapper接口
@Select("SELECT id, product_name FROM pet_product")
List<Product> getAll();
解决方案:
- 使用别名匹配:
sql复制SELECT id, product_name AS name FROM pet_product
- 配置结果映射:
xml复制<resultMap id="productMap" type="Product">
<result column="product_name" property="name"/>
</resultMap>
5.3 Vue路由刷新404问题
解决方案:
- 开发环境:在vite.config.ts中配置
typescript复制export default defineConfig({
server: {
historyApiFallback: true
}
})
- 生产环境:Nginx配置
nginx复制location / {
try_files $uri $uri/ /index.html;
}
6. 性能优化实践
6.1 数据库优化
- 索引优化:
sql复制ALTER TABLE order_info ADD INDEX idx_user_status (user_id, status);
- 查询优化:
java复制@Select("SELECT id, name FROM product WHERE status = 1 LIMIT 1000")
List<Product> getActiveProducts();
6.2 缓存策略
Redis缓存示例:
java复制@Cacheable(value = "products", key = "#id")
public Product getById(Long id) {
return productMapper.selectById(id);
}
@CacheEvict(value = "products", key = "#product.id")
public void updateProduct(Product product) {
productMapper.updateById(product);
}
6.3 前端性能优化
- 组件懒加载:
typescript复制const routes = [
{
path: '/detail/:id',
component: () => import('@/views/ProductDetail.vue')
}
]
- API请求节流:
typescript复制import { throttle } from 'lodash-es'
const searchProducts = throttle(async (keyword: string) => {
const res = await searchApi(keyword)
results.value = res.data
}, 500)
7. 安全防护措施
7.1 SQL注入防护
MyBatis推荐写法:
java复制@Select("SELECT * FROM user WHERE username = #{username} AND password = #{password}")
User login(@Param("username") String username,
@Param("password") String password);
7.2 XSS防护
前端处理:
typescript复制import DOMPurify from 'dompurify'
const cleanHtml = DOMPurify.sanitize(userInput)
后端处理:
java复制@PostMapping("/comment")
public Result addComment(@RequestBody @Valid CommentDTO dto) {
String safeContent = HtmlUtils.htmlEscape(dto.getContent());
// 处理安全内容
}
7.3 认证授权
JWT实现示例:
java复制public class JwtTokenUtil {
private static final String SECRET = "pet-mall-secret";
public static String generateToken(UserDetails user) {
return Jwts.builder()
.setSubject(user.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public static Boolean validateToken(String token, UserDetails user) {
final String username = extractUsername(token);
return (username.equals(user.getUsername()) && !isTokenExpired(token));
}
}
8. 监控与日志
8.1 SpringBoot Actuator
配置示例:
yaml复制management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
8.2 日志收集
Logback配置:
xml复制<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
8.3 前端错误监控
Sentry集成:
typescript复制import * as Sentry from '@sentry/vue'
Sentry.init({
app,
dsn: 'your-dsn',
integrations: [
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.vueRouterInstrumentation(router)
})
],
tracesSampleRate: 0.2
})
9. 测试策略
9.1 单元测试
SpringBoot测试示例:
java复制@SpringBootTest
class ProductServiceTest {
@Autowired
private ProductService productService;
@Test
void testGetProductDetail() {
ProductDetailVO detail = productService.getProductDetail(1L);
assertNotNull(detail);
assertEquals("狗粮", detail.getName());
}
}
9.2 接口测试
使用TestContainers进行集成测试:
java复制@Testcontainers
@SpringBootTest
class ProductControllerIT {
@Container
static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", mysql::getJdbcUrl);
registry.add("spring.datasource.username", mysql::getUsername);
registry.add("spring.datasource.password", mysql::getPassword);
}
@Test
void testListProducts() {
// 测试代码
}
}
9.3 前端组件测试
Vitest测试示例:
typescript复制import { mount } from '@vue/test-utils'
import ProductCard from '@/components/ProductCard.vue'
describe('ProductCard', () => {
it('renders product name correctly', () => {
const wrapper = mount(ProductCard, {
props: {
data: {
id: 1,
name: '测试商品',
price: 99.9
}
}
})
expect(wrapper.text()).toContain('测试商品')
})
})
10. 项目扩展方向
10.1 微服务改造
-
服务拆分:
- 用户服务
- 商品服务
- 订单服务
- 支付服务
-
Spring Cloud集成:
xml复制<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
10.2 管理后台开发
基于Vue Element Plus:
typescript复制// 路由配置
const routes = [
{
path: '/',
component: Layout,
children: [
{
path: 'products',
component: () => import('@/views/admin/ProductList.vue')
}
]
}
]
10.3 移动端适配
- 响应式布局:
css复制.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
}
@media (max-width: 768px) {
.product-grid {
grid-template-columns: 1fr 1fr;
}
}
- PWA支持:
javascript复制// vite.config.ts
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
VitePWA({
registerType: 'autoUpdate',
manifest: {
name: '宠物商城',
short_name: 'PetMall'
}
})
]
})
11. 项目源码结构建议
11.1 后端项目结构
code复制pet-mall-backend
├── src/main/java
│ ├── com.petmall
│ │ ├── config # 配置类
│ │ ├── controller # 控制器
│ │ ├── service # 业务服务
│ │ ├── mapper # MyBatis接口
│ │ ├── entity # 实体类
│ │ ├── dto # 数据传输对象
│ │ ├── vo # 视图对象
│ │ └── exception # 异常处理
├── src/main/resources
│ ├── mapper # MyBatis XML
│ ├── application.yml # 主配置文件
│ └── static # 静态资源
11.2 前端项目结构
code复制pet-mall-frontend
├── src
│ ├── api # API请求封装
│ ├── assets # 静态资源
│ ├── components # 公共组件
│ ├── composables # 组合式函数
│ ├── router # 路由配置
│ ├── stores # Pinia状态管理
│ ├── types # TypeScript类型
│ ├── utils # 工具函数
│ └── views # 页面组件
12. 开发协作规范
12.1 Git分支策略
-
主分支:
- main:生产环境代码
- develop:集成开发分支
-
功能开发:
- feature/xxx:新功能开发
- bugfix/xxx:问题修复
-
提交规范:
- feat: 新功能
- fix: bug修复
- docs: 文档变更
- style: 代码格式
- refactor: 代码重构
12.2 代码审查要点
-
后端审查:
- SQL注入风险
- N+1查询问题
- 事务边界控制
-
前端审查:
- 组件复用程度
- 状态管理合理性
- 性能敏感操作
12.3 文档要求
-
API文档:
- 使用Swagger或Knife4j
java复制@Operation(summary = "获取商品列表") @GetMapping("/list") public Result<List<ProductVO>> listProducts() { // ... } -
组件文档:
vue复制<script setup lang="ts"> interface Props { /** * 商品数据对象 */ data: Product /** * 是否显示价格 * @default true */ showPrice?: boolean }</script>
13. 持续集成部署
13.1 GitHub Actions配置
后端CI示例:
yaml复制name: Java CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
- name: Build with Maven
run: mvn -B package --file pom.xml
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: pet-mall-backend
path: target/*.jar
13.2 前端自动化部署
Vercel配置示例:
- 连接GitHub仓库
- 设置构建命令:
npm run build - 设置输出目录:
dist - 配置环境变量
13.3 数据库迁移
Flyway配置:
yaml复制spring:
flyway:
enabled: true
locations: classpath:db/migration
baseline-on-migrate: true
迁移脚本命名:
code复制V1__Initial_schema.sql
V2__Add_product_table.sql
14. 性能测试方案
14.1 JMeter测试
测试计划要素:
- 线程组设置:并发用户数、ramp-up时间
- HTTP请求采样器:API端点、参数
- 监听器:查看结果树、聚合报告
14.2 前端性能指标
Lighthouse测试关注:
- First Contentful Paint
- Time to Interactive
- Total Blocking Time
优化措施:
- 图片懒加载:
vue复制<img v-lazy="product.image" alt="product">
- 代码分割:
typescript复制const ProductDetail = defineAsyncComponent(() =>
import('@/views/ProductDetail.vue')
)
14.3 压力测试场景
典型测试用例:
- 商品搜索:模拟用户输入不同关键词
- 下单流程:从加入购物车到支付完成
- 秒杀活动:高并发抢购场景
15. 项目总结与反思
技术选型验证:
- Vue 3组合式API确实提高了代码组织性
- MyBatis在复杂查询场景下优势明显
- SpringBoot快速开发特性节省了大量时间
遇到的典型问题:
- 跨域处理:开发环境与生产环境配置差异
- 分页查询:MyBatis PageHelper与Vue前端分页组件对接
- 状态管理:Pinia模块划分的合理性
性能瓶颈点:
- 商品列表页图片加载
- 订单统计查询效率
- 购物车并发操作
后续优化方向:
- 引入Elasticsearch实现商品搜索
- 使用Redis缓存热点数据
- 实现分布式事务处理
