userid
build / build-push (push) Successful in 1m20s

This commit is contained in:
wanghu
2026-09-20 11:13:57 +08:00
parent 55db99e8c3
commit c4f6942ab2
10 changed files with 148 additions and 48 deletions
@@ -11,7 +11,7 @@ import java.time.LocalDateTime;
/** /**
* JPA 审计监听器:在实体持久化/更新前自动填充创建人/更新人(平台当前登录人)与时间戳。 * JPA 审计监听器:在实体持久化/更新前自动填充创建人/更新人(平台当前登录人)与时间戳。
* 对应 {@link BaseEntity} 的 createBy / updateBy / createTime / updateTime 字段。 * 对应 {@link BaseEntity} 的 createBy / updateBy / createTime / updateTime 字段。
* 当前登录人取自 {@link UserContext}(用户中心数字ID,字符串形式落审计列)。 * 当前登录人取自 {@link UserContext}(用户中心数字ID,直接以 INT 落审计列)。
*/ */
@Component @Component
public class HomeworkAuditListener { public class HomeworkAuditListener {
@@ -24,9 +24,9 @@ public class HomeworkAuditListener {
Integer userId = UserContext.getUserId(); Integer userId = UserContext.getUserId();
if (userId != null) { if (userId != null) {
if (entity.getCreateBy() == null) { if (entity.getCreateBy() == null) {
entity.setCreateBy(String.valueOf(userId)); entity.setCreateBy(userId);
} }
entity.setUpdateBy(String.valueOf(userId)); entity.setUpdateBy(userId);
} }
} }
@@ -35,7 +35,7 @@ public class HomeworkAuditListener {
entity.setUpdateTime(LocalDateTime.now()); entity.setUpdateTime(LocalDateTime.now());
Integer userId = UserContext.getUserId(); Integer userId = UserContext.getUserId();
if (userId != null) { if (userId != null) {
entity.setUpdateBy(String.valueOf(userId)); entity.setUpdateBy(userId);
} }
} }
@@ -24,10 +24,10 @@ public abstract class BaseEntity implements Serializable {
private Integer id; private Integer id;
/** /**
* 创建人ID(平台用户ID) * 创建人ID(用户中心数字ID,与业务人员标识同一 ID 空间)
*/ */
@Column(name = "create_by", updatable = false) @Column(name = "create_by", updatable = false)
private String createBy; private Integer createBy;
/** /**
* 创建时间 * 创建时间
@@ -36,10 +36,10 @@ public abstract class BaseEntity implements Serializable {
private LocalDateTime createTime; private LocalDateTime createTime;
/** /**
* 更新人ID(平台用户ID) * 更新人ID(用户中心数字ID)
*/ */
@Column(name = "update_by") @Column(name = "update_by")
private String updateBy; private Integer updateBy;
/** /**
* 更新时间 * 更新时间
@@ -18,6 +18,13 @@ import javax.persistence.Table;
@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) @EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
public class SysDict extends BaseEntity { public class SysDict extends BaseEntity {
/**
* 学校ID(多租户隔离:新增时从当前用户角色填充,查询时按校过滤;
* 存量数据为 NULL,视为全局字典,全校可见)。
*/
@Column(name = "sch_id")
private Integer schId;
/** 字典类型(如 "dept"=发文部门) */ /** 字典类型(如 "dept"=发文部门) */
@Column(name = "dict_type", length = 50) @Column(name = "dict_type", length = 50)
private String dictType; private String dictType;
@@ -73,9 +73,11 @@ public class HomeworkVo {
*/ */
private com.yunhai.homework.model.v2.ProgressVo progress; private com.yunhai.homework.model.v2.ProgressVo progress;
private String createBy; /** 创建人ID(用户中心数字ID) */
private Integer createBy;
private LocalDateTime createTime; private LocalDateTime createTime;
private String updateBy; /** 更新人ID(用户中心数字ID) */
private Integer updateBy;
private LocalDateTime updateTime; private LocalDateTime updateTime;
/** /**
@@ -2,14 +2,44 @@ package com.yunhai.homework.repository;
import com.yunhai.homework.entity.SysDict; import com.yunhai.homework.entity.SysDict;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
public interface SysDictRepository extends JpaRepository<SysDict, Integer> { public interface SysDictRepository extends JpaRepository<SysDict, Integer> {
// ---------------- 存量/无学校归属(schId 为空时不过滤,同 homework 多租户口径) ----------------
/** 全量(含禁用),用于管理列表与重名/序号计算 */
List<SysDict> findByDictTypeOrderBySortOrderAsc(String dictType); List<SysDict> findByDictTypeOrderBySortOrderAsc(String dictType);
/** 启用项(下拉框用) */
List<SysDict> findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag); List<SysDict> findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag);
/** 按类型+key 取单条字典(发文部门名称解析用) */ /** 按类型+key 取单条字典 */
Optional<SysDict> findByDictTypeAndDictKey(String dictType, String dictKey); Optional<SysDict> findByDictTypeAndDictKey(String dictType, String dictKey);
// ---------------- 多租户:本校 + 全局(sch_id 为 NULL 的存量视为全局,全校可见) ----------------
/** 可见全量(含禁用):本校 + 全局 */
@Query("select d from SysDict d where d.dictType = :dictType " +
"and (d.schId = :schId or d.schId is null) order by d.sortOrder asc")
List<SysDict> findVisibleBySchIdAndDictType(@Param("schId") Integer schId, @Param("dictType") String dictType);
/** 可见启用项:本校 + 全局(下拉框用) */
@Query("select d from SysDict d where d.dictType = :dictType and d.enableFlag = :enableFlag " +
"and (d.schId = :schId or d.schId is null) order by d.sortOrder asc")
List<SysDict> findVisibleEnabledBySchIdAndDictType(@Param("schId") Integer schId,
@Param("dictType") String dictType,
@Param("enableFlag") Integer enableFlag);
/** 可见单条:本校优先,其次全局;同 key 多条时按本校优先 + id 升序,服务侧取首条 */
@Query("select d from SysDict d where d.dictType = :dictType and d.dictKey = :dictKey " +
"and (d.schId = :schId or d.schId is null) " +
"order by case when d.schId = :schId then 0 else 1 end asc, d.id asc")
List<SysDict> findVisibleBySchIdAndDictTypeAndDictKey(@Param("schId") Integer schId,
@Param("dictType") String dictType,
@Param("dictKey") String dictKey);
} }
@@ -69,7 +69,10 @@ public interface IDictService {
* 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。
* *
* @param deptId 作业 deptId(存的是 sys_dict.dict_key) * @param deptId 作业 deptId(存的是 sys_dict.dict_key)
* @param schId 作业所属学校ID(展示场景传 homework.schId,写入场景传当前登录人学校;
* 多租户下优先本校字典、其次全局字典,不会命中其他学校同名 key;
* 为 null 时退化为全局匹配,兼容存量数据)
* @return 部门名称;无匹配时返回 null * @return 部门名称;无匹配时返回 null
*/ */
String resolveDeptName(String deptId); String resolveDeptName(String deptId, Integer schId);
} }
@@ -9,6 +9,7 @@ import com.yunhai.homework.repository.SmsModuleRepository;
import com.yunhai.homework.repository.SmsSchoolConfigRepository; import com.yunhai.homework.repository.SmsSchoolConfigRepository;
import com.yunhai.homework.repository.SysDictRepository; import com.yunhai.homework.repository.SysDictRepository;
import com.yunhai.homework.service.IDictService; import com.yunhai.homework.service.IDictService;
import com.yunhai.homework.utils.UserContext;
import com.yunhai.platform.apistd.BizException; import com.yunhai.platform.apistd.BizException;
import com.yunhai.platform.utils.exception.ErrorCode; import com.yunhai.platform.utils.exception.ErrorCode;
import org.slf4j.Logger; import org.slf4j.Logger;
@@ -19,6 +20,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import javax.persistence.criteria.Predicate;
import java.util.*; import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -64,7 +66,7 @@ public class DictService implements IDictService {
if (dictType == null) { if (dictType == null) {
throw new BizException(ErrorCode.PARAM_ERROR, "参数错误"); throw new BizException(ErrorCode.PARAM_ERROR, "参数错误");
} }
List<SysDict> list = sysDictRepository.findByDictTypeAndEnableFlagOrderBySortOrderAsc(dictType, 1); List<SysDict> list = visibleEnabledList(dictType);
if (list.isEmpty()) { if (list.isEmpty()) {
throw new BizException(ErrorCode.FORBIDDEN, "尚未添加发文部门,请联系管理员添加!"); throw new BizException(ErrorCode.FORBIDDEN, "尚未添加发文部门,请联系管理员添加!");
} }
@@ -90,7 +92,7 @@ public class DictService implements IDictService {
// 非 DEPT 类型需超管权限,本系统暂只支持 DEPT // 非 DEPT 类型需超管权限,本系统暂只支持 DEPT
throw new BizException(ErrorCode.FORBIDDEN, "非超管不得操作!"); throw new BizException(ErrorCode.FORBIDDEN, "非超管不得操作!");
} }
List<SysDict> list = sysDictRepository.findByDictTypeOrderBySortOrderAsc(dictType); List<SysDict> list = visibleList(dictType);
return list.stream() return list.stream()
.sorted(Comparator.comparingInt(d -> d.getSortOrder() == null ? 999 : d.getSortOrder())) .sorted(Comparator.comparingInt(d -> d.getSortOrder() == null ? 999 : d.getSortOrder()))
.map(d -> { .map(d -> {
@@ -113,21 +115,24 @@ public class DictService implements IDictService {
if (!StringUtils.hasText(name)) { if (!StringUtils.hasText(name)) {
throw new BizException(ErrorCode.PARAM_ERROR, "输入字符串格式不正确!"); throw new BizException(ErrorCode.PARAM_ERROR, "输入字符串格式不正确!");
} }
// 校验重名 Integer schId = UserContext.getSchId();
List<SysDict> existing = sysDictRepository.findByDictTypeOrderBySortOrderAsc(DICT_TYPE_DEPT); // 重名校验范围 = 本校 + 全局可见项,避免与存量全局字典重名
List<SysDict> existing = visibleList(DICT_TYPE_DEPT);
boolean duplicate = existing.stream().anyMatch(d -> boolean duplicate = existing.stream().anyMatch(d ->
DICT_TYPE_DEPT.equals(d.getDictType()) && name.equals(d.getDictValue()) && d.getEnableFlag() != 3); DICT_TYPE_DEPT.equals(d.getDictType()) && name.equals(d.getDictValue()) && d.getEnableFlag() != 3);
if (duplicate) { if (duplicate) {
throw new BizException(ErrorCode.FORBIDDEN, "已存在该分类,请重新填写!"); throw new BizException(ErrorCode.FORBIDDEN, "已存在该分类,请重新填写!");
} }
// 计算 sortOrder = max + 1 // 计算 sortOrder = max + 1(同样基于本校 + 全局可见项)
int maxSort = existing.stream() int maxSort = existing.stream()
.filter(d -> d.getEnableFlag() != 3) .filter(d -> d.getEnableFlag() != 3)
.mapToInt(d -> d.getSortOrder() != null ? d.getSortOrder() : 0) .mapToInt(d -> d.getSortOrder() != null ? d.getSortOrder() : 0)
.max().orElse(0); .max().orElse(0);
SysDict dict = new SysDict(); SysDict dict = new SysDict();
// 多租户:归属当前登录人学校;无学校归属(区域级)时为 NULL,即全局字典
dict.setSchId(schId);
dict.setDictType(DICT_TYPE_DEPT); dict.setDictType(DICT_TYPE_DEPT);
dict.setDictValue(name); dict.setDictValue(name);
dict.setSortOrder(maxSort + 1); dict.setSortOrder(maxSort + 1);
@@ -144,16 +149,18 @@ public class DictService implements IDictService {
if (serID == null) { if (serID == null) {
throw new BizException(ErrorCode.PARAM_ERROR, "参数错误!"); throw new BizException(ErrorCode.PARAM_ERROR, "参数错误!");
} }
// 校验重名(排除自身) SysDict dict = sysDictRepository.findById(serID)
List<SysDict> existing = sysDictRepository.findByDictTypeOrderBySortOrderAsc(DICT_TYPE_DEPT); .orElseThrow(() -> new BizException(ErrorCode.FORBIDDEN, "字典记录不存在"));
checkSchoolScope(dict);
// 校验重名(排除自身,范围 = 本校 + 全局可见项)
List<SysDict> existing = visibleList(DICT_TYPE_DEPT);
boolean duplicate = existing.stream().anyMatch(d -> boolean duplicate = existing.stream().anyMatch(d ->
!d.getId().equals(serID) && name.equals(d.getDictValue()) && d.getEnableFlag() != 3); !d.getId().equals(serID) && name.equals(d.getDictValue()) && d.getEnableFlag() != 3);
if (duplicate) { if (duplicate) {
throw new BizException(ErrorCode.FORBIDDEN, "已存在该分类,请重新填写!"); throw new BizException(ErrorCode.FORBIDDEN, "已存在该分类,请重新填写!");
} }
SysDict dict = sysDictRepository.findById(serID)
.orElseThrow(() -> new BizException(ErrorCode.FORBIDDEN, "字典记录不存在"));
if (StringUtils.hasText(name)) { if (StringUtils.hasText(name)) {
dict.setDictValue(name); dict.setDictValue(name);
} }
@@ -170,11 +177,20 @@ public class DictService implements IDictService {
public void delDept(Integer serID) { public void delDept(Integer serID) {
SysDict dict = sysDictRepository.findById(serID) SysDict dict = sysDictRepository.findById(serID)
.orElseThrow(() -> new BizException(ErrorCode.FORBIDDEN, "字典记录不存在")); .orElseThrow(() -> new BizException(ErrorCode.FORBIDDEN, "字典记录不存在"));
checkSchoolScope(dict);
// 校验是否被作业引用(按 deptId = dictKey 匹配) // 校验是否被作业引用(按 deptId = dictKey 匹配)
// C# 中检查 Notify_Message.DeptID == dict.CodeID // C# 中检查 Notify_Message.DeptID == dict.CodeID
// Java 中 Homework.deptId 存的是 dictKey // Java 中 Homework.deptId 存的是 dictKey;多租户下仅本校作业算引用,
List<Homework> referenced = homeworkRepository.findAll((root, query, cb) -> // 其他学校同名 key(如各校自建的 dept1)不受影响
cb.equal(root.get("deptId"), dict.getDictKey())); Integer refSchId = dict.getSchId() != null ? dict.getSchId() : UserContext.getSchId();
List<Homework> referenced = homeworkRepository.findAll((root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get("deptId"), dict.getDictKey()));
if (refSchId != null) {
predicates.add(cb.equal(root.get("schId"), refSchId));
}
return cb.and(predicates.toArray(new Predicate[0]));
});
if (!referenced.isEmpty()) { if (!referenced.isEmpty()) {
throw new BizException(ErrorCode.FORBIDDEN, "已引用该部门,无法删除!"); throw new BizException(ErrorCode.FORBIDDEN, "已引用该部门,无法删除!");
} }
@@ -189,7 +205,7 @@ public class DictService implements IDictService {
if (!StringUtils.hasText(ids)) { if (!StringUtils.hasText(ids)) {
throw new BizException(ErrorCode.PARAM_ERROR, "参数错误"); throw new BizException(ErrorCode.PARAM_ERROR, "参数错误");
} }
List<SysDict> list = sysDictRepository.findByDictTypeOrderBySortOrderAsc(DICT_TYPE_DEPT); List<SysDict> list = visibleList(DICT_TYPE_DEPT);
if (list.isEmpty()) { if (list.isEmpty()) {
throw new BizException(ErrorCode.FORBIDDEN, "字典类型错误,未找到该字典类型"); throw new BizException(ErrorCode.FORBIDDEN, "字典类型错误,未找到该字典类型");
} }
@@ -205,12 +221,18 @@ public class DictService implements IDictService {
.mapToInt(d -> d.getSortOrder() != null ? d.getSortOrder() : idArr.length) .mapToInt(d -> d.getSortOrder() != null ? d.getSortOrder() : idArr.length)
.max().orElse(idArr.length); .max().orElse(idArr.length);
// 仅本校可见字典可参与排序(前端 ids 即来自本校管理列表)
Set<Integer> visibleIds = list.stream().map(SysDict::getId).collect(Collectors.toSet());
// lambda 中不能修改局部变量,用数组包装 // lambda 中不能修改局部变量,用数组包装
final int[] indexOrd = { maxSort }; final int[] indexOrd = { maxSort };
// 逆序遍历,从最大序号递减 // 逆序遍历,从最大序号递减
for (int i = idArr.length - 1; i >= 0; i--) { for (int i = idArr.length - 1; i >= 0; i--) {
int id = idArr[i]; int id = idArr[i];
if (!visibleIds.contains(id)) {
continue;
}
sysDictRepository.findById(id).ifPresent(dict -> { sysDictRepository.findById(id).ifPresent(dict -> {
dict.setSortOrder(indexOrd[0]); dict.setSortOrder(indexOrd[0]);
sysDictRepository.save(dict); sysDictRepository.save(dict);
@@ -244,17 +266,51 @@ public class DictService implements IDictService {
/** /**
* 按发文部门字典 key 解析部门名称(对应 C# GetDeptName)。 * 按发文部门字典 key 解析部门名称(对应 C# GetDeptName)。
* 多租户:schId 非空时优先本校字典、其次全局(sch_id 为 NULL)字典,
* 不会命中其他学校的同名 key;schId 为空时退化为全局匹配(兼容存量调用)。
*/ */
@Override @Override
public String resolveDeptName(String deptId) { public String resolveDeptName(String deptId, Integer schId) {
if (!StringUtils.hasText(deptId)) { if (!StringUtils.hasText(deptId)) {
return null; return null;
} }
return sysDictRepository.findByDictTypeAndDictKey(DICT_TYPE_DEPT, deptId.trim()) String key = deptId.trim();
if (schId != null) {
List<SysDict> scoped = sysDictRepository.findVisibleBySchIdAndDictTypeAndDictKey(schId, DICT_TYPE_DEPT, key);
if (!scoped.isEmpty()) {
return scoped.get(0).getDictValue();
}
return null;
}
return sysDictRepository.findByDictTypeAndDictKey(DICT_TYPE_DEPT, key)
.map(SysDict::getDictValue) .map(SysDict::getDictValue)
.orElse(null); .orElse(null);
} }
/** 当前登录人可见的全量字典(本校 + 全局;无学校归属时不过滤) */
private List<SysDict> visibleList(String dictType) {
Integer schId = UserContext.getSchId();
return schId != null
? sysDictRepository.findVisibleBySchIdAndDictType(schId, dictType)
: sysDictRepository.findByDictTypeOrderBySortOrderAsc(dictType);
}
/** 当前登录人可见的启用字典(本校 + 全局;无学校归属时不过滤) */
private List<SysDict> visibleEnabledList(String dictType) {
Integer schId = UserContext.getSchId();
return schId != null
? sysDictRepository.findVisibleEnabledBySchIdAndDictType(schId, dictType, 1)
: sysDictRepository.findByDictTypeAndEnableFlagOrderBySortOrderAsc(dictType, 1);
}
/** 多租户写保护:禁止操作其他学校的字典(全局字典与无学校归属用户不受限) */
private void checkSchoolScope(SysDict dict) {
Integer schId = UserContext.getSchId();
if (dict.getSchId() != null && schId != null && !dict.getSchId().equals(schId)) {
throw new BizException(ErrorCode.FORBIDDEN, "无权操作其他学校的字典!");
}
}
/** /**
* 将前端 Type 参数映射到字典类型字符串。 * 将前端 Type 参数映射到字典类型字符串。
* C# 中使用枚举,Java 版仅支持 DEPT(1)。 * C# 中使用枚举,Java 版仅支持 DEPT(1)。
@@ -1021,9 +1021,9 @@ public class HomeworkService implements IHomeworkService {
String legacy = legacyHwStatus(entity); String legacy = legacyHwStatus(entity);
vo.setHwStatus(legacy); vo.setHwStatus(legacy);
vo.setHwStatusDesc(legacyHwStatusDesc(legacy)); vo.setHwStatusDesc(legacyHwStatusDesc(legacy));
// 存量数据 dept_name 为空时按 deptId 从字典兜底解析(对应 C# GetDeptName) // 存量数据 dept_name 为空时按 deptId 从字典兜底解析(对应 C# GetDeptName;按作业所属学校匹配)
if (!StringUtils.hasText(vo.getDeptName())) { if (!StringUtils.hasText(vo.getDeptName())) {
vo.setDeptName(dictService.resolveDeptName(vo.getDeptId())); vo.setDeptName(dictService.resolveDeptName(vo.getDeptId(), entity.getSchId()));
} }
vo.setRead(read); vo.setRead(read);
vo.setReadCount(homeworkReadRepository.countByHomeworkId(entity.getId())); vo.setReadCount(homeworkReadRepository.countByHomeworkId(entity.getId()));
@@ -1031,10 +1031,10 @@ public class HomeworkService implements IHomeworkService {
} }
/** /**
* 写入时解析部门名称:优先按 deptId 查字典,字典缺失时回退前端传值。 * 写入时解析部门名称:优先按 deptId 查本校字典,字典缺失时回退前端传值。
*/ */
private String resolveDeptName(HomeworkRequest request) { private String resolveDeptName(HomeworkRequest request) {
String resolved = dictService.resolveDeptName(request.getDeptId()); String resolved = dictService.resolveDeptName(request.getDeptId(), UserContext.getSchId());
return StringUtils.hasText(resolved) ? resolved : request.getDeptName(); return StringUtils.hasText(resolved) ? resolved : request.getDeptName();
} }
@@ -16,9 +16,9 @@ CREATE TABLE IF NOT EXISTS `homework_receiver` (
`receiver_id` INT NOT NULL COMMENT '接收人ID(用户中心数字ID)', `receiver_id` INT NOT NULL COMMENT '接收人ID(用户中心数字ID)',
`receiver_name` VARCHAR(64) NULL COMMENT '接收人姓名快照', `receiver_name` VARCHAR(64) NULL COMMENT '接收人姓名快照',
`receive_type` INT NOT NULL DEFAULT 1 COMMENT '接收类型:1 接收 / 2 抄送', `receive_type` INT NOT NULL DEFAULT 1 COMMENT '接收类型:1 接收 / 2 抄送',
`create_by` VARCHAR(64) NULL COMMENT '创建人ID(审计填充)', `create_by` INT NULL COMMENT '创建人ID(用户中心数字ID)',
`create_time` DATETIME NULL COMMENT '创建时间(审计填充)', `create_time` DATETIME NULL COMMENT '创建时间(审计填充)',
`update_by` VARCHAR(64) NULL COMMENT '更新人ID(审计填充)', `update_by` INT NULL COMMENT '更新人ID(用户中心数字ID)',
`update_time` DATETIME NULL COMMENT '更新时间(审计填充)', `update_time` DATETIME NULL COMMENT '更新时间(审计填充)',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `idx_homework_id` (`homework_id`), KEY `idx_homework_id` (`homework_id`),
+17 -15
View File
@@ -6,7 +6,9 @@
-- 2) MySQL 5.7 不支持 ADD COLUMN IF NOT EXISTS,重复执行前请先核对列是否存在; -- 2) MySQL 5.7 不支持 ADD COLUMN IF NOT EXISTS,重复执行前请先核对列是否存在;
-- 3) 定时发布时间复用 homework.timing_send_time(PRD scheduled_at 同义),不新增列。 -- 3) 定时发布时间复用 homework.timing_send_time(PRD scheduled_at 同义),不新增列。
-- 4) 人员标识口径:教师/学生一律为用户中心数字ID(INT),不存登录账号 -- 4) 人员标识口径:教师/学生一律为用户中心数字ID(INT),不存登录账号
-- (与 v3_account_to_id_refactor.sql / v4_person_id_to_int.sql 收敛后的最终形态一致)。 -- (与 v3_account_to_id_refactor.sql / v4_person_id_to_int.sql 收敛后的最终形态一致);
-- 审计列 create_by / update_by 亦为用户中心数字ID(INT),
-- 与 v5_audit_by_to_int.sql 收敛后的最终形态一致。
-- ===================================================================== -- =====================================================================
-- --------------------------------------------------------------------- -- ---------------------------------------------------------------------
@@ -32,9 +34,9 @@ CREATE TABLE IF NOT EXISTS `homework_class` (
`homework_id` INT NOT NULL, `homework_id` INT NOT NULL,
`class_id` VARCHAR(64) NOT NULL, `class_id` VARCHAR(64) NOT NULL,
`class_name` VARCHAR(50) NULL COMMENT '班级名快照', `class_name` VARCHAR(50) NULL COMMENT '班级名快照',
`create_by` VARCHAR(64) NULL, `create_by` INT NULL COMMENT '创建人ID(用户中心数字ID)',
`create_time` DATETIME NULL, `create_time` DATETIME NULL,
`update_by` VARCHAR(64) NULL, `update_by` INT NULL COMMENT '更新人ID(用户中心数字ID)',
`update_time` DATETIME NULL, `update_time` DATETIME NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `uk_hw_class` (`homework_id`, `class_id`) UNIQUE KEY `uk_hw_class` (`homework_id`, `class_id`)
@@ -51,9 +53,9 @@ CREATE TABLE IF NOT EXISTS `homework_attachment` (
`file_type` VARCHAR(20) NULL COMMENT 'image/file/...', `file_type` VARCHAR(20) NULL COMMENT 'image/file/...',
`file_name` VARCHAR(200) NULL, `file_name` VARCHAR(200) NULL,
`sort` INT NOT NULL DEFAULT 0, `sort` INT NOT NULL DEFAULT 0,
`create_by` VARCHAR(64) NULL, `create_by` INT NULL COMMENT '创建人ID(用户中心数字ID)',
`create_time` DATETIME NULL, `create_time` DATETIME NULL,
`update_by` VARCHAR(64) NULL, `update_by` INT NULL COMMENT '更新人ID(用户中心数字ID)',
`update_time` DATETIME NULL, `update_time` DATETIME NULL,
KEY `idx_hw` (`homework_id`), KEY `idx_hw` (`homework_id`),
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
@@ -72,9 +74,9 @@ CREATE TABLE IF NOT EXISTS `homework_read_record` (
`first_read_at` DATETIME NULL, `first_read_at` DATETIME NULL,
`last_read_at` DATETIME NULL, `last_read_at` DATETIME NULL,
`read_count` INT NOT NULL DEFAULT 0, `read_count` INT NOT NULL DEFAULT 0,
`create_by` VARCHAR(64) NULL, `create_by` INT NULL COMMENT '创建人ID(用户中心数字ID)',
`create_time` DATETIME NULL, `create_time` DATETIME NULL,
`update_by` VARCHAR(64) NULL, `update_by` INT NULL COMMENT '更新人ID(用户中心数字ID)',
`update_time` DATETIME NULL, `update_time` DATETIME NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `uk_hw_student` (`homework_id`, `student_id`) UNIQUE KEY `uk_hw_student` (`homework_id`, `student_id`)
@@ -103,9 +105,9 @@ CREATE TABLE IF NOT EXISTS `homework_submission` (
`reviewed_by` INT NULL COMMENT '批阅人ID(用户中心数字ID,同教师ID空间)', `reviewed_by` INT NULL COMMENT '批阅人ID(用户中心数字ID,同教师ID空间)',
`reviewed_by_name` VARCHAR(64) NULL COMMENT '批阅人姓名快照', `reviewed_by_name` VARCHAR(64) NULL COMMENT '批阅人姓名快照',
`reviewed_at` DATETIME NULL, `reviewed_at` DATETIME NULL,
`create_by` VARCHAR(64) NULL, `create_by` INT NULL COMMENT '创建人ID(用户中心数字ID)',
`create_time` DATETIME NULL, `create_time` DATETIME NULL,
`update_by` VARCHAR(64) NULL, `update_by` INT NULL COMMENT '更新人ID(用户中心数字ID)',
`update_time` DATETIME NULL, `update_time` DATETIME NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `uk_hw_student_ver` (`homework_id`, `student_id`, `version`), UNIQUE KEY `uk_hw_student_ver` (`homework_id`, `student_id`, `version`),
@@ -126,9 +128,9 @@ CREATE TABLE IF NOT EXISTS `submission_attachment` (
`file_type` VARCHAR(20) NULL COMMENT 'image/file', `file_type` VARCHAR(20) NULL COMMENT 'image/file',
`file_name` VARCHAR(200) NULL, `file_name` VARCHAR(200) NULL,
`sort` INT NOT NULL DEFAULT 0, `sort` INT NOT NULL DEFAULT 0,
`create_by` VARCHAR(64) NULL, `create_by` INT NULL COMMENT '创建人ID(用户中心数字ID)',
`create_time` DATETIME NULL, `create_time` DATETIME NULL,
`update_by` VARCHAR(64) NULL, `update_by` INT NULL COMMENT '更新人ID(用户中心数字ID)',
`update_time` DATETIME NULL, `update_time` DATETIME NULL,
KEY `idx_sub` (`submission_id`), KEY `idx_sub` (`submission_id`),
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
@@ -147,9 +149,9 @@ CREATE TABLE IF NOT EXISTS `class_student` (
`student_name` VARCHAR(50) NOT NULL, `student_name` VARCHAR(50) NOT NULL,
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1 在班 0 已转出/退课', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1 在班 0 已转出/退课',
`leave_at` DATETIME NULL, `leave_at` DATETIME NULL,
`create_by` VARCHAR(64) NULL, `create_by` INT NULL COMMENT '创建人ID(用户中心数字ID)',
`create_time` DATETIME NULL, `create_time` DATETIME NULL,
`update_by` VARCHAR(64) NULL, `update_by` INT NULL COMMENT '更新人ID(用户中心数字ID)',
`update_time` DATETIME NULL, `update_time` DATETIME NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `uk_class_student` (`class_id`, `student_id`) UNIQUE KEY `uk_class_student` (`class_id`, `student_id`)
@@ -172,9 +174,9 @@ CREATE TABLE IF NOT EXISTS `notification` (
`is_read` TINYINT NOT NULL DEFAULT 0, `is_read` TINYINT NOT NULL DEFAULT 0,
`read_at` DATETIME NULL, `read_at` DATETIME NULL,
`send_channel` VARCHAR(20) NOT NULL DEFAULT 'inapp' COMMENT 'inapp/sms', `send_channel` VARCHAR(20) NOT NULL DEFAULT 'inapp' COMMENT 'inapp/sms',
`create_by` VARCHAR(64) NULL, `create_by` INT NULL COMMENT '创建人ID(用户中心数字ID)',
`create_time` DATETIME NULL, `create_time` DATETIME NULL,
`update_by` VARCHAR(64) NULL, `update_by` INT NULL COMMENT '更新人ID(用户中心数字ID)',
`update_time` DATETIME NULL, `update_time` DATETIME NULL,
KEY `idx_receiver` (`receiver_id`, `is_read`), KEY `idx_receiver` (`receiver_id`, `is_read`),
KEY `idx_hw_type` (`homework_id`, `type`), KEY `idx_hw_type` (`homework_id`, `type`),