这个基于SpringBoot+Vue的电池销售系统是一个典型的前后端分离电商项目。前端采用Vue.js构建响应式用户界面,后端使用SpringBoot提供RESTful API服务,数据库选用MySQL进行数据持久化存储。
系统主要分为两大模块:
技术选型考量:SpringBoot提供了快速构建微服务的能力,Vue.js的组件化开发模式非常适合电商类项目的前端实现,两者结合既能保证开发效率又能获得良好的用户体验。
用户认证采用JWT(JSON Web Token)方案,后端Spring Security配置如下:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()))
.addFilter(new JwtAuthorizationFilter(authenticationManager()));
}
}
前端Vue中封装了axios拦截器处理token:
javascript复制// 请求拦截器
axios.interceptors.request.use(config => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截器
axios.interceptors.response.use(response => {
return response
}, error => {
if (error.response.status === 401) {
router.push('/login')
}
return Promise.reject(error)
})
商品列表采用分页加载,后端接口设计:
java复制@GetMapping("/api/products")
public Page<Product> getProducts(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword) {
Pageable pageable = PageRequest.of(page, size);
if (keyword != null && !keyword.isEmpty()) {
return productRepository.findByNameContaining(keyword, pageable);
}
return productRepository.findAll(pageable);
}
前端Vue组件实现商品卡片和搜索框:
vue复制<template>
<div class="product-list">
<input v-model="searchKeyword" @input="searchProducts" placeholder="搜索电池型号...">
<div class="product-card" v-for="product in products" :key="product.id">
<img :src="product.image" :alt="product.name">
<h3>{{ product.name }}</h3>
<p>¥{{ product.price }}</p>
<button @click="addToCart(product)">加入购物车</button>
</div>
<button @click="loadMore" v-if="hasMore">加载更多</button>
</div>
</template>
sql复制CREATE TABLE `product` (
`id` bigint NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`description` text,
`price` decimal(10,2) NOT NULL,
`stock` int NOT NULL DEFAULT '0',
`image` varchar(255) DEFAULT NULL,
`category_id` bigint DEFAULT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `order` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`total_amount` decimal(10,2) NOT NULL,
`status` varchar(20) NOT NULL DEFAULT 'pending',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_user` (`user_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
java复制@Cacheable(value = "products", key = "#page + '-' + #size + '-' + #keyword")
public Page<Product> findProducts(int page, int size, String keyword) {
// 查询逻辑
}
后端商品控制器示例:
java复制@RestController
@RequestMapping("/api/admin/products")
public class AdminProductController {
@Autowired
private ProductService productService;
@PostMapping
public Product createProduct(@Valid @RequestBody Product product) {
return productService.saveProduct(product);
}
@PutMapping("/{id}")
public Product updateProduct(@PathVariable Long id, @Valid @RequestBody Product product) {
product.setId(id);
return productService.saveProduct(product);
}
@DeleteMapping("/{id}")
public void deleteProduct(@PathVariable Long id) {
productService.deleteProduct(id);
}
}
使用Spring Data JPA的聚合查询:
java复制public interface OrderRepository extends JpaRepository<Order, Long> {
@Query("SELECT new com.example.dto.SalesDataDTO("
+ "DATE_FORMAT(o.createdAt, '%Y-%m'), "
+ "SUM(o.totalAmount), "
+ "COUNT(o.id)) "
+ "FROM Order o "
+ "WHERE o.status = 'completed' "
+ "GROUP BY DATE_FORMAT(o.createdAt, '%Y-%m')")
List<SalesDataDTO> findMonthlySales();
}
application-prod.properties配置示例:
properties复制spring.datasource.url=jdbc:mysql://prod-db:3306/battery_shop?useSSL=false
spring.datasource.username=prod_user
spring.datasource.password=${DB_PASSWORD}
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
logging.level.root=INFO
javascript复制// vue.config.js
module.exports = {
productionSourceMap: false,
configureWebpack: {
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
}
nginx复制server {
listen 80;
server_name battery-shop.com;
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
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;
}
}
javascript复制// vue.config.js
devServer: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
java复制public class JwtTokenProvider {
public String generateRefreshToken(User user) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + refreshExpirationInMs);
return Jwts.builder()
.setSubject(Long.toString(user.getId()))
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(SignatureAlgorithm.HS512, refreshSecret)
.compact();
}
}
javascript复制function compressImage(file, quality = 0.8) {
return new Promise((resolve) => {
const reader = new FileReader()
reader.onload = (event) => {
const img = new Image()
img.onload = () => {
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
canvas.width = img.width
canvas.height = img.height
ctx.drawImage(img, 0, 0)
canvas.toBlob(resolve, 'image/jpeg', quality)
}
img.src = event.target.result
}
reader.readAsDataURL(file)
})
}
这个项目完整展示了如何从零开始构建一个现代化的电商系统,涵盖了前后端分离架构的核心技术栈。在实际开发中,建议先从基础功能开始,逐步迭代完善,特别注意权限控制和数据安全方面的实现。