Java全栈项目-校园智能垃圾分类系统

项目介绍

随着环保意识的提升,垃圾分类已成为现代校园管理的重要议题。本项目旨在通过Java全栈技术,构建一个智能化的校园垃圾分类管理系统,助力校园环保事业发展。

技术栈

后端技术

  • Spring Boot 2.7.0:快速构建基于Spring的应用程序
  • Spring Security:实现身份认证和权限控制
  • MyBatis Plus:简化数据库操作
  • MySQL 8.0:数据持久化存储
  • Redis:缓存处理和会话管理
  • JWT:实现无状态的用户认证

前端技术

  • Vue 3:构建用户界面
  • Element Plus:UI组件库
  • Axios:处理HTTP请求
  • Echarts:数据可视化展示
  • Vite:前端构建工具

核心功能模块

1. 用户管理模块

  • 学生、教师、管理员多角色管理
  • 基于JWT的登录认证
  • 细粒度的权限控制
  • 个人信息管理

2. 垃圾分类指导模块

  • 垃圾分类知识库
  • 智能识别算法支持
  • 分类指导交互
  • 常见问题解答

3. 垃圾投放管理模块

  • 智能垃圾箱实时监控
  • 垃圾箱容量预警
  • 投放记录追踪
  • 分类准确度统计

4. 积分奖励模块

  • 正确投放积分奖励
  • 积分兑换商城
  • 排行榜系统
  • 成就体系

5. 数据分析模块

  • 垃圾分类数据统计
  • 投放行为分析
  • 效果评估报告
  • 趋势预测

系统架构

整体架构

采用前后端分离的微服务架构,主要分为以下几层:

  1. 表现层:处理用户交互和数据展示
  2. 业务层:实现核心业务逻辑
  3. 持久层:负责数据存储和访问
  4. 基础设施层:提供技术支持和服务

数据库设计

基于业务需求,设计了如下核心表结构:

  • 用户表(user)
  • 垃圾分类表(waste_category)
  • 投放记录表(disposal_record)
  • 积分记录表(point_record)
  • 知识库表(knowledge_base)

项目亮点

  1. 智能识别:集成深度学习算法,实现垃圾图像的自动分类识别

  2. 实时监控:通过IoT技术实现垃圾箱状态的实时监控和预警

  3. 数据可视化:运用Echarts实现直观的数据展示和分析

  4. 游戏化设计:引入积分、排行榜等游戏化元素,提高用户参与度

  5. 微服务架构:采用微服务设计,提高系统可扩展性和维护性

项目收获

通过本项目的开发,不仅提升了全栈开发能力,也深入理解了以下几点:

  1. 前后端分离架构的设计与实现
  2. 微服务的划分与治理
  3. 分布式系统的技术难点
  4. 大数据分析与可视化
  5. 项目开发完整流程

未来展望

  1. 引入更多AI技术,提升识别准确率
  2. 扩展到更多场景,如社区、企业等
  3. 优化系统性能,提升用户体验
  4. 增加更多互动功能,提高参与度
  5. 完善数据分析,为决策提供支持

本项目不仅是一个技术实践,更是对环保理念的实际应用。通过技术手段促进垃圾分类习惯的养成,为建设绿色校园贡献力量。

源代码

Directory Content Summary

Directory Structure

src/
  backend/
    pom.xml
    src/
      main/
        java/
          com/
            campus/
              waste/
                WasteApplication.java
                common/
                  R.java
                config/
                  SecurityConfig.java
                controller/
                  AchievementController.java
                  AuthController.java
                  DataAnalysisController.java
                  DisposalManagementController.java
                  KnowledgeController.java
                  PointsController.java
                  UserController.java
                  WasteClassificationController.java
                entity/
                  Achievement.java
                  BinAlert.java
                  ClassificationStats.java
                  DisposalBehavior.java
                  DisposalRecord.java
                  EffectivenessReport.java
                  FAQ.java
                  KnowledgeArticle.java
                  PointsExchange.java
                  PointsProduct.java
                  PointsRecord.java
                  SmartBin.java
                  TrendPrediction.java
                  User.java
                  UserAchievement.java
                  UserPoints.java
                  WasteCategory.java
                  WasteItem.java
                  WasteStatistics.java
                model/
                  dto/
                    ImageRecognitionDTO.java
                    LoginDTO.java
                    UserDTO.java
                  vo/
                    BinAlertVO.java
                    BinMonitoringVO.java
                    BinVolumeStatVO.java
                    ClassificationStatsVO.java
                    DisposalRecordVO.java
                    FAQVO.java
                    KnowledgeArticleVO.java
                    LoginVO.java
                    SmartBinDetailVO.java
                    SmartBinVO.java
                    UserVO.java
                    WasteCategoryVO.java
                    WasteItemVO.java
                security/
                  JwtAuthenticationFilter.java
                  JwtTokenProvider.java
                service/
                  AchievementService.java
                  DataAnalysisService.java
                  DisposalManagementService.java
                  KnowledgeService.java
                  PointsService.java
                  UserService.java
                  WasteClassificationService.java
                  impl/
                    AchievementServiceImpl.java
                    DataAnalysisServiceImpl.java
                    DisposalManagementServiceImpl.java
                    KnowledgeServiceImpl.java
                    PointsServiceImpl.java
                    UserServiceImpl.java
                    WasteClassificationServiceImpl.java
        resources/
          application.yml
          db/
            data_analysis.sql
            points_reward.sql
            schema.sql
            waste_classification.sql
            waste_disposal.sql
    target/
      classes/
        application.yml
        com/
          campus/
            waste/
              common/
              config/
              controller/
              entity/
              model/
                dto/
                vo/
              security/
              service/
                impl/
        db/
          data_analysis.sql
          points_reward.sql
          schema.sql
          waste_classification.sql
          waste_disposal.sql
      generated-sources/
        annotations/
      generated-test-sources/
        test-annotations/
      test-classes/
  frontend/
    src/
      api/
        achievement.js
        analysis.js
        disposal.js
        points.js
      views/
        analysis/
          Dashboard.vue
        disposal/
          BinMonitoring.vue
          ClassificationStats.vue
          DisposalRecords.vue
        points/
          Achievement.vue
          PointsMall.vue
        waste/
          WasteClassification.vue

File Contents

backend\pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.campus</groupId>
    <artifactId>waste-classification</artifactId>
    <version>1.0.0</version>
    <name>campus-waste-classification</name>
    <description>Campus Waste Classification System</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <mybatis-plus.version>3.5.2</mybatis-plus.version>
        <jjwt.version>0.9.1</jjwt.version>
        <druid.version>1.2.8</druid.version>
        <hutool.version>5.8.0</hutool.version>
    </properties>

    <dependencies>
        <!-- Spring Boot -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- MyBatis Plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

        <!-- MySQL -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- Druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>

        <!-- JWT -->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jjwt.version}</version>
        </dependency>

        <!-- Hutool工具包 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- Test -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

backend\src\main\java\com\campus\waste\WasteApplication.java

package com.campus.waste;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication
@EnableTransactionManagement
public class WasteApplication {
    public static void main(String[] args) {
        SpringApplication.run(WasteApplication.class, args);
    }
}

backend\src\main\java\com\campus\waste\common\R.java

package com.campus.waste.common;

import lombok.Data;

@Data
public class R<T> {
    private Integer code;
    private String message;
    private T data;

    private R(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public static <T> R<T> ok() {
        return new R<>(200, "success", null);
    }

    public static <T> R<T> ok(T data) {
        return new R<>(200, "success", data);
    }

    public static <T> R<T> error(String message) {
        return new R<>(500, message, null);
    }

    public static <T> R<T> error(Integer code, String message) {
        return new R<>(code, message, null);
    }
}

backend\src\main\java\com\campus\waste\config\SecurityConfig.java

package com.campus.waste.config;

import com.campus.waste.security.JwtAuthenticationFilter;
import com.campus.waste.security.JwtAuthenticationEntryPoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
    private final JwtAuthenticationFilter jwtAuthenticationFilter;

    public SecurityConfig(JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
                        JwtAuthenticationFilter jwtAuthenticationFilter) {
        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            .antMatchers("/auth/**").permitAll()
            .antMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
            .anyRequest().authenticated();

        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
        return authConfig.getAuthenticationManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

backend\src\main\java\com\campus\waste\controller\AchievementController.java

package com.campus.waste.controller;

import com.campus.waste.common.R;
import com.campus.waste.model.vo.*;
import com.campus.waste.service.AchievementService;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/achievement")
public class AchievementController {

    private final AchievementService achievementService;

    public AchievementController(AchievementService achievementService) {
        this.achievementService = achievementService;
    }

    @GetMapping("/list")
    public R<List<AchievementVO>> getAllAchievements() {
        return R.ok(achievementService.getAllAchievements());
    }

    @GetMapping("/user")
    public R<List<UserAchievementVO>> getUserAchievements() {
        Long userId = getCurrentUserId();
        return R.ok(achievementService.getUserAchievements(userId));
    }

    @GetMapping("/recent")
    public R<List<UserAchievementVO>> getRecentCompletedAchievements(
            @RequestParam(defaultValue = "5") Integer limit) {
        Long userId = getCurrentUserId();
        return R.ok(achievementService.getRecentCompletedAchievements(userId, limit));
    }

    @GetMapping("/stats")
    public R<AchievementStatsVO> getUserAchievementStats() {
        Long userId = getCurrentUserId();
        return R.ok(achievementService.getUserAchievementStats(userId));
    }

    // TODO: 从SecurityContext中获取当前用户ID
    private Long getCurrentUserId() {
        return 1L; // 临时返回测试用户ID
    }
}

backend\src\main\java\com\campus\waste\controller\AuthController.java

package com.campus.waste.controller;

import com.campus.waste.common.R;
import com.campus.waste.model.dto.LoginDTO;
import com.campus.waste.model.vo.LoginVO;
import com.campus.waste.security.JwtTokenProvider;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
@RequestMapping("/auth")
public class AuthController {

    private final AuthenticationManager authenticationManager;
    private final JwtTokenProvider tokenProvider;

    public AuthController(AuthenticationManager authenticationManager, JwtTokenProvider tokenProvider) {
        this.authenticationManager = authenticationManager;
        this.tokenProvider = tokenProvider;
    }

    @PostMapping("/login")
    public R<LoginVO> login(@Valid @RequestBody LoginDTO loginDTO) {
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                loginDTO.getUsername(),
                loginDTO.getPassword()
            )
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);

        String jwt = tokenProvider.generateToken(authentication);
        
        LoginVO loginVO = new LoginVO();
        loginVO.setToken(jwt);
        loginVO.setTokenType("Bearer");
        
        return R.ok(loginVO);
    }
}

backend\src\main\java\com\campus\waste\controller\DataAnalysisController.java

package com.campus.waste.controller;

import com.campus.waste.common.R;
import com.campus.waste.model.vo.*;
import com.campus.waste.service.DataAnalysisService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/analysis")
@PreAuthorize("hasRole('ADMIN')")
public class DataAnalysisController {

    private final DataAnalysisService dataAnalysisService;

    public DataAnalysisController(DataAnalysisService dataAnalysisService) {
        this.dataAnalysisService = dataAnalysisService;
    }

    @GetMapping("/statistics")
    public R<List<WasteStatisticsVO>> getWasteStatistics(
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate,
            @RequestParam(required = false) String wasteType) {
        return R.ok(dataAnalysisService.getWasteStatistics(startDate, endDate, wasteType));
    }

    @GetMapping("/behavior")
    public R<DisposalBehaviorAnalysisVO> getDisposalBehaviorAnalysis(
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
        return R.ok(dataAnalysisService.getDisposalBehaviorAnalysis(startDate, endDate));
    }

    @GetMapping("/behavior/user/{userId}")
    public R<List<DisposalBehaviorVO>> getUserDisposalBehaviors(
            @PathVariable Long userId,
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
        return R.ok(dataAnalysisService.getUserDisposalBehaviors(userId, startDate, endDate));
    }

    @GetMapping("/report")
    public R<EffectivenessReportVO> getEffectivenessReport(
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate reportDate) {
        return R.ok(dataAnalysisService.getEffectivenessReport(reportDate));
    }

    @PostMapping("/report/generate")
    public R<Void> generateEffectivenessReport(
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate reportDate) {
        dataAnalysisService.generateEffectivenessReport(reportDate);
        return R.ok();
    }

    @GetMapping("/prediction")
    public R<List<TrendPredictionVO>> getTrendPrediction(
            @RequestParam String wasteType,
            @RequestParam(defaultValue = "3") Integer predictionMonths) {
        return R.ok(dataAnalysisService.getTrendPrediction(wasteType, predictionMonths));
    }

    @PostMapping("/prediction/update")
    public R<Void> updateTrendPrediction(@RequestParam String wasteType) {
        dataAnalysisService.updateTrendPrediction(wasteType);
        return R.ok();
    }

    @GetMapping("/overview")
    public R<Map<String, Object>> getDataAnalysisOverview() {
        return R.ok(dataAnalysisService.getDataAnalysisOverview());
    }

    @GetMapping("/time")
    public R<List<TimeAnalysisVO>> getTimeAnalysis(
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
        return R.ok(dataAnalysisService.getTimeAnalysis(startDate, endDate));
    }

    @GetMapping("/region")
    public R<List<RegionAnalysisVO>> getRegionAnalysis(
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
        return R.ok(dataAnalysisService.getRegionAnalysis(startDate, endDate));
    }

