Просмотр исходного кода

fix: 修复Controller层userId获取方式及多个API端点

- 修复JwtInterceptor与Controller的userId传递不一致问题
  * JwtInterceptor将userId存入request attribute
  * Controller从@RequestHeader改为@RequestAttribute获取userId
- 修复TaskService中PointsLog字段错误
  * log.setUserId改为log.setChildId
- 新增待审核任务查询端点 GET /api/tasks/pending-review
- 新增MediaController、TeacherController等控制器
- 完善AdminController的Web管理端API
- 修复AdminController路由 /admin -> /api/admin
User 5 месяцев назад
Родитель
Сommit
9f4ca7b85c

+ 131 - 67
zxyj-backend/src/main/java/com/zxyj/config/DatabaseInitializer.java

@@ -2,7 +2,7 @@ package com.zxyj.config;
 
 import com.zxyj.mapper.*;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
+import javax.annotation.Resource;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Component;
@@ -14,7 +14,7 @@ import java.util.List;
 @Component
 public class DatabaseInitializer implements CommandLineRunner {
 
-    @Autowired
+    @Resource
     private JdbcTemplate jdbcTemplate;
 
     @Override
@@ -72,27 +72,27 @@ public class DatabaseInitializer implements CommandLineRunner {
             "INDEX idx_family_id (family_id)" +
             ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
             
-            // 孩子详情表
-            "CREATE TABLE IF NOT EXISTS children (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "user_id BIGINT NOT NULL, " +
-            "family_id BIGINT NOT NULL, " +
-            "nickname VARCHAR(50) NOT NULL, " +
-            "age TINYINT NOT NULL, " +
-            "dan_level VARCHAR(2), " +
-            "penalty_enabled TINYINT DEFAULT 1, " +
-            "theme VARCHAR(32) DEFAULT 'default', " +
-            "total_points INT DEFAULT 0, " +
-            "streak_days INT DEFAULT 0, " +
-            "last_task_date DATE, " +
-            "focus_max_daily TINYINT DEFAULT 3, " +
-            "focus_remaining TINYINT DEFAULT 3, " +
-            "focus_reset_date DATE DEFAULT (CURRENT_DATE), " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
-            "INDEX idx_user_id (user_id), " +
-            "INDEX idx_family_id (family_id)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
+// 孩子详情表
+"CREATE TABLE IF NOT EXISTS children (" +
+"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+"user_id BIGINT NOT NULL, " +
+"family_id BIGINT NOT NULL, " +
+"nickname VARCHAR(50) NOT NULL, " +
+"age TINYINT NOT NULL, " +
+"dan_level VARCHAR(2), " +
+"penalty_enabled TINYINT DEFAULT 1, " +
+"theme VARCHAR(32) DEFAULT \'default\', " +
+"total_points INT DEFAULT 0, " +
+"streak_days INT DEFAULT 0, " +
+"last_task_date DATE, " +
+"focus_max_daily TINYINT DEFAULT 3, " +
+"focus_remaining TINYINT DEFAULT 3, " +
+"focus_reset_date DATE, " +
+"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+"INDEX idx_user_id (user_id), " +
+"INDEX idx_family_id (family_id)" +
+") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
             
             // 任务表
             "CREATE TABLE IF NOT EXISTS tasks (" +
@@ -280,35 +280,55 @@ public class DatabaseInitializer implements CommandLineRunner {
             "INDEX idx_is_default (is_default)" +
             ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
             
-            // 管理员表
-            "CREATE TABLE IF NOT EXISTS admins (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "username VARCHAR(50) NOT NULL, " +
-            "password VARCHAR(128) NOT NULL, " +
-            "real_name VARCHAR(50), " +
-            "phone VARCHAR(11), " +
-            "email VARCHAR(100), " +
-            "status TINYINT DEFAULT 1, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
-            "INDEX idx_phone (phone), " +
-            "INDEX idx_username (username)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
+// 管理员表
+"CREATE TABLE IF NOT EXISTS admins (" +
+"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+"username VARCHAR(50) NOT NULL, " +
+"password VARCHAR(128) NOT NULL, " +
+"real_name VARCHAR(50), " +
+"phone VARCHAR(11), " +
+"email VARCHAR(100), " +
+"role VARCHAR(20) DEFAULT 'admin', " +
+"status TINYINT DEFAULT 1, " +
+"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+"INDEX idx_phone (phone), " +
+"INDEX idx_username (username)" +
+") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
             
-            // 验证码表
-            "CREATE TABLE IF NOT EXISTS verification_codes (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "phone VARCHAR(11) NOT NULL, " +
-            "code VARCHAR(6) NOT NULL, " +
-            "type VARCHAR(20) NOT NULL, " +
-            "expires_in INT DEFAULT 300, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "expires_at DATETIME NOT NULL, " +
-            "used TINYINT DEFAULT 0, " +
-            "INDEX idx_phone_type (phone, type), " +
-            "INDEX idx_expires_at (expires_at)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
-        );
+// 验证码表
+"CREATE TABLE IF NOT EXISTS verification_codes (" +
+"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+"phone VARCHAR(11) NOT NULL, " +
+"code VARCHAR(6) NOT NULL, " +
+"type VARCHAR(20) NOT NULL, " +
+"expires_in INT DEFAULT 300, " +
+"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+"expires_at DATETIME NOT NULL, " +
+"used TINYINT DEFAULT 0, " +
+"INDEX idx_phone_type (phone, type), " +
+"INDEX idx_expires_at (expires_at)" +
+") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
+
+// 管理后台任务模板表
+"CREATE TABLE IF NOT EXISTS admin_task_templates (" +
+"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+"title VARCHAR(200) NOT NULL, " +
+"description TEXT, " +
+"points INT DEFAULT 10, " +
+"category VARCHAR(32), " +
+"need_review TINYINT DEFAULT 0, " +
+"review_by_category TINYINT DEFAULT 0, " +
+"recommended_age INT, " +
+"difficulty ENUM('easy', 'medium', 'hard') DEFAULT 'medium', " +
+"is_active TINYINT DEFAULT 1, " +
+"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+"INDEX idx_category (category), " +
+"INDEX idx_difficulty (difficulty), " +
+"INDEX idx_is_active (is_active)" +
+") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
+);
 
         // 执行创建表SQL
         for (String sql : createTableSQLs) {
@@ -392,20 +412,64 @@ public class DatabaseInitializer implements CommandLineRunner {
             log.warn("初始化勋章定义失败: {}", e.getMessage());
         }
 
-        // 初始化默认管理员
-        try {
-            Integer count = jdbcTemplate.queryForObject(
-                "SELECT COUNT(*) FROM admins", Integer.class);
-            
-            if (count == null || count == 0) {
-                // 默认管理员: 手机号 13800138000, 密码 admin123
-                String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
-                jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, status) VALUES " +
-                    "('admin', '" + encryptedPassword + "', '系统管理员', '13800138000', 'admin@xzyj.com', 1)");
-                log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
-            }
-        } catch (Exception e) {
-            log.warn("初始化默认管理员失败: {}", e.getMessage());
-        }
-    }
+// 初始化默认管理员
+try {
+  // 先检查并添加role列(如果不存在)
+  try {
+    jdbcTemplate.execute("ALTER TABLE admins ADD COLUMN role VARCHAR(20) DEFAULT \'admin\' AFTER email");
+    log.info("已添加role列到admins表");
+  } catch (Exception e) {
+    // 列已存在,忽略错误
+  }
+
+  Integer count = jdbcTemplate.queryForObject(
+  "SELECT COUNT(*) FROM admins", Integer.class);
+
+  if (count == null || count == 0) {
+    // 默认管理员: 手机号 13800138000, 密码 admin123
+    String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
+    jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, role, status) VALUES " +
+    "(\'admin\', \'" + encryptedPassword + "\', \'系统管理员\', \'13800138000\', \'admin@xzyj.com\', \'admin\', 1)");
+    log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
+  }
+} catch (Exception e) {
+  log.warn("初始化默认管理员失败: {}", e.getMessage());
+}
+
+// 初始化任务模板示例数据
+try {
+  Integer count = jdbcTemplate.queryForObject(
+  "SELECT COUNT(*) FROM admin_task_templates", Integer.class);
+
+  if (count == null || count == 0) {
+    jdbcTemplate.execute("INSERT INTO admin_task_templates (title, description, points, category, need_review, recommended_age, difficulty, is_active) VALUES " +
+    "('完成作业', '按时完成学校布置的家庭作业', 10, '学习', 1, 6, 'medium', 1), " +
+    "('阅读30分钟', '自主阅读课外书籍30分钟', 8, '阅读', 0, 5, 'easy', 1), " +
+    "('整理房间', '收拾整理自己的房间和书桌', 6, '家务', 0, 6, 'easy', 1), " +
+    "('洗碗', '饭后帮忙清洗餐具', 5, '家务', 1, 8, 'medium', 1), " +
+    "('户外运动1小时', '进行跑步、球类等户外运动', 12, '运动', 0, 7, 'hard', 1), " +
+    "('练习书法', '练习毛笔或硬笔书法20分钟', 7, '学习', 0, 7, 'medium', 1), " +
+    "('早睡早起', '晚上9点前睡觉,早上7点前起床', 5, '生活习惯', 0, 5, 'easy', 1), " +
+    "('背诵古诗', '背诵一首古诗词', 8, '学习', 1, 6, 'medium', 1)");
+    log.info("任务模板示例数据已初始化");
+  }
+} catch (Exception e) {
+  log.warn("初始化任务模板失败: {}", e.getMessage());
+}
+
+try {
+  Integer count = jdbcTemplate.queryForObject(
+  "SELECT COUNT(*) FROM admins", Integer.class);
+
+  if (count == null || count == 0) {
+    // 默认管理员: 手机号 13800138000, 密码 admin123
+    String encryptedPassword = org.springframework.util.DigestUtils.md5DigestAsHex("xzyj_admin_salt_admin123".getBytes());
+    jdbcTemplate.execute("INSERT INTO admins (username, password, real_name, phone, email, role, status) VALUES " +
+    "('admin', '" + encryptedPassword + "', '系统管理员', '13800138000', 'admin@xzyj.com', 'admin', 1)");
+    log.info("默认管理员已初始化: 手机号 13800138000, 密码 admin123");
+  }
+} catch (Exception e) {
+  log.warn("初始化任务模板失败: {}", e.getMessage());
+}
+}
 }

