作为一名有着十年全栈开发经验的工程师,最近刚完成了一个基于SpringBoot+Vue的智能垃圾分类系统。这个项目不仅具备完整的垃圾分类识别、用户管理、数据统计等功能模块,更采用了当前主流的前后端分离架构。在实际开发过程中,我积累了不少关于SpringBoot整合、Vue组件化开发以及系统性能优化的实战经验,今天就来详细分享一下这个项目的技术实现细节。
智能垃圾分类系统主要解决传统垃圾分类效率低、准确率不高的问题。系统通过图像识别技术自动分类垃圾,结合后台管理系统实现用户行为分析、垃圾分类统计等功能。前端采用Vue+ElementUI实现响应式界面,后端基于SpringBoot+MyBatisPlus构建RESTful API,数据库选用MySQL 8.0,整个项目遵循MVC设计模式,实现了高内聚低耦合的代码结构。
技术选型考量:选择SpringBoot是因为它的自动配置和起步依赖能极大提升开发效率;Vue的响应式特性和组件化开发非常适合构建复杂的单页应用;而MyBatisPlus在MyBatis基础上增强了CRUD操作,减少了大量模板代码。
系统采用前后端分离的B/S架构,分为表现层、业务逻辑层和数据访问层:
code复制表现层:Vue 3 + Element Plus + Axios
业务逻辑层:Spring Boot 2.7 + Shiro(安全控制)
数据访问层:MyBatis-Plus 3.5 + MySQL 8.0
辅助工具:Redis(缓存)、Lombok(简化代码)
这种分层架构的优势在于:
系统主要包含以下功能模块:
每个模块都遵循单一职责原则,通过清晰的接口定义进行交互。例如垃圾分类识别模块提供以下API端点:
java复制@RestController
@RequestMapping("/api/garbage")
public class GarbageClassifyController {
@PostMapping("/classify")
public Result classify(@RequestParam("file") MultipartFile file) {
// 调用AI服务进行图像识别
}
@GetMapping("/history")
public Result getClassifyHistory(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
// 分页查询分类历史记录
}
}
数据库设计遵循第三范式,主要表结构包括:
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) COMMENT '加密盐值',
`email` varchar(100) COMMENT '邮箱',
`mobile` varchar(20) COMMENT '手机号',
`status` tinyint DEFAULT 1 COMMENT '状态 0:禁用 1:正常',
`create_time` datetime COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户';
sql复制CREATE TABLE `garbage_classify` (
`id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL COMMENT '用户ID',
`image_url` varchar(255) NOT NULL COMMENT '图片URL',
`result` varchar(50) NOT NULL COMMENT '分类结果',
`confidence` decimal(5,2) COMMENT '置信度',
`create_time` datetime COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='垃圾分类记录';
数据库设计心得:在用户表中添加salt字段用于密码加密,避免直接存储明文密码;垃圾分类记录表建立用户ID索引,提高查询效率;所有表都添加详细的字段注释,便于后期维护。
用户认证采用Shiro框架实现,核心流程包括:
java复制public class PasswordUtils {
private static final String ALGORITHM_NAME = "SHA-256";
private static final int HASH_ITERATIONS = 1024;
public static String encrypt(String password, String salt) {
return new SimpleHash(ALGORITHM_NAME, password,
ByteSource.Util.bytes(salt),
HASH_ITERATIONS).toHex();
}
public static String generateSalt() {
return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);
}
}
java复制public class JwtUtils {
private static final String SECRET = "your-secret-key";
private static final long EXPIRE = 60 * 60 * 24; // 24小时
public static String generateToken(Long userId) {
Date now = new Date();
Date expireDate = new Date(now.getTime() + EXPIRE * 1000);
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(userId.toString())
.setIssuedAt(now)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
public static Long getUserIdFromToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
return Long.parseLong(claims.getSubject());
}
}
该模块整合了百度AI的图像识别服务,主要实现步骤:
java复制@PostMapping("/upload")
public Result uploadImage(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.error("请选择要上传的图片");
}
try {
// 生成唯一文件名
String originalFilename = file.getOriginalFilename();
String fileExt = originalFilename.substring(originalFilename.lastIndexOf("."));
String fileName = UUID.randomUUID().toString() + fileExt;
// 保存到本地
File dest = new File(uploadPath + fileName);
file.transferTo(dest);
// 返回访问URL
String fileUrl = domain + "/uploads/" + fileName;
return Result.ok().put("url", fileUrl);
} catch (IOException e) {
logger.error("文件上传失败", e);
return Result.error("文件上传失败");
}
}
java复制public String classifyImage(String imageUrl) {
// 初始化百度AI客户端
AipImageClassify client = new AipImageClassify(APP_ID, API_KEY, SECRET_KEY);
// 设置网络图片参数
HashMap<String, String> options = new HashMap<>();
options.put("top_num", "3"); // 返回最可能的3个结果
// 调用垃圾分类接口
JSONObject res = client.advancedGeneral(imageUrl, options);
// 解析返回结果
JSONArray results = res.getJSONArray("result");
if (results != null && results.size() > 0) {
return results.getJSONObject(0).getString("keyword");
}
return "未知类型";
}
使用ECharts实现可视化数据展示,后端提供统计接口:
java复制@GetMapping("/statistics")
public Result getStatistics(@RequestParam(required = false) String dateRange) {
// 获取当前用户ID
Long userId = getCurrentUserId();
// 查询最近7天的分类记录
List<Map<String, Object>> dailyStats = garbageClassifyService.getDailyStats(userId, 7);
// 查询分类结果分布
List<Map<String, Object>> typeDistribution = garbageClassifyService.getTypeDistribution(userId);
return Result.ok()
.put("dailyStats", dailyStats)
.put("typeDistribution", typeDistribution);
}
前端使用Vue+ECharts渲染图表:
vue复制<template>
<div class="chart-container">
<div ref="dailyChart" style="width: 100%; height: 400px;"></div>
<div ref="typeChart" style="width: 100%; height: 400px;"></div>
</div>
</template>
<script>
import * as echarts from 'echarts';
export default {
mounted() {
this.fetchData();
},
methods: {
async fetchData() {
const res = await this.$http.get('/api/garbage/statistics');
this.initDailyChart(res.data.dailyStats);
this.initTypeChart(res.data.typeDistribution);
},
initDailyChart(data) {
const chart = echarts.init(this.$refs.dailyChart);
const option = {
title: { text: '近7天垃圾分类统计' },
tooltip: {},
xAxis: { data: data.map(item => item.date) },
yAxis: {},
series: [{
name: '分类次数',
type: 'bar',
data: data.map(item => item.count)
}]
};
chart.setOption(option);
}
}
}
</script>
前后端分离架构下,跨域是必须解决的问题。我们通过配置全局CORS过滤器实现:
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")
.allowCredentials(true)
.maxAge(3600);
}
}
注意事项:在生产环境中,allowedOrigins应该设置为具体的域名而非"*",以增强安全性。同时对于携带认证信息的请求(如JWT),需要设置allowCredentials为true。
java复制// 使用MyBatis-Plus的分页插件
@Configuration
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
// 在Service中使用LambdaQueryWrapper构建查询
public Page<GarbageClassify> queryPage(Map<String, Object> params, Long userId) {
LambdaQueryWrapper<GarbageClassify> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(GarbageClassify::getUserId, userId)
.orderByDesc(GarbageClassify::getCreateTime);
Page<GarbageClassify> page = new Page<>(
Integer.parseInt(params.get("page").toString()),
Integer.parseInt(params.get("limit").toString())
);
return this.page(page, queryWrapper);
}
java复制@Service
public class GarbageTypeServiceImpl implements GarbageTypeService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String CACHE_KEY = "garbage:types";
@Override
public List<GarbageType> getAllTypes() {
// 先查缓存
List<GarbageType> types = (List<GarbageType>) redisTemplate.opsForValue().get(CACHE_KEY);
if (types != null) {
return types;
}
// 缓存不存在则查数据库
types = baseMapper.selectList(null);
// 写入缓存,设置过期时间
redisTemplate.opsForValue().set(CACHE_KEY, types, 1, TimeUnit.HOURS);
return types;
}
}
通过@ControllerAdvice实现全局异常处理:
java复制@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
logger.error(e.getMessage(), e);
return Result.error("系统繁忙,请稍后再试");
}
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e) {
logger.warn(e.getMessage());
return Result.error(e.getMessage());
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getAllErrors()
.stream().map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining("; "));
return Result.error(message);
}
}
推荐使用Docker容器化部署,Dockerfile配置如下:
dockerfile复制# 基础镜像
FROM openjdk:8-jdk-alpine
# 维护者信息
LABEL maintainer="your-email@example.com"
# 设置时区
RUN apk add --no-cache tzdata && \
cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \
echo "Asia/Shanghai" > /etc/timezone
# 添加应用
ADD target/garbage-classify.jar app.jar
# 暴露端口
EXPOSE 8080
# 启动命令
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
构建并运行容器:
bash复制docker build -t garbage-classify .
docker run -d -p 8080:8080 --name gc-app garbage-classify
使用Nginx作为静态资源服务器,配置示例:
nginx复制server {
listen 80;
server_name your-domain.com;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
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;
}
location /uploads {
alias /path/to/upload/directory;
}
}
yaml复制# application.yml
management:
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
show-details: always
xml复制<!-- logback-spring.xml -->
<configuration>
<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.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="FILE" />
</root>
</configuration>
在实际开发和使用过程中,我发现系统还有以下可以改进的地方:
引入消息队列处理高并发请求
当用户量增大时,垃圾分类识别请求可能出现峰值,可以考虑引入RabbitMQ或Kafka进行请求排队和异步处理。
增加多模态识别能力
目前仅支持图像识别,可以扩展支持文本描述识别(如用户输入"奶茶杯"也能返回正确分类)。
用户积分激励机制
设计积分系统,用户正确分类垃圾可获得积分,积分可兑换奖励,提高用户参与度。
移动端适配优化
虽然前端是响应式设计,但针对移动端可以开发专门的轻量级应用,提升移动用户体验。
引入更先进的AI模型
可以尝试使用YOLO等目标检测模型替代现有分类模型,提高识别准确率。
这个项目从技术选型到最终实现,让我对现代Web开发的全流程有了更深入的理解。特别是在处理图像识别与业务系统集成方面积累了不少实战经验。对于想要学习SpringBoot和Vue全栈开发的同学,这个项目涵盖了从认证授权、前后端交互到系统部署的完整知识链,是一个非常不错的练手项目。