# 盘古用户平台 - 基础数据模块后端开发文档

---

| 文档信息     | 内容                          |
| -------- | --------------------------- |
| **文档版本** | V1.0                        |
| **项目名称** | 盘古用户平台（Pangu User Platform） |
| **模块名称** | 基础数据管理-后端开发                 |
| **编写团队** | pangu                       |
| **创建日期** | 2026-01-31                  |

---

## 1. 开发环境要求

| 环境            | 版本要求    | 说明           |
| ------------- | ------- | ------------ |
| JDK           | 17+     | Java运行环境     |
| Maven         | 3.8+    | 项目构建工具       |
| Spring Boot   | 3.3.x   | 应用框架         |
| MyBatis Plus  | 3.5.x   | ORM框架        |
| MySQL         | 8.0+    | 数据库          |
| Redis         | 7.x     | 缓存           |

---

## 2. 目录结构

```
pangu-admin/src/main/java/com/pangu/
├── base/                           # 基础数据模块
│   ├── controller/                 # 控制器层
│   │   ├── GradeController.java    # 年级管理
│   │   ├── PgClassController.java  # 班级管理
│   │   ├── SubjectController.java  # 学科管理
│   │   └── RegionController.java   # 区域管理
│   ├── service/                    # 服务层
│   │   ├── IGradeService.java
│   │   ├── IPgClassService.java
│   │   ├── ISubjectService.java
│   │   ├── IRegionService.java
│   │   └── impl/
│   │       ├── GradeServiceImpl.java
│   │       ├── PgClassServiceImpl.java
│   │       ├── SubjectServiceImpl.java
│   │       └── RegionServiceImpl.java
│   ├── mapper/                     # 数据访问层
│   │   ├── GradeMapper.java
│   │   ├── PgClassMapper.java
│   │   ├── SubjectMapper.java
│   │   └── RegionMapper.java
│   └── domain/                     # 实体类
│       ├── Grade.java
│       ├── PgClass.java
│       ├── Subject.java
│       └── Region.java
│
└── resources/mapper/base/          # MyBatis XML
    ├── GradeMapper.xml
    ├── PgClassMapper.xml
    ├── SubjectMapper.xml
    └── RegionMapper.xml
```

---

## 3. 实体类设计

### 3.1 年级实体（Grade.java）

```java
package com.pangu.base.domain;

import com.baomidou.mybatisplus.annotation.*;
import com.pangu.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

/**
 * 年级实体类
 * @author pangu
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("pg_grade")
public class Grade extends BaseEntity {
    
    private static final long serialVersionUID = 1L;
    
    /** 年级ID */
    @TableId(type = IdType.AUTO)
    private Long gradeId;
    
    /** 年级编码 */
    private String gradeCode;
    
    /** 年级名称 */
    @NotBlank(message = "年级名称不能为空")
    @Size(max = 50, message = "年级名称长度不能超过50个字符")
    private String gradeName;
    
    /** 显示顺序 */
    private Integer orderNum;
    
    /** 状态（0正常 1停用）*/
    private String status;
    
    /** 删除标志（0存在 1删除）*/
    @TableLogic
    private String delFlag;
}
```

### 3.2 班级实体（PgClass.java）

```java
package com.pangu.base.domain;

import com.baomidou.mybatisplus.annotation.*;
import com.pangu.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

/**
 * 班级实体类
 * 注意：类名使用PgClass避免与java.lang.Class冲突
 * @author pangu
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("pg_class")
public class PgClass extends BaseEntity {
    
    private static final long serialVersionUID = 1L;
    
    /** 班级ID */
    @TableId(type = IdType.AUTO)
    private Long classId;
    
    /** 班级编码 */
    private String classCode;
    
    /** 班级名称 */
    @NotBlank(message = "班级名称不能为空")
    @Size(max = 50, message = "班级名称长度不能超过50个字符")
    private String className;
    
    /** 显示顺序 */
    private Integer orderNum;
    
    /** 状态（0正常 1停用）*/
    private String status;
    
    /** 删除标志（0存在 1删除）*/
    @TableLogic
    private String delFlag;
}
```

### 3.3 学科实体（Subject.java）

```java
package com.pangu.base.domain;

import com.baomidou.mybatisplus.annotation.*;
import com.pangu.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

/**
 * 学科实体类
 * @author pangu
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("pg_subject")
public class Subject extends BaseEntity {
    
    private static final long serialVersionUID = 1L;
    
    /** 学科ID */
    @TableId(type = IdType.AUTO)
    private Long subjectId;
    
    /** 学科编码 */
    private String subjectCode;
    
    /** 学科名称 */
    @NotBlank(message = "学科名称不能为空")
    @Size(max = 50, message = "学科名称长度不能超过50个字符")
    private String subjectName;
    
    /** 显示顺序 */
    private Integer orderNum;
    
    /** 状态（0正常 1停用）*/
    private String status;
    
    /** 删除标志（0存在 1删除）*/
    @TableLogic
    private String delFlag;
}
```

