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

1579 lines
42 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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