宠物用品电商平台是近年来快速崛起的垂直领域,根据行业数据显示,2023年全球宠物用品市场规模已突破2000亿美元。这个基于SpringBoot+Vue的全栈项目,完美契合了当前市场需求和技术趋势。作为一名参与过多个电商系统开发的工程师,我认为这个项目特别适合作为Java全栈学习的实战案例。
系统采用经典的前后端分离架构,后端使用SpringBoot 2.7 + MyBatis Plus构建RESTful API,前端采用Vue 3 + Element Plus实现响应式界面。数据库选用MySQL 8.0,整体技术栈都是当前企业级开发的主流选择。我在实际开发中发现,这种架构组合既能保证开发效率,又具备良好的性能表现。
SpringBoot作为后端框架的选择主要基于以下考虑:
数据库访问层采用MyBatis Plus而非JPA,主要因为:
安全认证采用JWT而非Session,优势在于:
Vue 3的组合式API相比选项式API更适合大型项目开发:
Element Plus作为UI组件库的优势:
JWT实现的关键代码示例:
java复制// JWT工具类
public class JwtUtil {
private static final String SECRET_KEY = "your-256-bit-secret";
private static final long EXPIRATION = 86400000; // 24小时
public static String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public static Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
安全配置要点:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/products/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()))
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
商品服务的核心实现:
java复制@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
@Transactional
public ProductDTO createProduct(ProductDTO productDTO) {
Product product = new Product();
BeanUtils.copyProperties(productDTO, product);
product.setCreateTime(LocalDateTime.now());
product.setUpdateTime(LocalDateTime.now());
productMapper.insert(product);
return convertToDTO(product);
}
@Override
public Page<ProductDTO> getProductsByPage(int page, int size, String keyword) {
Page<Product> productPage = new Page<>(page, size);
LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>();
if(StringUtils.isNotBlank(keyword)){
queryWrapper.like(Product::getProductName, keyword);
}
productMapper.selectPage(productPage, queryWrapper);
return productPage.convert(this::convertToDTO);
}
private ProductDTO convertToDTO(Product product) {
ProductDTO dto = new ProductDTO();
BeanUtils.copyProperties(product, dto);
return dto;
}
}
订单状态机设计:
java复制public enum OrderStatus {
PENDING_PAYMENT(0, "待支付"),
PAID(1, "已支付"),
CANCELLED(2, "已取消"),
SHIPPED(3, "已发货"),
COMPLETED(4, "已完成");
private final int code;
private final String desc;
OrderStatus(int code, String desc) {
this.code = code;
this.desc = desc;
}
public static OrderStatus of(int code) {
return Arrays.stream(values())
.filter(status -> status.code == code)
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
订单服务关键逻辑:
java复制@Service
@Transactional
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
@Override
public OrderDTO createOrder(OrderCreateDTO createDTO) {
// 1. 验证商品库存
List<OrderItem> items = createDTO.getItems();
for (OrderItem item : items) {
Product product = productMapper.selectById(item.getProductId());
if (product.getStock() < item.getQuantity()) {
throw new BusinessException("商品库存不足: " + product.getProductName());
}
}
// 2. 扣减库存
for (OrderItem item : items) {
productMapper.deductStock(item.getProductId(), item.getQuantity());
}
// 3. 创建订单
Order order = new Order();
BeanUtils.copyProperties(createDTO, order);
order.setOrderStatus(OrderStatus.PENDING_PAYMENT.getCode());
order.setCreateTime(LocalDateTime.now());
order.setUpdateTime(LocalDateTime.now());
orderMapper.insert(order);
// 4. 返回订单详情
return getOrderById(order.getOrderId());
}
}
用户表索引设计:
sql复制CREATE TABLE `user` (
`user_id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password_hash` varchar(255) NOT NULL,
`email` varchar(100) NOT NULL,
`phone` varchar(20) DEFAULT NULL,
`register_time` datetime NOT NULL,
`last_login` datetime DEFAULT NULL,
`user_status` tinyint NOT NULL DEFAULT '1',
PRIMARY KEY (`user_id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_email` (`email`),
KEY `idx_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
商品表查询优化:
sql复制-- 添加全文索引提高搜索性能
ALTER TABLE product ADD FULLTEXT INDEX ft_product_name (product_name);
-- 分类查询优化
ALTER TABLE product ADD INDEX idx_category_status (category_id, is_deleted);
库存扣减的悲观锁实现:
java复制@Transactional
public boolean deductStockWithPessimisticLock(Long productId, int quantity) {
Product product = productMapper.selectByIdForUpdate(productId);
if (product.getStock() >= quantity) {
product.setStock(product.getStock() - quantity);
productMapper.updateById(product);
return true;
}
return false;
}
使用乐观锁防止超卖:
java复制@Transactional
public boolean deductStockWithOptimisticLock(Long productId, int quantity) {
int affectedRows = productMapper.deductStockWithVersion(
productId,
quantity,
LocalDateTime.now(),
version
);
return affectedRows > 0;
}
购物车状态管理示例:
javascript复制// store/modules/cart.js
const state = {
items: JSON.parse(localStorage.getItem('cartItems')) || []
}
const mutations = {
ADD_TO_CART(state, product) {
const existingItem = state.items.find(item => item.id === product.id)
if (existingItem) {
existingItem.quantity += product.quantity
} else {
state.items.push(product)
}
localStorage.setItem('cartItems', JSON.stringify(state.items))
},
REMOVE_FROM_CART(state, productId) {
state.items = state.items.filter(item => item.id !== productId)
localStorage.setItem('cartItems', JSON.stringify(state.items))
}
}
const actions = {
addToCart({ commit }, product) {
commit('ADD_TO_CART', {
id: product.productId,
name: product.productName,
price: product.price,
image: product.imageUrl,
quantity: 1
})
}
}
export default {
namespaced: true,
state,
mutations,
actions
}
动态路由实现:
javascript复制// router/index.js
const routes = [
{
path: '/',
component: Layout,
children: [
{
path: '',
name: 'Home',
component: Home
},
{
path: 'products',
name: 'ProductList',
component: ProductList,
meta: { requiresAuth: false }
},
{
path: 'dashboard',
name: 'Dashboard',
component: Dashboard,
meta: { requiresAuth: true, roles: ['ADMIN'] }
}
]
}
]
router.beforeEach((to, from, next) => {
const isAuthenticated = store.getters['auth/isAuthenticated']
const userRole = store.getters['auth/userRole']
if (to.meta.requiresAuth && !isAuthenticated) {
next('/login')
} else if (to.meta.roles && !to.meta.roles.includes(userRole)) {
next('/forbidden')
} else {
next()
}
})
Docker部署配置:
dockerfile复制# Dockerfile
FROM openjdk:11-jdk
VOLUME /tmp
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Nginx反向代理配置:
nginx复制server {
listen 80;
server_name api.yourdomain.com;
location / {
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;
# 超时设置
proxy_connect_timeout 60s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
Vue生产环境优化:
javascript复制// vue.config.js
module.exports = {
productionSourceMap: false,
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
},
chainWebpack: config => {
config.plugin('preload').tap(options => {
options[0].include = 'all'
return options
})
}
}
图片懒加载实现:
vue复制<template>
<img v-lazy="imageUrl" alt="product image">
</template>
<script>
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload, {
preLoad: 1.3,
error: require('@/assets/error.png'),
loading: require('@/assets/loading.gif'),
attempt: 3
})
</script>
SpringBoot跨域配置:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.exposedHeaders("Authorization")
.maxAge(3600);
}
}
前端代理配置(开发环境):
javascript复制// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
慢查询日志分析:
properties复制# application.properties
spring.datasource.hikari.connection-test-query=SELECT 1
spring.datasource.hikari.maximum-pool-size=20
spring.jpa.properties.hibernate.show_sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
JVM调优参数:
bash复制java -jar -Xms512m -Xmx1024m -XX:MetaspaceSize=128m \
-XX:MaxMetaspaceSize=256m -XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=4 \
-XX:ConcGCThreads=2 app.jar
SpringCloud Alibaba整合:
yaml复制# application.yml
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
sentinel:
transport:
dashboard: 127.0.0.1:8080
datasource:
ds1:
nacos:
server-addr: 127.0.0.1:8848
dataId: sentinel-rule
rule-type: flow
用户行为分析实现:
java复制@Service
public class UserBehaviorService {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
public void trackUserAction(Long userId, String action, String page) {
UserBehaviorEvent event = new UserBehaviorEvent();
event.setUserId(userId);
event.setAction(action);
event.setPage(page);
event.setTimestamp(System.currentTimeMillis());
kafkaTemplate.send("user_behavior", JSON.toJSONString(event));
}
}
Uniapp跨端开发配置:
javascript复制// pages.json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "宠物商城"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}
在实际开发中,我发现Element UI的表格组件在处理大数据量时性能较差,后来改用vxe-table后性能提升了3倍左右。另外,在JWT实现时,最初没有考虑token刷新机制,导致用户体验不佳,后来增加了refresh token方案才解决这个问题。