### 3.4 区域实体（Region.java）

```java
package com.pangu.base.domain;

import com.baomidou.mybatisplus.annotation.*;
import com.pangu.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import java.util.List;

/**
 * 区域实体类
 * @author pangu
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("pg_region")
public class Region extends BaseEntity {
    
    private static final long serialVersionUID = 1L;
    
    /** 区域ID */
    @TableId(type = IdType.AUTO)
    private Long regionId;
    
    /** 父区域ID */
    private Long parentId;
    
    /** 区域名称 */
    @NotBlank(message = "区域名称不能为空")
    @Size(max = 100, message = "区域名称长度不能超过100个字符")
    private String regionName;
    
    /** 区域编码 */
    private String regionCode;
    
    /** 层级（1省 2市 3区）*/
    private Integer level;
    
    /** 祖级列表 */
    private String ancestors;
    
    /** 显示顺序 */
    private Integer orderNum;
    
    /** 状态（0正常 1停用）*/
    private String status;
    
    /** 删除标志（0存在 1删除）*/
    @TableLogic
    private String delFlag;
    
    /** 子区域（非数据库字段）*/
    @TableField(exist = false)
    private List<Region> children;
}
```

---

## 4. Mapper层设计

### 4.1 年级Mapper接口

```java
package com.pangu.base.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pangu.base.domain.Grade;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 年级Mapper接口
 * @author pangu
 */
@Mapper
public interface GradeMapper extends BaseMapper<Grade> {
    
    /**
     * 查询年级列表
     * @param grade 查询条件
     * @return 年级列表
     */
    List<Grade> selectGradeList(Grade grade);
    
    /**
     * 根据ID查询年级
     * @param gradeId 年级ID
     * @return 年级信息
     */
    Grade selectGradeById(Long gradeId);
    
    /**
     * 新增年级
     * @param grade 年级信息
     * @return 影响行数
     */
    int insertGrade(Grade grade);
    
    /**
     * 修改年级
     * @param grade 年级信息
     * @return 影响行数
     */
    int updateGrade(Grade grade);
    
    /**
     * 删除年级（软删除）
     * @param gradeId 年级ID
     * @return 影响行数
     */
    int deleteGradeById(Long gradeId);
    
    /**
     * 校验年级名称唯一
     * @param gradeName 年级名称
     * @return 年级信息
     */
    Grade checkGradeNameUnique(@Param("gradeName") String gradeName);
    
    /**
     * 查询最大编码
     * @return 最大编码
     */
    String selectMaxGradeCode();
}
```