    @GetMapping("/export")
    public ResponseEntity<byte[]> exportAnalysisReport(
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
            @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate) {
        byte[] report = dataAnalysisService.exportAnalysisReport(startDate, endDate);
        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        headers.setContentDispositionFormData("attachment", 
            String.format("analysis_report_%s_%s.xlsx", startDate, endDate));
        
        return ResponseEntity.ok()
                .headers(headers)
                .body(report);
    }
}

backend\src\main\java\com\campus\waste\controller\DisposalManagementController.java

package com.campus.waste.controller;

import com.campus.waste.common.R;
import com.campus.waste.entity.DisposalRecord;
import com.campus.waste.model.vo.*;
import com.campus.waste.service.DisposalManagementService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/disposal")
public class DisposalManagementController {

    private final DisposalManagementService disposalManagementService;

    public DisposalManagementController(DisposalManagementService disposalManagementService) {
        this.disposalManagementService = disposalManagementService;
    }

    @GetMapping("/bins")
    public R<List<SmartBinVO>> getAllBins() {
        return R.ok(disposalManagementService.getAllBins());
    }

    @GetMapping("/bin/{binId}")
    public R<SmartBinDetailVO> getBinDetail(@PathVariable Long binId) {
        return R.ok(disposalManagementService.getBinDetail(binId));
    }

    @PutMapping("/bin/{binId}/status/{status}")
    @PreAuthorize("hasRole('ADMIN')")
    public R<Void> updateBinStatus(@PathVariable Long binId, @PathVariable Integer status) {
        disposalManagementService.updateBinStatus(binId, status);
        return R.ok();
    }

    @PostMapping("/record")
    public R<Void> recordDisposal(@RequestBody DisposalRecord record) {
        disposalManagementService.recordDisposal(record);
        return R.ok();
    }

    @GetMapping("/records")
    public R<IPage<DisposalRecordVO>> getDisposalRecords(
            @RequestParam(required = false) Long binId,
            @RequestParam(required = false) Long userId,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        return R.ok(disposalManagementService.getDisposalRecords(binId, userId, pageNum, pageSize));
    }

    @GetMapping("/alerts")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
    public R<List<BinAlertVO>> getUnprocessedAlerts() {
        return R.ok(disposalManagementService.getUnprocessedAlerts());
    }

    @PostMapping("/alert/{alertId}/process")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
    public R<Void> processAlert(
            @PathVariable Long alertId,
            @RequestParam String processor,
            @RequestParam String processResult) {
        disposalManagementService.processAlert(alertId, processor, processResult);
        return R.ok();
    }

    @GetMapping("/stats")
    public R<List<ClassificationStatsVO>> getClassificationStats(
            @RequestParam(required = false) Long userId,
            @RequestParam(defaultValue = "week") String timeRange) {
        return R.ok(disposalManagementService.getClassificationStats(userId, timeRange));
    }

    @GetMapping("/monitoring")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
    public R<List<BinMonitoringVO>> getBinMonitoringData() {
        return R.ok(disposalManagementService.getBinMonitoringData());
    }

    @PostMapping("/bin/{binId}/clean")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
    public R<Void> cleanBin(@PathVariable Long binId) {
        disposalManagementService.cleanBin(binId);
        return R.ok();
    }

    @GetMapping("/bin/{binId}/volume-trend")
    public R<List<BinVolumeStatVO>> getBinVolumeTrend(
            @PathVariable Long binId,
            @RequestParam(defaultValue = "day") String timeRange) {
        return R.ok(disposalManagementService.getBinVolumeTrend(binId, timeRange));
    }
}

backend\src\main\java\com\campus\waste\controller\KnowledgeController.java

package com.campus.waste.controller;

import com.campus.waste.common.R;
import com.campus.waste.entity.KnowledgeArticle;
import com.campus.waste.entity.FAQ;
import com.campus.waste.model.vo.KnowledgeArticleVO;
import com.campus.waste.model.vo.FAQVO;
import com.campus.waste.service.KnowledgeService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/knowledge")
public class KnowledgeController {

    private final KnowledgeService knowledgeService;

    public KnowledgeController(KnowledgeService knowledgeService) {
        this.knowledgeService = knowledgeService;
    }

    @GetMapping("/articles")
    public R<IPage<KnowledgeArticleVO>> getArticleList(
            @RequestParam(required = false) Long categoryId,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        return R.ok(knowledgeService.getArticleList(categoryId, pageNum, pageSize));
    }

    @GetMapping("/article/{articleId}")
    public R<KnowledgeArticleVO> getArticleDetail(@PathVariable Long articleId) {
        // 增加浏览次数
        knowledgeService.incrementArticleViewCount(articleId);
        return R.ok(knowledgeService.getArticleDetail(articleId));
    }

    @PostMapping("/article")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
    public R<Void> addArticle(@RequestBody KnowledgeArticle article) {
        knowledgeService.addArticle(article);
        return R.ok();
    }

    @PutMapping("/article")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
    public R<Void> updateArticle(@RequestBody KnowledgeArticle article) {
        knowledgeService.updateArticle(article);
        return R.ok();
    }

    @DeleteMapping("/article/{articleId}")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
    public R<Void> deleteArticle(@PathVariable Long articleId) {
        knowledgeService.deleteArticle(articleId);
        return R.ok();
    }

    @GetMapping("/faqs")
    public R<List<FAQVO>> getFAQList(@RequestParam(required = false) Long categoryId) {
        return R.ok(knowledgeService.getFAQList(categoryId));
    }

    @PostMapping("/faq")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
    public R<Void> addFAQ(@RequestBody FAQ faq) {
        knowledgeService.addFAQ(faq);
        return R.ok();
    }

    @PutMapping("/faq")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
    public R<Void> updateFAQ(@RequestBody FAQ faq) {
        knowledgeService.updateFAQ(faq);
        return R.ok();
    }

    @DeleteMapping("/faq/{faqId}")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER')")
    public R<Void> deleteFAQ(@PathVariable Long faqId) {
        knowledgeService.deleteFAQ(faqId);
        return R.ok();
    }

    @PostMapping("/faq/{faqId}/view")
    public R<Void> incrementFAQViewCount(@PathVariable Long faqId) {
        knowledgeService.incrementFAQViewCount(faqId);
        return R.ok();
    }
}

backend\src\main\java\com\campus\waste\controller\PointsController.java

package com.campus.waste.controller;

import com.campus.waste.common.R;
import com.campus.waste.model.vo.*;
import com.campus.waste.service.PointsService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/points")
public class PointsController {

    private final PointsService pointsService;

    public PointsController(PointsService pointsService) {
        this.pointsService = pointsService;
    }

    @GetMapping("/info")
    public R<UserPointsVO> getUserPoints() {
        Long userId = getCurrentUserId();
        return R.ok(pointsService.getUserPoints(userId));
    }

    @GetMapping("/records")
    public R<IPage<PointsRecordVO>> getPointsRecords(
            @RequestParam(required = false) Integer type,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        Long userId = getCurrentUserId();
        return R.ok(pointsService.getPointsRecords(userId, type, pageNum, pageSize));
    }

    @GetMapping("/products")
    public R<List<PointsProductVO>> getProductList(
            @RequestParam(defaultValue = "false") Boolean includeOffShelf) {
        return R.ok(pointsService.getProductList(includeOffShelf));
    }

    @PostMapping("/exchange")
    public R<Void> exchangeProduct(@RequestBody PointsExchangeDTO dto) {
        Long userId = getCurrentUserId();
        pointsService.exchangeProduct(
            userId,
            dto.getProductId(),
            dto.getQuantity(),
            dto.getReceiverName(),
            dto.getReceiverPhone(),
            dto.getReceiverAddress()
        );
        return R.ok();
    }

    @GetMapping("/exchanges")
    public R<IPage<PointsExchangeVO>> getExchangeRecords(
            @RequestParam(required = false) Integer status,
            @RequestParam(defaultValue = "1") Integer pageNum,
            @RequestParam(defaultValue = "10") Integer pageSize) {
        Long userId = getCurrentUserId();
        return R.ok(pointsService.getExchangeRecords(userId, status, pageNum, pageSize));
    }

    @GetMapping("/ranking")
    public R<List<PointsRankingVO>> getPointsRanking(
            @RequestParam(defaultValue = "week") String timeRange) {
        return R.ok(pointsService.getPointsRanking(timeRange));
    }

    @PutMapping("/exchange/{exchangeId}/status/{status}")
    @PreAuthorize("hasRole('ADMIN')")
    public R<Void> updateExchangeStatus(
            @PathVariable Long exchangeId,
            @PathVariable Integer status) {
        pointsService.updateExchangeStatus(exchangeId, status);
        return R.ok();
    }

    // TODO: 从SecurityContext中获取当前用户ID
    private Long getCurrentUserId() {
        return 1L; // 临时返回测试用户ID
    }
}

backend\src\main\java\com\campus\waste\controller\UserController.java

package com.campus.waste.controller;

import com.campus.waste.common.R;
import com.campus.waste.model.dto.UserDTO;
import com.campus.waste.model.vo.UserVO;
import com.campus.waste.service.UserService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

@RestController
@RequestMapping("/user")
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping("/register")
    public R<Void> register(@Valid @RequestBody UserDTO userDTO) {
        userService.register(userDTO);
        return R.ok();
    }

    @PutMapping
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER', 'STUDENT')")
    public R<Void> updateUserInfo(@Valid @RequestBody UserDTO userDTO) {
        userService.updateUserInfo(userDTO);
        return R.ok();
    }

    @GetMapping("/{userId}")
    @PreAuthorize("hasAnyRole('ADMIN', 'TEACHER', 'STUDENT')")
    public R<UserVO> getUserInfo(@PathVariable Long userId) {
        return R.ok(userService.getUserInfo(userId));
    }

    @GetMapping("/list")
    @PreAuthorize("hasRole('ADMIN')")
    public R<List<UserVO>> getUserList(@RequestParam(required = false) Integer userType) {
        return R.ok(userService.getUserList(userType));
    }

    @PutMapping("/{userId}/status/{status}")
    @PreAuthorize("hasRole('ADMIN')")
    public R<Void> updateUserStatus(@PathVariable Long userId, @PathVariable Integer status) {
        userService.updateUserStatus(userId, status);
        return R.ok();
    }

    @DeleteMapping("/{userId}")
    @PreAuthorize("hasRole('ADMIN')")
    public R<Void> deleteUser(@PathVariable Long userId) {
        userService.deleteUser(userId);
        return R.ok();
    }
}

backend\src\main\java\com\campus\waste\controller\WasteClassificationController.java

package com.campus.waste.controller;

import com.campus.waste.common.R;
import com.campus.waste.entity.WasteCategory;
import com.campus.waste.entity.WasteItem;
import com.campus.waste.model.dto.ImageRecognitionDTO;
import com.campus.waste.model.vo.WasteCategoryVO;
import com.campus.waste.model.vo.WasteItemVO;
import com.campus.waste.service.WasteClassificationService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@RestController
@RequestMapping("/waste")
public class WasteClassificationController {

    private final WasteClassificationService wasteClassificationService;

    public WasteClassificationController(WasteClassificationService wasteClassificationService) {
        this.wasteClassificationService = wasteClassificationService;
    }

    @GetMapping("/categories")
    public R<List<WasteCategoryVO>> getAllCategories() {
        return R.ok(wasteClassificationService.getAllCategories());
    }

    @GetMapping("/items/{categoryId}")
    public R<List<WasteItemVO>> getItemsByCategory(@PathVariable Long categoryId) {
        return R.ok(wasteClassificationService.getItemsByCategory(categoryId));
    }

    @GetMapping("/items/search")
    public R<List<WasteItemVO>> searchItems(@RequestParam String keyword) {
        return R.ok(wasteClassificationService.searchItems(keyword));
    }

    @PostMapping("/recognize")
    public R<ImageRecognitionDTO> recognizeImage(@RequestParam("file") MultipartFile image,
                                               @RequestParam Long userId) {
        return R.ok(wasteClassificationService.recognizeImage(image, userId));
    }

    @PostMapping("/category")
    @PreAuthorize("hasRole('ADMIN')")
    public R<Void> addCategory(@RequestBody WasteCategory category) {
        wasteClassificationService.addCategory(category);
        return R.ok();
    }

    @PostMapping("/item")
    @PreAuthorize("hasRole('ADMIN')")
    public R<Void> addItem(@RequestBody WasteItem item) {
        wasteClassificationService.addItem(item);
        return R.ok();
    }

    @PutMapping("/category")
    @PreAuthorize("hasRole('ADMIN')")
    public R<Void> updateCategory(@RequestBody WasteCategory category) {
        wasteClassificationService.updateCategory(category);
        return R.ok();
    }

    @PutMapping("/item")
    @PreAuthorize("hasRole('ADMIN')")
    public R<Void> updateItem(@RequestBody WasteItem item) {
        wasteClassificationService.updateItem(item);
        return R.ok();
    }

    @DeleteMapping("/category/{categoryId}")
    @PreAuthorize("hasRole('ADMIN')")
    public R<Void> deleteCategory(@PathVariable Long categoryId) {
        wasteClassificationService.deleteCategory(categoryId);
        return R.ok();
    }

    @DeleteMapping("/item/{itemId}")
    @PreAuthorize("hasRole('ADMIN')")
    public R<Void> deleteItem(@PathVariable Long itemId) {
        wasteClassificationService.deleteItem(itemId);
        return R.ok();
    }
}

backend\src\main\java\com\campus\waste\entity\Achievement.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("achievement")
public class Achievement {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String name;
    
    private String description;
    
    private String iconUrl;
    
    private Integer type;
    
    private Integer targetValue;
    
    private Integer pointsReward;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}

backend\src\main\java\com\campus\waste\entity\BinAlert.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("bin_alert")
public class BinAlert {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long binId;
    
    private Integer alertType;
    
