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

feat: 添加街道地址功能支持家庭和指导师区域匹配

- 新增Street实体和Streets表(省市区街道四级结构)
- Family和Teacher实体添加streetId及地址冗余字段
- 实现StreetService支持区域回退匹配逻辑
  * 优先匹配相同街道
  * 回退到区县级匹配
  * 最终回退到城市级匹配
- 实现StreetController API端点
  * GET /api/streets/provinces - 获取所有省份
  * GET /api/streets/children/{parentId} - 获取子级地址
  * GET /api/streets/{id}/match - 区域匹配
  * GET /api/streets/stats - 按区域统计
- 数据库迁移添加streets表示例数据(广东省)
- 修复TaskReviewDTO缺少approved和comment字段

用途:
- 家庭和指导师按街道进行匹配
- 按区域统计家庭数和指导师数
- 支持地址四级联动选择
User 5 месяцев назад
Родитель
Сommit
f64c6e4739

+ 136 - 19
zxyj-backend/src/main/java/com/zxyj/config/DatabaseInitializer.java

@@ -456,6 +456,84 @@ public class DatabaseInitializer implements CommandLineRunner {
             // 修改失败,忽略错误
         }
 
+        // 迁移6: 创建streets表(街道地址维度表)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS streets (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "province VARCHAR(50) COMMENT '省份', " +
+                    "province_code VARCHAR(20) COMMENT '省份编码', " +
+                    "city VARCHAR(50) COMMENT '城市', " +
+                    "city_code VARCHAR(20) COMMENT '城市编码', " +
+                    "district VARCHAR(50) COMMENT '区县', " +
+                    "district_code VARCHAR(20) COMMENT '区县编码', " +
+                    "street VARCHAR(100) COMMENT '街道', " +
+                    "street_code VARCHAR(20) COMMENT '街道编码', " +
+                    "full_name VARCHAR(200) COMMENT '完整地址', " +
+                    "level INT COMMENT '层级:1省2市3区4街道', " +
+                    "parent_id BIGINT COMMENT '父级ID', " +
+                    "sort_order INT DEFAULT 0 COMMENT '排序', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_level (level), " +
+                    "INDEX idx_parent_id (parent_id), " +
+                    "INDEX idx_street_code (street_code), " +
+                    "INDEX idx_full_name (full_name(50))" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
+            log.info("已创建streets表");
+        } catch (Exception e) {
+            // 表已存在,忽略错误
+        }
+
+        // 迁移7: families表添加地址字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN street_id BIGINT COMMENT '街道ID'");
+            log.info("已添加street_id列到families表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN province VARCHAR(50) COMMENT '省份'");
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN city VARCHAR(50) COMMENT '城市'");
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN district VARCHAR(50) COMMENT '区县'");
+            jdbcTemplate.execute("ALTER TABLE families ADD COLUMN street VARCHAR(100) COMMENT '街道'");
+            log.info("已添加地址字段到families表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE families ADD INDEX idx_street_id (street_id)");
+            log.info("已添加idx_street_id索引到families表");
+        } catch (Exception e) {
+            // 索引已存在,忽略错误
+        }
+
+        // 迁移8: teachers表添加地址字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN street_id BIGINT COMMENT '街道ID'");
+            log.info("已添加street_id列到teachers表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN province VARCHAR(50) COMMENT '省份'");
+            jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN city VARCHAR(50) COMMENT '城市'");
+            jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN district VARCHAR(50) COMMENT '区县'");
+            jdbcTemplate.execute("ALTER TABLE teachers ADD COLUMN street VARCHAR(100) COMMENT '街道'");
+            log.info("已添加地址字段到teachers表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE teachers ADD INDEX idx_street_id (street_id)");
+            log.info("已添加idx_street_id索引到teachers表");
+        } catch (Exception e) {
+            // 索引已存在,忽略错误
+        }
+
         log.info("数据库迁移完成");
     }
 
@@ -553,26 +631,65 @@ try {
   log.warn("初始化默认管理员失败: {}", e.getMessage());
 }
 