### 4.2 年级Mapper XML

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pangu.base.mapper.GradeMapper">

    <resultMap id="GradeResult" type="Grade">
        <id property="gradeId" column="grade_id"/>
        <result property="gradeCode" column="grade_code"/>
        <result property="gradeName" column="grade_name"/>
        <result property="orderNum" column="order_num"/>
        <result property="status" column="status"/>
        <result property="delFlag" column="del_flag"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
        <result property="remark" column="remark"/>
    </resultMap>

    <sql id="selectGradeVo">
        select grade_id, grade_code, grade_name, order_num, status, del_flag,
               create_by, create_time, update_by, update_time, remark
        from pg_grade
        where del_flag = '0'
    </sql>

    <!-- 查询年级列表 -->
    <select id="selectGradeList" parameterType="Grade" resultMap="GradeResult">
        <include refid="selectGradeVo"/>
        <if test="gradeName != null and gradeName != ''">
            AND grade_name like concat('%', #{gradeName}, '%')
        </if>
        <if test="gradeCode != null and gradeCode != ''">
            AND grade_code = #{gradeCode}
        </if>
        <if test="status != null and status != ''">
            AND status = #{status}
        </if>
        order by order_num
    </select>

    <!-- 根据ID查询年级 -->
    <select id="selectGradeById" parameterType="Long" resultMap="GradeResult">
        <include refid="selectGradeVo"/>
        AND grade_id = #{gradeId}
    </select>

    <!-- 新增年级 -->
    <insert id="insertGrade" parameterType="Grade" useGeneratedKeys="true" keyProperty="gradeId">
        insert into pg_grade (
            grade_code,
            grade_name,
            order_num,
            status,
            del_flag,
            create_by,
            create_time,
            remark
        ) values (
            #{gradeCode},
            #{gradeName},
            #{orderNum},
            #{status},
            '0',
            #{createBy},
            #{createTime},
            #{remark}
        )
    </insert>

    <!-- 修改年级 -->
    <update id="updateGrade" parameterType="Grade">
        update pg_grade
        <set>
            <if test="gradeName != null and gradeName != ''">grade_name = #{gradeName},</if>
            <if test="orderNum != null">order_num = #{orderNum},</if>
            <if test="status != null">status = #{status},</if>
            <if test="remark != null">remark = #{remark},</if>
            update_by = #{updateBy},
            update_time = #{updateTime}
        </set>
        where grade_id = #{gradeId}
    </update>

    <!-- 删除年级（软删除）-->
    <update id="deleteGradeById" parameterType="Long">
        update pg_grade set del_flag = '1' where grade_id = #{gradeId}
    </update>

    <!-- 校验年级名称唯一 -->
    <select id="checkGradeNameUnique" parameterType="String" resultMap="GradeResult">
        <include refid="selectGradeVo"/>
        AND grade_name = #{gradeName}
        limit 1
    </select>

    <!-- 查询最大编码 -->
    <select id="selectMaxGradeCode" resultType="String">
        select max(grade_code) from pg_grade where del_flag = '0'
    </select>

</mapper>
```

### 4.3 区域Mapper接口

```java
package com.pangu.base.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.pangu.base.domain.Region;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * 区域Mapper接口
 * @author pangu
 */
@Mapper
public interface RegionMapper extends BaseMapper<Region> {
    
    /**
     * 查询区域列表
     * @param region 查询条件
     * @return 区域列表
     */
    List<Region> selectRegionList(Region region);
    
    /**
     * 根据ID查询区域
     * @param regionId 区域ID
     * @return 区域信息
     */
    Region selectRegionById(Long regionId);
    
    /**
     * 新增区域
     * @param region 区域信息
     * @return 影响行数
     */
    int insertRegion(Region region);
    
    /**
     * 修改区域
     * @param region 区域信息
     * @return 影响行数
     */
    int updateRegion(Region region);
    
    /**
     * 删除区域（软删除）
     * @param regionId 区域ID
     * @return 影响行数
     */
    int deleteRegionById(Long regionId);
    
    /**
     * 查询子区域数量
     * @param parentId 父区域ID
     * @return 子区域数量
     */
    int countChildByParentId(@Param("parentId") Long parentId);
    
    /**
     * 查询指定层级的最大编码
     * @param level 层级
     * @return 最大编码
     */
    String selectMaxCodeByLevel(@Param("level") Integer level);
    
    /**
     * 查询指定父级下的最大编码
     * @param parentId 父区域ID
     * @return 最大编码
     */
    String selectMaxCodeByParent(@Param("parentId") Long parentId);
}
```

### 4.4 区域Mapper XML

```xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.pangu.base.mapper.RegionMapper">

    <resultMap id="RegionResult" type="Region">
        <id property="regionId" column="region_id"/>
        <result property="parentId" column="parent_id"/>
        <result property="regionName" column="region_name"/>
        <result property="regionCode" column="region_code"/>
        <result property="level" column="level"/>
        <result property="ancestors" column="ancestors"/>
        <result property="orderNum" column="order_num"/>
        <result property="status" column="status"/>
        <result property="delFlag" column="del_flag"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
    </resultMap>

    <sql id="selectRegionVo">
        select region_id, parent_id, region_name, region_code, level, ancestors,
               order_num, status, del_flag, create_by, create_time, update_by, update_time
        from pg_region
        where del_flag = '0'
    </sql>

    <!-- 查询区域列表 -->
    <select id="selectRegionList" parameterType="Region" resultMap="RegionResult">
        <include refid="selectRegionVo"/>
        <if test="regionName != null and regionName != ''">
            AND region_name like concat('%', #{regionName}, '%')
        </if>
        <if test="status != null and status != ''">
            AND status = #{status}
        </if>
        <if test="parentId != null">
            AND parent_id = #{parentId}
        </if>
        order by order_num
    </select>

    <!-- 根据ID查询区域 -->
    <select id="selectRegionById" parameterType="Long" resultMap="RegionResult">
        <include refid="selectRegionVo"/>
        AND region_id = #{regionId}
    </select>

    <!-- 新增区域 -->
    <insert id="insertRegion" parameterType="Region" useGeneratedKeys="true" keyProperty="regionId">
        insert into pg_region (
            parent_id,
            region_name,
            region_code,
            level,
            ancestors,
            order_num,
            status,
            del_flag,
            create_by,
            create_time
        ) values (
            #{parentId},
            #{regionName},
            #{regionCode},
            #{level},
            #{ancestors},
            #{orderNum},
            #{status},
            '0',
            #{createBy},
            #{createTime}
        )
    </insert>

    <!-- 修改区域 -->
    <update id="updateRegion" parameterType="Region">
        update pg_region
        <set>
            <if test="regionName != null and regionName != ''">region_name = #{regionName},</if>
            <if test="orderNum != null">order_num = #{orderNum},</if>
            <if test="status != null">status = #{status},</if>
            update_by = #{updateBy},
            update_time = #{updateTime}
        </set>
        where region_id = #{regionId}
    </update>

    <!-- 删除区域（软删除）-->
    <update id="deleteRegionById" parameterType="Long">
        update pg_region set del_flag = '1' where region_id = #{regionId}
    </update>

    <!-- 查询子区域数量 -->
    <select id="countChildByParentId" parameterType="Long" resultType="int">
        select count(*) from pg_region where parent_id = #{parentId} and del_flag = '0'
    </select>

    <!-- 查询指定层级的最大编码 -->
    <select id="selectMaxCodeByLevel" parameterType="int" resultType="String">
        select max(region_code) from pg_region where level = #{level} and del_flag = '0'
    </select>

    <!-- 查询指定父级下的最大编码 -->
    <select id="selectMaxCodeByParent" parameterType="Long" resultType="String">
        select max(region_code) from pg_region where parent_id = #{parentId} and del_flag = '0'
    </select>

</mapper>
```

---

## 5. Service层设计

### 5.1 年级Service接口

```java
package com.pangu.base.service;

import com.pangu.base.domain.Grade;

import java.util.List;

/**
 * 年级管理Service接口
 * @author pangu
 */
public interface IGradeService {
    
    /**
     * 查询年级分页列表
     * @param grade 查询条件
     * @return 年级列表
     */
    List<Grade> selectGradeList(Grade grade);
    
    /**
     * 查询年级选项列表（下拉用）
     * @return 启用状态的年级列表
     */
    List<Grade> selectGradeOptions();
    
    /**
     * 根据ID查询年级
     * @param gradeId 年级ID
     * @return 年级信息
     */
    Grade selectGradeById(Long gradeId);
    
    /**
     * 新增年级
     * @param grade 年级信息
     * @return 影响行数
     */
    int insertGrade(Grade grade);
    
    /**
     * 修改年级
     * @param grade 年级信息
     * @return 影响行数
     */
    int updateGrade(Grade grade);
    
    /**
     * 删除年级
     * @param gradeId 年级ID
     * @return 影响行数
     */
    int deleteGradeById(Long gradeId);
    
    /**
     * 校验年级名称是否唯一
     * @param grade 年级信息
     * @return true-唯一 false-不唯一
     */
    boolean checkGradeNameUnique(Grade grade);
    
    /**
     * 检查年级是否被学校使用
     * @param gradeId 年级ID
     * @return true-被使用 false-未使用
     */
    boolean checkGradeExistSchool(Long gradeId);
}
```

### 5.2 年级Service实现

```java
package com.pangu.base.service.impl;

import com.pangu.base.domain.Grade;
import com.pangu.base.mapper.GradeMapper;
import com.pangu.base.mapper.SchoolGradeMapper;
import com.pangu.base.service.IGradeService;
import com.pangu.common.utils.DateUtils;
import com.pangu.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 年级管理Service实现
 * @author pangu
 */
@Service
public class GradeServiceImpl implements IGradeService {

    @Autowired
    private GradeMapper gradeMapper;
    
    @Autowired
    private SchoolGradeMapper schoolGradeMapper;

    @Override
    public List<Grade> selectGradeList(Grade grade) {
        return gradeMapper.selectGradeList(grade);
    }
    
    @Override
    public List<Grade> selectGradeOptions() {
        Grade grade = new Grade();
        grade.setStatus("0"); // 只查启用的
        return gradeMapper.selectGradeList(grade);
    }

    @Override
    public Grade selectGradeById(Long gradeId) {
        return gradeMapper.selectGradeById(gradeId);
    }

    @Override
    public int insertGrade(Grade grade) {
        // 生成年级编码
        grade.setGradeCode(generateGradeCode());
        grade.setCreateTime(DateUtils.getNowDate());
        return gradeMapper.insertGrade(grade);
    }
    
    /**
     * 生成年级编码
     * 规则：GRD + 3位序号（如GRD001）
     */
    private String generateGradeCode() {
        String maxCode = gradeMapper.selectMaxGradeCode();
        if (StringUtils.isEmpty(maxCode)) {
            return "GRD001";
        }
        // 提取序号并+1
        int num = Integer.parseInt(maxCode.substring(3)) + 1;
        return String.format("GRD%03d", num);
    }

    @Override
    public int updateGrade(Grade grade) {
        grade.setUpdateTime(DateUtils.getNowDate());
        return gradeMapper.updateGrade(grade);
    }

    @Override
    public int deleteGradeById(Long gradeId) {
        return gradeMapper.deleteGradeById(gradeId);
    }

    @Override
    public boolean checkGradeNameUnique(Grade grade) {
        Long gradeId = grade.getGradeId() == null ? -1L : grade.getGradeId();
        Grade info = gradeMapper.checkGradeNameUnique(grade.getGradeName());
        // 名称不存在，或者是当前记录自己，则唯一
        return info == null || info.getGradeId().equals(gradeId);
    }

    @Override
    public boolean checkGradeExistSchool(Long gradeId) {
        int count = schoolGradeMapper.countByGradeId(gradeId);
        return count > 0;
    }
}
```

### 5.3 区域Service接口

```java
package com.pangu.base.service;

import com.pangu.base.domain.Region;

import java.util.List;

/**
 * 区域管理Service接口
 * @author pangu
 */
public interface IRegionService {
    
    /**
     * 查询区域树
     * @return 树形结构的区域列表
     */
    List<Region> selectRegionTree();
    
    /**
     * 根据ID查询区域
     * @param regionId 区域ID
     * @return 区域信息
     */
    Region selectRegionById(Long regionId);
    
    /**
     * 新增区域
     * @param region 区域信息
     * @return 影响行数
     */
    int insertRegion(Region region);
    
    /**
     * 修改区域
     * @param region 区域信息
     * @return 影响行数
     */
    int updateRegion(Region region);
    
    /**
     * 删除区域
     * @param regionId 区域ID
     * @return 影响行数
     */
    int deleteRegionById(Long regionId);
    
    /**
     * 是否存在子区域
     * @param regionId 区域ID
     * @return true-存在 false-不存在
     */
    boolean hasChildRegion(Long regionId);
    
    /**
     * 检查区域是否被学校使用
     * @param regionId 区域ID
     * @return true-被使用 false-未使用
     */
    boolean checkRegionExistSchool(Long regionId);
    
    /**
     * 刷新区域缓存
     */
    void refreshRegionCache();
}
```

### 5.4 区域Service实现

```java
package com.pangu.base.service.impl;

import com.pangu.base.domain.Region;
import com.pangu.base.mapper.MemberMapper;
import com.pangu.base.mapper.RegionMapper;
import com.pangu.base.mapper.SchoolMapper;
import com.pangu.base.service.IRegionService;
import com.pangu.common.core.redis.RedisCache;
import com.pangu.common.utils.DateUtils;
import com.pangu.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 区域管理Service实现
 * @author pangu
 */
@Service
public class RegionServiceImpl implements IRegionService {

    /** 区域树缓存Key */
    private static final String REGION_TREE_KEY = "base:region:tree";
    
    /** 缓存过期时间（小时）*/
    private static final int CACHE_EXPIRE_HOURS = 24;

    @Autowired
    private RegionMapper regionMapper;
    
    @Autowired
    private SchoolMapper schoolMapper;
    
    @Autowired
    private RedisCache redisCache;

    @Override
    public List<Region> selectRegionTree() {
        // 优先从缓存获取
        List<Region> cacheList = redisCache.getCacheObject(REGION_TREE_KEY);
        if (cacheList != null && !cacheList.isEmpty()) {
            return cacheList;
        }
        
        // 查询所有区域（未删除的）
        List<Region> regionList = regionMapper.selectRegionList(new Region());
        
        // 构建树形结构
        List<Region> tree = buildRegionTree(regionList);
        
        // 放入缓存
        redisCache.setCacheObject(REGION_TREE_KEY, tree, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
        
        return tree;
    }
    
    /**
     * 构建区域树形结构
     * @param regionList 区域列表
     * @return 树形结构
     */
    private List<Region> buildRegionTree(List<Region> regionList) {
        if (regionList == null || regionList.isEmpty()) {
            return new ArrayList<>();
        }
        
        // 构建ID到节点的映射
        Map<Long, Region> regionMap = regionList.stream()
            .collect(Collectors.toMap(Region::getRegionId, r -> r));
        
        List<Region> rootList = new ArrayList<>();
        
        for (Region region : regionList) {
            if (region.getParentId() == 0) {
                // 顶级节点
                rootList.add(region);
            } else {
                // 子节点，挂载到父节点下
                Region parent = regionMap.get(region.getParentId());
                if (parent != null) {
                    if (parent.getChildren() == null) {
                        parent.setChildren(new ArrayList<>());
                    }
                    parent.getChildren().add(region);
                }
            }
        }
        
        // 对每一层按orderNum排序
        sortRegionTree(rootList);
        
        return rootList;
    }
    
    /**
     * 递归排序区域树
     */
    private void sortRegionTree(List<Region> regions) {
        if (regions == null || regions.isEmpty()) {
            return;
        }
        regions.sort(Comparator.comparingInt(r -> r.getOrderNum() == null ? 0 : r.getOrderNum()));
        for (Region region : regions) {
            if (region.getChildren() != null) {
                sortRegionTree(region.getChildren());
            }
        }
    }

    @Override
    public Region selectRegionById(Long regionId) {
        return regionMapper.selectRegionById(regionId);
    }

    @Override
    public int insertRegion(Region region) {
        // 设置层级和祖级列表
        if (region.getParentId() == null || region.getParentId() == 0) {
            region.setParentId(0L);
            region.setLevel(1);
            region.setAncestors("0");
        } else {
            Region parent = regionMapper.selectRegionById(region.getParentId());
            if (parent == null) {
                throw new RuntimeException("父区域不存在");
            }
            region.setLevel(parent.getLevel() + 1);
            region.setAncestors(parent.getAncestors() + "," + parent.getRegionId());
        }
        
        // 生成区域编码
        region.setRegionCode(generateRegionCode(region));
        region.setCreateTime(DateUtils.getNowDate());
        
        // 默认状态为启用
        if (region.getStatus() == null) {
            region.setStatus("0");
        }
        
        int rows = regionMapper.insertRegion(region);
        
        // 清除缓存
        if (rows > 0) {
            refreshRegionCache();
        }
        
        return rows;
    }
    
    /**
     * 生成区域编码
     * 规则：
     * - 省级：REG + 2位序号（如REG01）
     * - 市级：父编码 + 2位序号（如REG0101）
     * - 区级：父编码 + 2位序号（如REG010101）
     */
    private String generateRegionCode(Region region) {
        if (region.getParentId() == 0) {
            // 省级编码
            String maxCode = regionMapper.selectMaxCodeByLevel(1);
            if (StringUtils.isEmpty(maxCode)) {
                return "REG01";
            }
            int num = Integer.parseInt(maxCode.substring(3)) + 1;
            return String.format("REG%02d", num);
        } else {
            // 市/区级编码
            Region parent = regionMapper.selectRegionById(region.getParentId());
            String maxCode = regionMapper.selectMaxCodeByParent(region.getParentId());
            if (StringUtils.isEmpty(maxCode)) {
                return parent.getRegionCode() + "01";
            }
            String suffix = maxCode.substring(parent.getRegionCode().length());
            int num = Integer.parseInt(suffix) + 1;
            return parent.getRegionCode() + String.format("%02d", num);
        }
    }

    @Override
    public int updateRegion(Region region) {
        region.setUpdateTime(DateUtils.getNowDate());
        int rows = regionMapper.updateRegion(region);
        
        // 清除缓存
        if (rows > 0) {
            refreshRegionCache();
        }
        
        return rows;
    }

    @Override
    public int deleteRegionById(Long regionId) {
        int rows = regionMapper.deleteRegionById(regionId);
        
        // 清除缓存
        if (rows > 0) {
            refreshRegionCache();
        }
        
        return rows;
    }

    @Override
    public boolean hasChildRegion(Long regionId) {
        int count = regionMapper.countChildByParentId(regionId);
        return count > 0;
    }

    @Override
    public boolean checkRegionExistSchool(Long regionId) {
        int count = schoolMapper.countByRegionId(regionId);
        return count > 0;
    }
    
    @Override
    public void refreshRegionCache() {
        redisCache.deleteObject(REGION_TREE_KEY);
    }
}
```

---

## 6. Controller层设计

### 6.1 年级Controller

```java
package com.pangu.base.controller;

import com.pangu.base.domain.Grade;
import com.pangu.base.service.IGradeService;
import com.pangu.common.annotation.Log;
import com.pangu.common.core.controller.BaseController;
import com.pangu.common.core.domain.AjaxResult;
import com.pangu.common.core.page.TableDataInfo;
import com.pangu.common.enums.BusinessType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 年级管理Controller
 * @author pangu
 */
@RestController
@RequestMapping("/api/grade")
public class GradeController extends BaseController {

    @Autowired
    private IGradeService gradeService;

    /**
     * 获取年级分页列表
     */
    @PreAuthorize("@ss.hasPermi('base:grade:list')")
    @GetMapping("/list")
    public TableDataInfo list(Grade grade) {
        startPage();
        List<Grade> list = gradeService.selectGradeList(grade);
        return getDataTable(list);
    }

    /**
     * 获取年级选项列表（下拉用）
     */
    @GetMapping("/options")
    public AjaxResult options() {
        List<Grade> list = gradeService.selectGradeOptions();
        return success(list);
    }

    /**
     * 获取年级详情
     */
    @PreAuthorize("@ss.hasPermi('base:grade:query')")
    @GetMapping("/{gradeId}")
    public AjaxResult getInfo(@PathVariable Long gradeId) {
        return success(gradeService.selectGradeById(gradeId));
    }

    /**
     * 新增年级
     */
    @PreAuthorize("@ss.hasPermi('base:grade:add')")
    @Log(title = "年级管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@Validated @RequestBody Grade grade) {
        // 校验名称唯一
        if (!gradeService.checkGradeNameUnique(grade)) {
            return error("新增年级'" + grade.getGradeName() + "'失败，年级名称已存在");
        }
        grade.setCreateBy(getUsername());
        return toAjax(gradeService.insertGrade(grade));
    }

    /**
     * 修改年级
     */
    @PreAuthorize("@ss.hasPermi('base:grade:edit')")
    @Log(title = "年级管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@Validated @RequestBody Grade grade) {
        // 校验名称唯一
        if (!gradeService.checkGradeNameUnique(grade)) {
            return error("修改年级'" + grade.getGradeName() + "'失败，年级名称已存在");
        }
        grade.setUpdateBy(getUsername());
        return toAjax(gradeService.updateGrade(grade));
    }

    /**
     * 删除年级
     */
    @PreAuthorize("@ss.hasPermi('base:grade:remove')")
    @Log(title = "年级管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{gradeId}")
    public AjaxResult remove(@PathVariable Long gradeId) {
        // 检查是否被学校引用
        if (gradeService.checkGradeExistSchool(gradeId)) {
            return error("该年级已被学校使用，不能删除");
        }
        return toAjax(gradeService.deleteGradeById(gradeId));
    }
}
```

### 6.2 区域Controller

```java
package com.pangu.base.controller;

import com.pangu.base.domain.Region;
import com.pangu.base.service.IRegionService;
import com.pangu.common.annotation.Log;
import com.pangu.common.core.controller.BaseController;
import com.pangu.common.core.domain.AjaxResult;
import com.pangu.common.enums.BusinessType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 区域管理Controller
 * @author pangu
 */
@RestController
@RequestMapping("/api/region")
public class RegionController extends BaseController {

    @Autowired
    private IRegionService regionService;

    /**
     * 获取区域树
     */
    @GetMapping("/tree")
    public AjaxResult tree() {
        List<Region> tree = regionService.selectRegionTree();
        return success(tree);
    }

    /**
     * 获取区域详情
     */
    @PreAuthorize("@ss.hasPermi('base:region:query')")
    @GetMapping("/{regionId}")
    public AjaxResult getInfo(@PathVariable Long regionId) {
        return success(regionService.selectRegionById(regionId));
    }

    /**
     * 新增区域
     */
    @PreAuthorize("@ss.hasPermi('base:region:add')")
    @Log(title = "区域管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@Validated @RequestBody Region region) {
        region.setCreateBy(getUsername());
        return toAjax(regionService.insertRegion(region));
    }

    /**
     * 修改区域
     */
    @PreAuthorize("@ss.hasPermi('base:region:edit')")
    @Log(title = "区域管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@Validated @RequestBody Region region) {
        // 父级不能选择自己
        if (region.getRegionId().equals(region.getParentId())) {
            return error("修改区域'" + region.getRegionName() + "'失败，上级区域不能选择自己");
        }
        region.setUpdateBy(getUsername());
        return toAjax(regionService.updateRegion(region));
    }

    /**
     * 删除区域
     */
    @PreAuthorize("@ss.hasPermi('base:region:remove')")
    @Log(title = "区域管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{regionId}")
    public AjaxResult remove(@PathVariable Long regionId) {
        // 检查是否有子区域
        if (regionService.hasChildRegion(regionId)) {
            return error("存在下级区域，不能删除");
        }
        // 检查是否被学校引用
        if (regionService.checkRegionExistSchool(regionId)) {
            return error("该区域已被学校使用，不能删除");
        }
        return toAjax(regionService.deleteRegionById(regionId));
    }
    
    /**
     * 刷新区域缓存
     */
    @PreAuthorize("@ss.hasPermi('base:region:remove')")
    @Log(title = "区域管理", businessType = BusinessType.CLEAN)
    @DeleteMapping("/refreshCache")
    public AjaxResult refreshCache() {
        regionService.refreshRegionCache();
        return success();
    }
}
```

---

## 7. 权限配置

### 7.1 菜单权限

在系统菜单中添加以下菜单：

| 菜单名称 | 菜单路径          | 权限标识             | 菜单类型 |
| ---- | ------------- | ---------------- | ---- |
| 基础数据 | /base         | -                | 目录   |
| 年级管理 | /base/grade   | base:grade:list  | 菜单   |
| 班级管理 | /base/class   | base:class:list  | 菜单   |
| 学科管理 | /base/subject | base:subject:list | 菜单   |
| 区域管理 | /base/region  | base:region:list | 菜单   |

### 7.2 按钮权限

| 功能   | 权限标识              |
| ---- | ----------------- |
| 年级查询 | base:grade:query  |
| 年级新增 | base:grade:add    |
| 年级修改 | base:grade:edit   |
| 年级删除 | base:grade:remove |
| 班级查询 | base:class:query  |
| 班级新增 | base:class:add    |
| 班级修改 | base:class:edit   |
| 班级删除 | base:class:remove |
| 学科查询 | base:subject:query |
| 学科新增 | base:subject:add   |
| 学科修改 | base:subject:edit  |
| 学科删除 | base:subject:remove |
| 区域查询 | base:region:query  |
| 区域新增 | base:region:add    |
| 区域修改 | base:region:edit   |
| 区域删除 | base:region:remove |

---

## 8. 单元测试

### 8.1 年级Service测试

```java
package com.pangu.base.service;

import com.pangu.base.domain.Grade;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 年级Service单元测试
 * @author pangu
 */
@SpringBootTest
public class GradeServiceTest {

    @Autowired
    private IGradeService gradeService;

    @Test
    public void testSelectGradeList() {
        Grade grade = new Grade();
        List<Grade> list = gradeService.selectGradeList(grade);
        assertNotNull(list);
        assertTrue(list.size() > 0);
    }

    @Test
    public void testSelectGradeOptions() {
        List<Grade> list = gradeService.selectGradeOptions();
        assertNotNull(list);
        // 选项列表应该只包含启用的
        for (Grade grade : list) {
            assertEquals("0", grade.getStatus());
        }
    }

    @Test
    public void testInsertGrade() {
        Grade grade = new Grade();
        grade.setGradeName("测试年级");
        grade.setOrderNum(99);
        grade.setStatus("0");
        grade.setCreateBy("test");
        
        int result = gradeService.insertGrade(grade);
        assertEquals(1, result);
        assertNotNull(grade.getGradeId());
        assertNotNull(grade.getGradeCode());
        assertTrue(grade.getGradeCode().startsWith("GRD"));
        
        // 清理测试数据
        gradeService.deleteGradeById(grade.getGradeId());
    }

    @Test
    public void testCheckGradeNameUnique() {
        // 新增时检查
        Grade grade = new Grade();
        grade.setGradeName("一年级"); // 假设已存在
        boolean unique = gradeService.checkGradeNameUnique(grade);
        assertFalse(unique);
        
        // 不存在的名称
        grade.setGradeName("不存在的年级");
        unique = gradeService.checkGradeNameUnique(grade);
        assertTrue(unique);
    }
}
```

### 8.2 区域Service测试

```java
package com.pangu.base.service;

import com.pangu.base.domain.Region;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 区域Service单元测试
 * @author pangu
 */
@SpringBootTest
public class RegionServiceTest {

    @Autowired
    private IRegionService regionService;

    @Test
    public void testSelectRegionTree() {
        List<Region> tree = regionService.selectRegionTree();
        assertNotNull(tree);
        assertTrue(tree.size() > 0);
        
        // 验证树形结构
        Region root = tree.get(0);
        assertEquals(Long.valueOf(0), root.getParentId());
        assertEquals(Integer.valueOf(1), root.getLevel());
    }

    @Test
    public void testInsertRegion() {
        // 新增省级区域
        Region region = new Region();
        region.setParentId(0L);
        region.setRegionName("测试省");
        region.setOrderNum(99);
        region.setCreateBy("test");
        
        int result = regionService.insertRegion(region);
        assertEquals(1, result);
        assertNotNull(region.getRegionId());
        assertNotNull(region.getRegionCode());
        assertEquals(Integer.valueOf(1), region.getLevel());
        assertEquals("0", region.getAncestors());
        
        // 清理测试数据
        regionService.deleteRegionById(region.getRegionId());
    }

    @Test
    public void testHasChildRegion() {
        // 湖北省应该有子区域
        boolean hasChild = regionService.hasChildRegion(1L);
        assertTrue(hasChild);
    }

    @Test
    public void testRefreshRegionCache() {
        // 第一次查询，建立缓存
        List<Region> tree1 = regionService.selectRegionTree();
        
        // 刷新缓存
        regionService.refreshRegionCache();
        
        // 第二次查询，重新从数据库加载
        List<Region> tree2 = regionService.selectRegionTree();
        
        // 数据应该一致
        assertEquals(tree1.size(), tree2.size());
    }
}
```

---

## 9. 开发检查清单

### 9.1 年级管理

- [ ] Grade 实体类
- [ ] GradeMapper 接口
- [ ] GradeMapper.xml
- [ ] IGradeService 接口
- [ ] GradeServiceImpl 实现
- [ ] GradeController
- [ ] 单元测试
- [ ] 权限配置

### 9.2 班级管理

- [ ] PgClass 实体类
- [ ] PgClassMapper 接口
- [ ] PgClassMapper.xml
- [ ] IPgClassService 接口
- [ ] PgClassServiceImpl 实现
- [ ] PgClassController
- [ ] 单元测试
- [ ] 权限配置

### 9.3 学科管理

- [ ] Subject 实体类
- [ ] SubjectMapper 接口
- [ ] SubjectMapper.xml
- [ ] ISubjectService 接口
- [ ] SubjectServiceImpl 实现
- [ ] SubjectController
- [ ] 单元测试
- [ ] 权限配置

### 9.4 区域管理

- [ ] Region 实体类
- [ ] RegionMapper 接口
- [ ] RegionMapper.xml
- [ ] IRegionService 接口
- [ ] RegionServiceImpl 实现（含缓存）
- [ ] RegionController
- [ ] 单元测试
- [ ] 权限配置

---

*文档结束*