    private String alertValue;
    
    private LocalDateTime alertTime;
    
    private LocalDateTime processedTime;
    
    private String processor;
    
    private String processResult;
    
    private Integer status;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\ClassificationStats.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@TableName("classification_stats")
public class ClassificationStats {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long userId;
    
    private Long categoryId;
    
    private Integer totalCount;
    
    private Integer correctCount;
    
    private LocalDate statsDate;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\DisposalBehavior.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
@TableName("disposal_behavior")
public class DisposalBehavior {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long userId;
    
    private String wasteType;
    
    private LocalDateTime disposalTime;
    
    private String location;
    
    private BigDecimal weight;
    
    private Boolean isCorrect;
    
    private String errorType;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}

backend\src\main\java\com\campus\waste\entity\DisposalRecord.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
import java.math.BigDecimal;

@Data
@TableName("disposal_record")
public class DisposalRecord {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long binId;
    
    private Long userId;
    
    private Long wasteTypeId;
    
    private BigDecimal weight;
    
    private BigDecimal volume;
    
    private Integer isCorrect;
    
    private String correctionNote;
    
    private LocalDateTime disposalTime;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}

backend\src\main\java\com\campus\waste\entity\EffectivenessReport.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@TableName("effectiveness_report")
public class EffectivenessReport {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private LocalDate reportDate;
    
    private Integer totalUsers;
    
    private Integer activeUsers;
    
    private BigDecimal totalWeight;
    
    private BigDecimal correctRate;
    
    private BigDecimal reductionRate;
    
    private BigDecimal satisfactionRate;
    
    private String mainProblems;
    
    private String suggestions;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\FAQ.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("faq")
public class FAQ {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String question;
    
    private String answer;
    
    private Long categoryId;
    
    private Integer viewCount;
    
    private Integer sortOrder;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\KnowledgeArticle.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("knowledge_article")
public class KnowledgeArticle {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String title;
    
    private String content;
    
    private Long categoryId;
    
    private String author;
    
    private Integer viewCount;
    
    private Integer status;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\PointsExchange.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("points_exchange")
public class PointsExchange {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long userId;
    
    private Long productId;
    
    private Integer pointsCost;
    
    private Integer quantity;
    
    private Integer status;
    
    private String receiverName;
    
    private String receiverPhone;
    
    private String receiverAddress;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\PointsProduct.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("points_product")
public class PointsProduct {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String name;
    
    private String description;
    
    private String imageUrl;
    
    private Integer points;
    
    private Integer stock;
    
    private Integer totalStock;
    
    private Integer exchangeLimit;
    
    private Integer status;
    
    private LocalDateTime startTime;
    
    private LocalDateTime endTime;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\PointsRecord.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("points_record")
public class PointsRecord {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long userId;
    
    private Integer points;
    
    private Integer type;
    
    private Long sourceId;
    
    private String sourceType;
    
    private String description;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
}

backend\src\main\java\com\campus\waste\entity\SmartBin.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;
import java.math.BigDecimal;

@Data
@TableName("smart_bin")
public class SmartBin {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String binCode;
    
    private String location;
    
    private Long categoryId;
    
    private Integer capacity;
    
    private Integer currentVolume;
    
    private Integer batteryLevel;
    
    private BigDecimal temperature;
    
    private BigDecimal humidity;
    
    private Integer status;
    
    private Integer alertThreshold;
    
    private LocalDateTime lastCleanedTime;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\TrendPrediction.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@TableName("trend_prediction")
public class TrendPrediction {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private LocalDate predictionDate;
    
    private String wasteType;
    
    private BigDecimal predictedWeight;
    
    private Integer predictedCount;
    
    private BigDecimal confidenceLevel;
    
    private String influencingFactors;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\User.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("sys_user")
public class User {
    
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String username;
    
    private String password;
    
    private String realName;
    
    private String email;
    
    private String phone;
    
    private String avatar;
    
    /**
     * 用户类型(0管理员,1教师,2学生)
     */
    private Integer userType;
    
    /**
     * 帐号状态(0正常 1停用)
     */
    private Integer status;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    
    @TableLogic
    private Integer delFlag;
}

backend\src\main\java\com\campus\waste\entity\UserAchievement.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("user_achievement")
public class UserAchievement {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long userId;
    
    private Long achievementId;
    
    private Integer currentValue;
    
    private Integer isCompleted;
    
    private LocalDateTime completeTime;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\UserPoints.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("user_points")
public class UserPoints {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private Long userId;
    
    private Integer totalPoints;
    
    private Integer availablePoints;
    
    private Integer level;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\WasteCategory.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("waste_category")
public class WasteCategory {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String categoryName;
    
    private String categoryCode;
    
    private String description;
    
    private String disposalGuide;
    
    private String categoryIcon;
    
    private Integer sortOrder;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\WasteItem.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.time.LocalDateTime;

@Data
@TableName("waste_item")
public class WasteItem {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String itemName;
    
    private Long categoryId;
    
    private String keywords;
    
    private String description;
    
    private String disposalMethod;
    
    private String imageUrl;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\entity\WasteStatistics.java

package com.campus.waste.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@TableName("waste_statistics")
public class WasteStatistics {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String wasteType;
    
    private BigDecimal weight;
    
    private Integer count;
    
    private BigDecimal correctRate;
    
    private LocalDate collectionTime;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\model\dto\ImageRecognitionDTO.java

package com.campus.waste.model.dto;

import lombok.Data;

@Data
public class ImageRecognitionDTO {
    private Long wasteItemId;
    private String itemName;
    private String categoryName;
    private String disposalMethod;
    private Double confidence;
    private String imageUrl;
}

backend\src\main\java\com\campus\waste\model\dto\LoginDTO.java

package com.campus.waste.model.dto;

import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class LoginDTO {
    @NotBlank(message = "用户名不能为空")
    private String username;

    @NotBlank(message = "密码不能为空")
    private String password;
}

backend\src\main\java\com\campus\waste\model\dto\UserDTO.java

package com.campus.waste.model.dto;

import lombok.Data;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;

@Data
public class UserDTO {
    private Long id;

    @NotBlank(message = "用户名不能为空")
    private String username;

    private String password;

    @NotBlank(message = "真实姓名不能为空")
    private String realName;

    @Email(message = "邮箱格式不正确")
    private String email;

    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String phone;

    private String avatar;