-// 初始化任务模板示例数据
-try {
-  Integer count = jdbcTemplate.queryForObject(
-  "SELECT COUNT(*) FROM admin_task_templates", Integer.class);
+        // 初始化任务模板示例数据
+        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());
-}
+            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 streets", Integer.class);
+
+            if (count == null || count == 0) {
+                // 省
+                jdbcTemplate.execute("INSERT INTO streets (province, province_code, full_name, level, parent_id, sort_order) VALUES " +
+                        "('广东省', '44', '广东省', 1, NULL, 1)");
+                
+                // 市
+                jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, full_name, level, parent_id, sort_order) VALUES " +
+                        "('广东省', '44', '广州市', '4401', '广东省广州市', 2, 1, 1), " +
+                        "('广东省', '44', '深圳市', '4403', '广东省深圳市', 2, 1, 2), " +
+                        "('广东省', '44', '珠海市', '4404', '广东省珠海市', 2, 1, 3)");
+                
+                // 区
+                jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, district, district_code, full_name, level, parent_id, sort_order) VALUES " +
+                        "('广东省', '44', '广州市', '4401', '天河区', '440106', '广东省广州市天河区', 3, 2, 1), " +
+                        "('广东省', '44', '广州市', '4401', '越秀区', '440104', '广东省广州市越秀区', 3, 2, 2), " +
+                        "('广东省', '44', '广州市', '4401', '番禺区', '440113', '广东省广州市番禺区', 3, 2, 3), " +
+                        "('广东省', '44', '深圳市', '4403', '南山区', '440305', '广东省深圳市南山区', 3, 3, 1), " +
+                        "('广东省', '44', '深圳市', '4403', '福田区', '440304', '广东省深圳市福田区', 3, 3, 2)");
+                
+                // 街道
+                jdbcTemplate.execute("INSERT INTO streets (province, province_code, city, city_code, district, district_code, street, street_code, full_name, level, parent_id, sort_order) VALUES " +
+                        "('广东省', '44', '广州市', '4401', '天河区', '440106', '天河南街道', '440106001', '广东省广州市天河区天河南街道', 4, 4, 1), " +
+                        "('广东省', '44', '广州市', '4401', '天河区', '440106', '沙河街道', '440106002', '广东省广州市天河区沙河街道', 4, 4, 2), " +
+                        "('广东省', '44', '广州市', '4401', '越秀区', '440104', '东山街道', '440104001', '广东省广州市越秀区东山街道', 4, 5, 1), " +
+                        "('广东省', '44', '广州市', '4401', '番禺区', '440113', '市桥街道', '440113001', '广东省广州市番禺区市桥街道', 4, 6, 1), " +
+                        "('广东省', '44', '深圳市', '4403', '南山区', '440305', '南山街道', '440305001', '广东省深圳市南山区南山街道', 4, 7, 1), " +
+                        "('广东省', '44', '深圳市', '4403', '福田区', '440304', '福田街道', '440304001', '广东省深圳市福田区福田街道', 4, 8, 1)");
+                
+                log.info("街道示例数据已初始化");
+            }
+        } catch (Exception e) {
+            log.warn("初始化街道数据失败: {}", e.getMessage());
+        }
 
 try {
   Integer count = jdbcTemplate.queryForObject(

+ 94 - 0
zxyj-backend/src/main/java/com/zxyj/controller/StreetController.java

@@ -0,0 +1,94 @@
+package com.zxyj.controller;
+
+import com.zxyj.common.Result;
+import com.zxyj.entity.Street;
+import com.zxyj.service.StreetService;
+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;
+import java.util.Map;
+
+/**
+ * 街道地址控制器 - 支持四级地址选择和匹配
+ */
+@Tag(name = "街道地址", description = "四级地址选择、区域匹配、统计接口")
+@RestController
+@RequestMapping("/api/streets")
+public class StreetController {
+
+    @Resource
+    private StreetService streetService;
+
+    /**
+     * 获取所有省份
+     */
+    @Operation(summary = "获取所有省份")
+    @GetMapping("/provinces")
+    public Result<List<Street>> getAllProvinces() {
+        List<Street> provinces = streetService.getAllProvinces();
+        return Result.success(provinces);
+    }
+
+    /**
+     * 根据父级ID获取子级地址
+     */
+    @Operation(summary = "根据父级ID获取子级地址")
+    @GetMapping("/children/{parentId}")
+    public Result<List<Street>> getChildren(@PathVariable Long parentId) {
+        List<Street> children = streetService.getChildrenByParentId(parentId);
+        return Result.success(children);
+    }
+
+    /**
+     * 根据ID获取地址详情
+     */
+    @Operation(summary = "根据ID获取地址详情")
+    @GetMapping("/{id}")
+    public Result<Street> getById(@PathVariable Long id) {
+        Street street = streetService.getById(id);
+        return Result.success(street);
+    }
+
+    /**
+     * 获取地址的完整路径
+     */
+    @Operation(summary = "获取地址完整路径")
+    @GetMapping("/{id}/path")
+    public Result<List<Street>> getAddressPath(@PathVariable Long id) {
+        List<Street> path = streetService.getAddressPath(id);
+        return Result.success(path);
+    }
+
+    /**
+     * 搜索地址
+     */
+    @Operation(summary = "搜索地址")
+    @GetMapping("/search")
+    public Result<List<Street>> search(@RequestParam String keyword) {
+        List<Street> results = streetService.search(keyword);
+        return Result.success(results);
+    }
+
+    /**
+     * 区域回退匹配 - 获取匹配范围
+     */
+    @Operation(summary = "区域回退匹配", description = "根据街道ID返回街道级、区县级、城市级的匹配范围")
+    @GetMapping("/{id}/match")
+    public Result<Map<String, Object>> match(@PathVariable Long id) {
+        Map<String, Object> matchResult = streetService.matchByStreetWithFallback(id);
+        return Result.success(matchResult);
+    }
+
+    /**
+     * 按区域统计
+     */
+    @Operation(summary = "按区域统计", description = "按指定层级统计地址数量")
+    @GetMapping("/stats")
+    public Result<Map<String, Object>> getStatistics(@RequestParam(defaultValue = "4") Integer level) {
+        Map<String, Object> stats = streetService.getStatisticsByLevel(level);
+        return Result.success(stats);
+    }
+}

+ 2 - 0
zxyj-backend/src/main/java/com/zxyj/dto/TaskReviewDTO.java

@@ -9,4 +9,6 @@ public class TaskReviewDTO {
     private String result;
     private String aiSuggestion;
     private Boolean overrideAi;
+    private Boolean approved;  // 审核结果(true通过/false拒绝)
+    private String comment;    // 审核意见
 }

+ 7 - 0
zxyj-backend/src/main/java/com/zxyj/entity/Family.java

@@ -19,6 +19,13 @@ public class Family implements Serializable {
 
     private String inviteCode;
 
+    private Long streetId;        // 街道ID(关联streets表)
+
+    private String province;      // 省份(冗余字段,方便查询)
+    private String city;          // 城市(冗余字段)
+    private String district;      // 区县(冗余字段)
+    private String street;        // 街道(冗余字段)
+
     private Date createdAt;
 
     private Date updatedAt;

+ 43 - 0
zxyj-backend/src/main/java/com/zxyj/entity/Street.java

@@ -0,0 +1,43 @@
+package com.zxyj.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 街道实体 - 支持四级地址结构(省市区街道)
+ */
+@Data
+@TableName("streets")
+public class Street implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String province;      // 省份
+    private String provinceCode;  // 省份编码
+
+    private String city;          // 城市
+    private String cityCode;      // 城市编码
+
+    private String district;      // 区/县
+    private String districtCode;  // 区/县编码
+
+    private String street;        // 街道
+    private String streetCode;    // 街道编码
+
+    private String fullName;      // 完整地址名称(省市区街道)
+
+    private Integer level;        // 层级:1省 2市 3区 4街道
+
+    private Long parentId;        // 父级ID(用于层级关系)
+
+    private Integer sortOrder;    // 排序
+
+    private Date createdAt;
+    private Date updatedAt;
+}

+ 9 - 2
zxyj-backend/src/main/java/com/zxyj/entity/Teacher.java

@@ -31,9 +31,16 @@ public class Teacher implements Serializable {
 
     private Integer totalAppointments;  // 总测评次数
 
-    private String status;  // active/inactive/suspended
+    private String status; // active/inactive/suspended
 
-    private String verificationStatus;  // pending/approved/rejected
+    private String verificationStatus; // pending/approved/rejected
+
+    private Long streetId;        // 街道ID(关联streets表)
+
+    private String province;      // 省份(冗余字段,方便查询)
+    private String city;          // 城市(冗余字段)
+    private String district;      // 区县(冗余字段)
+    private String street;        // 街道(冗余字段)
 
     private Date verifiedAt;
 

+ 61 - 0
zxyj-backend/src/main/java/com/zxyj/mapper/StreetMapper.java

@@ -0,0 +1,61 @@
+package com.zxyj.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zxyj.entity.Street;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+@Mapper
+public interface StreetMapper extends BaseMapper<Street> {
+
+    /**
+     * 根据层级查询地址列表
+     */
+    @Select("SELECT * FROM streets WHERE level = #{level} ORDER BY sort_order, id")
+    List<Street> findByLevel(@Param("level") Integer level);
+
+    /**
+     * 根据父级ID查询子级地址
+     */
+    @Select("SELECT * FROM streets WHERE parent_id = #{parentId} ORDER BY sort_order, id")
+    List<Street> findByParentId(@Param("parentId") Long parentId);
+
+    /**
+     * 根据编码查询
+     */
+    @Select("SELECT * FROM streets WHERE street_code = #{code}")
+    Street findByCode(@Param("code") String code);
+
+    /**
+     * 查询所有省份
+     */
+    @Select("SELECT * FROM streets WHERE level = 1 ORDER BY sort_order, id")
+    List<Street> findAllProvinces();
+
+    /**
+     * 查询指定省份下的城市
+     */
+    @Select("SELECT * FROM streets WHERE level = 2 AND parent_id = #{provinceId} ORDER BY sort_order, id")
+    List<Street> findCitiesByProvince(@Param("provinceId") Long provinceId);
+
+    /**
+     * 查询指定城市下的区县
+     */
+    @Select("SELECT * FROM streets WHERE level = 3 AND parent_id = #{cityId} ORDER BY sort_order, id")
+    List<Street> findDistrictsByCity(@Param("cityId") Long cityId);
+
+    /**
+     * 查询指定区县下的街道
+     */
+    @Select("SELECT * FROM streets WHERE level = 4 AND parent_id = #{districtId} ORDER BY sort_order, id")
+    List<Street> findStreetsByDistrict(@Param("districtId") Long districtId);
+
+    /**
+     * 搜索地址(模糊匹配)
+     */
+    @Select("SELECT * FROM streets WHERE full_name LIKE CONCAT('%', #{keyword}, '%') ORDER BY level, sort_order LIMIT 20")
+    List<Street> search(@Param("keyword") String keyword);
+}

+ 158 - 0
zxyj-backend/src/main/java/com/zxyj/service/StreetService.java

@@ -0,0 +1,158 @@
+package com.zxyj.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zxyj.entity.Street;
+import com.zxyj.mapper.StreetMapper;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * 街道服务 - 支持四级地址和区域回退匹配
+ */
+@Service
+public class StreetService {
+
+    @Resource
+    private StreetMapper streetMapper;
+
+    /**
+     * 获取所有省份
+     */
+    public List<Street> getAllProvinces() {
+        return streetMapper.findAllProvinces();
+    }
+
+    /**
+     * 根据父级ID获取子级地址
+     */
+    public List<Street> getChildrenByParentId(Long parentId) {
+        return streetMapper.findByParentId(parentId);
+    }
+
+    /**
+     * 根据ID获取地址详情
+     */
+    public Street getById(Long id) {
+        return streetMapper.selectById(id);
+    }
+
+    /**
+     * 搜索地址
+     */
+    public List<Street> search(String keyword) {
+        return streetMapper.search(keyword);
+    }
+
+    /**
+     * 获取地址的完整路径(省市区街道)
+     */
+    public List<Street> getAddressPath(Long streetId) {
+        List<Street> path = new ArrayList<>();
+        Street current = streetMapper.selectById(streetId);
+        
+        while (current != null) {
+            path.add(0, current);
+            if (current.getParentId() != null) {
+                current = streetMapper.selectById(current.getParentId());
+            } else {
+                break;
+            }
+        }
+        
+        return path;
+    }
+
+    /**
+     * 区域回退匹配 - 根据街道ID匹配相同区域的家庭和指导师
+     * 匹配策略:
+     * 1. 优先匹配相同街道
+     * 2. 如果无匹配,回退到相同区县
+     * 3. 如果仍无匹配,回退到相同城市
+     * 
+     * @param streetId 街道ID
+     * @return 匹配结果:街道级、区县级、城市级的实体列表
+     */
+    public Map<String, Object> matchByStreetWithFallback(Long streetId) {
+        Map<String, Object> result = new HashMap<>();
+        
+        // 获取完整地址路径
+        List<Street> path = getAddressPath(streetId);
+        if (path.isEmpty()) {
+            return result;
+        }
+        
+        // 街道级别(level=4)
+        Street streetEntity = path.stream()
+                .filter(s -> s.getLevel() == 4)
+                .findFirst()
+                .orElse(null);
+        
+        // 区县级别(level=3)
+        Street districtEntity = path.stream()
+                .filter(s -> s.getLevel() == 3)
+                .findFirst()
+                .orElse(null);
+        
+        // 城市级别(level=2)
+        Street cityEntity = path.stream()
+                .filter(s -> s.getLevel() == 2)
+                .findFirst()
+                .orElse(null);
+        
+        // 匹配结果
+        result.put("streetMatch", streetEntity != null ? streetEntity.getId() : null);
+        result.put("districtMatch", districtEntity != null ? districtEntity.getId() : null);
+        result.put("cityMatch", cityEntity != null ? cityEntity.getId() : null);
+        
+        // 返回各级别的匹配范围(用于前端查询)
+        if (streetEntity != null) {
+            result.put("streetIds", Collections.singletonList(streetEntity.getId()));
+        }
+        
+        if (districtEntity != null) {
+            // 获取该区县下所有街道
+            List<Street> streets = streetMapper.findByParentId(districtEntity.getId());
+            result.put("districtStreetIds", streets.stream().map(Street::getId).collect(Collectors.toList()));
+        }
+        
+        if (cityEntity != null) {
+            // 获取该城市下所有区县
+            List<Street> districts = streetMapper.findByParentId(cityEntity.getId());
+            List<Long> districtIds = districts.stream().map(Street::getId).collect(Collectors.toList());
+            
+            // 获取所有区县下的街道
+            List<Long> cityStreetIds = new ArrayList<>();
+            for (Long districtId : districtIds) {
+                List<Street> streets = streetMapper.findByParentId(districtId);
+                cityStreetIds.addAll(streets.stream().map(Street::getId).collect(Collectors.toList()));
+            }
+            result.put("cityStreetIds", cityStreetIds);
+        }
+        
+        return result;
+    }
+
+    /**
+     * 按区域统计(省市区街道各级统计)
+     */
+    public Map<String, Object> getStatisticsByLevel(Integer level) {
+        Map<String, Object> result = new HashMap<>();
+        
+        List<Street> streets = streetMapper.findByLevel(level);
+        
+        for (Street street : streets) {
+            Map<String, Object> stats = new HashMap<>();
+            stats.put("id", street.getId());
+            stats.put("name", street.getFullName());
+            stats.put("level", street.getLevel());
+            
+            // 这里返回基础数据,具体家庭数和指导师数由Controller调用相应Service查询
+            result.put(street.getId().toString(), stats);
+        }
+        
+        return result;
+    }
+}