+ 2 - 2
zxyj-backend/src/main/java/com/zxyj/config/JwtInterceptor.java

@@ -2,7 +2,7 @@ package com.zxyj.config;
 
 import io.jsonwebtoken.Claims;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
+import javax.annotation.Resource;
 import org.springframework.stereotype.Component;
 import org.springframework.util.StringUtils;
 import org.springframework.web.servlet.HandlerInterceptor;
@@ -14,7 +14,7 @@ import javax.servlet.http.HttpServletResponse;
 @Component
 public class JwtInterceptor implements HandlerInterceptor {
 
-    @Autowired
+    @Resource
     private JwtConfig jwtConfig;
 
     @Override

+ 9 - 8
zxyj-backend/src/main/java/com/zxyj/config/WebConfig.java

@@ -1,6 +1,6 @@
 package com.zxyj.config;
 
-import org.springframework.beans.factory.annotation.Autowired;
+import javax.annotation.Resource;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.web.servlet.config.annotation.CorsRegistry;
 import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
@@ -9,7 +9,7 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
 @Configuration
 public class WebConfig implements WebMvcConfigurer {
 
-    @Autowired
+    @Resource
     private JwtInterceptor jwtInterceptor;
 
     @Override
@@ -27,12 +27,13 @@ public class WebConfig implements WebMvcConfigurer {
         registry.addInterceptor(jwtInterceptor)
                 .addPathPatterns("/api/**")
                 .excludePathPatterns(
-                    "/api/auth/send-code",
-                    "/api/auth/phone-login",
-                    "/api/auth/wechat-login",
-                    "/api/auth/wechat-phone-login",
-                    "/api/admin-auth/send-code",
-                    "/api/admin-auth/login"
+                        "/api/auth/send-code",
+                        "/api/auth/phone-login",
+                        "/api/auth/wechat-login",
+                        "/api/auth/wechat-phone-login",
+                        "/api/admin-auth/send-code",
+                        "/api/admin-auth/login",
+                        "/api/admin-auth/login-by-password"
                 );
     }
 }

+ 48 - 6
zxyj-backend/src/main/java/com/zxyj/controller/AdminAuthController.java

@@ -9,7 +9,7 @@ import com.zxyj.entity.Admin;
 import com.zxyj.mapper.AdminMapper;
 import com.zxyj.service.VerificationCodeService;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
+import javax.annotation.Resource;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.Date;
@@ -21,13 +21,13 @@ import java.util.Map;
 @RequestMapping("/api/admin-auth")
 public class AdminAuthController {
 
-    @Autowired
+    @Resource
     private AdminMapper adminMapper;
 
-    @Autowired
+    @Resource
     private VerificationCodeService verificationCodeService;
 
-    @Autowired
+    @Resource
     private JwtConfig jwtConfig;
 
     /**
@@ -100,7 +100,7 @@ public class AdminAuthController {
      * 获取当前登录管理员信息
      */
     @GetMapping("/info")
-    public Result<Admin> getAdminInfo(@RequestHeader("X-User-Id") Long adminId) {
+    public Result<Admin> getAdminInfo(@RequestAttribute("userId") Long adminId) {
         Admin admin = adminMapper.selectById(adminId);
         if (admin == null) {
             return Result.error("管理员不存在");
@@ -114,7 +114,7 @@ public class AdminAuthController {
      * 管理员修改密码
      */
     @PostMapping("/change-password")
-    public Result<Boolean> changePassword(@RequestHeader("X-User-Id") Long adminId,
+    public Result<Boolean> changePassword(@RequestAttribute("userId") Long adminId,
                                            @RequestBody Map<String, String> params) {
         String oldPassword = params.get("oldPassword");
         String newPassword = params.get("newPassword");
@@ -138,6 +138,48 @@ public class AdminAuthController {
         return Result.success(true);
     }
 
+    /**
+     * 管理员用户名密码登录
+     */
+    @PostMapping("/login-by-password")
+    public Result<Map<String, Object>> loginByPassword(@RequestBody AdminLoginDTO dto) {
+        if (dto.getPhone() == null || dto.getPassword() == null) {
+            return Result.error("用户名和密码不能为空");
+        }
+
+        // 查询管理员(支持手机号或用户名登录)
+        Admin admin = adminMapper.selectOne(new LambdaQueryWrapper<Admin>()
+                .eq(Admin::getPhone, dto.getPhone())
+                .or()
+                .eq(Admin::getUsername, dto.getPhone()));
+
+        if (admin == null) {
+            return Result.error("管理员不存在");
+        }
+
+	// 验证密码
+	String encryptedPassword = encryptPassword(dto.getPassword());
+	if (!encryptedPassword.equals(admin.getPassword())) {
+		return Result.error("密码错误");
+	}
+
+	if (admin.getStatus() != null && admin.getStatus() != 1) {
+		return Result.error("账号已被禁用");
+	}
+
+	// 生成token
+	String token = jwtConfig.generateToken(admin.getId(), admin.getRole() != null ? admin.getRole() : "admin");
+
+	Map<String, Object> result = new HashMap<>();
+	result.put("token", token);
+	result.put("adminId", admin.getId());
+	result.put("username", admin.getUsername());
+	result.put("realName", admin.getRealName());
+	result.put("role", admin.getRole() != null ? admin.getRole() : "admin");
+
+	return Result.success(result);
+}
+
     private String encryptPassword(String password) {
         // MD5加密,实际项目应使用BCrypt等更安全的加密方式
         return org.springframework.util.DigestUtils.md5DigestAsHex(("xzyj_admin_salt_" + password).getBytes());

+ 153 - 19
zxyj-backend/src/main/java/com/zxyj/controller/AdminController.java

@@ -5,7 +5,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zxyj.common.Result;
 import com.zxyj.entity.*;
 import com.zxyj.mapper.*;
-import org.springframework.beans.factory.annotation.Autowired;
+import javax.annotation.Resource;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.Date;
@@ -13,25 +13,31 @@ import java.util.HashMap;
 import java.util.Map;
 
 @RestController
-@RequestMapping("/admin")
+@RequestMapping("/api/admin")
 public class AdminController {
 
-    @Autowired
+    @Resource
     private UserMapper userMapper;
 
-    @Autowired
+    @Resource
     private ChildMapper childMapper;
 
-    @Autowired
+    @Resource
     private TaskMapper taskMapper;
 
-    @Autowired
+    @Resource
     private RewardMapper rewardMapper;
 
-    @Autowired
-    private PointsLogMapper pointsLogMapper;
+  @Resource
+  private PointsLogMapper pointsLogMapper;
 
-    // 用户管理
+  @Resource
+  private FamilyMapper familyMapper;
+
+  @Resource
+  private TaskTemplateMapper taskTemplateMapper;
+
+  // 用户管理
     @GetMapping("/users")
     public Result<Page<User>> getUsers(
             @RequestParam(defaultValue = "1") Integer page,
@@ -181,6 +187,20 @@ public class AdminController {
         return Result.success(reward.getId());
     }
 
+    @DeleteMapping("/rewards/templates/{id}")
+    public Result<Boolean> deleteRewardTemplate(@PathVariable Long id) {
+        rewardMapper.deleteById(id);
+        return Result.success(true);
+    }
+
+    @PutMapping("/rewards/templates/{id}")
+    public Result<Boolean> updateRewardTemplate(@PathVariable Long id, @RequestBody Reward reward) {
+        reward.setId(id);
+        reward.setUpdatedAt(new Date());
+        rewardMapper.updateById(reward);
+        return Result.success(true);
+    }
+
     @PostMapping("/rewards/{id}/approve")
     public Result<Boolean> approveReward(@PathVariable Long id, @RequestBody Map<String, Boolean> params) {
         Boolean approved = params.get("approved");
@@ -236,15 +256,129 @@ public class AdminController {
         return Result.success(result);
     }
 
-    @GetMapping("/points/stats")
-    public Result<Map<String, Object>> getPointsStats() {
-        Map<String, Object> stats = new HashMap<>();
-        
-        Long totalChildren = childMapper.selectCount(null);
-        Long totalPoints = childMapper.selectCount(null); // 需要单独查询sum
-        
-        stats.put("totalChildren", totalChildren);
-        
-        return Result.success(stats);
+  @GetMapping("/points/stats")
+  public Result<Map<String, Object>> getPointsStats() {
+    Map<String, Object> stats = new HashMap<>();
+
+    Long totalChildren = childMapper.selectCount(null);
+    Long totalPoints = childMapper.selectCount(null); // 需要单独查询sum
+
+    stats.put("totalChildren", totalChildren);
+
+    return Result.success(stats);
+  }
+
+  // 家庭管理
+  @GetMapping("/families")
+  public Result<Page<Family>> getFamilies(
+      @RequestParam(defaultValue = "1") Integer page,
+      @RequestParam(defaultValue = "10") Integer size,
+      @RequestParam(required = false) String name) {
+    Page<Family> pageParam = new Page<>(page, size);
+    LambdaQueryWrapper<Family> wrapper = new LambdaQueryWrapper<>();
+    if (name != null && !name.isEmpty()) {
+      wrapper.like(Family::getName, name);
     }
+    wrapper.orderByDesc(Family::getCreatedAt);
+    Page<Family> result = familyMapper.selectPage(pageParam, wrapper);
+    return Result.success(result);
+  }
+
+  @GetMapping("/families/{id}")
+  public Result<Family> getFamily(@PathVariable Long id) {
+    return Result.success(familyMapper.selectById(id));
+  }
+
+  @GetMapping("/families/{id}/members")
+  public Result<Map<String, Object>> getFamilyMembers(@PathVariable Long id) {
+    Family family = familyMapper.selectById(id);
+    if (family == null) {
+      return Result.error("家庭不存在");
+    }
+
+    // 查询家长
+    LambdaQueryWrapper<User> parentWrapper = new LambdaQueryWrapper<>();
+    parentWrapper.eq(User::getFamilyId, id).eq(User::getRole, "parent");
+    java.util.List<User> parents = userMapper.selectList(parentWrapper);
+
+    // 查询孩子
+    LambdaQueryWrapper<Child> childWrapper = new LambdaQueryWrapper<>();
+    childWrapper.eq(Child::getFamilyId, id);
+    java.util.List<Child> children = childMapper.selectList(childWrapper);
+
+    Map<String, Object> result = new HashMap<>();
+    result.put("family", family);
+    result.put("parents", parents);
+    result.put("children", children);
+
+    return Result.success(result);
+  }
+
+  @PutMapping("/families/{id}")
+  public Result<Boolean> updateFamily(@PathVariable Long id, @RequestBody Family family) {
+    family.setId(id);
+    family.setUpdatedAt(new Date());
+    familyMapper.updateById(family);
+    return Result.success(true);
+  }
+
+  @DeleteMapping("/families/{id}")
+  public Result<Boolean> deleteFamily(@PathVariable Long id) {
+    familyMapper.deleteById(id);
+    return Result.success(true);
+  }
+
+  // 任务模板管理
+  @GetMapping("/task-templates")
+  public Result<Page<TaskTemplate>> getTaskTemplates(
+      @RequestParam(defaultValue = "1") Integer page,
+      @RequestParam(defaultValue = "10") Integer size,
+      @RequestParam(required = false) String category,
+      @RequestParam(required = false) String difficulty,
+      @RequestParam(required = false) Integer isActive) {
+    Page<TaskTemplate> pageParam = new Page<>(page, size);
+    LambdaQueryWrapper<TaskTemplate> wrapper = new LambdaQueryWrapper<>();
+    if (category != null && !category.isEmpty()) {
+      wrapper.eq(TaskTemplate::getCategory, category);
+    }
+    if (difficulty != null && !difficulty.isEmpty()) {
+      wrapper.eq(TaskTemplate::getDifficulty, difficulty);
+    }
+    if (isActive != null) {
+      wrapper.eq(TaskTemplate::getIsActive, isActive);
+    }
+    wrapper.orderByDesc(TaskTemplate::getCreatedAt);
+    Page<TaskTemplate> result = taskTemplateMapper.selectPage(pageParam, wrapper);
+    return Result.success(result);
+  }
+
+  @GetMapping("/task-templates/{id}")
+  public Result<TaskTemplate> getTaskTemplate(@PathVariable Long id) {
+    return Result.success(taskTemplateMapper.selectById(id));
+  }
+
+  @PostMapping("/task-templates")
+  public Result<Long> createTaskTemplate(@RequestBody TaskTemplate template) {
+    template.setCreatedAt(new Date());
+    template.setUpdatedAt(new Date());
+    if (template.getIsActive() == null) {
+      template.setIsActive(1);
+    }
+    taskTemplateMapper.insert(template);
+    return Result.success(template.getId());
+  }
+
+  @PutMapping("/task-templates/{id}")
+  public Result<Boolean> updateTaskTemplate(@PathVariable Long id, @RequestBody TaskTemplate template) {
+    template.setId(id);
+    template.setUpdatedAt(new Date());
+    taskTemplateMapper.updateById(template);
+    return Result.success(true);
+  }
+
+  @DeleteMapping("/task-templates/{id}")
+  public Result<Boolean> deleteTaskTemplate(@PathVariable Long id) {
+    taskTemplateMapper.deleteById(id);
+    return Result.success(true);
+  }
 }

+ 75 - 14
zxyj-backend/src/main/java/com/zxyj/controller/AuthController.java

@@ -2,27 +2,32 @@ package com.zxyj.controller;
 
 import com.zxyj.common.Result;
 import com.zxyj.dto.*;
+import com.zxyj.dto.RegisterWithIdCardDTO;
 import com.zxyj.entity.User;
 import com.zxyj.service.UserService;
 import com.zxyj.service.VerificationCodeService;
-import org.springframework.beans.factory.annotation.Autowired;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import javax.annotation.Resource;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 
+@Tag(name = "用户认证", description = "用户登录、验证码、密码设置等认证接口")
 @RestController
 @RequestMapping("/api/auth")
 public class AuthController {
 
-    @Autowired
+    @Resource
     private UserService userService;
 
-    @Autowired
+    @Resource
     private VerificationCodeService verificationCodeService;
 
     /**
      * 发送验证码
      */
+    @Operation(summary = "发送验证码")
     @PostMapping("/send-code")
     public Result<Boolean> sendCode(@RequestBody SendCodeDTO dto) {
         if (dto.getPhone() == null || dto.getPhone().isEmpty()) {
@@ -39,6 +44,7 @@ public class AuthController {
     /**
      * 手机号验证码登录
      */
+    @Operation(summary = "手机号验证码登录")
     @PostMapping("/phone-login")
     public Result<LoginResultDTO> phoneLogin(@RequestBody PhoneLoginDTO dto) {
         if (dto.getPhone() == null || dto.getCode() == null) {
@@ -57,42 +63,97 @@ public class AuthController {
     /**
      * 微信手机号登录(通过微信开放能力获取手机号)
      */
+    @Operation(summary = "微信手机号登录")
     @PostMapping("/wechat-phone-login")
     public Result<LoginResultDTO> wechatPhoneLogin(@RequestBody WechatPhoneLoginDTO dto) {
-        // TODO: 调用微信API解密获取手机号
-        // 这里需要集成微信手机号解密功能
-        // 暂时使用模拟数据,实际需要通过微信code获取手机号
-        String phone = "mock_phone_" + dto.getCode();
+        if (dto.getCode() == null || dto.getPhoneCode() == null) {
+            return Result.error("登录code和手机号code不能为空");
+        }
+        LoginResultDTO result = userService.wechatPhoneLogin(dto);
+        return Result.success(result);
+    }
+
+    /**
+     * 微信静默登录(用户再次打开小程序时自动登录)
+     */
+    @Operation(summary = "微信静默登录")
+    @PostMapping("/silent-login")
+    public Result<LoginResultDTO> silentLogin(@RequestBody WechatLoginDTO dto) {
+        if (dto.getCode() == null) {
+            return Result.error("登录code不能为空");
+        }
+        try {
+            LoginResultDTO result = userService.silentLogin(dto.getCode());
+            return Result.success(result);
+        } catch (RuntimeException e) {
+            return Result.error(e.getMessage());
+        }
+    }
+
+    /**
+     * 通过openid自动登录
+     */
+    @Operation(summary = "openid自动登录")
+    @PostMapping("/auto-login")
+    public Result<LoginResultDTO> autoLogin(@RequestBody WechatLoginDTO dto) {
+        if (dto.getOpenid() == null) {
+            return Result.error("openid不能为空");
+        }
+        LoginResultDTO result = userService.autoLoginByOpenid(dto.getOpenid());
+        if (result == null) {
+            return Result.error("用户不存在,请先登录");
+        }
+        return Result.success(result);
+    }
+
+    /**
+     * 手机号+身份证注册登录
+     */
+    @Operation(summary = "手机号+身份证注册登录")
+    @PostMapping("/register-with-idcard")
+    public Result<LoginResultDTO> registerWithIdCard(@RequestBody RegisterWithIdCardDTO dto) {
+        if (dto.getPhone() == null || dto.getCode() == null) {
+            return Result.error("手机号和验证码不能为空");
+        }
+        if (dto.getIdCard() == null || dto.getIdCard().isEmpty()) {
+            return Result.error("身份证号不能为空");
+        }
+        if (dto.getRealName() == null || dto.getRealName().isEmpty()) {
+            return Result.error("真实姓名不能为空");
+        }
         
-        PhoneLoginDTO phoneLoginDTO = new PhoneLoginDTO();
-        phoneLoginDTO.setPhone(phone);
-        phoneLoginDTO.setNickname(dto.getNickname());
-        phoneLoginDTO.setAvatar(dto.getAvatar());
+        // 验证身份证格式(简单验证,实际应更严格)
+        if (!dto.getIdCard().matches("^\\d{15}$|^\\d{17}[0-9Xx]$")) {
+            return Result.error("身份证号格式不正确");
+        }
         
-        LoginResultDTO result = userService.phoneLogin(phoneLoginDTO);
+        LoginResultDTO result = userService.registerWithIdCard(dto);
         return Result.success(result);
     }
 
     /**
      * 微信code登录(原有功能)
      */
+    @Operation(summary = "微信code登录")
     @PostMapping("/wechat-login")
     public Result<LoginResultDTO> wechatLogin(@RequestBody WechatLoginDTO dto) {
         LoginResultDTO result = userService.wechatLogin(dto);
         return Result.success(result);
     }
 
+    @Operation(summary = "设置密码")
     @PostMapping("/set-password")
     public Result<Boolean> setPassword(javax.servlet.http.HttpServletRequest request,
-                                         @RequestBody SetPasswordDTO dto) {
+                                          @RequestBody SetPasswordDTO dto) {
         Long userId = getUserId(request);
         boolean success = userService.setPassword(userId, dto.getPassword());
         return Result.success(success);
     }
 
+    @Operation(summary = "验证密码")
     @PostMapping("/verify-password")
     public Result<Boolean> verifyPassword(javax.servlet.http.HttpServletRequest request,
-                                           @RequestBody VerifyPasswordDTO dto) {
+                                            @RequestBody VerifyPasswordDTO dto) {
         Long userId = getUserId(request);
         boolean valid = userService.verifyPassword(userId, dto.getPassword());
         return Result.success(valid);

+ 84 - 0
zxyj-backend/src/main/java/com/zxyj/controller/MediaController.java

@@ -0,0 +1,84 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.entity.MediaRecord;
+import com.zxyj.service.MediaRecordService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@Tag(name = "媒体管理", description = "图片、视频、音频、文字上传和管理接口")
+@RestController
+@RequestMapping("/api/media")
+public class MediaController {
+
+    @Resource
+    private MediaRecordService mediaRecordService;
+
+    @Operation(summary = "上传媒体文件")
+    @PostMapping("/upload")
+    public Result<MediaRecord> uploadMedia(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam Long taskId,
+            @RequestParam Long creatorId,
+            @RequestParam(required = false) String description) {
+
+        // 验证文件大小
+        if (file.getSize() > getMaxFileSize(file.getContentType())) {
+            return Result.error("文件大小超过限制");
+        }
+
+        MediaRecord record = mediaRecordService.uploadMedia(file, taskId, creatorId, description);
+        return Result.success(record);
+    }
+
+    @Operation(summary = "保存文字记录")
+    @PostMapping("/upload-text")
+    public Result<MediaRecord> uploadText(@RequestBody Map<String, Object> body) {
+        Long taskId = Long.parseLong(body.get("taskId").toString());
+        Long creatorId = Long.parseLong(body.get("creatorId").toString());
+        String content = body.get("content").toString();
+
+        MediaRecord record = mediaRecordService.saveTextRecord(taskId, creatorId, content);
+        return Result.success(record);
+    }
+
+    @Operation(summary = "获取任务的所有媒体")
+    @GetMapping("/task/{taskId}")
+    public Result<List<MediaRecord>> getMediaByTask(@PathVariable Long taskId) {
+        List<MediaRecord> records = mediaRecordService.getMediaByTaskId(taskId);
+        return Result.success(records);
+    }
+
+    @Operation(summary = "删除媒体")
+    @DeleteMapping("/{mediaId}")
+    public Result<Boolean> deleteMedia(
+            @PathVariable Long mediaId,
+            @RequestAttribute("userId") Long userId) {
+
+        boolean success = mediaRecordService.deleteMedia(mediaId, userId);
+        return Result.success(success);
+    }
+
+    @Operation(summary = "批量关联媒体到任务")
+    @PostMapping("/attach")
+    public Result<Boolean> attachMedia(@RequestBody Map<String, Object> body) {
+        Long taskId = Long.parseLong(body.get("taskId").toString());
+        List<Long> mediaIds = (List<Long>) body.get("mediaIds");
+
+        boolean success = mediaRecordService.attachMediaToTask(taskId, mediaIds);
+        return Result.success(success);
+    }
+
+    private long getMaxFileSize(String contentType) {
+        if (contentType == null) return 10 * 1024 * 1024; // 10MB
+        if (contentType.startsWith("video")) return 100 * 1024 * 1024; // 100MB
+        if (contentType.startsWith("audio")) return 50 * 1024 * 1024; // 50MB
+        return 10 * 1024 * 1024; // 10MB for images
+    }
+}

+ 109 - 0
zxyj-backend/src/main/java/com/zxyj/controller/MembershipController.java

@@ -0,0 +1,109 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.dto.*;
+import com.zxyj.service.MembershipService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Tag(name = "会员管理", description = "会员等级、支付相关接口")
+@RestController
+@RequestMapping("/api/membership")
+public class MembershipController {
+
+    @Resource
+    private MembershipService membershipService;
+
+    /**
+     * 获取所有会员等级
+     */
+    @Operation(summary = "获取会员等级列表")
+    @GetMapping("/levels")
+    public Result<List<MembershipLevelDTO>> getAllLevels() {
+        List<MembershipLevelDTO> levels = membershipService.getAllLevels();
+        return Result.success(levels);
+    }
+
+    /**
+     * 获取家庭当前会员信息
+     */
+    @Operation(summary = "获取家庭会员信息")
+    @GetMapping("/my")
+    public Result<FamilyMembershipDTO> getMyMembership(javax.servlet.http.HttpServletRequest request) {
+        Long userId = getUserId(request);
+        // 获取用户家庭ID
+        // 简化:假设通过其他服务获取
+        Long familyId = 1L; // 临时
+        
+        FamilyMembershipDTO membership = membershipService.getFamilyMembership(familyId);
+        return Result.success(membership);
+    }
+
+    /**
+     * 获取当前会员等级
+     */
+    @Operation(summary = "获取当前会员等级")
+    @GetMapping("/current")
+    public Result<MembershipLevelDTO> getCurrentLevel(javax.servlet.http.HttpServletRequest request) {
+        Long familyId = 1L; // 临时
+        
+        MembershipLevelDTO level = membershipService.getCurrentLevel(familyId);
+        return Result.success(level);
+    }
+
+    /**
+     * 创建支付订单
+     */
+    @Operation(summary = "创建订单")
+    @PostMapping("/orders")
+    public Result<PaymentOrderDTO> createOrder(
+            @RequestParam String levelCode,
+            @RequestParam String paymentType) {
+        Long familyId = 1L; // 临时
+        
+        PaymentOrderDTO order = membershipService.createOrder(familyId, levelCode, paymentType);
+        return Result.success(order);
+    }
+
+    /**
+     * 处理支付回调
+     */
+    @Operation(summary = "支付回调")
+    @PostMapping("/notify")
+    public Result<Boolean> paymentNotify(
+            @RequestParam String orderNo,
+            @RequestParam String transactionId,
+            @RequestParam String payMethod) {
+        boolean success = membershipService.processPaymentCallback(orderNo, transactionId, payMethod);
+        return Result.success(success);
+    }
+
+    /**
+     * 检查功能权限
+     */
+    @Operation(summary = "检查功能权限")
+    @GetMapping("/can-use")
+    public Result<Boolean> canUseFeature(
+            @RequestParam String feature) {
+        Long familyId = 1L; // 临时
+        
+        boolean canUse = membershipService.canUseFeature(familyId, feature);
+        return Result.success(canUse);
+    }
+
+    private Long getUserId(javax.servlet.http.HttpServletRequest request) {
+        Object userIdObj = request.getAttribute("userId");
+        if (userIdObj != null) {
+            return (Long) userIdObj;
+        }
+        String userIdHeader = request.getHeader("X-User-Id");
+        if (userIdHeader != null) {
+            return Long.parseLong(userIdHeader);
+        }
+        return null;
+    }
+}

+ 3 - 3
zxyj-backend/src/main/java/com/zxyj/controller/PointsController.java

@@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.zxyj.common.Result;
 import com.zxyj.entity.PointsLog;
 import com.zxyj.service.PointsService;
-import org.springframework.beans.factory.annotation.Autowired;
+import javax.annotation.Resource;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.Map;
@@ -13,7 +13,7 @@ import java.util.Map;
 @RequestMapping("/api/points")
 public class PointsController {
 
-    @Autowired
+    @Resource
     private PointsService pointsService;
 
     @GetMapping("/balance")
@@ -35,7 +35,7 @@ public class PointsController {
                                                     @RequestParam Integer amount,
                                                     @RequestParam String reason,
                                                     @RequestParam String password,
-                                                    @RequestHeader("X-User-Id") Long userId) {
+                                                    @RequestAttribute("userId") Long userId) {
         Map<String, Object> result = pointsService.adjustPoints(childId, amount, reason, password, userId);
         return Result.success(result);
     }

+ 3 - 3
zxyj-backend/src/main/java/com/zxyj/controller/RewardController.java

@@ -6,7 +6,7 @@ import com.zxyj.dto.CreateRewardDTO;
 import com.zxyj.dto.RewardDTO;
 import com.zxyj.entity.Reward;
 import com.zxyj.service.RewardService;
-import org.springframework.beans.factory.annotation.Autowired;
+import javax.annotation.Resource;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
@@ -16,11 +16,11 @@ import java.util.Map;
 @RequestMapping("/api/rewards")
 public class RewardController {
 
-    @Autowired
+    @Resource
     private RewardService rewardService;
 
     @PostMapping
-    public Result<Long> createReward(@RequestHeader("X-User-Id") Long userId,
+    public Result<Long> createReward(@RequestAttribute("userId") Long userId,
                                      @RequestBody CreateRewardDTO dto) {
         Long rewardId = rewardService.createReward(userId, dto);
         return Result.success(rewardId);

+ 68 - 0
zxyj-backend/src/main/java/com/zxyj/controller/RewardWishlistController.java

@@ -0,0 +1,68 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.dto.CreateRewardWishlistDTO;
+import com.zxyj.entity.RewardWishlist;
+import com.zxyj.entity.User;
+import com.zxyj.service.RewardWishlistService;
+import com.zxyj.service.UserService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+@Tag(name = "家长心愿单管理")
+@RestController
+@RequestMapping("/api/parent/wishlist")
+public class RewardWishlistController {
+
+    @Resource
+    private RewardWishlistService rewardWishlistService;
+
+    @Resource
+    private UserService userService;
+
+    @Operation(summary = "创建心愿")
+    @PostMapping
+    public Result<Long> create(@RequestAttribute("userId") Long userId,
+                                @RequestBody CreateRewardWishlistDTO dto) {
+        Long id = rewardWishlistService.create(userId, dto);
+        return Result.success(id);
+    }
+
+    @Operation(summary = "获取我的心愿单")
+    @GetMapping
+    public Result<List<RewardWishlist>> getMyWishlist(@RequestAttribute("userId") Long userId) {
+        List<RewardWishlist> list = rewardWishlistService.getByUserId(userId);
+        return Result.success(list);
+    }
+
+    @Operation(summary = "获取家庭心愿单")
+    @GetMapping("/family")
+    public Result<List<RewardWishlist>> getFamilyWishlist(@RequestAttribute("userId") Long userId) {
+        User user = userService.getUserInfo(userId);
+        if (user == null) {
+            return Result.error("用户不存在");
+        }
+        List<RewardWishlist> list = rewardWishlistService.getByFamilyId(user.getFamilyId());
+        return Result.success(list);
+    }
+
+    @Operation(summary = "兑换心愿")
+    @PostMapping("/{id}/exchange")
+    public Result<Boolean> exchange(@PathVariable Long id,
+                                     @RequestAttribute("userId") Long userId) {
+        boolean success = rewardWishlistService.exchange(id, userId);
+        return Result.success(success);
+    }
+
+    @Operation(summary = "审核心愿")
+    @PostMapping("/{id}/approve")
+    public Result<Boolean> approve(@PathVariable Long id,
+                                   @RequestParam boolean approved) {
+        boolean success = rewardWishlistService.approve(id, approved);
+        return Result.success(success);
+    }
+}

+ 44 - 13
zxyj-backend/src/main/java/com/zxyj/controller/TaskController.java

@@ -7,32 +7,38 @@ import com.zxyj.dto.CreateTaskDTO;
 import com.zxyj.dto.TaskReviewDTO;
 import com.zxyj.entity.Task;
 import com.zxyj.service.TaskService;
-import org.springframework.beans.factory.annotation.Autowired;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import javax.annotation.Resource;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
 import java.util.Map;
 
+@Tag(name = "任务管理", description = "任务的创建、完成、审核、历史查询等接口")
 @RestController
 @RequestMapping("/api/tasks")
 public class TaskController {
 
-    @Autowired
+    @Resource
     private TaskService taskService;
 
+    @Operation(summary = "创建任务")
     @PostMapping
-    public Result<Long> createTask(@RequestHeader("X-User-Id") Long userId,
+    public Result<Long> createTask(@RequestAttribute("userId") Long userId,
                                    @RequestBody CreateTaskDTO dto) {
         Long taskId = taskService.createTask(userId, dto);
         return Result.success(taskId);
     }
 
+    @Operation(summary = "获取今日任务")
     @GetMapping("/today")
     public Result<List<Task>> getTodayTasks(@RequestParam Long childId) {
         List<Task> tasks = taskService.getTodayTasks(childId);
         return Result.success(tasks);
     }
 
+    @Operation(summary = "完成任务")
     @PostMapping("/{id}/complete")
     public Result<Map<String, Object>> completeTask(@PathVariable Long id,
                                                     @RequestParam Long childId,
@@ -41,6 +47,7 @@ public class TaskController {
         return Result.success(result);
     }
 
+    @Operation(summary = "审核任务")
     @PostMapping("/{id}/review")
     public Result<Boolean> reviewTask(@PathVariable Long id,
                                       @RequestBody TaskReviewDTO dto) {
@@ -48,18 +55,42 @@ public class TaskController {
         return Result.success(success);
     }
 
-    @GetMapping("/history")
-    public Result<Page<Task>> getTaskHistory(@RequestParam Long childId,
-                                             @RequestParam(defaultValue = "1") Integer page,
-                                             @RequestParam(defaultValue = "10") Integer size) {
-        Page<Task> history = taskService.getTaskHistory(childId, page, size);
-        return Result.success(history);
+  @Operation(summary = "获取任务历史")
+  @GetMapping("/history")
+  public Result<Page<Task>> getTaskHistory(@RequestParam Long childId,
+      @RequestParam(defaultValue = "1") Integer page,
+      @RequestParam(defaultValue = "10") Integer size) {
+    Page<Task> history = taskService.getTaskHistory(childId, page, size);
+    return Result.success(history);
+  }
+
+    @Operation(summary = "获取待审核任务")
+    @GetMapping("/pending-review")
+    public Result<List<Task>> getPendingReviewTasks(@RequestAttribute("userId") Long userId) {
+    List<Task> tasks = taskService.getPendingReviewTasks(userId);
+    return Result.success(tasks);
+  }
+
+    @Operation(summary = "获取家长今日任务")
+    @GetMapping("/today-parent")
+    public Result<List<Task>> getTodayParentTasks(@RequestAttribute("userId") Long userId) {
+        List<Task> tasks = taskService.getTodayParentTasks(userId);
+        return Result.success(tasks);
+    }
+
+    @Operation(summary = "家长完成任务")
+    @PostMapping("/{id}/complete-parent")
+    public Result<Map<String, Object>> completeParentTask(@PathVariable Long id,
+                                                        @RequestAttribute("userId") Long userId) {
+        Map<String, Object> result = taskService.completeParentTask(id, userId);
+        return Result.success(result);
     }
 
+    @Operation(summary = "删除任务")
     @DeleteMapping("/{id}")
     public Result<Boolean> deleteTask(@PathVariable Long id,
-                                      @RequestHeader("X-User-Id") Long userId) {
-        boolean success = taskService.deleteTask(id, userId);
-        return Result.success(success);
-    }
+                                      @RequestAttribute("userId") Long userId) {
+    boolean success = taskService.deleteTask(id, userId);
+    return Result.success(success);
+  }
 }

+ 152 - 0
zxyj-backend/src/main/java/com/zxyj/controller/TeacherController.java

@@ -0,0 +1,152 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.dto.*;
+import com.zxyj.entity.User;
+import com.zxyj.service.TeacherService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import javax.annotation.Resource;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 教师控制器
+ * 处理教师相关的业务操作
+ */
+@Tag(name = "教师管理", description = "教师任务创建、班级管理、学生进度查看等接口")
+@RestController
+@RequestMapping("/api/teacher")
+public class TeacherController {
+
+    @Resource
+    private TeacherService teacherService;
+
+  /**
+   * 获取教师信息
+   */
+    @Operation(summary = "获取教师信息")
+    @GetMapping("/info")
+    public Result<TeacherDTO> getTeacherInfo(@RequestAttribute("userId") Long teacherId) {
+    TeacherDTO info = teacherService.getTeacherInfo(teacherId);
+    return Result.success(info);
+  }
+
+  /**
+   * 创建教学任务
+   */
+    @Operation(summary = "创建教学任务")
+    @PostMapping("/tasks")
+    public Result<Long> createTask(@RequestAttribute("userId") Long teacherId,
+      @RequestBody CreateTeacherTaskDTO dto) {
+    // TODO: Implement createTask in TeacherService
+    // Long taskId = teacherService.createTask(teacherId, dto);
+    // return Result.success(taskId);
+    return Result.error("功能开发中");
+  }
+
+  /**
+   * 获取教师任务列表
+   */
+    @Operation(summary = "获取教师任务列表")
+    @GetMapping("/tasks")
+    public Result<List<TaskDTO>> getTaskList(@RequestAttribute("userId") Long teacherId) {
+    // TODO: Implement getTaskList in TeacherService
+    // List<TaskDTO> tasks = teacherService.getTaskList(teacherId);
+    // return Result.success(tasks);
+    return Result.success(null);
+  }
+
+  /**
+   * 创建班级
+   */
+    @Operation(summary = "创建班级")
+    @PostMapping("/classes")
+    public Result<Long> createClass(@RequestAttribute("userId") Long teacherId,
+      @RequestBody CreateClassDTO dto) {
+    // TODO: Implement createClass in TeacherService
+    // Long classId = teacherService.createClass(teacherId, dto);
+    // return Result.success(classId);
+    return Result.error("功能开发中");
+  }
+
+  /**
+   * 获取教师班级列表
+   */
+    @Operation(summary = "获取教师班级列表")
+    @GetMapping("/classes")
+    public Result<List<ClassInfoDTO>> getClassList(@RequestAttribute("userId") Long teacherId) {
+    // TODO: Implement getClassList in TeacherService
+    // List<ClassInfoDTO> classes = teacherService.getClassList(teacherId);
+    // return Result.success(classes);
+    return Result.success(null);
+  }
+
+  /**
+   * 获取班级学生列表
+   */
+    @Operation(summary = "获取班级学生列表")
+    @GetMapping("/classes/{classId}/students")
+    public Result<List<StudentInfoDTO>> getClassStudents(@RequestAttribute("userId") Long teacherId,
+      @PathVariable Long classId) {
+    // TODO: Implement getClassStudents in TeacherService
+    // List<StudentInfoDTO> students = teacherService.getClassStudents(teacherId, classId);
+    // return Result.success(students);
+    return Result.success(null);
+  }
+
+  /**
+   * 添加学生到班级
+   */
+    @Operation(summary = "添加学生到班级")
+    @PostMapping("/classes/{classId}/students")
+    public Result<Boolean> addStudentToClass(@RequestAttribute("userId") Long teacherId,
+      @PathVariable Long classId,
+      @RequestBody Map<String, Long> body) {
+    // Long studentId = body.get("studentId");
+    // boolean success = teacherService.addStudentToClass(teacherId, classId, studentId);
+    // return Result.success(success);
+    return Result.error("功能开发中");
+  }
+
+  /**
+   * 从班级移除学生
+   */
+    @Operation(summary = "从班级移除学生")
+    @DeleteMapping("/classes/{classId}/students/{studentId}")
+    public Result<Boolean> removeStudentFromClass(@RequestAttribute("userId") Long teacherId,
+      @PathVariable Long classId,
+      @PathVariable Long studentId) {
+    // boolean success = teacherService.removeStudentFromClass(teacherId, classId, studentId);
+    // return Result.success(success);
+    return Result.error("功能开发中");
+  }
+
+  /**
+   * 获取学生学习进度
+   */
+    @Operation(summary = "获取学生学习进度")
+    @GetMapping("/students/{studentId}/progress")
+    public Result<StudentProgressDTO> getStudentProgress(@RequestAttribute("userId") Long teacherId,
+      @PathVariable Long studentId) {
+    // TODO: Implement getStudentProgress in TeacherService
+    // StudentProgressDTO progress = teacherService.getStudentProgress(teacherId, studentId);
+    // return Result.success(progress);
+    return Result.success(null);
+  }
+
+  /**
+   * 获取班级学习报告
+   */
+    @Operation(summary = "获取班级学习报告")
+    @GetMapping("/classes/{classId}/report")
+    public Result<ClassReportDTO> getClassReport(@RequestAttribute("userId") Long teacherId,
+      @PathVariable Long classId) {
+    // TODO: Implement getClassReport in TeacherService
+    // ClassReportDTO report = teacherService.getClassReport(teacherId, classId);
+    // return Result.success(report);
+    return Result.success(null);
+  }
+}

+ 21 - 0
zxyj-backend/src/main/java/com/zxyj/controller/UserController.java

@@ -81,6 +81,27 @@ public class UserController {
         return Result.success(result);
     }
 
+    @Operation(summary = "切换用户角色 (支持 parent/child/teacher)")
+    @PostMapping("/switch-role")
+    public Result<String> switchRole(javax.servlet.http.HttpServletRequest request,
+                                      @RequestBody Map<String, String> body) {
+        Long userId = getUserId(request);
+        String role = body.get("role");
+        String result = userService.switchRole(userId, role);
+        if (result == null) {
+            return Result.error("切换失败,请确认您拥有该角色");
+        }
+        return Result.success(result);
+    }
+
+    @Operation(summary = "获取用户所有角色列表")
+    @GetMapping("/roles")
+    public Result<List<String>> getUserRoles(javax.servlet.http.HttpServletRequest request) {
+        Long userId = getUserId(request);
+        List<String> roles = userService.getUserRoles(userId);
+        return Result.success(roles);
+    }
+
     @Operation(summary = "家长切换到指定孩子身份")
     @PostMapping("/switch-to-child")
     public Result<Long> switchToChild(javax.servlet.http.HttpServletRequest request,

+ 161 - 24
zxyj-backend/src/main/java/com/zxyj/service/TaskService.java

@@ -6,7 +6,7 @@ import com.zxyj.dto.CreateTaskDTO;
 import com.zxyj.dto.TaskReviewDTO;
 import com.zxyj.entity.*;
 import com.zxyj.mapper.*;
-import org.springframework.beans.factory.annotation.Autowired;
+import javax.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
@@ -15,16 +15,19 @@ import java.util.*;
 @Service
 public class TaskService {
 
-    @Autowired
+    @Resource
     private TaskMapper taskMapper;
 
-    @Autowired
+    @Resource
     private ChildMapper childMapper;
 
-    @Autowired
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
     private PointsLogMapper pointsLogMapper;
 
-    @Autowired
+    @Resource
     private UserService userService;
 
     private static final int EARLY_BONUS = 1;       // 提前完成奖励
@@ -33,17 +36,44 @@ public class TaskService {
 
     @Transactional
     public Long createTask(Long userId, CreateTaskDTO dto) {
+        User currentUser = userService.getUserInfo(userId);
         Task task = new Task();
-        task.setFamilyId(userService.getUserInfo(userId).getFamilyId());
+        task.setFamilyId(currentUser.getFamilyId());
         task.setCreatorId(userId);
-        task.setChildId(dto.getChildId());
+        
+        // 处理执行者类型 - 支持孩子任务和家长任务
+        String executorType = dto.getExecutorType();
+        if (executorType == null || executorType.isEmpty()) {
+            // 兼容旧版:默认给孩子创建任务
+            executorType = "child";
+        }
+        task.setExecutorType(executorType);
+        
+        if ("child".equals(executorType)) {
+            // 给孩子安排的任务
+            task.setExecutorId(dto.getChildId() != null ? dto.getChildId() : dto.getExecutorId());
+            task.setChildId(dto.getChildId() != null ? dto.getChildId() : dto.getExecutorId());
+        } else {
+            // 给家长安排的任务
+            task.setExecutorId(dto.getExecutorId() != null ? dto.getExecutorId() : userId);
+            task.setChildId(null);
+        }
+        
         task.setTitle(dto.getTitle());
         task.setDescription(dto.getDescription());
         task.setPoints(dto.getPoints() != null ? dto.getPoints() : 2);
         task.setDeadline(dto.getDeadline());
         task.setRepeatType(dto.getRepeatType() != null ? dto.getRepeatType() : "none");
         task.setCategory(dto.getCategory());
-        task.setNeedReview(dto.getNeedReview() != null ? dto.getNeedReview() : 0);
+        
+        // 家长给孩子安排任务需要审核,孩子给家长安排任务也需要审核
+        if ("child".equals(executorType)) {
+            task.setNeedReview(dto.getNeedReview() != null ? dto.getNeedReview() : 0);
+        } else {
+            // 孩子给家长安排任务,默认需要审核
+            task.setNeedReview(dto.getNeedReview() != null ? dto.getNeedReview() : 1);
+        }
+        
         task.setReviewByCategory(dto.getReviewByCategory() != null ? dto.getReviewByCategory() : 0);
         task.setStatus("pending");
         task.setCreatedAt(new Date());
@@ -204,23 +234,130 @@ public class TaskService {
                 .orderByDesc(Task::getCreatedAt));
     }
 
-    public boolean deleteTask(Long taskId, Long userId) {
-        Task task = taskMapper.selectById(taskId);
-        if (task == null || !task.getCreatorId().equals(userId)) {
-            return false;
-        }
-        task.setStatus("cancelled");
-        task.setUpdatedAt(new Date());
-        taskMapper.updateById(task);
-        return true;
+  public boolean deleteTask(Long taskId, Long userId) {
+    Task task = taskMapper.selectById(taskId);
+    if (task == null || !task.getCreatorId().equals(userId)) {
+      return false;
     }
+    task.setStatus("cancelled");
+    task.setUpdatedAt(new Date());
+    taskMapper.updateById(task);
+    return true;
+  }
+
+  /**
+   * 获取待审核任务列表
+   * 家长查看需要审核的任务
+   */
+  public List<Task> getPendingReviewTasks(Long userId) {
+    // 获取用户信息
+    User user = userService.getUserInfo(userId);
+    if (user == null || !"parent".equals(user.getRole())) {
+      return new ArrayList<>();
+    }
+
+    // 查询该家庭下所有需要审核且已完成的任务
+    LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<>();
+    wrapper.eq(Task::getFamilyId, user.getFamilyId())
+        .eq(Task::getNeedReview, 1)
+        .eq(Task::getStatus, "completed")
+        .orderByDesc(Task::getCompletedAt);
+
+    return taskMapper.selectList(wrapper);
+  }
+
+  /**
+   * 获取家长今日任务 (家长作为执行者的任务)
+   */
+  public List<Task> getTodayParentTasks(Long userId) {
+    Calendar calendar = Calendar.getInstance();
+    calendar.set(Calendar.HOUR_OF_DAY, 0);
+    calendar.set(Calendar.MINUTE, 0);
+    calendar.set(Calendar.SECOND, 0);
+    calendar.set(Calendar.MILLISECOND, 0);
+    Date startOfDay = calendar.getTime();
+
+    calendar.add(Calendar.DAY_OF_MONTH, 1);
+    Date endOfDay = calendar.getTime();
 
-    private boolean isSameDay(Date date1, Date date2) {
-        Calendar cal1 = Calendar.getInstance();
-        cal1.setTime(date1);
-        Calendar cal2 = Calendar.getInstance();
-        cal2.setTime(date2);
-        return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
-                && cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
+    User user = userService.getUserInfo(userId);
+    if (user == null) {
+      return new ArrayList<>();
     }
+
+    return taskMapper.selectList(new LambdaQueryWrapper<Task>()
+            .eq(Task::getFamilyId, user.getFamilyId())
+            .eq(Task::getExecutorType, "parent")
+            .eq(Task::getExecutorId, userId)
+            .ge(Task::getDeadline, startOfDay)
+            .lt(Task::getDeadline, endOfDay)
+            .in(Task::getStatus, Arrays.asList("pending", "completed"))
+            .orderByAsc(Task::getDeadline));
+  }
+
+  /**
+   * 家长完成任务 (家长作为执行者完成任务)
+   */
+  @Transactional
+  public Map<String, Object> completeParentTask(Long taskId, Long userId) {
+    Task task = taskMapper.selectById(taskId);
+    if (task == null || !("parent".equals(task.getExecutorType()) && task.getExecutorId().equals(userId))) {
+      return null;
+    }
+
+    if ("completed".equals(task.getStatus())) {
+      return null;
+    }
+
+    User user = userMapper.selectById(userId);
+    if (user == null) {
+      return null;
+    }
+
+    Date now = new Date();
+    int pointsEarned = task.getPoints();
+    boolean isEarly = now.before(task.getDeadline());
+
+    // 提前完成获得奖励分
+    if (isEarly) {
+      pointsEarned += EARLY_BONUS;
+    }
+
+    // 更新任务状态
+    task.setStatus("completed");
+    task.setCompletedAt(now);
+    task.setUpdatedAt(new Date());
+    taskMapper.updateById(task);
+
+    // 更新家长积分
+    int newPoints = (user.getTotalPoints() != null ? user.getTotalPoints() : 0) + pointsEarned;
+    user.setTotalPoints(newPoints);
+    user.setUpdatedAt(new Date());
+    userMapper.updateById(user);
+
+        // 记录积分流水
+        PointsLog log = new PointsLog();
+        log.setChildId(task.getChildId());
+        log.setAmount(pointsEarned);
+        log.setType(isEarly ? "earn" : "earn");
+        log.setDescription("完成任务: " + task.getTitle());
+        log.setCreatedAt(new Date());
+        pointsLogMapper.insert(log);
+
+    Map<String, Object> result = new HashMap<>();
+    result.put("pointsEarned", pointsEarned);
+    result.put("newBalance", user.getTotalPoints());
+    result.put("needReview", task.getNeedReview() == 1);
+
+    return result;
+  }
+
+  private boolean isSameDay(Date date1, Date date2) {
+    Calendar cal1 = Calendar.getInstance();
+    cal1.setTime(date1);
+    Calendar cal2 = Calendar.getInstance();
+    cal2.setTime(date2);
+    return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR)
+        && cal1.get(Calendar.DAY_OF_YEAR) == cal2.get(Calendar.DAY_OF_YEAR);
+  }
 }