    private Integer userType;
}

backend\src\main\java\com\campus\waste\model\vo\BinAlertVO.java

package com.campus.waste.model.vo;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class BinAlertVO {
    private Long id;
    private Long binId;
    private String binCode;
    private String location;
    private Integer alertType;
    private String alertValue;
    private LocalDateTime alertTime;
    private LocalDateTime processedTime;
    private String processor;
    private String processResult;
    private Integer status;
}

backend\src\main\java\com\campus\waste\model\vo\BinMonitoringVO.java

package com.campus.waste.model.vo;

import lombok.Data;
import java.math.BigDecimal;

@Data
public class BinMonitoringVO {
    private Long id;
    private String binCode;
    private String location;
    private String categoryName;
    private Integer capacity;
    private Integer currentVolume;
    private Double usageRate;
    private Integer batteryLevel;
    private BigDecimal temperature;
    private BigDecimal humidity;
    private Integer status;
}

backend\src\main\java\com\campus\waste\model\vo\BinVolumeStatVO.java

package com.campus.waste.model.vo;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class BinVolumeStatVO {
    private LocalDateTime time;
    private Integer volume;
    private Double usageRate;
}

backend\src\main\java\com\campus\waste\model\vo\ClassificationStatsVO.java

package com.campus.waste.model.vo;

import lombok.Data;
import java.time.LocalDate;

@Data
public class ClassificationStatsVO {
    private Long id;
    private Long userId;
    private String username;
    private Long categoryId;
    private String categoryName;
    private Integer totalCount;
    private Integer correctCount;
    private Double accuracy;
    private LocalDate statsDate;
}

backend\src\main\java\com\campus\waste\model\vo\DisposalRecordVO.java

package com.campus.waste.model.vo;

import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
public class DisposalRecordVO {
    private Long id;
    private Long binId;
    private String binCode;
    private Long userId;
    private String username;
    private Long wasteTypeId;
    private String wasteTypeName;
    private BigDecimal weight;
    private BigDecimal volume;
    private Integer isCorrect;
    private String correctionNote;
    private LocalDateTime disposalTime;
}

backend\src\main\java\com\campus\waste\model\vo\FAQVO.java

package com.campus.waste.model.vo;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class FAQVO {
    private Long id;
    private String question;
    private String answer;
    private Long categoryId;
    private Integer viewCount;
    private Integer sortOrder;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\model\vo\KnowledgeArticleVO.java

package com.campus.waste.model.vo;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class KnowledgeArticleVO {
    private Long id;
    private String title;
    private String content;
    private Long categoryId;
    private String author;
    private Integer viewCount;
    private Integer status;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\model\vo\LoginVO.java

package com.campus.waste.model.vo;

import lombok.Data;

@Data
public class LoginVO {
    private String token;
    private String tokenType;
}

backend\src\main\java\com\campus\waste\model\vo\SmartBinDetailVO.java

package com.campus.waste.model.vo;

import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
public class SmartBinDetailVO {
    private Long id;
    private String binCode;
    private String location;
    private Long categoryId;
    private String categoryName;
    private Integer capacity;
    private Integer currentVolume;
    private Double usageRate;
    private Integer batteryLevel;
    private BigDecimal temperature;
    private BigDecimal humidity;
    private Integer status;
    private Integer alertThreshold;
    private LocalDateTime lastCleanedTime;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\model\vo\SmartBinVO.java

package com.campus.waste.model.vo;

import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;

@Data
public class SmartBinVO {
    private Long id;
    private String binCode;
    private String location;
    private Long categoryId;
    private Integer capacity;
    private Integer currentVolume;
    private Integer batteryLevel;
    private BigDecimal temperature;
    private BigDecimal humidity;
    private Integer status;
    private LocalDateTime lastCleanedTime;
}

backend\src\main\java\com\campus\waste\model\vo\UserVO.java

package com.campus.waste.model.vo;

import lombok.Data;
import java.time.LocalDateTime;

@Data
public class UserVO {
    private Long id;
    private String username;
    private String password;
    private String realName;
    private String email;
    private String phone;
    private String avatar;
    private Integer userType;
    private Integer status;
    private LocalDateTime createTime;
    private LocalDateTime updateTime;
}

backend\src\main\java\com\campus\waste\model\vo\WasteCategoryVO.java

package com.campus.waste.model.vo;

import lombok.Data;

@Data
public class WasteCategoryVO {
    private Long id;
    private String categoryName;
    private String categoryCode;
    private String description;
    private String disposalGuide;
    private String categoryIcon;
    private Integer sortOrder;
}

backend\src\main\java\com\campus\waste\model\vo\WasteItemVO.java

package com.campus.waste.model.vo;

import lombok.Data;

@Data
public class WasteItemVO {
    private Long id;
    private String itemName;
    private Long categoryId;
    private String categoryName;
    private String keywords;
    private String description;
    private String disposalMethod;
    private String imageUrl;
}

backend\src\main\java\com\campus\waste\security\JwtAuthenticationFilter.java

package com.campus.waste.security;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Value("${jwt.token-start-with}")
    private String tokenStartWith;

    private final JwtTokenProvider tokenProvider;
    private final CustomUserDetailsService customUserDetailsService;

    public JwtAuthenticationFilter(JwtTokenProvider tokenProvider, CustomUserDetailsService customUserDetailsService) {
        this.tokenProvider = tokenProvider;
        this.customUserDetailsService = customUserDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        try {
            String jwt = getJwtFromRequest(request);

            if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                Long userId = tokenProvider.getUserIdFromJWT(jwt);

                UserDetails userDetails = customUserDetailsService.loadUserById(userId);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(tokenStartWith)) {
            return bearerToken.substring(tokenStartWith.length());
        }
        return null;
    }
}

backend\src\main\java\com\campus\waste\security\JwtTokenProvider.java

package com.campus.waste.security;

import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtTokenProvider {

    @Value("${jwt.secret}")
    private String jwtSecret;

    @Value("${jwt.expiration}")
    private int jwtExpirationInMs;

    public String generateToken(Authentication authentication) {
        UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();

        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpirationInMs * 1000);

        return Jwts.builder()
                .setSubject(Long.toString(userPrincipal.getId()))
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(SignatureAlgorithm.HS512, jwtSecret)
                .compact();
    }

    public Long getUserIdFromJWT(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(jwtSecret)
                .parseClaimsJws(token)
                .getBody();

        return Long.parseLong(claims.getSubject());
    }

    public boolean validateToken(String authToken) {
        try {
            Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
            return true;
        } catch (SignatureException ex) {
            // 无效的JWT签名
            return false;
        } catch (MalformedJwtException ex) {
            // 无效的JWT令牌
            return false;
        } catch (ExpiredJwtException ex) {
            // 过期的JWT令牌
            return false;
        } catch (UnsupportedJwtException ex) {
            // 不支持的JWT令牌
            return false;
        } catch (IllegalArgumentException ex) {
            // JWT声明字符串为空
            return false;
        }
    }
}

backend\src\main\java\com\campus\waste\service\AchievementService.java

package com.campus.waste.service;

import com.campus.waste.entity.*;
import com.campus.waste.model.vo.*;
import java.util.List;

public interface AchievementService {
    
    /**
     * 获取所有成就列表
     */
    List<AchievementVO> getAllAchievements();
    
    /**
     * 获取用户成就列表
     */
    List<UserAchievementVO> getUserAchievements(Long userId);
    
    /**
     * 检查并更新用户成就
     */
    void checkAndUpdateAchievements(Long userId, Integer type, Integer value);
    
    /**
     * 获取用户最近完成的成就
     */
    List<UserAchievementVO> getRecentCompletedAchievements(Long userId, Integer limit);
    
    /**
     * 获取用户成就统计
     */
    AchievementStatsVO getUserAchievementStats(Long userId);
}

backend\src\main\java\com\campus\waste\service\DataAnalysisService.java

package com.campus.waste.service;

import com.campus.waste.entity.*;
import com.campus.waste.model.vo.*;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;

public interface DataAnalysisService {
    
    /**
     * 获取垃圾分类统计数据
     */
    List<WasteStatisticsVO> getWasteStatistics(LocalDate startDate, LocalDate endDate, String wasteType);
    
    /**
     * 获取投放行为分析数据
     */
    DisposalBehaviorAnalysisVO getDisposalBehaviorAnalysis(LocalDate startDate, LocalDate endDate);
    
    /**
     * 获取用户投放行为详情
     */
    List<DisposalBehaviorVO> getUserDisposalBehaviors(Long userId, LocalDate startDate, LocalDate endDate);
    
    /**
     * 获取效果评估报告
     */
    EffectivenessReportVO getEffectivenessReport(LocalDate reportDate);
    
    /**
     * 生成效果评估报告
     */
    void generateEffectivenessReport(LocalDate reportDate);
    
    /**
     * 获取趋势预测数据
     */
    List<TrendPredictionVO> getTrendPrediction(String wasteType, Integer predictionMonths);
    
    /**
     * 更新趋势预测
     */
    void updateTrendPrediction(String wasteType);
    
    /**
     * 获取数据分析概览
     */
    Map<String, Object> getDataAnalysisOverview();
    
    /**
     * 获取时段分析数据
     */
    List<TimeAnalysisVO> getTimeAnalysis(LocalDate startDate, LocalDate endDate);
    
    /**
     * 获取区域分析数据
     */
    List<RegionAnalysisVO> getRegionAnalysis(LocalDate startDate, LocalDate endDate);
    
    /**
     * 导出分析报告
     */
    byte[] exportAnalysisReport(LocalDate startDate, LocalDate endDate);
}

backend\src\main\java\com\campus\waste\service\DisposalManagementService.java

package com.campus.waste.service;

import com.campus.waste.entity.SmartBin;
import com.campus.waste.entity.DisposalRecord;
import com.campus.waste.entity.BinAlert;
import com.campus.waste.model.vo.*;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;

public interface DisposalManagementService {
    
    /**
     * 获取所有垃圾箱列表
     */
    List<SmartBinVO> getAllBins();
    
    /**
     * 获取垃圾箱详情
     */
    SmartBinDetailVO getBinDetail(Long binId);
    
    /**
     * 更新垃圾箱状态
     */
    void updateBinStatus(Long binId, Integer status);
    
    /**
     * 记录垃圾投放
     */
    void recordDisposal(DisposalRecord record);
    
    /**
     * 获取投放记录
     */
    IPage<DisposalRecordVO> getDisposalRecords(Long binId, Long userId, Integer pageNum, Integer pageSize);
    
    /**
     * 获取未处理的预警
     */
    List<BinAlertVO> getUnprocessedAlerts();
    
    /**
     * 处理预警
     */
    void processAlert(Long alertId, String processor, String processResult);
    
    /**
     * 获取分类准确率统计
     */
    List<ClassificationStatsVO> getClassificationStats(Long userId, String timeRange);
    
    /**
     * 检查并生成容量预警
     */
    void checkAndGenerateVolumeAlerts();
    
    /**
     * 获取实时监控数据
     */
    List<BinMonitoringVO> getBinMonitoringData();
    
    /**
     * 清空垃圾箱
     */
    void cleanBin(Long binId);
    
    /**
     * 获取垃圾箱容量趋势
     */
    List<BinVolumeStatVO> getBinVolumeTrend(Long binId, String timeRange);
}

backend\src\main\java\com\campus\waste\service\KnowledgeService.java

package com.campus.waste.service;

import com.campus.waste.entity.KnowledgeArticle;
import com.campus.waste.entity.FAQ;
import com.campus.waste.model.vo.KnowledgeArticleVO;
import com.campus.waste.model.vo.FAQVO;
import com.baomidou.mybatisplus.core.metadata.IPage;

import java.util.List;

public interface KnowledgeService {
    
    /**
     * 获取文章列表
     */
    IPage<KnowledgeArticleVO> getArticleList(Long categoryId, Integer pageNum, Integer pageSize);
    
    /**
     * 获取文章详情
     */
    KnowledgeArticleVO getArticleDetail(Long articleId);
    
    /**
     * 添加文章
     */
    void addArticle(KnowledgeArticle article);
    
    /**
     * 更新文章
     */
    void updateArticle(KnowledgeArticle article);
    
    /**
     * 删除文章
     */
    void deleteArticle(Long articleId);
    
    /**
     * 获取FAQ列表
     */
    List<FAQVO> getFAQList(Long categoryId);
    
    /**
     * 添加FAQ
     */
    void addFAQ(FAQ faq);
    
    /**
     * 更新FAQ
     */
    void updateFAQ(FAQ faq);
    
    /**
     * 删除FAQ
     */
    void deleteFAQ(Long faqId);
    
    /**
     * 增加文章浏览次数
     */
    void incrementArticleViewCount(Long articleId);
    
    /**
     * 增加FAQ浏览次数
     */
    void incrementFAQViewCount(Long faqId);
}

backend\src\main\java\com\campus\waste\service\PointsService.java

package com.campus.waste.service;

import com.campus.waste.entity.*;
import com.campus.waste.model.vo.*;
import com.baomidou.mybatisplus.core.metadata.IPage;
import java.util.List;

public interface PointsService {
    
    /**
     * 获取用户积分信息
     */
    UserPointsVO getUserPoints(Long userId);
    
    /**
     * 增加用户积分
     */
    void addPoints(Long userId, Integer points, Integer type, Long sourceId, String sourceType, String description);
    
    /**
     * 扣减用户积分
     */
    boolean deductPoints(Long userId, Integer points, Integer type, Long sourceId, String sourceType, String description);
    
    /**
     * 获取用户积分记录
     */
    IPage<PointsRecordVO> getPointsRecords(Long userId, Integer type, Integer pageNum, Integer pageSize);
    
    /**
     * 获取积分商城商品列表
     */
    List<PointsProductVO> getProductList(Boolean includeOffShelf);
    
    /**
     * 兑换商品
     */
    void exchangeProduct(Long userId, Long productId, Integer quantity, String receiverName, String receiverPhone, String receiverAddress);
    
    /**
     * 获取用户兑换记录
     */
    IPage<PointsExchangeVO> getExchangeRecords(Long userId, Integer status, Integer pageNum, Integer pageSize);
    
    /**
     * 获取积分排行榜
     */
    List<PointsRankingVO> getPointsRanking(String timeRange);
    
    /**
     * 更新兑换记录状态
     */
    void updateExchangeStatus(Long exchangeId, Integer status);
    
    /**
     * 检查并更新用户等级
     */
    void checkAndUpdateLevel(Long userId);
}

backend\src\main\java\com\campus\waste\service\UserService.java

package com.campus.waste.service;

import com.campus.waste.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;
import com.campus.waste.model.dto.UserDTO;
import com.campus.waste.model.vo.UserVO;
import java.util.List;

public interface UserService extends IService<User> {
    
    /**
     * 用户注册
     */
    void register(UserDTO userDTO);
    
    /**
     * 更新用户信息
     */
    void updateUserInfo(UserDTO userDTO);
    
    /**
     * 获取用户信息
     */
    UserVO getUserInfo(Long userId);
    
    /**
     * 获取用户列表
     */
    List<UserVO> getUserList(Integer userType);
    
    /**
     * 修改用户状态
     */
    void updateUserStatus(Long userId, Integer status);
    
    /**
     * 删除用户
     */
    void deleteUser(Long userId);
}

backend\src\main\java\com\campus\waste\service\WasteClassificationService.java

package com.campus.waste.service;

import com.campus.waste.entity.WasteCategory;
import com.campus.waste.entity.WasteItem;
import com.campus.waste.model.dto.ImageRecognitionDTO;
import com.campus.waste.model.vo.WasteCategoryVO;
import com.campus.waste.model.vo.WasteItemVO;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

public interface WasteClassificationService {
    
    /**
     * 获取所有垃圾分类
     */
    List<WasteCategoryVO> getAllCategories();
    
    /**
     * 根据分类ID获取物品列表
     */
    List<WasteItemVO> getItemsByCategory(Long categoryId);
    
    /**
     * 搜索垃圾物品
     */
    List<WasteItemVO> searchItems(String keyword);
    
    /**
     * 图像识别
     */
    ImageRecognitionDTO recognizeImage(MultipartFile image, Long userId);
    
    /**
     * 添加垃圾分类
     */
    void addCategory(WasteCategory category);
    
    /**
     * 添加垃圾物品
     */
    void addItem(WasteItem item);
    
    /**
     * 更新垃圾分类
     */
    void updateCategory(WasteCategory category);
    
    /**
     * 更新垃圾物品
     */
    void updateItem(WasteItem item);
    
    /**
     * 删除垃圾分类
     */
    void deleteCategory(Long categoryId);
    
    /**
     * 删除垃圾物品
     */
    void deleteItem(Long itemId);
}

backend\src\main\java\com\campus\waste\service\impl\AchievementServiceImpl.java

package com.campus.waste.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.campus.waste.entity.*;
import com.campus.waste.mapper.*;
import com.campus.waste.model.vo.*;
import com.campus.waste.service.AchievementService;
import com.campus.waste.service.PointsService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.BeanUtils;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class AchievementServiceImpl implements AchievementService {

    private final AchievementMapper achievementMapper;
    private final UserAchievementMapper userAchievementMapper;
    private final PointsService pointsService;

    public AchievementServiceImpl(AchievementMapper achievementMapper,
                                UserAchievementMapper userAchievementMapper,
                                PointsService pointsService) {
        this.achievementMapper = achievementMapper;
        this.userAchievementMapper = userAchievementMapper;
        this.pointsService = pointsService;
    }

    @Override
    public List<AchievementVO> getAllAchievements() {
        List<Achievement> achievements = achievementMapper.selectList(null);
        return achievements.stream()
                .map(this::convertToAchievementVO)
                .collect(Collectors.toList());
    }

    @Override
    public List<UserAchievementVO> getUserAchievements(Long userId) {
        // 获取所有成就
        List<Achievement> achievements = achievementMapper.selectList(null);
        
        // 获取用户已完成的成就
        List<UserAchievement> userAchievements = userAchievementMapper.selectList(
            new LambdaQueryWrapper<UserAchievement>()
                .eq(UserAchievement::getUserId, userId)
        );
        
        Map<Long, UserAchievement> userAchievementMap = userAchievements.stream()
                .collect(Collectors.toMap(UserAchievement::getAchievementId, ua -> ua));
        
        // 合并成就信息
        return achievements.stream()
                .map(achievement -> {
                    UserAchievementVO vo = new UserAchievementVO();
                    BeanUtils.copyProperties(achievement, vo);
                    
                    UserAchievement userAchievement = userAchievementMap.get(achievement.getId());
                    if (userAchievement != null) {
                        vo.setCurrentValue(userAchievement.getCurrentValue());
                        vo.setIsCompleted(userAchievement.getIsCompleted());
                        vo.setCompleteTime(userAchievement.getCompleteTime());
                    } else {
                        vo.setCurrentValue(0);
                        vo.setIsCompleted(0);
                    }
                    
                    return vo;
                })
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void checkAndUpdateAchievements(Long userId, Integer type, Integer value) {
        // 获取指定类型的未完成成就
        List<Achievement> achievements = achievementMapper.selectList(
            new LambdaQueryWrapper<Achievement>()
                .eq(Achievement::getType, type)
        );
        
        for (Achievement achievement : achievements) {
            UserAchievement userAchievement = userAchievementMapper.selectOne(
                new LambdaQueryWrapper<UserAchievement>()
                    .eq(UserAchievement::getUserId, userId)
                    .eq(UserAchievement::getAchievementId, achievement.getId())
            );
            
            if (userAchievement == null) {
                // 创建新的用户成就记录
                userAchievement = new UserAchievement();
                userAchievement.setUserId(userId);
                userAchievement.setAchievementId(achievement.getId());
                userAchievement.setCurrentValue(value);
                userAchievement.setIsCompleted(value >= achievement.getTargetValue() ? 1 : 0);
                if (userAchievement.getIsCompleted() == 1) {
                    userAchievement.setCompleteTime(LocalDateTime.now());
                    // 发放成就奖励
                    awardAchievementPoints(userId, achievement);
                }
                userAchievementMapper.insert(userAchievement);
            } else if (userAchievement.getIsCompleted() == 0) {
                // 更新进度
                userAchievement.setCurrentValue(value);
                if (value >= achievement.getTargetValue()) {
                    userAchievement.setIsCompleted(1);
                    userAchievement.setCompleteTime(LocalDateTime.now());
                    // 发放成就奖励
                    awardAchievementPoints(userId, achievement);
                }
                userAchievementMapper.updateById(userAchievement);
            }
        }
    }

    @Override
    public List<UserAchievementVO> getRecentCompletedAchievements(Long userId, Integer limit) {
        List<UserAchievement> userAchievements = userAchievementMapper.selectList(
            new LambdaQueryWrapper<UserAchievement>()
                .eq(UserAchievement::getUserId, userId)
                .eq(UserAchievement::getIsCompleted, 1)
                .orderByDesc(UserAchievement::getCompleteTime)
                .last("LIMIT " + limit)
        );
        
        return userAchievements.stream()
                .map(ua -> {
                    Achievement achievement = achievementMapper.selectById(ua.getAchievementId());
                    UserAchievementVO vo = new UserAchievementVO();
                    BeanUtils.copyProperties(achievement, vo);
                    vo.setCurrentValue(ua.getCurrentValue());
                    vo.setIsCompleted(ua.getIsCompleted());
                    vo.setCompleteTime(ua.getCompleteTime());
                    return vo;
                })
                .collect(Collectors.toList());
    }

    @Override
    public AchievementStatsVO getUserAchievementStats(Long userId) {
        AchievementStatsVO stats = new AchievementStatsVO();
        
        // 获取用户成就完成情况
        List<UserAchievement> userAchievements = userAchievementMapper.selectList(
            new LambdaQueryWrapper<UserAchievement>()
                .eq(UserAchievement::getUserId, userId)
        );
        
        // 获取所有成就总数
        int totalAchievements = achievementMapper.selectCount(null);
        
        // 计算完成数量
        long completedCount = userAchievements.stream()
                .filter(ua -> ua.getIsCompleted() == 1)
                .count();
        
        stats.setTotalCount(totalAchievements);
        stats.setCompletedCount((int) completedCount);
        stats.setCompletionRate(totalAchievements == 0 ? 0 : 
            (double) completedCount / totalAchievements * 100);
        
        return stats;
    }

    private void awardAchievementPoints(Long userId, Achievement achievement) {
        if (achievement.getPointsReward() > 0) {
            pointsService.addPoints(
                userId,
                achievement.getPointsReward(),
                3,
                achievement.getId(),
                "achievement_reward",
                "完成成就:" + achievement.getName()
            );
        }
    }

    private AchievementVO convertToAchievementVO(Achievement achievement) {
        AchievementVO vo = new AchievementVO();
        BeanUtils.copyProperties(achievement, vo);
        return vo;
    }
}

backend\src\main\java\com\campus\waste\service\impl\DataAnalysisServiceImpl.java

package com.campus.waste.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.campus.waste.entity.*;
import com.campus.waste.mapper.*;
import com.campus.waste.model.vo.*;
import com.campus.waste.service.DataAnalysisService;
import com.campus.waste.utils.ExcelUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.apache.commons.math3.stat.regression.SimpleRegression;
import org.springframework.beans.BeanUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import java.math.BigDecimal;
import java.math.RoundingMode;

@Service
public class DataAnalysisServiceImpl implements DataAnalysisService {

    private final WasteStatisticsMapper wasteStatisticsMapper;
    private final DisposalBehaviorMapper disposalBehaviorMapper;
    private final EffectivenessReportMapper effectivenessReportMapper;
    private final TrendPredictionMapper trendPredictionMapper;

    public DataAnalysisServiceImpl(WasteStatisticsMapper wasteStatisticsMapper,
                                 DisposalBehaviorMapper disposalBehaviorMapper,
                                 EffectivenessReportMapper effectivenessReportMapper,
                                 TrendPredictionMapper trendPredictionMapper) {
        this.wasteStatisticsMapper = wasteStatisticsMapper;
        this.disposalBehaviorMapper = disposalBehaviorMapper;
        this.effectivenessReportMapper = effectivenessReportMapper;
        this.trendPredictionMapper = trendPredictionMapper;
    }

    @Override
    public List<WasteStatisticsVO> getWasteStatistics(LocalDate startDate, LocalDate endDate, String wasteType) {
        LambdaQueryWrapper<WasteStatistics> wrapper = new LambdaQueryWrapper<>();
        wrapper.ge(WasteStatistics::getCollectionTime, startDate)
               .le(WasteStatistics::getCollectionTime, endDate)
               .eq(wasteType != null, WasteStatistics::getWasteType, wasteType)
               .orderByAsc(WasteStatistics::getCollectionTime);

        List<WasteStatistics> statistics = wasteStatisticsMapper.selectList(wrapper);
        return statistics.stream()
                .map(this::convertToWasteStatisticsVO)
                .collect(Collectors.toList());
    }

    @Override
    public DisposalBehaviorAnalysisVO getDisposalBehaviorAnalysis(LocalDate startDate, LocalDate endDate) {
        List<DisposalBehavior> behaviors = disposalBehaviorMapper.selectList(
            new LambdaQueryWrapper<DisposalBehavior>()
                .ge(DisposalBehavior::getDisposalTime, startDate.atStartOfDay())
                .le(DisposalBehavior::getDisposalTime, endDate.atTime(23, 59, 59))
        );

        DisposalBehaviorAnalysisVO analysis = new DisposalBehaviorAnalysisVO();
        
        // 计算总体正确率
        long correctCount = behaviors.stream().filter(DisposalBehavior::getIsCorrect).count();
        analysis.setOverallCorrectRate(calculatePercentage(correctCount, behaviors.size()));

        // 按垃圾类型分组统计
        Map<String, List<DisposalBehavior>> typeGroups = behaviors.stream()
                .collect(Collectors.groupingBy(DisposalBehavior::getWasteType));
        
        List<TypeAnalysisVO> typeAnalysis = typeGroups.entrySet().stream()
                .map(entry -> {
                    TypeAnalysisVO vo = new TypeAnalysisVO();
                    vo.setWasteType(entry.getKey());
                    vo.setCount(entry.getValue().size());
                    long typeCorrectCount = entry.getValue().stream()
                            .filter(DisposalBehavior::getIsCorrect)
                            .count();
                    vo.setCorrectRate(calculatePercentage(typeCorrectCount, entry.getValue().size()));
                    return vo;
                })
                .collect(Collectors.toList());
        analysis.setTypeAnalysis(typeAnalysis);

        // 错误类型分析
        Map<String, Long> errorTypes = behaviors.stream()
                .filter(b -> !b.getIsCorrect())
                .collect(Collectors.groupingBy(
                    DisposalBehavior::getErrorType,
                    Collectors.counting()
                ));
        analysis.setErrorTypeAnalysis(errorTypes);

        return analysis;
    }

    @Override
    public List<DisposalBehaviorVO> getUserDisposalBehaviors(Long userId, LocalDate startDate, LocalDate endDate) {
        List<DisposalBehavior> behaviors = disposalBehaviorMapper.selectList(
            new LambdaQueryWrapper<DisposalBehavior>()
                .eq(DisposalBehavior::getUserId, userId)
                .ge(DisposalBehavior::getDisposalTime, startDate.atStartOfDay())
                .le(DisposalBehavior::getDisposalTime, endDate.atTime(23, 59, 59))
                .orderByDesc(DisposalBehavior::getDisposalTime)
        );

        return behaviors.stream()
                .map(this::convertToDisposalBehaviorVO)
                .collect(Collectors.toList());
    }

    @Override
    public EffectivenessReportVO getEffectivenessReport(LocalDate reportDate) {
        EffectivenessReport report = effectivenessReportMapper.selectOne(
            new LambdaQueryWrapper<EffectivenessReport>()
                .eq(EffectivenessReport::getReportDate, reportDate)
        );

        if (report == null) {
            generateEffectivenessReport(reportDate);
            report = effectivenessReportMapper.selectOne(
                new LambdaQueryWrapper<EffectivenessReport>()
                    .eq(EffectivenessReport::getReportDate, reportDate)
            );
        }

        return convertToEffectivenessReportVO(report);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void generateEffectivenessReport(LocalDate reportDate) {
        // 获取统计周期(前一天)
        LocalDateTime startTime = reportDate.atStartOfDay();
        LocalDateTime endTime = reportDate.atTime(23, 59, 59);

        // 查询当天的投放行为数据
        List<DisposalBehavior> behaviors = disposalBehaviorMapper.selectList(
            new LambdaQueryWrapper<DisposalBehavior>()
                .ge(DisposalBehavior::getDisposalTime, startTime)
                .le(DisposalBehavior::getDisposalTime, endTime)
        );

        // 统计数据
        Set<Long> activeUsers = behaviors.stream()
                .map(DisposalBehavior::getUserId)
                .collect(Collectors.toSet());

        BigDecimal totalWeight = behaviors.stream()
                .map(DisposalBehavior::getWeight)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        long correctCount = behaviors.stream()
                .filter(DisposalBehavior::getIsCorrect)
                .count();

        // 创建报告
        EffectivenessReport report = new EffectivenessReport();
        report.setReportDate(reportDate);
        report.setTotalUsers(1000); // TODO: 从用户表获取
        report.setActiveUsers(activeUsers.size());
        report.setTotalWeight(totalWeight);
        report.setCorrectRate(calculatePercentage(correctCount, behaviors.size()));
        report.setReductionRate(calculateReductionRate(reportDate));
        report.setSatisfactionRate(new BigDecimal("95.00")); // TODO: 从满意度调查获取
        report.setMainProblems(analyzeMainProblems(behaviors));
        report.setSuggestions(generateSuggestions(behaviors));

        // 保存报告
        effectivenessReportMapper.insert(report);
    }

    @Override
    public List<TrendPredictionVO> getTrendPrediction(String wasteType, Integer predictionMonths) {
        // 获取历史数据
        LocalDate endDate = LocalDate.now();
        LocalDate startDate = endDate.minusMonths(12); // 使用过去12个月的数据进行预测

        List<WasteStatistics> historicalData = wasteStatisticsMapper.selectList(
            new LambdaQueryWrapper<WasteStatistics>()
                .eq(WasteStatistics::getWasteType, wasteType)
                .ge(WasteStatistics::getCollectionTime, startDate)
                .le(WasteStatistics::getCollectionTime, endDate)
                .orderByAsc(WasteStatistics::getCollectionTime)
        );

        // 使用简单线性回归进行预测
        SimpleRegression regression = new SimpleRegression();
        for (int i = 0; i < historicalData.size(); i++) {
            regression.addData(i, historicalData.get(i).getWeight().doubleValue());
        }

        // 生成预测数据
        List<TrendPredictionVO> predictions = new ArrayList<>();
        LocalDate currentDate = endDate.plusDays(1);
        for (int i = 0; i < predictionMonths * 30; i++) {
            TrendPredictionVO prediction = new TrendPredictionVO();
            prediction.setPredictionDate(currentDate);
            prediction.setWasteType(wasteType);
            
            double predictedValue = regression.predict(historicalData.size() + i);
            prediction.setPredictedWeight(new BigDecimal(predictedValue).setScale(2, RoundingMode.HALF_UP));
            
            // 计算预测区间
            double[] interval = regression.getConfidenceInterval(historicalData.size() + i, 0.95);
            prediction.setConfidenceLevel(new BigDecimal("95.00"));
            prediction.setLowerBound(new BigDecimal(interval[0]).setScale(2, RoundingMode.HALF_UP));
            prediction.setUpperBound(new BigDecimal(interval[1]).setScale(2, RoundingMode.HALF_UP));
            
            predictions.add(prediction);
            currentDate = currentDate.plusDays(1);
        }

        return predictions;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateTrendPrediction(String wasteType) {
        // 删除旧的预测数据
        trendPredictionMapper.delete(
            new LambdaQueryWrapper<TrendPrediction>()
                .eq(TrendPrediction::getWasteType, wasteType)
                .ge(TrendPrediction::getPredictionDate, LocalDate.now())
        );

        // 生成新的预测数据
        List<TrendPredictionVO> predictions = getTrendPrediction(wasteType, 3); // 预测未来3个月
        
        // 保存预测数据
        predictions.forEach(prediction -> {
            TrendPrediction entity = new TrendPrediction();
            BeanUtils.copyProperties(prediction, entity);
            trendPredictionMapper.insert(entity);
        });
    }

    @Override
    public Map<String, Object> getDataAnalysisOverview() {
        Map<String, Object> overview = new HashMap<>();
        LocalDate today = LocalDate.now();
        LocalDate startDate = today.minusDays(7);

        // 获取最近7天的数据
        List<WasteStatistics> weeklyStats = wasteStatisticsMapper.selectList(
            new LambdaQueryWrapper<WasteStatistics>()
                .ge(WasteStatistics::getCollectionTime, startDate)
                .le(WasteStatistics::getCollectionTime, today)
        );

        // 计算总量和增长率
        BigDecimal totalWeight = weeklyStats.stream()
                .map(WasteStatistics::getWeight)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        
        double averageCorrectRate = weeklyStats.stream()
                .mapToDouble(ws -> ws.getCorrectRate().doubleValue())
                .average()
                .orElse(0.0);

        // 获取最新的效果评估报告
        EffectivenessReport latestReport = effectivenessReportMapper.selectOne(
            new LambdaQueryWrapper<EffectivenessReport>()
                .orderByDesc(EffectivenessReport::getReportDate)
                .last("LIMIT 1")
        );

        overview.put("totalWeight", totalWeight);
        overview.put("averageCorrectRate", averageCorrectRate);
        overview.put("activeUsers", latestReport != null ? latestReport.getActiveUsers() : 0);
        overview.put("reductionRate", latestReport != null ? latestReport.getReductionRate() : 0);

        return overview;
    }

    @Override
    public List<TimeAnalysisVO> getTimeAnalysis(LocalDate startDate, LocalDate endDate) {
        List<DisposalBehavior> behaviors = disposalBehaviorMapper.selectList(
            new LambdaQueryWrapper<DisposalBehavior>()
                .ge(DisposalBehavior::getDisposalTime, startDate.atStartOfDay())
                .le(DisposalBehavior::getDisposalTime, endDate.atTime(23, 59, 59))
        );

        // 按小时统计
        Map<Integer, List<DisposalBehavior>> hourlyGroups = behaviors.stream()
                .collect(Collectors.groupingBy(b -> b.getDisposalTime().getHour()));

        return hourlyGroups.entrySet().stream()
                .map(entry -> {
                    TimeAnalysisVO vo = new TimeAnalysisVO();
                    vo.setHour(entry.getKey());
                    vo.setCount(entry.getValue().size());
                    long correctCount = entry.getValue().stream()
                            .filter(DisposalBehavior::getIsCorrect)
                            .count();
                    vo.setCorrectRate(calculatePercentage(correctCount, entry.getValue().size()));
                    return vo;
                })
                .sorted(Comparator.comparing(TimeAnalysisVO::getHour))
                .collect(Collectors.toList());
    }

    @Override
    public List<RegionAnalysisVO> getRegionAnalysis(LocalDate startDate, LocalDate endDate) {
        List<DisposalBehavior> behaviors = disposalBehaviorMapper.selectList(
            new LambdaQueryWrapper<DisposalBehavior>()
                .ge(DisposalBehavior::getDisposalTime, startDate.atStartOfDay())
                .le(DisposalBehavior::getDisposalTime, endDate.atTime(23, 59, 59))
        );

        // 按区域统计
        Map<String, List<DisposalBehavior>> regionGroups = behaviors.stream()
                .collect(Collectors.groupingBy(DisposalBehavior::getLocation));

        return regionGroups.entrySet().stream()
                .map(entry -> {
                    RegionAnalysisVO vo = new RegionAnalysisVO();
                    vo.setRegion(entry.getKey());
                    vo.setCount(entry.getValue().size());
                    long correctCount = entry.getValue().stream()
                            .filter(DisposalBehavior::getIsCorrect)
                            .count();
                    vo.setCorrectRate(calculatePercentage(correctCount, entry.getValue().size()));
                    
                    // 计算各类型垃圾占比
                    Map<String, Long> typeDistribution = entry.getValue().stream()
                            .collect(Collectors.groupingBy(
                                DisposalBehavior::getWasteType,
                                Collectors.counting()
                            ));
                    vo.setTypeDistribution(typeDistribution);
                    
                    return vo;
                })
                .collect(Collectors.toList());
    }

    @Override
    public byte[] exportAnalysisReport(LocalDate startDate, LocalDate endDate) {
        // 获取各项分析数据
        List<WasteStatisticsVO> statistics = getWasteStatistics(startDate, endDate, null);
        DisposalBehaviorAnalysisVO behaviorAnalysis = getDisposalBehaviorAnalysis(startDate, endDate);
        List<TimeAnalysisVO> timeAnalysis = getTimeAnalysis(startDate, endDate);
        List<RegionAnalysisVO> regionAnalysis = getRegionAnalysis(startDate, endDate);

        // 生成Excel报告
        return ExcelUtils.generateAnalysisReport(
            statistics,
            behaviorAnalysis,
            timeAnalysis,
            regionAnalysis,
            startDate,
            endDate
        );
    }

    private BigDecimal calculatePercentage(long part, long total) {
        if (total == 0) return BigDecimal.ZERO;
        return new BigDecimal(part)
                .multiply(new BigDecimal("100"))
                .divide(new BigDecimal(total), 2, RoundingMode.HALF_UP);
    }

    private BigDecimal calculateReductionRate(LocalDate currentDate) {
        // 获取上周同期数据
        LocalDate lastWeek = currentDate.minusWeeks(1);
        WasteStatistics lastWeekStats = wasteStatisticsMapper.selectOne(
            new LambdaQueryWrapper<WasteStatistics>()
                .eq(WasteStatistics::getCollectionTime, lastWeek)
        );

        WasteStatistics currentStats = wasteStatisticsMapper.selectOne(
            new LambdaQueryWrapper<WasteStatistics>()
                .eq(WasteStatistics::getCollectionTime, currentDate)
        );

        if (lastWeekStats == null || currentStats == null) {
            return BigDecimal.ZERO;
        }

        BigDecimal reduction = lastWeekStats.getWeight().subtract(currentStats.getWeight());
        return reduction.multiply(new BigDecimal("100"))
                .divide(lastWeekStats.getWeight(), 2, RoundingMode.HALF_UP);
    }

    private String analyzeMainProblems(List<DisposalBehavior> behaviors) {
        // 分析错误行为
        Map<String, Long> errorTypes = behaviors.stream()
                .filter(b -> !b.getIsCorrect())
                .collect(Collectors.groupingBy(
                    DisposalBehavior::getErrorType,
                    Collectors.counting()
                ));

        // 找出主要问题
        return errorTypes.entrySet().stream()
                .sorted((e1, e2) -> e2.getValue().compareTo(e1.getValue()))
                .limit(3)
                .map(e -> e.getKey() + "(" + e.getValue() + "次)")
                .collect(Collectors.joining("、"));
    }

    private String generateSuggestions(List<DisposalBehavior> behaviors) {
        List<String> suggestions = new ArrayList<>();

        // 分析错误率
        long totalErrors = behaviors.stream()
                .filter(b -> !b.getIsCorrect())
                .count();
        double errorRate = (double) totalErrors / behaviors.size();

        if (errorRate > 0.3) {
            suggestions.add("建议加强垃圾分类知识的宣传教育");
        }

        // 分析时段分布
        Map<Integer, Long> hourlyDistribution = behaviors.stream()
                .collect(Collectors.groupingBy(
                    b -> b.getDisposalTime().getHour(),
                    Collectors.counting()
                ));

        // 检查是否存在投放高峰期
        hourlyDistribution.entrySet().stream()
                .filter(e -> e.getValue() > behaviors.size() * 0.2)
                .findFirst()
                .ifPresent(e -> suggestions.add(
                    String.format("建议在%d时段增加垃圾收集频次", e.getKey())
                ));

        return String.join(";", suggestions);
    }

    private WasteStatisticsVO convertToWasteStatisticsVO(WasteStatistics statistics) {
        WasteStatisticsVO vo = new WasteStatisticsVO();
        BeanUtils.copyProperties(statistics, vo);
        return vo;
    }

    private DisposalBehaviorVO convertToDisposalBehaviorVO(DisposalBehavior behavior) {
        DisposalBehaviorVO vo = new DisposalBehaviorVO();
        BeanUtils.copyProperties(behavior, vo);
        return vo;
    }

    private EffectivenessReportVO convertToEffectivenessReportVO(EffectivenessReport report) {
        EffectivenessReportVO vo = new EffectivenessReportVO();
        BeanUtils.copyProperties(report, vo);
        return vo;
    }
}

backend\src\main\java\com\campus\waste\service\impl\DisposalManagementServiceImpl.java

package com.campus.waste.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.campus.waste.entity.*;
import com.campus.waste.mapper.*;
import com.campus.waste.model.vo.*;
import com.campus.waste.service.DisposalManagementService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.BeanUtils;

import java.time.LocalDateTime;
import java.time.LocalDate;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class DisposalManagementServiceImpl implements DisposalManagementService {

    private final SmartBinMapper smartBinMapper;
    private final DisposalRecordMapper disposalRecordMapper;
    private final BinAlertMapper binAlertMapper;
    private final ClassificationStatsMapper classificationStatsMapper;
    private final WasteCategoryMapper wasteCategoryMapper;

    public DisposalManagementServiceImpl(SmartBinMapper smartBinMapper,
                                       DisposalRecordMapper disposalRecordMapper,
                                       BinAlertMapper binAlertMapper,
                                       ClassificationStatsMapper classificationStatsMapper,
                                       WasteCategoryMapper wasteCategoryMapper) {
        this.smartBinMapper = smartBinMapper;
        this.disposalRecordMapper = disposalRecordMapper;
        this.binAlertMapper = binAlertMapper;
        this.classificationStatsMapper = classificationStatsMapper;
        this.wasteCategoryMapper = wasteCategoryMapper;
    }

    @Override
    public List<SmartBinVO> getAllBins() {
        List<SmartBin> bins = smartBinMapper.selectList(null);
        return bins.stream()
                .map(this::convertToSmartBinVO)
                .collect(Collectors.toList());
    }

    @Override
    public SmartBinDetailVO getBinDetail(Long binId) {
        SmartBin bin = smartBinMapper.selectById(binId);
        if (bin == null) {
            throw new RuntimeException("垃圾箱不存在");
        }
        
        SmartBinDetailVO vo = new SmartBinDetailVO();
        BeanUtils.copyProperties(bin, vo);
        
        // 获取分类信息
        WasteCategory category = wasteCategoryMapper.selectById(bin.getCategoryId());
        if (category != null) {
            vo.setCategoryName(category.getCategoryName());
        }
        
        // 计算容量使用率
        vo.setUsageRate((double) bin.getCurrentVolume() / bin.getCapacity() * 100);
        
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateBinStatus(Long binId, Integer status) {
        SmartBin bin = new SmartBin();
        bin.setId(binId);
        bin.setStatus(status);
        smartBinMapper.updateById(bin);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void recordDisposal(DisposalRecord record) {
        // 记录投放
        record.setDisposalTime(LocalDateTime.now());
        disposalRecordMapper.insert(record);
        
        // 更新垃圾箱容量
        SmartBin bin = smartBinMapper.selectById(record.getBinId());
        bin.setCurrentVolume(bin.getCurrentVolume() + record.getVolume().intValue());
        smartBinMapper.updateById(bin);
        
        // 更新统计数据
        updateClassificationStats(record);
        
        // 检查是否需要生成预警
        checkAndGenerateAlert(bin);
    }

    @Override
    public IPage<DisposalRecordVO> getDisposalRecords(Long binId, Long userId, Integer pageNum, Integer pageSize) {
        Page<DisposalRecord> page = new Page<>(pageNum, pageSize);
        
        LambdaQueryWrapper<DisposalRecord> wrapper = new LambdaQueryWrapper<>();
        if (binId != null) {
            wrapper.eq(DisposalRecord::getBinId, binId);
        }
        if (userId != null) {
            wrapper.eq(DisposalRecord::getUserId, userId);
        }
        wrapper.orderByDesc(DisposalRecord::getDisposalTime);
        
        IPage<DisposalRecord> recordPage = disposalRecordMapper.selectPage(page, wrapper);
        return recordPage.convert(this::convertToDisposalRecordVO);
    }

    @Override
    public List<BinAlertVO> getUnprocessedAlerts() {
        List<BinAlert> alerts = binAlertMapper.selectList(
            new LambdaQueryWrapper<BinAlert>()
                .eq(BinAlert::getStatus, 0)
                .orderByDesc(BinAlert::getAlertTime)
        );
        
        return alerts.stream()
                .map(this::convertToBinAlertVO)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void processAlert(Long alertId, String processor, String processResult) {
        BinAlert alert = new BinAlert();
        alert.setId(alertId);
        alert.setProcessor(processor);
        alert.setProcessResult(processResult);
        alert.setProcessedTime(LocalDateTime.now());
        alert.setStatus(1);
        binAlertMapper.updateById(alert);
    }

    @Override
    public List<ClassificationStatsVO> getClassificationStats(Long userId, String timeRange) {
        LocalDate startDate = getStartDateFromTimeRange(timeRange);
        
        List<ClassificationStats> statsList = classificationStatsMapper.selectList(
            new LambdaQueryWrapper<ClassificationStats>()
                .eq(userId != null, ClassificationStats::getUserId, userId)
                .ge(ClassificationStats::getStatsDate, startDate)
                .orderByAsc(ClassificationStats::getStatsDate)
        );
        
        return statsList.stream()
                .map(this::convertToClassificationStatsVO)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void checkAndGenerateVolumeAlerts() {
        List<SmartBin> bins = smartBinMapper.selectList(null);
        for (SmartBin bin : bins) {
            checkAndGenerateAlert(bin);
        }
    }

    @Override
    public List<BinMonitoringVO> getBinMonitoringData() {
        List<SmartBin> bins = smartBinMapper.selectList(null);
        return bins.stream()
                .map(this::convertToBinMonitoringVO)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cleanBin(Long binId) {
        SmartBin bin = new SmartBin();
        bin.setId(binId);
        bin.setCurrentVolume(0);
        bin.setLastCleanedTime(LocalDateTime.now());
        smartBinMapper.updateById(bin);
    }

    @Override
    public List<BinVolumeStatVO> getBinVolumeTrend(Long binId, String timeRange) {
        LocalDateTime startTime = getStartTimeFromTimeRange(timeRange);
        return disposalRecordMapper.getBinVolumeTrend(binId, startTime);
    }

    private void checkAndGenerateAlert(SmartBin bin) {
        // 检查容量预警
        double usageRate = (double) bin.getCurrentVolume() / bin.getCapacity() * 100;
        if (usageRate >= bin.getAlertThreshold()) {
            generateAlert(bin.getId(), 1, String.format("%.2f%%", usageRate));
        }
        
        // 检查电量预警
        if (bin.getBatteryLevel() != null && bin.getBatteryLevel() < 20) {
            generateAlert(bin.getId(), 2, bin.getBatteryLevel().toString());
        }
    }

    private void generateAlert(Long binId, Integer alertType, String alertValue) {
        // 检查是否已存在未处理的同类型预警
        Long count = binAlertMapper.selectCount(
            new LambdaQueryWrapper<BinAlert>()
                .eq(BinAlert::getBinId, binId)
                .eq(BinAlert::getAlertType, alertType)
                .eq(BinAlert::getStatus, 0)
        );
        
        if (count == 0) {
            BinAlert alert = new BinAlert();
            alert.setBinId(binId);
            alert.setAlertType(alertType);
            alert.setAlertValue(alertValue);
            alert.setAlertTime(LocalDateTime.now());
            alert.setStatus(0);
            binAlertMapper.insert(alert);
        }
    }

    private void updateClassificationStats(DisposalRecord record) {
        LocalDate statsDate = record.getDisposalTime().toLocalDate();
        
        ClassificationStats stats = classificationStatsMapper.selectOne(
            new LambdaQueryWrapper<ClassificationStats>()
                .eq(ClassificationStats::getUserId, record.getUserId())
                .eq(ClassificationStats::getCategoryId, record.getWasteTypeId())
                .eq(ClassificationStats::getStatsDate, statsDate)
        );
        
        if (stats == null) {
            stats = new ClassificationStats();
            stats.setUserId(record.getUserId());
            stats.setCategoryId(record.getWasteTypeId());
            stats.setStatsDate(statsDate);
            stats.setTotalCount(1);
            stats.setCorrectCount(record.getIsCorrect());
            classificationStatsMapper.insert(stats);
        } else {
            stats.setTotalCount(stats.getTotalCount() + 1);
            stats.setCorrectCount(stats.getCorrectCount() + record.getIsCorrect());
            classificationStatsMapper.updateById(stats);
        }
    }

    private SmartBinVO convertToSmartBinVO(SmartBin bin) {
        SmartBinVO vo = new SmartBinVO();
        BeanUtils.copyProperties(bin, vo);
        return vo;
    }

    private DisposalRecordVO convertToDisposalRecordVO(DisposalRecord record) {
        DisposalRecordVO vo = new DisposalRecordVO();
        BeanUtils.copyProperties(record, vo);
        return vo;
    }

    private BinAlertVO convertToBinAlertVO(BinAlert alert) {
        BinAlertVO vo = new BinAlertVO();
        BeanUtils.copyProperties(alert, vo);
        return vo;
    }

    private ClassificationStatsVO convertToClassificationStatsVO(ClassificationStats stats) {
        ClassificationStatsVO vo = new ClassificationStatsVO();
        BeanUtils.copyProperties(stats, vo);
        return vo;
    }

    private BinMonitoringVO convertToBinMonitoringVO(SmartBin bin) {
        BinMonitoringVO vo = new BinMonitoringVO();
        BeanUtils.copyProperties(bin, vo);
        vo.setUsageRate((double) bin.getCurrentVolume() / bin.getCapacity() * 100);
        return vo;
    }

    private LocalDate getStartDateFromTimeRange(String timeRange) {
        LocalDate now = LocalDate.now();
        switch (timeRange) {
            case "week":
                return now.minusWeeks(1);
            case "month":
                return now.minusMonths(1);
            case "year":
                return now.minusYears(1);
            default:
                return now.minusDays(7);
        }
    }

    private LocalDateTime getStartTimeFromTimeRange(String timeRange) {
        LocalDateTime now = LocalDateTime.now();
        switch (timeRange) {
            case "day":
                return now.minusDays(1);
            case "week":
                return now.minusWeeks(1);
            case "month":
                return now.minusMonths(1);
            default:
                return now.minusDays(1);
        }
    }
}

backend\src\main\java\com\campus\waste\service\impl\KnowledgeServiceImpl.java

package com.campus.waste.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.campus.waste.entity.KnowledgeArticle;
import com.campus.waste.entity.FAQ;
import com.campus.waste.mapper.KnowledgeArticleMapper;
import com.campus.waste.mapper.FAQMapper;
import com.campus.waste.model.vo.KnowledgeArticleVO;
import com.campus.waste.model.vo.FAQVO;
import com.campus.waste.service.KnowledgeService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class KnowledgeServiceImpl implements KnowledgeService {

    private final KnowledgeArticleMapper articleMapper;
    private final FAQMapper faqMapper;

    public KnowledgeServiceImpl(KnowledgeArticleMapper articleMapper, FAQMapper faqMapper) {
        this.articleMapper = articleMapper;
        this.faqMapper = faqMapper;
    }

    @Override
    public IPage<KnowledgeArticleVO> getArticleList(Long categoryId, Integer pageNum, Integer pageSize) {
        Page<KnowledgeArticle> page = new Page<>(pageNum, pageSize);
        
        LambdaQueryWrapper<KnowledgeArticle> wrapper = new LambdaQueryWrapper<KnowledgeArticle>()
            .eq(categoryId != null, KnowledgeArticle::getCategoryId, categoryId)
            .eq(KnowledgeArticle::getStatus, 1)
            .orderByDesc(KnowledgeArticle::getCreateTime);
            
        IPage<KnowledgeArticle> articlePage = articleMapper.selectPage(page, wrapper);
        
        return articlePage.convert(this::convertToArticleVO);
    }

    @Override
    public KnowledgeArticleVO getArticleDetail(Long articleId) {
        KnowledgeArticle article = articleMapper.selectById(articleId);
        if (article == null) {
            throw new RuntimeException("文章不存在");
        }
        return convertToArticleVO(article);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addArticle(KnowledgeArticle article) {
        articleMapper.insert(article);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateArticle(KnowledgeArticle article) {
        articleMapper.updateById(article);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteArticle(Long articleId) {
        articleMapper.deleteById(articleId);
    }

    @Override
    public List<FAQVO> getFAQList(Long categoryId) {
        LambdaQueryWrapper<FAQ> wrapper = new LambdaQueryWrapper<FAQ>()
            .eq(categoryId != null, FAQ::getCategoryId, categoryId)
            .orderByAsc(FAQ::getSortOrder);
            
        List<FAQ> faqs = faqMapper.selectList(wrapper);
        
        return faqs.stream()
                .map(this::convertToFAQVO)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addFAQ(FAQ faq) {
        faqMapper.insert(faq);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateFAQ(FAQ faq) {
        faqMapper.updateById(faq);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteFAQ(Long faqId) {
        faqMapper.deleteById(faqId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void incrementArticleViewCount(Long articleId) {
        articleMapper.incrementViewCount(articleId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void incrementFAQViewCount(Long faqId) {
        faqMapper.incrementViewCount(faqId);
    }

    private KnowledgeArticleVO convertToArticleVO(KnowledgeArticle article) {
        KnowledgeArticleVO vo = new KnowledgeArticleVO();
        BeanUtils.copyProperties(article, vo);
        return vo;
    }

    private FAQVO convertToFAQVO(FAQ faq) {
        FAQVO vo = new FAQVO();
        BeanUtils.copyProperties(faq, vo);
        return vo;
    }
}

backend\src\main\java\com\campus\waste\service\impl\PointsServiceImpl.java

package com.campus.waste.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.campus.waste.entity.*;
import com.campus.waste.mapper.*;
import com.campus.waste.model.vo.*;
import com.campus.waste.service.PointsService;
import com.campus.waste.service.AchievementService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.BeanUtils;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class PointsServiceImpl implements PointsService {

    private final UserPointsMapper userPointsMapper;
    private final PointsRecordMapper pointsRecordMapper;
    private final PointsProductMapper pointsProductMapper;
    private final PointsExchangeMapper pointsExchangeMapper;
    private final AchievementService achievementService;

    public PointsServiceImpl(UserPointsMapper userPointsMapper,
                           PointsRecordMapper pointsRecordMapper,
                           PointsProductMapper pointsProductMapper,
                           PointsExchangeMapper pointsExchangeMapper,
                           AchievementService achievementService) {
        this.userPointsMapper = userPointsMapper;
        this.pointsRecordMapper = pointsRecordMapper;
        this.pointsProductMapper = pointsProductMapper;
        this.pointsExchangeMapper = pointsExchangeMapper;
        this.achievementService = achievementService;
    }

    @Override
    public UserPointsVO getUserPoints(Long userId) {
        UserPoints userPoints = getUserPointsEntity(userId);
        UserPointsVO vo = new UserPointsVO();
        BeanUtils.copyProperties(userPoints, vo);
        return vo;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addPoints(Long userId, Integer points, Integer type, Long sourceId, String sourceType, String description) {
        // 更新用户积分
        UserPoints userPoints = getUserPointsEntity(userId);
        userPoints.setTotalPoints(userPoints.getTotalPoints() + points);
        userPoints.setAvailablePoints(userPoints.getAvailablePoints() + points);
        userPointsMapper.updateById(userPoints);

        // 记录积分变动
        PointsRecord record = new PointsRecord();
        record.setUserId(userId);
        record.setPoints(points);
        record.setType(type);
        record.setSourceId(sourceId);
        record.setSourceType(sourceType);
        record.setDescription(description);
        pointsRecordMapper.insert(record);

        // 检查并更新等级
        checkAndUpdateLevel(userId);

        // 检查并更新成就
        achievementService.checkAndUpdateAchievements(userId, 2, userPoints.getTotalPoints());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean deductPoints(Long userId, Integer points, Integer type, Long sourceId, String sourceType, String description) {
        UserPoints userPoints = getUserPointsEntity(userId);
        if (userPoints.getAvailablePoints() < points) {
            return false;
        }

        userPoints.setAvailablePoints(userPoints.getAvailablePoints() - points);
        userPointsMapper.updateById(userPoints);

        PointsRecord record = new PointsRecord();
        record.setUserId(userId);
        record.setPoints(-points);
        record.setType(type);
        record.setSourceId(sourceId);
        record.setSourceType(sourceType);
        record.setDescription(description);
        pointsRecordMapper.insert(record);

        return true;
    }

    @Override
    public IPage<PointsRecordVO> getPointsRecords(Long userId, Integer type, Integer pageNum, Integer pageSize) {
        Page<PointsRecord> page = new Page<>(pageNum, pageSize);
        
        LambdaQueryWrapper<PointsRecord> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PointsRecord::getUserId, userId)
               .eq(type != null, PointsRecord::getType, type)
               .orderByDesc(PointsRecord::getCreateTime);
        
        IPage<PointsRecord> recordPage = pointsRecordMapper.selectPage(page, wrapper);
        return recordPage.convert(this::convertToPointsRecordVO);
    }

    @Override
    public List<PointsProductVO> getProductList(Boolean includeOffShelf) {
        LambdaQueryWrapper<PointsProduct> wrapper = new LambdaQueryWrapper<>();
        if (!includeOffShelf) {
            wrapper.eq(PointsProduct::getStatus, 1)
                   .ge(PointsProduct::getEndTime, LocalDateTime.now())
                   .le(PointsProduct::getStartTime, LocalDateTime.now());
        }
        wrapper.orderByAsc(PointsProduct::getPoints);
        
        List<PointsProduct> products = pointsProductMapper.selectList(wrapper);
        return products.stream()
                .map(this::convertToPointsProductVO)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void exchangeProduct(Long userId, Long productId, Integer quantity, String receiverName, String receiverPhone, String receiverAddress) {
        // 检查商品
        PointsProduct product = pointsProductMapper.selectById(productId);
        if (product == null || product.getStatus() != 1 || product.getStock() < quantity) {
            throw new RuntimeException("商品不可兑换");
        }

        // 检查兑换限制
        Integer exchangedCount = pointsExchangeMapper.selectCount(
            new LambdaQueryWrapper<PointsExchange>()
                .eq(PointsExchange::getUserId, userId)
                .eq(PointsExchange::getProductId, productId)
        );
        if (exchangedCount + quantity > product.getExchangeLimit()) {
            throw new RuntimeException("超出兑换限制");
        }

        // 扣减积分
        int totalPoints = product.getPoints() * quantity;
        boolean success = deductPoints(userId, totalPoints, 2, productId, "product_exchange", "兑换商品: " + product.getName());
        if (!success) {
            throw new RuntimeException("积分不足");
        }

        // 扣减库存
        product.setStock(product.getStock() - quantity);
        pointsProductMapper.updateById(product);

        // 创建兑换记录
        PointsExchange exchange = new PointsExchange();
        exchange.setUserId(userId);
        exchange.setProductId(productId);
        exchange.setPointsCost(totalPoints);
        exchange.setQuantity(quantity);
        exchange.setStatus(0);
        exchange.setReceiverName(receiverName);
        exchange.setReceiverPhone(receiverPhone);
        exchange.setReceiverAddress(receiverAddress);
        pointsExchangeMapper.insert(exchange);
    }

    @Override
    public IPage<PointsExchangeVO> getExchangeRecords(Long userId, Integer status, Integer pageNum, Integer pageSize) {
        Page<PointsExchange> page = new Page<>(pageNum, pageSize);
        
        LambdaQueryWrapper<PointsExchange> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(PointsExchange::getUserId, userId)
               .eq(status != null, PointsExchange::getStatus, status)
               .orderByDesc(PointsExchange::getCreateTime);
        
        IPage<PointsExchange> exchangePage = pointsExchangeMapper.selectPage(page, wrapper);
        return exchangePage.convert(this::convertToPointsExchangeVO);
    }

    @Override
    public List<PointsRankingVO> getPointsRanking(String timeRange) {
        return userPointsMapper.getPointsRanking(timeRange);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateExchangeStatus(Long exchangeId, Integer status) {
        PointsExchange exchange = new PointsExchange();
        exchange.setId(exchangeId);
        exchange.setStatus(status);
        pointsExchangeMapper.updateById(exchange);
    }

    @Override
    public void checkAndUpdateLevel(Long userId) {
        UserPoints userPoints = getUserPointsEntity(userId);
        int newLevel = calculateLevel(userPoints.getTotalPoints());
        
        if (newLevel != userPoints.getLevel()) {
            userPoints.setLevel(newLevel);
            userPointsMapper.updateById(userPoints);
        }
    }

    private UserPoints getUserPointsEntity(Long userId) {
        UserPoints userPoints = userPointsMapper.selectOne(
            new LambdaQueryWrapper<UserPoints>()
                .eq(UserPoints::getUserId, userId)
        );
        
        if (userPoints == null) {
            userPoints = new UserPoints();
            userPoints.setUserId(userId);
            userPoints.setTotalPoints(0);
            userPoints.setAvailablePoints(0);
            userPoints.setLevel(1);
            userPointsMapper.insert(userPoints);
        }
        
        return userPoints;
    }

    private int calculateLevel(int totalPoints) {
        if (totalPoints < 100) return 1;
        if (totalPoints < 500) return 2;
        if (totalPoints < 2000) return 3;
        if (totalPoints < 5000) return 4;
        if (totalPoints < 10000) return 5;
        return 6;
    }

    private PointsRecordVO convertToPointsRecordVO(PointsRecord record) {
        PointsRecordVO vo = new PointsRecordVO();
        BeanUtils.copyProperties(record, vo);
        return vo;
    }

    private PointsProductVO convertToPointsProductVO(PointsProduct product) {
        PointsProductVO vo = new PointsProductVO();
        BeanUtils.copyProperties(product, vo);
        return vo;
    }

    private PointsExchangeVO convertToPointsExchangeVO(PointsExchange exchange) {
        PointsExchangeVO vo = new PointsExchangeVO();
        BeanUtils.copyProperties(exchange, vo);
        
        // 获取商品信息
        PointsProduct product = pointsProductMapper.selectById(exchange.getProductId());
        if (product != null) {
            vo.setProductName(product.getName());
            vo.setProductImage(product.getImageUrl());
        }
        
        return vo;
    }
}

backend\src\main\java\com\campus\waste\service\impl\UserServiceImpl.java

package com.campus.waste.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.campus.waste.entity.User;
import com.campus.waste.mapper.UserMapper;
import com.campus.waste.model.dto.UserDTO;
import com.campus.waste.model.vo.UserVO;
import com.campus.waste.service.UserService;
import com.campus.waste.exception.BusinessException;
import org.springframework.beans.BeanUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    private final PasswordEncoder passwordEncoder;

    public UserServiceImpl(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void register(UserDTO userDTO) {
        // 验证用户名是否已存在
        if (isUsernameExists(userDTO.getUsername())) {
            throw new BusinessException("用户名已存在");
        }

        User user = new User();
        BeanUtils.copyProperties(userDTO, user);
        // 加密密码
        user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
        // 设置默认状态
        user.setStatus(0);
        
        save(user);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserInfo(UserDTO userDTO) {
        User user = getById(userDTO.getId());
        if (user == null) {
            throw new BusinessException("用户不存在");
        }

        // 如果修改了用户名,需要验证新用户名是否存在
        if (!user.getUsername().equals(userDTO.getUsername()) && isUsernameExists(userDTO.getUsername())) {
            throw new BusinessException("用户名已存在");
        }

        BeanUtils.copyProperties(userDTO, user);
        // 如果密码不为空,则更新密码
        if (userDTO.getPassword() != null && !userDTO.getPassword().isEmpty()) {
            user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
        }
        
        updateById(user);
    }

    @Override
    public UserVO getUserInfo(Long userId) {
        User user = getById(userId);
        if (user == null) {
            throw new BusinessException("用户不存在");
        }
        return convertToVO(user);
    }

    @Override
    public List<UserVO> getUserList(Integer userType) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        if (userType != null) {
            wrapper.eq(User::getUserType, userType);
        }
        return list(wrapper).stream()
                .map(this::convertToVO)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserStatus(Long userId, Integer status) {
        User user = getById(userId);
        if (user == null) {
            throw new BusinessException("用户不存在");
        }
        
        User updateUser = new User();
        updateUser.setId(userId);
        updateUser.setStatus(status);
        updateById(updateUser);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteUser(Long userId) {
        User user = getById(userId);
        if (user == null) {
            throw new BusinessException("用户不存在");
        }
        
        // 逻辑删除
        removeById(userId);
    }

    private boolean isUsernameExists(String username) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        return count(wrapper) > 0;
    }

    private UserVO convertToVO(User user) {
        UserVO vo = new UserVO();
        BeanUtils.copyProperties(user, vo);
        // 清除敏感信息
        vo.setPassword(null);
        return vo;
    }
}

backend\src\main\java\com\campus\waste\service\impl\WasteClassificationServiceImpl.java

package com.campus.waste.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.campus.waste.entity.WasteCategory;
import com.campus.waste.entity.WasteItem;
import com.campus.waste.mapper.WasteCategoryMapper;
import com.campus.waste.mapper.WasteItemMapper;
import com.campus.waste.model.dto.ImageRecognitionDTO;
import com.campus.waste.model.vo.WasteCategoryVO;
import com.campus.waste.model.vo.WasteItemVO;
import com.campus.waste.service.WasteClassificationService;
import com.campus.waste.service.ImageRecognitionService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;
import java.util.stream.Collectors;

@Service
public class WasteClassificationServiceImpl implements WasteClassificationService {

    private final WasteCategoryMapper categoryMapper;
    private final WasteItemMapper itemMapper;
    private final ImageRecognitionService imageRecognitionService;

    public WasteClassificationServiceImpl(WasteCategoryMapper categoryMapper,
                                        WasteItemMapper itemMapper,
                                        ImageRecognitionService imageRecognitionService) {
        this.categoryMapper = categoryMapper;
        this.itemMapper = itemMapper;
        this.imageRecognitionService = imageRecognitionService;
    }

    @Override
    public List<WasteCategoryVO> getAllCategories() {
        List<WasteCategory> categories = categoryMapper.selectList(
            new LambdaQueryWrapper<WasteCategory>()
                .orderByAsc(WasteCategory::getSortOrder)
        );
        
        return categories.stream()
                .map(this::convertToCategoryVO)
                .collect(Collectors.toList());
    }

    @Override
    public List<WasteItemVO> getItemsByCategory(Long categoryId) {
        List<WasteItem> items = itemMapper.selectList(
            new LambdaQueryWrapper<WasteItem>()
                .eq(WasteItem::getCategoryId, categoryId)
        );
        
        return items.stream()
                .map(this::convertToItemVO)
                .collect(Collectors.toList());
    }

    @Override
    public List<WasteItemVO> searchItems(String keyword) {
        List<WasteItem> items = itemMapper.selectList(
            new LambdaQueryWrapper<WasteItem>()
                .like(WasteItem::getItemName, keyword)
                .or()
                .like(WasteItem::getKeywords, keyword)
        );
        
        return items.stream()
                .map(this::convertToItemVO)
                .collect(Collectors.toList());
    }

    @Override
    public ImageRecognitionDTO recognizeImage(MultipartFile image, Long userId) {
        return imageRecognitionService.recognizeWaste(image, userId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addCategory(WasteCategory category) {
        categoryMapper.insert(category);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addItem(WasteItem item) {
        itemMapper.insert(item);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCategory(WasteCategory category) {
        categoryMapper.updateById(category);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateItem(WasteItem item) {
        itemMapper.updateById(item);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteCategory(Long categoryId) {
        // 检查是否存在关联的物品
        long count = itemMapper.selectCount(
            new LambdaQueryWrapper<WasteItem>()
                .eq(WasteItem::getCategoryId, categoryId)
        );
        
        if (count > 0) {
            throw new RuntimeException("该分类下存在物品,无法删除");
        }
        
        categoryMapper.deleteById(categoryId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteItem(Long itemId) {
        itemMapper.deleteById(itemId);
    }

    private WasteCategoryVO convertToCategoryVO(WasteCategory category) {
        WasteCategoryVO vo = new WasteCategoryVO();
        BeanUtils.copyProperties(category, vo);
        return vo;
    }

    private WasteItemVO convertToItemVO(WasteItem item) {
        WasteItemVO vo = new WasteItemVO();
        BeanUtils.copyProperties(item, vo);
        
        // 获取分类信息
        WasteCategory category = categoryMapper.selectById(item.getCategoryId());
        if (category != null) {
            vo.setCategoryName(category.getCategoryName());
        }
        
        return vo;
    }
}

backend\src\main\resources\application.yml

server:
  port: 8080
  servlet:
    context-path: /api

spring:
  application:
    name: campus-waste-classification
  
  # 数据库配置
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/campus_waste?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: root
    password: 123456
    druid:
      initial-size: 5
      min-idle: 10
      max-active: 20
      
  # Redis配置
  redis:
    host: localhost
    port: 6379
    database: 0
    timeout: 10s
    lettuce:
      pool:
        min-idle: 0
        max-idle: 8
        max-active: 8
        max-wait: -1ms

# MyBatis Plus配置
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml
  type-aliases-package: com.campus.waste.entity
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  global-config:
    db-config:
      logic-delete-field: delFlag
      logic-delete-value: 1
      logic-not-delete-value: 0
      id-type: auto

# JWT配置
jwt:
  secret: campus-waste-classification-secret
  expiration: 604800 # 7天,单位秒
  token-start-with: "Bearer "

backend\src\main\resources\db\data_analysis.sql

-- 垃圾分类数据统计表
CREATE TABLE waste_statistics (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    waste_type VARCHAR(50) NOT NULL COMMENT '垃圾类型',
    weight DECIMAL(10,2) NOT NULL COMMENT '重量(kg)',
    count INT NOT NULL COMMENT '数量',
    correct_rate DECIMAL(5,2) NOT NULL COMMENT '正确率(%)',
    collection_time DATE NOT NULL COMMENT '统计日期',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) COMMENT '垃圾分类数据统计';

-- 投放行为分析表
CREATE TABLE disposal_behavior (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL COMMENT '用户ID',
    waste_type VARCHAR(50) NOT NULL COMMENT '垃圾类型',
    disposal_time DATETIME NOT NULL COMMENT '投放时间',
    location VARCHAR(100) NOT NULL COMMENT '投放地点',
    weight DECIMAL(10,2) NOT NULL COMMENT '重量(kg)',
    is_correct TINYINT(1) NOT NULL COMMENT '是否正确分类',
    error_type VARCHAR(50) COMMENT '错误类型',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) COMMENT '投放行为分析';

-- 效果评估报告表
CREATE TABLE effectiveness_report (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    report_date DATE NOT NULL COMMENT '报告日期',
    total_users INT NOT NULL COMMENT '总用户数',
    active_users INT NOT NULL COMMENT '活跃用户数',
    total_weight DECIMAL(10,2) NOT NULL COMMENT '总重量(kg)',
    correct_rate DECIMAL(5,2) NOT NULL COMMENT '整体正确率(%)',
    reduction_rate DECIMAL(5,2) NOT NULL COMMENT '减少率(%)',
    satisfaction_rate DECIMAL(5,2) NOT NULL COMMENT '满意度(%)',
    main_problems TEXT COMMENT '主要问题',
    suggestions TEXT COMMENT '改进建议',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) COMMENT '效果评估报告';

-- 趋势预测表
CREATE TABLE trend_prediction (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    prediction_date DATE NOT NULL COMMENT '预测日期',
    waste_type VARCHAR(50) NOT NULL COMMENT '垃圾类型',
    predicted_weight DECIMAL(10,2) NOT NULL COMMENT '预测重量(kg)',
    predicted_count INT NOT NULL COMMENT '预测数量',
    confidence_level DECIMAL(5,2) NOT NULL COMMENT '置信度(%)',
    influencing_factors TEXT COMMENT '影响因素',
    create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) COMMENT '趋势预测';

-- 添加索引
CREATE INDEX idx_waste_statistics_date ON waste_statistics(collection_time);
CREATE INDEX idx_disposal_behavior_user ON disposal_behavior(user_id);
CREATE INDEX idx_disposal_behavior_time ON disposal_behavior(disposal_time);
CREATE INDEX idx_effectiveness_report_date ON effectiveness_report(report_date);
CREATE INDEX idx_trend_prediction_date ON trend_prediction(prediction_date);

backend\src\main\resources\db\points_reward.sql

-- 用户积分表
CREATE TABLE IF NOT EXISTS user_points (
    id BIGINT AUTO_INCREMENT COMMENT '主键ID',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    total_points INT NOT NULL DEFAULT 0 COMMENT '总积分',
    available_points INT NOT NULL DEFAULT 0 COMMENT '可用积分',
    level INT NOT NULL DEFAULT 1 COMMENT '用户等级',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (id),
    UNIQUE KEY uk_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户积分表';

-- 积分记录表
CREATE TABLE IF NOT EXISTS points_record (
    id BIGINT AUTO_INCREMENT COMMENT '主键ID',
    user_id BIGINT NOT NULL COMMENT '用户ID',
    points INT NOT NULL COMMENT '积分变动值',
    type TINYINT NOT NULL COMMENT '类型(1:投放奖励 2:兑换消费 3:活动奖励)',
    source_id BIGINT COMMENT '来源ID',
    source_type VARCHAR(50) COMMENT '来源类型',
    description VARCHAR(255) COMMENT '描述',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    PRIMARY KEY (id),
    KEY idx_user_id (user_id),
    KEY idx_create_time (create_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分记录表';

-- 积分商城商品表
CREATE TABLE IF NOT EXISTS points_product (
    id BIGINT AUTO_INCREMENT COMMENT '主键ID',
    name VARCHAR(100) NOT NULL COMMENT '商品名称',
    description TEXT COMMENT '商品描述',
    image_url VARCHAR(255) COMMENT '商品图片',
    points INT NOT NULL COMMENT '所需积分',
    stock INT NOT NULL DEFAULT 0 COMMENT '库存数量',
    total_stock INT NOT NULL DEFAULT 0 COMMENT '总库存',
    exchange_limit INT DEFAULT 1 COMMENT '每人限兑数量',
    status TINYINT DEFAULT 1 COMMENT '状态(0:下架 1:上架)',
    start_time DATETIME COMMENT '开始时间',
    end_time DATETIME COMMENT '结束时间',
    create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
    PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分商城商品表';


源码下载地址:源码下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天天进步2015

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值