pangu-user-platform/docs/05-技术方案/基础数据模块_后端开发文档.md

1579 lines
42 KiB
Markdown
Raw Normal View History

# 盘古用户平台 - 基础数据模块后端开发文档
---
| 文档信息 | 内容 |
| -------- | --------------------------- |
| **文档版本** | 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
- [ ] 单元测试
- [ ] 权限配置
---
*文档结束*