diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a46b5f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +target/ +.idea/ +*.iml +*.log +.DS_Store diff --git a/docker/application-prod.yml b/docker/application-prod.yml new file mode 100644 index 0000000..b7c86b5 --- /dev/null +++ b/docker/application-prod.yml @@ -0,0 +1,57 @@ +# ClassHomework 生产配置(线上以 nacos 的 classhomework.yaml 为准,本文件是 nacos 不可达时的兜底) +# 真正上线请在 Nacos 新建 dataId=classhomework.yaml、group=OA_APP 的配置覆盖以下项 +# (尤其 datasource 的库名/账号、yh.sso.client-host-url)。 +server: + port: 8070 + servlet: + # 对外经 caddy 暴露为 http://base.yunhaixingtong.com/classhomework + context-path: /classhomework + +spring: + mvc: + date-format: yyyy-MM-dd HH:mm:ss + jackson: + time-zone: GMT+8 + date-format: yyyy-MM-dd HH:mm:ss + datasource: + # 与 UserBase 共用同一台 mysql8;库名为 classhomework(需先在 mysql8 上建库) + url: jdbc:mysql://mysql8:3306/classhomework?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true + username: root + password: 'CVf^x$sW*b6CGHxh' + driver-class-name: com.mysql.cj.jdbc.Driver + min-idle: 10 + max-active: 30 + jpa: + database: MySQL + show-sql: false + hibernate: + ddl-auto: update + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect + properties: + hibernate.order_by.default_null_ordering: last + open-in-view: true + +# 单点登录 / 应用间签名鉴权(基础平台 yh-auth / yh-app-auth) +yh: + env-config: + source: + # 前端运行时配置(JSON 字符串),线上由 Nacos/projman 下发。 + # 端点 /api/env-config 由平台 yh-env-config-spring-boot-starter 提供(免登录 SSO 旁路)。 + front: "" + sso: + server-url-prefix: http://sso.yunhaixingtong.com + client-host-url: http://base.yunhaixingtong.com/classhomework + allow-headers: all + allow-origins: all + url-pattern: /api/* + app-auth: + enabled: true + # 本应用自身身份(客户端签名用) + app-key: 100617 + app-secret: 2a3b4c5d6e7f8091a2b3c4d5e6f70819 + # 受信应用(服务端验签用):appKey -> appSecret + trusted-apps: + 100617: 2a3b4c5d6e7f8091a2b3c4d5e6f70819 + +# 上传文件落容器卷,compose 里挂到宿主机 ./upload-data +FILE_PATH: /data/upload diff --git a/docker/run-app.sh b/docker/run-app.sh new file mode 100644 index 0000000..df00715 --- /dev/null +++ b/docker/run-app.sh @@ -0,0 +1,9 @@ +#!/bin/sh +# ClassHomework 启动入口:配置统一由 Nacos 配置中心在 bootstrap 阶段下发(见 bootstrap.yml) +# 不再需要手动 curl 拉取;若 nacos 不可达,bootstrap 中 fail-fast=false 会自动回退到 +# 镜像内烤好的 application-prod.yml 兜底默认。 +set -e + +echo "[run-app] starting ClassHomework (profile=prod, nacos=${NACOS_ADDR:-nacos.yunhaixingtong.com:8848}) ..." +exec java ${JAVA_OPTS:--Xms512m -Xmx1024m} -Dfile.encoding=UTF-8 -jar /app/classhomework.jar \ + --spring.profiles.active=prod diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1280970 --- /dev/null +++ b/pom.xml @@ -0,0 +1,198 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + + com.yunhai + classhomework + 0.0.1 + ClassHomework + 课堂作业子应用 + + 1.8 + + + + + + + com.yunhai.platform + yh-platform-dependencies + 1.0.0-SNAPSHOT + pom + import + + + + com.alibaba.cloud + spring-cloud-alibaba-dependencies + 2021.0.5.0 + pom + import + + + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-validation + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + + org.springframework.security + spring-security-crypto + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + 3.1.8 + + + + com.mysql + mysql-connector-j + runtime + + + + org.projectlombok + lombok + true + + + + + com.alibaba + fastjson + 1.2.80 + + + + + org.springdoc + springdoc-openapi-ui + 1.7.0 + + + + + com.yunhai.platform + yh-spring-boot-starter + + + + + org.jasig.cas.client + cas-client-core + 3.5.1 + + + + + com.alibaba + easyexcel + 3.3.2 + + + + + + + + classhomework + + + org.apache.maven.plugins + maven-compiler-plugin + + + false + + + + + org.springframework.boot + spring-boot-maven-plugin + + com.yunhai.homework.HomeworkApplication + + + + + repackage + + + + + + + + + + + nexus-public + bsch/nexus public + http://nexus.yunhaixingtong.com/repository/maven-public/ + true + true + + + + + + ci + + C:/classhomework-build/classhomework + + + + + diff --git a/src/main/java/com/yunhai/homework/HomeworkApplication.java b/src/main/java/com/yunhai/homework/HomeworkApplication.java new file mode 100644 index 0000000..3cb5b11 --- /dev/null +++ b/src/main/java/com/yunhai/homework/HomeworkApplication.java @@ -0,0 +1,18 @@ +package com.yunhai.homework; + +import com.alibaba.fastjson.parser.ParserConfig; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@SpringBootApplication(scanBasePackages = "com.yunhai.homework") +@EnableTransactionManagement +public class HomeworkApplication { + + public static void main(String[] args) { + // 开启 fastjson 安全模式 + ParserConfig.getGlobalInstance().setSafeMode(true); + SpringApplication.run(HomeworkApplication.class, args); + } + +} diff --git a/src/main/java/com/yunhai/homework/common/listener/HomeworkAuditListener.java b/src/main/java/com/yunhai/homework/common/listener/HomeworkAuditListener.java new file mode 100644 index 0000000..67cbf7c --- /dev/null +++ b/src/main/java/com/yunhai/homework/common/listener/HomeworkAuditListener.java @@ -0,0 +1,42 @@ +package com.yunhai.homework.common.listener; + +import com.yunhai.homework.entity.BaseEntity; +import com.yunhai.homework.utils.UserContext; +import org.springframework.stereotype.Component; + +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; +import java.time.LocalDateTime; + +/** + * JPA 审计监听器:在实体持久化/更新前自动填充创建人/更新人(平台当前登录人)与时间戳。 + * 对应 {@link BaseEntity} 的 createBy / updateBy / createTime / updateTime 字段。 + * 当前登录人取自 {@link UserContext}(用户中心数字ID,字符串形式落审计列)。 + */ +@Component +public class HomeworkAuditListener { + + @PrePersist + public void prePersist(BaseEntity entity) { + LocalDateTime now = LocalDateTime.now(); + entity.setCreateTime(now); + entity.setUpdateTime(now); + Integer userId = UserContext.getUserId(); + if (userId != null) { + if (entity.getCreateBy() == null) { + entity.setCreateBy(String.valueOf(userId)); + } + entity.setUpdateBy(String.valueOf(userId)); + } + } + + @PreUpdate + public void preUpdate(BaseEntity entity) { + entity.setUpdateTime(LocalDateTime.now()); + Integer userId = UserContext.getUserId(); + if (userId != null) { + entity.setUpdateBy(String.valueOf(userId)); + } + } + +} diff --git a/src/main/java/com/yunhai/homework/common/validation/ValidationGroups.java b/src/main/java/com/yunhai/homework/common/validation/ValidationGroups.java new file mode 100644 index 0000000..8021929 --- /dev/null +++ b/src/main/java/com/yunhai/homework/common/validation/ValidationGroups.java @@ -0,0 +1,10 @@ +package com.yunhai.homework.common.validation; + +/** + * JSR-303 分组校验标记接口(对齐 UserBase 规范)。 + * Create:新建场景必填字段;Update:编辑场景必填字段(通常含 id)。 + */ +public interface ValidationGroups { + interface Create {} + interface Update {} +} diff --git a/src/main/java/com/yunhai/homework/config/Config.java b/src/main/java/com/yunhai/homework/config/Config.java new file mode 100644 index 0000000..6eb628a --- /dev/null +++ b/src/main/java/com/yunhai/homework/config/Config.java @@ -0,0 +1,11 @@ +package com.yunhai.homework.config; + +/** + * 全局路径常量。与 UserBase 一致:API 统一挂在 /{platform}/ 之下(platform = api / manage / mobiapi)。 + */ +public enum Config { + Inst; + + public static final String API_PATH = "/{platform}"; // manage / api / mobiapi,前面有/后面无/,拼接子路径时用"/"分隔 + public static final String OPEN_API_PATH = "/oapi"; +} diff --git a/src/main/java/com/yunhai/homework/config/JacksonConfig.java b/src/main/java/com/yunhai/homework/config/JacksonConfig.java new file mode 100644 index 0000000..5dd094b --- /dev/null +++ b/src/main/java/com/yunhai/homework/config/JacksonConfig.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.config; + +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer; +import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; + +import java.time.format.DateTimeFormatter; + +/** + * Jackson 时间序列化全局约定(对齐旧 .NET 版 "yyyy-MM-dd HH:mm:ss"): + * 接口出入参中的 LocalDateTime/LocalDate 统一使用该格式,避免 ISO 的 "T" 分隔符 + * (如 2026-09-01T16:38:40)直接暴露给前端表格。 + */ +@Configuration +public class JacksonConfig { + + private static final DateTimeFormatter DATE_TIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { + return builder -> builder + .serializers( + new LocalDateTimeSerializer(DATE_TIME), + new LocalDateSerializer(DATE)) + .deserializers( + new LocalDateTimeDeserializer(DATE_TIME), + new LocalDateDeserializer(DATE)); + } +} diff --git a/src/main/java/com/yunhai/homework/config/SchedulingConfig.java b/src/main/java/com/yunhai/homework/config/SchedulingConfig.java new file mode 100644 index 0000000..1264f19 --- /dev/null +++ b/src/main/java/com/yunhai/homework/config/SchedulingConfig.java @@ -0,0 +1,13 @@ +package com.yunhai.homework.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * 开启 @Scheduled 定时任务(作业定时发布 / 到点截止 / 截止提醒 / 名册同步)。 + * 单实例假设;多实例部署前需引入分布式锁(计划 §13-7)。 + */ +@Configuration +@EnableScheduling +public class SchedulingConfig { +} diff --git a/src/main/java/com/yunhai/homework/config/SwaggerConfig.java b/src/main/java/com/yunhai/homework/config/SwaggerConfig.java new file mode 100644 index 0000000..160f8a4 --- /dev/null +++ b/src/main/java/com/yunhai/homework/config/SwaggerConfig.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.config; + +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * 接口文档(springdoc-openapi,替代旧 Springfox)。 + */ +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI homeworkOpenAPI() { + return new OpenAPI() + .info(new Info() + .title("课程作业接口文档") + .description("课程作业子应用:发作业、列表、删除、编辑、管理员查看、统计(springdoc-openapi)") + .version("1.0")); + } +} diff --git a/src/main/java/com/yunhai/homework/config/UserContextConfig.java b/src/main/java/com/yunhai/homework/config/UserContextConfig.java new file mode 100644 index 0000000..3ea0e45 --- /dev/null +++ b/src/main/java/com/yunhai/homework/config/UserContextConfig.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.config; + +import com.yunhai.homework.utils.UserContext; +import com.yunhai.platform.auth.spring.CurrentBoaUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import javax.annotation.PostConstruct; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * {@link UserContext} 生命周期配置: + * 启动时向 UserContext 注入平台 BoaUser 供应器(取值在首次访问时惰性解析), + * 并注册拦截器在每个请求结束后清理 ThreadLocal(Tomcat 线程复用防串号)。 + */ +@Configuration +public class UserContextConfig { + + @Autowired + private CurrentBoaUser currentBoaUser; + + @PostConstruct + public void init() { + UserContext.init(currentBoaUser); + } + + @Bean + public HandlerInterceptor userContextCleanInterceptor() { + return new HandlerInterceptor() { + @Override + public void afterCompletion(HttpServletRequest request, HttpServletResponse response, + Object handler, Exception ex) { + UserContext.clear(); + } + }; + } + + @Bean + public WebMvcConfigurer userContextConfigurer(HandlerInterceptor userContextCleanInterceptor) { + return new WebMvcConfigurer() { + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(userContextCleanInterceptor).addPathPatterns("/**"); + } + }; + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/BaseApiController.java b/src/main/java/com/yunhai/homework/controller/api/BaseApiController.java new file mode 100644 index 0000000..5ef98ed --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/BaseApiController.java @@ -0,0 +1,110 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.homework.UpFileResultVo; +import com.yunhai.homework.service.IFileService; +import com.yunhai.homework.service.IUserCenterService; +import com.yunhai.platform.auth.LoginUser; +import com.yunhai.platform.auth.UserContextHolder; +import com.yunhai.platform.utils.model.BoaUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; +import java.util.Map; + +/** + * 平台代理域接口(base/)。 + *
路径前缀:{@code Config.API_PATH + "/base/"}(原硬编码已修正)。 + * 承载平台代理接口:GetUserInfo / GetUserMod / GetSchoolAll / GetGroupUser / Logout, + * 业务逻辑已下沉 {@link IUserCenterService},本控制器仅做"取参 → 调 Service → 返回"。 + * 返回裸对象,由 ApiResponseAdvice 自动包装为 {code, msg, data}。
路径前缀:{@code Config.API_PATH + "/contacts/"}(原硬编码已修正为 Config 拼接)。 + * 方法名已为 PascalCase 且写操作已是 POST,保持不动。 + * 返回裸对象,由 ApiResponseAdvice 自动包装为 {code, msg, data}。
路径前缀:{@code Config.API_PATH + "/dict/"}(原 base/ 前缀已修正)。 + * 写操作一律 POST(原 DelDept/OrdbyDicIds 为 GET 写操作,已 POST 化)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/dict/") +public class DictController { + + @Autowired + private IDictService dictService; + + /** + * 获取系统字典(下拉框用)。 + */ + @GetMapping("List") + public List> list(@RequestParam(value = "type", defaultValue = "0") Integer type) { + return dictService.getSysDic(type); + } + + /** + * 获取系统字典管理列表。 + */ + @GetMapping("ManageList") + public List> manageList(@RequestParam(value = "type", defaultValue = "dept") String type) { + return dictService.getDictMangeList(type); + } + + /** + * 添加发文单位。 + */ + @PostMapping("Add") + public void add(@RequestParam("name") String name) { + dictService.addDeptDic(name); + } + + /** + * 更新发文单位。 + */ + @PostMapping("Update") + public void update( + @RequestParam("SerID") Integer serID, + @RequestParam("Name") String name, + @RequestParam(value = "Enable", required = false, defaultValue = "1") Integer enable) { + dictService.updateDeptDic(serID, name, enable); + } + + /** + * 删除发文单位(原 GET DelDept,已 POST 化)。 + */ + @PostMapping("Delete") + public void delete(@RequestParam("serid") Integer serID) { + dictService.delDept(serID); + } + + /** + * 发文单位排序(原 GET OrdbyDicIds,已 POST 化)。 + */ + @PostMapping("Sort") + public void sort(@RequestParam("ids") String ids) { + dictService.ordbyDicIds(ids); + } + + /** + * 本校是否开通短信模块(前端用作"本校是否开通短信")。 + * 取当前登录用户角色所属学校,查 sms_school_config 是否开通本应用模块。 + */ + @GetMapping("Sync") + public Boolean sync(BoaUser authUserWithDept) { + Integer schId = authUserWithDept != null && authUserWithDept.getCurrentRole() != null + ? authUserWithDept.getCurrentRole().getSchId() : null; + return dictService.getSchModuleIsSms(schId); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/FileController.java b/src/main/java/com/yunhai/homework/controller/api/FileController.java new file mode 100644 index 0000000..6edad5e --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/FileController.java @@ -0,0 +1,88 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.homework.UpFileResultVo; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.service.IFileService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 附件域接口(file/)。 + * 路径前缀:{@code Config.API_PATH + "/file/"}(原 base/ 前缀已修正为独立文件域)。 + * 方法名统一 PascalCase;写操作一律 POST。 + * 返回裸对象,由 ApiResponseAdvice 自动包装为 {code, msg, data}。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/file/") +public class FileController { + + @Autowired + private IFileService fileService; + + /** + * 附件上传(单文件)。 + * 前端: POST file/Upload (form-data) + * 返回: { fileid, host, fileurl, name, type, size } + */ + @PostMapping("Upload") + public UpFileResultVo upload(@RequestParam("file") MultipartFile file) { + return fileService.uploadToResult(file, "homework"); + } + + /** + * 图片上传(多文件,返回数组)。 + * 前端: POST file/UploadImage (form-data, accept=image/*) + */ + @PostMapping("UploadImage") + public List uploadImage(@RequestParam("file") MultipartFile[] files) { + return fileService.uploadImages(files, "homework"); + } + /** + * 文件下载(浏览器直接 location.href 跳转)。 + */ + @GetMapping("Down") + public void down(@RequestParam("id") Integer id, HttpServletResponse response) { + fileService.download(id, response); + } + + /** + * 文件在线预览(inline 输出,前端取 Blob 交给 open-file-viewer 渲染)。 + */ + @GetMapping("Preview") + public void preview(@RequestParam("id") Integer id, + @RequestParam(value = "type", required = false) Integer type, + HttpServletResponse response) { + fileService.preview(id, response); + } + + /** + * 删除文件。 + */ + @PostMapping("Delete") + public void delete(@RequestParam("fileId") Integer fileId) { + fileService.delete(fileId); + } + + /** + * 查询某作业下的附件列表。 + */ + @GetMapping("List") + public List list(@RequestParam("topicId") Integer topicId) { + return fileService.listByTopic("homework", topicId); + } + + /** + * 批量绑定附件到作业。 + */ + @PostMapping("Bind") + public void bind(@RequestParam("fileIds") List fileIds, + @RequestParam("topicId") Integer topicId) { + fileService.bindToTopic(fileIds, topicId); + } + +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/controller/api/HomeworkController.java b/src/main/java/com/yunhai/homework/controller/api/HomeworkController.java new file mode 100644 index 0000000..ee424c3 --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/HomeworkController.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.common.validation.ValidationGroups; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import com.yunhai.homework.service.IHomeworkService; +import com.yunhai.homework.utils.PageUtils; +import com.yunhai.platform.utils.model.BoaUser; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.SortDefault; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业接口(合并 HomeworkApiController + 旧 HomeworkController)。 + * 路径前缀:{@code Config.API_PATH + "/homework/"}(platform = api / manage / mobiapi)。 + * 方法动词式 PascalCase(对齐 UserBase);写操作一律 POST。 + * 返回裸对象,由 ApiResponseAdvice 自动包装为 {code, msg, data}。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/homework/") +public class HomeworkController { + + @Autowired + private IHomeworkService homeworkService; + + // ============================ 收件箱列表 ============================ + + /** + * 已收作业列表(用户端)。 + */ + @GetMapping("List") + public HomeworkPageResult list(HomeworkQuery query, + @SortDefault(sort = {"topmost", "createTime"}, + direction = Sort.Direction.DESC) Sort sort) { + return homeworkService.userList(query, PageUtils.toPageable(query, sort)); + } + + // ============================ 已发/全部作业列表 ============================ + + /** + * 已发/全部作业列表(管理端)。type=user 只看自己。 + */ + @GetMapping("SendList") + public BasePageResult sendList(HomeworkQuery query, + @SortDefault(sort = "createTime", + direction = Sort.Direction.DESC) Sort sort) { + return homeworkService.adminList(query, PageUtils.toPageable(query, sort)); + } + + // ============================ 查看详情(标读) ============================ + + /** + * 查看作业详情(type=1 普通查看标记已读, type=0 编辑模式不标记已读)。 + */ + @GetMapping("Detail") + public HomeworkDetailVo detail(@RequestParam("serid") Integer serid, + @RequestParam(value = "type", defaultValue = "1") Integer type) { + return homeworkService.detailMap(serid, type); + } + + // ============================ 编辑回显(不标读) ============================ + + /** + * 获取作业详情(编辑/转发用,不标记已读)。 + */ + @GetMapping("GetDetail") + public HomeworkDetailVo getDetail(@RequestParam("serid") Integer serid) { + return homeworkService.getDetailMap(serid); + } + + // ============================ 新建 ============================ + + /** + * 新建作业(JSON body)。fileIds 为附件ID逗号串,smsFlag 为短信标记。 + */ + @PostMapping("Create") + public Integer create(@RequestBody @Validated(ValidationGroups.Create.class) HomeworkRequest request) { + return homeworkService.publish(request); + } + + // ============================ 编辑 ============================ + + /** + * 编辑作业(JSON body,含 id)。 + */ + @PostMapping("Edit") + public void edit(@RequestBody @Validated(ValidationGroups.Update.class) HomeworkRequest request) { + homeworkService.edit(request.getSourceId() != null ? request.getSourceId() : 0, request); + } + + // ============================ 批量删除 ============================ + + /** + * 批量删除作业(ids 数组)。 + */ + @PostMapping("Delete") + public void delete(@RequestBody List ids) { + homeworkService.deleteBatch(ids); + } + + // ============================ 撤回 ============================ + + /** + * 撤回作业。 + */ + @PostMapping("UserPull") + public void userPull(@RequestParam("serid") Integer serid) { + homeworkService.pull(serid); + } + + // ============================ 置顶 ============================ + + /** + * 置顶/取消置顶。 + */ + @PostMapping("SetTopmost") + public void setTopmost(@RequestParam("serid") Integer serid, + @RequestParam(value = "topmost", defaultValue = "1") Integer topmost) { + homeworkService.setTopmost(serid, topmost); + } + + // ============================ 追加接收人 ============================ + + /** + * 追加接收人。 + */ + @PostMapping("ReceiveUsers") + public void receiveUsers(@RequestParam("serid") Integer serid, + @RequestParam("users") String users) { + homeworkService.receiveUsers(serid, users); + } + + // ============================ 催收 ============================ + + /** + * 催收未读。 + */ + @PostMapping("Expedite") + public void expedite(@RequestParam("serid") Integer serid) { + homeworkService.expedite(serid); + } + + // ============================ 一键已读 ============================ + + /** + * 一键全部已读(POST 化,原为 GET 写操作)。 + */ + @PostMapping("InitializeRed") + public InitializeRedVo initializeRed() { + return homeworkService.initializeRedResult(); + } + + // ============================ 阅读情况 ============================ + + /** + * 阅读情况(按已读/未读分组)。 + */ + @GetMapping("ReadDetail") + public List> readDetail(@RequestParam("serid") Integer serid) { + return homeworkService.getReadDetailGrouped(serid); + } + + // ============================ 上一篇/下一篇 ============================ + + /** + * 上一篇/下一篇作业ID。 + */ + @GetMapping("GetNextPrev") + public Map getNextPrev(@RequestParam("serid") Integer serid, + @RequestParam("type") String type) { + return homeworkService.getNextPrev(serid, type); + } + + // ============================ 附件打包下载 ============================ + + /** + * 批量下载作业全部附件(打包 zip)。 + */ + @GetMapping("AllFileDown") + public void allFileDown(@RequestParam("dataid") Integer dataid, + HttpServletResponse response) { + homeworkService.downloadAllFiles(dataid, response); + } + + // ============================ 统计 ============================ + + /** + * 作业统计。 + */ + @GetMapping("Statis") + public HomeworkStatisticsVo statis( + @RequestParam(value = "starttime", required = false) + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start, + @RequestParam(value = "endtime", required = false) + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end) { + return homeworkService.statistics(new HomeworkQuery(), start, end); + } + + // ============================ 转发(合并旧 REST 壳 forward) ============================ + + /** + * 转发作业(基于源作业创建新作业)。sourceId 在 request body 中传递。 + */ + @PostMapping("Forward") + public Integer forward(@RequestBody @Validated(ValidationGroups.Create.class) HomeworkRequest request) { + return homeworkService.forward(request.getSourceId(), request); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/NotificationController.java b/src/main/java/com/yunhai/homework/controller/api/NotificationController.java new file mode 100644 index 0000000..e82ba9c --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/NotificationController.java @@ -0,0 +1,53 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.v2.NotificationPageResult; +import com.yunhai.homework.model.v2.NotificationQuery; +import com.yunhai.homework.service.impl.NotificationService; +import com.yunhai.homework.utils.UserContext; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 站内通知接口(PRD §18 未列,按计划 §7.3 补充)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/notifications") +public class NotificationController { + + @Autowired + private NotificationService notificationService; + + /** 我的通知分页(含未读数;type/isRead 过滤) */ + @GetMapping + public NotificationPageResult page(NotificationQuery query) { + return notificationService.page(currentUserId(), query); + } + + /** 标记单条已读 */ + @PostMapping("/{id}/read") + public void markRead(@PathVariable("id") Integer id) { + notificationService.markRead(id, currentUserId()); + } + + /** 全部已读 */ + @PostMapping("/read-all") + public void markAllRead() { + notificationService.markAllRead(currentUserId()); + } + + /** 当前登录人ID(用户中心数字ID),无登录态时拒绝 */ + private static Integer currentUserId() { + Integer userId = UserContext.getUserId(); + if (userId == null) { + throw new BizException(ErrorCode.FORBIDDEN, "未获取到登录身份"); + } + return userId; + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/SmsController.java b/src/main/java/com/yunhai/homework/controller/api/SmsController.java new file mode 100644 index 0000000..25d4311 --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/SmsController.java @@ -0,0 +1,88 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.service.ISmsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置接口(短信域)。 + * 路径前缀:{@code Config.API_PATH + "/sms/"}(原 schsms/ 已修正)。 + * 方法名 PascalCase(原全小写已修正);写操作一律 POST(原 setschallsms/setschmodulesms 为 GET 写操作,已 POST 化)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/sms/") +public class SmsController { + + @Autowired + private ISmsService smsService; + + /** + * 获取短信模块列表。 + */ + @GetMapping("GetModule") + public List> getModule() { + return smsService.getModuleList(); + } + + /** + * 获取学校父类组列表。 + */ + @GetMapping("GetGroup") + public List> getGroup() { + return smsService.getSchParentGroup(); + } + + /** + * 分页获取学校短信开关列表。 + */ + @GetMapping("GetSchoolSms") + public Map getSchoolSms( + @RequestParam(value = "schpid", required = false) String schpid, + @RequestParam("PageSize") Integer pageSize, + @RequestParam("PageIndex") Integer pageIndex, + @RequestParam(value = "SchName", required = false, defaultValue = "") String schName) { + return smsService.getSchIsSmsList(pageSize, pageIndex, schName); + } + + /** + * 获取学校列表。 + */ + @GetMapping("GetSchool") + public List> getSchool() { + return smsService.getSchList(); + } + + /** + * 设置单校单模块短信开关。 + */ + @PostMapping("SetSchoolSms") + public void setSchoolSms(@RequestParam("itemsModu") String itemsModuJson) { + smsService.setSchSmsFromJson(itemsModuJson); + } + + /** + * 设置某校全部模块短信开关(原 GET setschallsms,已 POST 化)。 + */ + @PostMapping("SetAllSchoolSms") + public void setAllSchoolSms( + @RequestParam("SchSerID") Integer schSerID, + @RequestParam("IsSms") Boolean isSms) { + smsService.setSchSmsAll(schSerID, isSms); + } + + /** + * 批量设置多校多模块短信开关(原 GET setschmodulesms,已 POST 化)。 + */ + @PostMapping("SetSchoolModuleSms") + public void setSchoolModuleSms( + @RequestParam("SchSerID") String schSerID, + @RequestParam("Module") String module, + @RequestParam("IsSms") Boolean isSms) { + smsService.setSchModuleSms(schSerID, module, isSms); + } + +} diff --git a/src/main/java/com/yunhai/homework/controller/api/StudentHomeworkController.java b/src/main/java/com/yunhai/homework/controller/api/StudentHomeworkController.java new file mode 100644 index 0000000..56a37b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/StudentHomeworkController.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.BasePageQuery; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.v2.StudentHomeworkDetailVo; +import com.yunhai.homework.model.v2.StudentHomeworkVo; +import com.yunhai.homework.model.v2.StudentResultVo; +import com.yunhai.homework.model.v2.StudentSubmitRequest; +import com.yunhai.homework.model.v2.SubmissionActionRequest; +import com.yunhai.homework.service.impl.StudentHomeworkService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * 课堂作业 V2 学生端接口(PRD §18 路径一字对齐:/student/homeworks…)。 + * + * 身份:当前登录用户ID ↔ class_student 名册;仅可见本人班级作业,禁 IDOR。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/student/homeworks") +public class StudentHomeworkController { + + @Autowired + private StudentHomeworkService studentHomeworkService; + + /** 我的作业列表(tab=all/todo/done/closed) */ + @GetMapping + public BasePageResult list( + @RequestParam(value = "tab", defaultValue = "all") String tab, + BasePageQuery query) { + return studentHomeworkService.list(tab, query); + } + + /** 作业详情 */ + @GetMapping("/{id}") + public StudentHomeworkDetailVo detail(@PathVariable("id") Integer id) { + return studentHomeworkService.detail(id); + } + + /** 阅读上报(前端停留 ≥3 秒后调用;幂等) */ + @PostMapping("/{id}/read") + public void read(@PathVariable("id") Integer id) { + studentHomeworkService.read(id); + } + + /** 提交作业,返回新提交行 id */ + @PostMapping("/{id}/submissions") + public Map submit(@PathVariable("id") Integer id, + @RequestBody @Validated StudentSubmitRequest request) { + Integer submissionId = studentHomeworkService.submit(id, request); + return java.util.Collections.singletonMap("submissionId", submissionId); + } + + /** 撤回 / 打回后重交(action=withdraw|resubmit),返回新提交行 id */ + @PutMapping("/{id}/submissions/{submissionId}") + public Map action(@PathVariable("id") Integer id, + @PathVariable("submissionId") Integer submissionId, + @RequestBody @Validated SubmissionActionRequest request) { + Integer newId = studentHomeworkService.action(id, submissionId, request); + return java.util.Collections.singletonMap("submissionId", newId); + } + + /** 批阅结果(当前版本 + 历史版本) */ + @GetMapping("/{id}/result") + public StudentResultVo result(@PathVariable("id") Integer id) { + return studentHomeworkService.result(id); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java b/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java new file mode 100644 index 0000000..5335083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java @@ -0,0 +1,219 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.v2.ClassTreeNode; +import com.yunhai.homework.model.v2.ExtendRequest; +import com.yunhai.homework.model.v2.HomeworkStatsVo; +import com.yunhai.homework.model.v2.HomeworkV2Request; +import com.yunhai.homework.model.v2.HomeworkV2Vo; +import com.yunhai.homework.model.v2.RemindRequest; +import com.yunhai.homework.model.v2.ReturnRequest; +import com.yunhai.homework.model.v2.ReviewRequest; +import com.yunhai.homework.model.v2.ScheduleRequest; +import com.yunhai.homework.model.v2.StudentMatrixQuery; +import com.yunhai.homework.model.v2.StudentMatrixVo; +import com.yunhai.homework.model.v2.SubmissionVersionVo; +import com.yunhai.homework.model.v2.SubjectDto; +import com.yunhai.homework.model.v2.TeacherHomeworkQuery; +import com.yunhai.homework.model.v2.TeachingClassVo; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.SortDefault; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * 课堂作业 V2 教师端接口(PRD §18 路径一字对齐:/teacher/homeworks…)。 + * + * 与 notice 语义的旧接口(/{platform}/homework/*)并存:旧接口冻结维护, + * 新业务一律走本控制器;返回裸对象由 ApiResponseAdvice 包装 {code,msg,data}。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/teacher/homeworks") +public class TeacherHomeworkV2Controller { + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + // ============================ 页 1:发布作业 ============================ + + /** 创建作业(落草稿) */ + @PostMapping + public Integer create(@RequestBody @Validated HomeworkV2Request request) { + return teacherHomeworkV2Service.create(request); + } + + /** 编辑作业(仅草稿) */ + @PutMapping("/{id}") + public void edit(@PathVariable("id") Integer id, + @RequestBody @Validated HomeworkV2Request request) { + teacherHomeworkV2Service.edit(id, request); + } + + /** 删除作业(仅草稿) */ + @DeleteMapping("/{id}") + public void delete(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.delete(id); + } + + /** 立即发布(草稿 → 进行中) */ + @PostMapping("/{id}/publish") + public void publish(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.publish(id); + } + + /** 定时发布 / 取消定时(body: {scheduledAt} 或 {cancel:true}) */ + @PostMapping("/{id}/schedule") + public void schedule(@PathVariable("id") Integer id, @RequestBody ScheduleRequest request) { + teacherHomeworkV2Service.schedule(id, request); + } + + // ============================ 页 2:草稿箱 ============================ + + /** 草稿箱分页 */ + @GetMapping("/drafts") + public BasePageResult drafts(TeacherHomeworkQuery query, + @SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort) { + return teacherHomeworkV2Service.drafts(query, sort); + } + + // ============================ 页 3:已发布列表 ============================ + + /** 已发布列表分页(含应交/已读/已交/未读未交聚合) */ + @GetMapping("/published") + public BasePageResult published(TeacherHomeworkQuery query, + @SortDefault(sort = "publishedAt", direction = Sort.Direction.DESC) Sort sort) { + return teacherHomeworkV2Service.published(query, sort); + } + + /** 提醒未读/未交学生,返回实际提醒人数 */ + @PostMapping("/{id}/remind") + public Map remind(@PathVariable("id") Integer id, @RequestBody RemindRequest request) { + int reminded = teacherHomeworkV2Service.remind(id, request); + return java.util.Collections.singletonMap("reminded", reminded); + } + + /** 延长截止时间 */ + @PostMapping("/{id}/extend") + public void extend(@PathVariable("id") Integer id, @RequestBody @Validated ExtendRequest request) { + teacherHomeworkV2Service.extend(id, request); + } + + /** 复制为新草稿,返回新作业 id */ + @PostMapping("/{id}/copy") + public Integer copy(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.copy(id); + } + + /** 归档(只读,不再触发任何提醒) */ + @PostMapping("/{id}/archive") + public void archive(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.archive(id); + } + + /** 导出学生矩阵 Excel */ + @GetMapping("/{id}/export") + public void export(@PathVariable("id") Integer id, HttpServletResponse response) throws IOException { + teacherHomeworkV2Service.export(id, response); + } + + // ============================ 页 4:详情 / 统计 ============================ + + /** 作业详情 */ + @GetMapping("/{id}") + public HomeworkV2Vo detail(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.detail(id); + } + + /** 汇总统计卡片(总进度 + 分班进度) */ + @GetMapping("/{id}/stats") + public HomeworkStatsVo stats(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.stats(id); + } + + // ============================ 页 5:学生矩阵 ============================ + + /** 学生矩阵分页(阅读/提交/批阅状态,未交学生生成占位行) */ + @GetMapping("/{id}/students") + public BasePageResult students(@PathVariable("id") Integer id, + StudentMatrixQuery query) { + return teacherHomeworkV2Service.students(id, query); + } + + // ============================ 页 6:批阅 / 打回 / 版本 ============================ + + /** 保存批阅(→ REVIEWED,推送批阅完成通知) */ + @PostMapping("/{id}/submissions/{studentId}/review") + public void review(@PathVariable("id") Integer id, @PathVariable("studentId") Integer studentId, + @RequestBody @Validated ReviewRequest request) { + teacherHomeworkV2Service.review(id, studentId, request); + } + + /** 打回(→ RETURNED,推送打回通知) */ + @PostMapping("/{id}/submissions/{studentId}/return") + public void returnSubmission(@PathVariable("id") Integer id, @PathVariable("studentId") Integer studentId, + @RequestBody @Validated ReturnRequest request) { + teacherHomeworkV2Service.returnSubmission(id, studentId, request); + } + + /** 历史提交版本(批阅弹窗切换版本) */ + @GetMapping("/{id}/submissions/{studentId}/versions") + public List versions(@PathVariable("id") Integer id, + @PathVariable("studentId") Integer studentId) { + return teacherHomeworkV2Service.versions(id, studentId); + } + + // ============================ 辅助:学科 ============================ + + /** 我布置过的学科(本人已发布及之后作业的学科 ∪ 我的任教学科,合并去重;草稿不计) */ + @GetMapping("/subjects/assigned") + public List assignedSubjects() { + return teacherHomeworkV2Service.assignedSubjects(); + } + + /** 我的任教学科(bschclass 任教关系去重,名称取本校学科字典) */ + @GetMapping("/subjects/teaching") + public List teachingSubjects() { + return teacherHomeworkV2Service.teachingSubjects(); + } + + /** 按学科筛选我的任教班级(subId=学科编码;班级名称取学校班级列表) */ + @GetMapping("/subjects/classes") + public List teachingClasses(@RequestParam("subId") String subId) { + return teacherHomeworkV2Service.teachingClasses(subId); + } + + // ============================ 辅助:班级树 / 名册同步 ============================ + + /** 年级→班级树(发布选班;名册快照聚合) */ + @GetMapping("/classes") + public List classTree() { + return teacherHomeworkV2Service.classTree(); + } + + /** 手动触发名册同步(bschclass 接口就绪前为空操作) */ + @PostMapping("/classes/sync") + public Map syncClasses() { + int synced = classStudentService.syncAll(); + return java.util.Collections.singletonMap("synced", synced); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/TemplateController.java b/src/main/java/com/yunhai/homework/controller/api/TemplateController.java new file mode 100644 index 0000000..e9c593c --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/TemplateController.java @@ -0,0 +1,88 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.service.ITemplateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板接口(模板域)。 + * 路径前缀:{@code Config.API_PATH + "/template/"}(原 homework/Template/ 三级路径已修正)。 + * 写操作一律 POST(原 EditType/DelTemplate 为 GET 写操作,已 POST 化)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/template/") +public class TemplateController { + + @Autowired + private ITemplateService templateService; + + /** + * 获取模板类型列表。 + */ + @GetMapping("Typelist") + public List> getTypelist() { + return templateService.getTypelist(); + } + + /** + * 新增/编辑模板类型(原 GET EditType,已 POST 化)。 + */ + @PostMapping("TypeEdit") + public void typeEdit( + @RequestParam("typeid") Integer typeid, + @RequestParam("TypeName") String typeName) { + templateService.editType(typeid, typeName); + } + + /** + * 删除模板类型(原 GET DelType,已 POST 化)。 + */ + @PostMapping("TypeDelete") + public void typeDelete(@RequestParam("typeid") Integer typeid) { + templateService.delType(typeid); + } + + /** + * 分页获取模板列表。 + */ + @GetMapping("List") + public Map list( + @RequestParam("PageSize") Integer pageSize, + @RequestParam("PageIndex") Integer pageIndex, + @RequestParam(value = "typeid", defaultValue = "0") Integer typeid) { + return templateService.getTemplatelist(pageSize, pageIndex, typeid); + } + + /** + * 新增/编辑模板。 + */ + @PostMapping("TemplateEdit") + public void templateEdit( + @RequestParam(value = "SerID", required = false) Integer serID, + @RequestParam("Title") String title, + @RequestParam("Contents") String contents, + @RequestParam(value = "Type", required = false) Integer type, + @RequestParam(value = "Cover", required = false) Integer cover) { + templateService.editTemplate(serID, title, contents, type, cover); + } + + /** + * 删除模板(原 GET DelTemplate,已 POST 化)。 + */ + @PostMapping("TemplateDelete") + public void templateDelete(@RequestParam("SerID") Integer serid) { + templateService.delTemplate(serid); + } + + /** + * 获取单个模板详情。 + */ + @GetMapping("TemplateFirst") + public Map templateFirst(@RequestParam("serid") Integer serid) { + return templateService.getTemplateFist(serid); + } +} diff --git a/src/main/java/com/yunhai/homework/entity/BaseEntity.java b/src/main/java/com/yunhai/homework/entity/BaseEntity.java new file mode 100644 index 0000000..5f98e8c --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/BaseEntity.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.common.listener.HomeworkAuditListener; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 所有实体的公共基类:自增主键 + 创建人/时间 + 更新人/时间(由 {@link HomeworkAuditListener} 自动填充)。 + */ +@MappedSuperclass +@Getter +@Setter +@EntityListeners(HomeworkAuditListener.class) +public abstract class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 创建人ID(平台用户ID) + */ + @Column(name = "create_by", updatable = false) + private String createBy; + + /** + * 创建时间 + */ + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + /** + * 更新人ID(平台用户ID) + */ + @Column(name = "update_by") + private String updateBy; + + /** + * 更新时间 + */ + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/ClassStudent.java b/src/main/java/com/yunhai/homework/entity/ClassStudent.java new file mode 100644 index 0000000..793e295 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ClassStudent.java @@ -0,0 +1,59 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 班级学生名册快照(PRD class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据),每日全量刷 + 手动触发; + * 同步不物理删,转出置 status=0。学生身份口径:用户中心数字ID(UserContext.getUserId()) + * ↔ student_id(SchStudent.UserStuSerID),名册仅存学生ID + 姓名快照。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "class_student", + uniqueConstraints = @UniqueConstraint(name = "uk_class_student", columnNames = {"class_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ClassStudent extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; + + /** 年级名快照 */ + @Column(name = "grade_name", length = 50) + private String gradeName; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 在班状态:1 在班 / 0 已转出退课 */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** 离班时间 */ + @Column(name = "leave_at") + private LocalDateTime leaveAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroup.java b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java new file mode 100644 index 0000000..a002e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 联系组。对应原 .NET Contacts_Group 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroup extends BaseEntity { + + /** 组名称 */ + @Column(name = "group_name", length = 200) + private String groupName; + + /** 创建人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "creator_id") + private Integer creatorId; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java new file mode 100644 index 0000000..eb07763 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 联系组-用户关联。对应原 .NET Contacts_Tea 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group_user", + uniqueConstraints = @UniqueConstraint(name = "uk_group_user", + columnNames = {"group_id", "user_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroupUser extends BaseEntity { + + /** 联系组ID */ + @Column(name = "group_id", nullable = false) + private Integer groupId; + + /** 用户ID(基础平台用户ID) */ + @Column(name = "user_id", nullable = false) + private Integer userId; + + /** 用户姓名 */ + @Column(name = "user_name", length = 64) + private String userName; +} diff --git a/src/main/java/com/yunhai/homework/entity/Homework.java b/src/main/java/com/yunhai/homework/entity/Homework.java new file mode 100644 index 0000000..6e615f2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Homework.java @@ -0,0 +1,208 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 课程作业主表。对应原 .NET Notify_Message(已剔除短信/模板/门户等无关字段)。 + * + * 阅读状态不落主表:采用「懒标记」模型——读者首次查看时写入 {@link HomeworkRead}, + * 不存在阅读记录即视为未读,从而无需在发作业时枚举全校用户预插接收行。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Homework extends BaseEntity { + + /** + * 作业标题 + */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** + * 学校ID(多租户隔离:发布时从当前用户角色填充,查询时按校过滤)。 + */ + @Column(name = "sch_id") + private Integer schId; + + /** + * 作业内容 + */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** + * 作业类型:1 普通 / 2 重要(见 {@link com.yunhai.homework.enums.HomeworkTypeEnum}) + */ + @Column(name = "type", nullable = false) + private Integer type = 1; + + /** + * 发布人ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "publisher_id") + private Integer publisherId; + + /** + * 发布人姓名 + */ + @Column(name = "publisher_name", length = 64) + private String publisherName; + + /** + * 发布部门ID(原 DeptID) + */ + @Column(name = "dept_id", length = 64) + private String deptId; + + /** + * 发布部门名称 + */ + @Column(name = "dept_name", length = 128) + private String deptName; + + /** + * 状态:1 已发布(无草稿态,见 {@link com.yunhai.homework.enums.HomeworkStatusEnum}) + */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** + * 是否置顶:0 否 / 1 是(原 topmost) + */ + @Column(name = "topmost") + private Integer topmost = 0; + + // ==================== 迁移补全字段(对应 C# Notify_Message) ==================== + + /** + * 接收人列表(JSON 数组,如 ["10012","10013"] 或接收组ID列表)。 + * 对应 C# Notify_Message.ReceiveList + */ + @Column(name = "receive_list", columnDefinition = "LONGTEXT") + private String receiveList; + + /** + * 密码发送(对应 C# Notify_Message.PwdSend) + */ + @Column(name = "pwd_send", length = 255) + private String pwdSend; + + /** + * 抄送人列表(对应 C# Notify_Message.ChaoSend) + */ + @Column(name = "chao_send", length = 2000) + private String chaoSend; + + /** + * 已读数(发送时初始化为 0,也可由 HomeworkRead 表 COUNT 实时计算)。 + * 对应 C# Notify_Message.readcount + */ + @Column(name = "read_count") + private Integer readCount = 0; + + /** + * 应读数(接收人总数)。对应 C# Notify_Message.allowcount + */ + @Column(name = "allow_count") + private Integer allowCount = 0; + + /** + * 父作业ID(转发时关联原作业)。对应 C# Notify_Message.fkid + */ + @Column(name = "parent_id") + private Integer parentId; + + /** + * 回复ID。对应 C# Notify_Message.reid + */ + @Column(name = "reply_id", length = 255) + private String replyId; + + /** + * 是否撤回:0 否 / 1 是。对应 C# Notify_Message.ispull + */ + @Column(name = "is_pulled") + private Integer isPulled = 0; + + /** + * 定时发送时间(到时间后自动发布)。对应 C# Notify_Message.DsfsTime。 + * PRD 语义中的 scheduled_at 复用本列。 + */ + @Column(name = "timing_send_time") + private LocalDateTime timingSendTime; + + /** + * 短信发送标记:0 不发短信 / 1 发短信。对应 C# Notify_Message.dxbit + */ + @Column(name = "sms_flag") + private Integer smsFlag = 0; + + // ==================== 课堂作业 V3(PRD V1.0)扩展列 ==================== + + /** + * 学科(字典值)。 + */ + @Column(name = "subject", length = 50) + private String subject; + + /** + * 截止时间(精确到分钟;空 = 仅查阅型,不设截止,到点关闭任务跳过)。 + */ + @Column(name = "deadline_at") + private LocalDateTime deadlineAt; + + /** + * 作业流程状态(PRD 五态,见 {@link com.yunhai.homework.enums.HwStatusEnum})。 + * notice 原 status 列继续承载发布/撤回语义,两者并存。 + */ + @Column(name = "hw_status", nullable = false, length = 20) + private String hwStatus = com.yunhai.homework.enums.HwStatusEnum.DRAFT_UNPUBLISHED.getCode(); + + /** + * 发布时间(立即发布或定时到点发布时写入)。 + */ + @Column(name = "published_at") + private LocalDateTime publishedAt; + + /** + * 提交方式,JSON 数组文本,如 ["image","attach"]。 + * 取值:text / image / attach / none(PRD 的 图片/文件/两者 映射为 + * ["image"] / ["attach"] / ["image","attach"])。 + */ + @Column(name = "submit_type", length = 100) + private String submitType; + + /** + * 截止后是否允许逾期提交:0 否 / 1 是。 + */ + @Column(name = "allow_late", nullable = false) + private Integer allowLate = 0; + + /** + * 提醒设置:截止前 1 天提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1d", nullable = false) + private Integer remind1d = 1; + + /** + * 提醒设置:截止前 1 小时提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1h", nullable = false) + private Integer remind1h = 1; + + /** + * 归档时间(教师手动归档时写入;归档后只读,所有提醒任务跳过)。 + */ + @Column(name = "archived_at") + private LocalDateTime archivedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java new file mode 100644 index 0000000..6fc7804 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业附件表(PRD homework_attachment)。文件实体仍存 up_file(file_id 关联 + file_url 冗余)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_attachment") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkAttachment extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** up_file.id(可空:允许外部 URL 附件) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file / ... */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号(越小越靠前) */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkClass.java b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java new file mode 100644 index 0000000..c01cefb --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 作业-班级关联表(PRD homework_class)。 + * 发布/编辑时由 receiveList("all" 或班级 id 串)解析写入,是应交名单与统计的权威关联。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_class", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_class", columnNames = {"homework_id", "class_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkClass extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkRead.java b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java new file mode 100644 index 0000000..35b773a --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.time.LocalDateTime; + +/** + * 作业阅读记录(懒标记)。对应原 .NET Notify_ReceiveList 的「逐用户阅读状态」语义, + * 但仅在读者首次查看时落库,避免发作业时预插全员。 + * + * 唯一约束 (homework_id, reader_id):同一用户对同一作业只记一条已读。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read", + uniqueConstraints = @UniqueConstraint( + name = "uk_homework_reader", + columnNames = {"homework_id", "reader_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkRead extends BaseEntity { + + /** + * 作业ID(关联 homework.id) + */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** + * 读者ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "reader_id", nullable = false) + private Integer readerId; + + /** + * 读者姓名 + */ + @Column(name = "reader_name", length = 64) + private String readerName; + + /** + * 阅读时间 + */ + @Column(name = "read_time") + private LocalDateTime readTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java new file mode 100644 index 0000000..8179e79 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java @@ -0,0 +1,58 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 学生作业阅读记录(PRD homework_read_record)。 + * + * 学生维度,含首次/最近阅读时间,与 notice 语义的 {@link HomeworkRead}(教师/阅读者已读)互不影响。 + * 行仅在判定已读时写入;未读名单 = 名册左连本表(无记录或 is_read=0)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read_record", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student", columnNames = {"homework_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReadRecord extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", length = 64) + private String classId; + + /** 阅读状态:0 UNREAD / 1 READ */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 首次阅读时间(写入后不再变更) */ + @Column(name = "first_read_at") + private LocalDateTime firstReadAt; + + /** 最近阅读时间(每次上报刷新) */ + @Column(name = "last_read_at") + private LocalDateTime lastReadAt; + + /** 阅读次数 */ + @Column(name = "read_count", nullable = false) + private Integer readCount = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java new file mode 100644 index 0000000..97e068b --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java @@ -0,0 +1,45 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 作业接收人关联表(替代旧 receive_list JSON 字符串存储)。 + * + * 每条记录代表一个作业的一个接收人(用户ID + 姓名快照,不含账号)。 + * 全员作业(receive_list="ALL")不写明细行, + * 读取时按 receive_list="ALL" 标识走旧逻辑兜底。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_receiver", + indexes = { + @Index(name = "idx_homework_id", columnList = "homework_id"), + @Index(name = "idx_receiver", columnList = "receiver_id") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReceiver extends BaseEntity { + + /** 作业ID(关联 homework.id) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 接收人ID(用户中心数字ID) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收类型:1 接收 / 2 抄送 */ + @Column(name = "receive_type", nullable = false) + private Integer receiveType = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java new file mode 100644 index 0000000..4626044 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java @@ -0,0 +1,98 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.enums.ReviewStatusEnum; +import com.yunhai.homework.enums.SubmitStatusEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 作业提交表,多版本建模(PRD homework_submission)。 + * + * 同一学生 (homework_id, student_id) 下 version 单调递增;提交/打回重交/撤回均新增行, + * 旧行 is_latest 置 0 保留历史;is_latest=1 的行是当前有效版本。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_submission", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student_ver", + columnNames = {"homework_id", "student_id", "version"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkSubmission extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", nullable = false, length = 50) + private String className; + + /** 提交版本,同一学生单调递增(撤回/重交均 +1) */ + @Column(name = "version", nullable = false) + private Integer version = 1; + + /** 是否当前版本:1 是 / 0 历史 */ + @Column(name = "is_latest", nullable = false) + private Integer isLatest = 1; + + /** 提交状态(见 {@link SubmitStatusEnum}) */ + @Column(name = "status", nullable = false, length = 20) + private String status = SubmitStatusEnum.SUBMITTED.getCode(); + + /** 是否逾期:0 否 / 1 是 */ + @Column(name = "is_late", nullable = false) + private Integer isLate = 0; + + /** 提交时间(NOT_SUBMITTED 空行为空) */ + @Column(name = "submitted_at") + private LocalDateTime submittedAt; + + /** 文本作业内容 */ + @Column(name = "text", columnDefinition = "TEXT") + private String text; + + /** 评分 */ + @Column(name = "score", precision = 6, scale = 2) + private BigDecimal score; + + /** 评语(打回时 = 订正原因,必填) */ + @Column(name = "comment", columnDefinition = "TEXT") + private String comment; + + /** 批阅状态(见 {@link ReviewStatusEnum}) */ + @Column(name = "review_status", nullable = false, length = 20) + private String reviewStatus = ReviewStatusEnum.PENDING.getCode(); + + /** 批阅人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "reviewed_by") + private Integer reviewedBy; + + /** 批阅人姓名快照 */ + @Column(name = "reviewed_by_name", length = 64) + private String reviewedByName; + + /** 批阅时间 */ + @Column(name = "reviewed_at") + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java new file mode 100644 index 0000000..f088c92 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板。对应原 .NET Notify_Template 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_template") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkTemplate extends BaseEntity { + + /** 模板标题 */ + @Column(name = "title", length = 200) + private String title; + + /** 模板内容(HTML) */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** 封面图片文件ID */ + @Column(name = "cover") + private Integer cover; + + /** 引用次数 */ + @Column(name = "ref_count") + private Integer refCount = 0; + + /** 模板类型ID */ + @Column(name = "type_id") + private Integer typeId; +} diff --git a/src/main/java/com/yunhai/homework/entity/Notification.java b/src/main/java/com/yunhai/homework/entity/Notification.java new file mode 100644 index 0000000..2a931a1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Notification.java @@ -0,0 +1,71 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 站内通知(PRD notification)。六类通知的统一落表 + 已读状态。 + * 通知已读独立于作业阅读记录(本表 is_read 只反映通知列表已读)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "notification", + indexes = { + @Index(name = "idx_receiver", columnList = "receiver_id, is_read"), + @Index(name = "idx_hw_type", columnList = "homework_id, type") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Notification extends BaseEntity { + + /** 通知类型(见 {@link com.yunhai.homework.enums.NotificationTypeEnum}) */ + @Column(name = "type", nullable = false, length = 30) + private String type; + + /** 关联作业ID */ + @Column(name = "homework_id") + private Integer homeworkId; + + /** 关联业务ID(如 submissionId) */ + @Column(name = "related_id") + private Integer relatedId; + + /** 通知标题 */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** 通知内容 */ + @Column(name = "content", length = 1000) + private String content; + + /** 接收人ID(学生 = student_id,教师 = 用户中心数字ID,同一 ID 空间) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收人角色:student / teacher */ + @Column(name = "receiver_role", nullable = false, length = 20) + private String receiverRole = "student"; + + /** 是否已读:0 未读 / 1 已读 */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 已读时间 */ + @Column(name = "read_at") + private LocalDateTime readAt; + + /** 发送通道:inapp 站内 / sms 短信 */ + @Column(name = "send_channel", nullable = false, length = 20) + private String sendChannel = "inapp"; +} diff --git a/src/main/java/com/yunhai/homework/entity/SchClassTeach.java b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java new file mode 100644 index 0000000..d1a091f --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java @@ -0,0 +1,46 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 班级任教关系快照(bschclass sch_class_teach 本地镜像,PRD 口径同 class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据);bschclass 任教接口 + * 不可用时,任教学科链路降级读本表。教师口径:用户中心数字ID(UserContext.getUserId()); + * class_id 与 class_student.class_id 同一取值域。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sch_class_teach", + uniqueConstraints = @UniqueConstraint(name = "uk_sch_class_teach", columnNames = {"school_id", "class_id", "tea_id", "sub_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SchClassTeach extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID(与 class_student.class_id 同一取值域) */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 教师ID(用户中心数字ID) */ + @Column(name = "tea_id", nullable = false) + private Integer teaId; + + /** 学科编码(bschclass SubID;本地无字典时编码即展示名) */ + @Column(name = "sub_id", nullable = false, length = 64) + private String subId; + + /** 有效状态:1 有效 / 0 失效 */ + @Column(name = "status", nullable = false) + private Integer status = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsModule.java b/src/main/java/com/yunhai/homework/entity/SmsModule.java new file mode 100644 index 0000000..3f4d444 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsModule.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 短信模块配置。对应原 .NET SchSms 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_module") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsModule extends BaseEntity { + + /** 模块名称 */ + @Column(name = "module_name", length = 100) + private String moduleName; + + /** 模块标识 */ + @Column(name = "module_code", length = 50) + private String moduleCode; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java new file mode 100644 index 0000000..428d271 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java @@ -0,0 +1,37 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 学校短信开通配置。对应原 .NET SchSms_Set 表。 + * 记录某学校某模块是否开通短信。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_school_config") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsSchoolConfig extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 学校名称 */ + @Column(name = "school_name", length = 200) + private String schoolName; + + /** 模块ID */ + @Column(name = "module_id") + private Integer moduleId; + + /** 是否开通短信 */ + @Column(name = "is_sms") + private Boolean isSms = false; +} diff --git a/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java new file mode 100644 index 0000000..85df7ba --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java @@ -0,0 +1,54 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 提交附件表(PRD submission_attachment)。替代 JSON 方案,支撑矩阵缩略图与批阅大图预览。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "submission_attachment", + indexes = @Index(name = "idx_sub", columnList = "submission_id")) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SubmissionAttachment extends BaseEntity { + + /** 提交行ID(homework_submission.id) */ + @Column(name = "submission_id", nullable = false) + private Integer submissionId; + + /** 作业ID(冗余,便于统计与清理) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + @Column(name = "student_id") + private Integer studentId; + + /** up_file.id(可空:允许外部 URL) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号 */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/SysDict.java b/src/main/java/com/yunhai/homework/entity/SysDict.java new file mode 100644 index 0000000..3091084 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SysDict.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 系统字典(发文部门等)。对应原 .NET Common_DeptDic 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sys_dict") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SysDict extends BaseEntity { + + /** 字典类型(如 "dept"=发文部门) */ + @Column(name = "dict_type", length = 50) + private String dictType; + + /** 字典 key(原 CodeID) */ + @Column(name = "dict_key", length = 100) + private String dictKey; + + /** 字典 value(显示名称,原 Name) */ + @Column(name = "dict_value", length = 200) + private String dictValue; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; + + /** 是否启用:0 禁用 / 1 启用(原 Enable) */ + @Column(name = "enable_flag") + private Integer enableFlag = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/TemplateType.java b/src/main/java/com/yunhai/homework/entity/TemplateType.java new file mode 100644 index 0000000..aab8163 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/TemplateType.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板类型。对应原 .NET Notify_TemplateType 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "template_type") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class TemplateType extends BaseEntity { + + /** 类型名称 */ + @Column(name = "type_name", length = 100) + private String typeName; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/UpFile.java b/src/main/java/com/yunhai/homework/entity/UpFile.java new file mode 100644 index 0000000..38248c7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/UpFile.java @@ -0,0 +1,47 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 附件文件记录。对应原 .NET Common_UpFiles 表。 + * + * 存储文件元信息(真实文件名、存储路径、大小、类型),物理文件存储在 + * application.yml 的 file.upload-path 目录下。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "up_file") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class UpFile extends BaseEntity { + + /** 应用类型(如 "homework"),用于区分不同业务的附件 */ + @Column(name = "topic_type", length = 50) + private String topicType; + + /** 关联主题ID(如作业 serid) */ + @Column(name = "topic_id") + private Integer topicId; + + /** 文件真实名称(用户上传时的原始文件名) */ + @Column(name = "file_true_name", length = 255) + private String fileTrueName; + + /** 文件存储路径(相对路径,如 homework/20260823/uuid.pdf) */ + @Column(name = "file_name", length = 500) + private String fileName; + + /** 文件大小(字节) */ + @Column(name = "file_size") + private Integer fileSize; + + /** 文件后缀名(如 pdf, docx, jpg) */ + @Column(name = "file_type", length = 20) + private String fileType; +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java new file mode 100644 index 0000000..124bec9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业状态。无草稿态:发作业一律置为「已发布」, + * 定时发送通过 timingSendTime + 查询侧「已到点」过滤控制可见性。 + * 对应原 .NET Notify_Message.status(原系统已发布=2,此处归一化为 1)。 + */ +@Getter +public enum HomeworkStatusEnum { + + PUBLISHED(1, "已发布"); + + private final int code; + private final String desc; + + HomeworkStatusEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkStatusEnum of(Integer code) { + if (code == null) { + return PUBLISHED; + } + for (HomeworkStatusEnum e : values()) { + if (e.code == code) { + return e; + } + } + return PUBLISHED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java new file mode 100644 index 0000000..077396d --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业类型。对应原 .NET Notify_Message.type(原系统作业类=2,此处归一化)。 + */ +@Getter +public enum HomeworkTypeEnum { + + NORMAL(1, "普通作业"), + IMPORTANT(2, "重要作业"); + + private final int code; + private final String desc; + + HomeworkTypeEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkTypeEnum of(Integer code) { + if (code == null) { + return NORMAL; + } + for (HomeworkTypeEnum e : values()) { + if (e.code == code) { + return e; + } + } + return NORMAL; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java new file mode 100644 index 0000000..8b10083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业流程状态(PRD 五态,存 homework.hw_status)。 + * + * 与 notice 原 status(Integer,发布/撤回语义)并存:本枚举承载课堂作业业务流转, + * 流转收敛在 HomeworkStateService,条件更新保证幂等。 + */ +@Getter +public enum HwStatusEnum { + + /** 草稿 - 未发布 */ + DRAFT_UNPUBLISHED("DRAFT_UNPUBLISHED", "草稿-未发布"), + /** 草稿 - 定时待发布 */ + DRAFT_SCHEDULED("DRAFT_SCHEDULED", "草稿-定时待发布"), + /** 已发布 - 进行中 */ + PUBLISHED("PUBLISHED", "已发布-进行中"), + /** 已截止 */ + CLOSED("CLOSED", "已截止"), + /** 已归档(只读,不再触发任何提醒) */ + ARCHIVED("ARCHIVED", "已归档"); + + private final String code; + private final String desc; + + HwStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HwStatusEnum of(String code) { + if (code == null) { + return DRAFT_UNPUBLISHED; + } + for (HwStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return DRAFT_UNPUBLISHED; + } + + public boolean isDraft() { + return this == DRAFT_UNPUBLISHED || this == DRAFT_SCHEDULED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java new file mode 100644 index 0000000..df75af1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 站内通知类型(PRD 六类,存 notification.type)。 + */ +@Getter +public enum NotificationTypeEnum { + + /** 作业发布(对应班级学生) */ + HOMEWORK_PUBLISHED("HOMEWORK_PUBLISHED", "作业发布"), + /** 截止提醒(截止前 1 天 / 1 小时) */ + DEADLINE_REMINDER("DEADLINE_REMINDER", "截止提醒"), + /** 未交提醒(教师手动) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未交提醒"), + /** 未读提醒(教师手动) */ + NOT_READ("NOT_READ", "未读提醒"), + /** 批阅完成 */ + REVIEW_DONE("REVIEW_DONE", "批阅完成"), + /** 打回通知 */ + RETURNED("RETURNED", "打回通知"), + /** + * 定时发布失败回退(PRD §17.1 教师泳道要求通知教师,但 PRD 枚举表未列—— + * 按 classwork-homework-plan-v3 §13-2 建议补充,待产品确认命名)。 + */ + SCHEDULE_FAILED("SCHEDULE_FAILED", "定时发布失败"), + /** + * 截止时间变更(PRD §17.1 延长截止要求通知学生,PRD 枚举表未列——同上补充)。 + */ + DEADLINE_CHANGED("DEADLINE_CHANGED", "截止时间变更"); + + private final String code; + private final String desc; + + NotificationTypeEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static NotificationTypeEnum of(String code) { + if (code == null) { + return null; + } + for (NotificationTypeEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return null; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java new file mode 100644 index 0000000..031eddc --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java @@ -0,0 +1,39 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 批阅状态(PRD 三态,存 homework_submission.review_status)。 + * 与 SubmitStatusEnum 同源联动:批阅保存 → status=REVIEWED + review_status=REVIEWED; + * 打回 → status=RETURNED + review_status=RETURNED。 + */ +@Getter +public enum ReviewStatusEnum { + + /** 待批阅 */ + PENDING("PENDING", "待批阅"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"), + /** 已打回 */ + RETURNED("RETURNED", "已打回"); + + private final String code; + private final String desc; + + ReviewStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static ReviewStatusEnum of(String code) { + if (code == null) { + return PENDING; + } + for (ReviewStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return PENDING; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java new file mode 100644 index 0000000..7d6aaca --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java @@ -0,0 +1,50 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 学生提交状态(PRD 五态,存 homework_submission.status)。 + * + * NOT_SUBMITTED 空行仅由「撤回」产生;未提交学生本身不落行(统计时由名册左连补位)。 + */ +@Getter +public enum SubmitStatusEnum { + + /** 未提交(撤回后产生的空提交行) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未提交"), + /** 已提交(含打回后重新提交) */ + SUBMITTED("SUBMITTED", "已提交"), + /** 逾期提交(截止后提交,is_late=1) */ + LATE_SUBMITTED("LATE_SUBMITTED", "逾期提交"), + /** 已打回(待重新提交) */ + RETURNED("RETURNED", "已打回"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"); + + private final String code; + private final String desc; + + SubmitStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static SubmitStatusEnum of(String code) { + if (code == null) { + return NOT_SUBMITTED; + } + for (SubmitStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return NOT_SUBMITTED; + } + + /** + * 是否计入「已交」口径:打回未重交也算已交(PRD 学生矩阵口径)。 + */ + public boolean countsAsSubmitted() { + return this != NOT_SUBMITTED; + } +} diff --git a/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java new file mode 100644 index 0000000..5377254 --- /dev/null +++ b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java @@ -0,0 +1,161 @@ +package com.yunhai.homework.job; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.enums.HwStatusEnum; +import com.yunhai.homework.enums.NotificationTypeEnum; +import com.yunhai.homework.repository.HomeworkClassRepository; +import com.yunhai.homework.repository.HomeworkRepository; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.HomeworkStateService; +import com.yunhai.homework.service.impl.NotificationService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 课堂作业定时任务(PRD §17.1 调度泳道)。 + * + * 全部基于状态机 CAS 条件更新,天然幂等,可安全重复执行; + * ARCHIVED 作业被显式跳过(归档后不再触发任何提醒)。 + */ +@Component +public class HomeworkScheduledJobs { + + private static final Logger log = LoggerFactory.getLogger(HomeworkScheduledJobs.class); + + @Autowired + private HomeworkRepository homeworkRepository; + + @Autowired + private HomeworkClassRepository homeworkClassRepository; + + @Autowired + private HomeworkStateService stateService; + + @Autowired + private NotificationService notificationService; + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + /** + * 定时发布:每分钟扫描到点的 DRAFT_SCHEDULED 作业 → PUBLISHED + 作业发布通知; + * 失败回退 DRAFT_UNPUBLISHED 并站内通知教师。 + */ + @Scheduled(cron = "0 * * * * ?") + public void scheduledPublish() { + List due = homeworkRepository + .findByHwStatusAndTimingSendTimeLessThanEqual(HwStatusEnum.DRAFT_SCHEDULED.getCode(), + LocalDateTime.now()); + if (due.isEmpty()) { + return; + } + log.info("[scheduledPublish] 到点作业 {} 个", due.size()); + for (Homework homework : due) { + Integer id = homework.getId(); + try { + teacherHomeworkV2Service.publishInternal(id, HwStatusEnum.DRAFT_SCHEDULED); + log.info("[scheduledPublish] 发布成功 homeworkId={}", id); + } catch (Exception e) { + log.error("[scheduledPublish] 发布失败,回退草稿 homeworkId={}", id, e); + try { + boolean rolledBack = stateService.rollbackFromSchedule(id); + if (rolledBack) { + notificationService.send(NotificationTypeEnum.SCHEDULE_FAILED, id, null, + homework.getPublisherId(), homework.getPublisherName(), + NotificationService.ROLE_TEACHER, + "定时发布失败:《" + homework.getTitle() + "》", + "定时发布任务执行异常,作业已回退为「草稿-未发布」,请重新发布。"); + } + } catch (Exception ex) { + log.error("[scheduledPublish] 回退失败 homeworkId={}", id, ex); + } + } + } + } + + /** + * 到点截止:每分钟扫描已过截止时间的 PUBLISHED 作业 → CLOSED(无对应通知类型,不推送)。 + */ + @Scheduled(cron = "30 * * * * ?") + public void deadlineClose() { + List overdue = homeworkRepository + .findByHwStatusAndDeadlineAtBefore(HwStatusEnum.PUBLISHED.getCode(), LocalDateTime.now()); + for (Homework homework : overdue) { + if (stateService.cas(homework.getId(), HwStatusEnum.PUBLISHED, HwStatusEnum.CLOSED)) { + log.info("[deadlineClose] 作业已截止 homeworkId={}", homework.getId()); + } + } + } + + /** + * 截止提醒:每分钟扫描截止时间在 24h 内的 PUBLISHED 作业, + * 命中「1 天档 / 1 小时档」窗口且开关开启时向未交学生推送 DEADLINE_REMINDER; + * 档位去重(related_id:1=1 天档 2=1 小时档),每作业每档至多发一轮。 + */ + @Scheduled(cron = "40 * * * * ?") + public void deadlineReminder() { + LocalDateTime now = LocalDateTime.now(); + List window = homeworkRepository + .findByHwStatusAndDeadlineAtBetween(HwStatusEnum.PUBLISHED.getCode(), now, now.plusHours(24)); + for (Homework homework : window) { + if (homework.getDeadlineAt() == null) { + continue; + } + // 1 小时档:截止前 [55, 60) 分钟窗口 + if (homework.getRemind1h() != null && homework.getRemind1h() == 1 + && inWindow(homework.getDeadlineAt(), now, 55, 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 2)) { + sendReminder(homework, 2, "1 小时"); + } + // 1 天档:截止前 [23, 24) 小时窗口 + if (homework.getRemind1d() != null && homework.getRemind1d() == 1 + && inWindow(homework.getDeadlineAt(), now, 23 * 60, 24 * 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 1)) { + sendReminder(homework, 1, "1 天"); + } + } + } + + private boolean inWindow(LocalDateTime deadline, LocalDateTime now, int fromMinutes, int toMinutes) { + long minutes = java.time.Duration.between(now, deadline).toMinutes(); + return minutes >= fromMinutes && minutes < toMinutes; + } + + private void sendReminder(Homework homework, int slot, String slotDesc) { + // 通知接收人直接使用学生ID(名册口径:student_id = 平台用户ID) + List targets = teacherHomeworkV2Service.notSubmittedStudentIds(homework.getId()).stream() + .distinct() + .collect(Collectors.toList()); + if (targets.isEmpty()) { + return; + } + int sent = notificationService.sendBatch(NotificationTypeEnum.DEADLINE_REMINDER, homework.getId(), slot, + NotificationService.distinct(targets), + NotificationService.deadlineReminderTitle(homework), + NotificationService.deadlineReminderContent(homework) + "(剩余不足 " + slotDesc + ")"); + log.info("[deadlineReminder] homeworkId={}, slot={}, sent={}", homework.getId(), slot, sent); + } + + /** + * 名册同步:每日 02:00 全量刷新 class_student(bschclass 接口就绪前仅日志)。 + */ + @Scheduled(cron = "${homework.sync.class-data-cron:0 0 2 * * ?}") + public void classDataSync() { + try { + classStudentService.syncAll(); + } catch (Exception e) { + log.error("[classDataSync] 名册同步失败", e); + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageQuery.java b/src/main/java/com/yunhai/homework/model/BasePageQuery.java new file mode 100644 index 0000000..7f46326 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageQuery.java @@ -0,0 +1,14 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +/** + * 统一分页查询入参。pageIndex 从 1 起。 + */ +@Data +public class BasePageQuery { + private int pageIndex = 1; + private int pageSize = 10; + /** 通用模糊搜索关键词 */ + private String keyword; +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageResult.java b/src/main/java/com/yunhai/homework/model/BasePageResult.java new file mode 100644 index 0000000..058c96c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageResult.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +import java.util.List; + +/** + * 统一分页响应外壳。 + */ +@Data +public class BasePageResult { + /** 总记录数 */ + private int total; + /** 当前页数据 */ + private List content; + /** 当前页码(从 1 起) */ + private int pageIndex; + /** 每页条数 */ + private int pageSize; + /** 总页数 */ + private int totalPages; + /** 是否还有下一页 */ + private boolean hasNext; + + public BasePageResult(List content, int total, int pageIndex, int pageSize, int totalPages, boolean hasNext) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + } +} diff --git a/src/main/java/com/yunhai/homework/model/SchTeaList.java b/src/main/java/com/yunhai/homework/model/SchTeaList.java new file mode 100644 index 0000000..80f9b9c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/SchTeaList.java @@ -0,0 +1,106 @@ +package com.yunhai.homework.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * 班级管理接口-教师(bschclass {@code /oapi/DbClass/GetSchteaList} 响应项)。 + * 对应 C# BestSchool.Entity.Model.SchTeaList。 + */ +public class SchTeaList { + + /** 学校 GUID(SchSerID) */ + @JsonProperty("SchSerID") + private String schSerID; + + @JsonProperty("TeaTel") + private String teaTel; + + /** 教师姓名 */ + @JsonProperty("TeaName") + private String teaName; + + /** 教师用户ID */ + @JsonProperty("UserTeaSerID") + private Integer userTeaSerID; + + @JsonProperty("TeaIDCard") + private String teaIDCard; + + /** 教师工号 */ + @JsonProperty("TeaNo") + private String teaNo; + + /** 教师所属组ID列表(兼容旧字段 GroupIds) */ + @JsonProperty("GroupIds") + private List groupIds; + + /** 教师所属组ID(接口现行字段 GroupID,单个) */ + @JsonProperty("GroupID") + private Integer groupId; + + public Integer getGroupId() { + return groupId; + } + + public void setGroupId(Integer groupId) { + this.groupId = groupId; + } + + public String getSchSerID() { + return schSerID; + } + + public void setSchSerID(String schSerID) { + this.schSerID = schSerID; + } + + public String getTeaTel() { + return teaTel; + } + + public void setTeaTel(String teaTel) { + this.teaTel = teaTel; + } + + public String getTeaName() { + return teaName; + } + + public void setTeaName(String teaName) { + this.teaName = teaName; + } + + public Integer getUserTeaSerID() { + return userTeaSerID; + } + + public void setUserTeaSerID(Integer userTeaSerID) { + this.userTeaSerID = userTeaSerID; + } + + public String getTeaIDCard() { + return teaIDCard; + } + + public void setTeaIDCard(String teaIDCard) { + this.teaIDCard = teaIDCard; + } + + public String getTeaNo() { + return teaNo; + } + + public void setTeaNo(String teaNo) { + this.teaNo = teaNo; + } + + public List getGroupIds() { + return groupIds; + } + + public void setGroupIds(List groupIds) { + this.groupIds = groupIds; + } +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java new file mode 100644 index 0000000..cf9c417 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 班级 DTO(/oapi/VSchClass/GetVClassList,SchClass 视图)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchClassDto { + + /** 班级业务ID(sch_class.class_id,scopeType=班级 的 scopeId;学生 ClassID 指向它) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级名称 */ + @JsonProperty("ClassName") + private String className; + + /** 年级ID(= gradeId) */ + @JsonProperty("Levell") + private Integer gradeId; + + /** 排序号 */ + @JsonProperty("sx") + private Integer sort; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java new file mode 100644 index 0000000..01ff15b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 年级 DTO(/oapi/Schoolen/GetSchLenLevellist,BaseSchsystemGrade)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchGradeDto { + + /** 年级ID(=gradeId,scopeType=年级 的 scopeId;班级 Levell 指向它) */ + @JsonProperty("Id") + private Integer id; + + /** 年级名称(gradeNickname,如「初一」) */ + @JsonProperty("Name") + private String name; + + /** 所属学段(学制)ID */ + @JsonProperty("OwnerId") + private Integer ownerId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java new file mode 100644 index 0000000..06185b6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校信息 DTO(/oapi/SchInfo/GetUserSchFirst)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchInfoDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerId") + private String schSerId; + + /** 用户中心学校组ID(与 CampusNotice schId 同域) */ + @JsonProperty("UserSchSerId") + private Integer userSchSerId; + + /** 学校名称 */ + @JsonProperty("SchName") + private String schName; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java new file mode 100644 index 0000000..f96913d --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学生 DTO(/oapi/OAStu/*,SchStudent 视图)。 + * account 仅为上游接口契约字段(兼容反序列化),本系统落库/身份映射不再使用; + * 学生身份以 stuId(UserStuSerID,= 平台用户ID)+ stuName 承载。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchStudentDto { + + /** 学员登录账号(上游契约字段,仅兼容保留,不落库) */ + @JsonProperty("Account") + private String account; + + /** 学生ID */ + @JsonProperty("UserStuSerID") + private Integer stuId; + + /** 学生姓名 */ + @JsonProperty("StuName") + private String stuName; + + /** 所在班级ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 所在组ID */ + @JsonProperty("GroupID") + private Integer groupId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java new file mode 100644 index 0000000..ff7add8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校学科 DTO(/oapi/SubInfo/GetSchSublist)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSubjectDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 学科ID(学科编码;字典无编码时为字典ID字符串) */ + @JsonProperty("SubID") + private String subId; + + /** 学科名称 */ + @JsonProperty("SubName") + private String subName; + + /** 排序号 */ + @JsonProperty("No") + private Integer no; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java new file mode 100644 index 0000000..f0df157 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学段(学制)DTO(/oapi/Schoolen/GetSchLenlist,BaseSchsystem)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSystemDto { + + /** 学制ID(scopeType=学部 的 scopeId) */ + @JsonProperty("Id") + private Integer id; + + /** 学段名称(如「小学部」「初中部」) */ + @JsonProperty("Name") + private String name; + + /** 学制类型 */ + @JsonProperty("Type") + private Integer type; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java new file mode 100644 index 0000000..86ec6f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Date; + +/** + * bschclass 任教关系 DTO(/oapi/TeachInfo/*,sch_class_teach 视图)。 + * 一条记录 = 某学校某班级中某教师任教某学科。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTeachDto { + + /** 任教记录ID(sch_class_teach.id) */ + @JsonProperty("SerID") + private Integer serId; + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 教师ID(= 平台用户ID) */ + @JsonProperty("UserTeaSerID") + private Integer teaId; + + /** 学科ID(学科编码) */ + @JsonProperty("SubID") + private String subId; + + /** 班级业务ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级ID(与 ClassID 同值,上游兼容字段) */ + @JsonProperty("UserClassID") + private Integer userClassId; + + /** 创建时间 */ + @JsonProperty("CreationTime") + private Date creationTime; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java new file mode 100644 index 0000000..29623ad --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java @@ -0,0 +1,41 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * bschclass 组织树节点 DTO(/oapi/SchGroup/GetTeaTree、GetStuTree,TeaTreeVO 视图)。 + * type=1 组节点(学校/学段/年级/班级、教师组),type=2 人员叶子(学生/教师)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTreeNodeDto { + + /** 节点ID(学校/学段/年级/班级为对应业务ID,叶子为学生 stuId 或教师 teaId) */ + private Integer id; + + /** 节点名称 */ + private String name; + + /** 父节点ID(根节点为 0) */ + private Integer pid; + + /** 排序号(组取 sort,叶子固定 0) */ + private Integer no; + + /** 1=组节点,2=人员叶子 */ + private Integer type; + + /** 是否父节点(组 true,叶子 false) */ + @JsonProperty("isParent") + private Boolean isParent; + + /** 头像(当前固定空串) */ + private String photo; + + /** 子节点(叶子为 null,组节点非 null) */ + private List children; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java new file mode 100644 index 0000000..7621af2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java @@ -0,0 +1,81 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 作业详情视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class HomeworkDetailVo { + + @JsonProperty("serid") + private Integer serid; + + @JsonProperty("title") + private String title; + + @JsonProperty("Contents") + private String contents; + + @JsonProperty("DeptID") + private String deptId; + + @JsonProperty("DeptName") + private String deptName; + + @JsonProperty("TeaName") + private String publisherName; + + @JsonProperty("EditDate") + private String editDate; + + @JsonProperty("numcount") + private Integer numCount; + + @JsonProperty("readnumcount") + private Long readNumCount; + + @JsonProperty("ReceiveList") + private String receiveList; + + @JsonProperty("DsfsTime") + private String dsfsTime; + + @JsonProperty("isDsfs") + private Boolean isDsfs; + + @JsonProperty("ChaoSend") + private List chaoSend; + + /** 接收人列表:"all" 或用户ID列表 */ + @JsonProperty("tlist") + private Object tlist; + + /** + * 接收对象可读文本("全部班级" 或班级名顿号串)。 + * 详情页「接收班级」标签直接展示,避免前端拿 ReceiveList 的 id 串渲染。 + */ + @JsonProperty("receiveText") + private String receiveText; + + /** + * 附件项(旧 ChaoSend 数组元素)。 + */ + @Data + public static class FileItem { + @JsonProperty("id") + private Integer id; + @JsonProperty("name") + private String name; + @JsonProperty("size") + private Integer size; + @JsonProperty("fileid") + private Integer fileid; + @JsonProperty("filename") + private String filename; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java new file mode 100644 index 0000000..e1c2fa2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 作业分页结果(扩展 BasePageResult,额外携带 unreadCount)。 + * + * 对齐方案 M7:unreadCount 并入分页外壳扩展子类,不改基类契约。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkPageResult extends BasePageResult { + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkPageResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + super(content, total, pageIndex, pageSize, totalPages, hasNext); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java new file mode 100644 index 0000000..afea9a6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +/** + * 作业列表查询条件。 + * + * - 管理端(adminList):用 status / type / deptId / publisherId / 时间范围过滤; + * - 用户端(userList):额外用 readType 过滤未读/已读(0 全部 / 1 未读 / 2 已读)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkQuery extends BasePageQuery { + + /** 状态过滤:null 不过滤;1 已发布 */ + private Integer status; + + /** 类型过滤:1 普通 / 2 重要 */ + private Integer type; + + /** 列表范围(管理端):"user" 只看自己发布的,null 看全部 */ + private String scope; + + /** 发布部门ID */ + private String deptId; + + /** 发布人ID(用户中心数字ID,管理端按发布人查) */ + private Integer publisherId; + + /** 开始时间(创建时间 >=,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startTime; + + /** 结束时间(创建时间 <= 当天 23:59:59,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endTime; + + /** 阅读状态过滤(仅用户端):0 全部 / 1 未读 / 2 已读 */ + private Integer readType = 0; + + /** 排序:1 时间倒序(默认)/ 2 时间正序 */ + private String orderByTime = "1"; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java new file mode 100644 index 0000000..2cb560f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java @@ -0,0 +1,65 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import com.yunhai.homework.common.validation.ValidationGroups; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 作业发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。 + */ +@Data +public class HomeworkRequest { + + /** 作业标题 */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业标题不得为空") + private String title; + + /** 作业内容 */ + private String content; + + /** 作业类型:1 普通 / 2 重要 */ + @NotNull(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业类型不得为空") + private Integer type = 1; + + /** 发布部门ID(原 DeptID,必填——对应原系统「发布部门不得为空」校验) */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "发布部门不得为空") + private String deptId; + + /** 发布部门名称 */ + private String deptName; + + /** 是否置顶:0 否 / 1 是 */ + private Integer topmost = 0; + + /** 接收人列表(JSON 数组字符串,如 ["10012","10013"]——用户中心数字ID,或 "ALL" 表示全员) */ + private String receiveList; + + /** 附件文件ID列表(逗号分隔串,如 "1,2,3");旧名 chaoSend,已更名 fileIds(M6) */ + private String fileIds; + + /** 定时发送时间(为空表示立即发布) */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timingSendTime; + + /** 短信发送标记:0 不发短信 / 1 发短信(旧名 topmost 实指发短信,已正名 smsFlag,M6) */ + private Integer smsFlag = 0; + + /** 转发源作业ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */ + private Integer sourceId; + + // ==================== 课堂作业 V1/V2 扩展(前端契约字段,兼容层同步写新列) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deadline; + + /** 提交方式:text/image/attach/none 组合 */ + private java.util.List submitType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java new file mode 100644 index 0000000..c492f86 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java @@ -0,0 +1,113 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 作业统计视图(对应原系统 GetStatisData / 按发布人统计)。 + */ +@Data +public class HomeworkStatisticsVo { + + /** 作业总数(全部已发布) */ + private long total; + + /** 已发布数 */ + private long publishedCount; + + /** 阅读记录总数(累计已读人次) */ + private long totalReadRecords; + + /** 当前用户未读数(已到点的已发布 - 当前用户已读) */ + private long unreadForCurrentUser; + + /** 按发布人统计 */ + private List byPublisher; + + /** 按月统计(已发布作业数) */ + private List byMonth; + + // ==================== 课程作业口径(前端 /stats 页消费) ==================== + + /** 我布置的作业数(当前登录人发布的全部作业,含草稿) */ + private long myAssignCount; + + /** 我的作业待批改总量(已交未批的当前版本行合计) */ + private long pendingGrade; + + /** 各作业平均提交率(%,仅统计需收集提交的作业;无样本时为 0) */ + private double avgSubmitRate; + + /** 按作业的提交与批改进度明细(草稿与仅查阅型不计) */ + private List byHomework = new ArrayList<>(); + + /** 已批阅提交的分数分布(四档) */ + private List scoreDist = new ArrayList<>(); + + /** 单作业进度条目(口径对齐前端 HomeworkProgress 契约) */ + @Data + public static class HomeworkStat { + private Integer id; + private String title; + private String subject; + /** 旧契约四态派生值:draft / collecting / closed / done */ + private String hwStatus; + private String hwStatusDesc; + /** 应交人数 */ + private long should; + /** 已读人数 */ + private long read; + /** 已交人数 */ + private long submitted; + /** 已批阅人数 */ + private long graded; + /** 待批改人数 */ + private long pendingGrade; + /** 未交人数 */ + private long unsubmitted; + /** 提交率(%) */ + private double submitRate; + /** 批阅率(%) */ + private double gradeRate; + } + + /** 分数分布档(key 供前端配色映射:excellent/good/pass/fail) */ + @Data + public static class ScoreBucket { + private String label; + private String key; + private long count; + + public ScoreBucket(String label, String key, long count) { + this.label = label; + this.key = key; + this.count = count; + } + } + + @Data + public static class PublisherStat { + private String publisherName; + private long count; + + public PublisherStat(String publisherName, long count) { + this.publisherName = publisherName; + this.count = count; + } + } + + @Data + public static class MonthStat { + private int year; + private int month; + private long count; + + public MonthStat(int year, int month, long count) { + this.year = year; + this.month = month; + this.count = count; + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java new file mode 100644 index 0000000..c906e2e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.List; + +/** + * 用户端作业列表结果:在分页数据之外额外携带「当前用户未读总数」, + * 供前端展示未读角标(对应原系统 OtherData.unReadCount)。 + */ +@Data +public class HomeworkUserListResult { + + private int total; + private List content; + private int pageIndex; + private int pageSize; + private int totalPages; + private boolean hasNext; + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkUserListResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java new file mode 100644 index 0000000..333d49e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java @@ -0,0 +1,92 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 作业视图对象(列表/详情返回)。 + */ +@Data +public class HomeworkVo { + + private Integer id; + private String title; + private String content; + private Integer type; + private String typeDesc; + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + private String publisherName; + private String deptId; + private String deptName; + private Integer status; + private String statusDesc; + private Integer topmost; + + /** 已读人数(基于阅读记录实时统计) */ + private Long readCount = 0L; + + /** 应读人数 */ + private Integer allowCount; + + /** 当前用户是否已读(用户端列表使用;管理端恒为 false 或不关注) */ + private boolean read = false; + + /** 是否撤回:0 否 / 1 是 */ + private Integer isPulled; + + /** 定时发送时间 */ + private LocalDateTime timingSendTime; + + /** 父作业ID(转发时关联原作业) */ + private Integer parentId; + + /** 短信发送标记 */ + private Integer smsFlag; + + /** 接收人列表(JSON) */ + private String receiveList; + + /** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */ + private String UpFile; + + // ==================== 课堂作业 V1/V2 扩展(前端 types/homework.ts 契约) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间(前端契约字段名 deadline) */ + private LocalDateTime deadline; + + /** 提交方式数组:text/image/attach/none */ + private java.util.List submitType; + + /** 作业流程状态——旧契约四态派生值(draft/collecting/closed/done),权威五态见 homework.hw_status */ + private String hwStatus; + + private String hwStatusDesc; + + /** + * 提交/批改进度(管理端列表页「提交进度 / 未读未交 / 批改」列使用)。 + * 草稿或仅查阅型为 null;列表场景由 HomeworkProgressCalculator 批量聚合回填。 + */ + private com.yunhai.homework.model.v2.ProgressVo progress; + + private String createBy; + private LocalDateTime createTime; + private String updateBy; + private LocalDateTime updateTime; + + /** + * 阅读明细视图(阅读列表查询用)。 + */ + @Data + public static class ReadDetail { + /** 读者ID(用户中心数字ID) */ + private Integer id; + private String name; + private boolean read; + private LocalDateTime readTime; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java new file mode 100644 index 0000000..15ece01 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 一键已读结果视图对象(替代原 Map<String,Object> 返回)。 + */ +@Data +public class InitializeRedVo { + + @JsonProperty("count") + private Integer count; + + public InitializeRedVo() {} + + public InitializeRedVo(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java new file mode 100644 index 0000000..8127480 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java @@ -0,0 +1,30 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 文件上传结果视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class UpFileResultVo { + + @JsonProperty("fileid") + private Integer fileid; + + @JsonProperty("name") + private String name; + + @JsonProperty("fileurl") + private String fileurl; + + @JsonProperty("host") + private String host; + + @JsonProperty("type") + private String type; + + @JsonProperty("size") + private Integer size; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java new file mode 100644 index 0000000..76dfa2f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +/** + * 附件视图对象(对应原 .NET FileObj)。 + */ +@Data +public class UpFileVo { + + /** 文件记录ID */ + private Integer id; + + /** 文件真实名称 */ + private String fileTrueName; + + /** 文件存储路径(相对路径) */ + private String fileName; + + /** 文件大小(字节) */ + private Integer fileSize; + + /** 文件后缀名 */ + private String fileType; + + /** 关联主题ID */ + private Integer topicId; + + /** 应用类型 */ + private String topicType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java new file mode 100644 index 0000000..771eb3a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java @@ -0,0 +1,122 @@ +package com.yunhai.homework.model.homework; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户可访问模块(左菜单)视图对象。 + * 将基础平台返回的扁平 {@code List} 转换为前端 layout(参考 UserBase)期望的树结构: + * {@code modId}(路由高亮匹配)/{@code page}(跳转路径)/{@code children}(子菜单)。 + */ +public class UserModuleVo { + + /** 模块主键 id(基础平台 module id) */ + private Integer id; + + /** 菜单高亮匹配用:等价于后端模块 id */ + private Integer modId; + + /** 模块名称 */ + private String name; + + /** 父级模块 id(0 表示根节点) */ + private Integer parentId; + + /** 前端路由路径(由 BoaModule.path 映射而来) */ + private String page; + + /** 层级(0=根分组) */ + private Integer level; + + /** 菜单图标 */ + private String icon; + + /** 排序号 */ + private Integer orderNumber; + + /** 是否可用 */ + private Boolean usable; + + /** 子菜单 */ + private List children = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getModId() { + return modId; + } + + public void setModId(Integer modId) { + this.modId = modId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Integer getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(Integer orderNumber) { + this.orderNumber = orderNumber; + } + + public Boolean getUsable() { + return usable; + } + + public void setUsable(Boolean usable) { + this.usable = usable; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java new file mode 100644 index 0000000..bedf7b5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 附件条目(作业附件与提交附件共用)。fileUrl 必传;fileId 为 file/Upload 产物时回传。 + */ +@Data +public class AttachmentItem { + + /** up_file.id */ + private Integer fileId; + + /** 文件访问 URL */ + private String fileUrl; + + /** 文件类型:image / file */ + private String fileType; + + /** 文件名 */ + private String fileName; + + /** 排序号 */ + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassItem.java b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java new file mode 100644 index 0000000..459ea80 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java @@ -0,0 +1,18 @@ +package com.yunhai.homework.model.v2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 班级条目(作业参与班级展示)。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClassItem { + + private String classId; + + private String className; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java new file mode 100644 index 0000000..c0f400b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 年级→班级树(发布作业选班 + 应交名单基数来源)。 + * 叶子节点 id = 班级 id(class_student 快照聚合)。 + */ +@Data +public class ClassTreeNode { + + /** 年级节点为 gradeName,班级叶子为 classId */ + private String id; + + /** 展示名 */ + private String name; + + /** 是否父节点(年级) */ + private Boolean isParent; + + /** 叶子节点:在班学生数 */ + private Integer studentCount; + + private List children = new ArrayList<>(); +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java new file mode 100644 index 0000000..f8c5dec --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 延长截止请求(新截止时间必须晚于当前时间)。 + */ +@Data +public class ExtendRequest { + + @NotNull(message = "新截止时间不得为空") + private LocalDateTime deadlineAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java new file mode 100644 index 0000000..1ac46f8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java @@ -0,0 +1,36 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 作业详情汇总统计卡片(PRD §18 页 4 stats 接口出参)。 + */ +@Data +public class HomeworkStatsVo { + + private Integer homeworkId; + + private String title; + + private String hwStatus; + + private String hwStatusDesc; + + /** 总进度 */ + private ProgressVo total; + + /** 分班进度 */ + private List byClass; + + @Data + public static class ByClass { + + private String classId; + + private String className; + + private ProgressVo progress; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java new file mode 100644 index 0000000..9464910 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 创建/编辑作业请求(PRD §18 页 1 表单)。 + * 创建一律落草稿(DRAFT_UNPUBLISHED),发布/定时走独立接口。 + */ +@Data +public class HomeworkV2Request { + + /** 作业标题(必填 ≤50 字) */ + @NotBlank(message = "作业标题不得为空") + @Size(max = 50, message = "作业标题不超过50字") + private String title; + + /** 学科(字典值) */ + private String subject; + + /** 作业说明(富文本) */ + private String description; + + /** 参与班级 id 列表 */ + private List classIds; + + /** 附件列表 */ + private List attachments; + + /** 截止时间(精确到分钟;空 = 仅查阅型) */ + private LocalDateTime deadlineAt; + + /** 提交方式:image / attach / text / none 的组合(PRD 图片/文件/两者) */ + private List submitType; + + /** 截止后是否允许逾期提交:0 否 / 1 是 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天提醒(0 关 / 1 开) */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时提醒(0 关 / 1 开) */ + private Integer remind1h; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java new file mode 100644 index 0000000..8bf1b02 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java @@ -0,0 +1,73 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 作业视图对象 V2(草稿箱/已发布列表/详情共用)。 + * 列表场景 attachments/classes 可按需填充;progress 仅非草稿且非仅查阅型作业返回。 + */ +@Data +public class HomeworkV2Vo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 作业说明(富文本;列表场景可置空省流) */ + private String description; + + /** 作业状态(PRD 五态) */ + private String hwStatus; + + private String hwStatusDesc; + + /** 定时发布时间(复用 timing_send_time) */ + private LocalDateTime scheduledAt; + + /** 发布时间 */ + private LocalDateTime publishedAt; + + /** 截止时间 */ + private LocalDateTime deadlineAt; + + /** 归档时间 */ + private LocalDateTime archivedAt; + + /** 提交方式数组 */ + private List submitType; + + /** 截止后允许逾期提交:0/1 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天 */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时 */ + private Integer remind1h; + + /** 参与班级 */ + private List classes; + + /** 附件列表(详情场景填充) */ + private List attachments; + + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + + /** 发布人姓名 */ + private String publisherName; + + /** 创建/更新时间(审计) */ + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + /** 汇总进度(草稿或仅查阅型为 null) */ + private ProgressVo progress; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java new file mode 100644 index 0000000..0303eb8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知分页结果(含当前用户未读数)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationPageResult extends BasePageResult { + + /** 当前用户未读通知总数 */ + private long unreadCount; + + public NotificationPageResult(BasePageResult page, long unreadCount) { + super(page.getContent(), page.getTotal(), page.getPageIndex(), page.getPageSize(), + page.getTotalPages(), page.isHasNext()); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java new file mode 100644 index 0000000..f8af4a7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知列表查询。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationQuery extends BasePageQuery { + + /** 类型过滤(NotificationTypeEnum code) */ + private String type; + + /** 已读过滤:0 未读 / 1 已读(空 = 全部) */ + private Integer isRead; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java new file mode 100644 index 0000000..2a9b61a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知列表项。 + */ +@Data +public class NotificationVo { + + private Integer id; + + /** 通知类型 */ + private String type; + + private String typeDesc; + + private String title; + + private String content; + + private Integer homeworkId; + + private Integer relatedId; + + /** 是否已读:0/1 */ + private Integer isRead; + + private LocalDateTime readAt; + + private LocalDateTime createTime; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java new file mode 100644 index 0000000..201a460 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 提交/批阅/阅读进度(PRD §18 页 3 汇总口径)。 + * should(应交)为分母;打回未重交计入已交。 + */ +@Data +public class ProgressVo { + + /** 应交人数(参与班级在班学生去重) */ + private int should; + + /** 已读人数 */ + private int read; + + /** 已交人数(含逾期/打回/已批阅) */ + private int submitted; + + /** 已批阅人数 */ + private int graded; + + /** 待批阅人数(submitted - graded) */ + private int pendingGrade; + + /** 未交人数 */ + private int unsubmitted; + + /** 未读人数 */ + private int unread; + + /** 未读且未交人数(高亮口径:should - 已读∪已交) */ + private int unreadUndone; + + /** 阅读率(百分数,一位小数) */ + private double readRate; + + /** 提交率(百分数,一位小数) */ + private double submitRate; + + /** 批阅率(已批阅 / 已交,百分数一位小数;submitted=0 时为 0) */ + private double gradeRate; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java new file mode 100644 index 0000000..a97e0c1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 教师手动提醒请求:type = NOT_READ / NOT_SUBMITTED,studentIds 为空时提醒全部未读/未交学生。 + */ +@Data +public class RemindRequest { + + /** 提醒类型:NOT_READ / NOT_SUBMITTED */ + @NotBlank(message = "提醒类型不得为空") + private String type; + + /** 定向学生ID(用户中心数字ID;空 = 全部未读/未交) */ + private List studentIds; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java new file mode 100644 index 0000000..45d3f3e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 打回请求(PRD 页 6)。comment 为订正原因,必填。 + */ +@Data +public class ReturnRequest { + + @NotBlank(message = "打回须填写订正原因") + private String comment; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java new file mode 100644 index 0000000..166b2e1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 批阅保存请求(PRD 页 6)。score 与 comment 至少一项有值。 + */ +@Data +public class ReviewRequest { + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 圈划标注图片(P2,先收参) */ + private List markedImages; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java new file mode 100644 index 0000000..894b433 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 定时发布 / 取消定时请求。 + * cancel=true 时忽略 scheduledAt,作业回退草稿-未发布并清空定时时间。 + */ +@Data +public class ScheduleRequest { + + /** 定时发布时间(cancel=false 时必填且须晚于当前时间) */ + private LocalDateTime scheduledAt; + + /** 是否取消定时 */ + private Boolean cancel = false; + + @NotNull + public Boolean getCancel() { + return cancel; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java new file mode 100644 index 0000000..6372883 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 学生端作业详情(PRD §18 学生页 2)。进入详情后由前端停留 ≥3 秒再调 read 上报。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentHomeworkDetailVo extends StudentHomeworkVo { + + /** 作业说明(富文本) */ + private String description; + + /** 作业附件 */ + private List attachments; + + /** 参与班级(学生所属班级视角) */ + private List classes; + + /** 我当前版本的提交附件(未提交为 null) */ + private List myAttachments; + + /** 我的文本内容(当前版本) */ + private String myText; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java new file mode 100644 index 0000000..9d30037 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学生端作业列表项(PRD §18 学生页 1)。 + */ +@Data +public class StudentHomeworkVo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 任课教师(发布人姓名) */ + private String teacherName; + + /** 截止时间(前端做倒计时) */ + private LocalDateTime deadlineAt; + + /** 提交方式 */ + private java.util.List submitType; + + /** 作业状态 */ + private String hwStatus; + + private String hwStatusDesc; + + /** 截止后是否允许逾期补交:0 否 / 1 是(学生端据此决定是否渲染「补交」入口) */ + private Integer allowLate; + + /** 我的提交概要(未提交为 null) */ + private MySubmission my; + + @Data + public static class MySubmission { + + /** 当前版本提交行ID */ + private Integer submissionId; + + /** 提交状态(PRD 五态) */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 版本号 */ + private Integer version; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分(已批阅才有) */ + private java.math.BigDecimal score; + + /** 评语 */ + private String comment; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java new file mode 100644 index 0000000..49e3144 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学生矩阵查询(PRD §18 页 5)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentMatrixQuery extends BasePageQuery { + + /** 班级筛选(空 = 全部参与班级) */ + private String classId; + + /** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */ + private String submitStatus; + + /** 批阅状态筛选:PENDING/REVIEWED/RETURNED */ + private String reviewStatus; + + /** 只看待批阅(reviewStatus=PENDING 且已提交)快捷过滤 */ + private Boolean pendingGrade; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java new file mode 100644 index 0000000..6257285 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java @@ -0,0 +1,57 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学生矩阵行(PRD §18 页 5)。未交学生也生成占位行(submissionId=0)。 + */ +@Data +public class StudentMatrixVo { + + /** 当前版本提交行ID(未交占位为 0) */ + private Integer submissionId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + /** 姓名 */ + private String studentName; + + private String classId; + + private String className; + + /** 阅读状态:0 未读 / 1 已读 */ + private Integer isRead; + + /** 首次阅读时间 */ + private LocalDateTime firstReadAt; + + /** 最近阅读时间 */ + private LocalDateTime lastReadAt; + + /** 提交状态(PRD 五态;未交为 NOT_SUBMITTED) */ + private String submitStatus; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期:0/1 */ + private Integer isLate; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分 */ + private BigDecimal score; + + /** 最新提交附件 URL 列表(缩略图) */ + private List thumbnails; + + /** 提交内容类型摘要:image=拍照/图片、attach=上传附件、text=文字作答(未交为空列表) */ + private List contentTypes; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java new file mode 100644 index 0000000..f4d9cd1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 学生查看批阅结果(PRD §18 学生页 2 result 接口)。 + */ +@Data +public class StudentResultVo { + + private Integer homeworkId; + + private String homeworkTitle; + + /** 当前版本批阅结果(未批阅为 null) */ + private SubmissionVersionVo latest; + + /** 全部版本(含各版评语,新→旧) */ + private List versions; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java new file mode 100644 index 0000000..1fa7545 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 学生提交作业请求(PRD §18 学生页 3)。附件只接受 file/Upload 产物(fileId 或平台 URL),拒绝 dataURL。 + */ +@Data +public class StudentSubmitRequest { + + /** 文本内容(submitType 含 text 时有效) */ + @Size(max = 20000, message = "文本内容过长") + private String text; + + /** 附件列表(图片/文件) */ + private List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java new file mode 100644 index 0000000..6c576d9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 学科 DTO(布置过的学科 / 我的任教学科共用返回结构)。 + */ +@Data +public class SubjectDto { + + /** 学科编码(homework.subject 字典值 / bschclass SubID) */ + private String subId; + + /** 学科名称(本校学科字典解析;字典缺失时回退为编码) */ + private String subName; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java new file mode 100644 index 0000000..cac00dc --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 撤回重交动作(PUT /student/homeworks/{id}/submissions/{submissionId})。 + * withdraw = 撤回(截止前,产生 NOT_SUBMITTED 新版本行);resubmit = 打回后重交(走提交语义)。 + */ +@Data +public class SubmissionActionRequest { + + @NotBlank(message = "action 不得为空") + private String action; + + /** resubmit 时的提交内容(等同 StudentSubmitRequest) */ + private String text; + + /** resubmit 时的附件 */ + private java.util.List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java new file mode 100644 index 0000000..bff2972 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java @@ -0,0 +1,63 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 提交版本条目(历史版本/批阅弹窗/学生结果页共用)。 + */ +@Data +public class SubmissionVersionVo { + + private Integer submissionId; + + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + private String studentName; + + /** 版本号 */ + private Integer version; + + /** 是否当前版本:0/1 */ + private Integer isLatest; + + /** 提交状态 */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 文本内容 */ + private String text; + + /** 附件 */ + private List attachments; + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 批阅状态 */ + private String reviewStatus; + + private String reviewStatusDesc; + + /** 批阅人(展示姓名快照,缺失时回退ID) */ + private String reviewedBy; + + /** 批阅时间 */ + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java new file mode 100644 index 0000000..6d6991e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教师端作业列表查询(草稿箱/已发布列表共用)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TeacherHomeworkQuery extends BasePageQuery { + + /** 学科筛选 */ + private String subject; + + /** 作业状态筛选(已发布列表:PUBLISHED/CLOSED/ARCHIVED) */ + private String hwStatus; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java new file mode 100644 index 0000000..d9fd64e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 任教班级 VO(按学科筛选任教班级返回结构)。 + */ +@Data +public class TeachingClassVo { + + /** 班级业务ID(bschclass sch_class.class_id) */ + private String classId; + + /** 班级名称(学校班级列表映射;缺失时回退为班级ID) */ + private String className; +} diff --git a/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java new file mode 100644 index 0000000..0581e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ClassStudent; +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; + +/** + * 班级学生名册仓储。 + */ +public interface ClassStudentRepository extends JpaRepository { + + /** 按学生ID(用户中心数字ID)反查学生身份(学生端接口入口) */ + List findByStudentIdAndStatus(Integer studentId, Integer status); + + /** 班级在班学生 */ + List findByClassIdAndStatus(String classId, Integer status); + + /** 指定班级集合的全部在班学生(应交名单) */ + List findByClassIdInAndStatus(List classIds, Integer status); + + /** 指定学校的全部名册(同步任务全量刷新用) */ + List findBySchoolId(Integer schoolId); + + /** 年级→班级树:按学校去重班级(class_student 快照聚合),列序:classId, className, gradeName */ + @Query("select distinct c.classId, c.className, c.gradeName from ClassStudent c " + + "where c.schoolId = :schoolId and c.status = 1") + List findDistinctClasses(@Param("schoolId") Integer schoolId); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java new file mode 100644 index 0000000..4dc9ee4 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java @@ -0,0 +1,27 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroup; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ContactsGroupRepository extends JpaRepository { + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + + /** + * 按创建人ID(用户中心数字ID)查询联系组列表。 + */ + List findByCreatorId(Integer creatorId); + + /** + * 按创建人ID查询联系组列表(分页)。 + */ + Page findByCreatorId(Integer creatorId, Pageable pageable); + + /** + * 查询全部联系组(按创建时间降序)。 + */ + List findAllByOrderByCreateTimeDesc(); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java new file mode 100644 index 0000000..df5ab28 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java @@ -0,0 +1,42 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroupUser; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ContactsGroupUserRepository extends JpaRepository { + Page findByGroupId(Integer groupId, Pageable pageable); + List findByGroupId(Integer groupId); + + /** + * 按组ID批量删除成员(JPQL bulk delete,SQL 立即执行)。 + * 注意:不能用 derived delete——其 DELETE 延迟到 flush 才执行, + * 同事务内先 insert 会触发 uk_group_user 唯一约束冲突。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId = :groupId") + void deleteByGroupIdBulk(@Param("groupId") Integer groupId); + + /** + * 按多个组ID批量删除成员。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId in :groupIds") + void deleteByGroupIdInBulk(@Param("groupIds") List groupIds); + + /** + * 按多个组ID查询所有成员(用于统计每组人数、组树构建)。 + */ + List findByGroupIdIn(List groupIds); + + /** + * 按组ID和用户ID查询(判断是否已存在)。 + */ + boolean existsByGroupIdAndUserId(Integer groupId, Integer userId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java new file mode 100644 index 0000000..1e6af55 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 作业附件仓储。 + */ +public interface HomeworkAttachmentRepository extends JpaRepository { + + List findByHomeworkIdOrderBySortAscIdAsc(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java new file mode 100644 index 0000000..8f651f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkClass; +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; + +/** + * 作业-班级关联仓储。 + */ +public interface HomeworkClassRepository extends JpaRepository { + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 指定班级的全部作业ID(学生端列表倒查用) */ + @Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds") + List findHomeworkIdsByClassIds(@Param("classIds") List classIds); + + /** 批量查 N 个作业的班级关联(列表展示避免 N+1) */ + @Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java new file mode 100644 index 0000000..6c16a81 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java @@ -0,0 +1,23 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReadRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 学生阅读记录仓储。 + */ +public interface HomeworkReadRecordRepository extends JpaRepository { + + Optional findByHomeworkIdAndStudentId(Integer homeworkId, Integer studentId); + + List findByHomeworkId(Integer homeworkId); + + /** 批量查 N 个作业的已读记录(列表统计避免 N+1) */ + List findByHomeworkIdIn(Collection homeworkIds); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java new file mode 100644 index 0000000..20dc83c --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkRead; +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.Optional; + +/** + * 作业阅读记录仓储(懒标记模型)。 + */ +public interface HomeworkReadRepository extends JpaRepository { + + /** + * 某用户是否已读某作业。 + */ + boolean existsByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 某作业的总已读人数。 + */ + long countByHomeworkId(Integer homeworkId); + + /** + * 删除某作业的全部阅读记录(作业删除时级联清理)。 + */ + void deleteByHomeworkId(Integer homeworkId); + + /** + * 某用户已读的作业总数(用于「我的未读」= 已发布总数 - 已读总数)。 + */ + long countByReaderId(Integer readerId); + + /** + * 查询某用户已读的作业ID列表(用于列表页批量判定未读)。 + */ + @Query("select r.homeworkId from HomeworkRead r where r.readerId = :readerId") + List findHomeworkIdsByReaderId(@Param("readerId") Integer readerId); + + /** + * 查询某用户对某作业的阅读记录(标记已读时幂等判断)。 + */ + Optional findByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 查询某作业的全部阅读记录(按阅读时间倒序),用于阅读明细列表。 + */ + @Query("select r from HomeworkRead r where r.homeworkId = :homeworkId order by r.readTime desc") + List findByHomeworkIdOrderByReadTimeDesc(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java new file mode 100644 index 0000000..402d455 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReceiver; +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; + +/** + * 作业接收人关联表仓储。 + */ +public interface HomeworkReceiverRepository extends JpaRepository { + + /** 按作业ID查询全部接收人 */ + List findByHomeworkId(Integer homeworkId); + + /** 按作业ID删除全部接收人 */ + void deleteByHomeworkId(Integer homeworkId); + + /** 按作业ID和接收类型查询接收人 */ + List findByHomeworkIdAndReceiveType(Integer homeworkId, Integer receiveType); + + /** + * 批量查 N 个作业的接收人,用于列表展示(避免 N+1)。 + * @param homeworkIds 作业ID列表 + * @return 接收人关联记录列表 + */ + @Query("select r from HomeworkReceiver r where r.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java new file mode 100644 index 0000000..bf862b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java @@ -0,0 +1,139 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Homework; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 课程作业仓储。 + */ +public interface HomeworkRepository extends JpaRepository, + JpaSpecificationExecutor { + + /** + * 作业流程状态条件更新(CAS):仅当当前状态为 from 时更新为 to。 + * 状态机单点收敛用,天然幂等、并发安全(定时任务与接口共用)。 + * clearAutomatically:批量更新绕过一级缓存,必须清空以避免后续 findById 读到旧实体 + * 再被 save() 写回旧状态(同事务内必现,集成测试发现)。 + * + * @return 影响行数(0 = 状态已变化,调用方应放弃本次迁移) + */ + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update Homework h set h.hwStatus = :to where h.id = :id and h.hwStatus = :from") + int casHwStatus(@Param("id") Integer id, + @Param("from") String from, + @Param("to") String to); + + /** + * 定时发布任务扫描:定时待发布且已到点。 + */ + List findByHwStatusAndTimingSendTimeLessThanEqual(String hwStatus, LocalDateTime now); + + /** + * 到点关闭任务扫描:进行中且已过截止时间。 + */ + List findByHwStatusAndDeadlineAtBefore(String hwStatus, LocalDateTime now); + + /** + * 截止提醒任务扫描:进行中且截止时间落在指定窗口内(remind_1d/remind_1h 开关在服务侧逐单过滤)。 + */ + List findByHwStatusAndDeadlineAtBetween(String hwStatus, LocalDateTime start, LocalDateTime end); + + /** + * 按作业流程状态查询(兼容层/统计用)。 + */ + List findByHwStatus(String hwStatus); + + /** + * 当前登录人发布的全部作业(课程作业口径统计:我布置的作业 / 提交与批改进度明细)。 + */ + List findByPublisherId(Integer publisherId); + + /** + * 当前登录人布置过(已发布及之后状态)作业的学科去重清单(学科筛选用,字典序;草稿不计)。 + */ + @Query("select distinct h.subject from Homework h " + + "where h.publisherId = :publisherId and h.subject is not null and h.subject <> '' " + + "and h.hwStatus in :statuses order by h.subject") + List findAssignedSubjects(@Param("publisherId") Integer publisherId, + @Param("statuses") Collection statuses); + + /** + * 按状态计数(用于统计已发布总数)。 + */ + long countByStatus(Integer status); + + /** + * 已发布且已到点(定时发送未到点不计入)的作业数,用于用户端未读数统计。 + */ + @Query("select count(n) from Homework n where n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDue(@Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定状态、指定时间范围内的作业创建时间(用于按月统计,避免加载大字段)。 + */ + @Query("select n.createTime from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定状态、指定时间范围内的发布人姓名(用于按发布人统计)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 按学校和状态计数(多租户隔离统计)。 + */ + long countBySchIdAndStatus(Integer schId, Integer status); + + /** + * 按学校计数(多租户隔离统计:全部状态)。 + */ + long countBySchId(Integer schId); + + /** + * 已发布且已到点(定时发送未到点不计入)且属于指定学校的作业数(用户端未读数统计,多租户隔离)。 + */ + @Query("select count(n) from Homework n where n.schId = :schId and n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDueBySchId(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定学校、指定状态、指定时间范围内的作业创建时间(按月统计,多租户隔离)。 + */ + @Query("select n.createTime from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesBySchIdStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定学校、指定状态、指定时间范围内的发布人姓名(按发布人统计,多租户隔离)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesBySchStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java new file mode 100644 index 0000000..547c5ea --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkSubmission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 作业提交仓储(多版本)。统计一律基于 is_latest=1 的当前版本行。 + */ +public interface HomeworkSubmissionRepository extends JpaRepository { + + /** 学生在指定作业的当前版本行 */ + Optional findByHomeworkIdAndStudentIdAndIsLatest(Integer homeworkId, + Integer studentId, + Integer isLatest); + + /** 学生在指定作业的全部版本(新→旧) */ + List findByHomeworkIdAndStudentIdOrderByVersionDesc(Integer homeworkId, Integer studentId); + + /** 指定作业的当前版本行(统计/矩阵用) */ + List findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest); + + /** 批量查 N 个作业的当前版本行(列表统计避免 N+1) */ + List findByHomeworkIdInAndIsLatest(Collection homeworkIds, Integer isLatest); + + /** 学生矩阵分页 */ + Page findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest, Pageable pageable); + + /** 学生当前版本最大版本号 */ + @Query("select max(s.version) from HomeworkSubmission s " + + "where s.homeworkId = :homeworkId and s.studentId = :studentId") + Integer findMaxVersion(@Param("homeworkId") Integer homeworkId, @Param("studentId") Integer studentId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 附件等子表清理后按作业删除历史版本行 */ + @Modifying + @Query("delete from HomeworkSubmission s where s.homeworkId = :homeworkId") + int deleteAllByHomeworkId(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java new file mode 100644 index 0000000..ecc75cb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HomeworkTemplateRepository extends JpaRepository { + Page findByTypeId(Integer typeId, Pageable pageable); + + /** + * 查询全部模板(按引用次数降序),分页。 + */ + Page findAllByOrderByRefCountDesc(Pageable pageable); + + /** + * 按类型ID查询全部模板(不分页,用于检查类型是否被引用)。 + */ + long countByTypeId(Integer typeId); +} diff --git a/src/main/java/com/yunhai/homework/repository/NotificationRepository.java b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java new file mode 100644 index 0000000..1066c10 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + * 站内通知仓储。 + */ +public interface NotificationRepository extends JpaRepository { + + Page findByReceiverId(Integer receiverId, Pageable pageable); + + Page findByReceiverIdAndType(Integer receiverId, String type, Pageable pageable); + + Page findByReceiverIdAndIsRead(Integer receiverId, Integer isRead, Pageable pageable); + + Page findByReceiverIdAndTypeAndIsRead(Integer receiverId, String type, + Integer isRead, Pageable pageable); + + long countByReceiverIdAndIsRead(Integer receiverId, Integer isRead); + + Optional findByIdAndReceiverId(Integer id, Integer receiverId); + + /** 截止提醒去重:同作业同类型同档位已发过即跳过(related_id 存档位:1=1天档 2=1小时档) */ + boolean existsByHomeworkIdAndTypeAndRelatedId(Integer homeworkId, String type, Integer relatedId); + + /** 截止提醒去重(不分档位) */ + boolean existsByHomeworkIdAndType(Integer homeworkId, String type); + + /** 全部已读(clearAutomatically 防同事务内缓存脏读) */ + @Modifying(clearAutomatically = true) + @Query("update Notification n set n.isRead = 1, n.readAt = :now " + + "where n.receiverId = :receiverId and n.isRead = 0") + int markAllRead(@Param("receiverId") Integer receiverId, @Param("now") java.time.LocalDateTime now); + + /** 按作业删除通知(作业删除级联) */ + void deleteByHomeworkId(Integer homeworkId); + + /** 指定作业的全部通知(清理/统计用) */ + List findByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java new file mode 100644 index 0000000..8e60f47 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SchClassTeach; +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; + +/** + * 班级任教关系快照仓储。 + */ +public interface SchClassTeachRepository extends JpaRepository { + + /** 指定学校指定教师的全部有效任教记录 */ + List findBySchoolIdAndTeaIdAndStatus(Integer schoolId, Integer teaId, Integer status); + + /** 指定学校指定教师去重学科编码(字典序由调用方保证) */ + @Query("select distinct t.subId from SchClassTeach t " + + "where t.schoolId = :schoolId and t.teaId = :teaId and t.status = 1") + List findDistinctSubIds(@Param("schoolId") Integer schoolId, @Param("teaId") Integer teaId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java new file mode 100644 index 0000000..18595bb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java @@ -0,0 +1,13 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsModule; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsModuleRepository extends JpaRepository { + List findAllByOrderByIdAsc(); + + /** 按模块标识查询(如 module_code="homework")。 */ + SmsModule findByModuleCode(String moduleCode); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java new file mode 100644 index 0000000..0d277a0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsSchoolConfig; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsSchoolConfigRepository extends JpaRepository { + Page findBySchoolNameContaining(String schoolName, Pageable pageable); + Page findAll(Pageable pageable); + List findBySchoolId(Integer schoolId); + List findBySchoolIdInAndModuleIdIn(List schoolIds, List moduleIds); + + /** + * 按学校ID和模块ID查询单条配置(判断是否已存在)。 + */ + SmsSchoolConfig findBySchoolIdAndModuleId(Integer schoolId, Integer moduleId); + + /** + * 按学校ID查询全部模块配置(用于"设置某校全部模块")。 + */ + List findBySchoolIdIn(List schoolIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java new file mode 100644 index 0000000..8848b14 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SubmissionAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; + +/** + * 提交附件仓储。 + */ +public interface SubmissionAttachmentRepository extends JpaRepository { + + List findBySubmissionIdOrderBySortAscIdAsc(Integer submissionId); + + /** 批量查 N 个提交行的附件(矩阵/版本列表避免 N+1) */ + List findBySubmissionIdIn(Collection submissionIds); + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SysDictRepository.java b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java new file mode 100644 index 0000000..c083840 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SysDict; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SysDictRepository extends JpaRepository { + List findByDictTypeOrderBySortOrderAsc(String dictType); + List findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag); + + /** 按类型+key 取单条字典(发文部门名称解析用) */ + Optional findByDictTypeAndDictKey(String dictType, String dictKey); +} diff --git a/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java new file mode 100644 index 0000000..a3a0f12 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.TemplateType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TemplateTypeRepository extends JpaRepository { + List findAllByOrderBySortOrderAsc(); + + /** + * 按类型名称查询(重名校验)。 + */ + boolean existsByTypeName(String typeName); + + /** + * 按类型名称查询(重名校验,排除自身)。 + */ + boolean existsByTypeNameAndIdNot(String typeName, Integer id); +} diff --git a/src/main/java/com/yunhai/homework/repository/UpFileRepository.java b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java new file mode 100644 index 0000000..421f142 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.UpFile; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +/** + * 附件文件仓储。 + */ +public interface UpFileRepository extends JpaRepository { + + /** + * 按主题类型和主题ID查询附件列表。 + */ + List findByTopicTypeAndTopicId(String topicType, Integer topicId); + + /** + * 按主题类型和多个主题ID批量查询附件(消除 N+1)。 + */ + List findByTopicTypeAndTopicIdIn(String topicType, List topicIds); + + /** + * 按主题类型和主题ID删除附件(作业删除时级联清理)。 + */ + void deleteByTopicTypeAndTopicId(String topicType, Integer topicId); +} diff --git a/src/main/java/com/yunhai/homework/service/IClassService.java b/src/main/java/com/yunhai/homework/service/IClassService.java new file mode 100644 index 0000000..2346cd9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IClassService.java @@ -0,0 +1,116 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; + +import java.util.List; + +/** + * 班级管理(bschclass)开放接口客户端门面。 + * 基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。 + * 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。 + */ +public interface IClassService { + + /** + * 获取指定学校全部教师(含所属组 GroupIds)。 + * 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}} + * + * @param schId 学校ID(即用户当前身份的 schId) + */ + List getSchTeaList(int schId); + + /** + * 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。 + * 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}} + */ + SchInfoDto getSchFirst(int schId); + + /** + * 学校学段(学制)列表:小学部 / 初中部…。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=} + */ + List getSchLenList(String schSerId); + + /** + * 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=} + */ + List getSchGradeList(String schSerId); + + /** + * 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。 + * 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=} + */ + List getSchClassList(String schSerId); + + /** + * 按校全部学生(圈人:全校范围展开)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=} + */ + List getSchStuList(String schSerId); + + /** + * 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=} + */ + List getGradeStuList(String schSerId, Integer gradeId); + + /** + * 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=} + */ + List getSubStuList(String schSerId, Integer schSystemId); + + /** + * 按班级批量查学生(圈人:班级范围一次多班)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3} + */ + List getClaArrStuList(List classIds); + + /** + * 按学生ID批量查(圈人:指定学员范围)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3} + */ + List getStuIdsList(List stuIds); + + /** + * 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。 + * 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。 + * 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=} + */ + List getTeaTree(Integer schId, boolean toUser); + + List getStuTree(Integer schId,boolean toUser); + + /** + * 学校学科列表(含学科编码、名称、排序号)。 + * 对应:GET {@code {bschclass}/oapi/SubInfo/GetSchSublist?SchID=} + */ + List getSchSubList(String schSerId); + + /** + * 指定教师的全部任教(校验教师归属该校;SchID 过滤学校、UserTeaSerID 过滤教师)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=&UserTeaSerID=} + */ + List getSchTeachList(String schSerId, Integer teaId); + + /** + * 指定班级的全部任教(班级维度:该班所有教师与学科)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchClaTeachlist?ClassID=} + */ + List getSchClaTeachList(Integer classId); + + /** + * 学校全部任教(全校所有教师的班级任教关系)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=} + */ + List getSchAllTeachList(String schSerId); +} diff --git a/src/main/java/com/yunhai/homework/service/IContactsService.java b/src/main/java/com/yunhai/homework/service/IContactsService.java new file mode 100644 index 0000000..0e1fdce --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IContactsService.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 联系组业务接口。 + * 对应前端 Contacts 组接口:AddGroup/GetGroup/EditGroup/DelGroup/SetContactsTea/GetContactsTea/GetGroupTree/GetDeptRoleGroupTree/Import/Export。 + */ +public interface IContactsService { + + /** + * 添加联系组。 + * + * @param name 组名 + * @param type 类型(1 自定义组, 2 明信片) + */ + void addGroup(String name, Integer type); + + /** + * 获取联系组列表(含每组人数,分页)。 + * + * @param page 页码(从1起) + * @param count 每页条数 + * @param type 类型(1 自定义组, 2 明信片) + * @return Map: { list, total } + */ + Map getGroup(Integer page, Integer count, Integer type); + + /** + * 编辑联系组。 + * + * @param id 组ID + * @param name 组名 + * @param type 类型 + */ + void editGroup(Integer id, String name, Integer type); + + /** + * 删除联系组(支持批量,同时删除组内成员)。 + * + * @param ids 逗号分隔的组ID + */ + void delGroup(String ids); + + /** + * 设置联系组教师(全量替换:先删后增)。 + * + * @param gid 组ID + * @param ids 逗号分隔的用户ID + */ + void setContactsTea(Integer gid, String ids); + + /** + * 获取联系组教师列表(分页)。 + * + * @param gid 组ID + * @param page 页码 + * @param count 每页条数 + * @return Map: { list, total } + */ + Map getContactsTea(String gid, Integer page, Integer count); + + /** + * 获取联系组树(组节点+用户节点,递归构建)。 + * + * @param type 类型(1 自定义组, 2 明信片) + * @return 树形结构 List<Map> + */ + List> getGroupTree(Integer type); + + /** + * 获取部门角色组树。 + * C# 从外部API获取部门树+用户角色映射。Java版预留。 + * + * @return 树形结构 List<Map> + */ + List> getDeptRoleGroupTree(); +} diff --git a/src/main/java/com/yunhai/homework/service/IDictService.java b/src/main/java/com/yunhai/homework/service/IDictService.java new file mode 100644 index 0000000..31ef515 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IDictService.java @@ -0,0 +1,75 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 系统字典业务接口。 + * 对应前端 Base 组接口:GetSysDic/GetDictMangeList/AddDeptDic/UpdateDeptDic/DelDept/OrdbyDicIds/GetDeptSync/GetSchModuleIsSms。 + */ +public interface IDictService { + + /** + * 获取系统字典(下拉框用)。 + * C# GetSysDic: Type=1 部门类别, 其余类型略。 + * + * @param type 字典类型编号(1=部门类别) + * @return List<Map> 每项 {k, v} + */ + List> getSysDic(Integer type); + + /** + * 获取系统字典管理列表(含 SerID/CodeID/Name/Des/Enable)。 + * + * @param type 字典类型编号(0=部门类别) + * @return List<Map> + */ + List> getDictMangeList(String type); + + /** + * 添加发文单位。 + * + * @param name 单位名称 + */ + void addDeptDic(String name); + + /** + * 更新发文单位。 + * + * @param serID 记录ID + * @param name 名称 + * @param enable 启用状态 + */ + void updateDeptDic(Integer serID, String name, Integer enable); + + /** + * 删除发文单位(校验是否被作业引用)。 + * + * @param serID 记录ID + */ + void delDept(Integer serID); + + /** + * 发文单位排序。 + * + * @param ids 逗号分隔的 SerID 列表(按前端顺序) + */ + void ordbyDicIds(String ids); + + /** + * 本校是否开通短信模块(前端 dict/Sync,用于"本校是否开通短信"判断)。 + * + * @param schId 当前学校ID(取当前登录用户角色所属学校) + * @return 开通返回 true,未开通/未配置返回 false + */ + Boolean getSchModuleIsSms(Integer schId); + + /** + * 按发文部门字典 key(CodeID)解析部门名称;未命中返回 null。 + * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 + * + * @param deptId 作业 deptId(存的是 sys_dict.dict_key) + * @return 部门名称;无匹配时返回 null + */ + String resolveDeptName(String deptId); +} diff --git a/src/main/java/com/yunhai/homework/service/IFileService.java b/src/main/java/com/yunhai/homework/service/IFileService.java new file mode 100644 index 0000000..1b01bbf --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IFileService.java @@ -0,0 +1,94 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.UpFile; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.model.homework.UpFileResultVo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件上传/下载/删除业务接口。 + */ +public interface IFileService { + /** + * 上传单个文件。 + * + * @param file 上传的文件 + * @param appType 应用类型(如 "homework") + * @param topicId 关联主题ID(可为空,作业保存后再绑定) + * @return 文件视图对象 + */ + UpFileVo upload(MultipartFile file, String appType, Integer topicId); + + /** + * 将已上传但未绑定主题的附件绑定到指定作业。 + * + * @param fileIds 文件记录ID列表 + * @param topicId 作业ID + */ + void bindToTopic(List fileIds, Integer topicId); + + /** + * 查询某主题下的附件列表。 + * + * @param appType 应用类型 + * @param topicId 主题ID + * @return 附件列表 + */ + List listByTopic(String appType, Integer topicId); + + /** + * 下载文件。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void download(Integer fileId, HttpServletResponse response); + + /** + * 在线预览文件(Content-Disposition: inline,按扩展名输出真实 Content-Type, + * 供前端 open-file-viewer 取 Blob / video 播放)。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void preview(Integer fileId, HttpServletResponse response); + + /** + * 删除文件(同时删除物理文件和数据库记录)。 + * + * @param fileId 文件记录ID + */ + void delete(Integer fileId); + + /** + * 打包下载多个附件为 ZIP。 + * + * @param files 附件记录列表(调用方按 fileIds 查好) + * @param zipName 压缩包显示名(自动追加 .zip) + * @param response HTTP 响应 + */ + void downloadZip(List files, String zipName, HttpServletResponse response); + + /** + * 按主题类型和主题ID打包下载附件为 ZIP(Controller 便捷方法,内部自行查附件列表)。 + * + * @param topicType 主题类型(如 "homework") + * @param topicId 主题ID + * @param zipName 压缩包显示名 + * @param response HTTP 响应 + */ + void downloadZipByTopic(String topicType, Integer topicId, String zipName, HttpServletResponse response); + + /** + * 批量上传图片(循环上传,返回每个文件的视图对象列表)。 + */ + List uploadImages(MultipartFile[] files, String appType); + + /** + * 上传单个文件并返回前端兼容的结果 VO(含 fileid/name/fileurl/host/type/size)。 + */ + UpFileResultVo uploadToResult(MultipartFile file, String appType); +} diff --git a/src/main/java/com/yunhai/homework/service/IHomeworkService.java b/src/main/java/com/yunhai/homework/service/IHomeworkService.java new file mode 100644 index 0000000..ed50167 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IHomeworkService.java @@ -0,0 +1,142 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业业务接口。 + */ +public interface IHomeworkService { + + /** + * 发作业:以当前登录用户为发布人,状态置为「已发布」入库。 + * 若 request.timingSendTime 不为空且晚于当前时间,则作为定时发送, + * 未到点前用户端收件箱不可见(到点后由查询过滤自然可见)。 + * + * @return 新建作业 ID(用于后续回填附件 TopicID 等) + */ + Integer publish(HomeworkRequest request); + + /** + * 编辑作业:按 id 更新标题/内容/类型/部门/置顶,发布人与状态保持不变。 + */ + void edit(Integer id, HomeworkRequest request); + + /** + * 批量删除作业:删除主记录并级联清理阅读记录、接收人、附件关联。 + */ + void deleteBatch(List ids); + + /** + * 撤回作业:仅已发布且未撤回的作业可撤回,置 isPulled=1。 + */ + void pull(Integer id); + + /** + * 置顶/取消置顶。 + */ + void setTopmost(Integer id, Integer topmost); + + /** + * 转发作业:基于源作业创建新作业(已发布),parentId 关联源作业。 + */ + Integer forward(Integer sourceId, HomeworkRequest request); + + /** + * 用户端列表:仅展示已发布且已到点的作业,并标记当前用户是否已读,附未读总数。 + * 返回 HomeworkPageResult(扩展 unreadCount)。 + */ + HomeworkPageResult userList(HomeworkQuery query, Pageable pageable); + + /** + * 管理端列表:按条件查询全部作业(管理员查看全部)。 + */ + BasePageResult adminList(HomeworkQuery query, Pageable pageable); + + /** + * 作业详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。 + */ + HomeworkVo detail(Integer id); + + /** + * 编辑回显详情(不标记已读)。 + */ + HomeworkVo getDetail(Integer id); + + /** + * 标记已读:为当前用户写入(幂等)该作业的阅读记录。 + */ + void markRead(Integer id); + + /** + * 一键全部已读(批量化):当前用户所有未读的已发布作业批量插入阅读记录。 + * @return 已读条数 + */ + int initializeRed(); + + /** + * 追加接收人(事务化):并入接收人列表并重算 allowcount。 + */ + void receiveUsers(Integer homeworkId, String users); + + /** + * 上一篇/下一篇作业ID(SQL 相邻查询)。 + * @param serid 当前作业ID + * @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列) + * @return {prevId, nextId} + */ + Map getNextPrev(Integer serid, String type); + + /** + * 获取某作业的阅读明细列表。 + */ + List getReadDetail(Integer homeworkId); + + /** + * 阅读情况分组(已读/未读)。 + */ + List> getReadDetailGrouped(Integer serid); + + /** + * 催收未读:枚举未读接收人,后续接入消息推送。 + */ + void expedite(Integer serid); + + /** + * 作业统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。 + */ + HomeworkStatisticsVo statistics(HomeworkQuery query, LocalDateTime start, LocalDateTime end); + + /** + * 作业详情视图(含附件、接收人信息),供 Controller 返回。 + */ + HomeworkDetailVo getDetailMap(Integer serid); + + /** + * 已发列表附件批量查询(消除 N+1)。 + */ + Map> batchFileIds(List homeworkIds); + + /** + * 查看作业详情视图(含附件、接收人)。 + * type=1 时先标记已读再返回,type=0 时直接返回不标记。 + */ + HomeworkDetailVo detailMap(Integer serid, Integer type); + + /** + * 一键全部已读并返回结果(count = 已读条数)。 + */ + InitializeRedVo initializeRedResult(); + + /** + * 批量下载作业全部附件(打包 zip):查作业标题后调文件域打包下载。 + */ + void downloadAllFiles(Integer dataid, HttpServletResponse response); +} diff --git a/src/main/java/com/yunhai/homework/service/ISmsService.java b/src/main/java/com/yunhai/homework/service/ISmsService.java new file mode 100644 index 0000000..09b9b5f --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ISmsService.java @@ -0,0 +1,77 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置业务接口。 + * 对应前端 setSms 组接口:getmodule/getgroup/getschsms/getsch/setschsms/setschallsms/setschmodulesms。 + */ +public interface ISmsService { + + /** + * 获取短信模块列表。 + * C# 从 AuthConfig.AppKeyMap 读取。Java版从 sms_module 表读取。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getModuleList(); + + /** + * 获取学校父类组列表。 + * C# 从外部BLL获取学校组。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchParentGroup(); + + /** + * 分页获取学校短信开关列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param schName 学校名称(模糊搜索) + * @return Map: { list, total } + */ + Map getSchIsSmsList(Integer pageSize, Integer pageIndex, String schName); + + /** + * 获取学校列表。 + * C# 从外部BLL获取学校列表。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchList(); + + /** + * 设置单校单模块短信开关。 + * + * @param schSerID 学校ID + * @param moduleId 模块ID + * @param isSms 是否开通 + */ + void setSchSms(Integer schSerID, Integer moduleId, Boolean isSms); + + /** + * 设置某校全部模块短信开关。 + * + * @param schSerID 学校ID + * @param isSms 是否开通 + */ + void setSchSmsAll(Integer schSerID, Boolean isSms); + + /** + * 批量设置多校多模块短信开关。 + * + * @param schSerIDs 逗号分隔的学校ID + * @param modules 逗号分隔的模块ID + * @param isSms 是否开通 + */ + void setSchModuleSms(String schSerIDs, String modules, Boolean isSms); + + /** + * 从 JSON 字符串解析并设置单校单模块短信开关。 + * 解析 SchSerID/ModuID/IsSms 三个字段。 + */ + void setSchSmsFromJson(String itemsModuJson); +} diff --git a/src/main/java/com/yunhai/homework/service/ITemplateService.java b/src/main/java/com/yunhai/homework/service/ITemplateService.java new file mode 100644 index 0000000..5594401 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ITemplateService.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板业务接口。 + * 对应前端 Temp 组接口:GetTypelist/EditType/DelType/GetTemplatelist/EditTemplate/DelTemplate/GetTemplateFist。 + */ +public interface ITemplateService { + + /** + * 获取模板类型列表(首项固定为"全部")。 + * + * @return List<Map> 每项 {TypeID, TypeName} + */ + List> getTypelist(); + + /** + * 新增/编辑模板类型。 + * + * @param typeid 类型ID(0=新增) + * @param typeName 类型名称 + */ + void editType(Integer typeid, String typeName); + + /** + * 删除模板类型(校验是否有模板引用此类型)。 + * + * @param typeid 类型ID + */ + void delType(Integer typeid); + + /** + * 分页获取模板列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param typeid 类型ID(0=全部) + * @return Map: { list, total } + */ + Map getTemplatelist(Integer pageSize, Integer pageIndex, Integer typeid); + + /** + * 新增/编辑模板。 + * + * @param serID 模板ID(null=新增) + * @param title 标题 + * @param content 内容 + * @param typeId 类型ID + * @param cover 封面文件ID + */ + void editTemplate(Integer serID, String title, String content, Integer typeId, Integer cover); + + /** + * 删除模板。 + * + * @param serID 模板ID + */ + void delTemplate(Integer serID); + + /** + * 获取单个模板详情。 + * + * @param serID 模板ID + * @return Map 模板详情 + */ + Map getTemplateFist(Integer serID); +} diff --git a/src/main/java/com/yunhai/homework/service/IUserCenterService.java b/src/main/java/com/yunhai/homework/service/IUserCenterService.java new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IUserCenterService.java @@ -0,0 +1,51 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 用户中心门面服务(对齐 UserBase {@code UserCenterService} 模式)。 + * 封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。 + */ +public interface IUserCenterService { + + /** + * 获取当前用户学校组及用户(树形结构)。 + * 组装规则(从原 BaseApiController.getSchoolAll 下沉): + * + * userbase 读取当前学校整棵组树(getGroupById(schId, withSub=true)),剔除学生组(type=8)及其子组; + * bschclass 获取该校全部教师(/oapi/DbClass/GetSchteaList?Group=schId),按 GroupIds 挂到对应组下, + * 无有效组(如所属组全被剔除)的教师兜底挂学校根节点; + * 扁平节点按 pid 递归组装为树(空组剔除,同 C# FormatUserTree)。 + * + * + * @param schId 学校ID(用户当前角色所属学校) + * @return 用户树形结构(组节点 type=1 / 教师节点 type=2) + */ + List> getSchoolAllTree(Integer schId); + + /** + * 获取指定组内用户(教师 + 管理员)。 + * 对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。 + * + * @param gid 组ID + * @param role 角色(默认 2=教师;1=管理员等) + * @return 组内用户列表 + */ + List> getGroupUser(Integer gid, Integer role); + + /** + * 获取当前用户可访问的模块树(左侧菜单)。 + * 从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。 + * + * @param userId 用户ID + * @param schId 学校ID(可为 null) + * @return 模块树(顶层 List,含 children 嵌套) + */ + List> getUserModuleTree(Integer userId, Integer schId); +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/service/impl/ClassService.java b/src/main/java/com/yunhai/homework/service/impl/ClassService.java new file mode 100644 index 0000000..8be5e2a --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/impl/ClassService.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.service.impl; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.service.IClassService; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班级管理(bschclass)开放接口实现。 + * bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。 + * 推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List
路径前缀:{@code Config.API_PATH + "/file/"}(原 base/ 前缀已修正为独立文件域)。 + * 方法名统一 PascalCase;写操作一律 POST。 + * 返回裸对象,由 ApiResponseAdvice 自动包装为 {code, msg, data}。
路径前缀:{@code Config.API_PATH + "/homework/"}(platform = api / manage / mobiapi)。 + * 方法动词式 PascalCase(对齐 UserBase);写操作一律 POST。 + * 返回裸对象,由 ApiResponseAdvice 自动包装为 {code, msg, data}。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/homework/") +public class HomeworkController { + + @Autowired + private IHomeworkService homeworkService; + + // ============================ 收件箱列表 ============================ + + /** + * 已收作业列表(用户端)。 + */ + @GetMapping("List") + public HomeworkPageResult list(HomeworkQuery query, + @SortDefault(sort = {"topmost", "createTime"}, + direction = Sort.Direction.DESC) Sort sort) { + return homeworkService.userList(query, PageUtils.toPageable(query, sort)); + } + + // ============================ 已发/全部作业列表 ============================ + + /** + * 已发/全部作业列表(管理端)。type=user 只看自己。 + */ + @GetMapping("SendList") + public BasePageResult sendList(HomeworkQuery query, + @SortDefault(sort = "createTime", + direction = Sort.Direction.DESC) Sort sort) { + return homeworkService.adminList(query, PageUtils.toPageable(query, sort)); + } + + // ============================ 查看详情(标读) ============================ + + /** + * 查看作业详情(type=1 普通查看标记已读, type=0 编辑模式不标记已读)。 + */ + @GetMapping("Detail") + public HomeworkDetailVo detail(@RequestParam("serid") Integer serid, + @RequestParam(value = "type", defaultValue = "1") Integer type) { + return homeworkService.detailMap(serid, type); + } + + // ============================ 编辑回显(不标读) ============================ + + /** + * 获取作业详情(编辑/转发用,不标记已读)。 + */ + @GetMapping("GetDetail") + public HomeworkDetailVo getDetail(@RequestParam("serid") Integer serid) { + return homeworkService.getDetailMap(serid); + } + + // ============================ 新建 ============================ + + /** + * 新建作业(JSON body)。fileIds 为附件ID逗号串,smsFlag 为短信标记。 + */ + @PostMapping("Create") + public Integer create(@RequestBody @Validated(ValidationGroups.Create.class) HomeworkRequest request) { + return homeworkService.publish(request); + } + + // ============================ 编辑 ============================ + + /** + * 编辑作业(JSON body,含 id)。 + */ + @PostMapping("Edit") + public void edit(@RequestBody @Validated(ValidationGroups.Update.class) HomeworkRequest request) { + homeworkService.edit(request.getSourceId() != null ? request.getSourceId() : 0, request); + } + + // ============================ 批量删除 ============================ + + /** + * 批量删除作业(ids 数组)。 + */ + @PostMapping("Delete") + public void delete(@RequestBody List ids) { + homeworkService.deleteBatch(ids); + } + + // ============================ 撤回 ============================ + + /** + * 撤回作业。 + */ + @PostMapping("UserPull") + public void userPull(@RequestParam("serid") Integer serid) { + homeworkService.pull(serid); + } + + // ============================ 置顶 ============================ + + /** + * 置顶/取消置顶。 + */ + @PostMapping("SetTopmost") + public void setTopmost(@RequestParam("serid") Integer serid, + @RequestParam(value = "topmost", defaultValue = "1") Integer topmost) { + homeworkService.setTopmost(serid, topmost); + } + + // ============================ 追加接收人 ============================ + + /** + * 追加接收人。 + */ + @PostMapping("ReceiveUsers") + public void receiveUsers(@RequestParam("serid") Integer serid, + @RequestParam("users") String users) { + homeworkService.receiveUsers(serid, users); + } + + // ============================ 催收 ============================ + + /** + * 催收未读。 + */ + @PostMapping("Expedite") + public void expedite(@RequestParam("serid") Integer serid) { + homeworkService.expedite(serid); + } + + // ============================ 一键已读 ============================ + + /** + * 一键全部已读(POST 化,原为 GET 写操作)。 + */ + @PostMapping("InitializeRed") + public InitializeRedVo initializeRed() { + return homeworkService.initializeRedResult(); + } + + // ============================ 阅读情况 ============================ + + /** + * 阅读情况(按已读/未读分组)。 + */ + @GetMapping("ReadDetail") + public List> readDetail(@RequestParam("serid") Integer serid) { + return homeworkService.getReadDetailGrouped(serid); + } + + // ============================ 上一篇/下一篇 ============================ + + /** + * 上一篇/下一篇作业ID。 + */ + @GetMapping("GetNextPrev") + public Map getNextPrev(@RequestParam("serid") Integer serid, + @RequestParam("type") String type) { + return homeworkService.getNextPrev(serid, type); + } + + // ============================ 附件打包下载 ============================ + + /** + * 批量下载作业全部附件(打包 zip)。 + */ + @GetMapping("AllFileDown") + public void allFileDown(@RequestParam("dataid") Integer dataid, + HttpServletResponse response) { + homeworkService.downloadAllFiles(dataid, response); + } + + // ============================ 统计 ============================ + + /** + * 作业统计。 + */ + @GetMapping("Statis") + public HomeworkStatisticsVo statis( + @RequestParam(value = "starttime", required = false) + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime start, + @RequestParam(value = "endtime", required = false) + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime end) { + return homeworkService.statistics(new HomeworkQuery(), start, end); + } + + // ============================ 转发(合并旧 REST 壳 forward) ============================ + + /** + * 转发作业(基于源作业创建新作业)。sourceId 在 request body 中传递。 + */ + @PostMapping("Forward") + public Integer forward(@RequestBody @Validated(ValidationGroups.Create.class) HomeworkRequest request) { + return homeworkService.forward(request.getSourceId(), request); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/NotificationController.java b/src/main/java/com/yunhai/homework/controller/api/NotificationController.java new file mode 100644 index 0000000..e82ba9c --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/NotificationController.java @@ -0,0 +1,53 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.v2.NotificationPageResult; +import com.yunhai.homework.model.v2.NotificationQuery; +import com.yunhai.homework.service.impl.NotificationService; +import com.yunhai.homework.utils.UserContext; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * 站内通知接口(PRD §18 未列,按计划 §7.3 补充)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/notifications") +public class NotificationController { + + @Autowired + private NotificationService notificationService; + + /** 我的通知分页(含未读数;type/isRead 过滤) */ + @GetMapping + public NotificationPageResult page(NotificationQuery query) { + return notificationService.page(currentUserId(), query); + } + + /** 标记单条已读 */ + @PostMapping("/{id}/read") + public void markRead(@PathVariable("id") Integer id) { + notificationService.markRead(id, currentUserId()); + } + + /** 全部已读 */ + @PostMapping("/read-all") + public void markAllRead() { + notificationService.markAllRead(currentUserId()); + } + + /** 当前登录人ID(用户中心数字ID),无登录态时拒绝 */ + private static Integer currentUserId() { + Integer userId = UserContext.getUserId(); + if (userId == null) { + throw new BizException(ErrorCode.FORBIDDEN, "未获取到登录身份"); + } + return userId; + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/SmsController.java b/src/main/java/com/yunhai/homework/controller/api/SmsController.java new file mode 100644 index 0000000..25d4311 --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/SmsController.java @@ -0,0 +1,88 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.service.ISmsService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置接口(短信域)。 + * 路径前缀:{@code Config.API_PATH + "/sms/"}(原 schsms/ 已修正)。 + * 方法名 PascalCase(原全小写已修正);写操作一律 POST(原 setschallsms/setschmodulesms 为 GET 写操作,已 POST 化)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/sms/") +public class SmsController { + + @Autowired + private ISmsService smsService; + + /** + * 获取短信模块列表。 + */ + @GetMapping("GetModule") + public List> getModule() { + return smsService.getModuleList(); + } + + /** + * 获取学校父类组列表。 + */ + @GetMapping("GetGroup") + public List> getGroup() { + return smsService.getSchParentGroup(); + } + + /** + * 分页获取学校短信开关列表。 + */ + @GetMapping("GetSchoolSms") + public Map getSchoolSms( + @RequestParam(value = "schpid", required = false) String schpid, + @RequestParam("PageSize") Integer pageSize, + @RequestParam("PageIndex") Integer pageIndex, + @RequestParam(value = "SchName", required = false, defaultValue = "") String schName) { + return smsService.getSchIsSmsList(pageSize, pageIndex, schName); + } + + /** + * 获取学校列表。 + */ + @GetMapping("GetSchool") + public List> getSchool() { + return smsService.getSchList(); + } + + /** + * 设置单校单模块短信开关。 + */ + @PostMapping("SetSchoolSms") + public void setSchoolSms(@RequestParam("itemsModu") String itemsModuJson) { + smsService.setSchSmsFromJson(itemsModuJson); + } + + /** + * 设置某校全部模块短信开关(原 GET setschallsms,已 POST 化)。 + */ + @PostMapping("SetAllSchoolSms") + public void setAllSchoolSms( + @RequestParam("SchSerID") Integer schSerID, + @RequestParam("IsSms") Boolean isSms) { + smsService.setSchSmsAll(schSerID, isSms); + } + + /** + * 批量设置多校多模块短信开关(原 GET setschmodulesms,已 POST 化)。 + */ + @PostMapping("SetSchoolModuleSms") + public void setSchoolModuleSms( + @RequestParam("SchSerID") String schSerID, + @RequestParam("Module") String module, + @RequestParam("IsSms") Boolean isSms) { + smsService.setSchModuleSms(schSerID, module, isSms); + } + +} diff --git a/src/main/java/com/yunhai/homework/controller/api/StudentHomeworkController.java b/src/main/java/com/yunhai/homework/controller/api/StudentHomeworkController.java new file mode 100644 index 0000000..56a37b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/StudentHomeworkController.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.BasePageQuery; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.v2.StudentHomeworkDetailVo; +import com.yunhai.homework.model.v2.StudentHomeworkVo; +import com.yunhai.homework.model.v2.StudentResultVo; +import com.yunhai.homework.model.v2.StudentSubmitRequest; +import com.yunhai.homework.model.v2.SubmissionActionRequest; +import com.yunhai.homework.service.impl.StudentHomeworkService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * 课堂作业 V2 学生端接口(PRD §18 路径一字对齐:/student/homeworks…)。 + * + * 身份:当前登录用户ID ↔ class_student 名册;仅可见本人班级作业,禁 IDOR。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/student/homeworks") +public class StudentHomeworkController { + + @Autowired + private StudentHomeworkService studentHomeworkService; + + /** 我的作业列表(tab=all/todo/done/closed) */ + @GetMapping + public BasePageResult list( + @RequestParam(value = "tab", defaultValue = "all") String tab, + BasePageQuery query) { + return studentHomeworkService.list(tab, query); + } + + /** 作业详情 */ + @GetMapping("/{id}") + public StudentHomeworkDetailVo detail(@PathVariable("id") Integer id) { + return studentHomeworkService.detail(id); + } + + /** 阅读上报(前端停留 ≥3 秒后调用;幂等) */ + @PostMapping("/{id}/read") + public void read(@PathVariable("id") Integer id) { + studentHomeworkService.read(id); + } + + /** 提交作业,返回新提交行 id */ + @PostMapping("/{id}/submissions") + public Map submit(@PathVariable("id") Integer id, + @RequestBody @Validated StudentSubmitRequest request) { + Integer submissionId = studentHomeworkService.submit(id, request); + return java.util.Collections.singletonMap("submissionId", submissionId); + } + + /** 撤回 / 打回后重交(action=withdraw|resubmit),返回新提交行 id */ + @PutMapping("/{id}/submissions/{submissionId}") + public Map action(@PathVariable("id") Integer id, + @PathVariable("submissionId") Integer submissionId, + @RequestBody @Validated SubmissionActionRequest request) { + Integer newId = studentHomeworkService.action(id, submissionId, request); + return java.util.Collections.singletonMap("submissionId", newId); + } + + /** 批阅结果(当前版本 + 历史版本) */ + @GetMapping("/{id}/result") + public StudentResultVo result(@PathVariable("id") Integer id) { + return studentHomeworkService.result(id); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java b/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java new file mode 100644 index 0000000..5335083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java @@ -0,0 +1,219 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.v2.ClassTreeNode; +import com.yunhai.homework.model.v2.ExtendRequest; +import com.yunhai.homework.model.v2.HomeworkStatsVo; +import com.yunhai.homework.model.v2.HomeworkV2Request; +import com.yunhai.homework.model.v2.HomeworkV2Vo; +import com.yunhai.homework.model.v2.RemindRequest; +import com.yunhai.homework.model.v2.ReturnRequest; +import com.yunhai.homework.model.v2.ReviewRequest; +import com.yunhai.homework.model.v2.ScheduleRequest; +import com.yunhai.homework.model.v2.StudentMatrixQuery; +import com.yunhai.homework.model.v2.StudentMatrixVo; +import com.yunhai.homework.model.v2.SubmissionVersionVo; +import com.yunhai.homework.model.v2.SubjectDto; +import com.yunhai.homework.model.v2.TeacherHomeworkQuery; +import com.yunhai.homework.model.v2.TeachingClassVo; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.SortDefault; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * 课堂作业 V2 教师端接口(PRD §18 路径一字对齐:/teacher/homeworks…)。 + * + * 与 notice 语义的旧接口(/{platform}/homework/*)并存:旧接口冻结维护, + * 新业务一律走本控制器;返回裸对象由 ApiResponseAdvice 包装 {code,msg,data}。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/teacher/homeworks") +public class TeacherHomeworkV2Controller { + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + // ============================ 页 1:发布作业 ============================ + + /** 创建作业(落草稿) */ + @PostMapping + public Integer create(@RequestBody @Validated HomeworkV2Request request) { + return teacherHomeworkV2Service.create(request); + } + + /** 编辑作业(仅草稿) */ + @PutMapping("/{id}") + public void edit(@PathVariable("id") Integer id, + @RequestBody @Validated HomeworkV2Request request) { + teacherHomeworkV2Service.edit(id, request); + } + + /** 删除作业(仅草稿) */ + @DeleteMapping("/{id}") + public void delete(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.delete(id); + } + + /** 立即发布(草稿 → 进行中) */ + @PostMapping("/{id}/publish") + public void publish(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.publish(id); + } + + /** 定时发布 / 取消定时(body: {scheduledAt} 或 {cancel:true}) */ + @PostMapping("/{id}/schedule") + public void schedule(@PathVariable("id") Integer id, @RequestBody ScheduleRequest request) { + teacherHomeworkV2Service.schedule(id, request); + } + + // ============================ 页 2:草稿箱 ============================ + + /** 草稿箱分页 */ + @GetMapping("/drafts") + public BasePageResult drafts(TeacherHomeworkQuery query, + @SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort) { + return teacherHomeworkV2Service.drafts(query, sort); + } + + // ============================ 页 3:已发布列表 ============================ + + /** 已发布列表分页(含应交/已读/已交/未读未交聚合) */ + @GetMapping("/published") + public BasePageResult published(TeacherHomeworkQuery query, + @SortDefault(sort = "publishedAt", direction = Sort.Direction.DESC) Sort sort) { + return teacherHomeworkV2Service.published(query, sort); + } + + /** 提醒未读/未交学生,返回实际提醒人数 */ + @PostMapping("/{id}/remind") + public Map remind(@PathVariable("id") Integer id, @RequestBody RemindRequest request) { + int reminded = teacherHomeworkV2Service.remind(id, request); + return java.util.Collections.singletonMap("reminded", reminded); + } + + /** 延长截止时间 */ + @PostMapping("/{id}/extend") + public void extend(@PathVariable("id") Integer id, @RequestBody @Validated ExtendRequest request) { + teacherHomeworkV2Service.extend(id, request); + } + + /** 复制为新草稿,返回新作业 id */ + @PostMapping("/{id}/copy") + public Integer copy(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.copy(id); + } + + /** 归档(只读,不再触发任何提醒) */ + @PostMapping("/{id}/archive") + public void archive(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.archive(id); + } + + /** 导出学生矩阵 Excel */ + @GetMapping("/{id}/export") + public void export(@PathVariable("id") Integer id, HttpServletResponse response) throws IOException { + teacherHomeworkV2Service.export(id, response); + } + + // ============================ 页 4:详情 / 统计 ============================ + + /** 作业详情 */ + @GetMapping("/{id}") + public HomeworkV2Vo detail(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.detail(id); + } + + /** 汇总统计卡片(总进度 + 分班进度) */ + @GetMapping("/{id}/stats") + public HomeworkStatsVo stats(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.stats(id); + } + + // ============================ 页 5:学生矩阵 ============================ + + /** 学生矩阵分页(阅读/提交/批阅状态,未交学生生成占位行) */ + @GetMapping("/{id}/students") + public BasePageResult students(@PathVariable("id") Integer id, + StudentMatrixQuery query) { + return teacherHomeworkV2Service.students(id, query); + } + + // ============================ 页 6:批阅 / 打回 / 版本 ============================ + + /** 保存批阅(→ REVIEWED,推送批阅完成通知) */ + @PostMapping("/{id}/submissions/{studentId}/review") + public void review(@PathVariable("id") Integer id, @PathVariable("studentId") Integer studentId, + @RequestBody @Validated ReviewRequest request) { + teacherHomeworkV2Service.review(id, studentId, request); + } + + /** 打回(→ RETURNED,推送打回通知) */ + @PostMapping("/{id}/submissions/{studentId}/return") + public void returnSubmission(@PathVariable("id") Integer id, @PathVariable("studentId") Integer studentId, + @RequestBody @Validated ReturnRequest request) { + teacherHomeworkV2Service.returnSubmission(id, studentId, request); + } + + /** 历史提交版本(批阅弹窗切换版本) */ + @GetMapping("/{id}/submissions/{studentId}/versions") + public List versions(@PathVariable("id") Integer id, + @PathVariable("studentId") Integer studentId) { + return teacherHomeworkV2Service.versions(id, studentId); + } + + // ============================ 辅助:学科 ============================ + + /** 我布置过的学科(本人已发布及之后作业的学科 ∪ 我的任教学科,合并去重;草稿不计) */ + @GetMapping("/subjects/assigned") + public List assignedSubjects() { + return teacherHomeworkV2Service.assignedSubjects(); + } + + /** 我的任教学科(bschclass 任教关系去重,名称取本校学科字典) */ + @GetMapping("/subjects/teaching") + public List teachingSubjects() { + return teacherHomeworkV2Service.teachingSubjects(); + } + + /** 按学科筛选我的任教班级(subId=学科编码;班级名称取学校班级列表) */ + @GetMapping("/subjects/classes") + public List teachingClasses(@RequestParam("subId") String subId) { + return teacherHomeworkV2Service.teachingClasses(subId); + } + + // ============================ 辅助:班级树 / 名册同步 ============================ + + /** 年级→班级树(发布选班;名册快照聚合) */ + @GetMapping("/classes") + public List classTree() { + return teacherHomeworkV2Service.classTree(); + } + + /** 手动触发名册同步(bschclass 接口就绪前为空操作) */ + @PostMapping("/classes/sync") + public Map syncClasses() { + int synced = classStudentService.syncAll(); + return java.util.Collections.singletonMap("synced", synced); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/TemplateController.java b/src/main/java/com/yunhai/homework/controller/api/TemplateController.java new file mode 100644 index 0000000..e9c593c --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/TemplateController.java @@ -0,0 +1,88 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.service.ITemplateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板接口(模板域)。 + * 路径前缀:{@code Config.API_PATH + "/template/"}(原 homework/Template/ 三级路径已修正)。 + * 写操作一律 POST(原 EditType/DelTemplate 为 GET 写操作,已 POST 化)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/template/") +public class TemplateController { + + @Autowired + private ITemplateService templateService; + + /** + * 获取模板类型列表。 + */ + @GetMapping("Typelist") + public List> getTypelist() { + return templateService.getTypelist(); + } + + /** + * 新增/编辑模板类型(原 GET EditType,已 POST 化)。 + */ + @PostMapping("TypeEdit") + public void typeEdit( + @RequestParam("typeid") Integer typeid, + @RequestParam("TypeName") String typeName) { + templateService.editType(typeid, typeName); + } + + /** + * 删除模板类型(原 GET DelType,已 POST 化)。 + */ + @PostMapping("TypeDelete") + public void typeDelete(@RequestParam("typeid") Integer typeid) { + templateService.delType(typeid); + } + + /** + * 分页获取模板列表。 + */ + @GetMapping("List") + public Map list( + @RequestParam("PageSize") Integer pageSize, + @RequestParam("PageIndex") Integer pageIndex, + @RequestParam(value = "typeid", defaultValue = "0") Integer typeid) { + return templateService.getTemplatelist(pageSize, pageIndex, typeid); + } + + /** + * 新增/编辑模板。 + */ + @PostMapping("TemplateEdit") + public void templateEdit( + @RequestParam(value = "SerID", required = false) Integer serID, + @RequestParam("Title") String title, + @RequestParam("Contents") String contents, + @RequestParam(value = "Type", required = false) Integer type, + @RequestParam(value = "Cover", required = false) Integer cover) { + templateService.editTemplate(serID, title, contents, type, cover); + } + + /** + * 删除模板(原 GET DelTemplate,已 POST 化)。 + */ + @PostMapping("TemplateDelete") + public void templateDelete(@RequestParam("SerID") Integer serid) { + templateService.delTemplate(serid); + } + + /** + * 获取单个模板详情。 + */ + @GetMapping("TemplateFirst") + public Map templateFirst(@RequestParam("serid") Integer serid) { + return templateService.getTemplateFist(serid); + } +} diff --git a/src/main/java/com/yunhai/homework/entity/BaseEntity.java b/src/main/java/com/yunhai/homework/entity/BaseEntity.java new file mode 100644 index 0000000..5f98e8c --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/BaseEntity.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.common.listener.HomeworkAuditListener; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 所有实体的公共基类:自增主键 + 创建人/时间 + 更新人/时间(由 {@link HomeworkAuditListener} 自动填充)。 + */ +@MappedSuperclass +@Getter +@Setter +@EntityListeners(HomeworkAuditListener.class) +public abstract class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 创建人ID(平台用户ID) + */ + @Column(name = "create_by", updatable = false) + private String createBy; + + /** + * 创建时间 + */ + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + /** + * 更新人ID(平台用户ID) + */ + @Column(name = "update_by") + private String updateBy; + + /** + * 更新时间 + */ + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/ClassStudent.java b/src/main/java/com/yunhai/homework/entity/ClassStudent.java new file mode 100644 index 0000000..793e295 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ClassStudent.java @@ -0,0 +1,59 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 班级学生名册快照(PRD class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据),每日全量刷 + 手动触发; + * 同步不物理删,转出置 status=0。学生身份口径:用户中心数字ID(UserContext.getUserId()) + * ↔ student_id(SchStudent.UserStuSerID),名册仅存学生ID + 姓名快照。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "class_student", + uniqueConstraints = @UniqueConstraint(name = "uk_class_student", columnNames = {"class_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ClassStudent extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; + + /** 年级名快照 */ + @Column(name = "grade_name", length = 50) + private String gradeName; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 在班状态:1 在班 / 0 已转出退课 */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** 离班时间 */ + @Column(name = "leave_at") + private LocalDateTime leaveAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroup.java b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java new file mode 100644 index 0000000..a002e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 联系组。对应原 .NET Contacts_Group 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroup extends BaseEntity { + + /** 组名称 */ + @Column(name = "group_name", length = 200) + private String groupName; + + /** 创建人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "creator_id") + private Integer creatorId; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java new file mode 100644 index 0000000..eb07763 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 联系组-用户关联。对应原 .NET Contacts_Tea 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group_user", + uniqueConstraints = @UniqueConstraint(name = "uk_group_user", + columnNames = {"group_id", "user_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroupUser extends BaseEntity { + + /** 联系组ID */ + @Column(name = "group_id", nullable = false) + private Integer groupId; + + /** 用户ID(基础平台用户ID) */ + @Column(name = "user_id", nullable = false) + private Integer userId; + + /** 用户姓名 */ + @Column(name = "user_name", length = 64) + private String userName; +} diff --git a/src/main/java/com/yunhai/homework/entity/Homework.java b/src/main/java/com/yunhai/homework/entity/Homework.java new file mode 100644 index 0000000..6e615f2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Homework.java @@ -0,0 +1,208 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 课程作业主表。对应原 .NET Notify_Message(已剔除短信/模板/门户等无关字段)。 + * + * 阅读状态不落主表:采用「懒标记」模型——读者首次查看时写入 {@link HomeworkRead}, + * 不存在阅读记录即视为未读,从而无需在发作业时枚举全校用户预插接收行。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Homework extends BaseEntity { + + /** + * 作业标题 + */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** + * 学校ID(多租户隔离:发布时从当前用户角色填充,查询时按校过滤)。 + */ + @Column(name = "sch_id") + private Integer schId; + + /** + * 作业内容 + */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** + * 作业类型:1 普通 / 2 重要(见 {@link com.yunhai.homework.enums.HomeworkTypeEnum}) + */ + @Column(name = "type", nullable = false) + private Integer type = 1; + + /** + * 发布人ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "publisher_id") + private Integer publisherId; + + /** + * 发布人姓名 + */ + @Column(name = "publisher_name", length = 64) + private String publisherName; + + /** + * 发布部门ID(原 DeptID) + */ + @Column(name = "dept_id", length = 64) + private String deptId; + + /** + * 发布部门名称 + */ + @Column(name = "dept_name", length = 128) + private String deptName; + + /** + * 状态:1 已发布(无草稿态,见 {@link com.yunhai.homework.enums.HomeworkStatusEnum}) + */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** + * 是否置顶:0 否 / 1 是(原 topmost) + */ + @Column(name = "topmost") + private Integer topmost = 0; + + // ==================== 迁移补全字段(对应 C# Notify_Message) ==================== + + /** + * 接收人列表(JSON 数组,如 ["10012","10013"] 或接收组ID列表)。 + * 对应 C# Notify_Message.ReceiveList + */ + @Column(name = "receive_list", columnDefinition = "LONGTEXT") + private String receiveList; + + /** + * 密码发送(对应 C# Notify_Message.PwdSend) + */ + @Column(name = "pwd_send", length = 255) + private String pwdSend; + + /** + * 抄送人列表(对应 C# Notify_Message.ChaoSend) + */ + @Column(name = "chao_send", length = 2000) + private String chaoSend; + + /** + * 已读数(发送时初始化为 0,也可由 HomeworkRead 表 COUNT 实时计算)。 + * 对应 C# Notify_Message.readcount + */ + @Column(name = "read_count") + private Integer readCount = 0; + + /** + * 应读数(接收人总数)。对应 C# Notify_Message.allowcount + */ + @Column(name = "allow_count") + private Integer allowCount = 0; + + /** + * 父作业ID(转发时关联原作业)。对应 C# Notify_Message.fkid + */ + @Column(name = "parent_id") + private Integer parentId; + + /** + * 回复ID。对应 C# Notify_Message.reid + */ + @Column(name = "reply_id", length = 255) + private String replyId; + + /** + * 是否撤回:0 否 / 1 是。对应 C# Notify_Message.ispull + */ + @Column(name = "is_pulled") + private Integer isPulled = 0; + + /** + * 定时发送时间(到时间后自动发布)。对应 C# Notify_Message.DsfsTime。 + * PRD 语义中的 scheduled_at 复用本列。 + */ + @Column(name = "timing_send_time") + private LocalDateTime timingSendTime; + + /** + * 短信发送标记:0 不发短信 / 1 发短信。对应 C# Notify_Message.dxbit + */ + @Column(name = "sms_flag") + private Integer smsFlag = 0; + + // ==================== 课堂作业 V3(PRD V1.0)扩展列 ==================== + + /** + * 学科(字典值)。 + */ + @Column(name = "subject", length = 50) + private String subject; + + /** + * 截止时间(精确到分钟;空 = 仅查阅型,不设截止,到点关闭任务跳过)。 + */ + @Column(name = "deadline_at") + private LocalDateTime deadlineAt; + + /** + * 作业流程状态(PRD 五态,见 {@link com.yunhai.homework.enums.HwStatusEnum})。 + * notice 原 status 列继续承载发布/撤回语义,两者并存。 + */ + @Column(name = "hw_status", nullable = false, length = 20) + private String hwStatus = com.yunhai.homework.enums.HwStatusEnum.DRAFT_UNPUBLISHED.getCode(); + + /** + * 发布时间(立即发布或定时到点发布时写入)。 + */ + @Column(name = "published_at") + private LocalDateTime publishedAt; + + /** + * 提交方式,JSON 数组文本,如 ["image","attach"]。 + * 取值:text / image / attach / none(PRD 的 图片/文件/两者 映射为 + * ["image"] / ["attach"] / ["image","attach"])。 + */ + @Column(name = "submit_type", length = 100) + private String submitType; + + /** + * 截止后是否允许逾期提交:0 否 / 1 是。 + */ + @Column(name = "allow_late", nullable = false) + private Integer allowLate = 0; + + /** + * 提醒设置:截止前 1 天提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1d", nullable = false) + private Integer remind1d = 1; + + /** + * 提醒设置:截止前 1 小时提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1h", nullable = false) + private Integer remind1h = 1; + + /** + * 归档时间(教师手动归档时写入;归档后只读,所有提醒任务跳过)。 + */ + @Column(name = "archived_at") + private LocalDateTime archivedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java new file mode 100644 index 0000000..6fc7804 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业附件表(PRD homework_attachment)。文件实体仍存 up_file(file_id 关联 + file_url 冗余)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_attachment") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkAttachment extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** up_file.id(可空:允许外部 URL 附件) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file / ... */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号(越小越靠前) */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkClass.java b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java new file mode 100644 index 0000000..c01cefb --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 作业-班级关联表(PRD homework_class)。 + * 发布/编辑时由 receiveList("all" 或班级 id 串)解析写入,是应交名单与统计的权威关联。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_class", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_class", columnNames = {"homework_id", "class_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkClass extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkRead.java b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java new file mode 100644 index 0000000..35b773a --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.time.LocalDateTime; + +/** + * 作业阅读记录(懒标记)。对应原 .NET Notify_ReceiveList 的「逐用户阅读状态」语义, + * 但仅在读者首次查看时落库,避免发作业时预插全员。 + * + * 唯一约束 (homework_id, reader_id):同一用户对同一作业只记一条已读。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read", + uniqueConstraints = @UniqueConstraint( + name = "uk_homework_reader", + columnNames = {"homework_id", "reader_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkRead extends BaseEntity { + + /** + * 作业ID(关联 homework.id) + */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** + * 读者ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "reader_id", nullable = false) + private Integer readerId; + + /** + * 读者姓名 + */ + @Column(name = "reader_name", length = 64) + private String readerName; + + /** + * 阅读时间 + */ + @Column(name = "read_time") + private LocalDateTime readTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java new file mode 100644 index 0000000..8179e79 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java @@ -0,0 +1,58 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 学生作业阅读记录(PRD homework_read_record)。 + * + * 学生维度,含首次/最近阅读时间,与 notice 语义的 {@link HomeworkRead}(教师/阅读者已读)互不影响。 + * 行仅在判定已读时写入;未读名单 = 名册左连本表(无记录或 is_read=0)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read_record", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student", columnNames = {"homework_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReadRecord extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", length = 64) + private String classId; + + /** 阅读状态:0 UNREAD / 1 READ */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 首次阅读时间(写入后不再变更) */ + @Column(name = "first_read_at") + private LocalDateTime firstReadAt; + + /** 最近阅读时间(每次上报刷新) */ + @Column(name = "last_read_at") + private LocalDateTime lastReadAt; + + /** 阅读次数 */ + @Column(name = "read_count", nullable = false) + private Integer readCount = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java new file mode 100644 index 0000000..97e068b --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java @@ -0,0 +1,45 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 作业接收人关联表(替代旧 receive_list JSON 字符串存储)。 + * + * 每条记录代表一个作业的一个接收人(用户ID + 姓名快照,不含账号)。 + * 全员作业(receive_list="ALL")不写明细行, + * 读取时按 receive_list="ALL" 标识走旧逻辑兜底。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_receiver", + indexes = { + @Index(name = "idx_homework_id", columnList = "homework_id"), + @Index(name = "idx_receiver", columnList = "receiver_id") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReceiver extends BaseEntity { + + /** 作业ID(关联 homework.id) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 接收人ID(用户中心数字ID) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收类型:1 接收 / 2 抄送 */ + @Column(name = "receive_type", nullable = false) + private Integer receiveType = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java new file mode 100644 index 0000000..4626044 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java @@ -0,0 +1,98 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.enums.ReviewStatusEnum; +import com.yunhai.homework.enums.SubmitStatusEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 作业提交表,多版本建模(PRD homework_submission)。 + * + * 同一学生 (homework_id, student_id) 下 version 单调递增;提交/打回重交/撤回均新增行, + * 旧行 is_latest 置 0 保留历史;is_latest=1 的行是当前有效版本。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_submission", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student_ver", + columnNames = {"homework_id", "student_id", "version"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkSubmission extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", nullable = false, length = 50) + private String className; + + /** 提交版本,同一学生单调递增(撤回/重交均 +1) */ + @Column(name = "version", nullable = false) + private Integer version = 1; + + /** 是否当前版本:1 是 / 0 历史 */ + @Column(name = "is_latest", nullable = false) + private Integer isLatest = 1; + + /** 提交状态(见 {@link SubmitStatusEnum}) */ + @Column(name = "status", nullable = false, length = 20) + private String status = SubmitStatusEnum.SUBMITTED.getCode(); + + /** 是否逾期:0 否 / 1 是 */ + @Column(name = "is_late", nullable = false) + private Integer isLate = 0; + + /** 提交时间(NOT_SUBMITTED 空行为空) */ + @Column(name = "submitted_at") + private LocalDateTime submittedAt; + + /** 文本作业内容 */ + @Column(name = "text", columnDefinition = "TEXT") + private String text; + + /** 评分 */ + @Column(name = "score", precision = 6, scale = 2) + private BigDecimal score; + + /** 评语(打回时 = 订正原因,必填) */ + @Column(name = "comment", columnDefinition = "TEXT") + private String comment; + + /** 批阅状态(见 {@link ReviewStatusEnum}) */ + @Column(name = "review_status", nullable = false, length = 20) + private String reviewStatus = ReviewStatusEnum.PENDING.getCode(); + + /** 批阅人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "reviewed_by") + private Integer reviewedBy; + + /** 批阅人姓名快照 */ + @Column(name = "reviewed_by_name", length = 64) + private String reviewedByName; + + /** 批阅时间 */ + @Column(name = "reviewed_at") + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java new file mode 100644 index 0000000..f088c92 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板。对应原 .NET Notify_Template 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_template") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkTemplate extends BaseEntity { + + /** 模板标题 */ + @Column(name = "title", length = 200) + private String title; + + /** 模板内容(HTML) */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** 封面图片文件ID */ + @Column(name = "cover") + private Integer cover; + + /** 引用次数 */ + @Column(name = "ref_count") + private Integer refCount = 0; + + /** 模板类型ID */ + @Column(name = "type_id") + private Integer typeId; +} diff --git a/src/main/java/com/yunhai/homework/entity/Notification.java b/src/main/java/com/yunhai/homework/entity/Notification.java new file mode 100644 index 0000000..2a931a1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Notification.java @@ -0,0 +1,71 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 站内通知(PRD notification)。六类通知的统一落表 + 已读状态。 + * 通知已读独立于作业阅读记录(本表 is_read 只反映通知列表已读)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "notification", + indexes = { + @Index(name = "idx_receiver", columnList = "receiver_id, is_read"), + @Index(name = "idx_hw_type", columnList = "homework_id, type") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Notification extends BaseEntity { + + /** 通知类型(见 {@link com.yunhai.homework.enums.NotificationTypeEnum}) */ + @Column(name = "type", nullable = false, length = 30) + private String type; + + /** 关联作业ID */ + @Column(name = "homework_id") + private Integer homeworkId; + + /** 关联业务ID(如 submissionId) */ + @Column(name = "related_id") + private Integer relatedId; + + /** 通知标题 */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** 通知内容 */ + @Column(name = "content", length = 1000) + private String content; + + /** 接收人ID(学生 = student_id,教师 = 用户中心数字ID,同一 ID 空间) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收人角色:student / teacher */ + @Column(name = "receiver_role", nullable = false, length = 20) + private String receiverRole = "student"; + + /** 是否已读:0 未读 / 1 已读 */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 已读时间 */ + @Column(name = "read_at") + private LocalDateTime readAt; + + /** 发送通道:inapp 站内 / sms 短信 */ + @Column(name = "send_channel", nullable = false, length = 20) + private String sendChannel = "inapp"; +} diff --git a/src/main/java/com/yunhai/homework/entity/SchClassTeach.java b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java new file mode 100644 index 0000000..d1a091f --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java @@ -0,0 +1,46 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 班级任教关系快照(bschclass sch_class_teach 本地镜像,PRD 口径同 class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据);bschclass 任教接口 + * 不可用时,任教学科链路降级读本表。教师口径:用户中心数字ID(UserContext.getUserId()); + * class_id 与 class_student.class_id 同一取值域。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sch_class_teach", + uniqueConstraints = @UniqueConstraint(name = "uk_sch_class_teach", columnNames = {"school_id", "class_id", "tea_id", "sub_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SchClassTeach extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID(与 class_student.class_id 同一取值域) */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 教师ID(用户中心数字ID) */ + @Column(name = "tea_id", nullable = false) + private Integer teaId; + + /** 学科编码(bschclass SubID;本地无字典时编码即展示名) */ + @Column(name = "sub_id", nullable = false, length = 64) + private String subId; + + /** 有效状态:1 有效 / 0 失效 */ + @Column(name = "status", nullable = false) + private Integer status = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsModule.java b/src/main/java/com/yunhai/homework/entity/SmsModule.java new file mode 100644 index 0000000..3f4d444 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsModule.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 短信模块配置。对应原 .NET SchSms 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_module") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsModule extends BaseEntity { + + /** 模块名称 */ + @Column(name = "module_name", length = 100) + private String moduleName; + + /** 模块标识 */ + @Column(name = "module_code", length = 50) + private String moduleCode; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java new file mode 100644 index 0000000..428d271 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java @@ -0,0 +1,37 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 学校短信开通配置。对应原 .NET SchSms_Set 表。 + * 记录某学校某模块是否开通短信。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_school_config") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsSchoolConfig extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 学校名称 */ + @Column(name = "school_name", length = 200) + private String schoolName; + + /** 模块ID */ + @Column(name = "module_id") + private Integer moduleId; + + /** 是否开通短信 */ + @Column(name = "is_sms") + private Boolean isSms = false; +} diff --git a/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java new file mode 100644 index 0000000..85df7ba --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java @@ -0,0 +1,54 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 提交附件表(PRD submission_attachment)。替代 JSON 方案,支撑矩阵缩略图与批阅大图预览。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "submission_attachment", + indexes = @Index(name = "idx_sub", columnList = "submission_id")) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SubmissionAttachment extends BaseEntity { + + /** 提交行ID(homework_submission.id) */ + @Column(name = "submission_id", nullable = false) + private Integer submissionId; + + /** 作业ID(冗余,便于统计与清理) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + @Column(name = "student_id") + private Integer studentId; + + /** up_file.id(可空:允许外部 URL) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号 */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/SysDict.java b/src/main/java/com/yunhai/homework/entity/SysDict.java new file mode 100644 index 0000000..3091084 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SysDict.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 系统字典(发文部门等)。对应原 .NET Common_DeptDic 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sys_dict") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SysDict extends BaseEntity { + + /** 字典类型(如 "dept"=发文部门) */ + @Column(name = "dict_type", length = 50) + private String dictType; + + /** 字典 key(原 CodeID) */ + @Column(name = "dict_key", length = 100) + private String dictKey; + + /** 字典 value(显示名称,原 Name) */ + @Column(name = "dict_value", length = 200) + private String dictValue; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; + + /** 是否启用:0 禁用 / 1 启用(原 Enable) */ + @Column(name = "enable_flag") + private Integer enableFlag = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/TemplateType.java b/src/main/java/com/yunhai/homework/entity/TemplateType.java new file mode 100644 index 0000000..aab8163 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/TemplateType.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板类型。对应原 .NET Notify_TemplateType 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "template_type") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class TemplateType extends BaseEntity { + + /** 类型名称 */ + @Column(name = "type_name", length = 100) + private String typeName; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/UpFile.java b/src/main/java/com/yunhai/homework/entity/UpFile.java new file mode 100644 index 0000000..38248c7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/UpFile.java @@ -0,0 +1,47 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 附件文件记录。对应原 .NET Common_UpFiles 表。 + * + * 存储文件元信息(真实文件名、存储路径、大小、类型),物理文件存储在 + * application.yml 的 file.upload-path 目录下。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "up_file") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class UpFile extends BaseEntity { + + /** 应用类型(如 "homework"),用于区分不同业务的附件 */ + @Column(name = "topic_type", length = 50) + private String topicType; + + /** 关联主题ID(如作业 serid) */ + @Column(name = "topic_id") + private Integer topicId; + + /** 文件真实名称(用户上传时的原始文件名) */ + @Column(name = "file_true_name", length = 255) + private String fileTrueName; + + /** 文件存储路径(相对路径,如 homework/20260823/uuid.pdf) */ + @Column(name = "file_name", length = 500) + private String fileName; + + /** 文件大小(字节) */ + @Column(name = "file_size") + private Integer fileSize; + + /** 文件后缀名(如 pdf, docx, jpg) */ + @Column(name = "file_type", length = 20) + private String fileType; +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java new file mode 100644 index 0000000..124bec9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业状态。无草稿态:发作业一律置为「已发布」, + * 定时发送通过 timingSendTime + 查询侧「已到点」过滤控制可见性。 + * 对应原 .NET Notify_Message.status(原系统已发布=2,此处归一化为 1)。 + */ +@Getter +public enum HomeworkStatusEnum { + + PUBLISHED(1, "已发布"); + + private final int code; + private final String desc; + + HomeworkStatusEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkStatusEnum of(Integer code) { + if (code == null) { + return PUBLISHED; + } + for (HomeworkStatusEnum e : values()) { + if (e.code == code) { + return e; + } + } + return PUBLISHED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java new file mode 100644 index 0000000..077396d --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业类型。对应原 .NET Notify_Message.type(原系统作业类=2,此处归一化)。 + */ +@Getter +public enum HomeworkTypeEnum { + + NORMAL(1, "普通作业"), + IMPORTANT(2, "重要作业"); + + private final int code; + private final String desc; + + HomeworkTypeEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkTypeEnum of(Integer code) { + if (code == null) { + return NORMAL; + } + for (HomeworkTypeEnum e : values()) { + if (e.code == code) { + return e; + } + } + return NORMAL; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java new file mode 100644 index 0000000..8b10083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业流程状态(PRD 五态,存 homework.hw_status)。 + * + * 与 notice 原 status(Integer,发布/撤回语义)并存:本枚举承载课堂作业业务流转, + * 流转收敛在 HomeworkStateService,条件更新保证幂等。 + */ +@Getter +public enum HwStatusEnum { + + /** 草稿 - 未发布 */ + DRAFT_UNPUBLISHED("DRAFT_UNPUBLISHED", "草稿-未发布"), + /** 草稿 - 定时待发布 */ + DRAFT_SCHEDULED("DRAFT_SCHEDULED", "草稿-定时待发布"), + /** 已发布 - 进行中 */ + PUBLISHED("PUBLISHED", "已发布-进行中"), + /** 已截止 */ + CLOSED("CLOSED", "已截止"), + /** 已归档(只读,不再触发任何提醒) */ + ARCHIVED("ARCHIVED", "已归档"); + + private final String code; + private final String desc; + + HwStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HwStatusEnum of(String code) { + if (code == null) { + return DRAFT_UNPUBLISHED; + } + for (HwStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return DRAFT_UNPUBLISHED; + } + + public boolean isDraft() { + return this == DRAFT_UNPUBLISHED || this == DRAFT_SCHEDULED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java new file mode 100644 index 0000000..df75af1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 站内通知类型(PRD 六类,存 notification.type)。 + */ +@Getter +public enum NotificationTypeEnum { + + /** 作业发布(对应班级学生) */ + HOMEWORK_PUBLISHED("HOMEWORK_PUBLISHED", "作业发布"), + /** 截止提醒(截止前 1 天 / 1 小时) */ + DEADLINE_REMINDER("DEADLINE_REMINDER", "截止提醒"), + /** 未交提醒(教师手动) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未交提醒"), + /** 未读提醒(教师手动) */ + NOT_READ("NOT_READ", "未读提醒"), + /** 批阅完成 */ + REVIEW_DONE("REVIEW_DONE", "批阅完成"), + /** 打回通知 */ + RETURNED("RETURNED", "打回通知"), + /** + * 定时发布失败回退(PRD §17.1 教师泳道要求通知教师,但 PRD 枚举表未列—— + * 按 classwork-homework-plan-v3 §13-2 建议补充,待产品确认命名)。 + */ + SCHEDULE_FAILED("SCHEDULE_FAILED", "定时发布失败"), + /** + * 截止时间变更(PRD §17.1 延长截止要求通知学生,PRD 枚举表未列——同上补充)。 + */ + DEADLINE_CHANGED("DEADLINE_CHANGED", "截止时间变更"); + + private final String code; + private final String desc; + + NotificationTypeEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static NotificationTypeEnum of(String code) { + if (code == null) { + return null; + } + for (NotificationTypeEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return null; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java new file mode 100644 index 0000000..031eddc --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java @@ -0,0 +1,39 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 批阅状态(PRD 三态,存 homework_submission.review_status)。 + * 与 SubmitStatusEnum 同源联动:批阅保存 → status=REVIEWED + review_status=REVIEWED; + * 打回 → status=RETURNED + review_status=RETURNED。 + */ +@Getter +public enum ReviewStatusEnum { + + /** 待批阅 */ + PENDING("PENDING", "待批阅"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"), + /** 已打回 */ + RETURNED("RETURNED", "已打回"); + + private final String code; + private final String desc; + + ReviewStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static ReviewStatusEnum of(String code) { + if (code == null) { + return PENDING; + } + for (ReviewStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return PENDING; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java new file mode 100644 index 0000000..7d6aaca --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java @@ -0,0 +1,50 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 学生提交状态(PRD 五态,存 homework_submission.status)。 + * + * NOT_SUBMITTED 空行仅由「撤回」产生;未提交学生本身不落行(统计时由名册左连补位)。 + */ +@Getter +public enum SubmitStatusEnum { + + /** 未提交(撤回后产生的空提交行) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未提交"), + /** 已提交(含打回后重新提交) */ + SUBMITTED("SUBMITTED", "已提交"), + /** 逾期提交(截止后提交,is_late=1) */ + LATE_SUBMITTED("LATE_SUBMITTED", "逾期提交"), + /** 已打回(待重新提交) */ + RETURNED("RETURNED", "已打回"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"); + + private final String code; + private final String desc; + + SubmitStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static SubmitStatusEnum of(String code) { + if (code == null) { + return NOT_SUBMITTED; + } + for (SubmitStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return NOT_SUBMITTED; + } + + /** + * 是否计入「已交」口径:打回未重交也算已交(PRD 学生矩阵口径)。 + */ + public boolean countsAsSubmitted() { + return this != NOT_SUBMITTED; + } +} diff --git a/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java new file mode 100644 index 0000000..5377254 --- /dev/null +++ b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java @@ -0,0 +1,161 @@ +package com.yunhai.homework.job; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.enums.HwStatusEnum; +import com.yunhai.homework.enums.NotificationTypeEnum; +import com.yunhai.homework.repository.HomeworkClassRepository; +import com.yunhai.homework.repository.HomeworkRepository; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.HomeworkStateService; +import com.yunhai.homework.service.impl.NotificationService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 课堂作业定时任务(PRD §17.1 调度泳道)。 + * + * 全部基于状态机 CAS 条件更新,天然幂等,可安全重复执行; + * ARCHIVED 作业被显式跳过(归档后不再触发任何提醒)。 + */ +@Component +public class HomeworkScheduledJobs { + + private static final Logger log = LoggerFactory.getLogger(HomeworkScheduledJobs.class); + + @Autowired + private HomeworkRepository homeworkRepository; + + @Autowired + private HomeworkClassRepository homeworkClassRepository; + + @Autowired + private HomeworkStateService stateService; + + @Autowired + private NotificationService notificationService; + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + /** + * 定时发布:每分钟扫描到点的 DRAFT_SCHEDULED 作业 → PUBLISHED + 作业发布通知; + * 失败回退 DRAFT_UNPUBLISHED 并站内通知教师。 + */ + @Scheduled(cron = "0 * * * * ?") + public void scheduledPublish() { + List due = homeworkRepository + .findByHwStatusAndTimingSendTimeLessThanEqual(HwStatusEnum.DRAFT_SCHEDULED.getCode(), + LocalDateTime.now()); + if (due.isEmpty()) { + return; + } + log.info("[scheduledPublish] 到点作业 {} 个", due.size()); + for (Homework homework : due) { + Integer id = homework.getId(); + try { + teacherHomeworkV2Service.publishInternal(id, HwStatusEnum.DRAFT_SCHEDULED); + log.info("[scheduledPublish] 发布成功 homeworkId={}", id); + } catch (Exception e) { + log.error("[scheduledPublish] 发布失败,回退草稿 homeworkId={}", id, e); + try { + boolean rolledBack = stateService.rollbackFromSchedule(id); + if (rolledBack) { + notificationService.send(NotificationTypeEnum.SCHEDULE_FAILED, id, null, + homework.getPublisherId(), homework.getPublisherName(), + NotificationService.ROLE_TEACHER, + "定时发布失败:《" + homework.getTitle() + "》", + "定时发布任务执行异常,作业已回退为「草稿-未发布」,请重新发布。"); + } + } catch (Exception ex) { + log.error("[scheduledPublish] 回退失败 homeworkId={}", id, ex); + } + } + } + } + + /** + * 到点截止:每分钟扫描已过截止时间的 PUBLISHED 作业 → CLOSED(无对应通知类型,不推送)。 + */ + @Scheduled(cron = "30 * * * * ?") + public void deadlineClose() { + List overdue = homeworkRepository + .findByHwStatusAndDeadlineAtBefore(HwStatusEnum.PUBLISHED.getCode(), LocalDateTime.now()); + for (Homework homework : overdue) { + if (stateService.cas(homework.getId(), HwStatusEnum.PUBLISHED, HwStatusEnum.CLOSED)) { + log.info("[deadlineClose] 作业已截止 homeworkId={}", homework.getId()); + } + } + } + + /** + * 截止提醒:每分钟扫描截止时间在 24h 内的 PUBLISHED 作业, + * 命中「1 天档 / 1 小时档」窗口且开关开启时向未交学生推送 DEADLINE_REMINDER; + * 档位去重(related_id:1=1 天档 2=1 小时档),每作业每档至多发一轮。 + */ + @Scheduled(cron = "40 * * * * ?") + public void deadlineReminder() { + LocalDateTime now = LocalDateTime.now(); + List window = homeworkRepository + .findByHwStatusAndDeadlineAtBetween(HwStatusEnum.PUBLISHED.getCode(), now, now.plusHours(24)); + for (Homework homework : window) { + if (homework.getDeadlineAt() == null) { + continue; + } + // 1 小时档:截止前 [55, 60) 分钟窗口 + if (homework.getRemind1h() != null && homework.getRemind1h() == 1 + && inWindow(homework.getDeadlineAt(), now, 55, 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 2)) { + sendReminder(homework, 2, "1 小时"); + } + // 1 天档:截止前 [23, 24) 小时窗口 + if (homework.getRemind1d() != null && homework.getRemind1d() == 1 + && inWindow(homework.getDeadlineAt(), now, 23 * 60, 24 * 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 1)) { + sendReminder(homework, 1, "1 天"); + } + } + } + + private boolean inWindow(LocalDateTime deadline, LocalDateTime now, int fromMinutes, int toMinutes) { + long minutes = java.time.Duration.between(now, deadline).toMinutes(); + return minutes >= fromMinutes && minutes < toMinutes; + } + + private void sendReminder(Homework homework, int slot, String slotDesc) { + // 通知接收人直接使用学生ID(名册口径:student_id = 平台用户ID) + List targets = teacherHomeworkV2Service.notSubmittedStudentIds(homework.getId()).stream() + .distinct() + .collect(Collectors.toList()); + if (targets.isEmpty()) { + return; + } + int sent = notificationService.sendBatch(NotificationTypeEnum.DEADLINE_REMINDER, homework.getId(), slot, + NotificationService.distinct(targets), + NotificationService.deadlineReminderTitle(homework), + NotificationService.deadlineReminderContent(homework) + "(剩余不足 " + slotDesc + ")"); + log.info("[deadlineReminder] homeworkId={}, slot={}, sent={}", homework.getId(), slot, sent); + } + + /** + * 名册同步:每日 02:00 全量刷新 class_student(bschclass 接口就绪前仅日志)。 + */ + @Scheduled(cron = "${homework.sync.class-data-cron:0 0 2 * * ?}") + public void classDataSync() { + try { + classStudentService.syncAll(); + } catch (Exception e) { + log.error("[classDataSync] 名册同步失败", e); + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageQuery.java b/src/main/java/com/yunhai/homework/model/BasePageQuery.java new file mode 100644 index 0000000..7f46326 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageQuery.java @@ -0,0 +1,14 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +/** + * 统一分页查询入参。pageIndex 从 1 起。 + */ +@Data +public class BasePageQuery { + private int pageIndex = 1; + private int pageSize = 10; + /** 通用模糊搜索关键词 */ + private String keyword; +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageResult.java b/src/main/java/com/yunhai/homework/model/BasePageResult.java new file mode 100644 index 0000000..058c96c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageResult.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +import java.util.List; + +/** + * 统一分页响应外壳。 + */ +@Data +public class BasePageResult { + /** 总记录数 */ + private int total; + /** 当前页数据 */ + private List content; + /** 当前页码(从 1 起) */ + private int pageIndex; + /** 每页条数 */ + private int pageSize; + /** 总页数 */ + private int totalPages; + /** 是否还有下一页 */ + private boolean hasNext; + + public BasePageResult(List content, int total, int pageIndex, int pageSize, int totalPages, boolean hasNext) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + } +} diff --git a/src/main/java/com/yunhai/homework/model/SchTeaList.java b/src/main/java/com/yunhai/homework/model/SchTeaList.java new file mode 100644 index 0000000..80f9b9c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/SchTeaList.java @@ -0,0 +1,106 @@ +package com.yunhai.homework.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * 班级管理接口-教师(bschclass {@code /oapi/DbClass/GetSchteaList} 响应项)。 + * 对应 C# BestSchool.Entity.Model.SchTeaList。 + */ +public class SchTeaList { + + /** 学校 GUID(SchSerID) */ + @JsonProperty("SchSerID") + private String schSerID; + + @JsonProperty("TeaTel") + private String teaTel; + + /** 教师姓名 */ + @JsonProperty("TeaName") + private String teaName; + + /** 教师用户ID */ + @JsonProperty("UserTeaSerID") + private Integer userTeaSerID; + + @JsonProperty("TeaIDCard") + private String teaIDCard; + + /** 教师工号 */ + @JsonProperty("TeaNo") + private String teaNo; + + /** 教师所属组ID列表(兼容旧字段 GroupIds) */ + @JsonProperty("GroupIds") + private List groupIds; + + /** 教师所属组ID(接口现行字段 GroupID,单个) */ + @JsonProperty("GroupID") + private Integer groupId; + + public Integer getGroupId() { + return groupId; + } + + public void setGroupId(Integer groupId) { + this.groupId = groupId; + } + + public String getSchSerID() { + return schSerID; + } + + public void setSchSerID(String schSerID) { + this.schSerID = schSerID; + } + + public String getTeaTel() { + return teaTel; + } + + public void setTeaTel(String teaTel) { + this.teaTel = teaTel; + } + + public String getTeaName() { + return teaName; + } + + public void setTeaName(String teaName) { + this.teaName = teaName; + } + + public Integer getUserTeaSerID() { + return userTeaSerID; + } + + public void setUserTeaSerID(Integer userTeaSerID) { + this.userTeaSerID = userTeaSerID; + } + + public String getTeaIDCard() { + return teaIDCard; + } + + public void setTeaIDCard(String teaIDCard) { + this.teaIDCard = teaIDCard; + } + + public String getTeaNo() { + return teaNo; + } + + public void setTeaNo(String teaNo) { + this.teaNo = teaNo; + } + + public List getGroupIds() { + return groupIds; + } + + public void setGroupIds(List groupIds) { + this.groupIds = groupIds; + } +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java new file mode 100644 index 0000000..cf9c417 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 班级 DTO(/oapi/VSchClass/GetVClassList,SchClass 视图)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchClassDto { + + /** 班级业务ID(sch_class.class_id,scopeType=班级 的 scopeId;学生 ClassID 指向它) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级名称 */ + @JsonProperty("ClassName") + private String className; + + /** 年级ID(= gradeId) */ + @JsonProperty("Levell") + private Integer gradeId; + + /** 排序号 */ + @JsonProperty("sx") + private Integer sort; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java new file mode 100644 index 0000000..01ff15b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 年级 DTO(/oapi/Schoolen/GetSchLenLevellist,BaseSchsystemGrade)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchGradeDto { + + /** 年级ID(=gradeId,scopeType=年级 的 scopeId;班级 Levell 指向它) */ + @JsonProperty("Id") + private Integer id; + + /** 年级名称(gradeNickname,如「初一」) */ + @JsonProperty("Name") + private String name; + + /** 所属学段(学制)ID */ + @JsonProperty("OwnerId") + private Integer ownerId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java new file mode 100644 index 0000000..06185b6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校信息 DTO(/oapi/SchInfo/GetUserSchFirst)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchInfoDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerId") + private String schSerId; + + /** 用户中心学校组ID(与 CampusNotice schId 同域) */ + @JsonProperty("UserSchSerId") + private Integer userSchSerId; + + /** 学校名称 */ + @JsonProperty("SchName") + private String schName; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java new file mode 100644 index 0000000..f96913d --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学生 DTO(/oapi/OAStu/*,SchStudent 视图)。 + * account 仅为上游接口契约字段(兼容反序列化),本系统落库/身份映射不再使用; + * 学生身份以 stuId(UserStuSerID,= 平台用户ID)+ stuName 承载。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchStudentDto { + + /** 学员登录账号(上游契约字段,仅兼容保留,不落库) */ + @JsonProperty("Account") + private String account; + + /** 学生ID */ + @JsonProperty("UserStuSerID") + private Integer stuId; + + /** 学生姓名 */ + @JsonProperty("StuName") + private String stuName; + + /** 所在班级ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 所在组ID */ + @JsonProperty("GroupID") + private Integer groupId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java new file mode 100644 index 0000000..ff7add8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校学科 DTO(/oapi/SubInfo/GetSchSublist)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSubjectDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 学科ID(学科编码;字典无编码时为字典ID字符串) */ + @JsonProperty("SubID") + private String subId; + + /** 学科名称 */ + @JsonProperty("SubName") + private String subName; + + /** 排序号 */ + @JsonProperty("No") + private Integer no; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java new file mode 100644 index 0000000..f0df157 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学段(学制)DTO(/oapi/Schoolen/GetSchLenlist,BaseSchsystem)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSystemDto { + + /** 学制ID(scopeType=学部 的 scopeId) */ + @JsonProperty("Id") + private Integer id; + + /** 学段名称(如「小学部」「初中部」) */ + @JsonProperty("Name") + private String name; + + /** 学制类型 */ + @JsonProperty("Type") + private Integer type; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java new file mode 100644 index 0000000..86ec6f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Date; + +/** + * bschclass 任教关系 DTO(/oapi/TeachInfo/*,sch_class_teach 视图)。 + * 一条记录 = 某学校某班级中某教师任教某学科。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTeachDto { + + /** 任教记录ID(sch_class_teach.id) */ + @JsonProperty("SerID") + private Integer serId; + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 教师ID(= 平台用户ID) */ + @JsonProperty("UserTeaSerID") + private Integer teaId; + + /** 学科ID(学科编码) */ + @JsonProperty("SubID") + private String subId; + + /** 班级业务ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级ID(与 ClassID 同值,上游兼容字段) */ + @JsonProperty("UserClassID") + private Integer userClassId; + + /** 创建时间 */ + @JsonProperty("CreationTime") + private Date creationTime; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java new file mode 100644 index 0000000..29623ad --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java @@ -0,0 +1,41 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * bschclass 组织树节点 DTO(/oapi/SchGroup/GetTeaTree、GetStuTree,TeaTreeVO 视图)。 + * type=1 组节点(学校/学段/年级/班级、教师组),type=2 人员叶子(学生/教师)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTreeNodeDto { + + /** 节点ID(学校/学段/年级/班级为对应业务ID,叶子为学生 stuId 或教师 teaId) */ + private Integer id; + + /** 节点名称 */ + private String name; + + /** 父节点ID(根节点为 0) */ + private Integer pid; + + /** 排序号(组取 sort,叶子固定 0) */ + private Integer no; + + /** 1=组节点,2=人员叶子 */ + private Integer type; + + /** 是否父节点(组 true,叶子 false) */ + @JsonProperty("isParent") + private Boolean isParent; + + /** 头像(当前固定空串) */ + private String photo; + + /** 子节点(叶子为 null,组节点非 null) */ + private List children; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java new file mode 100644 index 0000000..7621af2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java @@ -0,0 +1,81 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 作业详情视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class HomeworkDetailVo { + + @JsonProperty("serid") + private Integer serid; + + @JsonProperty("title") + private String title; + + @JsonProperty("Contents") + private String contents; + + @JsonProperty("DeptID") + private String deptId; + + @JsonProperty("DeptName") + private String deptName; + + @JsonProperty("TeaName") + private String publisherName; + + @JsonProperty("EditDate") + private String editDate; + + @JsonProperty("numcount") + private Integer numCount; + + @JsonProperty("readnumcount") + private Long readNumCount; + + @JsonProperty("ReceiveList") + private String receiveList; + + @JsonProperty("DsfsTime") + private String dsfsTime; + + @JsonProperty("isDsfs") + private Boolean isDsfs; + + @JsonProperty("ChaoSend") + private List chaoSend; + + /** 接收人列表:"all" 或用户ID列表 */ + @JsonProperty("tlist") + private Object tlist; + + /** + * 接收对象可读文本("全部班级" 或班级名顿号串)。 + * 详情页「接收班级」标签直接展示,避免前端拿 ReceiveList 的 id 串渲染。 + */ + @JsonProperty("receiveText") + private String receiveText; + + /** + * 附件项(旧 ChaoSend 数组元素)。 + */ + @Data + public static class FileItem { + @JsonProperty("id") + private Integer id; + @JsonProperty("name") + private String name; + @JsonProperty("size") + private Integer size; + @JsonProperty("fileid") + private Integer fileid; + @JsonProperty("filename") + private String filename; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java new file mode 100644 index 0000000..e1c2fa2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 作业分页结果(扩展 BasePageResult,额外携带 unreadCount)。 + * + * 对齐方案 M7:unreadCount 并入分页外壳扩展子类,不改基类契约。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkPageResult extends BasePageResult { + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkPageResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + super(content, total, pageIndex, pageSize, totalPages, hasNext); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java new file mode 100644 index 0000000..afea9a6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +/** + * 作业列表查询条件。 + * + * - 管理端(adminList):用 status / type / deptId / publisherId / 时间范围过滤; + * - 用户端(userList):额外用 readType 过滤未读/已读(0 全部 / 1 未读 / 2 已读)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkQuery extends BasePageQuery { + + /** 状态过滤:null 不过滤;1 已发布 */ + private Integer status; + + /** 类型过滤:1 普通 / 2 重要 */ + private Integer type; + + /** 列表范围(管理端):"user" 只看自己发布的,null 看全部 */ + private String scope; + + /** 发布部门ID */ + private String deptId; + + /** 发布人ID(用户中心数字ID,管理端按发布人查) */ + private Integer publisherId; + + /** 开始时间(创建时间 >=,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startTime; + + /** 结束时间(创建时间 <= 当天 23:59:59,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endTime; + + /** 阅读状态过滤(仅用户端):0 全部 / 1 未读 / 2 已读 */ + private Integer readType = 0; + + /** 排序:1 时间倒序(默认)/ 2 时间正序 */ + private String orderByTime = "1"; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java new file mode 100644 index 0000000..2cb560f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java @@ -0,0 +1,65 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import com.yunhai.homework.common.validation.ValidationGroups; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 作业发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。 + */ +@Data +public class HomeworkRequest { + + /** 作业标题 */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业标题不得为空") + private String title; + + /** 作业内容 */ + private String content; + + /** 作业类型:1 普通 / 2 重要 */ + @NotNull(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业类型不得为空") + private Integer type = 1; + + /** 发布部门ID(原 DeptID,必填——对应原系统「发布部门不得为空」校验) */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "发布部门不得为空") + private String deptId; + + /** 发布部门名称 */ + private String deptName; + + /** 是否置顶:0 否 / 1 是 */ + private Integer topmost = 0; + + /** 接收人列表(JSON 数组字符串,如 ["10012","10013"]——用户中心数字ID,或 "ALL" 表示全员) */ + private String receiveList; + + /** 附件文件ID列表(逗号分隔串,如 "1,2,3");旧名 chaoSend,已更名 fileIds(M6) */ + private String fileIds; + + /** 定时发送时间(为空表示立即发布) */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timingSendTime; + + /** 短信发送标记:0 不发短信 / 1 发短信(旧名 topmost 实指发短信,已正名 smsFlag,M6) */ + private Integer smsFlag = 0; + + /** 转发源作业ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */ + private Integer sourceId; + + // ==================== 课堂作业 V1/V2 扩展(前端契约字段,兼容层同步写新列) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deadline; + + /** 提交方式:text/image/attach/none 组合 */ + private java.util.List submitType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java new file mode 100644 index 0000000..c492f86 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java @@ -0,0 +1,113 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 作业统计视图(对应原系统 GetStatisData / 按发布人统计)。 + */ +@Data +public class HomeworkStatisticsVo { + + /** 作业总数(全部已发布) */ + private long total; + + /** 已发布数 */ + private long publishedCount; + + /** 阅读记录总数(累计已读人次) */ + private long totalReadRecords; + + /** 当前用户未读数(已到点的已发布 - 当前用户已读) */ + private long unreadForCurrentUser; + + /** 按发布人统计 */ + private List byPublisher; + + /** 按月统计(已发布作业数) */ + private List byMonth; + + // ==================== 课程作业口径(前端 /stats 页消费) ==================== + + /** 我布置的作业数(当前登录人发布的全部作业,含草稿) */ + private long myAssignCount; + + /** 我的作业待批改总量(已交未批的当前版本行合计) */ + private long pendingGrade; + + /** 各作业平均提交率(%,仅统计需收集提交的作业;无样本时为 0) */ + private double avgSubmitRate; + + /** 按作业的提交与批改进度明细(草稿与仅查阅型不计) */ + private List byHomework = new ArrayList<>(); + + /** 已批阅提交的分数分布(四档) */ + private List scoreDist = new ArrayList<>(); + + /** 单作业进度条目(口径对齐前端 HomeworkProgress 契约) */ + @Data + public static class HomeworkStat { + private Integer id; + private String title; + private String subject; + /** 旧契约四态派生值:draft / collecting / closed / done */ + private String hwStatus; + private String hwStatusDesc; + /** 应交人数 */ + private long should; + /** 已读人数 */ + private long read; + /** 已交人数 */ + private long submitted; + /** 已批阅人数 */ + private long graded; + /** 待批改人数 */ + private long pendingGrade; + /** 未交人数 */ + private long unsubmitted; + /** 提交率(%) */ + private double submitRate; + /** 批阅率(%) */ + private double gradeRate; + } + + /** 分数分布档(key 供前端配色映射:excellent/good/pass/fail) */ + @Data + public static class ScoreBucket { + private String label; + private String key; + private long count; + + public ScoreBucket(String label, String key, long count) { + this.label = label; + this.key = key; + this.count = count; + } + } + + @Data + public static class PublisherStat { + private String publisherName; + private long count; + + public PublisherStat(String publisherName, long count) { + this.publisherName = publisherName; + this.count = count; + } + } + + @Data + public static class MonthStat { + private int year; + private int month; + private long count; + + public MonthStat(int year, int month, long count) { + this.year = year; + this.month = month; + this.count = count; + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java new file mode 100644 index 0000000..c906e2e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.List; + +/** + * 用户端作业列表结果:在分页数据之外额外携带「当前用户未读总数」, + * 供前端展示未读角标(对应原系统 OtherData.unReadCount)。 + */ +@Data +public class HomeworkUserListResult { + + private int total; + private List content; + private int pageIndex; + private int pageSize; + private int totalPages; + private boolean hasNext; + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkUserListResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java new file mode 100644 index 0000000..333d49e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java @@ -0,0 +1,92 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 作业视图对象(列表/详情返回)。 + */ +@Data +public class HomeworkVo { + + private Integer id; + private String title; + private String content; + private Integer type; + private String typeDesc; + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + private String publisherName; + private String deptId; + private String deptName; + private Integer status; + private String statusDesc; + private Integer topmost; + + /** 已读人数(基于阅读记录实时统计) */ + private Long readCount = 0L; + + /** 应读人数 */ + private Integer allowCount; + + /** 当前用户是否已读(用户端列表使用;管理端恒为 false 或不关注) */ + private boolean read = false; + + /** 是否撤回:0 否 / 1 是 */ + private Integer isPulled; + + /** 定时发送时间 */ + private LocalDateTime timingSendTime; + + /** 父作业ID(转发时关联原作业) */ + private Integer parentId; + + /** 短信发送标记 */ + private Integer smsFlag; + + /** 接收人列表(JSON) */ + private String receiveList; + + /** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */ + private String UpFile; + + // ==================== 课堂作业 V1/V2 扩展(前端 types/homework.ts 契约) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间(前端契约字段名 deadline) */ + private LocalDateTime deadline; + + /** 提交方式数组:text/image/attach/none */ + private java.util.List submitType; + + /** 作业流程状态——旧契约四态派生值(draft/collecting/closed/done),权威五态见 homework.hw_status */ + private String hwStatus; + + private String hwStatusDesc; + + /** + * 提交/批改进度(管理端列表页「提交进度 / 未读未交 / 批改」列使用)。 + * 草稿或仅查阅型为 null;列表场景由 HomeworkProgressCalculator 批量聚合回填。 + */ + private com.yunhai.homework.model.v2.ProgressVo progress; + + private String createBy; + private LocalDateTime createTime; + private String updateBy; + private LocalDateTime updateTime; + + /** + * 阅读明细视图(阅读列表查询用)。 + */ + @Data + public static class ReadDetail { + /** 读者ID(用户中心数字ID) */ + private Integer id; + private String name; + private boolean read; + private LocalDateTime readTime; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java new file mode 100644 index 0000000..15ece01 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 一键已读结果视图对象(替代原 Map<String,Object> 返回)。 + */ +@Data +public class InitializeRedVo { + + @JsonProperty("count") + private Integer count; + + public InitializeRedVo() {} + + public InitializeRedVo(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java new file mode 100644 index 0000000..8127480 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java @@ -0,0 +1,30 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 文件上传结果视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class UpFileResultVo { + + @JsonProperty("fileid") + private Integer fileid; + + @JsonProperty("name") + private String name; + + @JsonProperty("fileurl") + private String fileurl; + + @JsonProperty("host") + private String host; + + @JsonProperty("type") + private String type; + + @JsonProperty("size") + private Integer size; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java new file mode 100644 index 0000000..76dfa2f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +/** + * 附件视图对象(对应原 .NET FileObj)。 + */ +@Data +public class UpFileVo { + + /** 文件记录ID */ + private Integer id; + + /** 文件真实名称 */ + private String fileTrueName; + + /** 文件存储路径(相对路径) */ + private String fileName; + + /** 文件大小(字节) */ + private Integer fileSize; + + /** 文件后缀名 */ + private String fileType; + + /** 关联主题ID */ + private Integer topicId; + + /** 应用类型 */ + private String topicType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java new file mode 100644 index 0000000..771eb3a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java @@ -0,0 +1,122 @@ +package com.yunhai.homework.model.homework; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户可访问模块(左菜单)视图对象。 + * 将基础平台返回的扁平 {@code List} 转换为前端 layout(参考 UserBase)期望的树结构: + * {@code modId}(路由高亮匹配)/{@code page}(跳转路径)/{@code children}(子菜单)。 + */ +public class UserModuleVo { + + /** 模块主键 id(基础平台 module id) */ + private Integer id; + + /** 菜单高亮匹配用:等价于后端模块 id */ + private Integer modId; + + /** 模块名称 */ + private String name; + + /** 父级模块 id(0 表示根节点) */ + private Integer parentId; + + /** 前端路由路径(由 BoaModule.path 映射而来) */ + private String page; + + /** 层级(0=根分组) */ + private Integer level; + + /** 菜单图标 */ + private String icon; + + /** 排序号 */ + private Integer orderNumber; + + /** 是否可用 */ + private Boolean usable; + + /** 子菜单 */ + private List children = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getModId() { + return modId; + } + + public void setModId(Integer modId) { + this.modId = modId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Integer getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(Integer orderNumber) { + this.orderNumber = orderNumber; + } + + public Boolean getUsable() { + return usable; + } + + public void setUsable(Boolean usable) { + this.usable = usable; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java new file mode 100644 index 0000000..bedf7b5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 附件条目(作业附件与提交附件共用)。fileUrl 必传;fileId 为 file/Upload 产物时回传。 + */ +@Data +public class AttachmentItem { + + /** up_file.id */ + private Integer fileId; + + /** 文件访问 URL */ + private String fileUrl; + + /** 文件类型:image / file */ + private String fileType; + + /** 文件名 */ + private String fileName; + + /** 排序号 */ + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassItem.java b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java new file mode 100644 index 0000000..459ea80 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java @@ -0,0 +1,18 @@ +package com.yunhai.homework.model.v2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 班级条目(作业参与班级展示)。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClassItem { + + private String classId; + + private String className; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java new file mode 100644 index 0000000..c0f400b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 年级→班级树(发布作业选班 + 应交名单基数来源)。 + * 叶子节点 id = 班级 id(class_student 快照聚合)。 + */ +@Data +public class ClassTreeNode { + + /** 年级节点为 gradeName,班级叶子为 classId */ + private String id; + + /** 展示名 */ + private String name; + + /** 是否父节点(年级) */ + private Boolean isParent; + + /** 叶子节点:在班学生数 */ + private Integer studentCount; + + private List children = new ArrayList<>(); +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java new file mode 100644 index 0000000..f8c5dec --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 延长截止请求(新截止时间必须晚于当前时间)。 + */ +@Data +public class ExtendRequest { + + @NotNull(message = "新截止时间不得为空") + private LocalDateTime deadlineAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java new file mode 100644 index 0000000..1ac46f8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java @@ -0,0 +1,36 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 作业详情汇总统计卡片(PRD §18 页 4 stats 接口出参)。 + */ +@Data +public class HomeworkStatsVo { + + private Integer homeworkId; + + private String title; + + private String hwStatus; + + private String hwStatusDesc; + + /** 总进度 */ + private ProgressVo total; + + /** 分班进度 */ + private List byClass; + + @Data + public static class ByClass { + + private String classId; + + private String className; + + private ProgressVo progress; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java new file mode 100644 index 0000000..9464910 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 创建/编辑作业请求(PRD §18 页 1 表单)。 + * 创建一律落草稿(DRAFT_UNPUBLISHED),发布/定时走独立接口。 + */ +@Data +public class HomeworkV2Request { + + /** 作业标题(必填 ≤50 字) */ + @NotBlank(message = "作业标题不得为空") + @Size(max = 50, message = "作业标题不超过50字") + private String title; + + /** 学科(字典值) */ + private String subject; + + /** 作业说明(富文本) */ + private String description; + + /** 参与班级 id 列表 */ + private List classIds; + + /** 附件列表 */ + private List attachments; + + /** 截止时间(精确到分钟;空 = 仅查阅型) */ + private LocalDateTime deadlineAt; + + /** 提交方式:image / attach / text / none 的组合(PRD 图片/文件/两者) */ + private List submitType; + + /** 截止后是否允许逾期提交:0 否 / 1 是 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天提醒(0 关 / 1 开) */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时提醒(0 关 / 1 开) */ + private Integer remind1h; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java new file mode 100644 index 0000000..8bf1b02 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java @@ -0,0 +1,73 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 作业视图对象 V2(草稿箱/已发布列表/详情共用)。 + * 列表场景 attachments/classes 可按需填充;progress 仅非草稿且非仅查阅型作业返回。 + */ +@Data +public class HomeworkV2Vo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 作业说明(富文本;列表场景可置空省流) */ + private String description; + + /** 作业状态(PRD 五态) */ + private String hwStatus; + + private String hwStatusDesc; + + /** 定时发布时间(复用 timing_send_time) */ + private LocalDateTime scheduledAt; + + /** 发布时间 */ + private LocalDateTime publishedAt; + + /** 截止时间 */ + private LocalDateTime deadlineAt; + + /** 归档时间 */ + private LocalDateTime archivedAt; + + /** 提交方式数组 */ + private List submitType; + + /** 截止后允许逾期提交:0/1 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天 */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时 */ + private Integer remind1h; + + /** 参与班级 */ + private List classes; + + /** 附件列表(详情场景填充) */ + private List attachments; + + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + + /** 发布人姓名 */ + private String publisherName; + + /** 创建/更新时间(审计) */ + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + /** 汇总进度(草稿或仅查阅型为 null) */ + private ProgressVo progress; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java new file mode 100644 index 0000000..0303eb8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知分页结果(含当前用户未读数)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationPageResult extends BasePageResult { + + /** 当前用户未读通知总数 */ + private long unreadCount; + + public NotificationPageResult(BasePageResult page, long unreadCount) { + super(page.getContent(), page.getTotal(), page.getPageIndex(), page.getPageSize(), + page.getTotalPages(), page.isHasNext()); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java new file mode 100644 index 0000000..f8af4a7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知列表查询。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationQuery extends BasePageQuery { + + /** 类型过滤(NotificationTypeEnum code) */ + private String type; + + /** 已读过滤:0 未读 / 1 已读(空 = 全部) */ + private Integer isRead; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java new file mode 100644 index 0000000..2a9b61a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知列表项。 + */ +@Data +public class NotificationVo { + + private Integer id; + + /** 通知类型 */ + private String type; + + private String typeDesc; + + private String title; + + private String content; + + private Integer homeworkId; + + private Integer relatedId; + + /** 是否已读:0/1 */ + private Integer isRead; + + private LocalDateTime readAt; + + private LocalDateTime createTime; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java new file mode 100644 index 0000000..201a460 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 提交/批阅/阅读进度(PRD §18 页 3 汇总口径)。 + * should(应交)为分母;打回未重交计入已交。 + */ +@Data +public class ProgressVo { + + /** 应交人数(参与班级在班学生去重) */ + private int should; + + /** 已读人数 */ + private int read; + + /** 已交人数(含逾期/打回/已批阅) */ + private int submitted; + + /** 已批阅人数 */ + private int graded; + + /** 待批阅人数(submitted - graded) */ + private int pendingGrade; + + /** 未交人数 */ + private int unsubmitted; + + /** 未读人数 */ + private int unread; + + /** 未读且未交人数(高亮口径:should - 已读∪已交) */ + private int unreadUndone; + + /** 阅读率(百分数,一位小数) */ + private double readRate; + + /** 提交率(百分数,一位小数) */ + private double submitRate; + + /** 批阅率(已批阅 / 已交,百分数一位小数;submitted=0 时为 0) */ + private double gradeRate; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java new file mode 100644 index 0000000..a97e0c1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 教师手动提醒请求:type = NOT_READ / NOT_SUBMITTED,studentIds 为空时提醒全部未读/未交学生。 + */ +@Data +public class RemindRequest { + + /** 提醒类型:NOT_READ / NOT_SUBMITTED */ + @NotBlank(message = "提醒类型不得为空") + private String type; + + /** 定向学生ID(用户中心数字ID;空 = 全部未读/未交) */ + private List studentIds; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java new file mode 100644 index 0000000..45d3f3e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 打回请求(PRD 页 6)。comment 为订正原因,必填。 + */ +@Data +public class ReturnRequest { + + @NotBlank(message = "打回须填写订正原因") + private String comment; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java new file mode 100644 index 0000000..166b2e1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 批阅保存请求(PRD 页 6)。score 与 comment 至少一项有值。 + */ +@Data +public class ReviewRequest { + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 圈划标注图片(P2,先收参) */ + private List markedImages; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java new file mode 100644 index 0000000..894b433 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 定时发布 / 取消定时请求。 + * cancel=true 时忽略 scheduledAt,作业回退草稿-未发布并清空定时时间。 + */ +@Data +public class ScheduleRequest { + + /** 定时发布时间(cancel=false 时必填且须晚于当前时间) */ + private LocalDateTime scheduledAt; + + /** 是否取消定时 */ + private Boolean cancel = false; + + @NotNull + public Boolean getCancel() { + return cancel; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java new file mode 100644 index 0000000..6372883 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 学生端作业详情(PRD §18 学生页 2)。进入详情后由前端停留 ≥3 秒再调 read 上报。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentHomeworkDetailVo extends StudentHomeworkVo { + + /** 作业说明(富文本) */ + private String description; + + /** 作业附件 */ + private List attachments; + + /** 参与班级(学生所属班级视角) */ + private List classes; + + /** 我当前版本的提交附件(未提交为 null) */ + private List myAttachments; + + /** 我的文本内容(当前版本) */ + private String myText; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java new file mode 100644 index 0000000..9d30037 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学生端作业列表项(PRD §18 学生页 1)。 + */ +@Data +public class StudentHomeworkVo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 任课教师(发布人姓名) */ + private String teacherName; + + /** 截止时间(前端做倒计时) */ + private LocalDateTime deadlineAt; + + /** 提交方式 */ + private java.util.List submitType; + + /** 作业状态 */ + private String hwStatus; + + private String hwStatusDesc; + + /** 截止后是否允许逾期补交:0 否 / 1 是(学生端据此决定是否渲染「补交」入口) */ + private Integer allowLate; + + /** 我的提交概要(未提交为 null) */ + private MySubmission my; + + @Data + public static class MySubmission { + + /** 当前版本提交行ID */ + private Integer submissionId; + + /** 提交状态(PRD 五态) */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 版本号 */ + private Integer version; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分(已批阅才有) */ + private java.math.BigDecimal score; + + /** 评语 */ + private String comment; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java new file mode 100644 index 0000000..49e3144 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学生矩阵查询(PRD §18 页 5)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentMatrixQuery extends BasePageQuery { + + /** 班级筛选(空 = 全部参与班级) */ + private String classId; + + /** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */ + private String submitStatus; + + /** 批阅状态筛选:PENDING/REVIEWED/RETURNED */ + private String reviewStatus; + + /** 只看待批阅(reviewStatus=PENDING 且已提交)快捷过滤 */ + private Boolean pendingGrade; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java new file mode 100644 index 0000000..6257285 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java @@ -0,0 +1,57 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学生矩阵行(PRD §18 页 5)。未交学生也生成占位行(submissionId=0)。 + */ +@Data +public class StudentMatrixVo { + + /** 当前版本提交行ID(未交占位为 0) */ + private Integer submissionId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + /** 姓名 */ + private String studentName; + + private String classId; + + private String className; + + /** 阅读状态:0 未读 / 1 已读 */ + private Integer isRead; + + /** 首次阅读时间 */ + private LocalDateTime firstReadAt; + + /** 最近阅读时间 */ + private LocalDateTime lastReadAt; + + /** 提交状态(PRD 五态;未交为 NOT_SUBMITTED) */ + private String submitStatus; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期:0/1 */ + private Integer isLate; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分 */ + private BigDecimal score; + + /** 最新提交附件 URL 列表(缩略图) */ + private List thumbnails; + + /** 提交内容类型摘要:image=拍照/图片、attach=上传附件、text=文字作答(未交为空列表) */ + private List contentTypes; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java new file mode 100644 index 0000000..f4d9cd1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 学生查看批阅结果(PRD §18 学生页 2 result 接口)。 + */ +@Data +public class StudentResultVo { + + private Integer homeworkId; + + private String homeworkTitle; + + /** 当前版本批阅结果(未批阅为 null) */ + private SubmissionVersionVo latest; + + /** 全部版本(含各版评语,新→旧) */ + private List versions; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java new file mode 100644 index 0000000..1fa7545 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 学生提交作业请求(PRD §18 学生页 3)。附件只接受 file/Upload 产物(fileId 或平台 URL),拒绝 dataURL。 + */ +@Data +public class StudentSubmitRequest { + + /** 文本内容(submitType 含 text 时有效) */ + @Size(max = 20000, message = "文本内容过长") + private String text; + + /** 附件列表(图片/文件) */ + private List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java new file mode 100644 index 0000000..6c576d9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 学科 DTO(布置过的学科 / 我的任教学科共用返回结构)。 + */ +@Data +public class SubjectDto { + + /** 学科编码(homework.subject 字典值 / bschclass SubID) */ + private String subId; + + /** 学科名称(本校学科字典解析;字典缺失时回退为编码) */ + private String subName; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java new file mode 100644 index 0000000..cac00dc --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 撤回重交动作(PUT /student/homeworks/{id}/submissions/{submissionId})。 + * withdraw = 撤回(截止前,产生 NOT_SUBMITTED 新版本行);resubmit = 打回后重交(走提交语义)。 + */ +@Data +public class SubmissionActionRequest { + + @NotBlank(message = "action 不得为空") + private String action; + + /** resubmit 时的提交内容(等同 StudentSubmitRequest) */ + private String text; + + /** resubmit 时的附件 */ + private java.util.List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java new file mode 100644 index 0000000..bff2972 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java @@ -0,0 +1,63 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 提交版本条目(历史版本/批阅弹窗/学生结果页共用)。 + */ +@Data +public class SubmissionVersionVo { + + private Integer submissionId; + + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + private String studentName; + + /** 版本号 */ + private Integer version; + + /** 是否当前版本:0/1 */ + private Integer isLatest; + + /** 提交状态 */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 文本内容 */ + private String text; + + /** 附件 */ + private List attachments; + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 批阅状态 */ + private String reviewStatus; + + private String reviewStatusDesc; + + /** 批阅人(展示姓名快照,缺失时回退ID) */ + private String reviewedBy; + + /** 批阅时间 */ + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java new file mode 100644 index 0000000..6d6991e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教师端作业列表查询(草稿箱/已发布列表共用)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TeacherHomeworkQuery extends BasePageQuery { + + /** 学科筛选 */ + private String subject; + + /** 作业状态筛选(已发布列表:PUBLISHED/CLOSED/ARCHIVED) */ + private String hwStatus; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java new file mode 100644 index 0000000..d9fd64e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 任教班级 VO(按学科筛选任教班级返回结构)。 + */ +@Data +public class TeachingClassVo { + + /** 班级业务ID(bschclass sch_class.class_id) */ + private String classId; + + /** 班级名称(学校班级列表映射;缺失时回退为班级ID) */ + private String className; +} diff --git a/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java new file mode 100644 index 0000000..0581e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ClassStudent; +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; + +/** + * 班级学生名册仓储。 + */ +public interface ClassStudentRepository extends JpaRepository { + + /** 按学生ID(用户中心数字ID)反查学生身份(学生端接口入口) */ + List findByStudentIdAndStatus(Integer studentId, Integer status); + + /** 班级在班学生 */ + List findByClassIdAndStatus(String classId, Integer status); + + /** 指定班级集合的全部在班学生(应交名单) */ + List findByClassIdInAndStatus(List classIds, Integer status); + + /** 指定学校的全部名册(同步任务全量刷新用) */ + List findBySchoolId(Integer schoolId); + + /** 年级→班级树:按学校去重班级(class_student 快照聚合),列序:classId, className, gradeName */ + @Query("select distinct c.classId, c.className, c.gradeName from ClassStudent c " + + "where c.schoolId = :schoolId and c.status = 1") + List findDistinctClasses(@Param("schoolId") Integer schoolId); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java new file mode 100644 index 0000000..4dc9ee4 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java @@ -0,0 +1,27 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroup; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ContactsGroupRepository extends JpaRepository { + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + + /** + * 按创建人ID(用户中心数字ID)查询联系组列表。 + */ + List findByCreatorId(Integer creatorId); + + /** + * 按创建人ID查询联系组列表(分页)。 + */ + Page findByCreatorId(Integer creatorId, Pageable pageable); + + /** + * 查询全部联系组(按创建时间降序)。 + */ + List findAllByOrderByCreateTimeDesc(); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java new file mode 100644 index 0000000..df5ab28 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java @@ -0,0 +1,42 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroupUser; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ContactsGroupUserRepository extends JpaRepository { + Page findByGroupId(Integer groupId, Pageable pageable); + List findByGroupId(Integer groupId); + + /** + * 按组ID批量删除成员(JPQL bulk delete,SQL 立即执行)。 + * 注意:不能用 derived delete——其 DELETE 延迟到 flush 才执行, + * 同事务内先 insert 会触发 uk_group_user 唯一约束冲突。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId = :groupId") + void deleteByGroupIdBulk(@Param("groupId") Integer groupId); + + /** + * 按多个组ID批量删除成员。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId in :groupIds") + void deleteByGroupIdInBulk(@Param("groupIds") List groupIds); + + /** + * 按多个组ID查询所有成员(用于统计每组人数、组树构建)。 + */ + List findByGroupIdIn(List groupIds); + + /** + * 按组ID和用户ID查询(判断是否已存在)。 + */ + boolean existsByGroupIdAndUserId(Integer groupId, Integer userId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java new file mode 100644 index 0000000..1e6af55 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 作业附件仓储。 + */ +public interface HomeworkAttachmentRepository extends JpaRepository { + + List findByHomeworkIdOrderBySortAscIdAsc(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java new file mode 100644 index 0000000..8f651f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkClass; +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; + +/** + * 作业-班级关联仓储。 + */ +public interface HomeworkClassRepository extends JpaRepository { + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 指定班级的全部作业ID(学生端列表倒查用) */ + @Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds") + List findHomeworkIdsByClassIds(@Param("classIds") List classIds); + + /** 批量查 N 个作业的班级关联(列表展示避免 N+1) */ + @Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java new file mode 100644 index 0000000..6c16a81 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java @@ -0,0 +1,23 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReadRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 学生阅读记录仓储。 + */ +public interface HomeworkReadRecordRepository extends JpaRepository { + + Optional findByHomeworkIdAndStudentId(Integer homeworkId, Integer studentId); + + List findByHomeworkId(Integer homeworkId); + + /** 批量查 N 个作业的已读记录(列表统计避免 N+1) */ + List findByHomeworkIdIn(Collection homeworkIds); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java new file mode 100644 index 0000000..20dc83c --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkRead; +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.Optional; + +/** + * 作业阅读记录仓储(懒标记模型)。 + */ +public interface HomeworkReadRepository extends JpaRepository { + + /** + * 某用户是否已读某作业。 + */ + boolean existsByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 某作业的总已读人数。 + */ + long countByHomeworkId(Integer homeworkId); + + /** + * 删除某作业的全部阅读记录(作业删除时级联清理)。 + */ + void deleteByHomeworkId(Integer homeworkId); + + /** + * 某用户已读的作业总数(用于「我的未读」= 已发布总数 - 已读总数)。 + */ + long countByReaderId(Integer readerId); + + /** + * 查询某用户已读的作业ID列表(用于列表页批量判定未读)。 + */ + @Query("select r.homeworkId from HomeworkRead r where r.readerId = :readerId") + List findHomeworkIdsByReaderId(@Param("readerId") Integer readerId); + + /** + * 查询某用户对某作业的阅读记录(标记已读时幂等判断)。 + */ + Optional findByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 查询某作业的全部阅读记录(按阅读时间倒序),用于阅读明细列表。 + */ + @Query("select r from HomeworkRead r where r.homeworkId = :homeworkId order by r.readTime desc") + List findByHomeworkIdOrderByReadTimeDesc(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java new file mode 100644 index 0000000..402d455 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReceiver; +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; + +/** + * 作业接收人关联表仓储。 + */ +public interface HomeworkReceiverRepository extends JpaRepository { + + /** 按作业ID查询全部接收人 */ + List findByHomeworkId(Integer homeworkId); + + /** 按作业ID删除全部接收人 */ + void deleteByHomeworkId(Integer homeworkId); + + /** 按作业ID和接收类型查询接收人 */ + List findByHomeworkIdAndReceiveType(Integer homeworkId, Integer receiveType); + + /** + * 批量查 N 个作业的接收人,用于列表展示(避免 N+1)。 + * @param homeworkIds 作业ID列表 + * @return 接收人关联记录列表 + */ + @Query("select r from HomeworkReceiver r where r.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java new file mode 100644 index 0000000..bf862b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java @@ -0,0 +1,139 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Homework; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 课程作业仓储。 + */ +public interface HomeworkRepository extends JpaRepository, + JpaSpecificationExecutor { + + /** + * 作业流程状态条件更新(CAS):仅当当前状态为 from 时更新为 to。 + * 状态机单点收敛用,天然幂等、并发安全(定时任务与接口共用)。 + * clearAutomatically:批量更新绕过一级缓存,必须清空以避免后续 findById 读到旧实体 + * 再被 save() 写回旧状态(同事务内必现,集成测试发现)。 + * + * @return 影响行数(0 = 状态已变化,调用方应放弃本次迁移) + */ + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update Homework h set h.hwStatus = :to where h.id = :id and h.hwStatus = :from") + int casHwStatus(@Param("id") Integer id, + @Param("from") String from, + @Param("to") String to); + + /** + * 定时发布任务扫描:定时待发布且已到点。 + */ + List findByHwStatusAndTimingSendTimeLessThanEqual(String hwStatus, LocalDateTime now); + + /** + * 到点关闭任务扫描:进行中且已过截止时间。 + */ + List findByHwStatusAndDeadlineAtBefore(String hwStatus, LocalDateTime now); + + /** + * 截止提醒任务扫描:进行中且截止时间落在指定窗口内(remind_1d/remind_1h 开关在服务侧逐单过滤)。 + */ + List findByHwStatusAndDeadlineAtBetween(String hwStatus, LocalDateTime start, LocalDateTime end); + + /** + * 按作业流程状态查询(兼容层/统计用)。 + */ + List findByHwStatus(String hwStatus); + + /** + * 当前登录人发布的全部作业(课程作业口径统计:我布置的作业 / 提交与批改进度明细)。 + */ + List findByPublisherId(Integer publisherId); + + /** + * 当前登录人布置过(已发布及之后状态)作业的学科去重清单(学科筛选用,字典序;草稿不计)。 + */ + @Query("select distinct h.subject from Homework h " + + "where h.publisherId = :publisherId and h.subject is not null and h.subject <> '' " + + "and h.hwStatus in :statuses order by h.subject") + List findAssignedSubjects(@Param("publisherId") Integer publisherId, + @Param("statuses") Collection statuses); + + /** + * 按状态计数(用于统计已发布总数)。 + */ + long countByStatus(Integer status); + + /** + * 已发布且已到点(定时发送未到点不计入)的作业数,用于用户端未读数统计。 + */ + @Query("select count(n) from Homework n where n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDue(@Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定状态、指定时间范围内的作业创建时间(用于按月统计,避免加载大字段)。 + */ + @Query("select n.createTime from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定状态、指定时间范围内的发布人姓名(用于按发布人统计)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 按学校和状态计数(多租户隔离统计)。 + */ + long countBySchIdAndStatus(Integer schId, Integer status); + + /** + * 按学校计数(多租户隔离统计:全部状态)。 + */ + long countBySchId(Integer schId); + + /** + * 已发布且已到点(定时发送未到点不计入)且属于指定学校的作业数(用户端未读数统计,多租户隔离)。 + */ + @Query("select count(n) from Homework n where n.schId = :schId and n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDueBySchId(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定学校、指定状态、指定时间范围内的作业创建时间(按月统计,多租户隔离)。 + */ + @Query("select n.createTime from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesBySchIdStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定学校、指定状态、指定时间范围内的发布人姓名(按发布人统计,多租户隔离)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesBySchStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java new file mode 100644 index 0000000..547c5ea --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkSubmission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 作业提交仓储(多版本)。统计一律基于 is_latest=1 的当前版本行。 + */ +public interface HomeworkSubmissionRepository extends JpaRepository { + + /** 学生在指定作业的当前版本行 */ + Optional findByHomeworkIdAndStudentIdAndIsLatest(Integer homeworkId, + Integer studentId, + Integer isLatest); + + /** 学生在指定作业的全部版本(新→旧) */ + List findByHomeworkIdAndStudentIdOrderByVersionDesc(Integer homeworkId, Integer studentId); + + /** 指定作业的当前版本行(统计/矩阵用) */ + List findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest); + + /** 批量查 N 个作业的当前版本行(列表统计避免 N+1) */ + List findByHomeworkIdInAndIsLatest(Collection homeworkIds, Integer isLatest); + + /** 学生矩阵分页 */ + Page findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest, Pageable pageable); + + /** 学生当前版本最大版本号 */ + @Query("select max(s.version) from HomeworkSubmission s " + + "where s.homeworkId = :homeworkId and s.studentId = :studentId") + Integer findMaxVersion(@Param("homeworkId") Integer homeworkId, @Param("studentId") Integer studentId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 附件等子表清理后按作业删除历史版本行 */ + @Modifying + @Query("delete from HomeworkSubmission s where s.homeworkId = :homeworkId") + int deleteAllByHomeworkId(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java new file mode 100644 index 0000000..ecc75cb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HomeworkTemplateRepository extends JpaRepository { + Page findByTypeId(Integer typeId, Pageable pageable); + + /** + * 查询全部模板(按引用次数降序),分页。 + */ + Page findAllByOrderByRefCountDesc(Pageable pageable); + + /** + * 按类型ID查询全部模板(不分页,用于检查类型是否被引用)。 + */ + long countByTypeId(Integer typeId); +} diff --git a/src/main/java/com/yunhai/homework/repository/NotificationRepository.java b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java new file mode 100644 index 0000000..1066c10 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + * 站内通知仓储。 + */ +public interface NotificationRepository extends JpaRepository { + + Page findByReceiverId(Integer receiverId, Pageable pageable); + + Page findByReceiverIdAndType(Integer receiverId, String type, Pageable pageable); + + Page findByReceiverIdAndIsRead(Integer receiverId, Integer isRead, Pageable pageable); + + Page findByReceiverIdAndTypeAndIsRead(Integer receiverId, String type, + Integer isRead, Pageable pageable); + + long countByReceiverIdAndIsRead(Integer receiverId, Integer isRead); + + Optional findByIdAndReceiverId(Integer id, Integer receiverId); + + /** 截止提醒去重:同作业同类型同档位已发过即跳过(related_id 存档位:1=1天档 2=1小时档) */ + boolean existsByHomeworkIdAndTypeAndRelatedId(Integer homeworkId, String type, Integer relatedId); + + /** 截止提醒去重(不分档位) */ + boolean existsByHomeworkIdAndType(Integer homeworkId, String type); + + /** 全部已读(clearAutomatically 防同事务内缓存脏读) */ + @Modifying(clearAutomatically = true) + @Query("update Notification n set n.isRead = 1, n.readAt = :now " + + "where n.receiverId = :receiverId and n.isRead = 0") + int markAllRead(@Param("receiverId") Integer receiverId, @Param("now") java.time.LocalDateTime now); + + /** 按作业删除通知(作业删除级联) */ + void deleteByHomeworkId(Integer homeworkId); + + /** 指定作业的全部通知(清理/统计用) */ + List findByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java new file mode 100644 index 0000000..8e60f47 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SchClassTeach; +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; + +/** + * 班级任教关系快照仓储。 + */ +public interface SchClassTeachRepository extends JpaRepository { + + /** 指定学校指定教师的全部有效任教记录 */ + List findBySchoolIdAndTeaIdAndStatus(Integer schoolId, Integer teaId, Integer status); + + /** 指定学校指定教师去重学科编码(字典序由调用方保证) */ + @Query("select distinct t.subId from SchClassTeach t " + + "where t.schoolId = :schoolId and t.teaId = :teaId and t.status = 1") + List findDistinctSubIds(@Param("schoolId") Integer schoolId, @Param("teaId") Integer teaId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java new file mode 100644 index 0000000..18595bb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java @@ -0,0 +1,13 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsModule; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsModuleRepository extends JpaRepository { + List findAllByOrderByIdAsc(); + + /** 按模块标识查询(如 module_code="homework")。 */ + SmsModule findByModuleCode(String moduleCode); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java new file mode 100644 index 0000000..0d277a0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsSchoolConfig; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsSchoolConfigRepository extends JpaRepository { + Page findBySchoolNameContaining(String schoolName, Pageable pageable); + Page findAll(Pageable pageable); + List findBySchoolId(Integer schoolId); + List findBySchoolIdInAndModuleIdIn(List schoolIds, List moduleIds); + + /** + * 按学校ID和模块ID查询单条配置(判断是否已存在)。 + */ + SmsSchoolConfig findBySchoolIdAndModuleId(Integer schoolId, Integer moduleId); + + /** + * 按学校ID查询全部模块配置(用于"设置某校全部模块")。 + */ + List findBySchoolIdIn(List schoolIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java new file mode 100644 index 0000000..8848b14 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SubmissionAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; + +/** + * 提交附件仓储。 + */ +public interface SubmissionAttachmentRepository extends JpaRepository { + + List findBySubmissionIdOrderBySortAscIdAsc(Integer submissionId); + + /** 批量查 N 个提交行的附件(矩阵/版本列表避免 N+1) */ + List findBySubmissionIdIn(Collection submissionIds); + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SysDictRepository.java b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java new file mode 100644 index 0000000..c083840 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SysDict; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SysDictRepository extends JpaRepository { + List findByDictTypeOrderBySortOrderAsc(String dictType); + List findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag); + + /** 按类型+key 取单条字典(发文部门名称解析用) */ + Optional findByDictTypeAndDictKey(String dictType, String dictKey); +} diff --git a/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java new file mode 100644 index 0000000..a3a0f12 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.TemplateType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TemplateTypeRepository extends JpaRepository { + List findAllByOrderBySortOrderAsc(); + + /** + * 按类型名称查询(重名校验)。 + */ + boolean existsByTypeName(String typeName); + + /** + * 按类型名称查询(重名校验,排除自身)。 + */ + boolean existsByTypeNameAndIdNot(String typeName, Integer id); +} diff --git a/src/main/java/com/yunhai/homework/repository/UpFileRepository.java b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java new file mode 100644 index 0000000..421f142 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.UpFile; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +/** + * 附件文件仓储。 + */ +public interface UpFileRepository extends JpaRepository { + + /** + * 按主题类型和主题ID查询附件列表。 + */ + List findByTopicTypeAndTopicId(String topicType, Integer topicId); + + /** + * 按主题类型和多个主题ID批量查询附件(消除 N+1)。 + */ + List findByTopicTypeAndTopicIdIn(String topicType, List topicIds); + + /** + * 按主题类型和主题ID删除附件(作业删除时级联清理)。 + */ + void deleteByTopicTypeAndTopicId(String topicType, Integer topicId); +} diff --git a/src/main/java/com/yunhai/homework/service/IClassService.java b/src/main/java/com/yunhai/homework/service/IClassService.java new file mode 100644 index 0000000..2346cd9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IClassService.java @@ -0,0 +1,116 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; + +import java.util.List; + +/** + * 班级管理(bschclass)开放接口客户端门面。 + * 基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。 + * 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。 + */ +public interface IClassService { + + /** + * 获取指定学校全部教师(含所属组 GroupIds)。 + * 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}} + * + * @param schId 学校ID(即用户当前身份的 schId) + */ + List getSchTeaList(int schId); + + /** + * 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。 + * 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}} + */ + SchInfoDto getSchFirst(int schId); + + /** + * 学校学段(学制)列表:小学部 / 初中部…。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=} + */ + List getSchLenList(String schSerId); + + /** + * 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=} + */ + List getSchGradeList(String schSerId); + + /** + * 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。 + * 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=} + */ + List getSchClassList(String schSerId); + + /** + * 按校全部学生(圈人:全校范围展开)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=} + */ + List getSchStuList(String schSerId); + + /** + * 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=} + */ + List getGradeStuList(String schSerId, Integer gradeId); + + /** + * 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=} + */ + List getSubStuList(String schSerId, Integer schSystemId); + + /** + * 按班级批量查学生(圈人:班级范围一次多班)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3} + */ + List getClaArrStuList(List classIds); + + /** + * 按学生ID批量查(圈人:指定学员范围)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3} + */ + List getStuIdsList(List stuIds); + + /** + * 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。 + * 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。 + * 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=} + */ + List getTeaTree(Integer schId, boolean toUser); + + List getStuTree(Integer schId,boolean toUser); + + /** + * 学校学科列表(含学科编码、名称、排序号)。 + * 对应:GET {@code {bschclass}/oapi/SubInfo/GetSchSublist?SchID=} + */ + List getSchSubList(String schSerId); + + /** + * 指定教师的全部任教(校验教师归属该校;SchID 过滤学校、UserTeaSerID 过滤教师)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=&UserTeaSerID=} + */ + List getSchTeachList(String schSerId, Integer teaId); + + /** + * 指定班级的全部任教(班级维度:该班所有教师与学科)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchClaTeachlist?ClassID=} + */ + List getSchClaTeachList(Integer classId); + + /** + * 学校全部任教(全校所有教师的班级任教关系)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=} + */ + List getSchAllTeachList(String schSerId); +} diff --git a/src/main/java/com/yunhai/homework/service/IContactsService.java b/src/main/java/com/yunhai/homework/service/IContactsService.java new file mode 100644 index 0000000..0e1fdce --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IContactsService.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 联系组业务接口。 + * 对应前端 Contacts 组接口:AddGroup/GetGroup/EditGroup/DelGroup/SetContactsTea/GetContactsTea/GetGroupTree/GetDeptRoleGroupTree/Import/Export。 + */ +public interface IContactsService { + + /** + * 添加联系组。 + * + * @param name 组名 + * @param type 类型(1 自定义组, 2 明信片) + */ + void addGroup(String name, Integer type); + + /** + * 获取联系组列表(含每组人数,分页)。 + * + * @param page 页码(从1起) + * @param count 每页条数 + * @param type 类型(1 自定义组, 2 明信片) + * @return Map: { list, total } + */ + Map getGroup(Integer page, Integer count, Integer type); + + /** + * 编辑联系组。 + * + * @param id 组ID + * @param name 组名 + * @param type 类型 + */ + void editGroup(Integer id, String name, Integer type); + + /** + * 删除联系组(支持批量,同时删除组内成员)。 + * + * @param ids 逗号分隔的组ID + */ + void delGroup(String ids); + + /** + * 设置联系组教师(全量替换:先删后增)。 + * + * @param gid 组ID + * @param ids 逗号分隔的用户ID + */ + void setContactsTea(Integer gid, String ids); + + /** + * 获取联系组教师列表(分页)。 + * + * @param gid 组ID + * @param page 页码 + * @param count 每页条数 + * @return Map: { list, total } + */ + Map getContactsTea(String gid, Integer page, Integer count); + + /** + * 获取联系组树(组节点+用户节点,递归构建)。 + * + * @param type 类型(1 自定义组, 2 明信片) + * @return 树形结构 List<Map> + */ + List> getGroupTree(Integer type); + + /** + * 获取部门角色组树。 + * C# 从外部API获取部门树+用户角色映射。Java版预留。 + * + * @return 树形结构 List<Map> + */ + List> getDeptRoleGroupTree(); +} diff --git a/src/main/java/com/yunhai/homework/service/IDictService.java b/src/main/java/com/yunhai/homework/service/IDictService.java new file mode 100644 index 0000000..31ef515 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IDictService.java @@ -0,0 +1,75 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 系统字典业务接口。 + * 对应前端 Base 组接口:GetSysDic/GetDictMangeList/AddDeptDic/UpdateDeptDic/DelDept/OrdbyDicIds/GetDeptSync/GetSchModuleIsSms。 + */ +public interface IDictService { + + /** + * 获取系统字典(下拉框用)。 + * C# GetSysDic: Type=1 部门类别, 其余类型略。 + * + * @param type 字典类型编号(1=部门类别) + * @return List<Map> 每项 {k, v} + */ + List> getSysDic(Integer type); + + /** + * 获取系统字典管理列表(含 SerID/CodeID/Name/Des/Enable)。 + * + * @param type 字典类型编号(0=部门类别) + * @return List<Map> + */ + List> getDictMangeList(String type); + + /** + * 添加发文单位。 + * + * @param name 单位名称 + */ + void addDeptDic(String name); + + /** + * 更新发文单位。 + * + * @param serID 记录ID + * @param name 名称 + * @param enable 启用状态 + */ + void updateDeptDic(Integer serID, String name, Integer enable); + + /** + * 删除发文单位(校验是否被作业引用)。 + * + * @param serID 记录ID + */ + void delDept(Integer serID); + + /** + * 发文单位排序。 + * + * @param ids 逗号分隔的 SerID 列表(按前端顺序) + */ + void ordbyDicIds(String ids); + + /** + * 本校是否开通短信模块(前端 dict/Sync,用于"本校是否开通短信"判断)。 + * + * @param schId 当前学校ID(取当前登录用户角色所属学校) + * @return 开通返回 true,未开通/未配置返回 false + */ + Boolean getSchModuleIsSms(Integer schId); + + /** + * 按发文部门字典 key(CodeID)解析部门名称;未命中返回 null。 + * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 + * + * @param deptId 作业 deptId(存的是 sys_dict.dict_key) + * @return 部门名称;无匹配时返回 null + */ + String resolveDeptName(String deptId); +} diff --git a/src/main/java/com/yunhai/homework/service/IFileService.java b/src/main/java/com/yunhai/homework/service/IFileService.java new file mode 100644 index 0000000..1b01bbf --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IFileService.java @@ -0,0 +1,94 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.UpFile; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.model.homework.UpFileResultVo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件上传/下载/删除业务接口。 + */ +public interface IFileService { + /** + * 上传单个文件。 + * + * @param file 上传的文件 + * @param appType 应用类型(如 "homework") + * @param topicId 关联主题ID(可为空,作业保存后再绑定) + * @return 文件视图对象 + */ + UpFileVo upload(MultipartFile file, String appType, Integer topicId); + + /** + * 将已上传但未绑定主题的附件绑定到指定作业。 + * + * @param fileIds 文件记录ID列表 + * @param topicId 作业ID + */ + void bindToTopic(List fileIds, Integer topicId); + + /** + * 查询某主题下的附件列表。 + * + * @param appType 应用类型 + * @param topicId 主题ID + * @return 附件列表 + */ + List listByTopic(String appType, Integer topicId); + + /** + * 下载文件。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void download(Integer fileId, HttpServletResponse response); + + /** + * 在线预览文件(Content-Disposition: inline,按扩展名输出真实 Content-Type, + * 供前端 open-file-viewer 取 Blob / video 播放)。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void preview(Integer fileId, HttpServletResponse response); + + /** + * 删除文件(同时删除物理文件和数据库记录)。 + * + * @param fileId 文件记录ID + */ + void delete(Integer fileId); + + /** + * 打包下载多个附件为 ZIP。 + * + * @param files 附件记录列表(调用方按 fileIds 查好) + * @param zipName 压缩包显示名(自动追加 .zip) + * @param response HTTP 响应 + */ + void downloadZip(List files, String zipName, HttpServletResponse response); + + /** + * 按主题类型和主题ID打包下载附件为 ZIP(Controller 便捷方法,内部自行查附件列表)。 + * + * @param topicType 主题类型(如 "homework") + * @param topicId 主题ID + * @param zipName 压缩包显示名 + * @param response HTTP 响应 + */ + void downloadZipByTopic(String topicType, Integer topicId, String zipName, HttpServletResponse response); + + /** + * 批量上传图片(循环上传,返回每个文件的视图对象列表)。 + */ + List uploadImages(MultipartFile[] files, String appType); + + /** + * 上传单个文件并返回前端兼容的结果 VO(含 fileid/name/fileurl/host/type/size)。 + */ + UpFileResultVo uploadToResult(MultipartFile file, String appType); +} diff --git a/src/main/java/com/yunhai/homework/service/IHomeworkService.java b/src/main/java/com/yunhai/homework/service/IHomeworkService.java new file mode 100644 index 0000000..ed50167 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IHomeworkService.java @@ -0,0 +1,142 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业业务接口。 + */ +public interface IHomeworkService { + + /** + * 发作业:以当前登录用户为发布人,状态置为「已发布」入库。 + * 若 request.timingSendTime 不为空且晚于当前时间,则作为定时发送, + * 未到点前用户端收件箱不可见(到点后由查询过滤自然可见)。 + * + * @return 新建作业 ID(用于后续回填附件 TopicID 等) + */ + Integer publish(HomeworkRequest request); + + /** + * 编辑作业:按 id 更新标题/内容/类型/部门/置顶,发布人与状态保持不变。 + */ + void edit(Integer id, HomeworkRequest request); + + /** + * 批量删除作业:删除主记录并级联清理阅读记录、接收人、附件关联。 + */ + void deleteBatch(List ids); + + /** + * 撤回作业:仅已发布且未撤回的作业可撤回,置 isPulled=1。 + */ + void pull(Integer id); + + /** + * 置顶/取消置顶。 + */ + void setTopmost(Integer id, Integer topmost); + + /** + * 转发作业:基于源作业创建新作业(已发布),parentId 关联源作业。 + */ + Integer forward(Integer sourceId, HomeworkRequest request); + + /** + * 用户端列表:仅展示已发布且已到点的作业,并标记当前用户是否已读,附未读总数。 + * 返回 HomeworkPageResult(扩展 unreadCount)。 + */ + HomeworkPageResult userList(HomeworkQuery query, Pageable pageable); + + /** + * 管理端列表:按条件查询全部作业(管理员查看全部)。 + */ + BasePageResult adminList(HomeworkQuery query, Pageable pageable); + + /** + * 作业详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。 + */ + HomeworkVo detail(Integer id); + + /** + * 编辑回显详情(不标记已读)。 + */ + HomeworkVo getDetail(Integer id); + + /** + * 标记已读:为当前用户写入(幂等)该作业的阅读记录。 + */ + void markRead(Integer id); + + /** + * 一键全部已读(批量化):当前用户所有未读的已发布作业批量插入阅读记录。 + * @return 已读条数 + */ + int initializeRed(); + + /** + * 追加接收人(事务化):并入接收人列表并重算 allowcount。 + */ + void receiveUsers(Integer homeworkId, String users); + + /** + * 上一篇/下一篇作业ID(SQL 相邻查询)。 + * @param serid 当前作业ID + * @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列) + * @return {prevId, nextId} + */ + Map getNextPrev(Integer serid, String type); + + /** + * 获取某作业的阅读明细列表。 + */ + List getReadDetail(Integer homeworkId); + + /** + * 阅读情况分组(已读/未读)。 + */ + List> getReadDetailGrouped(Integer serid); + + /** + * 催收未读:枚举未读接收人,后续接入消息推送。 + */ + void expedite(Integer serid); + + /** + * 作业统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。 + */ + HomeworkStatisticsVo statistics(HomeworkQuery query, LocalDateTime start, LocalDateTime end); + + /** + * 作业详情视图(含附件、接收人信息),供 Controller 返回。 + */ + HomeworkDetailVo getDetailMap(Integer serid); + + /** + * 已发列表附件批量查询(消除 N+1)。 + */ + Map> batchFileIds(List homeworkIds); + + /** + * 查看作业详情视图(含附件、接收人)。 + * type=1 时先标记已读再返回,type=0 时直接返回不标记。 + */ + HomeworkDetailVo detailMap(Integer serid, Integer type); + + /** + * 一键全部已读并返回结果(count = 已读条数)。 + */ + InitializeRedVo initializeRedResult(); + + /** + * 批量下载作业全部附件(打包 zip):查作业标题后调文件域打包下载。 + */ + void downloadAllFiles(Integer dataid, HttpServletResponse response); +} diff --git a/src/main/java/com/yunhai/homework/service/ISmsService.java b/src/main/java/com/yunhai/homework/service/ISmsService.java new file mode 100644 index 0000000..09b9b5f --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ISmsService.java @@ -0,0 +1,77 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置业务接口。 + * 对应前端 setSms 组接口:getmodule/getgroup/getschsms/getsch/setschsms/setschallsms/setschmodulesms。 + */ +public interface ISmsService { + + /** + * 获取短信模块列表。 + * C# 从 AuthConfig.AppKeyMap 读取。Java版从 sms_module 表读取。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getModuleList(); + + /** + * 获取学校父类组列表。 + * C# 从外部BLL获取学校组。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchParentGroup(); + + /** + * 分页获取学校短信开关列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param schName 学校名称(模糊搜索) + * @return Map: { list, total } + */ + Map getSchIsSmsList(Integer pageSize, Integer pageIndex, String schName); + + /** + * 获取学校列表。 + * C# 从外部BLL获取学校列表。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchList(); + + /** + * 设置单校单模块短信开关。 + * + * @param schSerID 学校ID + * @param moduleId 模块ID + * @param isSms 是否开通 + */ + void setSchSms(Integer schSerID, Integer moduleId, Boolean isSms); + + /** + * 设置某校全部模块短信开关。 + * + * @param schSerID 学校ID + * @param isSms 是否开通 + */ + void setSchSmsAll(Integer schSerID, Boolean isSms); + + /** + * 批量设置多校多模块短信开关。 + * + * @param schSerIDs 逗号分隔的学校ID + * @param modules 逗号分隔的模块ID + * @param isSms 是否开通 + */ + void setSchModuleSms(String schSerIDs, String modules, Boolean isSms); + + /** + * 从 JSON 字符串解析并设置单校单模块短信开关。 + * 解析 SchSerID/ModuID/IsSms 三个字段。 + */ + void setSchSmsFromJson(String itemsModuJson); +} diff --git a/src/main/java/com/yunhai/homework/service/ITemplateService.java b/src/main/java/com/yunhai/homework/service/ITemplateService.java new file mode 100644 index 0000000..5594401 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ITemplateService.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板业务接口。 + * 对应前端 Temp 组接口:GetTypelist/EditType/DelType/GetTemplatelist/EditTemplate/DelTemplate/GetTemplateFist。 + */ +public interface ITemplateService { + + /** + * 获取模板类型列表(首项固定为"全部")。 + * + * @return List<Map> 每项 {TypeID, TypeName} + */ + List> getTypelist(); + + /** + * 新增/编辑模板类型。 + * + * @param typeid 类型ID(0=新增) + * @param typeName 类型名称 + */ + void editType(Integer typeid, String typeName); + + /** + * 删除模板类型(校验是否有模板引用此类型)。 + * + * @param typeid 类型ID + */ + void delType(Integer typeid); + + /** + * 分页获取模板列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param typeid 类型ID(0=全部) + * @return Map: { list, total } + */ + Map getTemplatelist(Integer pageSize, Integer pageIndex, Integer typeid); + + /** + * 新增/编辑模板。 + * + * @param serID 模板ID(null=新增) + * @param title 标题 + * @param content 内容 + * @param typeId 类型ID + * @param cover 封面文件ID + */ + void editTemplate(Integer serID, String title, String content, Integer typeId, Integer cover); + + /** + * 删除模板。 + * + * @param serID 模板ID + */ + void delTemplate(Integer serID); + + /** + * 获取单个模板详情。 + * + * @param serID 模板ID + * @return Map 模板详情 + */ + Map getTemplateFist(Integer serID); +} diff --git a/src/main/java/com/yunhai/homework/service/IUserCenterService.java b/src/main/java/com/yunhai/homework/service/IUserCenterService.java new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IUserCenterService.java @@ -0,0 +1,51 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 用户中心门面服务(对齐 UserBase {@code UserCenterService} 模式)。 + * 封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。 + */ +public interface IUserCenterService { + + /** + * 获取当前用户学校组及用户(树形结构)。 + * 组装规则(从原 BaseApiController.getSchoolAll 下沉): + * + * userbase 读取当前学校整棵组树(getGroupById(schId, withSub=true)),剔除学生组(type=8)及其子组; + * bschclass 获取该校全部教师(/oapi/DbClass/GetSchteaList?Group=schId),按 GroupIds 挂到对应组下, + * 无有效组(如所属组全被剔除)的教师兜底挂学校根节点; + * 扁平节点按 pid 递归组装为树(空组剔除,同 C# FormatUserTree)。 + * + * + * @param schId 学校ID(用户当前角色所属学校) + * @return 用户树形结构(组节点 type=1 / 教师节点 type=2) + */ + List> getSchoolAllTree(Integer schId); + + /** + * 获取指定组内用户(教师 + 管理员)。 + * 对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。 + * + * @param gid 组ID + * @param role 角色(默认 2=教师;1=管理员等) + * @return 组内用户列表 + */ + List> getGroupUser(Integer gid, Integer role); + + /** + * 获取当前用户可访问的模块树(左侧菜单)。 + * 从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。 + * + * @param userId 用户ID + * @param schId 学校ID(可为 null) + * @return 模块树(顶层 List,含 children 嵌套) + */ + List> getUserModuleTree(Integer userId, Integer schId); +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/service/impl/ClassService.java b/src/main/java/com/yunhai/homework/service/impl/ClassService.java new file mode 100644 index 0000000..8be5e2a --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/impl/ClassService.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.service.impl; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.service.IClassService; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班级管理(bschclass)开放接口实现。 + * bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。 + * 推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List
路径前缀:{@code Config.API_PATH + "/sms/"}(原 schsms/ 已修正)。 + * 方法名 PascalCase(原全小写已修正);写操作一律 POST(原 setschallsms/setschmodulesms 为 GET 写操作,已 POST 化)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/sms/") +public class SmsController { + + @Autowired + private ISmsService smsService; + + /** + * 获取短信模块列表。 + */ + @GetMapping("GetModule") + public List> getModule() { + return smsService.getModuleList(); + } + + /** + * 获取学校父类组列表。 + */ + @GetMapping("GetGroup") + public List> getGroup() { + return smsService.getSchParentGroup(); + } + + /** + * 分页获取学校短信开关列表。 + */ + @GetMapping("GetSchoolSms") + public Map getSchoolSms( + @RequestParam(value = "schpid", required = false) String schpid, + @RequestParam("PageSize") Integer pageSize, + @RequestParam("PageIndex") Integer pageIndex, + @RequestParam(value = "SchName", required = false, defaultValue = "") String schName) { + return smsService.getSchIsSmsList(pageSize, pageIndex, schName); + } + + /** + * 获取学校列表。 + */ + @GetMapping("GetSchool") + public List> getSchool() { + return smsService.getSchList(); + } + + /** + * 设置单校单模块短信开关。 + */ + @PostMapping("SetSchoolSms") + public void setSchoolSms(@RequestParam("itemsModu") String itemsModuJson) { + smsService.setSchSmsFromJson(itemsModuJson); + } + + /** + * 设置某校全部模块短信开关(原 GET setschallsms,已 POST 化)。 + */ + @PostMapping("SetAllSchoolSms") + public void setAllSchoolSms( + @RequestParam("SchSerID") Integer schSerID, + @RequestParam("IsSms") Boolean isSms) { + smsService.setSchSmsAll(schSerID, isSms); + } + + /** + * 批量设置多校多模块短信开关(原 GET setschmodulesms,已 POST 化)。 + */ + @PostMapping("SetSchoolModuleSms") + public void setSchoolModuleSms( + @RequestParam("SchSerID") String schSerID, + @RequestParam("Module") String module, + @RequestParam("IsSms") Boolean isSms) { + smsService.setSchModuleSms(schSerID, module, isSms); + } + +} diff --git a/src/main/java/com/yunhai/homework/controller/api/StudentHomeworkController.java b/src/main/java/com/yunhai/homework/controller/api/StudentHomeworkController.java new file mode 100644 index 0000000..56a37b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/StudentHomeworkController.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.BasePageQuery; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.v2.StudentHomeworkDetailVo; +import com.yunhai.homework.model.v2.StudentHomeworkVo; +import com.yunhai.homework.model.v2.StudentResultVo; +import com.yunhai.homework.model.v2.StudentSubmitRequest; +import com.yunhai.homework.model.v2.SubmissionActionRequest; +import com.yunhai.homework.service.impl.StudentHomeworkService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +/** + * 课堂作业 V2 学生端接口(PRD §18 路径一字对齐:/student/homeworks…)。 + * + * 身份:当前登录用户ID ↔ class_student 名册;仅可见本人班级作业,禁 IDOR。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/student/homeworks") +public class StudentHomeworkController { + + @Autowired + private StudentHomeworkService studentHomeworkService; + + /** 我的作业列表(tab=all/todo/done/closed) */ + @GetMapping + public BasePageResult list( + @RequestParam(value = "tab", defaultValue = "all") String tab, + BasePageQuery query) { + return studentHomeworkService.list(tab, query); + } + + /** 作业详情 */ + @GetMapping("/{id}") + public StudentHomeworkDetailVo detail(@PathVariable("id") Integer id) { + return studentHomeworkService.detail(id); + } + + /** 阅读上报(前端停留 ≥3 秒后调用;幂等) */ + @PostMapping("/{id}/read") + public void read(@PathVariable("id") Integer id) { + studentHomeworkService.read(id); + } + + /** 提交作业,返回新提交行 id */ + @PostMapping("/{id}/submissions") + public Map submit(@PathVariable("id") Integer id, + @RequestBody @Validated StudentSubmitRequest request) { + Integer submissionId = studentHomeworkService.submit(id, request); + return java.util.Collections.singletonMap("submissionId", submissionId); + } + + /** 撤回 / 打回后重交(action=withdraw|resubmit),返回新提交行 id */ + @PutMapping("/{id}/submissions/{submissionId}") + public Map action(@PathVariable("id") Integer id, + @PathVariable("submissionId") Integer submissionId, + @RequestBody @Validated SubmissionActionRequest request) { + Integer newId = studentHomeworkService.action(id, submissionId, request); + return java.util.Collections.singletonMap("submissionId", newId); + } + + /** 批阅结果(当前版本 + 历史版本) */ + @GetMapping("/{id}/result") + public StudentResultVo result(@PathVariable("id") Integer id) { + return studentHomeworkService.result(id); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java b/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java new file mode 100644 index 0000000..5335083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java @@ -0,0 +1,219 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.v2.ClassTreeNode; +import com.yunhai.homework.model.v2.ExtendRequest; +import com.yunhai.homework.model.v2.HomeworkStatsVo; +import com.yunhai.homework.model.v2.HomeworkV2Request; +import com.yunhai.homework.model.v2.HomeworkV2Vo; +import com.yunhai.homework.model.v2.RemindRequest; +import com.yunhai.homework.model.v2.ReturnRequest; +import com.yunhai.homework.model.v2.ReviewRequest; +import com.yunhai.homework.model.v2.ScheduleRequest; +import com.yunhai.homework.model.v2.StudentMatrixQuery; +import com.yunhai.homework.model.v2.StudentMatrixVo; +import com.yunhai.homework.model.v2.SubmissionVersionVo; +import com.yunhai.homework.model.v2.SubjectDto; +import com.yunhai.homework.model.v2.TeacherHomeworkQuery; +import com.yunhai.homework.model.v2.TeachingClassVo; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.SortDefault; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * 课堂作业 V2 教师端接口(PRD §18 路径一字对齐:/teacher/homeworks…)。 + * + * 与 notice 语义的旧接口(/{platform}/homework/*)并存:旧接口冻结维护, + * 新业务一律走本控制器;返回裸对象由 ApiResponseAdvice 包装 {code,msg,data}。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/teacher/homeworks") +public class TeacherHomeworkV2Controller { + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + // ============================ 页 1:发布作业 ============================ + + /** 创建作业(落草稿) */ + @PostMapping + public Integer create(@RequestBody @Validated HomeworkV2Request request) { + return teacherHomeworkV2Service.create(request); + } + + /** 编辑作业(仅草稿) */ + @PutMapping("/{id}") + public void edit(@PathVariable("id") Integer id, + @RequestBody @Validated HomeworkV2Request request) { + teacherHomeworkV2Service.edit(id, request); + } + + /** 删除作业(仅草稿) */ + @DeleteMapping("/{id}") + public void delete(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.delete(id); + } + + /** 立即发布(草稿 → 进行中) */ + @PostMapping("/{id}/publish") + public void publish(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.publish(id); + } + + /** 定时发布 / 取消定时(body: {scheduledAt} 或 {cancel:true}) */ + @PostMapping("/{id}/schedule") + public void schedule(@PathVariable("id") Integer id, @RequestBody ScheduleRequest request) { + teacherHomeworkV2Service.schedule(id, request); + } + + // ============================ 页 2:草稿箱 ============================ + + /** 草稿箱分页 */ + @GetMapping("/drafts") + public BasePageResult drafts(TeacherHomeworkQuery query, + @SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort) { + return teacherHomeworkV2Service.drafts(query, sort); + } + + // ============================ 页 3:已发布列表 ============================ + + /** 已发布列表分页(含应交/已读/已交/未读未交聚合) */ + @GetMapping("/published") + public BasePageResult published(TeacherHomeworkQuery query, + @SortDefault(sort = "publishedAt", direction = Sort.Direction.DESC) Sort sort) { + return teacherHomeworkV2Service.published(query, sort); + } + + /** 提醒未读/未交学生,返回实际提醒人数 */ + @PostMapping("/{id}/remind") + public Map remind(@PathVariable("id") Integer id, @RequestBody RemindRequest request) { + int reminded = teacherHomeworkV2Service.remind(id, request); + return java.util.Collections.singletonMap("reminded", reminded); + } + + /** 延长截止时间 */ + @PostMapping("/{id}/extend") + public void extend(@PathVariable("id") Integer id, @RequestBody @Validated ExtendRequest request) { + teacherHomeworkV2Service.extend(id, request); + } + + /** 复制为新草稿,返回新作业 id */ + @PostMapping("/{id}/copy") + public Integer copy(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.copy(id); + } + + /** 归档(只读,不再触发任何提醒) */ + @PostMapping("/{id}/archive") + public void archive(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.archive(id); + } + + /** 导出学生矩阵 Excel */ + @GetMapping("/{id}/export") + public void export(@PathVariable("id") Integer id, HttpServletResponse response) throws IOException { + teacherHomeworkV2Service.export(id, response); + } + + // ============================ 页 4:详情 / 统计 ============================ + + /** 作业详情 */ + @GetMapping("/{id}") + public HomeworkV2Vo detail(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.detail(id); + } + + /** 汇总统计卡片(总进度 + 分班进度) */ + @GetMapping("/{id}/stats") + public HomeworkStatsVo stats(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.stats(id); + } + + // ============================ 页 5:学生矩阵 ============================ + + /** 学生矩阵分页(阅读/提交/批阅状态,未交学生生成占位行) */ + @GetMapping("/{id}/students") + public BasePageResult students(@PathVariable("id") Integer id, + StudentMatrixQuery query) { + return teacherHomeworkV2Service.students(id, query); + } + + // ============================ 页 6:批阅 / 打回 / 版本 ============================ + + /** 保存批阅(→ REVIEWED,推送批阅完成通知) */ + @PostMapping("/{id}/submissions/{studentId}/review") + public void review(@PathVariable("id") Integer id, @PathVariable("studentId") Integer studentId, + @RequestBody @Validated ReviewRequest request) { + teacherHomeworkV2Service.review(id, studentId, request); + } + + /** 打回(→ RETURNED,推送打回通知) */ + @PostMapping("/{id}/submissions/{studentId}/return") + public void returnSubmission(@PathVariable("id") Integer id, @PathVariable("studentId") Integer studentId, + @RequestBody @Validated ReturnRequest request) { + teacherHomeworkV2Service.returnSubmission(id, studentId, request); + } + + /** 历史提交版本(批阅弹窗切换版本) */ + @GetMapping("/{id}/submissions/{studentId}/versions") + public List versions(@PathVariable("id") Integer id, + @PathVariable("studentId") Integer studentId) { + return teacherHomeworkV2Service.versions(id, studentId); + } + + // ============================ 辅助:学科 ============================ + + /** 我布置过的学科(本人已发布及之后作业的学科 ∪ 我的任教学科,合并去重;草稿不计) */ + @GetMapping("/subjects/assigned") + public List assignedSubjects() { + return teacherHomeworkV2Service.assignedSubjects(); + } + + /** 我的任教学科(bschclass 任教关系去重,名称取本校学科字典) */ + @GetMapping("/subjects/teaching") + public List teachingSubjects() { + return teacherHomeworkV2Service.teachingSubjects(); + } + + /** 按学科筛选我的任教班级(subId=学科编码;班级名称取学校班级列表) */ + @GetMapping("/subjects/classes") + public List teachingClasses(@RequestParam("subId") String subId) { + return teacherHomeworkV2Service.teachingClasses(subId); + } + + // ============================ 辅助:班级树 / 名册同步 ============================ + + /** 年级→班级树(发布选班;名册快照聚合) */ + @GetMapping("/classes") + public List classTree() { + return teacherHomeworkV2Service.classTree(); + } + + /** 手动触发名册同步(bschclass 接口就绪前为空操作) */ + @PostMapping("/classes/sync") + public Map syncClasses() { + int synced = classStudentService.syncAll(); + return java.util.Collections.singletonMap("synced", synced); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/TemplateController.java b/src/main/java/com/yunhai/homework/controller/api/TemplateController.java new file mode 100644 index 0000000..e9c593c --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/TemplateController.java @@ -0,0 +1,88 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.service.ITemplateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板接口(模板域)。 + * 路径前缀:{@code Config.API_PATH + "/template/"}(原 homework/Template/ 三级路径已修正)。 + * 写操作一律 POST(原 EditType/DelTemplate 为 GET 写操作,已 POST 化)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/template/") +public class TemplateController { + + @Autowired + private ITemplateService templateService; + + /** + * 获取模板类型列表。 + */ + @GetMapping("Typelist") + public List> getTypelist() { + return templateService.getTypelist(); + } + + /** + * 新增/编辑模板类型(原 GET EditType,已 POST 化)。 + */ + @PostMapping("TypeEdit") + public void typeEdit( + @RequestParam("typeid") Integer typeid, + @RequestParam("TypeName") String typeName) { + templateService.editType(typeid, typeName); + } + + /** + * 删除模板类型(原 GET DelType,已 POST 化)。 + */ + @PostMapping("TypeDelete") + public void typeDelete(@RequestParam("typeid") Integer typeid) { + templateService.delType(typeid); + } + + /** + * 分页获取模板列表。 + */ + @GetMapping("List") + public Map list( + @RequestParam("PageSize") Integer pageSize, + @RequestParam("PageIndex") Integer pageIndex, + @RequestParam(value = "typeid", defaultValue = "0") Integer typeid) { + return templateService.getTemplatelist(pageSize, pageIndex, typeid); + } + + /** + * 新增/编辑模板。 + */ + @PostMapping("TemplateEdit") + public void templateEdit( + @RequestParam(value = "SerID", required = false) Integer serID, + @RequestParam("Title") String title, + @RequestParam("Contents") String contents, + @RequestParam(value = "Type", required = false) Integer type, + @RequestParam(value = "Cover", required = false) Integer cover) { + templateService.editTemplate(serID, title, contents, type, cover); + } + + /** + * 删除模板(原 GET DelTemplate,已 POST 化)。 + */ + @PostMapping("TemplateDelete") + public void templateDelete(@RequestParam("SerID") Integer serid) { + templateService.delTemplate(serid); + } + + /** + * 获取单个模板详情。 + */ + @GetMapping("TemplateFirst") + public Map templateFirst(@RequestParam("serid") Integer serid) { + return templateService.getTemplateFist(serid); + } +} diff --git a/src/main/java/com/yunhai/homework/entity/BaseEntity.java b/src/main/java/com/yunhai/homework/entity/BaseEntity.java new file mode 100644 index 0000000..5f98e8c --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/BaseEntity.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.common.listener.HomeworkAuditListener; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 所有实体的公共基类:自增主键 + 创建人/时间 + 更新人/时间(由 {@link HomeworkAuditListener} 自动填充)。 + */ +@MappedSuperclass +@Getter +@Setter +@EntityListeners(HomeworkAuditListener.class) +public abstract class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 创建人ID(平台用户ID) + */ + @Column(name = "create_by", updatable = false) + private String createBy; + + /** + * 创建时间 + */ + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + /** + * 更新人ID(平台用户ID) + */ + @Column(name = "update_by") + private String updateBy; + + /** + * 更新时间 + */ + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/ClassStudent.java b/src/main/java/com/yunhai/homework/entity/ClassStudent.java new file mode 100644 index 0000000..793e295 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ClassStudent.java @@ -0,0 +1,59 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 班级学生名册快照(PRD class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据),每日全量刷 + 手动触发; + * 同步不物理删,转出置 status=0。学生身份口径:用户中心数字ID(UserContext.getUserId()) + * ↔ student_id(SchStudent.UserStuSerID),名册仅存学生ID + 姓名快照。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "class_student", + uniqueConstraints = @UniqueConstraint(name = "uk_class_student", columnNames = {"class_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ClassStudent extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; + + /** 年级名快照 */ + @Column(name = "grade_name", length = 50) + private String gradeName; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 在班状态:1 在班 / 0 已转出退课 */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** 离班时间 */ + @Column(name = "leave_at") + private LocalDateTime leaveAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroup.java b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java new file mode 100644 index 0000000..a002e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 联系组。对应原 .NET Contacts_Group 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroup extends BaseEntity { + + /** 组名称 */ + @Column(name = "group_name", length = 200) + private String groupName; + + /** 创建人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "creator_id") + private Integer creatorId; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java new file mode 100644 index 0000000..eb07763 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 联系组-用户关联。对应原 .NET Contacts_Tea 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group_user", + uniqueConstraints = @UniqueConstraint(name = "uk_group_user", + columnNames = {"group_id", "user_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroupUser extends BaseEntity { + + /** 联系组ID */ + @Column(name = "group_id", nullable = false) + private Integer groupId; + + /** 用户ID(基础平台用户ID) */ + @Column(name = "user_id", nullable = false) + private Integer userId; + + /** 用户姓名 */ + @Column(name = "user_name", length = 64) + private String userName; +} diff --git a/src/main/java/com/yunhai/homework/entity/Homework.java b/src/main/java/com/yunhai/homework/entity/Homework.java new file mode 100644 index 0000000..6e615f2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Homework.java @@ -0,0 +1,208 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 课程作业主表。对应原 .NET Notify_Message(已剔除短信/模板/门户等无关字段)。 + * + * 阅读状态不落主表:采用「懒标记」模型——读者首次查看时写入 {@link HomeworkRead}, + * 不存在阅读记录即视为未读,从而无需在发作业时枚举全校用户预插接收行。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Homework extends BaseEntity { + + /** + * 作业标题 + */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** + * 学校ID(多租户隔离:发布时从当前用户角色填充,查询时按校过滤)。 + */ + @Column(name = "sch_id") + private Integer schId; + + /** + * 作业内容 + */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** + * 作业类型:1 普通 / 2 重要(见 {@link com.yunhai.homework.enums.HomeworkTypeEnum}) + */ + @Column(name = "type", nullable = false) + private Integer type = 1; + + /** + * 发布人ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "publisher_id") + private Integer publisherId; + + /** + * 发布人姓名 + */ + @Column(name = "publisher_name", length = 64) + private String publisherName; + + /** + * 发布部门ID(原 DeptID) + */ + @Column(name = "dept_id", length = 64) + private String deptId; + + /** + * 发布部门名称 + */ + @Column(name = "dept_name", length = 128) + private String deptName; + + /** + * 状态:1 已发布(无草稿态,见 {@link com.yunhai.homework.enums.HomeworkStatusEnum}) + */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** + * 是否置顶:0 否 / 1 是(原 topmost) + */ + @Column(name = "topmost") + private Integer topmost = 0; + + // ==================== 迁移补全字段(对应 C# Notify_Message) ==================== + + /** + * 接收人列表(JSON 数组,如 ["10012","10013"] 或接收组ID列表)。 + * 对应 C# Notify_Message.ReceiveList + */ + @Column(name = "receive_list", columnDefinition = "LONGTEXT") + private String receiveList; + + /** + * 密码发送(对应 C# Notify_Message.PwdSend) + */ + @Column(name = "pwd_send", length = 255) + private String pwdSend; + + /** + * 抄送人列表(对应 C# Notify_Message.ChaoSend) + */ + @Column(name = "chao_send", length = 2000) + private String chaoSend; + + /** + * 已读数(发送时初始化为 0,也可由 HomeworkRead 表 COUNT 实时计算)。 + * 对应 C# Notify_Message.readcount + */ + @Column(name = "read_count") + private Integer readCount = 0; + + /** + * 应读数(接收人总数)。对应 C# Notify_Message.allowcount + */ + @Column(name = "allow_count") + private Integer allowCount = 0; + + /** + * 父作业ID(转发时关联原作业)。对应 C# Notify_Message.fkid + */ + @Column(name = "parent_id") + private Integer parentId; + + /** + * 回复ID。对应 C# Notify_Message.reid + */ + @Column(name = "reply_id", length = 255) + private String replyId; + + /** + * 是否撤回:0 否 / 1 是。对应 C# Notify_Message.ispull + */ + @Column(name = "is_pulled") + private Integer isPulled = 0; + + /** + * 定时发送时间(到时间后自动发布)。对应 C# Notify_Message.DsfsTime。 + * PRD 语义中的 scheduled_at 复用本列。 + */ + @Column(name = "timing_send_time") + private LocalDateTime timingSendTime; + + /** + * 短信发送标记:0 不发短信 / 1 发短信。对应 C# Notify_Message.dxbit + */ + @Column(name = "sms_flag") + private Integer smsFlag = 0; + + // ==================== 课堂作业 V3(PRD V1.0)扩展列 ==================== + + /** + * 学科(字典值)。 + */ + @Column(name = "subject", length = 50) + private String subject; + + /** + * 截止时间(精确到分钟;空 = 仅查阅型,不设截止,到点关闭任务跳过)。 + */ + @Column(name = "deadline_at") + private LocalDateTime deadlineAt; + + /** + * 作业流程状态(PRD 五态,见 {@link com.yunhai.homework.enums.HwStatusEnum})。 + * notice 原 status 列继续承载发布/撤回语义,两者并存。 + */ + @Column(name = "hw_status", nullable = false, length = 20) + private String hwStatus = com.yunhai.homework.enums.HwStatusEnum.DRAFT_UNPUBLISHED.getCode(); + + /** + * 发布时间(立即发布或定时到点发布时写入)。 + */ + @Column(name = "published_at") + private LocalDateTime publishedAt; + + /** + * 提交方式,JSON 数组文本,如 ["image","attach"]。 + * 取值:text / image / attach / none(PRD 的 图片/文件/两者 映射为 + * ["image"] / ["attach"] / ["image","attach"])。 + */ + @Column(name = "submit_type", length = 100) + private String submitType; + + /** + * 截止后是否允许逾期提交:0 否 / 1 是。 + */ + @Column(name = "allow_late", nullable = false) + private Integer allowLate = 0; + + /** + * 提醒设置:截止前 1 天提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1d", nullable = false) + private Integer remind1d = 1; + + /** + * 提醒设置:截止前 1 小时提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1h", nullable = false) + private Integer remind1h = 1; + + /** + * 归档时间(教师手动归档时写入;归档后只读,所有提醒任务跳过)。 + */ + @Column(name = "archived_at") + private LocalDateTime archivedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java new file mode 100644 index 0000000..6fc7804 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业附件表(PRD homework_attachment)。文件实体仍存 up_file(file_id 关联 + file_url 冗余)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_attachment") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkAttachment extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** up_file.id(可空:允许外部 URL 附件) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file / ... */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号(越小越靠前) */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkClass.java b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java new file mode 100644 index 0000000..c01cefb --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 作业-班级关联表(PRD homework_class)。 + * 发布/编辑时由 receiveList("all" 或班级 id 串)解析写入,是应交名单与统计的权威关联。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_class", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_class", columnNames = {"homework_id", "class_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkClass extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkRead.java b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java new file mode 100644 index 0000000..35b773a --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.time.LocalDateTime; + +/** + * 作业阅读记录(懒标记)。对应原 .NET Notify_ReceiveList 的「逐用户阅读状态」语义, + * 但仅在读者首次查看时落库,避免发作业时预插全员。 + * + * 唯一约束 (homework_id, reader_id):同一用户对同一作业只记一条已读。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read", + uniqueConstraints = @UniqueConstraint( + name = "uk_homework_reader", + columnNames = {"homework_id", "reader_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkRead extends BaseEntity { + + /** + * 作业ID(关联 homework.id) + */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** + * 读者ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "reader_id", nullable = false) + private Integer readerId; + + /** + * 读者姓名 + */ + @Column(name = "reader_name", length = 64) + private String readerName; + + /** + * 阅读时间 + */ + @Column(name = "read_time") + private LocalDateTime readTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java new file mode 100644 index 0000000..8179e79 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java @@ -0,0 +1,58 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 学生作业阅读记录(PRD homework_read_record)。 + * + * 学生维度,含首次/最近阅读时间,与 notice 语义的 {@link HomeworkRead}(教师/阅读者已读)互不影响。 + * 行仅在判定已读时写入;未读名单 = 名册左连本表(无记录或 is_read=0)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read_record", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student", columnNames = {"homework_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReadRecord extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", length = 64) + private String classId; + + /** 阅读状态:0 UNREAD / 1 READ */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 首次阅读时间(写入后不再变更) */ + @Column(name = "first_read_at") + private LocalDateTime firstReadAt; + + /** 最近阅读时间(每次上报刷新) */ + @Column(name = "last_read_at") + private LocalDateTime lastReadAt; + + /** 阅读次数 */ + @Column(name = "read_count", nullable = false) + private Integer readCount = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java new file mode 100644 index 0000000..97e068b --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java @@ -0,0 +1,45 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 作业接收人关联表(替代旧 receive_list JSON 字符串存储)。 + * + * 每条记录代表一个作业的一个接收人(用户ID + 姓名快照,不含账号)。 + * 全员作业(receive_list="ALL")不写明细行, + * 读取时按 receive_list="ALL" 标识走旧逻辑兜底。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_receiver", + indexes = { + @Index(name = "idx_homework_id", columnList = "homework_id"), + @Index(name = "idx_receiver", columnList = "receiver_id") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReceiver extends BaseEntity { + + /** 作业ID(关联 homework.id) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 接收人ID(用户中心数字ID) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收类型:1 接收 / 2 抄送 */ + @Column(name = "receive_type", nullable = false) + private Integer receiveType = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java new file mode 100644 index 0000000..4626044 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java @@ -0,0 +1,98 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.enums.ReviewStatusEnum; +import com.yunhai.homework.enums.SubmitStatusEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 作业提交表,多版本建模(PRD homework_submission)。 + * + * 同一学生 (homework_id, student_id) 下 version 单调递增;提交/打回重交/撤回均新增行, + * 旧行 is_latest 置 0 保留历史;is_latest=1 的行是当前有效版本。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_submission", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student_ver", + columnNames = {"homework_id", "student_id", "version"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkSubmission extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", nullable = false, length = 50) + private String className; + + /** 提交版本,同一学生单调递增(撤回/重交均 +1) */ + @Column(name = "version", nullable = false) + private Integer version = 1; + + /** 是否当前版本:1 是 / 0 历史 */ + @Column(name = "is_latest", nullable = false) + private Integer isLatest = 1; + + /** 提交状态(见 {@link SubmitStatusEnum}) */ + @Column(name = "status", nullable = false, length = 20) + private String status = SubmitStatusEnum.SUBMITTED.getCode(); + + /** 是否逾期:0 否 / 1 是 */ + @Column(name = "is_late", nullable = false) + private Integer isLate = 0; + + /** 提交时间(NOT_SUBMITTED 空行为空) */ + @Column(name = "submitted_at") + private LocalDateTime submittedAt; + + /** 文本作业内容 */ + @Column(name = "text", columnDefinition = "TEXT") + private String text; + + /** 评分 */ + @Column(name = "score", precision = 6, scale = 2) + private BigDecimal score; + + /** 评语(打回时 = 订正原因,必填) */ + @Column(name = "comment", columnDefinition = "TEXT") + private String comment; + + /** 批阅状态(见 {@link ReviewStatusEnum}) */ + @Column(name = "review_status", nullable = false, length = 20) + private String reviewStatus = ReviewStatusEnum.PENDING.getCode(); + + /** 批阅人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "reviewed_by") + private Integer reviewedBy; + + /** 批阅人姓名快照 */ + @Column(name = "reviewed_by_name", length = 64) + private String reviewedByName; + + /** 批阅时间 */ + @Column(name = "reviewed_at") + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java new file mode 100644 index 0000000..f088c92 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板。对应原 .NET Notify_Template 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_template") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkTemplate extends BaseEntity { + + /** 模板标题 */ + @Column(name = "title", length = 200) + private String title; + + /** 模板内容(HTML) */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** 封面图片文件ID */ + @Column(name = "cover") + private Integer cover; + + /** 引用次数 */ + @Column(name = "ref_count") + private Integer refCount = 0; + + /** 模板类型ID */ + @Column(name = "type_id") + private Integer typeId; +} diff --git a/src/main/java/com/yunhai/homework/entity/Notification.java b/src/main/java/com/yunhai/homework/entity/Notification.java new file mode 100644 index 0000000..2a931a1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Notification.java @@ -0,0 +1,71 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 站内通知(PRD notification)。六类通知的统一落表 + 已读状态。 + * 通知已读独立于作业阅读记录(本表 is_read 只反映通知列表已读)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "notification", + indexes = { + @Index(name = "idx_receiver", columnList = "receiver_id, is_read"), + @Index(name = "idx_hw_type", columnList = "homework_id, type") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Notification extends BaseEntity { + + /** 通知类型(见 {@link com.yunhai.homework.enums.NotificationTypeEnum}) */ + @Column(name = "type", nullable = false, length = 30) + private String type; + + /** 关联作业ID */ + @Column(name = "homework_id") + private Integer homeworkId; + + /** 关联业务ID(如 submissionId) */ + @Column(name = "related_id") + private Integer relatedId; + + /** 通知标题 */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** 通知内容 */ + @Column(name = "content", length = 1000) + private String content; + + /** 接收人ID(学生 = student_id,教师 = 用户中心数字ID,同一 ID 空间) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收人角色:student / teacher */ + @Column(name = "receiver_role", nullable = false, length = 20) + private String receiverRole = "student"; + + /** 是否已读:0 未读 / 1 已读 */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 已读时间 */ + @Column(name = "read_at") + private LocalDateTime readAt; + + /** 发送通道:inapp 站内 / sms 短信 */ + @Column(name = "send_channel", nullable = false, length = 20) + private String sendChannel = "inapp"; +} diff --git a/src/main/java/com/yunhai/homework/entity/SchClassTeach.java b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java new file mode 100644 index 0000000..d1a091f --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java @@ -0,0 +1,46 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 班级任教关系快照(bschclass sch_class_teach 本地镜像,PRD 口径同 class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据);bschclass 任教接口 + * 不可用时,任教学科链路降级读本表。教师口径:用户中心数字ID(UserContext.getUserId()); + * class_id 与 class_student.class_id 同一取值域。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sch_class_teach", + uniqueConstraints = @UniqueConstraint(name = "uk_sch_class_teach", columnNames = {"school_id", "class_id", "tea_id", "sub_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SchClassTeach extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID(与 class_student.class_id 同一取值域) */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 教师ID(用户中心数字ID) */ + @Column(name = "tea_id", nullable = false) + private Integer teaId; + + /** 学科编码(bschclass SubID;本地无字典时编码即展示名) */ + @Column(name = "sub_id", nullable = false, length = 64) + private String subId; + + /** 有效状态:1 有效 / 0 失效 */ + @Column(name = "status", nullable = false) + private Integer status = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsModule.java b/src/main/java/com/yunhai/homework/entity/SmsModule.java new file mode 100644 index 0000000..3f4d444 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsModule.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 短信模块配置。对应原 .NET SchSms 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_module") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsModule extends BaseEntity { + + /** 模块名称 */ + @Column(name = "module_name", length = 100) + private String moduleName; + + /** 模块标识 */ + @Column(name = "module_code", length = 50) + private String moduleCode; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java new file mode 100644 index 0000000..428d271 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java @@ -0,0 +1,37 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 学校短信开通配置。对应原 .NET SchSms_Set 表。 + * 记录某学校某模块是否开通短信。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_school_config") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsSchoolConfig extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 学校名称 */ + @Column(name = "school_name", length = 200) + private String schoolName; + + /** 模块ID */ + @Column(name = "module_id") + private Integer moduleId; + + /** 是否开通短信 */ + @Column(name = "is_sms") + private Boolean isSms = false; +} diff --git a/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java new file mode 100644 index 0000000..85df7ba --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java @@ -0,0 +1,54 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 提交附件表(PRD submission_attachment)。替代 JSON 方案,支撑矩阵缩略图与批阅大图预览。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "submission_attachment", + indexes = @Index(name = "idx_sub", columnList = "submission_id")) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SubmissionAttachment extends BaseEntity { + + /** 提交行ID(homework_submission.id) */ + @Column(name = "submission_id", nullable = false) + private Integer submissionId; + + /** 作业ID(冗余,便于统计与清理) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + @Column(name = "student_id") + private Integer studentId; + + /** up_file.id(可空:允许外部 URL) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号 */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/SysDict.java b/src/main/java/com/yunhai/homework/entity/SysDict.java new file mode 100644 index 0000000..3091084 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SysDict.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 系统字典(发文部门等)。对应原 .NET Common_DeptDic 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sys_dict") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SysDict extends BaseEntity { + + /** 字典类型(如 "dept"=发文部门) */ + @Column(name = "dict_type", length = 50) + private String dictType; + + /** 字典 key(原 CodeID) */ + @Column(name = "dict_key", length = 100) + private String dictKey; + + /** 字典 value(显示名称,原 Name) */ + @Column(name = "dict_value", length = 200) + private String dictValue; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; + + /** 是否启用:0 禁用 / 1 启用(原 Enable) */ + @Column(name = "enable_flag") + private Integer enableFlag = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/TemplateType.java b/src/main/java/com/yunhai/homework/entity/TemplateType.java new file mode 100644 index 0000000..aab8163 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/TemplateType.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板类型。对应原 .NET Notify_TemplateType 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "template_type") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class TemplateType extends BaseEntity { + + /** 类型名称 */ + @Column(name = "type_name", length = 100) + private String typeName; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/UpFile.java b/src/main/java/com/yunhai/homework/entity/UpFile.java new file mode 100644 index 0000000..38248c7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/UpFile.java @@ -0,0 +1,47 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 附件文件记录。对应原 .NET Common_UpFiles 表。 + * + * 存储文件元信息(真实文件名、存储路径、大小、类型),物理文件存储在 + * application.yml 的 file.upload-path 目录下。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "up_file") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class UpFile extends BaseEntity { + + /** 应用类型(如 "homework"),用于区分不同业务的附件 */ + @Column(name = "topic_type", length = 50) + private String topicType; + + /** 关联主题ID(如作业 serid) */ + @Column(name = "topic_id") + private Integer topicId; + + /** 文件真实名称(用户上传时的原始文件名) */ + @Column(name = "file_true_name", length = 255) + private String fileTrueName; + + /** 文件存储路径(相对路径,如 homework/20260823/uuid.pdf) */ + @Column(name = "file_name", length = 500) + private String fileName; + + /** 文件大小(字节) */ + @Column(name = "file_size") + private Integer fileSize; + + /** 文件后缀名(如 pdf, docx, jpg) */ + @Column(name = "file_type", length = 20) + private String fileType; +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java new file mode 100644 index 0000000..124bec9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业状态。无草稿态:发作业一律置为「已发布」, + * 定时发送通过 timingSendTime + 查询侧「已到点」过滤控制可见性。 + * 对应原 .NET Notify_Message.status(原系统已发布=2,此处归一化为 1)。 + */ +@Getter +public enum HomeworkStatusEnum { + + PUBLISHED(1, "已发布"); + + private final int code; + private final String desc; + + HomeworkStatusEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkStatusEnum of(Integer code) { + if (code == null) { + return PUBLISHED; + } + for (HomeworkStatusEnum e : values()) { + if (e.code == code) { + return e; + } + } + return PUBLISHED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java new file mode 100644 index 0000000..077396d --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业类型。对应原 .NET Notify_Message.type(原系统作业类=2,此处归一化)。 + */ +@Getter +public enum HomeworkTypeEnum { + + NORMAL(1, "普通作业"), + IMPORTANT(2, "重要作业"); + + private final int code; + private final String desc; + + HomeworkTypeEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkTypeEnum of(Integer code) { + if (code == null) { + return NORMAL; + } + for (HomeworkTypeEnum e : values()) { + if (e.code == code) { + return e; + } + } + return NORMAL; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java new file mode 100644 index 0000000..8b10083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业流程状态(PRD 五态,存 homework.hw_status)。 + * + * 与 notice 原 status(Integer,发布/撤回语义)并存:本枚举承载课堂作业业务流转, + * 流转收敛在 HomeworkStateService,条件更新保证幂等。 + */ +@Getter +public enum HwStatusEnum { + + /** 草稿 - 未发布 */ + DRAFT_UNPUBLISHED("DRAFT_UNPUBLISHED", "草稿-未发布"), + /** 草稿 - 定时待发布 */ + DRAFT_SCHEDULED("DRAFT_SCHEDULED", "草稿-定时待发布"), + /** 已发布 - 进行中 */ + PUBLISHED("PUBLISHED", "已发布-进行中"), + /** 已截止 */ + CLOSED("CLOSED", "已截止"), + /** 已归档(只读,不再触发任何提醒) */ + ARCHIVED("ARCHIVED", "已归档"); + + private final String code; + private final String desc; + + HwStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HwStatusEnum of(String code) { + if (code == null) { + return DRAFT_UNPUBLISHED; + } + for (HwStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return DRAFT_UNPUBLISHED; + } + + public boolean isDraft() { + return this == DRAFT_UNPUBLISHED || this == DRAFT_SCHEDULED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java new file mode 100644 index 0000000..df75af1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 站内通知类型(PRD 六类,存 notification.type)。 + */ +@Getter +public enum NotificationTypeEnum { + + /** 作业发布(对应班级学生) */ + HOMEWORK_PUBLISHED("HOMEWORK_PUBLISHED", "作业发布"), + /** 截止提醒(截止前 1 天 / 1 小时) */ + DEADLINE_REMINDER("DEADLINE_REMINDER", "截止提醒"), + /** 未交提醒(教师手动) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未交提醒"), + /** 未读提醒(教师手动) */ + NOT_READ("NOT_READ", "未读提醒"), + /** 批阅完成 */ + REVIEW_DONE("REVIEW_DONE", "批阅完成"), + /** 打回通知 */ + RETURNED("RETURNED", "打回通知"), + /** + * 定时发布失败回退(PRD §17.1 教师泳道要求通知教师,但 PRD 枚举表未列—— + * 按 classwork-homework-plan-v3 §13-2 建议补充,待产品确认命名)。 + */ + SCHEDULE_FAILED("SCHEDULE_FAILED", "定时发布失败"), + /** + * 截止时间变更(PRD §17.1 延长截止要求通知学生,PRD 枚举表未列——同上补充)。 + */ + DEADLINE_CHANGED("DEADLINE_CHANGED", "截止时间变更"); + + private final String code; + private final String desc; + + NotificationTypeEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static NotificationTypeEnum of(String code) { + if (code == null) { + return null; + } + for (NotificationTypeEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return null; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java new file mode 100644 index 0000000..031eddc --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java @@ -0,0 +1,39 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 批阅状态(PRD 三态,存 homework_submission.review_status)。 + * 与 SubmitStatusEnum 同源联动:批阅保存 → status=REVIEWED + review_status=REVIEWED; + * 打回 → status=RETURNED + review_status=RETURNED。 + */ +@Getter +public enum ReviewStatusEnum { + + /** 待批阅 */ + PENDING("PENDING", "待批阅"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"), + /** 已打回 */ + RETURNED("RETURNED", "已打回"); + + private final String code; + private final String desc; + + ReviewStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static ReviewStatusEnum of(String code) { + if (code == null) { + return PENDING; + } + for (ReviewStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return PENDING; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java new file mode 100644 index 0000000..7d6aaca --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java @@ -0,0 +1,50 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 学生提交状态(PRD 五态,存 homework_submission.status)。 + * + * NOT_SUBMITTED 空行仅由「撤回」产生;未提交学生本身不落行(统计时由名册左连补位)。 + */ +@Getter +public enum SubmitStatusEnum { + + /** 未提交(撤回后产生的空提交行) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未提交"), + /** 已提交(含打回后重新提交) */ + SUBMITTED("SUBMITTED", "已提交"), + /** 逾期提交(截止后提交,is_late=1) */ + LATE_SUBMITTED("LATE_SUBMITTED", "逾期提交"), + /** 已打回(待重新提交) */ + RETURNED("RETURNED", "已打回"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"); + + private final String code; + private final String desc; + + SubmitStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static SubmitStatusEnum of(String code) { + if (code == null) { + return NOT_SUBMITTED; + } + for (SubmitStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return NOT_SUBMITTED; + } + + /** + * 是否计入「已交」口径:打回未重交也算已交(PRD 学生矩阵口径)。 + */ + public boolean countsAsSubmitted() { + return this != NOT_SUBMITTED; + } +} diff --git a/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java new file mode 100644 index 0000000..5377254 --- /dev/null +++ b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java @@ -0,0 +1,161 @@ +package com.yunhai.homework.job; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.enums.HwStatusEnum; +import com.yunhai.homework.enums.NotificationTypeEnum; +import com.yunhai.homework.repository.HomeworkClassRepository; +import com.yunhai.homework.repository.HomeworkRepository; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.HomeworkStateService; +import com.yunhai.homework.service.impl.NotificationService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 课堂作业定时任务(PRD §17.1 调度泳道)。 + * + * 全部基于状态机 CAS 条件更新,天然幂等,可安全重复执行; + * ARCHIVED 作业被显式跳过(归档后不再触发任何提醒)。 + */ +@Component +public class HomeworkScheduledJobs { + + private static final Logger log = LoggerFactory.getLogger(HomeworkScheduledJobs.class); + + @Autowired + private HomeworkRepository homeworkRepository; + + @Autowired + private HomeworkClassRepository homeworkClassRepository; + + @Autowired + private HomeworkStateService stateService; + + @Autowired + private NotificationService notificationService; + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + /** + * 定时发布:每分钟扫描到点的 DRAFT_SCHEDULED 作业 → PUBLISHED + 作业发布通知; + * 失败回退 DRAFT_UNPUBLISHED 并站内通知教师。 + */ + @Scheduled(cron = "0 * * * * ?") + public void scheduledPublish() { + List due = homeworkRepository + .findByHwStatusAndTimingSendTimeLessThanEqual(HwStatusEnum.DRAFT_SCHEDULED.getCode(), + LocalDateTime.now()); + if (due.isEmpty()) { + return; + } + log.info("[scheduledPublish] 到点作业 {} 个", due.size()); + for (Homework homework : due) { + Integer id = homework.getId(); + try { + teacherHomeworkV2Service.publishInternal(id, HwStatusEnum.DRAFT_SCHEDULED); + log.info("[scheduledPublish] 发布成功 homeworkId={}", id); + } catch (Exception e) { + log.error("[scheduledPublish] 发布失败,回退草稿 homeworkId={}", id, e); + try { + boolean rolledBack = stateService.rollbackFromSchedule(id); + if (rolledBack) { + notificationService.send(NotificationTypeEnum.SCHEDULE_FAILED, id, null, + homework.getPublisherId(), homework.getPublisherName(), + NotificationService.ROLE_TEACHER, + "定时发布失败:《" + homework.getTitle() + "》", + "定时发布任务执行异常,作业已回退为「草稿-未发布」,请重新发布。"); + } + } catch (Exception ex) { + log.error("[scheduledPublish] 回退失败 homeworkId={}", id, ex); + } + } + } + } + + /** + * 到点截止:每分钟扫描已过截止时间的 PUBLISHED 作业 → CLOSED(无对应通知类型,不推送)。 + */ + @Scheduled(cron = "30 * * * * ?") + public void deadlineClose() { + List overdue = homeworkRepository + .findByHwStatusAndDeadlineAtBefore(HwStatusEnum.PUBLISHED.getCode(), LocalDateTime.now()); + for (Homework homework : overdue) { + if (stateService.cas(homework.getId(), HwStatusEnum.PUBLISHED, HwStatusEnum.CLOSED)) { + log.info("[deadlineClose] 作业已截止 homeworkId={}", homework.getId()); + } + } + } + + /** + * 截止提醒:每分钟扫描截止时间在 24h 内的 PUBLISHED 作业, + * 命中「1 天档 / 1 小时档」窗口且开关开启时向未交学生推送 DEADLINE_REMINDER; + * 档位去重(related_id:1=1 天档 2=1 小时档),每作业每档至多发一轮。 + */ + @Scheduled(cron = "40 * * * * ?") + public void deadlineReminder() { + LocalDateTime now = LocalDateTime.now(); + List window = homeworkRepository + .findByHwStatusAndDeadlineAtBetween(HwStatusEnum.PUBLISHED.getCode(), now, now.plusHours(24)); + for (Homework homework : window) { + if (homework.getDeadlineAt() == null) { + continue; + } + // 1 小时档:截止前 [55, 60) 分钟窗口 + if (homework.getRemind1h() != null && homework.getRemind1h() == 1 + && inWindow(homework.getDeadlineAt(), now, 55, 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 2)) { + sendReminder(homework, 2, "1 小时"); + } + // 1 天档:截止前 [23, 24) 小时窗口 + if (homework.getRemind1d() != null && homework.getRemind1d() == 1 + && inWindow(homework.getDeadlineAt(), now, 23 * 60, 24 * 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 1)) { + sendReminder(homework, 1, "1 天"); + } + } + } + + private boolean inWindow(LocalDateTime deadline, LocalDateTime now, int fromMinutes, int toMinutes) { + long minutes = java.time.Duration.between(now, deadline).toMinutes(); + return minutes >= fromMinutes && minutes < toMinutes; + } + + private void sendReminder(Homework homework, int slot, String slotDesc) { + // 通知接收人直接使用学生ID(名册口径:student_id = 平台用户ID) + List targets = teacherHomeworkV2Service.notSubmittedStudentIds(homework.getId()).stream() + .distinct() + .collect(Collectors.toList()); + if (targets.isEmpty()) { + return; + } + int sent = notificationService.sendBatch(NotificationTypeEnum.DEADLINE_REMINDER, homework.getId(), slot, + NotificationService.distinct(targets), + NotificationService.deadlineReminderTitle(homework), + NotificationService.deadlineReminderContent(homework) + "(剩余不足 " + slotDesc + ")"); + log.info("[deadlineReminder] homeworkId={}, slot={}, sent={}", homework.getId(), slot, sent); + } + + /** + * 名册同步:每日 02:00 全量刷新 class_student(bschclass 接口就绪前仅日志)。 + */ + @Scheduled(cron = "${homework.sync.class-data-cron:0 0 2 * * ?}") + public void classDataSync() { + try { + classStudentService.syncAll(); + } catch (Exception e) { + log.error("[classDataSync] 名册同步失败", e); + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageQuery.java b/src/main/java/com/yunhai/homework/model/BasePageQuery.java new file mode 100644 index 0000000..7f46326 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageQuery.java @@ -0,0 +1,14 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +/** + * 统一分页查询入参。pageIndex 从 1 起。 + */ +@Data +public class BasePageQuery { + private int pageIndex = 1; + private int pageSize = 10; + /** 通用模糊搜索关键词 */ + private String keyword; +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageResult.java b/src/main/java/com/yunhai/homework/model/BasePageResult.java new file mode 100644 index 0000000..058c96c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageResult.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +import java.util.List; + +/** + * 统一分页响应外壳。 + */ +@Data +public class BasePageResult { + /** 总记录数 */ + private int total; + /** 当前页数据 */ + private List content; + /** 当前页码(从 1 起) */ + private int pageIndex; + /** 每页条数 */ + private int pageSize; + /** 总页数 */ + private int totalPages; + /** 是否还有下一页 */ + private boolean hasNext; + + public BasePageResult(List content, int total, int pageIndex, int pageSize, int totalPages, boolean hasNext) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + } +} diff --git a/src/main/java/com/yunhai/homework/model/SchTeaList.java b/src/main/java/com/yunhai/homework/model/SchTeaList.java new file mode 100644 index 0000000..80f9b9c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/SchTeaList.java @@ -0,0 +1,106 @@ +package com.yunhai.homework.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * 班级管理接口-教师(bschclass {@code /oapi/DbClass/GetSchteaList} 响应项)。 + * 对应 C# BestSchool.Entity.Model.SchTeaList。 + */ +public class SchTeaList { + + /** 学校 GUID(SchSerID) */ + @JsonProperty("SchSerID") + private String schSerID; + + @JsonProperty("TeaTel") + private String teaTel; + + /** 教师姓名 */ + @JsonProperty("TeaName") + private String teaName; + + /** 教师用户ID */ + @JsonProperty("UserTeaSerID") + private Integer userTeaSerID; + + @JsonProperty("TeaIDCard") + private String teaIDCard; + + /** 教师工号 */ + @JsonProperty("TeaNo") + private String teaNo; + + /** 教师所属组ID列表(兼容旧字段 GroupIds) */ + @JsonProperty("GroupIds") + private List groupIds; + + /** 教师所属组ID(接口现行字段 GroupID,单个) */ + @JsonProperty("GroupID") + private Integer groupId; + + public Integer getGroupId() { + return groupId; + } + + public void setGroupId(Integer groupId) { + this.groupId = groupId; + } + + public String getSchSerID() { + return schSerID; + } + + public void setSchSerID(String schSerID) { + this.schSerID = schSerID; + } + + public String getTeaTel() { + return teaTel; + } + + public void setTeaTel(String teaTel) { + this.teaTel = teaTel; + } + + public String getTeaName() { + return teaName; + } + + public void setTeaName(String teaName) { + this.teaName = teaName; + } + + public Integer getUserTeaSerID() { + return userTeaSerID; + } + + public void setUserTeaSerID(Integer userTeaSerID) { + this.userTeaSerID = userTeaSerID; + } + + public String getTeaIDCard() { + return teaIDCard; + } + + public void setTeaIDCard(String teaIDCard) { + this.teaIDCard = teaIDCard; + } + + public String getTeaNo() { + return teaNo; + } + + public void setTeaNo(String teaNo) { + this.teaNo = teaNo; + } + + public List getGroupIds() { + return groupIds; + } + + public void setGroupIds(List groupIds) { + this.groupIds = groupIds; + } +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java new file mode 100644 index 0000000..cf9c417 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 班级 DTO(/oapi/VSchClass/GetVClassList,SchClass 视图)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchClassDto { + + /** 班级业务ID(sch_class.class_id,scopeType=班级 的 scopeId;学生 ClassID 指向它) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级名称 */ + @JsonProperty("ClassName") + private String className; + + /** 年级ID(= gradeId) */ + @JsonProperty("Levell") + private Integer gradeId; + + /** 排序号 */ + @JsonProperty("sx") + private Integer sort; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java new file mode 100644 index 0000000..01ff15b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 年级 DTO(/oapi/Schoolen/GetSchLenLevellist,BaseSchsystemGrade)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchGradeDto { + + /** 年级ID(=gradeId,scopeType=年级 的 scopeId;班级 Levell 指向它) */ + @JsonProperty("Id") + private Integer id; + + /** 年级名称(gradeNickname,如「初一」) */ + @JsonProperty("Name") + private String name; + + /** 所属学段(学制)ID */ + @JsonProperty("OwnerId") + private Integer ownerId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java new file mode 100644 index 0000000..06185b6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校信息 DTO(/oapi/SchInfo/GetUserSchFirst)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchInfoDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerId") + private String schSerId; + + /** 用户中心学校组ID(与 CampusNotice schId 同域) */ + @JsonProperty("UserSchSerId") + private Integer userSchSerId; + + /** 学校名称 */ + @JsonProperty("SchName") + private String schName; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java new file mode 100644 index 0000000..f96913d --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学生 DTO(/oapi/OAStu/*,SchStudent 视图)。 + * account 仅为上游接口契约字段(兼容反序列化),本系统落库/身份映射不再使用; + * 学生身份以 stuId(UserStuSerID,= 平台用户ID)+ stuName 承载。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchStudentDto { + + /** 学员登录账号(上游契约字段,仅兼容保留,不落库) */ + @JsonProperty("Account") + private String account; + + /** 学生ID */ + @JsonProperty("UserStuSerID") + private Integer stuId; + + /** 学生姓名 */ + @JsonProperty("StuName") + private String stuName; + + /** 所在班级ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 所在组ID */ + @JsonProperty("GroupID") + private Integer groupId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java new file mode 100644 index 0000000..ff7add8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校学科 DTO(/oapi/SubInfo/GetSchSublist)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSubjectDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 学科ID(学科编码;字典无编码时为字典ID字符串) */ + @JsonProperty("SubID") + private String subId; + + /** 学科名称 */ + @JsonProperty("SubName") + private String subName; + + /** 排序号 */ + @JsonProperty("No") + private Integer no; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java new file mode 100644 index 0000000..f0df157 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学段(学制)DTO(/oapi/Schoolen/GetSchLenlist,BaseSchsystem)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSystemDto { + + /** 学制ID(scopeType=学部 的 scopeId) */ + @JsonProperty("Id") + private Integer id; + + /** 学段名称(如「小学部」「初中部」) */ + @JsonProperty("Name") + private String name; + + /** 学制类型 */ + @JsonProperty("Type") + private Integer type; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java new file mode 100644 index 0000000..86ec6f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Date; + +/** + * bschclass 任教关系 DTO(/oapi/TeachInfo/*,sch_class_teach 视图)。 + * 一条记录 = 某学校某班级中某教师任教某学科。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTeachDto { + + /** 任教记录ID(sch_class_teach.id) */ + @JsonProperty("SerID") + private Integer serId; + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 教师ID(= 平台用户ID) */ + @JsonProperty("UserTeaSerID") + private Integer teaId; + + /** 学科ID(学科编码) */ + @JsonProperty("SubID") + private String subId; + + /** 班级业务ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级ID(与 ClassID 同值,上游兼容字段) */ + @JsonProperty("UserClassID") + private Integer userClassId; + + /** 创建时间 */ + @JsonProperty("CreationTime") + private Date creationTime; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java new file mode 100644 index 0000000..29623ad --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java @@ -0,0 +1,41 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * bschclass 组织树节点 DTO(/oapi/SchGroup/GetTeaTree、GetStuTree,TeaTreeVO 视图)。 + * type=1 组节点(学校/学段/年级/班级、教师组),type=2 人员叶子(学生/教师)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTreeNodeDto { + + /** 节点ID(学校/学段/年级/班级为对应业务ID,叶子为学生 stuId 或教师 teaId) */ + private Integer id; + + /** 节点名称 */ + private String name; + + /** 父节点ID(根节点为 0) */ + private Integer pid; + + /** 排序号(组取 sort,叶子固定 0) */ + private Integer no; + + /** 1=组节点,2=人员叶子 */ + private Integer type; + + /** 是否父节点(组 true,叶子 false) */ + @JsonProperty("isParent") + private Boolean isParent; + + /** 头像(当前固定空串) */ + private String photo; + + /** 子节点(叶子为 null,组节点非 null) */ + private List children; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java new file mode 100644 index 0000000..7621af2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java @@ -0,0 +1,81 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 作业详情视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class HomeworkDetailVo { + + @JsonProperty("serid") + private Integer serid; + + @JsonProperty("title") + private String title; + + @JsonProperty("Contents") + private String contents; + + @JsonProperty("DeptID") + private String deptId; + + @JsonProperty("DeptName") + private String deptName; + + @JsonProperty("TeaName") + private String publisherName; + + @JsonProperty("EditDate") + private String editDate; + + @JsonProperty("numcount") + private Integer numCount; + + @JsonProperty("readnumcount") + private Long readNumCount; + + @JsonProperty("ReceiveList") + private String receiveList; + + @JsonProperty("DsfsTime") + private String dsfsTime; + + @JsonProperty("isDsfs") + private Boolean isDsfs; + + @JsonProperty("ChaoSend") + private List chaoSend; + + /** 接收人列表:"all" 或用户ID列表 */ + @JsonProperty("tlist") + private Object tlist; + + /** + * 接收对象可读文本("全部班级" 或班级名顿号串)。 + * 详情页「接收班级」标签直接展示,避免前端拿 ReceiveList 的 id 串渲染。 + */ + @JsonProperty("receiveText") + private String receiveText; + + /** + * 附件项(旧 ChaoSend 数组元素)。 + */ + @Data + public static class FileItem { + @JsonProperty("id") + private Integer id; + @JsonProperty("name") + private String name; + @JsonProperty("size") + private Integer size; + @JsonProperty("fileid") + private Integer fileid; + @JsonProperty("filename") + private String filename; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java new file mode 100644 index 0000000..e1c2fa2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 作业分页结果(扩展 BasePageResult,额外携带 unreadCount)。 + * + * 对齐方案 M7:unreadCount 并入分页外壳扩展子类,不改基类契约。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkPageResult extends BasePageResult { + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkPageResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + super(content, total, pageIndex, pageSize, totalPages, hasNext); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java new file mode 100644 index 0000000..afea9a6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +/** + * 作业列表查询条件。 + * + * - 管理端(adminList):用 status / type / deptId / publisherId / 时间范围过滤; + * - 用户端(userList):额外用 readType 过滤未读/已读(0 全部 / 1 未读 / 2 已读)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkQuery extends BasePageQuery { + + /** 状态过滤:null 不过滤;1 已发布 */ + private Integer status; + + /** 类型过滤:1 普通 / 2 重要 */ + private Integer type; + + /** 列表范围(管理端):"user" 只看自己发布的,null 看全部 */ + private String scope; + + /** 发布部门ID */ + private String deptId; + + /** 发布人ID(用户中心数字ID,管理端按发布人查) */ + private Integer publisherId; + + /** 开始时间(创建时间 >=,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startTime; + + /** 结束时间(创建时间 <= 当天 23:59:59,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endTime; + + /** 阅读状态过滤(仅用户端):0 全部 / 1 未读 / 2 已读 */ + private Integer readType = 0; + + /** 排序:1 时间倒序(默认)/ 2 时间正序 */ + private String orderByTime = "1"; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java new file mode 100644 index 0000000..2cb560f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java @@ -0,0 +1,65 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import com.yunhai.homework.common.validation.ValidationGroups; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 作业发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。 + */ +@Data +public class HomeworkRequest { + + /** 作业标题 */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业标题不得为空") + private String title; + + /** 作业内容 */ + private String content; + + /** 作业类型:1 普通 / 2 重要 */ + @NotNull(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业类型不得为空") + private Integer type = 1; + + /** 发布部门ID(原 DeptID,必填——对应原系统「发布部门不得为空」校验) */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "发布部门不得为空") + private String deptId; + + /** 发布部门名称 */ + private String deptName; + + /** 是否置顶:0 否 / 1 是 */ + private Integer topmost = 0; + + /** 接收人列表(JSON 数组字符串,如 ["10012","10013"]——用户中心数字ID,或 "ALL" 表示全员) */ + private String receiveList; + + /** 附件文件ID列表(逗号分隔串,如 "1,2,3");旧名 chaoSend,已更名 fileIds(M6) */ + private String fileIds; + + /** 定时发送时间(为空表示立即发布) */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timingSendTime; + + /** 短信发送标记:0 不发短信 / 1 发短信(旧名 topmost 实指发短信,已正名 smsFlag,M6) */ + private Integer smsFlag = 0; + + /** 转发源作业ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */ + private Integer sourceId; + + // ==================== 课堂作业 V1/V2 扩展(前端契约字段,兼容层同步写新列) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deadline; + + /** 提交方式:text/image/attach/none 组合 */ + private java.util.List submitType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java new file mode 100644 index 0000000..c492f86 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java @@ -0,0 +1,113 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 作业统计视图(对应原系统 GetStatisData / 按发布人统计)。 + */ +@Data +public class HomeworkStatisticsVo { + + /** 作业总数(全部已发布) */ + private long total; + + /** 已发布数 */ + private long publishedCount; + + /** 阅读记录总数(累计已读人次) */ + private long totalReadRecords; + + /** 当前用户未读数(已到点的已发布 - 当前用户已读) */ + private long unreadForCurrentUser; + + /** 按发布人统计 */ + private List byPublisher; + + /** 按月统计(已发布作业数) */ + private List byMonth; + + // ==================== 课程作业口径(前端 /stats 页消费) ==================== + + /** 我布置的作业数(当前登录人发布的全部作业,含草稿) */ + private long myAssignCount; + + /** 我的作业待批改总量(已交未批的当前版本行合计) */ + private long pendingGrade; + + /** 各作业平均提交率(%,仅统计需收集提交的作业;无样本时为 0) */ + private double avgSubmitRate; + + /** 按作业的提交与批改进度明细(草稿与仅查阅型不计) */ + private List byHomework = new ArrayList<>(); + + /** 已批阅提交的分数分布(四档) */ + private List scoreDist = new ArrayList<>(); + + /** 单作业进度条目(口径对齐前端 HomeworkProgress 契约) */ + @Data + public static class HomeworkStat { + private Integer id; + private String title; + private String subject; + /** 旧契约四态派生值:draft / collecting / closed / done */ + private String hwStatus; + private String hwStatusDesc; + /** 应交人数 */ + private long should; + /** 已读人数 */ + private long read; + /** 已交人数 */ + private long submitted; + /** 已批阅人数 */ + private long graded; + /** 待批改人数 */ + private long pendingGrade; + /** 未交人数 */ + private long unsubmitted; + /** 提交率(%) */ + private double submitRate; + /** 批阅率(%) */ + private double gradeRate; + } + + /** 分数分布档(key 供前端配色映射:excellent/good/pass/fail) */ + @Data + public static class ScoreBucket { + private String label; + private String key; + private long count; + + public ScoreBucket(String label, String key, long count) { + this.label = label; + this.key = key; + this.count = count; + } + } + + @Data + public static class PublisherStat { + private String publisherName; + private long count; + + public PublisherStat(String publisherName, long count) { + this.publisherName = publisherName; + this.count = count; + } + } + + @Data + public static class MonthStat { + private int year; + private int month; + private long count; + + public MonthStat(int year, int month, long count) { + this.year = year; + this.month = month; + this.count = count; + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java new file mode 100644 index 0000000..c906e2e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.List; + +/** + * 用户端作业列表结果:在分页数据之外额外携带「当前用户未读总数」, + * 供前端展示未读角标(对应原系统 OtherData.unReadCount)。 + */ +@Data +public class HomeworkUserListResult { + + private int total; + private List content; + private int pageIndex; + private int pageSize; + private int totalPages; + private boolean hasNext; + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkUserListResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java new file mode 100644 index 0000000..333d49e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java @@ -0,0 +1,92 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 作业视图对象(列表/详情返回)。 + */ +@Data +public class HomeworkVo { + + private Integer id; + private String title; + private String content; + private Integer type; + private String typeDesc; + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + private String publisherName; + private String deptId; + private String deptName; + private Integer status; + private String statusDesc; + private Integer topmost; + + /** 已读人数(基于阅读记录实时统计) */ + private Long readCount = 0L; + + /** 应读人数 */ + private Integer allowCount; + + /** 当前用户是否已读(用户端列表使用;管理端恒为 false 或不关注) */ + private boolean read = false; + + /** 是否撤回:0 否 / 1 是 */ + private Integer isPulled; + + /** 定时发送时间 */ + private LocalDateTime timingSendTime; + + /** 父作业ID(转发时关联原作业) */ + private Integer parentId; + + /** 短信发送标记 */ + private Integer smsFlag; + + /** 接收人列表(JSON) */ + private String receiveList; + + /** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */ + private String UpFile; + + // ==================== 课堂作业 V1/V2 扩展(前端 types/homework.ts 契约) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间(前端契约字段名 deadline) */ + private LocalDateTime deadline; + + /** 提交方式数组:text/image/attach/none */ + private java.util.List submitType; + + /** 作业流程状态——旧契约四态派生值(draft/collecting/closed/done),权威五态见 homework.hw_status */ + private String hwStatus; + + private String hwStatusDesc; + + /** + * 提交/批改进度(管理端列表页「提交进度 / 未读未交 / 批改」列使用)。 + * 草稿或仅查阅型为 null;列表场景由 HomeworkProgressCalculator 批量聚合回填。 + */ + private com.yunhai.homework.model.v2.ProgressVo progress; + + private String createBy; + private LocalDateTime createTime; + private String updateBy; + private LocalDateTime updateTime; + + /** + * 阅读明细视图(阅读列表查询用)。 + */ + @Data + public static class ReadDetail { + /** 读者ID(用户中心数字ID) */ + private Integer id; + private String name; + private boolean read; + private LocalDateTime readTime; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java new file mode 100644 index 0000000..15ece01 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 一键已读结果视图对象(替代原 Map<String,Object> 返回)。 + */ +@Data +public class InitializeRedVo { + + @JsonProperty("count") + private Integer count; + + public InitializeRedVo() {} + + public InitializeRedVo(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java new file mode 100644 index 0000000..8127480 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java @@ -0,0 +1,30 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 文件上传结果视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class UpFileResultVo { + + @JsonProperty("fileid") + private Integer fileid; + + @JsonProperty("name") + private String name; + + @JsonProperty("fileurl") + private String fileurl; + + @JsonProperty("host") + private String host; + + @JsonProperty("type") + private String type; + + @JsonProperty("size") + private Integer size; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java new file mode 100644 index 0000000..76dfa2f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +/** + * 附件视图对象(对应原 .NET FileObj)。 + */ +@Data +public class UpFileVo { + + /** 文件记录ID */ + private Integer id; + + /** 文件真实名称 */ + private String fileTrueName; + + /** 文件存储路径(相对路径) */ + private String fileName; + + /** 文件大小(字节) */ + private Integer fileSize; + + /** 文件后缀名 */ + private String fileType; + + /** 关联主题ID */ + private Integer topicId; + + /** 应用类型 */ + private String topicType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java new file mode 100644 index 0000000..771eb3a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java @@ -0,0 +1,122 @@ +package com.yunhai.homework.model.homework; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户可访问模块(左菜单)视图对象。 + * 将基础平台返回的扁平 {@code List} 转换为前端 layout(参考 UserBase)期望的树结构: + * {@code modId}(路由高亮匹配)/{@code page}(跳转路径)/{@code children}(子菜单)。 + */ +public class UserModuleVo { + + /** 模块主键 id(基础平台 module id) */ + private Integer id; + + /** 菜单高亮匹配用:等价于后端模块 id */ + private Integer modId; + + /** 模块名称 */ + private String name; + + /** 父级模块 id(0 表示根节点) */ + private Integer parentId; + + /** 前端路由路径(由 BoaModule.path 映射而来) */ + private String page; + + /** 层级(0=根分组) */ + private Integer level; + + /** 菜单图标 */ + private String icon; + + /** 排序号 */ + private Integer orderNumber; + + /** 是否可用 */ + private Boolean usable; + + /** 子菜单 */ + private List children = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getModId() { + return modId; + } + + public void setModId(Integer modId) { + this.modId = modId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Integer getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(Integer orderNumber) { + this.orderNumber = orderNumber; + } + + public Boolean getUsable() { + return usable; + } + + public void setUsable(Boolean usable) { + this.usable = usable; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java new file mode 100644 index 0000000..bedf7b5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 附件条目(作业附件与提交附件共用)。fileUrl 必传;fileId 为 file/Upload 产物时回传。 + */ +@Data +public class AttachmentItem { + + /** up_file.id */ + private Integer fileId; + + /** 文件访问 URL */ + private String fileUrl; + + /** 文件类型:image / file */ + private String fileType; + + /** 文件名 */ + private String fileName; + + /** 排序号 */ + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassItem.java b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java new file mode 100644 index 0000000..459ea80 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java @@ -0,0 +1,18 @@ +package com.yunhai.homework.model.v2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 班级条目(作业参与班级展示)。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClassItem { + + private String classId; + + private String className; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java new file mode 100644 index 0000000..c0f400b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 年级→班级树(发布作业选班 + 应交名单基数来源)。 + * 叶子节点 id = 班级 id(class_student 快照聚合)。 + */ +@Data +public class ClassTreeNode { + + /** 年级节点为 gradeName,班级叶子为 classId */ + private String id; + + /** 展示名 */ + private String name; + + /** 是否父节点(年级) */ + private Boolean isParent; + + /** 叶子节点:在班学生数 */ + private Integer studentCount; + + private List children = new ArrayList<>(); +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java new file mode 100644 index 0000000..f8c5dec --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 延长截止请求(新截止时间必须晚于当前时间)。 + */ +@Data +public class ExtendRequest { + + @NotNull(message = "新截止时间不得为空") + private LocalDateTime deadlineAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java new file mode 100644 index 0000000..1ac46f8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java @@ -0,0 +1,36 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 作业详情汇总统计卡片(PRD §18 页 4 stats 接口出参)。 + */ +@Data +public class HomeworkStatsVo { + + private Integer homeworkId; + + private String title; + + private String hwStatus; + + private String hwStatusDesc; + + /** 总进度 */ + private ProgressVo total; + + /** 分班进度 */ + private List byClass; + + @Data + public static class ByClass { + + private String classId; + + private String className; + + private ProgressVo progress; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java new file mode 100644 index 0000000..9464910 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 创建/编辑作业请求(PRD §18 页 1 表单)。 + * 创建一律落草稿(DRAFT_UNPUBLISHED),发布/定时走独立接口。 + */ +@Data +public class HomeworkV2Request { + + /** 作业标题(必填 ≤50 字) */ + @NotBlank(message = "作业标题不得为空") + @Size(max = 50, message = "作业标题不超过50字") + private String title; + + /** 学科(字典值) */ + private String subject; + + /** 作业说明(富文本) */ + private String description; + + /** 参与班级 id 列表 */ + private List classIds; + + /** 附件列表 */ + private List attachments; + + /** 截止时间(精确到分钟;空 = 仅查阅型) */ + private LocalDateTime deadlineAt; + + /** 提交方式:image / attach / text / none 的组合(PRD 图片/文件/两者) */ + private List submitType; + + /** 截止后是否允许逾期提交:0 否 / 1 是 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天提醒(0 关 / 1 开) */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时提醒(0 关 / 1 开) */ + private Integer remind1h; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java new file mode 100644 index 0000000..8bf1b02 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java @@ -0,0 +1,73 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 作业视图对象 V2(草稿箱/已发布列表/详情共用)。 + * 列表场景 attachments/classes 可按需填充;progress 仅非草稿且非仅查阅型作业返回。 + */ +@Data +public class HomeworkV2Vo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 作业说明(富文本;列表场景可置空省流) */ + private String description; + + /** 作业状态(PRD 五态) */ + private String hwStatus; + + private String hwStatusDesc; + + /** 定时发布时间(复用 timing_send_time) */ + private LocalDateTime scheduledAt; + + /** 发布时间 */ + private LocalDateTime publishedAt; + + /** 截止时间 */ + private LocalDateTime deadlineAt; + + /** 归档时间 */ + private LocalDateTime archivedAt; + + /** 提交方式数组 */ + private List submitType; + + /** 截止后允许逾期提交:0/1 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天 */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时 */ + private Integer remind1h; + + /** 参与班级 */ + private List classes; + + /** 附件列表(详情场景填充) */ + private List attachments; + + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + + /** 发布人姓名 */ + private String publisherName; + + /** 创建/更新时间(审计) */ + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + /** 汇总进度(草稿或仅查阅型为 null) */ + private ProgressVo progress; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java new file mode 100644 index 0000000..0303eb8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知分页结果(含当前用户未读数)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationPageResult extends BasePageResult { + + /** 当前用户未读通知总数 */ + private long unreadCount; + + public NotificationPageResult(BasePageResult page, long unreadCount) { + super(page.getContent(), page.getTotal(), page.getPageIndex(), page.getPageSize(), + page.getTotalPages(), page.isHasNext()); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java new file mode 100644 index 0000000..f8af4a7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知列表查询。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationQuery extends BasePageQuery { + + /** 类型过滤(NotificationTypeEnum code) */ + private String type; + + /** 已读过滤:0 未读 / 1 已读(空 = 全部) */ + private Integer isRead; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java new file mode 100644 index 0000000..2a9b61a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知列表项。 + */ +@Data +public class NotificationVo { + + private Integer id; + + /** 通知类型 */ + private String type; + + private String typeDesc; + + private String title; + + private String content; + + private Integer homeworkId; + + private Integer relatedId; + + /** 是否已读:0/1 */ + private Integer isRead; + + private LocalDateTime readAt; + + private LocalDateTime createTime; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java new file mode 100644 index 0000000..201a460 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 提交/批阅/阅读进度(PRD §18 页 3 汇总口径)。 + * should(应交)为分母;打回未重交计入已交。 + */ +@Data +public class ProgressVo { + + /** 应交人数(参与班级在班学生去重) */ + private int should; + + /** 已读人数 */ + private int read; + + /** 已交人数(含逾期/打回/已批阅) */ + private int submitted; + + /** 已批阅人数 */ + private int graded; + + /** 待批阅人数(submitted - graded) */ + private int pendingGrade; + + /** 未交人数 */ + private int unsubmitted; + + /** 未读人数 */ + private int unread; + + /** 未读且未交人数(高亮口径:should - 已读∪已交) */ + private int unreadUndone; + + /** 阅读率(百分数,一位小数) */ + private double readRate; + + /** 提交率(百分数,一位小数) */ + private double submitRate; + + /** 批阅率(已批阅 / 已交,百分数一位小数;submitted=0 时为 0) */ + private double gradeRate; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java new file mode 100644 index 0000000..a97e0c1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 教师手动提醒请求:type = NOT_READ / NOT_SUBMITTED,studentIds 为空时提醒全部未读/未交学生。 + */ +@Data +public class RemindRequest { + + /** 提醒类型:NOT_READ / NOT_SUBMITTED */ + @NotBlank(message = "提醒类型不得为空") + private String type; + + /** 定向学生ID(用户中心数字ID;空 = 全部未读/未交) */ + private List studentIds; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java new file mode 100644 index 0000000..45d3f3e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 打回请求(PRD 页 6)。comment 为订正原因,必填。 + */ +@Data +public class ReturnRequest { + + @NotBlank(message = "打回须填写订正原因") + private String comment; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java new file mode 100644 index 0000000..166b2e1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 批阅保存请求(PRD 页 6)。score 与 comment 至少一项有值。 + */ +@Data +public class ReviewRequest { + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 圈划标注图片(P2,先收参) */ + private List markedImages; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java new file mode 100644 index 0000000..894b433 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 定时发布 / 取消定时请求。 + * cancel=true 时忽略 scheduledAt,作业回退草稿-未发布并清空定时时间。 + */ +@Data +public class ScheduleRequest { + + /** 定时发布时间(cancel=false 时必填且须晚于当前时间) */ + private LocalDateTime scheduledAt; + + /** 是否取消定时 */ + private Boolean cancel = false; + + @NotNull + public Boolean getCancel() { + return cancel; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java new file mode 100644 index 0000000..6372883 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 学生端作业详情(PRD §18 学生页 2)。进入详情后由前端停留 ≥3 秒再调 read 上报。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentHomeworkDetailVo extends StudentHomeworkVo { + + /** 作业说明(富文本) */ + private String description; + + /** 作业附件 */ + private List attachments; + + /** 参与班级(学生所属班级视角) */ + private List classes; + + /** 我当前版本的提交附件(未提交为 null) */ + private List myAttachments; + + /** 我的文本内容(当前版本) */ + private String myText; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java new file mode 100644 index 0000000..9d30037 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学生端作业列表项(PRD §18 学生页 1)。 + */ +@Data +public class StudentHomeworkVo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 任课教师(发布人姓名) */ + private String teacherName; + + /** 截止时间(前端做倒计时) */ + private LocalDateTime deadlineAt; + + /** 提交方式 */ + private java.util.List submitType; + + /** 作业状态 */ + private String hwStatus; + + private String hwStatusDesc; + + /** 截止后是否允许逾期补交:0 否 / 1 是(学生端据此决定是否渲染「补交」入口) */ + private Integer allowLate; + + /** 我的提交概要(未提交为 null) */ + private MySubmission my; + + @Data + public static class MySubmission { + + /** 当前版本提交行ID */ + private Integer submissionId; + + /** 提交状态(PRD 五态) */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 版本号 */ + private Integer version; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分(已批阅才有) */ + private java.math.BigDecimal score; + + /** 评语 */ + private String comment; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java new file mode 100644 index 0000000..49e3144 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学生矩阵查询(PRD §18 页 5)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentMatrixQuery extends BasePageQuery { + + /** 班级筛选(空 = 全部参与班级) */ + private String classId; + + /** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */ + private String submitStatus; + + /** 批阅状态筛选:PENDING/REVIEWED/RETURNED */ + private String reviewStatus; + + /** 只看待批阅(reviewStatus=PENDING 且已提交)快捷过滤 */ + private Boolean pendingGrade; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java new file mode 100644 index 0000000..6257285 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java @@ -0,0 +1,57 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学生矩阵行(PRD §18 页 5)。未交学生也生成占位行(submissionId=0)。 + */ +@Data +public class StudentMatrixVo { + + /** 当前版本提交行ID(未交占位为 0) */ + private Integer submissionId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + /** 姓名 */ + private String studentName; + + private String classId; + + private String className; + + /** 阅读状态:0 未读 / 1 已读 */ + private Integer isRead; + + /** 首次阅读时间 */ + private LocalDateTime firstReadAt; + + /** 最近阅读时间 */ + private LocalDateTime lastReadAt; + + /** 提交状态(PRD 五态;未交为 NOT_SUBMITTED) */ + private String submitStatus; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期:0/1 */ + private Integer isLate; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分 */ + private BigDecimal score; + + /** 最新提交附件 URL 列表(缩略图) */ + private List thumbnails; + + /** 提交内容类型摘要:image=拍照/图片、attach=上传附件、text=文字作答(未交为空列表) */ + private List contentTypes; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java new file mode 100644 index 0000000..f4d9cd1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 学生查看批阅结果(PRD §18 学生页 2 result 接口)。 + */ +@Data +public class StudentResultVo { + + private Integer homeworkId; + + private String homeworkTitle; + + /** 当前版本批阅结果(未批阅为 null) */ + private SubmissionVersionVo latest; + + /** 全部版本(含各版评语,新→旧) */ + private List versions; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java new file mode 100644 index 0000000..1fa7545 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 学生提交作业请求(PRD §18 学生页 3)。附件只接受 file/Upload 产物(fileId 或平台 URL),拒绝 dataURL。 + */ +@Data +public class StudentSubmitRequest { + + /** 文本内容(submitType 含 text 时有效) */ + @Size(max = 20000, message = "文本内容过长") + private String text; + + /** 附件列表(图片/文件) */ + private List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java new file mode 100644 index 0000000..6c576d9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 学科 DTO(布置过的学科 / 我的任教学科共用返回结构)。 + */ +@Data +public class SubjectDto { + + /** 学科编码(homework.subject 字典值 / bschclass SubID) */ + private String subId; + + /** 学科名称(本校学科字典解析;字典缺失时回退为编码) */ + private String subName; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java new file mode 100644 index 0000000..cac00dc --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 撤回重交动作(PUT /student/homeworks/{id}/submissions/{submissionId})。 + * withdraw = 撤回(截止前,产生 NOT_SUBMITTED 新版本行);resubmit = 打回后重交(走提交语义)。 + */ +@Data +public class SubmissionActionRequest { + + @NotBlank(message = "action 不得为空") + private String action; + + /** resubmit 时的提交内容(等同 StudentSubmitRequest) */ + private String text; + + /** resubmit 时的附件 */ + private java.util.List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java new file mode 100644 index 0000000..bff2972 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java @@ -0,0 +1,63 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 提交版本条目(历史版本/批阅弹窗/学生结果页共用)。 + */ +@Data +public class SubmissionVersionVo { + + private Integer submissionId; + + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + private String studentName; + + /** 版本号 */ + private Integer version; + + /** 是否当前版本:0/1 */ + private Integer isLatest; + + /** 提交状态 */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 文本内容 */ + private String text; + + /** 附件 */ + private List attachments; + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 批阅状态 */ + private String reviewStatus; + + private String reviewStatusDesc; + + /** 批阅人(展示姓名快照,缺失时回退ID) */ + private String reviewedBy; + + /** 批阅时间 */ + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java new file mode 100644 index 0000000..6d6991e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教师端作业列表查询(草稿箱/已发布列表共用)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TeacherHomeworkQuery extends BasePageQuery { + + /** 学科筛选 */ + private String subject; + + /** 作业状态筛选(已发布列表:PUBLISHED/CLOSED/ARCHIVED) */ + private String hwStatus; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java new file mode 100644 index 0000000..d9fd64e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 任教班级 VO(按学科筛选任教班级返回结构)。 + */ +@Data +public class TeachingClassVo { + + /** 班级业务ID(bschclass sch_class.class_id) */ + private String classId; + + /** 班级名称(学校班级列表映射;缺失时回退为班级ID) */ + private String className; +} diff --git a/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java new file mode 100644 index 0000000..0581e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ClassStudent; +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; + +/** + * 班级学生名册仓储。 + */ +public interface ClassStudentRepository extends JpaRepository { + + /** 按学生ID(用户中心数字ID)反查学生身份(学生端接口入口) */ + List findByStudentIdAndStatus(Integer studentId, Integer status); + + /** 班级在班学生 */ + List findByClassIdAndStatus(String classId, Integer status); + + /** 指定班级集合的全部在班学生(应交名单) */ + List findByClassIdInAndStatus(List classIds, Integer status); + + /** 指定学校的全部名册(同步任务全量刷新用) */ + List findBySchoolId(Integer schoolId); + + /** 年级→班级树:按学校去重班级(class_student 快照聚合),列序:classId, className, gradeName */ + @Query("select distinct c.classId, c.className, c.gradeName from ClassStudent c " + + "where c.schoolId = :schoolId and c.status = 1") + List findDistinctClasses(@Param("schoolId") Integer schoolId); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java new file mode 100644 index 0000000..4dc9ee4 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java @@ -0,0 +1,27 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroup; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ContactsGroupRepository extends JpaRepository { + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + + /** + * 按创建人ID(用户中心数字ID)查询联系组列表。 + */ + List findByCreatorId(Integer creatorId); + + /** + * 按创建人ID查询联系组列表(分页)。 + */ + Page findByCreatorId(Integer creatorId, Pageable pageable); + + /** + * 查询全部联系组(按创建时间降序)。 + */ + List findAllByOrderByCreateTimeDesc(); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java new file mode 100644 index 0000000..df5ab28 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java @@ -0,0 +1,42 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroupUser; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ContactsGroupUserRepository extends JpaRepository { + Page findByGroupId(Integer groupId, Pageable pageable); + List findByGroupId(Integer groupId); + + /** + * 按组ID批量删除成员(JPQL bulk delete,SQL 立即执行)。 + * 注意:不能用 derived delete——其 DELETE 延迟到 flush 才执行, + * 同事务内先 insert 会触发 uk_group_user 唯一约束冲突。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId = :groupId") + void deleteByGroupIdBulk(@Param("groupId") Integer groupId); + + /** + * 按多个组ID批量删除成员。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId in :groupIds") + void deleteByGroupIdInBulk(@Param("groupIds") List groupIds); + + /** + * 按多个组ID查询所有成员(用于统计每组人数、组树构建)。 + */ + List findByGroupIdIn(List groupIds); + + /** + * 按组ID和用户ID查询(判断是否已存在)。 + */ + boolean existsByGroupIdAndUserId(Integer groupId, Integer userId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java new file mode 100644 index 0000000..1e6af55 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 作业附件仓储。 + */ +public interface HomeworkAttachmentRepository extends JpaRepository { + + List findByHomeworkIdOrderBySortAscIdAsc(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java new file mode 100644 index 0000000..8f651f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkClass; +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; + +/** + * 作业-班级关联仓储。 + */ +public interface HomeworkClassRepository extends JpaRepository { + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 指定班级的全部作业ID(学生端列表倒查用) */ + @Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds") + List findHomeworkIdsByClassIds(@Param("classIds") List classIds); + + /** 批量查 N 个作业的班级关联(列表展示避免 N+1) */ + @Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java new file mode 100644 index 0000000..6c16a81 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java @@ -0,0 +1,23 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReadRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 学生阅读记录仓储。 + */ +public interface HomeworkReadRecordRepository extends JpaRepository { + + Optional findByHomeworkIdAndStudentId(Integer homeworkId, Integer studentId); + + List findByHomeworkId(Integer homeworkId); + + /** 批量查 N 个作业的已读记录(列表统计避免 N+1) */ + List findByHomeworkIdIn(Collection homeworkIds); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java new file mode 100644 index 0000000..20dc83c --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkRead; +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.Optional; + +/** + * 作业阅读记录仓储(懒标记模型)。 + */ +public interface HomeworkReadRepository extends JpaRepository { + + /** + * 某用户是否已读某作业。 + */ + boolean existsByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 某作业的总已读人数。 + */ + long countByHomeworkId(Integer homeworkId); + + /** + * 删除某作业的全部阅读记录(作业删除时级联清理)。 + */ + void deleteByHomeworkId(Integer homeworkId); + + /** + * 某用户已读的作业总数(用于「我的未读」= 已发布总数 - 已读总数)。 + */ + long countByReaderId(Integer readerId); + + /** + * 查询某用户已读的作业ID列表(用于列表页批量判定未读)。 + */ + @Query("select r.homeworkId from HomeworkRead r where r.readerId = :readerId") + List findHomeworkIdsByReaderId(@Param("readerId") Integer readerId); + + /** + * 查询某用户对某作业的阅读记录(标记已读时幂等判断)。 + */ + Optional findByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 查询某作业的全部阅读记录(按阅读时间倒序),用于阅读明细列表。 + */ + @Query("select r from HomeworkRead r where r.homeworkId = :homeworkId order by r.readTime desc") + List findByHomeworkIdOrderByReadTimeDesc(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java new file mode 100644 index 0000000..402d455 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReceiver; +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; + +/** + * 作业接收人关联表仓储。 + */ +public interface HomeworkReceiverRepository extends JpaRepository { + + /** 按作业ID查询全部接收人 */ + List findByHomeworkId(Integer homeworkId); + + /** 按作业ID删除全部接收人 */ + void deleteByHomeworkId(Integer homeworkId); + + /** 按作业ID和接收类型查询接收人 */ + List findByHomeworkIdAndReceiveType(Integer homeworkId, Integer receiveType); + + /** + * 批量查 N 个作业的接收人,用于列表展示(避免 N+1)。 + * @param homeworkIds 作业ID列表 + * @return 接收人关联记录列表 + */ + @Query("select r from HomeworkReceiver r where r.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java new file mode 100644 index 0000000..bf862b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java @@ -0,0 +1,139 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Homework; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 课程作业仓储。 + */ +public interface HomeworkRepository extends JpaRepository, + JpaSpecificationExecutor { + + /** + * 作业流程状态条件更新(CAS):仅当当前状态为 from 时更新为 to。 + * 状态机单点收敛用,天然幂等、并发安全(定时任务与接口共用)。 + * clearAutomatically:批量更新绕过一级缓存,必须清空以避免后续 findById 读到旧实体 + * 再被 save() 写回旧状态(同事务内必现,集成测试发现)。 + * + * @return 影响行数(0 = 状态已变化,调用方应放弃本次迁移) + */ + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update Homework h set h.hwStatus = :to where h.id = :id and h.hwStatus = :from") + int casHwStatus(@Param("id") Integer id, + @Param("from") String from, + @Param("to") String to); + + /** + * 定时发布任务扫描:定时待发布且已到点。 + */ + List findByHwStatusAndTimingSendTimeLessThanEqual(String hwStatus, LocalDateTime now); + + /** + * 到点关闭任务扫描:进行中且已过截止时间。 + */ + List findByHwStatusAndDeadlineAtBefore(String hwStatus, LocalDateTime now); + + /** + * 截止提醒任务扫描:进行中且截止时间落在指定窗口内(remind_1d/remind_1h 开关在服务侧逐单过滤)。 + */ + List findByHwStatusAndDeadlineAtBetween(String hwStatus, LocalDateTime start, LocalDateTime end); + + /** + * 按作业流程状态查询(兼容层/统计用)。 + */ + List findByHwStatus(String hwStatus); + + /** + * 当前登录人发布的全部作业(课程作业口径统计:我布置的作业 / 提交与批改进度明细)。 + */ + List findByPublisherId(Integer publisherId); + + /** + * 当前登录人布置过(已发布及之后状态)作业的学科去重清单(学科筛选用,字典序;草稿不计)。 + */ + @Query("select distinct h.subject from Homework h " + + "where h.publisherId = :publisherId and h.subject is not null and h.subject <> '' " + + "and h.hwStatus in :statuses order by h.subject") + List findAssignedSubjects(@Param("publisherId") Integer publisherId, + @Param("statuses") Collection statuses); + + /** + * 按状态计数(用于统计已发布总数)。 + */ + long countByStatus(Integer status); + + /** + * 已发布且已到点(定时发送未到点不计入)的作业数,用于用户端未读数统计。 + */ + @Query("select count(n) from Homework n where n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDue(@Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定状态、指定时间范围内的作业创建时间(用于按月统计,避免加载大字段)。 + */ + @Query("select n.createTime from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定状态、指定时间范围内的发布人姓名(用于按发布人统计)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 按学校和状态计数(多租户隔离统计)。 + */ + long countBySchIdAndStatus(Integer schId, Integer status); + + /** + * 按学校计数(多租户隔离统计:全部状态)。 + */ + long countBySchId(Integer schId); + + /** + * 已发布且已到点(定时发送未到点不计入)且属于指定学校的作业数(用户端未读数统计,多租户隔离)。 + */ + @Query("select count(n) from Homework n where n.schId = :schId and n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDueBySchId(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定学校、指定状态、指定时间范围内的作业创建时间(按月统计,多租户隔离)。 + */ + @Query("select n.createTime from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesBySchIdStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定学校、指定状态、指定时间范围内的发布人姓名(按发布人统计,多租户隔离)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesBySchStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java new file mode 100644 index 0000000..547c5ea --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkSubmission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 作业提交仓储(多版本)。统计一律基于 is_latest=1 的当前版本行。 + */ +public interface HomeworkSubmissionRepository extends JpaRepository { + + /** 学生在指定作业的当前版本行 */ + Optional findByHomeworkIdAndStudentIdAndIsLatest(Integer homeworkId, + Integer studentId, + Integer isLatest); + + /** 学生在指定作业的全部版本(新→旧) */ + List findByHomeworkIdAndStudentIdOrderByVersionDesc(Integer homeworkId, Integer studentId); + + /** 指定作业的当前版本行(统计/矩阵用) */ + List findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest); + + /** 批量查 N 个作业的当前版本行(列表统计避免 N+1) */ + List findByHomeworkIdInAndIsLatest(Collection homeworkIds, Integer isLatest); + + /** 学生矩阵分页 */ + Page findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest, Pageable pageable); + + /** 学生当前版本最大版本号 */ + @Query("select max(s.version) from HomeworkSubmission s " + + "where s.homeworkId = :homeworkId and s.studentId = :studentId") + Integer findMaxVersion(@Param("homeworkId") Integer homeworkId, @Param("studentId") Integer studentId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 附件等子表清理后按作业删除历史版本行 */ + @Modifying + @Query("delete from HomeworkSubmission s where s.homeworkId = :homeworkId") + int deleteAllByHomeworkId(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java new file mode 100644 index 0000000..ecc75cb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HomeworkTemplateRepository extends JpaRepository { + Page findByTypeId(Integer typeId, Pageable pageable); + + /** + * 查询全部模板(按引用次数降序),分页。 + */ + Page findAllByOrderByRefCountDesc(Pageable pageable); + + /** + * 按类型ID查询全部模板(不分页,用于检查类型是否被引用)。 + */ + long countByTypeId(Integer typeId); +} diff --git a/src/main/java/com/yunhai/homework/repository/NotificationRepository.java b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java new file mode 100644 index 0000000..1066c10 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + * 站内通知仓储。 + */ +public interface NotificationRepository extends JpaRepository { + + Page findByReceiverId(Integer receiverId, Pageable pageable); + + Page findByReceiverIdAndType(Integer receiverId, String type, Pageable pageable); + + Page findByReceiverIdAndIsRead(Integer receiverId, Integer isRead, Pageable pageable); + + Page findByReceiverIdAndTypeAndIsRead(Integer receiverId, String type, + Integer isRead, Pageable pageable); + + long countByReceiverIdAndIsRead(Integer receiverId, Integer isRead); + + Optional findByIdAndReceiverId(Integer id, Integer receiverId); + + /** 截止提醒去重:同作业同类型同档位已发过即跳过(related_id 存档位:1=1天档 2=1小时档) */ + boolean existsByHomeworkIdAndTypeAndRelatedId(Integer homeworkId, String type, Integer relatedId); + + /** 截止提醒去重(不分档位) */ + boolean existsByHomeworkIdAndType(Integer homeworkId, String type); + + /** 全部已读(clearAutomatically 防同事务内缓存脏读) */ + @Modifying(clearAutomatically = true) + @Query("update Notification n set n.isRead = 1, n.readAt = :now " + + "where n.receiverId = :receiverId and n.isRead = 0") + int markAllRead(@Param("receiverId") Integer receiverId, @Param("now") java.time.LocalDateTime now); + + /** 按作业删除通知(作业删除级联) */ + void deleteByHomeworkId(Integer homeworkId); + + /** 指定作业的全部通知(清理/统计用) */ + List findByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java new file mode 100644 index 0000000..8e60f47 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SchClassTeach; +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; + +/** + * 班级任教关系快照仓储。 + */ +public interface SchClassTeachRepository extends JpaRepository { + + /** 指定学校指定教师的全部有效任教记录 */ + List findBySchoolIdAndTeaIdAndStatus(Integer schoolId, Integer teaId, Integer status); + + /** 指定学校指定教师去重学科编码(字典序由调用方保证) */ + @Query("select distinct t.subId from SchClassTeach t " + + "where t.schoolId = :schoolId and t.teaId = :teaId and t.status = 1") + List findDistinctSubIds(@Param("schoolId") Integer schoolId, @Param("teaId") Integer teaId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java new file mode 100644 index 0000000..18595bb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java @@ -0,0 +1,13 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsModule; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsModuleRepository extends JpaRepository { + List findAllByOrderByIdAsc(); + + /** 按模块标识查询(如 module_code="homework")。 */ + SmsModule findByModuleCode(String moduleCode); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java new file mode 100644 index 0000000..0d277a0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsSchoolConfig; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsSchoolConfigRepository extends JpaRepository { + Page findBySchoolNameContaining(String schoolName, Pageable pageable); + Page findAll(Pageable pageable); + List findBySchoolId(Integer schoolId); + List findBySchoolIdInAndModuleIdIn(List schoolIds, List moduleIds); + + /** + * 按学校ID和模块ID查询单条配置(判断是否已存在)。 + */ + SmsSchoolConfig findBySchoolIdAndModuleId(Integer schoolId, Integer moduleId); + + /** + * 按学校ID查询全部模块配置(用于"设置某校全部模块")。 + */ + List findBySchoolIdIn(List schoolIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java new file mode 100644 index 0000000..8848b14 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SubmissionAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; + +/** + * 提交附件仓储。 + */ +public interface SubmissionAttachmentRepository extends JpaRepository { + + List findBySubmissionIdOrderBySortAscIdAsc(Integer submissionId); + + /** 批量查 N 个提交行的附件(矩阵/版本列表避免 N+1) */ + List findBySubmissionIdIn(Collection submissionIds); + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SysDictRepository.java b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java new file mode 100644 index 0000000..c083840 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SysDict; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SysDictRepository extends JpaRepository { + List findByDictTypeOrderBySortOrderAsc(String dictType); + List findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag); + + /** 按类型+key 取单条字典(发文部门名称解析用) */ + Optional findByDictTypeAndDictKey(String dictType, String dictKey); +} diff --git a/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java new file mode 100644 index 0000000..a3a0f12 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.TemplateType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TemplateTypeRepository extends JpaRepository { + List findAllByOrderBySortOrderAsc(); + + /** + * 按类型名称查询(重名校验)。 + */ + boolean existsByTypeName(String typeName); + + /** + * 按类型名称查询(重名校验,排除自身)。 + */ + boolean existsByTypeNameAndIdNot(String typeName, Integer id); +} diff --git a/src/main/java/com/yunhai/homework/repository/UpFileRepository.java b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java new file mode 100644 index 0000000..421f142 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.UpFile; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +/** + * 附件文件仓储。 + */ +public interface UpFileRepository extends JpaRepository { + + /** + * 按主题类型和主题ID查询附件列表。 + */ + List findByTopicTypeAndTopicId(String topicType, Integer topicId); + + /** + * 按主题类型和多个主题ID批量查询附件(消除 N+1)。 + */ + List findByTopicTypeAndTopicIdIn(String topicType, List topicIds); + + /** + * 按主题类型和主题ID删除附件(作业删除时级联清理)。 + */ + void deleteByTopicTypeAndTopicId(String topicType, Integer topicId); +} diff --git a/src/main/java/com/yunhai/homework/service/IClassService.java b/src/main/java/com/yunhai/homework/service/IClassService.java new file mode 100644 index 0000000..2346cd9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IClassService.java @@ -0,0 +1,116 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; + +import java.util.List; + +/** + * 班级管理(bschclass)开放接口客户端门面。 + * 基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。 + * 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。 + */ +public interface IClassService { + + /** + * 获取指定学校全部教师(含所属组 GroupIds)。 + * 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}} + * + * @param schId 学校ID(即用户当前身份的 schId) + */ + List getSchTeaList(int schId); + + /** + * 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。 + * 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}} + */ + SchInfoDto getSchFirst(int schId); + + /** + * 学校学段(学制)列表:小学部 / 初中部…。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=} + */ + List getSchLenList(String schSerId); + + /** + * 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=} + */ + List getSchGradeList(String schSerId); + + /** + * 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。 + * 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=} + */ + List getSchClassList(String schSerId); + + /** + * 按校全部学生(圈人:全校范围展开)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=} + */ + List getSchStuList(String schSerId); + + /** + * 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=} + */ + List getGradeStuList(String schSerId, Integer gradeId); + + /** + * 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=} + */ + List getSubStuList(String schSerId, Integer schSystemId); + + /** + * 按班级批量查学生(圈人:班级范围一次多班)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3} + */ + List getClaArrStuList(List classIds); + + /** + * 按学生ID批量查(圈人:指定学员范围)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3} + */ + List getStuIdsList(List stuIds); + + /** + * 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。 + * 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。 + * 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=} + */ + List getTeaTree(Integer schId, boolean toUser); + + List getStuTree(Integer schId,boolean toUser); + + /** + * 学校学科列表(含学科编码、名称、排序号)。 + * 对应:GET {@code {bschclass}/oapi/SubInfo/GetSchSublist?SchID=} + */ + List getSchSubList(String schSerId); + + /** + * 指定教师的全部任教(校验教师归属该校;SchID 过滤学校、UserTeaSerID 过滤教师)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=&UserTeaSerID=} + */ + List getSchTeachList(String schSerId, Integer teaId); + + /** + * 指定班级的全部任教(班级维度:该班所有教师与学科)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchClaTeachlist?ClassID=} + */ + List getSchClaTeachList(Integer classId); + + /** + * 学校全部任教(全校所有教师的班级任教关系)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=} + */ + List getSchAllTeachList(String schSerId); +} diff --git a/src/main/java/com/yunhai/homework/service/IContactsService.java b/src/main/java/com/yunhai/homework/service/IContactsService.java new file mode 100644 index 0000000..0e1fdce --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IContactsService.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 联系组业务接口。 + * 对应前端 Contacts 组接口:AddGroup/GetGroup/EditGroup/DelGroup/SetContactsTea/GetContactsTea/GetGroupTree/GetDeptRoleGroupTree/Import/Export。 + */ +public interface IContactsService { + + /** + * 添加联系组。 + * + * @param name 组名 + * @param type 类型(1 自定义组, 2 明信片) + */ + void addGroup(String name, Integer type); + + /** + * 获取联系组列表(含每组人数,分页)。 + * + * @param page 页码(从1起) + * @param count 每页条数 + * @param type 类型(1 自定义组, 2 明信片) + * @return Map: { list, total } + */ + Map getGroup(Integer page, Integer count, Integer type); + + /** + * 编辑联系组。 + * + * @param id 组ID + * @param name 组名 + * @param type 类型 + */ + void editGroup(Integer id, String name, Integer type); + + /** + * 删除联系组(支持批量,同时删除组内成员)。 + * + * @param ids 逗号分隔的组ID + */ + void delGroup(String ids); + + /** + * 设置联系组教师(全量替换:先删后增)。 + * + * @param gid 组ID + * @param ids 逗号分隔的用户ID + */ + void setContactsTea(Integer gid, String ids); + + /** + * 获取联系组教师列表(分页)。 + * + * @param gid 组ID + * @param page 页码 + * @param count 每页条数 + * @return Map: { list, total } + */ + Map getContactsTea(String gid, Integer page, Integer count); + + /** + * 获取联系组树(组节点+用户节点,递归构建)。 + * + * @param type 类型(1 自定义组, 2 明信片) + * @return 树形结构 List<Map> + */ + List> getGroupTree(Integer type); + + /** + * 获取部门角色组树。 + * C# 从外部API获取部门树+用户角色映射。Java版预留。 + * + * @return 树形结构 List<Map> + */ + List> getDeptRoleGroupTree(); +} diff --git a/src/main/java/com/yunhai/homework/service/IDictService.java b/src/main/java/com/yunhai/homework/service/IDictService.java new file mode 100644 index 0000000..31ef515 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IDictService.java @@ -0,0 +1,75 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 系统字典业务接口。 + * 对应前端 Base 组接口:GetSysDic/GetDictMangeList/AddDeptDic/UpdateDeptDic/DelDept/OrdbyDicIds/GetDeptSync/GetSchModuleIsSms。 + */ +public interface IDictService { + + /** + * 获取系统字典(下拉框用)。 + * C# GetSysDic: Type=1 部门类别, 其余类型略。 + * + * @param type 字典类型编号(1=部门类别) + * @return List<Map> 每项 {k, v} + */ + List> getSysDic(Integer type); + + /** + * 获取系统字典管理列表(含 SerID/CodeID/Name/Des/Enable)。 + * + * @param type 字典类型编号(0=部门类别) + * @return List<Map> + */ + List> getDictMangeList(String type); + + /** + * 添加发文单位。 + * + * @param name 单位名称 + */ + void addDeptDic(String name); + + /** + * 更新发文单位。 + * + * @param serID 记录ID + * @param name 名称 + * @param enable 启用状态 + */ + void updateDeptDic(Integer serID, String name, Integer enable); + + /** + * 删除发文单位(校验是否被作业引用)。 + * + * @param serID 记录ID + */ + void delDept(Integer serID); + + /** + * 发文单位排序。 + * + * @param ids 逗号分隔的 SerID 列表(按前端顺序) + */ + void ordbyDicIds(String ids); + + /** + * 本校是否开通短信模块(前端 dict/Sync,用于"本校是否开通短信"判断)。 + * + * @param schId 当前学校ID(取当前登录用户角色所属学校) + * @return 开通返回 true,未开通/未配置返回 false + */ + Boolean getSchModuleIsSms(Integer schId); + + /** + * 按发文部门字典 key(CodeID)解析部门名称;未命中返回 null。 + * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 + * + * @param deptId 作业 deptId(存的是 sys_dict.dict_key) + * @return 部门名称;无匹配时返回 null + */ + String resolveDeptName(String deptId); +} diff --git a/src/main/java/com/yunhai/homework/service/IFileService.java b/src/main/java/com/yunhai/homework/service/IFileService.java new file mode 100644 index 0000000..1b01bbf --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IFileService.java @@ -0,0 +1,94 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.UpFile; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.model.homework.UpFileResultVo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件上传/下载/删除业务接口。 + */ +public interface IFileService { + /** + * 上传单个文件。 + * + * @param file 上传的文件 + * @param appType 应用类型(如 "homework") + * @param topicId 关联主题ID(可为空,作业保存后再绑定) + * @return 文件视图对象 + */ + UpFileVo upload(MultipartFile file, String appType, Integer topicId); + + /** + * 将已上传但未绑定主题的附件绑定到指定作业。 + * + * @param fileIds 文件记录ID列表 + * @param topicId 作业ID + */ + void bindToTopic(List fileIds, Integer topicId); + + /** + * 查询某主题下的附件列表。 + * + * @param appType 应用类型 + * @param topicId 主题ID + * @return 附件列表 + */ + List listByTopic(String appType, Integer topicId); + + /** + * 下载文件。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void download(Integer fileId, HttpServletResponse response); + + /** + * 在线预览文件(Content-Disposition: inline,按扩展名输出真实 Content-Type, + * 供前端 open-file-viewer 取 Blob / video 播放)。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void preview(Integer fileId, HttpServletResponse response); + + /** + * 删除文件(同时删除物理文件和数据库记录)。 + * + * @param fileId 文件记录ID + */ + void delete(Integer fileId); + + /** + * 打包下载多个附件为 ZIP。 + * + * @param files 附件记录列表(调用方按 fileIds 查好) + * @param zipName 压缩包显示名(自动追加 .zip) + * @param response HTTP 响应 + */ + void downloadZip(List files, String zipName, HttpServletResponse response); + + /** + * 按主题类型和主题ID打包下载附件为 ZIP(Controller 便捷方法,内部自行查附件列表)。 + * + * @param topicType 主题类型(如 "homework") + * @param topicId 主题ID + * @param zipName 压缩包显示名 + * @param response HTTP 响应 + */ + void downloadZipByTopic(String topicType, Integer topicId, String zipName, HttpServletResponse response); + + /** + * 批量上传图片(循环上传,返回每个文件的视图对象列表)。 + */ + List uploadImages(MultipartFile[] files, String appType); + + /** + * 上传单个文件并返回前端兼容的结果 VO(含 fileid/name/fileurl/host/type/size)。 + */ + UpFileResultVo uploadToResult(MultipartFile file, String appType); +} diff --git a/src/main/java/com/yunhai/homework/service/IHomeworkService.java b/src/main/java/com/yunhai/homework/service/IHomeworkService.java new file mode 100644 index 0000000..ed50167 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IHomeworkService.java @@ -0,0 +1,142 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业业务接口。 + */ +public interface IHomeworkService { + + /** + * 发作业:以当前登录用户为发布人,状态置为「已发布」入库。 + * 若 request.timingSendTime 不为空且晚于当前时间,则作为定时发送, + * 未到点前用户端收件箱不可见(到点后由查询过滤自然可见)。 + * + * @return 新建作业 ID(用于后续回填附件 TopicID 等) + */ + Integer publish(HomeworkRequest request); + + /** + * 编辑作业:按 id 更新标题/内容/类型/部门/置顶,发布人与状态保持不变。 + */ + void edit(Integer id, HomeworkRequest request); + + /** + * 批量删除作业:删除主记录并级联清理阅读记录、接收人、附件关联。 + */ + void deleteBatch(List ids); + + /** + * 撤回作业:仅已发布且未撤回的作业可撤回,置 isPulled=1。 + */ + void pull(Integer id); + + /** + * 置顶/取消置顶。 + */ + void setTopmost(Integer id, Integer topmost); + + /** + * 转发作业:基于源作业创建新作业(已发布),parentId 关联源作业。 + */ + Integer forward(Integer sourceId, HomeworkRequest request); + + /** + * 用户端列表:仅展示已发布且已到点的作业,并标记当前用户是否已读,附未读总数。 + * 返回 HomeworkPageResult(扩展 unreadCount)。 + */ + HomeworkPageResult userList(HomeworkQuery query, Pageable pageable); + + /** + * 管理端列表:按条件查询全部作业(管理员查看全部)。 + */ + BasePageResult adminList(HomeworkQuery query, Pageable pageable); + + /** + * 作业详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。 + */ + HomeworkVo detail(Integer id); + + /** + * 编辑回显详情(不标记已读)。 + */ + HomeworkVo getDetail(Integer id); + + /** + * 标记已读:为当前用户写入(幂等)该作业的阅读记录。 + */ + void markRead(Integer id); + + /** + * 一键全部已读(批量化):当前用户所有未读的已发布作业批量插入阅读记录。 + * @return 已读条数 + */ + int initializeRed(); + + /** + * 追加接收人(事务化):并入接收人列表并重算 allowcount。 + */ + void receiveUsers(Integer homeworkId, String users); + + /** + * 上一篇/下一篇作业ID(SQL 相邻查询)。 + * @param serid 当前作业ID + * @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列) + * @return {prevId, nextId} + */ + Map getNextPrev(Integer serid, String type); + + /** + * 获取某作业的阅读明细列表。 + */ + List getReadDetail(Integer homeworkId); + + /** + * 阅读情况分组(已读/未读)。 + */ + List> getReadDetailGrouped(Integer serid); + + /** + * 催收未读:枚举未读接收人,后续接入消息推送。 + */ + void expedite(Integer serid); + + /** + * 作业统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。 + */ + HomeworkStatisticsVo statistics(HomeworkQuery query, LocalDateTime start, LocalDateTime end); + + /** + * 作业详情视图(含附件、接收人信息),供 Controller 返回。 + */ + HomeworkDetailVo getDetailMap(Integer serid); + + /** + * 已发列表附件批量查询(消除 N+1)。 + */ + Map> batchFileIds(List homeworkIds); + + /** + * 查看作业详情视图(含附件、接收人)。 + * type=1 时先标记已读再返回,type=0 时直接返回不标记。 + */ + HomeworkDetailVo detailMap(Integer serid, Integer type); + + /** + * 一键全部已读并返回结果(count = 已读条数)。 + */ + InitializeRedVo initializeRedResult(); + + /** + * 批量下载作业全部附件(打包 zip):查作业标题后调文件域打包下载。 + */ + void downloadAllFiles(Integer dataid, HttpServletResponse response); +} diff --git a/src/main/java/com/yunhai/homework/service/ISmsService.java b/src/main/java/com/yunhai/homework/service/ISmsService.java new file mode 100644 index 0000000..09b9b5f --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ISmsService.java @@ -0,0 +1,77 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置业务接口。 + * 对应前端 setSms 组接口:getmodule/getgroup/getschsms/getsch/setschsms/setschallsms/setschmodulesms。 + */ +public interface ISmsService { + + /** + * 获取短信模块列表。 + * C# 从 AuthConfig.AppKeyMap 读取。Java版从 sms_module 表读取。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getModuleList(); + + /** + * 获取学校父类组列表。 + * C# 从外部BLL获取学校组。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchParentGroup(); + + /** + * 分页获取学校短信开关列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param schName 学校名称(模糊搜索) + * @return Map: { list, total } + */ + Map getSchIsSmsList(Integer pageSize, Integer pageIndex, String schName); + + /** + * 获取学校列表。 + * C# 从外部BLL获取学校列表。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchList(); + + /** + * 设置单校单模块短信开关。 + * + * @param schSerID 学校ID + * @param moduleId 模块ID + * @param isSms 是否开通 + */ + void setSchSms(Integer schSerID, Integer moduleId, Boolean isSms); + + /** + * 设置某校全部模块短信开关。 + * + * @param schSerID 学校ID + * @param isSms 是否开通 + */ + void setSchSmsAll(Integer schSerID, Boolean isSms); + + /** + * 批量设置多校多模块短信开关。 + * + * @param schSerIDs 逗号分隔的学校ID + * @param modules 逗号分隔的模块ID + * @param isSms 是否开通 + */ + void setSchModuleSms(String schSerIDs, String modules, Boolean isSms); + + /** + * 从 JSON 字符串解析并设置单校单模块短信开关。 + * 解析 SchSerID/ModuID/IsSms 三个字段。 + */ + void setSchSmsFromJson(String itemsModuJson); +} diff --git a/src/main/java/com/yunhai/homework/service/ITemplateService.java b/src/main/java/com/yunhai/homework/service/ITemplateService.java new file mode 100644 index 0000000..5594401 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ITemplateService.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板业务接口。 + * 对应前端 Temp 组接口:GetTypelist/EditType/DelType/GetTemplatelist/EditTemplate/DelTemplate/GetTemplateFist。 + */ +public interface ITemplateService { + + /** + * 获取模板类型列表(首项固定为"全部")。 + * + * @return List<Map> 每项 {TypeID, TypeName} + */ + List> getTypelist(); + + /** + * 新增/编辑模板类型。 + * + * @param typeid 类型ID(0=新增) + * @param typeName 类型名称 + */ + void editType(Integer typeid, String typeName); + + /** + * 删除模板类型(校验是否有模板引用此类型)。 + * + * @param typeid 类型ID + */ + void delType(Integer typeid); + + /** + * 分页获取模板列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param typeid 类型ID(0=全部) + * @return Map: { list, total } + */ + Map getTemplatelist(Integer pageSize, Integer pageIndex, Integer typeid); + + /** + * 新增/编辑模板。 + * + * @param serID 模板ID(null=新增) + * @param title 标题 + * @param content 内容 + * @param typeId 类型ID + * @param cover 封面文件ID + */ + void editTemplate(Integer serID, String title, String content, Integer typeId, Integer cover); + + /** + * 删除模板。 + * + * @param serID 模板ID + */ + void delTemplate(Integer serID); + + /** + * 获取单个模板详情。 + * + * @param serID 模板ID + * @return Map 模板详情 + */ + Map getTemplateFist(Integer serID); +} diff --git a/src/main/java/com/yunhai/homework/service/IUserCenterService.java b/src/main/java/com/yunhai/homework/service/IUserCenterService.java new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IUserCenterService.java @@ -0,0 +1,51 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 用户中心门面服务(对齐 UserBase {@code UserCenterService} 模式)。 + * 封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。 + */ +public interface IUserCenterService { + + /** + * 获取当前用户学校组及用户(树形结构)。 + * 组装规则(从原 BaseApiController.getSchoolAll 下沉): + * + * userbase 读取当前学校整棵组树(getGroupById(schId, withSub=true)),剔除学生组(type=8)及其子组; + * bschclass 获取该校全部教师(/oapi/DbClass/GetSchteaList?Group=schId),按 GroupIds 挂到对应组下, + * 无有效组(如所属组全被剔除)的教师兜底挂学校根节点; + * 扁平节点按 pid 递归组装为树(空组剔除,同 C# FormatUserTree)。 + * + * + * @param schId 学校ID(用户当前角色所属学校) + * @return 用户树形结构(组节点 type=1 / 教师节点 type=2) + */ + List> getSchoolAllTree(Integer schId); + + /** + * 获取指定组内用户(教师 + 管理员)。 + * 对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。 + * + * @param gid 组ID + * @param role 角色(默认 2=教师;1=管理员等) + * @return 组内用户列表 + */ + List> getGroupUser(Integer gid, Integer role); + + /** + * 获取当前用户可访问的模块树(左侧菜单)。 + * 从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。 + * + * @param userId 用户ID + * @param schId 学校ID(可为 null) + * @return 模块树(顶层 List,含 children 嵌套) + */ + List> getUserModuleTree(Integer userId, Integer schId); +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/service/impl/ClassService.java b/src/main/java/com/yunhai/homework/service/impl/ClassService.java new file mode 100644 index 0000000..8be5e2a --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/impl/ClassService.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.service.impl; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.service.IClassService; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班级管理(bschclass)开放接口实现。 + * bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。 + * 推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List
+ * 身份:当前登录用户ID ↔ class_student 名册;仅可见本人班级作业,禁 IDOR。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/student/homeworks") +public class StudentHomeworkController { + + @Autowired + private StudentHomeworkService studentHomeworkService; + + /** 我的作业列表(tab=all/todo/done/closed) */ + @GetMapping + public BasePageResult list( + @RequestParam(value = "tab", defaultValue = "all") String tab, + BasePageQuery query) { + return studentHomeworkService.list(tab, query); + } + + /** 作业详情 */ + @GetMapping("/{id}") + public StudentHomeworkDetailVo detail(@PathVariable("id") Integer id) { + return studentHomeworkService.detail(id); + } + + /** 阅读上报(前端停留 ≥3 秒后调用;幂等) */ + @PostMapping("/{id}/read") + public void read(@PathVariable("id") Integer id) { + studentHomeworkService.read(id); + } + + /** 提交作业,返回新提交行 id */ + @PostMapping("/{id}/submissions") + public Map submit(@PathVariable("id") Integer id, + @RequestBody @Validated StudentSubmitRequest request) { + Integer submissionId = studentHomeworkService.submit(id, request); + return java.util.Collections.singletonMap("submissionId", submissionId); + } + + /** 撤回 / 打回后重交(action=withdraw|resubmit),返回新提交行 id */ + @PutMapping("/{id}/submissions/{submissionId}") + public Map action(@PathVariable("id") Integer id, + @PathVariable("submissionId") Integer submissionId, + @RequestBody @Validated SubmissionActionRequest request) { + Integer newId = studentHomeworkService.action(id, submissionId, request); + return java.util.Collections.singletonMap("submissionId", newId); + } + + /** 批阅结果(当前版本 + 历史版本) */ + @GetMapping("/{id}/result") + public StudentResultVo result(@PathVariable("id") Integer id) { + return studentHomeworkService.result(id); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java b/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java new file mode 100644 index 0000000..5335083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/TeacherHomeworkV2Controller.java @@ -0,0 +1,219 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.v2.ClassTreeNode; +import com.yunhai.homework.model.v2.ExtendRequest; +import com.yunhai.homework.model.v2.HomeworkStatsVo; +import com.yunhai.homework.model.v2.HomeworkV2Request; +import com.yunhai.homework.model.v2.HomeworkV2Vo; +import com.yunhai.homework.model.v2.RemindRequest; +import com.yunhai.homework.model.v2.ReturnRequest; +import com.yunhai.homework.model.v2.ReviewRequest; +import com.yunhai.homework.model.v2.ScheduleRequest; +import com.yunhai.homework.model.v2.StudentMatrixQuery; +import com.yunhai.homework.model.v2.StudentMatrixVo; +import com.yunhai.homework.model.v2.SubmissionVersionVo; +import com.yunhai.homework.model.v2.SubjectDto; +import com.yunhai.homework.model.v2.TeacherHomeworkQuery; +import com.yunhai.homework.model.v2.TeachingClassVo; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.SortDefault; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * 课堂作业 V2 教师端接口(PRD §18 路径一字对齐:/teacher/homeworks…)。 + * + * 与 notice 语义的旧接口(/{platform}/homework/*)并存:旧接口冻结维护, + * 新业务一律走本控制器;返回裸对象由 ApiResponseAdvice 包装 {code,msg,data}。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/teacher/homeworks") +public class TeacherHomeworkV2Controller { + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + // ============================ 页 1:发布作业 ============================ + + /** 创建作业(落草稿) */ + @PostMapping + public Integer create(@RequestBody @Validated HomeworkV2Request request) { + return teacherHomeworkV2Service.create(request); + } + + /** 编辑作业(仅草稿) */ + @PutMapping("/{id}") + public void edit(@PathVariable("id") Integer id, + @RequestBody @Validated HomeworkV2Request request) { + teacherHomeworkV2Service.edit(id, request); + } + + /** 删除作业(仅草稿) */ + @DeleteMapping("/{id}") + public void delete(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.delete(id); + } + + /** 立即发布(草稿 → 进行中) */ + @PostMapping("/{id}/publish") + public void publish(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.publish(id); + } + + /** 定时发布 / 取消定时(body: {scheduledAt} 或 {cancel:true}) */ + @PostMapping("/{id}/schedule") + public void schedule(@PathVariable("id") Integer id, @RequestBody ScheduleRequest request) { + teacherHomeworkV2Service.schedule(id, request); + } + + // ============================ 页 2:草稿箱 ============================ + + /** 草稿箱分页 */ + @GetMapping("/drafts") + public BasePageResult drafts(TeacherHomeworkQuery query, + @SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort) { + return teacherHomeworkV2Service.drafts(query, sort); + } + + // ============================ 页 3:已发布列表 ============================ + + /** 已发布列表分页(含应交/已读/已交/未读未交聚合) */ + @GetMapping("/published") + public BasePageResult published(TeacherHomeworkQuery query, + @SortDefault(sort = "publishedAt", direction = Sort.Direction.DESC) Sort sort) { + return teacherHomeworkV2Service.published(query, sort); + } + + /** 提醒未读/未交学生,返回实际提醒人数 */ + @PostMapping("/{id}/remind") + public Map remind(@PathVariable("id") Integer id, @RequestBody RemindRequest request) { + int reminded = teacherHomeworkV2Service.remind(id, request); + return java.util.Collections.singletonMap("reminded", reminded); + } + + /** 延长截止时间 */ + @PostMapping("/{id}/extend") + public void extend(@PathVariable("id") Integer id, @RequestBody @Validated ExtendRequest request) { + teacherHomeworkV2Service.extend(id, request); + } + + /** 复制为新草稿,返回新作业 id */ + @PostMapping("/{id}/copy") + public Integer copy(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.copy(id); + } + + /** 归档(只读,不再触发任何提醒) */ + @PostMapping("/{id}/archive") + public void archive(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.archive(id); + } + + /** 导出学生矩阵 Excel */ + @GetMapping("/{id}/export") + public void export(@PathVariable("id") Integer id, HttpServletResponse response) throws IOException { + teacherHomeworkV2Service.export(id, response); + } + + // ============================ 页 4:详情 / 统计 ============================ + + /** 作业详情 */ + @GetMapping("/{id}") + public HomeworkV2Vo detail(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.detail(id); + } + + /** 汇总统计卡片(总进度 + 分班进度) */ + @GetMapping("/{id}/stats") + public HomeworkStatsVo stats(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.stats(id); + } + + // ============================ 页 5:学生矩阵 ============================ + + /** 学生矩阵分页(阅读/提交/批阅状态,未交学生生成占位行) */ + @GetMapping("/{id}/students") + public BasePageResult students(@PathVariable("id") Integer id, + StudentMatrixQuery query) { + return teacherHomeworkV2Service.students(id, query); + } + + // ============================ 页 6:批阅 / 打回 / 版本 ============================ + + /** 保存批阅(→ REVIEWED,推送批阅完成通知) */ + @PostMapping("/{id}/submissions/{studentId}/review") + public void review(@PathVariable("id") Integer id, @PathVariable("studentId") Integer studentId, + @RequestBody @Validated ReviewRequest request) { + teacherHomeworkV2Service.review(id, studentId, request); + } + + /** 打回(→ RETURNED,推送打回通知) */ + @PostMapping("/{id}/submissions/{studentId}/return") + public void returnSubmission(@PathVariable("id") Integer id, @PathVariable("studentId") Integer studentId, + @RequestBody @Validated ReturnRequest request) { + teacherHomeworkV2Service.returnSubmission(id, studentId, request); + } + + /** 历史提交版本(批阅弹窗切换版本) */ + @GetMapping("/{id}/submissions/{studentId}/versions") + public List versions(@PathVariable("id") Integer id, + @PathVariable("studentId") Integer studentId) { + return teacherHomeworkV2Service.versions(id, studentId); + } + + // ============================ 辅助:学科 ============================ + + /** 我布置过的学科(本人已发布及之后作业的学科 ∪ 我的任教学科,合并去重;草稿不计) */ + @GetMapping("/subjects/assigned") + public List assignedSubjects() { + return teacherHomeworkV2Service.assignedSubjects(); + } + + /** 我的任教学科(bschclass 任教关系去重,名称取本校学科字典) */ + @GetMapping("/subjects/teaching") + public List teachingSubjects() { + return teacherHomeworkV2Service.teachingSubjects(); + } + + /** 按学科筛选我的任教班级(subId=学科编码;班级名称取学校班级列表) */ + @GetMapping("/subjects/classes") + public List teachingClasses(@RequestParam("subId") String subId) { + return teacherHomeworkV2Service.teachingClasses(subId); + } + + // ============================ 辅助:班级树 / 名册同步 ============================ + + /** 年级→班级树(发布选班;名册快照聚合) */ + @GetMapping("/classes") + public List classTree() { + return teacherHomeworkV2Service.classTree(); + } + + /** 手动触发名册同步(bschclass 接口就绪前为空操作) */ + @PostMapping("/classes/sync") + public Map syncClasses() { + int synced = classStudentService.syncAll(); + return java.util.Collections.singletonMap("synced", synced); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/TemplateController.java b/src/main/java/com/yunhai/homework/controller/api/TemplateController.java new file mode 100644 index 0000000..e9c593c --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/TemplateController.java @@ -0,0 +1,88 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.service.ITemplateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板接口(模板域)。 + * 路径前缀:{@code Config.API_PATH + "/template/"}(原 homework/Template/ 三级路径已修正)。 + * 写操作一律 POST(原 EditType/DelTemplate 为 GET 写操作,已 POST 化)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/template/") +public class TemplateController { + + @Autowired + private ITemplateService templateService; + + /** + * 获取模板类型列表。 + */ + @GetMapping("Typelist") + public List> getTypelist() { + return templateService.getTypelist(); + } + + /** + * 新增/编辑模板类型(原 GET EditType,已 POST 化)。 + */ + @PostMapping("TypeEdit") + public void typeEdit( + @RequestParam("typeid") Integer typeid, + @RequestParam("TypeName") String typeName) { + templateService.editType(typeid, typeName); + } + + /** + * 删除模板类型(原 GET DelType,已 POST 化)。 + */ + @PostMapping("TypeDelete") + public void typeDelete(@RequestParam("typeid") Integer typeid) { + templateService.delType(typeid); + } + + /** + * 分页获取模板列表。 + */ + @GetMapping("List") + public Map list( + @RequestParam("PageSize") Integer pageSize, + @RequestParam("PageIndex") Integer pageIndex, + @RequestParam(value = "typeid", defaultValue = "0") Integer typeid) { + return templateService.getTemplatelist(pageSize, pageIndex, typeid); + } + + /** + * 新增/编辑模板。 + */ + @PostMapping("TemplateEdit") + public void templateEdit( + @RequestParam(value = "SerID", required = false) Integer serID, + @RequestParam("Title") String title, + @RequestParam("Contents") String contents, + @RequestParam(value = "Type", required = false) Integer type, + @RequestParam(value = "Cover", required = false) Integer cover) { + templateService.editTemplate(serID, title, contents, type, cover); + } + + /** + * 删除模板(原 GET DelTemplate,已 POST 化)。 + */ + @PostMapping("TemplateDelete") + public void templateDelete(@RequestParam("SerID") Integer serid) { + templateService.delTemplate(serid); + } + + /** + * 获取单个模板详情。 + */ + @GetMapping("TemplateFirst") + public Map templateFirst(@RequestParam("serid") Integer serid) { + return templateService.getTemplateFist(serid); + } +} diff --git a/src/main/java/com/yunhai/homework/entity/BaseEntity.java b/src/main/java/com/yunhai/homework/entity/BaseEntity.java new file mode 100644 index 0000000..5f98e8c --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/BaseEntity.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.common.listener.HomeworkAuditListener; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 所有实体的公共基类:自增主键 + 创建人/时间 + 更新人/时间(由 {@link HomeworkAuditListener} 自动填充)。 + */ +@MappedSuperclass +@Getter +@Setter +@EntityListeners(HomeworkAuditListener.class) +public abstract class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 创建人ID(平台用户ID) + */ + @Column(name = "create_by", updatable = false) + private String createBy; + + /** + * 创建时间 + */ + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + /** + * 更新人ID(平台用户ID) + */ + @Column(name = "update_by") + private String updateBy; + + /** + * 更新时间 + */ + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/ClassStudent.java b/src/main/java/com/yunhai/homework/entity/ClassStudent.java new file mode 100644 index 0000000..793e295 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ClassStudent.java @@ -0,0 +1,59 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 班级学生名册快照(PRD class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据),每日全量刷 + 手动触发; + * 同步不物理删,转出置 status=0。学生身份口径:用户中心数字ID(UserContext.getUserId()) + * ↔ student_id(SchStudent.UserStuSerID),名册仅存学生ID + 姓名快照。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "class_student", + uniqueConstraints = @UniqueConstraint(name = "uk_class_student", columnNames = {"class_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ClassStudent extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; + + /** 年级名快照 */ + @Column(name = "grade_name", length = 50) + private String gradeName; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 在班状态:1 在班 / 0 已转出退课 */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** 离班时间 */ + @Column(name = "leave_at") + private LocalDateTime leaveAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroup.java b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java new file mode 100644 index 0000000..a002e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 联系组。对应原 .NET Contacts_Group 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroup extends BaseEntity { + + /** 组名称 */ + @Column(name = "group_name", length = 200) + private String groupName; + + /** 创建人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "creator_id") + private Integer creatorId; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java new file mode 100644 index 0000000..eb07763 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 联系组-用户关联。对应原 .NET Contacts_Tea 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group_user", + uniqueConstraints = @UniqueConstraint(name = "uk_group_user", + columnNames = {"group_id", "user_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroupUser extends BaseEntity { + + /** 联系组ID */ + @Column(name = "group_id", nullable = false) + private Integer groupId; + + /** 用户ID(基础平台用户ID) */ + @Column(name = "user_id", nullable = false) + private Integer userId; + + /** 用户姓名 */ + @Column(name = "user_name", length = 64) + private String userName; +} diff --git a/src/main/java/com/yunhai/homework/entity/Homework.java b/src/main/java/com/yunhai/homework/entity/Homework.java new file mode 100644 index 0000000..6e615f2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Homework.java @@ -0,0 +1,208 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 课程作业主表。对应原 .NET Notify_Message(已剔除短信/模板/门户等无关字段)。 + * + * 阅读状态不落主表:采用「懒标记」模型——读者首次查看时写入 {@link HomeworkRead}, + * 不存在阅读记录即视为未读,从而无需在发作业时枚举全校用户预插接收行。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Homework extends BaseEntity { + + /** + * 作业标题 + */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** + * 学校ID(多租户隔离:发布时从当前用户角色填充,查询时按校过滤)。 + */ + @Column(name = "sch_id") + private Integer schId; + + /** + * 作业内容 + */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** + * 作业类型:1 普通 / 2 重要(见 {@link com.yunhai.homework.enums.HomeworkTypeEnum}) + */ + @Column(name = "type", nullable = false) + private Integer type = 1; + + /** + * 发布人ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "publisher_id") + private Integer publisherId; + + /** + * 发布人姓名 + */ + @Column(name = "publisher_name", length = 64) + private String publisherName; + + /** + * 发布部门ID(原 DeptID) + */ + @Column(name = "dept_id", length = 64) + private String deptId; + + /** + * 发布部门名称 + */ + @Column(name = "dept_name", length = 128) + private String deptName; + + /** + * 状态:1 已发布(无草稿态,见 {@link com.yunhai.homework.enums.HomeworkStatusEnum}) + */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** + * 是否置顶:0 否 / 1 是(原 topmost) + */ + @Column(name = "topmost") + private Integer topmost = 0; + + // ==================== 迁移补全字段(对应 C# Notify_Message) ==================== + + /** + * 接收人列表(JSON 数组,如 ["10012","10013"] 或接收组ID列表)。 + * 对应 C# Notify_Message.ReceiveList + */ + @Column(name = "receive_list", columnDefinition = "LONGTEXT") + private String receiveList; + + /** + * 密码发送(对应 C# Notify_Message.PwdSend) + */ + @Column(name = "pwd_send", length = 255) + private String pwdSend; + + /** + * 抄送人列表(对应 C# Notify_Message.ChaoSend) + */ + @Column(name = "chao_send", length = 2000) + private String chaoSend; + + /** + * 已读数(发送时初始化为 0,也可由 HomeworkRead 表 COUNT 实时计算)。 + * 对应 C# Notify_Message.readcount + */ + @Column(name = "read_count") + private Integer readCount = 0; + + /** + * 应读数(接收人总数)。对应 C# Notify_Message.allowcount + */ + @Column(name = "allow_count") + private Integer allowCount = 0; + + /** + * 父作业ID(转发时关联原作业)。对应 C# Notify_Message.fkid + */ + @Column(name = "parent_id") + private Integer parentId; + + /** + * 回复ID。对应 C# Notify_Message.reid + */ + @Column(name = "reply_id", length = 255) + private String replyId; + + /** + * 是否撤回:0 否 / 1 是。对应 C# Notify_Message.ispull + */ + @Column(name = "is_pulled") + private Integer isPulled = 0; + + /** + * 定时发送时间(到时间后自动发布)。对应 C# Notify_Message.DsfsTime。 + * PRD 语义中的 scheduled_at 复用本列。 + */ + @Column(name = "timing_send_time") + private LocalDateTime timingSendTime; + + /** + * 短信发送标记:0 不发短信 / 1 发短信。对应 C# Notify_Message.dxbit + */ + @Column(name = "sms_flag") + private Integer smsFlag = 0; + + // ==================== 课堂作业 V3(PRD V1.0)扩展列 ==================== + + /** + * 学科(字典值)。 + */ + @Column(name = "subject", length = 50) + private String subject; + + /** + * 截止时间(精确到分钟;空 = 仅查阅型,不设截止,到点关闭任务跳过)。 + */ + @Column(name = "deadline_at") + private LocalDateTime deadlineAt; + + /** + * 作业流程状态(PRD 五态,见 {@link com.yunhai.homework.enums.HwStatusEnum})。 + * notice 原 status 列继续承载发布/撤回语义,两者并存。 + */ + @Column(name = "hw_status", nullable = false, length = 20) + private String hwStatus = com.yunhai.homework.enums.HwStatusEnum.DRAFT_UNPUBLISHED.getCode(); + + /** + * 发布时间(立即发布或定时到点发布时写入)。 + */ + @Column(name = "published_at") + private LocalDateTime publishedAt; + + /** + * 提交方式,JSON 数组文本,如 ["image","attach"]。 + * 取值:text / image / attach / none(PRD 的 图片/文件/两者 映射为 + * ["image"] / ["attach"] / ["image","attach"])。 + */ + @Column(name = "submit_type", length = 100) + private String submitType; + + /** + * 截止后是否允许逾期提交:0 否 / 1 是。 + */ + @Column(name = "allow_late", nullable = false) + private Integer allowLate = 0; + + /** + * 提醒设置:截止前 1 天提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1d", nullable = false) + private Integer remind1d = 1; + + /** + * 提醒设置:截止前 1 小时提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1h", nullable = false) + private Integer remind1h = 1; + + /** + * 归档时间(教师手动归档时写入;归档后只读,所有提醒任务跳过)。 + */ + @Column(name = "archived_at") + private LocalDateTime archivedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java new file mode 100644 index 0000000..6fc7804 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业附件表(PRD homework_attachment)。文件实体仍存 up_file(file_id 关联 + file_url 冗余)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_attachment") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkAttachment extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** up_file.id(可空:允许外部 URL 附件) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file / ... */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号(越小越靠前) */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkClass.java b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java new file mode 100644 index 0000000..c01cefb --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 作业-班级关联表(PRD homework_class)。 + * 发布/编辑时由 receiveList("all" 或班级 id 串)解析写入,是应交名单与统计的权威关联。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_class", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_class", columnNames = {"homework_id", "class_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkClass extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkRead.java b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java new file mode 100644 index 0000000..35b773a --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.time.LocalDateTime; + +/** + * 作业阅读记录(懒标记)。对应原 .NET Notify_ReceiveList 的「逐用户阅读状态」语义, + * 但仅在读者首次查看时落库,避免发作业时预插全员。 + * + * 唯一约束 (homework_id, reader_id):同一用户对同一作业只记一条已读。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read", + uniqueConstraints = @UniqueConstraint( + name = "uk_homework_reader", + columnNames = {"homework_id", "reader_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkRead extends BaseEntity { + + /** + * 作业ID(关联 homework.id) + */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** + * 读者ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "reader_id", nullable = false) + private Integer readerId; + + /** + * 读者姓名 + */ + @Column(name = "reader_name", length = 64) + private String readerName; + + /** + * 阅读时间 + */ + @Column(name = "read_time") + private LocalDateTime readTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java new file mode 100644 index 0000000..8179e79 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java @@ -0,0 +1,58 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 学生作业阅读记录(PRD homework_read_record)。 + * + * 学生维度,含首次/最近阅读时间,与 notice 语义的 {@link HomeworkRead}(教师/阅读者已读)互不影响。 + * 行仅在判定已读时写入;未读名单 = 名册左连本表(无记录或 is_read=0)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read_record", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student", columnNames = {"homework_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReadRecord extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", length = 64) + private String classId; + + /** 阅读状态:0 UNREAD / 1 READ */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 首次阅读时间(写入后不再变更) */ + @Column(name = "first_read_at") + private LocalDateTime firstReadAt; + + /** 最近阅读时间(每次上报刷新) */ + @Column(name = "last_read_at") + private LocalDateTime lastReadAt; + + /** 阅读次数 */ + @Column(name = "read_count", nullable = false) + private Integer readCount = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java new file mode 100644 index 0000000..97e068b --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java @@ -0,0 +1,45 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 作业接收人关联表(替代旧 receive_list JSON 字符串存储)。 + * + * 每条记录代表一个作业的一个接收人(用户ID + 姓名快照,不含账号)。 + * 全员作业(receive_list="ALL")不写明细行, + * 读取时按 receive_list="ALL" 标识走旧逻辑兜底。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_receiver", + indexes = { + @Index(name = "idx_homework_id", columnList = "homework_id"), + @Index(name = "idx_receiver", columnList = "receiver_id") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReceiver extends BaseEntity { + + /** 作业ID(关联 homework.id) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 接收人ID(用户中心数字ID) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收类型:1 接收 / 2 抄送 */ + @Column(name = "receive_type", nullable = false) + private Integer receiveType = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java new file mode 100644 index 0000000..4626044 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java @@ -0,0 +1,98 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.enums.ReviewStatusEnum; +import com.yunhai.homework.enums.SubmitStatusEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 作业提交表,多版本建模(PRD homework_submission)。 + * + * 同一学生 (homework_id, student_id) 下 version 单调递增;提交/打回重交/撤回均新增行, + * 旧行 is_latest 置 0 保留历史;is_latest=1 的行是当前有效版本。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_submission", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student_ver", + columnNames = {"homework_id", "student_id", "version"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkSubmission extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", nullable = false, length = 50) + private String className; + + /** 提交版本,同一学生单调递增(撤回/重交均 +1) */ + @Column(name = "version", nullable = false) + private Integer version = 1; + + /** 是否当前版本:1 是 / 0 历史 */ + @Column(name = "is_latest", nullable = false) + private Integer isLatest = 1; + + /** 提交状态(见 {@link SubmitStatusEnum}) */ + @Column(name = "status", nullable = false, length = 20) + private String status = SubmitStatusEnum.SUBMITTED.getCode(); + + /** 是否逾期:0 否 / 1 是 */ + @Column(name = "is_late", nullable = false) + private Integer isLate = 0; + + /** 提交时间(NOT_SUBMITTED 空行为空) */ + @Column(name = "submitted_at") + private LocalDateTime submittedAt; + + /** 文本作业内容 */ + @Column(name = "text", columnDefinition = "TEXT") + private String text; + + /** 评分 */ + @Column(name = "score", precision = 6, scale = 2) + private BigDecimal score; + + /** 评语(打回时 = 订正原因,必填) */ + @Column(name = "comment", columnDefinition = "TEXT") + private String comment; + + /** 批阅状态(见 {@link ReviewStatusEnum}) */ + @Column(name = "review_status", nullable = false, length = 20) + private String reviewStatus = ReviewStatusEnum.PENDING.getCode(); + + /** 批阅人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "reviewed_by") + private Integer reviewedBy; + + /** 批阅人姓名快照 */ + @Column(name = "reviewed_by_name", length = 64) + private String reviewedByName; + + /** 批阅时间 */ + @Column(name = "reviewed_at") + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java new file mode 100644 index 0000000..f088c92 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板。对应原 .NET Notify_Template 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_template") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkTemplate extends BaseEntity { + + /** 模板标题 */ + @Column(name = "title", length = 200) + private String title; + + /** 模板内容(HTML) */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** 封面图片文件ID */ + @Column(name = "cover") + private Integer cover; + + /** 引用次数 */ + @Column(name = "ref_count") + private Integer refCount = 0; + + /** 模板类型ID */ + @Column(name = "type_id") + private Integer typeId; +} diff --git a/src/main/java/com/yunhai/homework/entity/Notification.java b/src/main/java/com/yunhai/homework/entity/Notification.java new file mode 100644 index 0000000..2a931a1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Notification.java @@ -0,0 +1,71 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 站内通知(PRD notification)。六类通知的统一落表 + 已读状态。 + * 通知已读独立于作业阅读记录(本表 is_read 只反映通知列表已读)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "notification", + indexes = { + @Index(name = "idx_receiver", columnList = "receiver_id, is_read"), + @Index(name = "idx_hw_type", columnList = "homework_id, type") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Notification extends BaseEntity { + + /** 通知类型(见 {@link com.yunhai.homework.enums.NotificationTypeEnum}) */ + @Column(name = "type", nullable = false, length = 30) + private String type; + + /** 关联作业ID */ + @Column(name = "homework_id") + private Integer homeworkId; + + /** 关联业务ID(如 submissionId) */ + @Column(name = "related_id") + private Integer relatedId; + + /** 通知标题 */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** 通知内容 */ + @Column(name = "content", length = 1000) + private String content; + + /** 接收人ID(学生 = student_id,教师 = 用户中心数字ID,同一 ID 空间) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收人角色:student / teacher */ + @Column(name = "receiver_role", nullable = false, length = 20) + private String receiverRole = "student"; + + /** 是否已读:0 未读 / 1 已读 */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 已读时间 */ + @Column(name = "read_at") + private LocalDateTime readAt; + + /** 发送通道:inapp 站内 / sms 短信 */ + @Column(name = "send_channel", nullable = false, length = 20) + private String sendChannel = "inapp"; +} diff --git a/src/main/java/com/yunhai/homework/entity/SchClassTeach.java b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java new file mode 100644 index 0000000..d1a091f --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java @@ -0,0 +1,46 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 班级任教关系快照(bschclass sch_class_teach 本地镜像,PRD 口径同 class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据);bschclass 任教接口 + * 不可用时,任教学科链路降级读本表。教师口径:用户中心数字ID(UserContext.getUserId()); + * class_id 与 class_student.class_id 同一取值域。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sch_class_teach", + uniqueConstraints = @UniqueConstraint(name = "uk_sch_class_teach", columnNames = {"school_id", "class_id", "tea_id", "sub_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SchClassTeach extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID(与 class_student.class_id 同一取值域) */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 教师ID(用户中心数字ID) */ + @Column(name = "tea_id", nullable = false) + private Integer teaId; + + /** 学科编码(bschclass SubID;本地无字典时编码即展示名) */ + @Column(name = "sub_id", nullable = false, length = 64) + private String subId; + + /** 有效状态:1 有效 / 0 失效 */ + @Column(name = "status", nullable = false) + private Integer status = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsModule.java b/src/main/java/com/yunhai/homework/entity/SmsModule.java new file mode 100644 index 0000000..3f4d444 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsModule.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 短信模块配置。对应原 .NET SchSms 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_module") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsModule extends BaseEntity { + + /** 模块名称 */ + @Column(name = "module_name", length = 100) + private String moduleName; + + /** 模块标识 */ + @Column(name = "module_code", length = 50) + private String moduleCode; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java new file mode 100644 index 0000000..428d271 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java @@ -0,0 +1,37 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 学校短信开通配置。对应原 .NET SchSms_Set 表。 + * 记录某学校某模块是否开通短信。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_school_config") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsSchoolConfig extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 学校名称 */ + @Column(name = "school_name", length = 200) + private String schoolName; + + /** 模块ID */ + @Column(name = "module_id") + private Integer moduleId; + + /** 是否开通短信 */ + @Column(name = "is_sms") + private Boolean isSms = false; +} diff --git a/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java new file mode 100644 index 0000000..85df7ba --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java @@ -0,0 +1,54 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 提交附件表(PRD submission_attachment)。替代 JSON 方案,支撑矩阵缩略图与批阅大图预览。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "submission_attachment", + indexes = @Index(name = "idx_sub", columnList = "submission_id")) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SubmissionAttachment extends BaseEntity { + + /** 提交行ID(homework_submission.id) */ + @Column(name = "submission_id", nullable = false) + private Integer submissionId; + + /** 作业ID(冗余,便于统计与清理) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + @Column(name = "student_id") + private Integer studentId; + + /** up_file.id(可空:允许外部 URL) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号 */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/SysDict.java b/src/main/java/com/yunhai/homework/entity/SysDict.java new file mode 100644 index 0000000..3091084 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SysDict.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 系统字典(发文部门等)。对应原 .NET Common_DeptDic 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sys_dict") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SysDict extends BaseEntity { + + /** 字典类型(如 "dept"=发文部门) */ + @Column(name = "dict_type", length = 50) + private String dictType; + + /** 字典 key(原 CodeID) */ + @Column(name = "dict_key", length = 100) + private String dictKey; + + /** 字典 value(显示名称,原 Name) */ + @Column(name = "dict_value", length = 200) + private String dictValue; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; + + /** 是否启用:0 禁用 / 1 启用(原 Enable) */ + @Column(name = "enable_flag") + private Integer enableFlag = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/TemplateType.java b/src/main/java/com/yunhai/homework/entity/TemplateType.java new file mode 100644 index 0000000..aab8163 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/TemplateType.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板类型。对应原 .NET Notify_TemplateType 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "template_type") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class TemplateType extends BaseEntity { + + /** 类型名称 */ + @Column(name = "type_name", length = 100) + private String typeName; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/UpFile.java b/src/main/java/com/yunhai/homework/entity/UpFile.java new file mode 100644 index 0000000..38248c7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/UpFile.java @@ -0,0 +1,47 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 附件文件记录。对应原 .NET Common_UpFiles 表。 + * + * 存储文件元信息(真实文件名、存储路径、大小、类型),物理文件存储在 + * application.yml 的 file.upload-path 目录下。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "up_file") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class UpFile extends BaseEntity { + + /** 应用类型(如 "homework"),用于区分不同业务的附件 */ + @Column(name = "topic_type", length = 50) + private String topicType; + + /** 关联主题ID(如作业 serid) */ + @Column(name = "topic_id") + private Integer topicId; + + /** 文件真实名称(用户上传时的原始文件名) */ + @Column(name = "file_true_name", length = 255) + private String fileTrueName; + + /** 文件存储路径(相对路径,如 homework/20260823/uuid.pdf) */ + @Column(name = "file_name", length = 500) + private String fileName; + + /** 文件大小(字节) */ + @Column(name = "file_size") + private Integer fileSize; + + /** 文件后缀名(如 pdf, docx, jpg) */ + @Column(name = "file_type", length = 20) + private String fileType; +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java new file mode 100644 index 0000000..124bec9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业状态。无草稿态:发作业一律置为「已发布」, + * 定时发送通过 timingSendTime + 查询侧「已到点」过滤控制可见性。 + * 对应原 .NET Notify_Message.status(原系统已发布=2,此处归一化为 1)。 + */ +@Getter +public enum HomeworkStatusEnum { + + PUBLISHED(1, "已发布"); + + private final int code; + private final String desc; + + HomeworkStatusEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkStatusEnum of(Integer code) { + if (code == null) { + return PUBLISHED; + } + for (HomeworkStatusEnum e : values()) { + if (e.code == code) { + return e; + } + } + return PUBLISHED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java new file mode 100644 index 0000000..077396d --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业类型。对应原 .NET Notify_Message.type(原系统作业类=2,此处归一化)。 + */ +@Getter +public enum HomeworkTypeEnum { + + NORMAL(1, "普通作业"), + IMPORTANT(2, "重要作业"); + + private final int code; + private final String desc; + + HomeworkTypeEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkTypeEnum of(Integer code) { + if (code == null) { + return NORMAL; + } + for (HomeworkTypeEnum e : values()) { + if (e.code == code) { + return e; + } + } + return NORMAL; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java new file mode 100644 index 0000000..8b10083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业流程状态(PRD 五态,存 homework.hw_status)。 + * + * 与 notice 原 status(Integer,发布/撤回语义)并存:本枚举承载课堂作业业务流转, + * 流转收敛在 HomeworkStateService,条件更新保证幂等。 + */ +@Getter +public enum HwStatusEnum { + + /** 草稿 - 未发布 */ + DRAFT_UNPUBLISHED("DRAFT_UNPUBLISHED", "草稿-未发布"), + /** 草稿 - 定时待发布 */ + DRAFT_SCHEDULED("DRAFT_SCHEDULED", "草稿-定时待发布"), + /** 已发布 - 进行中 */ + PUBLISHED("PUBLISHED", "已发布-进行中"), + /** 已截止 */ + CLOSED("CLOSED", "已截止"), + /** 已归档(只读,不再触发任何提醒) */ + ARCHIVED("ARCHIVED", "已归档"); + + private final String code; + private final String desc; + + HwStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HwStatusEnum of(String code) { + if (code == null) { + return DRAFT_UNPUBLISHED; + } + for (HwStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return DRAFT_UNPUBLISHED; + } + + public boolean isDraft() { + return this == DRAFT_UNPUBLISHED || this == DRAFT_SCHEDULED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java new file mode 100644 index 0000000..df75af1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 站内通知类型(PRD 六类,存 notification.type)。 + */ +@Getter +public enum NotificationTypeEnum { + + /** 作业发布(对应班级学生) */ + HOMEWORK_PUBLISHED("HOMEWORK_PUBLISHED", "作业发布"), + /** 截止提醒(截止前 1 天 / 1 小时) */ + DEADLINE_REMINDER("DEADLINE_REMINDER", "截止提醒"), + /** 未交提醒(教师手动) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未交提醒"), + /** 未读提醒(教师手动) */ + NOT_READ("NOT_READ", "未读提醒"), + /** 批阅完成 */ + REVIEW_DONE("REVIEW_DONE", "批阅完成"), + /** 打回通知 */ + RETURNED("RETURNED", "打回通知"), + /** + * 定时发布失败回退(PRD §17.1 教师泳道要求通知教师,但 PRD 枚举表未列—— + * 按 classwork-homework-plan-v3 §13-2 建议补充,待产品确认命名)。 + */ + SCHEDULE_FAILED("SCHEDULE_FAILED", "定时发布失败"), + /** + * 截止时间变更(PRD §17.1 延长截止要求通知学生,PRD 枚举表未列——同上补充)。 + */ + DEADLINE_CHANGED("DEADLINE_CHANGED", "截止时间变更"); + + private final String code; + private final String desc; + + NotificationTypeEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static NotificationTypeEnum of(String code) { + if (code == null) { + return null; + } + for (NotificationTypeEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return null; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java new file mode 100644 index 0000000..031eddc --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java @@ -0,0 +1,39 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 批阅状态(PRD 三态,存 homework_submission.review_status)。 + * 与 SubmitStatusEnum 同源联动:批阅保存 → status=REVIEWED + review_status=REVIEWED; + * 打回 → status=RETURNED + review_status=RETURNED。 + */ +@Getter +public enum ReviewStatusEnum { + + /** 待批阅 */ + PENDING("PENDING", "待批阅"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"), + /** 已打回 */ + RETURNED("RETURNED", "已打回"); + + private final String code; + private final String desc; + + ReviewStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static ReviewStatusEnum of(String code) { + if (code == null) { + return PENDING; + } + for (ReviewStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return PENDING; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java new file mode 100644 index 0000000..7d6aaca --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java @@ -0,0 +1,50 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 学生提交状态(PRD 五态,存 homework_submission.status)。 + * + * NOT_SUBMITTED 空行仅由「撤回」产生;未提交学生本身不落行(统计时由名册左连补位)。 + */ +@Getter +public enum SubmitStatusEnum { + + /** 未提交(撤回后产生的空提交行) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未提交"), + /** 已提交(含打回后重新提交) */ + SUBMITTED("SUBMITTED", "已提交"), + /** 逾期提交(截止后提交,is_late=1) */ + LATE_SUBMITTED("LATE_SUBMITTED", "逾期提交"), + /** 已打回(待重新提交) */ + RETURNED("RETURNED", "已打回"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"); + + private final String code; + private final String desc; + + SubmitStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static SubmitStatusEnum of(String code) { + if (code == null) { + return NOT_SUBMITTED; + } + for (SubmitStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return NOT_SUBMITTED; + } + + /** + * 是否计入「已交」口径:打回未重交也算已交(PRD 学生矩阵口径)。 + */ + public boolean countsAsSubmitted() { + return this != NOT_SUBMITTED; + } +} diff --git a/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java new file mode 100644 index 0000000..5377254 --- /dev/null +++ b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java @@ -0,0 +1,161 @@ +package com.yunhai.homework.job; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.enums.HwStatusEnum; +import com.yunhai.homework.enums.NotificationTypeEnum; +import com.yunhai.homework.repository.HomeworkClassRepository; +import com.yunhai.homework.repository.HomeworkRepository; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.HomeworkStateService; +import com.yunhai.homework.service.impl.NotificationService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 课堂作业定时任务(PRD §17.1 调度泳道)。 + * + * 全部基于状态机 CAS 条件更新,天然幂等,可安全重复执行; + * ARCHIVED 作业被显式跳过(归档后不再触发任何提醒)。 + */ +@Component +public class HomeworkScheduledJobs { + + private static final Logger log = LoggerFactory.getLogger(HomeworkScheduledJobs.class); + + @Autowired + private HomeworkRepository homeworkRepository; + + @Autowired + private HomeworkClassRepository homeworkClassRepository; + + @Autowired + private HomeworkStateService stateService; + + @Autowired + private NotificationService notificationService; + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + /** + * 定时发布:每分钟扫描到点的 DRAFT_SCHEDULED 作业 → PUBLISHED + 作业发布通知; + * 失败回退 DRAFT_UNPUBLISHED 并站内通知教师。 + */ + @Scheduled(cron = "0 * * * * ?") + public void scheduledPublish() { + List due = homeworkRepository + .findByHwStatusAndTimingSendTimeLessThanEqual(HwStatusEnum.DRAFT_SCHEDULED.getCode(), + LocalDateTime.now()); + if (due.isEmpty()) { + return; + } + log.info("[scheduledPublish] 到点作业 {} 个", due.size()); + for (Homework homework : due) { + Integer id = homework.getId(); + try { + teacherHomeworkV2Service.publishInternal(id, HwStatusEnum.DRAFT_SCHEDULED); + log.info("[scheduledPublish] 发布成功 homeworkId={}", id); + } catch (Exception e) { + log.error("[scheduledPublish] 发布失败,回退草稿 homeworkId={}", id, e); + try { + boolean rolledBack = stateService.rollbackFromSchedule(id); + if (rolledBack) { + notificationService.send(NotificationTypeEnum.SCHEDULE_FAILED, id, null, + homework.getPublisherId(), homework.getPublisherName(), + NotificationService.ROLE_TEACHER, + "定时发布失败:《" + homework.getTitle() + "》", + "定时发布任务执行异常,作业已回退为「草稿-未发布」,请重新发布。"); + } + } catch (Exception ex) { + log.error("[scheduledPublish] 回退失败 homeworkId={}", id, ex); + } + } + } + } + + /** + * 到点截止:每分钟扫描已过截止时间的 PUBLISHED 作业 → CLOSED(无对应通知类型,不推送)。 + */ + @Scheduled(cron = "30 * * * * ?") + public void deadlineClose() { + List overdue = homeworkRepository + .findByHwStatusAndDeadlineAtBefore(HwStatusEnum.PUBLISHED.getCode(), LocalDateTime.now()); + for (Homework homework : overdue) { + if (stateService.cas(homework.getId(), HwStatusEnum.PUBLISHED, HwStatusEnum.CLOSED)) { + log.info("[deadlineClose] 作业已截止 homeworkId={}", homework.getId()); + } + } + } + + /** + * 截止提醒:每分钟扫描截止时间在 24h 内的 PUBLISHED 作业, + * 命中「1 天档 / 1 小时档」窗口且开关开启时向未交学生推送 DEADLINE_REMINDER; + * 档位去重(related_id:1=1 天档 2=1 小时档),每作业每档至多发一轮。 + */ + @Scheduled(cron = "40 * * * * ?") + public void deadlineReminder() { + LocalDateTime now = LocalDateTime.now(); + List window = homeworkRepository + .findByHwStatusAndDeadlineAtBetween(HwStatusEnum.PUBLISHED.getCode(), now, now.plusHours(24)); + for (Homework homework : window) { + if (homework.getDeadlineAt() == null) { + continue; + } + // 1 小时档:截止前 [55, 60) 分钟窗口 + if (homework.getRemind1h() != null && homework.getRemind1h() == 1 + && inWindow(homework.getDeadlineAt(), now, 55, 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 2)) { + sendReminder(homework, 2, "1 小时"); + } + // 1 天档:截止前 [23, 24) 小时窗口 + if (homework.getRemind1d() != null && homework.getRemind1d() == 1 + && inWindow(homework.getDeadlineAt(), now, 23 * 60, 24 * 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 1)) { + sendReminder(homework, 1, "1 天"); + } + } + } + + private boolean inWindow(LocalDateTime deadline, LocalDateTime now, int fromMinutes, int toMinutes) { + long minutes = java.time.Duration.between(now, deadline).toMinutes(); + return minutes >= fromMinutes && minutes < toMinutes; + } + + private void sendReminder(Homework homework, int slot, String slotDesc) { + // 通知接收人直接使用学生ID(名册口径:student_id = 平台用户ID) + List targets = teacherHomeworkV2Service.notSubmittedStudentIds(homework.getId()).stream() + .distinct() + .collect(Collectors.toList()); + if (targets.isEmpty()) { + return; + } + int sent = notificationService.sendBatch(NotificationTypeEnum.DEADLINE_REMINDER, homework.getId(), slot, + NotificationService.distinct(targets), + NotificationService.deadlineReminderTitle(homework), + NotificationService.deadlineReminderContent(homework) + "(剩余不足 " + slotDesc + ")"); + log.info("[deadlineReminder] homeworkId={}, slot={}, sent={}", homework.getId(), slot, sent); + } + + /** + * 名册同步:每日 02:00 全量刷新 class_student(bschclass 接口就绪前仅日志)。 + */ + @Scheduled(cron = "${homework.sync.class-data-cron:0 0 2 * * ?}") + public void classDataSync() { + try { + classStudentService.syncAll(); + } catch (Exception e) { + log.error("[classDataSync] 名册同步失败", e); + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageQuery.java b/src/main/java/com/yunhai/homework/model/BasePageQuery.java new file mode 100644 index 0000000..7f46326 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageQuery.java @@ -0,0 +1,14 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +/** + * 统一分页查询入参。pageIndex 从 1 起。 + */ +@Data +public class BasePageQuery { + private int pageIndex = 1; + private int pageSize = 10; + /** 通用模糊搜索关键词 */ + private String keyword; +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageResult.java b/src/main/java/com/yunhai/homework/model/BasePageResult.java new file mode 100644 index 0000000..058c96c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageResult.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +import java.util.List; + +/** + * 统一分页响应外壳。 + */ +@Data +public class BasePageResult { + /** 总记录数 */ + private int total; + /** 当前页数据 */ + private List content; + /** 当前页码(从 1 起) */ + private int pageIndex; + /** 每页条数 */ + private int pageSize; + /** 总页数 */ + private int totalPages; + /** 是否还有下一页 */ + private boolean hasNext; + + public BasePageResult(List content, int total, int pageIndex, int pageSize, int totalPages, boolean hasNext) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + } +} diff --git a/src/main/java/com/yunhai/homework/model/SchTeaList.java b/src/main/java/com/yunhai/homework/model/SchTeaList.java new file mode 100644 index 0000000..80f9b9c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/SchTeaList.java @@ -0,0 +1,106 @@ +package com.yunhai.homework.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * 班级管理接口-教师(bschclass {@code /oapi/DbClass/GetSchteaList} 响应项)。 + * 对应 C# BestSchool.Entity.Model.SchTeaList。 + */ +public class SchTeaList { + + /** 学校 GUID(SchSerID) */ + @JsonProperty("SchSerID") + private String schSerID; + + @JsonProperty("TeaTel") + private String teaTel; + + /** 教师姓名 */ + @JsonProperty("TeaName") + private String teaName; + + /** 教师用户ID */ + @JsonProperty("UserTeaSerID") + private Integer userTeaSerID; + + @JsonProperty("TeaIDCard") + private String teaIDCard; + + /** 教师工号 */ + @JsonProperty("TeaNo") + private String teaNo; + + /** 教师所属组ID列表(兼容旧字段 GroupIds) */ + @JsonProperty("GroupIds") + private List groupIds; + + /** 教师所属组ID(接口现行字段 GroupID,单个) */ + @JsonProperty("GroupID") + private Integer groupId; + + public Integer getGroupId() { + return groupId; + } + + public void setGroupId(Integer groupId) { + this.groupId = groupId; + } + + public String getSchSerID() { + return schSerID; + } + + public void setSchSerID(String schSerID) { + this.schSerID = schSerID; + } + + public String getTeaTel() { + return teaTel; + } + + public void setTeaTel(String teaTel) { + this.teaTel = teaTel; + } + + public String getTeaName() { + return teaName; + } + + public void setTeaName(String teaName) { + this.teaName = teaName; + } + + public Integer getUserTeaSerID() { + return userTeaSerID; + } + + public void setUserTeaSerID(Integer userTeaSerID) { + this.userTeaSerID = userTeaSerID; + } + + public String getTeaIDCard() { + return teaIDCard; + } + + public void setTeaIDCard(String teaIDCard) { + this.teaIDCard = teaIDCard; + } + + public String getTeaNo() { + return teaNo; + } + + public void setTeaNo(String teaNo) { + this.teaNo = teaNo; + } + + public List getGroupIds() { + return groupIds; + } + + public void setGroupIds(List groupIds) { + this.groupIds = groupIds; + } +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java new file mode 100644 index 0000000..cf9c417 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 班级 DTO(/oapi/VSchClass/GetVClassList,SchClass 视图)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchClassDto { + + /** 班级业务ID(sch_class.class_id,scopeType=班级 的 scopeId;学生 ClassID 指向它) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级名称 */ + @JsonProperty("ClassName") + private String className; + + /** 年级ID(= gradeId) */ + @JsonProperty("Levell") + private Integer gradeId; + + /** 排序号 */ + @JsonProperty("sx") + private Integer sort; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java new file mode 100644 index 0000000..01ff15b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 年级 DTO(/oapi/Schoolen/GetSchLenLevellist,BaseSchsystemGrade)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchGradeDto { + + /** 年级ID(=gradeId,scopeType=年级 的 scopeId;班级 Levell 指向它) */ + @JsonProperty("Id") + private Integer id; + + /** 年级名称(gradeNickname,如「初一」) */ + @JsonProperty("Name") + private String name; + + /** 所属学段(学制)ID */ + @JsonProperty("OwnerId") + private Integer ownerId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java new file mode 100644 index 0000000..06185b6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校信息 DTO(/oapi/SchInfo/GetUserSchFirst)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchInfoDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerId") + private String schSerId; + + /** 用户中心学校组ID(与 CampusNotice schId 同域) */ + @JsonProperty("UserSchSerId") + private Integer userSchSerId; + + /** 学校名称 */ + @JsonProperty("SchName") + private String schName; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java new file mode 100644 index 0000000..f96913d --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学生 DTO(/oapi/OAStu/*,SchStudent 视图)。 + * account 仅为上游接口契约字段(兼容反序列化),本系统落库/身份映射不再使用; + * 学生身份以 stuId(UserStuSerID,= 平台用户ID)+ stuName 承载。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchStudentDto { + + /** 学员登录账号(上游契约字段,仅兼容保留,不落库) */ + @JsonProperty("Account") + private String account; + + /** 学生ID */ + @JsonProperty("UserStuSerID") + private Integer stuId; + + /** 学生姓名 */ + @JsonProperty("StuName") + private String stuName; + + /** 所在班级ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 所在组ID */ + @JsonProperty("GroupID") + private Integer groupId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java new file mode 100644 index 0000000..ff7add8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校学科 DTO(/oapi/SubInfo/GetSchSublist)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSubjectDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 学科ID(学科编码;字典无编码时为字典ID字符串) */ + @JsonProperty("SubID") + private String subId; + + /** 学科名称 */ + @JsonProperty("SubName") + private String subName; + + /** 排序号 */ + @JsonProperty("No") + private Integer no; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java new file mode 100644 index 0000000..f0df157 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学段(学制)DTO(/oapi/Schoolen/GetSchLenlist,BaseSchsystem)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSystemDto { + + /** 学制ID(scopeType=学部 的 scopeId) */ + @JsonProperty("Id") + private Integer id; + + /** 学段名称(如「小学部」「初中部」) */ + @JsonProperty("Name") + private String name; + + /** 学制类型 */ + @JsonProperty("Type") + private Integer type; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java new file mode 100644 index 0000000..86ec6f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Date; + +/** + * bschclass 任教关系 DTO(/oapi/TeachInfo/*,sch_class_teach 视图)。 + * 一条记录 = 某学校某班级中某教师任教某学科。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTeachDto { + + /** 任教记录ID(sch_class_teach.id) */ + @JsonProperty("SerID") + private Integer serId; + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 教师ID(= 平台用户ID) */ + @JsonProperty("UserTeaSerID") + private Integer teaId; + + /** 学科ID(学科编码) */ + @JsonProperty("SubID") + private String subId; + + /** 班级业务ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级ID(与 ClassID 同值,上游兼容字段) */ + @JsonProperty("UserClassID") + private Integer userClassId; + + /** 创建时间 */ + @JsonProperty("CreationTime") + private Date creationTime; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java new file mode 100644 index 0000000..29623ad --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java @@ -0,0 +1,41 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * bschclass 组织树节点 DTO(/oapi/SchGroup/GetTeaTree、GetStuTree,TeaTreeVO 视图)。 + * type=1 组节点(学校/学段/年级/班级、教师组),type=2 人员叶子(学生/教师)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTreeNodeDto { + + /** 节点ID(学校/学段/年级/班级为对应业务ID,叶子为学生 stuId 或教师 teaId) */ + private Integer id; + + /** 节点名称 */ + private String name; + + /** 父节点ID(根节点为 0) */ + private Integer pid; + + /** 排序号(组取 sort,叶子固定 0) */ + private Integer no; + + /** 1=组节点,2=人员叶子 */ + private Integer type; + + /** 是否父节点(组 true,叶子 false) */ + @JsonProperty("isParent") + private Boolean isParent; + + /** 头像(当前固定空串) */ + private String photo; + + /** 子节点(叶子为 null,组节点非 null) */ + private List children; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java new file mode 100644 index 0000000..7621af2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java @@ -0,0 +1,81 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 作业详情视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class HomeworkDetailVo { + + @JsonProperty("serid") + private Integer serid; + + @JsonProperty("title") + private String title; + + @JsonProperty("Contents") + private String contents; + + @JsonProperty("DeptID") + private String deptId; + + @JsonProperty("DeptName") + private String deptName; + + @JsonProperty("TeaName") + private String publisherName; + + @JsonProperty("EditDate") + private String editDate; + + @JsonProperty("numcount") + private Integer numCount; + + @JsonProperty("readnumcount") + private Long readNumCount; + + @JsonProperty("ReceiveList") + private String receiveList; + + @JsonProperty("DsfsTime") + private String dsfsTime; + + @JsonProperty("isDsfs") + private Boolean isDsfs; + + @JsonProperty("ChaoSend") + private List chaoSend; + + /** 接收人列表:"all" 或用户ID列表 */ + @JsonProperty("tlist") + private Object tlist; + + /** + * 接收对象可读文本("全部班级" 或班级名顿号串)。 + * 详情页「接收班级」标签直接展示,避免前端拿 ReceiveList 的 id 串渲染。 + */ + @JsonProperty("receiveText") + private String receiveText; + + /** + * 附件项(旧 ChaoSend 数组元素)。 + */ + @Data + public static class FileItem { + @JsonProperty("id") + private Integer id; + @JsonProperty("name") + private String name; + @JsonProperty("size") + private Integer size; + @JsonProperty("fileid") + private Integer fileid; + @JsonProperty("filename") + private String filename; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java new file mode 100644 index 0000000..e1c2fa2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 作业分页结果(扩展 BasePageResult,额外携带 unreadCount)。 + * + * 对齐方案 M7:unreadCount 并入分页外壳扩展子类,不改基类契约。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkPageResult extends BasePageResult { + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkPageResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + super(content, total, pageIndex, pageSize, totalPages, hasNext); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java new file mode 100644 index 0000000..afea9a6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +/** + * 作业列表查询条件。 + * + * - 管理端(adminList):用 status / type / deptId / publisherId / 时间范围过滤; + * - 用户端(userList):额外用 readType 过滤未读/已读(0 全部 / 1 未读 / 2 已读)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkQuery extends BasePageQuery { + + /** 状态过滤:null 不过滤;1 已发布 */ + private Integer status; + + /** 类型过滤:1 普通 / 2 重要 */ + private Integer type; + + /** 列表范围(管理端):"user" 只看自己发布的,null 看全部 */ + private String scope; + + /** 发布部门ID */ + private String deptId; + + /** 发布人ID(用户中心数字ID,管理端按发布人查) */ + private Integer publisherId; + + /** 开始时间(创建时间 >=,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startTime; + + /** 结束时间(创建时间 <= 当天 23:59:59,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endTime; + + /** 阅读状态过滤(仅用户端):0 全部 / 1 未读 / 2 已读 */ + private Integer readType = 0; + + /** 排序:1 时间倒序(默认)/ 2 时间正序 */ + private String orderByTime = "1"; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java new file mode 100644 index 0000000..2cb560f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java @@ -0,0 +1,65 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import com.yunhai.homework.common.validation.ValidationGroups; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 作业发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。 + */ +@Data +public class HomeworkRequest { + + /** 作业标题 */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业标题不得为空") + private String title; + + /** 作业内容 */ + private String content; + + /** 作业类型:1 普通 / 2 重要 */ + @NotNull(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业类型不得为空") + private Integer type = 1; + + /** 发布部门ID(原 DeptID,必填——对应原系统「发布部门不得为空」校验) */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "发布部门不得为空") + private String deptId; + + /** 发布部门名称 */ + private String deptName; + + /** 是否置顶:0 否 / 1 是 */ + private Integer topmost = 0; + + /** 接收人列表(JSON 数组字符串,如 ["10012","10013"]——用户中心数字ID,或 "ALL" 表示全员) */ + private String receiveList; + + /** 附件文件ID列表(逗号分隔串,如 "1,2,3");旧名 chaoSend,已更名 fileIds(M6) */ + private String fileIds; + + /** 定时发送时间(为空表示立即发布) */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timingSendTime; + + /** 短信发送标记:0 不发短信 / 1 发短信(旧名 topmost 实指发短信,已正名 smsFlag,M6) */ + private Integer smsFlag = 0; + + /** 转发源作业ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */ + private Integer sourceId; + + // ==================== 课堂作业 V1/V2 扩展(前端契约字段,兼容层同步写新列) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deadline; + + /** 提交方式:text/image/attach/none 组合 */ + private java.util.List submitType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java new file mode 100644 index 0000000..c492f86 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java @@ -0,0 +1,113 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 作业统计视图(对应原系统 GetStatisData / 按发布人统计)。 + */ +@Data +public class HomeworkStatisticsVo { + + /** 作业总数(全部已发布) */ + private long total; + + /** 已发布数 */ + private long publishedCount; + + /** 阅读记录总数(累计已读人次) */ + private long totalReadRecords; + + /** 当前用户未读数(已到点的已发布 - 当前用户已读) */ + private long unreadForCurrentUser; + + /** 按发布人统计 */ + private List byPublisher; + + /** 按月统计(已发布作业数) */ + private List byMonth; + + // ==================== 课程作业口径(前端 /stats 页消费) ==================== + + /** 我布置的作业数(当前登录人发布的全部作业,含草稿) */ + private long myAssignCount; + + /** 我的作业待批改总量(已交未批的当前版本行合计) */ + private long pendingGrade; + + /** 各作业平均提交率(%,仅统计需收集提交的作业;无样本时为 0) */ + private double avgSubmitRate; + + /** 按作业的提交与批改进度明细(草稿与仅查阅型不计) */ + private List byHomework = new ArrayList<>(); + + /** 已批阅提交的分数分布(四档) */ + private List scoreDist = new ArrayList<>(); + + /** 单作业进度条目(口径对齐前端 HomeworkProgress 契约) */ + @Data + public static class HomeworkStat { + private Integer id; + private String title; + private String subject; + /** 旧契约四态派生值:draft / collecting / closed / done */ + private String hwStatus; + private String hwStatusDesc; + /** 应交人数 */ + private long should; + /** 已读人数 */ + private long read; + /** 已交人数 */ + private long submitted; + /** 已批阅人数 */ + private long graded; + /** 待批改人数 */ + private long pendingGrade; + /** 未交人数 */ + private long unsubmitted; + /** 提交率(%) */ + private double submitRate; + /** 批阅率(%) */ + private double gradeRate; + } + + /** 分数分布档(key 供前端配色映射:excellent/good/pass/fail) */ + @Data + public static class ScoreBucket { + private String label; + private String key; + private long count; + + public ScoreBucket(String label, String key, long count) { + this.label = label; + this.key = key; + this.count = count; + } + } + + @Data + public static class PublisherStat { + private String publisherName; + private long count; + + public PublisherStat(String publisherName, long count) { + this.publisherName = publisherName; + this.count = count; + } + } + + @Data + public static class MonthStat { + private int year; + private int month; + private long count; + + public MonthStat(int year, int month, long count) { + this.year = year; + this.month = month; + this.count = count; + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java new file mode 100644 index 0000000..c906e2e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.List; + +/** + * 用户端作业列表结果:在分页数据之外额外携带「当前用户未读总数」, + * 供前端展示未读角标(对应原系统 OtherData.unReadCount)。 + */ +@Data +public class HomeworkUserListResult { + + private int total; + private List content; + private int pageIndex; + private int pageSize; + private int totalPages; + private boolean hasNext; + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkUserListResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java new file mode 100644 index 0000000..333d49e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java @@ -0,0 +1,92 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 作业视图对象(列表/详情返回)。 + */ +@Data +public class HomeworkVo { + + private Integer id; + private String title; + private String content; + private Integer type; + private String typeDesc; + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + private String publisherName; + private String deptId; + private String deptName; + private Integer status; + private String statusDesc; + private Integer topmost; + + /** 已读人数(基于阅读记录实时统计) */ + private Long readCount = 0L; + + /** 应读人数 */ + private Integer allowCount; + + /** 当前用户是否已读(用户端列表使用;管理端恒为 false 或不关注) */ + private boolean read = false; + + /** 是否撤回:0 否 / 1 是 */ + private Integer isPulled; + + /** 定时发送时间 */ + private LocalDateTime timingSendTime; + + /** 父作业ID(转发时关联原作业) */ + private Integer parentId; + + /** 短信发送标记 */ + private Integer smsFlag; + + /** 接收人列表(JSON) */ + private String receiveList; + + /** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */ + private String UpFile; + + // ==================== 课堂作业 V1/V2 扩展(前端 types/homework.ts 契约) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间(前端契约字段名 deadline) */ + private LocalDateTime deadline; + + /** 提交方式数组:text/image/attach/none */ + private java.util.List submitType; + + /** 作业流程状态——旧契约四态派生值(draft/collecting/closed/done),权威五态见 homework.hw_status */ + private String hwStatus; + + private String hwStatusDesc; + + /** + * 提交/批改进度(管理端列表页「提交进度 / 未读未交 / 批改」列使用)。 + * 草稿或仅查阅型为 null;列表场景由 HomeworkProgressCalculator 批量聚合回填。 + */ + private com.yunhai.homework.model.v2.ProgressVo progress; + + private String createBy; + private LocalDateTime createTime; + private String updateBy; + private LocalDateTime updateTime; + + /** + * 阅读明细视图(阅读列表查询用)。 + */ + @Data + public static class ReadDetail { + /** 读者ID(用户中心数字ID) */ + private Integer id; + private String name; + private boolean read; + private LocalDateTime readTime; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java new file mode 100644 index 0000000..15ece01 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 一键已读结果视图对象(替代原 Map<String,Object> 返回)。 + */ +@Data +public class InitializeRedVo { + + @JsonProperty("count") + private Integer count; + + public InitializeRedVo() {} + + public InitializeRedVo(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java new file mode 100644 index 0000000..8127480 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java @@ -0,0 +1,30 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 文件上传结果视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class UpFileResultVo { + + @JsonProperty("fileid") + private Integer fileid; + + @JsonProperty("name") + private String name; + + @JsonProperty("fileurl") + private String fileurl; + + @JsonProperty("host") + private String host; + + @JsonProperty("type") + private String type; + + @JsonProperty("size") + private Integer size; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java new file mode 100644 index 0000000..76dfa2f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +/** + * 附件视图对象(对应原 .NET FileObj)。 + */ +@Data +public class UpFileVo { + + /** 文件记录ID */ + private Integer id; + + /** 文件真实名称 */ + private String fileTrueName; + + /** 文件存储路径(相对路径) */ + private String fileName; + + /** 文件大小(字节) */ + private Integer fileSize; + + /** 文件后缀名 */ + private String fileType; + + /** 关联主题ID */ + private Integer topicId; + + /** 应用类型 */ + private String topicType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java new file mode 100644 index 0000000..771eb3a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java @@ -0,0 +1,122 @@ +package com.yunhai.homework.model.homework; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户可访问模块(左菜单)视图对象。 + * 将基础平台返回的扁平 {@code List} 转换为前端 layout(参考 UserBase)期望的树结构: + * {@code modId}(路由高亮匹配)/{@code page}(跳转路径)/{@code children}(子菜单)。 + */ +public class UserModuleVo { + + /** 模块主键 id(基础平台 module id) */ + private Integer id; + + /** 菜单高亮匹配用:等价于后端模块 id */ + private Integer modId; + + /** 模块名称 */ + private String name; + + /** 父级模块 id(0 表示根节点) */ + private Integer parentId; + + /** 前端路由路径(由 BoaModule.path 映射而来) */ + private String page; + + /** 层级(0=根分组) */ + private Integer level; + + /** 菜单图标 */ + private String icon; + + /** 排序号 */ + private Integer orderNumber; + + /** 是否可用 */ + private Boolean usable; + + /** 子菜单 */ + private List children = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getModId() { + return modId; + } + + public void setModId(Integer modId) { + this.modId = modId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Integer getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(Integer orderNumber) { + this.orderNumber = orderNumber; + } + + public Boolean getUsable() { + return usable; + } + + public void setUsable(Boolean usable) { + this.usable = usable; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java new file mode 100644 index 0000000..bedf7b5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 附件条目(作业附件与提交附件共用)。fileUrl 必传;fileId 为 file/Upload 产物时回传。 + */ +@Data +public class AttachmentItem { + + /** up_file.id */ + private Integer fileId; + + /** 文件访问 URL */ + private String fileUrl; + + /** 文件类型:image / file */ + private String fileType; + + /** 文件名 */ + private String fileName; + + /** 排序号 */ + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassItem.java b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java new file mode 100644 index 0000000..459ea80 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java @@ -0,0 +1,18 @@ +package com.yunhai.homework.model.v2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 班级条目(作业参与班级展示)。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClassItem { + + private String classId; + + private String className; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java new file mode 100644 index 0000000..c0f400b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 年级→班级树(发布作业选班 + 应交名单基数来源)。 + * 叶子节点 id = 班级 id(class_student 快照聚合)。 + */ +@Data +public class ClassTreeNode { + + /** 年级节点为 gradeName,班级叶子为 classId */ + private String id; + + /** 展示名 */ + private String name; + + /** 是否父节点(年级) */ + private Boolean isParent; + + /** 叶子节点:在班学生数 */ + private Integer studentCount; + + private List children = new ArrayList<>(); +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java new file mode 100644 index 0000000..f8c5dec --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 延长截止请求(新截止时间必须晚于当前时间)。 + */ +@Data +public class ExtendRequest { + + @NotNull(message = "新截止时间不得为空") + private LocalDateTime deadlineAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java new file mode 100644 index 0000000..1ac46f8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java @@ -0,0 +1,36 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 作业详情汇总统计卡片(PRD §18 页 4 stats 接口出参)。 + */ +@Data +public class HomeworkStatsVo { + + private Integer homeworkId; + + private String title; + + private String hwStatus; + + private String hwStatusDesc; + + /** 总进度 */ + private ProgressVo total; + + /** 分班进度 */ + private List byClass; + + @Data + public static class ByClass { + + private String classId; + + private String className; + + private ProgressVo progress; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java new file mode 100644 index 0000000..9464910 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 创建/编辑作业请求(PRD §18 页 1 表单)。 + * 创建一律落草稿(DRAFT_UNPUBLISHED),发布/定时走独立接口。 + */ +@Data +public class HomeworkV2Request { + + /** 作业标题(必填 ≤50 字) */ + @NotBlank(message = "作业标题不得为空") + @Size(max = 50, message = "作业标题不超过50字") + private String title; + + /** 学科(字典值) */ + private String subject; + + /** 作业说明(富文本) */ + private String description; + + /** 参与班级 id 列表 */ + private List classIds; + + /** 附件列表 */ + private List attachments; + + /** 截止时间(精确到分钟;空 = 仅查阅型) */ + private LocalDateTime deadlineAt; + + /** 提交方式:image / attach / text / none 的组合(PRD 图片/文件/两者) */ + private List submitType; + + /** 截止后是否允许逾期提交:0 否 / 1 是 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天提醒(0 关 / 1 开) */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时提醒(0 关 / 1 开) */ + private Integer remind1h; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java new file mode 100644 index 0000000..8bf1b02 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java @@ -0,0 +1,73 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 作业视图对象 V2(草稿箱/已发布列表/详情共用)。 + * 列表场景 attachments/classes 可按需填充;progress 仅非草稿且非仅查阅型作业返回。 + */ +@Data +public class HomeworkV2Vo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 作业说明(富文本;列表场景可置空省流) */ + private String description; + + /** 作业状态(PRD 五态) */ + private String hwStatus; + + private String hwStatusDesc; + + /** 定时发布时间(复用 timing_send_time) */ + private LocalDateTime scheduledAt; + + /** 发布时间 */ + private LocalDateTime publishedAt; + + /** 截止时间 */ + private LocalDateTime deadlineAt; + + /** 归档时间 */ + private LocalDateTime archivedAt; + + /** 提交方式数组 */ + private List submitType; + + /** 截止后允许逾期提交:0/1 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天 */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时 */ + private Integer remind1h; + + /** 参与班级 */ + private List classes; + + /** 附件列表(详情场景填充) */ + private List attachments; + + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + + /** 发布人姓名 */ + private String publisherName; + + /** 创建/更新时间(审计) */ + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + /** 汇总进度(草稿或仅查阅型为 null) */ + private ProgressVo progress; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java new file mode 100644 index 0000000..0303eb8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知分页结果(含当前用户未读数)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationPageResult extends BasePageResult { + + /** 当前用户未读通知总数 */ + private long unreadCount; + + public NotificationPageResult(BasePageResult page, long unreadCount) { + super(page.getContent(), page.getTotal(), page.getPageIndex(), page.getPageSize(), + page.getTotalPages(), page.isHasNext()); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java new file mode 100644 index 0000000..f8af4a7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知列表查询。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationQuery extends BasePageQuery { + + /** 类型过滤(NotificationTypeEnum code) */ + private String type; + + /** 已读过滤:0 未读 / 1 已读(空 = 全部) */ + private Integer isRead; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java new file mode 100644 index 0000000..2a9b61a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知列表项。 + */ +@Data +public class NotificationVo { + + private Integer id; + + /** 通知类型 */ + private String type; + + private String typeDesc; + + private String title; + + private String content; + + private Integer homeworkId; + + private Integer relatedId; + + /** 是否已读:0/1 */ + private Integer isRead; + + private LocalDateTime readAt; + + private LocalDateTime createTime; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java new file mode 100644 index 0000000..201a460 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 提交/批阅/阅读进度(PRD §18 页 3 汇总口径)。 + * should(应交)为分母;打回未重交计入已交。 + */ +@Data +public class ProgressVo { + + /** 应交人数(参与班级在班学生去重) */ + private int should; + + /** 已读人数 */ + private int read; + + /** 已交人数(含逾期/打回/已批阅) */ + private int submitted; + + /** 已批阅人数 */ + private int graded; + + /** 待批阅人数(submitted - graded) */ + private int pendingGrade; + + /** 未交人数 */ + private int unsubmitted; + + /** 未读人数 */ + private int unread; + + /** 未读且未交人数(高亮口径:should - 已读∪已交) */ + private int unreadUndone; + + /** 阅读率(百分数,一位小数) */ + private double readRate; + + /** 提交率(百分数,一位小数) */ + private double submitRate; + + /** 批阅率(已批阅 / 已交,百分数一位小数;submitted=0 时为 0) */ + private double gradeRate; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java new file mode 100644 index 0000000..a97e0c1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 教师手动提醒请求:type = NOT_READ / NOT_SUBMITTED,studentIds 为空时提醒全部未读/未交学生。 + */ +@Data +public class RemindRequest { + + /** 提醒类型:NOT_READ / NOT_SUBMITTED */ + @NotBlank(message = "提醒类型不得为空") + private String type; + + /** 定向学生ID(用户中心数字ID;空 = 全部未读/未交) */ + private List studentIds; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java new file mode 100644 index 0000000..45d3f3e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 打回请求(PRD 页 6)。comment 为订正原因,必填。 + */ +@Data +public class ReturnRequest { + + @NotBlank(message = "打回须填写订正原因") + private String comment; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java new file mode 100644 index 0000000..166b2e1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 批阅保存请求(PRD 页 6)。score 与 comment 至少一项有值。 + */ +@Data +public class ReviewRequest { + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 圈划标注图片(P2,先收参) */ + private List markedImages; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java new file mode 100644 index 0000000..894b433 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 定时发布 / 取消定时请求。 + * cancel=true 时忽略 scheduledAt,作业回退草稿-未发布并清空定时时间。 + */ +@Data +public class ScheduleRequest { + + /** 定时发布时间(cancel=false 时必填且须晚于当前时间) */ + private LocalDateTime scheduledAt; + + /** 是否取消定时 */ + private Boolean cancel = false; + + @NotNull + public Boolean getCancel() { + return cancel; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java new file mode 100644 index 0000000..6372883 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 学生端作业详情(PRD §18 学生页 2)。进入详情后由前端停留 ≥3 秒再调 read 上报。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentHomeworkDetailVo extends StudentHomeworkVo { + + /** 作业说明(富文本) */ + private String description; + + /** 作业附件 */ + private List attachments; + + /** 参与班级(学生所属班级视角) */ + private List classes; + + /** 我当前版本的提交附件(未提交为 null) */ + private List myAttachments; + + /** 我的文本内容(当前版本) */ + private String myText; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java new file mode 100644 index 0000000..9d30037 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学生端作业列表项(PRD §18 学生页 1)。 + */ +@Data +public class StudentHomeworkVo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 任课教师(发布人姓名) */ + private String teacherName; + + /** 截止时间(前端做倒计时) */ + private LocalDateTime deadlineAt; + + /** 提交方式 */ + private java.util.List submitType; + + /** 作业状态 */ + private String hwStatus; + + private String hwStatusDesc; + + /** 截止后是否允许逾期补交:0 否 / 1 是(学生端据此决定是否渲染「补交」入口) */ + private Integer allowLate; + + /** 我的提交概要(未提交为 null) */ + private MySubmission my; + + @Data + public static class MySubmission { + + /** 当前版本提交行ID */ + private Integer submissionId; + + /** 提交状态(PRD 五态) */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 版本号 */ + private Integer version; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分(已批阅才有) */ + private java.math.BigDecimal score; + + /** 评语 */ + private String comment; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java new file mode 100644 index 0000000..49e3144 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学生矩阵查询(PRD §18 页 5)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentMatrixQuery extends BasePageQuery { + + /** 班级筛选(空 = 全部参与班级) */ + private String classId; + + /** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */ + private String submitStatus; + + /** 批阅状态筛选:PENDING/REVIEWED/RETURNED */ + private String reviewStatus; + + /** 只看待批阅(reviewStatus=PENDING 且已提交)快捷过滤 */ + private Boolean pendingGrade; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java new file mode 100644 index 0000000..6257285 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java @@ -0,0 +1,57 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学生矩阵行(PRD §18 页 5)。未交学生也生成占位行(submissionId=0)。 + */ +@Data +public class StudentMatrixVo { + + /** 当前版本提交行ID(未交占位为 0) */ + private Integer submissionId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + /** 姓名 */ + private String studentName; + + private String classId; + + private String className; + + /** 阅读状态:0 未读 / 1 已读 */ + private Integer isRead; + + /** 首次阅读时间 */ + private LocalDateTime firstReadAt; + + /** 最近阅读时间 */ + private LocalDateTime lastReadAt; + + /** 提交状态(PRD 五态;未交为 NOT_SUBMITTED) */ + private String submitStatus; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期:0/1 */ + private Integer isLate; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分 */ + private BigDecimal score; + + /** 最新提交附件 URL 列表(缩略图) */ + private List thumbnails; + + /** 提交内容类型摘要:image=拍照/图片、attach=上传附件、text=文字作答(未交为空列表) */ + private List contentTypes; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java new file mode 100644 index 0000000..f4d9cd1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 学生查看批阅结果(PRD §18 学生页 2 result 接口)。 + */ +@Data +public class StudentResultVo { + + private Integer homeworkId; + + private String homeworkTitle; + + /** 当前版本批阅结果(未批阅为 null) */ + private SubmissionVersionVo latest; + + /** 全部版本(含各版评语,新→旧) */ + private List versions; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java new file mode 100644 index 0000000..1fa7545 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 学生提交作业请求(PRD §18 学生页 3)。附件只接受 file/Upload 产物(fileId 或平台 URL),拒绝 dataURL。 + */ +@Data +public class StudentSubmitRequest { + + /** 文本内容(submitType 含 text 时有效) */ + @Size(max = 20000, message = "文本内容过长") + private String text; + + /** 附件列表(图片/文件) */ + private List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java new file mode 100644 index 0000000..6c576d9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 学科 DTO(布置过的学科 / 我的任教学科共用返回结构)。 + */ +@Data +public class SubjectDto { + + /** 学科编码(homework.subject 字典值 / bschclass SubID) */ + private String subId; + + /** 学科名称(本校学科字典解析;字典缺失时回退为编码) */ + private String subName; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java new file mode 100644 index 0000000..cac00dc --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 撤回重交动作(PUT /student/homeworks/{id}/submissions/{submissionId})。 + * withdraw = 撤回(截止前,产生 NOT_SUBMITTED 新版本行);resubmit = 打回后重交(走提交语义)。 + */ +@Data +public class SubmissionActionRequest { + + @NotBlank(message = "action 不得为空") + private String action; + + /** resubmit 时的提交内容(等同 StudentSubmitRequest) */ + private String text; + + /** resubmit 时的附件 */ + private java.util.List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java new file mode 100644 index 0000000..bff2972 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java @@ -0,0 +1,63 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 提交版本条目(历史版本/批阅弹窗/学生结果页共用)。 + */ +@Data +public class SubmissionVersionVo { + + private Integer submissionId; + + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + private String studentName; + + /** 版本号 */ + private Integer version; + + /** 是否当前版本:0/1 */ + private Integer isLatest; + + /** 提交状态 */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 文本内容 */ + private String text; + + /** 附件 */ + private List attachments; + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 批阅状态 */ + private String reviewStatus; + + private String reviewStatusDesc; + + /** 批阅人(展示姓名快照,缺失时回退ID) */ + private String reviewedBy; + + /** 批阅时间 */ + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java new file mode 100644 index 0000000..6d6991e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教师端作业列表查询(草稿箱/已发布列表共用)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TeacherHomeworkQuery extends BasePageQuery { + + /** 学科筛选 */ + private String subject; + + /** 作业状态筛选(已发布列表:PUBLISHED/CLOSED/ARCHIVED) */ + private String hwStatus; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java new file mode 100644 index 0000000..d9fd64e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 任教班级 VO(按学科筛选任教班级返回结构)。 + */ +@Data +public class TeachingClassVo { + + /** 班级业务ID(bschclass sch_class.class_id) */ + private String classId; + + /** 班级名称(学校班级列表映射;缺失时回退为班级ID) */ + private String className; +} diff --git a/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java new file mode 100644 index 0000000..0581e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ClassStudent; +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; + +/** + * 班级学生名册仓储。 + */ +public interface ClassStudentRepository extends JpaRepository { + + /** 按学生ID(用户中心数字ID)反查学生身份(学生端接口入口) */ + List findByStudentIdAndStatus(Integer studentId, Integer status); + + /** 班级在班学生 */ + List findByClassIdAndStatus(String classId, Integer status); + + /** 指定班级集合的全部在班学生(应交名单) */ + List findByClassIdInAndStatus(List classIds, Integer status); + + /** 指定学校的全部名册(同步任务全量刷新用) */ + List findBySchoolId(Integer schoolId); + + /** 年级→班级树:按学校去重班级(class_student 快照聚合),列序:classId, className, gradeName */ + @Query("select distinct c.classId, c.className, c.gradeName from ClassStudent c " + + "where c.schoolId = :schoolId and c.status = 1") + List findDistinctClasses(@Param("schoolId") Integer schoolId); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java new file mode 100644 index 0000000..4dc9ee4 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java @@ -0,0 +1,27 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroup; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ContactsGroupRepository extends JpaRepository { + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + + /** + * 按创建人ID(用户中心数字ID)查询联系组列表。 + */ + List findByCreatorId(Integer creatorId); + + /** + * 按创建人ID查询联系组列表(分页)。 + */ + Page findByCreatorId(Integer creatorId, Pageable pageable); + + /** + * 查询全部联系组(按创建时间降序)。 + */ + List findAllByOrderByCreateTimeDesc(); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java new file mode 100644 index 0000000..df5ab28 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java @@ -0,0 +1,42 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroupUser; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ContactsGroupUserRepository extends JpaRepository { + Page findByGroupId(Integer groupId, Pageable pageable); + List findByGroupId(Integer groupId); + + /** + * 按组ID批量删除成员(JPQL bulk delete,SQL 立即执行)。 + * 注意:不能用 derived delete——其 DELETE 延迟到 flush 才执行, + * 同事务内先 insert 会触发 uk_group_user 唯一约束冲突。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId = :groupId") + void deleteByGroupIdBulk(@Param("groupId") Integer groupId); + + /** + * 按多个组ID批量删除成员。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId in :groupIds") + void deleteByGroupIdInBulk(@Param("groupIds") List groupIds); + + /** + * 按多个组ID查询所有成员(用于统计每组人数、组树构建)。 + */ + List findByGroupIdIn(List groupIds); + + /** + * 按组ID和用户ID查询(判断是否已存在)。 + */ + boolean existsByGroupIdAndUserId(Integer groupId, Integer userId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java new file mode 100644 index 0000000..1e6af55 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 作业附件仓储。 + */ +public interface HomeworkAttachmentRepository extends JpaRepository { + + List findByHomeworkIdOrderBySortAscIdAsc(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java new file mode 100644 index 0000000..8f651f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkClass; +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; + +/** + * 作业-班级关联仓储。 + */ +public interface HomeworkClassRepository extends JpaRepository { + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 指定班级的全部作业ID(学生端列表倒查用) */ + @Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds") + List findHomeworkIdsByClassIds(@Param("classIds") List classIds); + + /** 批量查 N 个作业的班级关联(列表展示避免 N+1) */ + @Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java new file mode 100644 index 0000000..6c16a81 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java @@ -0,0 +1,23 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReadRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 学生阅读记录仓储。 + */ +public interface HomeworkReadRecordRepository extends JpaRepository { + + Optional findByHomeworkIdAndStudentId(Integer homeworkId, Integer studentId); + + List findByHomeworkId(Integer homeworkId); + + /** 批量查 N 个作业的已读记录(列表统计避免 N+1) */ + List findByHomeworkIdIn(Collection homeworkIds); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java new file mode 100644 index 0000000..20dc83c --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkRead; +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.Optional; + +/** + * 作业阅读记录仓储(懒标记模型)。 + */ +public interface HomeworkReadRepository extends JpaRepository { + + /** + * 某用户是否已读某作业。 + */ + boolean existsByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 某作业的总已读人数。 + */ + long countByHomeworkId(Integer homeworkId); + + /** + * 删除某作业的全部阅读记录(作业删除时级联清理)。 + */ + void deleteByHomeworkId(Integer homeworkId); + + /** + * 某用户已读的作业总数(用于「我的未读」= 已发布总数 - 已读总数)。 + */ + long countByReaderId(Integer readerId); + + /** + * 查询某用户已读的作业ID列表(用于列表页批量判定未读)。 + */ + @Query("select r.homeworkId from HomeworkRead r where r.readerId = :readerId") + List findHomeworkIdsByReaderId(@Param("readerId") Integer readerId); + + /** + * 查询某用户对某作业的阅读记录(标记已读时幂等判断)。 + */ + Optional findByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 查询某作业的全部阅读记录(按阅读时间倒序),用于阅读明细列表。 + */ + @Query("select r from HomeworkRead r where r.homeworkId = :homeworkId order by r.readTime desc") + List findByHomeworkIdOrderByReadTimeDesc(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java new file mode 100644 index 0000000..402d455 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReceiver; +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; + +/** + * 作业接收人关联表仓储。 + */ +public interface HomeworkReceiverRepository extends JpaRepository { + + /** 按作业ID查询全部接收人 */ + List findByHomeworkId(Integer homeworkId); + + /** 按作业ID删除全部接收人 */ + void deleteByHomeworkId(Integer homeworkId); + + /** 按作业ID和接收类型查询接收人 */ + List findByHomeworkIdAndReceiveType(Integer homeworkId, Integer receiveType); + + /** + * 批量查 N 个作业的接收人,用于列表展示(避免 N+1)。 + * @param homeworkIds 作业ID列表 + * @return 接收人关联记录列表 + */ + @Query("select r from HomeworkReceiver r where r.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java new file mode 100644 index 0000000..bf862b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java @@ -0,0 +1,139 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Homework; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 课程作业仓储。 + */ +public interface HomeworkRepository extends JpaRepository, + JpaSpecificationExecutor { + + /** + * 作业流程状态条件更新(CAS):仅当当前状态为 from 时更新为 to。 + * 状态机单点收敛用,天然幂等、并发安全(定时任务与接口共用)。 + * clearAutomatically:批量更新绕过一级缓存,必须清空以避免后续 findById 读到旧实体 + * 再被 save() 写回旧状态(同事务内必现,集成测试发现)。 + * + * @return 影响行数(0 = 状态已变化,调用方应放弃本次迁移) + */ + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update Homework h set h.hwStatus = :to where h.id = :id and h.hwStatus = :from") + int casHwStatus(@Param("id") Integer id, + @Param("from") String from, + @Param("to") String to); + + /** + * 定时发布任务扫描:定时待发布且已到点。 + */ + List findByHwStatusAndTimingSendTimeLessThanEqual(String hwStatus, LocalDateTime now); + + /** + * 到点关闭任务扫描:进行中且已过截止时间。 + */ + List findByHwStatusAndDeadlineAtBefore(String hwStatus, LocalDateTime now); + + /** + * 截止提醒任务扫描:进行中且截止时间落在指定窗口内(remind_1d/remind_1h 开关在服务侧逐单过滤)。 + */ + List findByHwStatusAndDeadlineAtBetween(String hwStatus, LocalDateTime start, LocalDateTime end); + + /** + * 按作业流程状态查询(兼容层/统计用)。 + */ + List findByHwStatus(String hwStatus); + + /** + * 当前登录人发布的全部作业(课程作业口径统计:我布置的作业 / 提交与批改进度明细)。 + */ + List findByPublisherId(Integer publisherId); + + /** + * 当前登录人布置过(已发布及之后状态)作业的学科去重清单(学科筛选用,字典序;草稿不计)。 + */ + @Query("select distinct h.subject from Homework h " + + "where h.publisherId = :publisherId and h.subject is not null and h.subject <> '' " + + "and h.hwStatus in :statuses order by h.subject") + List findAssignedSubjects(@Param("publisherId") Integer publisherId, + @Param("statuses") Collection statuses); + + /** + * 按状态计数(用于统计已发布总数)。 + */ + long countByStatus(Integer status); + + /** + * 已发布且已到点(定时发送未到点不计入)的作业数,用于用户端未读数统计。 + */ + @Query("select count(n) from Homework n where n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDue(@Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定状态、指定时间范围内的作业创建时间(用于按月统计,避免加载大字段)。 + */ + @Query("select n.createTime from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定状态、指定时间范围内的发布人姓名(用于按发布人统计)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 按学校和状态计数(多租户隔离统计)。 + */ + long countBySchIdAndStatus(Integer schId, Integer status); + + /** + * 按学校计数(多租户隔离统计:全部状态)。 + */ + long countBySchId(Integer schId); + + /** + * 已发布且已到点(定时发送未到点不计入)且属于指定学校的作业数(用户端未读数统计,多租户隔离)。 + */ + @Query("select count(n) from Homework n where n.schId = :schId and n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDueBySchId(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定学校、指定状态、指定时间范围内的作业创建时间(按月统计,多租户隔离)。 + */ + @Query("select n.createTime from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesBySchIdStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定学校、指定状态、指定时间范围内的发布人姓名(按发布人统计,多租户隔离)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesBySchStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java new file mode 100644 index 0000000..547c5ea --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkSubmission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 作业提交仓储(多版本)。统计一律基于 is_latest=1 的当前版本行。 + */ +public interface HomeworkSubmissionRepository extends JpaRepository { + + /** 学生在指定作业的当前版本行 */ + Optional findByHomeworkIdAndStudentIdAndIsLatest(Integer homeworkId, + Integer studentId, + Integer isLatest); + + /** 学生在指定作业的全部版本(新→旧) */ + List findByHomeworkIdAndStudentIdOrderByVersionDesc(Integer homeworkId, Integer studentId); + + /** 指定作业的当前版本行(统计/矩阵用) */ + List findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest); + + /** 批量查 N 个作业的当前版本行(列表统计避免 N+1) */ + List findByHomeworkIdInAndIsLatest(Collection homeworkIds, Integer isLatest); + + /** 学生矩阵分页 */ + Page findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest, Pageable pageable); + + /** 学生当前版本最大版本号 */ + @Query("select max(s.version) from HomeworkSubmission s " + + "where s.homeworkId = :homeworkId and s.studentId = :studentId") + Integer findMaxVersion(@Param("homeworkId") Integer homeworkId, @Param("studentId") Integer studentId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 附件等子表清理后按作业删除历史版本行 */ + @Modifying + @Query("delete from HomeworkSubmission s where s.homeworkId = :homeworkId") + int deleteAllByHomeworkId(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java new file mode 100644 index 0000000..ecc75cb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HomeworkTemplateRepository extends JpaRepository { + Page findByTypeId(Integer typeId, Pageable pageable); + + /** + * 查询全部模板(按引用次数降序),分页。 + */ + Page findAllByOrderByRefCountDesc(Pageable pageable); + + /** + * 按类型ID查询全部模板(不分页,用于检查类型是否被引用)。 + */ + long countByTypeId(Integer typeId); +} diff --git a/src/main/java/com/yunhai/homework/repository/NotificationRepository.java b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java new file mode 100644 index 0000000..1066c10 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + * 站内通知仓储。 + */ +public interface NotificationRepository extends JpaRepository { + + Page findByReceiverId(Integer receiverId, Pageable pageable); + + Page findByReceiverIdAndType(Integer receiverId, String type, Pageable pageable); + + Page findByReceiverIdAndIsRead(Integer receiverId, Integer isRead, Pageable pageable); + + Page findByReceiverIdAndTypeAndIsRead(Integer receiverId, String type, + Integer isRead, Pageable pageable); + + long countByReceiverIdAndIsRead(Integer receiverId, Integer isRead); + + Optional findByIdAndReceiverId(Integer id, Integer receiverId); + + /** 截止提醒去重:同作业同类型同档位已发过即跳过(related_id 存档位:1=1天档 2=1小时档) */ + boolean existsByHomeworkIdAndTypeAndRelatedId(Integer homeworkId, String type, Integer relatedId); + + /** 截止提醒去重(不分档位) */ + boolean existsByHomeworkIdAndType(Integer homeworkId, String type); + + /** 全部已读(clearAutomatically 防同事务内缓存脏读) */ + @Modifying(clearAutomatically = true) + @Query("update Notification n set n.isRead = 1, n.readAt = :now " + + "where n.receiverId = :receiverId and n.isRead = 0") + int markAllRead(@Param("receiverId") Integer receiverId, @Param("now") java.time.LocalDateTime now); + + /** 按作业删除通知(作业删除级联) */ + void deleteByHomeworkId(Integer homeworkId); + + /** 指定作业的全部通知(清理/统计用) */ + List findByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java new file mode 100644 index 0000000..8e60f47 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SchClassTeach; +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; + +/** + * 班级任教关系快照仓储。 + */ +public interface SchClassTeachRepository extends JpaRepository { + + /** 指定学校指定教师的全部有效任教记录 */ + List findBySchoolIdAndTeaIdAndStatus(Integer schoolId, Integer teaId, Integer status); + + /** 指定学校指定教师去重学科编码(字典序由调用方保证) */ + @Query("select distinct t.subId from SchClassTeach t " + + "where t.schoolId = :schoolId and t.teaId = :teaId and t.status = 1") + List findDistinctSubIds(@Param("schoolId") Integer schoolId, @Param("teaId") Integer teaId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java new file mode 100644 index 0000000..18595bb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java @@ -0,0 +1,13 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsModule; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsModuleRepository extends JpaRepository { + List findAllByOrderByIdAsc(); + + /** 按模块标识查询(如 module_code="homework")。 */ + SmsModule findByModuleCode(String moduleCode); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java new file mode 100644 index 0000000..0d277a0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsSchoolConfig; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsSchoolConfigRepository extends JpaRepository { + Page findBySchoolNameContaining(String schoolName, Pageable pageable); + Page findAll(Pageable pageable); + List findBySchoolId(Integer schoolId); + List findBySchoolIdInAndModuleIdIn(List schoolIds, List moduleIds); + + /** + * 按学校ID和模块ID查询单条配置(判断是否已存在)。 + */ + SmsSchoolConfig findBySchoolIdAndModuleId(Integer schoolId, Integer moduleId); + + /** + * 按学校ID查询全部模块配置(用于"设置某校全部模块")。 + */ + List findBySchoolIdIn(List schoolIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java new file mode 100644 index 0000000..8848b14 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SubmissionAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; + +/** + * 提交附件仓储。 + */ +public interface SubmissionAttachmentRepository extends JpaRepository { + + List findBySubmissionIdOrderBySortAscIdAsc(Integer submissionId); + + /** 批量查 N 个提交行的附件(矩阵/版本列表避免 N+1) */ + List findBySubmissionIdIn(Collection submissionIds); + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SysDictRepository.java b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java new file mode 100644 index 0000000..c083840 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SysDict; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SysDictRepository extends JpaRepository { + List findByDictTypeOrderBySortOrderAsc(String dictType); + List findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag); + + /** 按类型+key 取单条字典(发文部门名称解析用) */ + Optional findByDictTypeAndDictKey(String dictType, String dictKey); +} diff --git a/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java new file mode 100644 index 0000000..a3a0f12 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.TemplateType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TemplateTypeRepository extends JpaRepository { + List findAllByOrderBySortOrderAsc(); + + /** + * 按类型名称查询(重名校验)。 + */ + boolean existsByTypeName(String typeName); + + /** + * 按类型名称查询(重名校验,排除自身)。 + */ + boolean existsByTypeNameAndIdNot(String typeName, Integer id); +} diff --git a/src/main/java/com/yunhai/homework/repository/UpFileRepository.java b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java new file mode 100644 index 0000000..421f142 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.UpFile; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +/** + * 附件文件仓储。 + */ +public interface UpFileRepository extends JpaRepository { + + /** + * 按主题类型和主题ID查询附件列表。 + */ + List findByTopicTypeAndTopicId(String topicType, Integer topicId); + + /** + * 按主题类型和多个主题ID批量查询附件(消除 N+1)。 + */ + List findByTopicTypeAndTopicIdIn(String topicType, List topicIds); + + /** + * 按主题类型和主题ID删除附件(作业删除时级联清理)。 + */ + void deleteByTopicTypeAndTopicId(String topicType, Integer topicId); +} diff --git a/src/main/java/com/yunhai/homework/service/IClassService.java b/src/main/java/com/yunhai/homework/service/IClassService.java new file mode 100644 index 0000000..2346cd9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IClassService.java @@ -0,0 +1,116 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; + +import java.util.List; + +/** + * 班级管理(bschclass)开放接口客户端门面。 + * 基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。 + * 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。 + */ +public interface IClassService { + + /** + * 获取指定学校全部教师(含所属组 GroupIds)。 + * 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}} + * + * @param schId 学校ID(即用户当前身份的 schId) + */ + List getSchTeaList(int schId); + + /** + * 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。 + * 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}} + */ + SchInfoDto getSchFirst(int schId); + + /** + * 学校学段(学制)列表:小学部 / 初中部…。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=} + */ + List getSchLenList(String schSerId); + + /** + * 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=} + */ + List getSchGradeList(String schSerId); + + /** + * 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。 + * 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=} + */ + List getSchClassList(String schSerId); + + /** + * 按校全部学生(圈人:全校范围展开)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=} + */ + List getSchStuList(String schSerId); + + /** + * 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=} + */ + List getGradeStuList(String schSerId, Integer gradeId); + + /** + * 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=} + */ + List getSubStuList(String schSerId, Integer schSystemId); + + /** + * 按班级批量查学生(圈人:班级范围一次多班)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3} + */ + List getClaArrStuList(List classIds); + + /** + * 按学生ID批量查(圈人:指定学员范围)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3} + */ + List getStuIdsList(List stuIds); + + /** + * 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。 + * 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。 + * 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=} + */ + List getTeaTree(Integer schId, boolean toUser); + + List getStuTree(Integer schId,boolean toUser); + + /** + * 学校学科列表(含学科编码、名称、排序号)。 + * 对应:GET {@code {bschclass}/oapi/SubInfo/GetSchSublist?SchID=} + */ + List getSchSubList(String schSerId); + + /** + * 指定教师的全部任教(校验教师归属该校;SchID 过滤学校、UserTeaSerID 过滤教师)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=&UserTeaSerID=} + */ + List getSchTeachList(String schSerId, Integer teaId); + + /** + * 指定班级的全部任教(班级维度:该班所有教师与学科)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchClaTeachlist?ClassID=} + */ + List getSchClaTeachList(Integer classId); + + /** + * 学校全部任教(全校所有教师的班级任教关系)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=} + */ + List getSchAllTeachList(String schSerId); +} diff --git a/src/main/java/com/yunhai/homework/service/IContactsService.java b/src/main/java/com/yunhai/homework/service/IContactsService.java new file mode 100644 index 0000000..0e1fdce --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IContactsService.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 联系组业务接口。 + * 对应前端 Contacts 组接口:AddGroup/GetGroup/EditGroup/DelGroup/SetContactsTea/GetContactsTea/GetGroupTree/GetDeptRoleGroupTree/Import/Export。 + */ +public interface IContactsService { + + /** + * 添加联系组。 + * + * @param name 组名 + * @param type 类型(1 自定义组, 2 明信片) + */ + void addGroup(String name, Integer type); + + /** + * 获取联系组列表(含每组人数,分页)。 + * + * @param page 页码(从1起) + * @param count 每页条数 + * @param type 类型(1 自定义组, 2 明信片) + * @return Map: { list, total } + */ + Map getGroup(Integer page, Integer count, Integer type); + + /** + * 编辑联系组。 + * + * @param id 组ID + * @param name 组名 + * @param type 类型 + */ + void editGroup(Integer id, String name, Integer type); + + /** + * 删除联系组(支持批量,同时删除组内成员)。 + * + * @param ids 逗号分隔的组ID + */ + void delGroup(String ids); + + /** + * 设置联系组教师(全量替换:先删后增)。 + * + * @param gid 组ID + * @param ids 逗号分隔的用户ID + */ + void setContactsTea(Integer gid, String ids); + + /** + * 获取联系组教师列表(分页)。 + * + * @param gid 组ID + * @param page 页码 + * @param count 每页条数 + * @return Map: { list, total } + */ + Map getContactsTea(String gid, Integer page, Integer count); + + /** + * 获取联系组树(组节点+用户节点,递归构建)。 + * + * @param type 类型(1 自定义组, 2 明信片) + * @return 树形结构 List<Map> + */ + List> getGroupTree(Integer type); + + /** + * 获取部门角色组树。 + * C# 从外部API获取部门树+用户角色映射。Java版预留。 + * + * @return 树形结构 List<Map> + */ + List> getDeptRoleGroupTree(); +} diff --git a/src/main/java/com/yunhai/homework/service/IDictService.java b/src/main/java/com/yunhai/homework/service/IDictService.java new file mode 100644 index 0000000..31ef515 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IDictService.java @@ -0,0 +1,75 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 系统字典业务接口。 + * 对应前端 Base 组接口:GetSysDic/GetDictMangeList/AddDeptDic/UpdateDeptDic/DelDept/OrdbyDicIds/GetDeptSync/GetSchModuleIsSms。 + */ +public interface IDictService { + + /** + * 获取系统字典(下拉框用)。 + * C# GetSysDic: Type=1 部门类别, 其余类型略。 + * + * @param type 字典类型编号(1=部门类别) + * @return List<Map> 每项 {k, v} + */ + List> getSysDic(Integer type); + + /** + * 获取系统字典管理列表(含 SerID/CodeID/Name/Des/Enable)。 + * + * @param type 字典类型编号(0=部门类别) + * @return List<Map> + */ + List> getDictMangeList(String type); + + /** + * 添加发文单位。 + * + * @param name 单位名称 + */ + void addDeptDic(String name); + + /** + * 更新发文单位。 + * + * @param serID 记录ID + * @param name 名称 + * @param enable 启用状态 + */ + void updateDeptDic(Integer serID, String name, Integer enable); + + /** + * 删除发文单位(校验是否被作业引用)。 + * + * @param serID 记录ID + */ + void delDept(Integer serID); + + /** + * 发文单位排序。 + * + * @param ids 逗号分隔的 SerID 列表(按前端顺序) + */ + void ordbyDicIds(String ids); + + /** + * 本校是否开通短信模块(前端 dict/Sync,用于"本校是否开通短信"判断)。 + * + * @param schId 当前学校ID(取当前登录用户角色所属学校) + * @return 开通返回 true,未开通/未配置返回 false + */ + Boolean getSchModuleIsSms(Integer schId); + + /** + * 按发文部门字典 key(CodeID)解析部门名称;未命中返回 null。 + * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 + * + * @param deptId 作业 deptId(存的是 sys_dict.dict_key) + * @return 部门名称;无匹配时返回 null + */ + String resolveDeptName(String deptId); +} diff --git a/src/main/java/com/yunhai/homework/service/IFileService.java b/src/main/java/com/yunhai/homework/service/IFileService.java new file mode 100644 index 0000000..1b01bbf --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IFileService.java @@ -0,0 +1,94 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.UpFile; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.model.homework.UpFileResultVo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件上传/下载/删除业务接口。 + */ +public interface IFileService { + /** + * 上传单个文件。 + * + * @param file 上传的文件 + * @param appType 应用类型(如 "homework") + * @param topicId 关联主题ID(可为空,作业保存后再绑定) + * @return 文件视图对象 + */ + UpFileVo upload(MultipartFile file, String appType, Integer topicId); + + /** + * 将已上传但未绑定主题的附件绑定到指定作业。 + * + * @param fileIds 文件记录ID列表 + * @param topicId 作业ID + */ + void bindToTopic(List fileIds, Integer topicId); + + /** + * 查询某主题下的附件列表。 + * + * @param appType 应用类型 + * @param topicId 主题ID + * @return 附件列表 + */ + List listByTopic(String appType, Integer topicId); + + /** + * 下载文件。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void download(Integer fileId, HttpServletResponse response); + + /** + * 在线预览文件(Content-Disposition: inline,按扩展名输出真实 Content-Type, + * 供前端 open-file-viewer 取 Blob / video 播放)。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void preview(Integer fileId, HttpServletResponse response); + + /** + * 删除文件(同时删除物理文件和数据库记录)。 + * + * @param fileId 文件记录ID + */ + void delete(Integer fileId); + + /** + * 打包下载多个附件为 ZIP。 + * + * @param files 附件记录列表(调用方按 fileIds 查好) + * @param zipName 压缩包显示名(自动追加 .zip) + * @param response HTTP 响应 + */ + void downloadZip(List files, String zipName, HttpServletResponse response); + + /** + * 按主题类型和主题ID打包下载附件为 ZIP(Controller 便捷方法,内部自行查附件列表)。 + * + * @param topicType 主题类型(如 "homework") + * @param topicId 主题ID + * @param zipName 压缩包显示名 + * @param response HTTP 响应 + */ + void downloadZipByTopic(String topicType, Integer topicId, String zipName, HttpServletResponse response); + + /** + * 批量上传图片(循环上传,返回每个文件的视图对象列表)。 + */ + List uploadImages(MultipartFile[] files, String appType); + + /** + * 上传单个文件并返回前端兼容的结果 VO(含 fileid/name/fileurl/host/type/size)。 + */ + UpFileResultVo uploadToResult(MultipartFile file, String appType); +} diff --git a/src/main/java/com/yunhai/homework/service/IHomeworkService.java b/src/main/java/com/yunhai/homework/service/IHomeworkService.java new file mode 100644 index 0000000..ed50167 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IHomeworkService.java @@ -0,0 +1,142 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业业务接口。 + */ +public interface IHomeworkService { + + /** + * 发作业:以当前登录用户为发布人,状态置为「已发布」入库。 + * 若 request.timingSendTime 不为空且晚于当前时间,则作为定时发送, + * 未到点前用户端收件箱不可见(到点后由查询过滤自然可见)。 + * + * @return 新建作业 ID(用于后续回填附件 TopicID 等) + */ + Integer publish(HomeworkRequest request); + + /** + * 编辑作业:按 id 更新标题/内容/类型/部门/置顶,发布人与状态保持不变。 + */ + void edit(Integer id, HomeworkRequest request); + + /** + * 批量删除作业:删除主记录并级联清理阅读记录、接收人、附件关联。 + */ + void deleteBatch(List ids); + + /** + * 撤回作业:仅已发布且未撤回的作业可撤回,置 isPulled=1。 + */ + void pull(Integer id); + + /** + * 置顶/取消置顶。 + */ + void setTopmost(Integer id, Integer topmost); + + /** + * 转发作业:基于源作业创建新作业(已发布),parentId 关联源作业。 + */ + Integer forward(Integer sourceId, HomeworkRequest request); + + /** + * 用户端列表:仅展示已发布且已到点的作业,并标记当前用户是否已读,附未读总数。 + * 返回 HomeworkPageResult(扩展 unreadCount)。 + */ + HomeworkPageResult userList(HomeworkQuery query, Pageable pageable); + + /** + * 管理端列表:按条件查询全部作业(管理员查看全部)。 + */ + BasePageResult adminList(HomeworkQuery query, Pageable pageable); + + /** + * 作业详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。 + */ + HomeworkVo detail(Integer id); + + /** + * 编辑回显详情(不标记已读)。 + */ + HomeworkVo getDetail(Integer id); + + /** + * 标记已读:为当前用户写入(幂等)该作业的阅读记录。 + */ + void markRead(Integer id); + + /** + * 一键全部已读(批量化):当前用户所有未读的已发布作业批量插入阅读记录。 + * @return 已读条数 + */ + int initializeRed(); + + /** + * 追加接收人(事务化):并入接收人列表并重算 allowcount。 + */ + void receiveUsers(Integer homeworkId, String users); + + /** + * 上一篇/下一篇作业ID(SQL 相邻查询)。 + * @param serid 当前作业ID + * @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列) + * @return {prevId, nextId} + */ + Map getNextPrev(Integer serid, String type); + + /** + * 获取某作业的阅读明细列表。 + */ + List getReadDetail(Integer homeworkId); + + /** + * 阅读情况分组(已读/未读)。 + */ + List> getReadDetailGrouped(Integer serid); + + /** + * 催收未读:枚举未读接收人,后续接入消息推送。 + */ + void expedite(Integer serid); + + /** + * 作业统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。 + */ + HomeworkStatisticsVo statistics(HomeworkQuery query, LocalDateTime start, LocalDateTime end); + + /** + * 作业详情视图(含附件、接收人信息),供 Controller 返回。 + */ + HomeworkDetailVo getDetailMap(Integer serid); + + /** + * 已发列表附件批量查询(消除 N+1)。 + */ + Map> batchFileIds(List homeworkIds); + + /** + * 查看作业详情视图(含附件、接收人)。 + * type=1 时先标记已读再返回,type=0 时直接返回不标记。 + */ + HomeworkDetailVo detailMap(Integer serid, Integer type); + + /** + * 一键全部已读并返回结果(count = 已读条数)。 + */ + InitializeRedVo initializeRedResult(); + + /** + * 批量下载作业全部附件(打包 zip):查作业标题后调文件域打包下载。 + */ + void downloadAllFiles(Integer dataid, HttpServletResponse response); +} diff --git a/src/main/java/com/yunhai/homework/service/ISmsService.java b/src/main/java/com/yunhai/homework/service/ISmsService.java new file mode 100644 index 0000000..09b9b5f --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ISmsService.java @@ -0,0 +1,77 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置业务接口。 + * 对应前端 setSms 组接口:getmodule/getgroup/getschsms/getsch/setschsms/setschallsms/setschmodulesms。 + */ +public interface ISmsService { + + /** + * 获取短信模块列表。 + * C# 从 AuthConfig.AppKeyMap 读取。Java版从 sms_module 表读取。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getModuleList(); + + /** + * 获取学校父类组列表。 + * C# 从外部BLL获取学校组。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchParentGroup(); + + /** + * 分页获取学校短信开关列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param schName 学校名称(模糊搜索) + * @return Map: { list, total } + */ + Map getSchIsSmsList(Integer pageSize, Integer pageIndex, String schName); + + /** + * 获取学校列表。 + * C# 从外部BLL获取学校列表。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchList(); + + /** + * 设置单校单模块短信开关。 + * + * @param schSerID 学校ID + * @param moduleId 模块ID + * @param isSms 是否开通 + */ + void setSchSms(Integer schSerID, Integer moduleId, Boolean isSms); + + /** + * 设置某校全部模块短信开关。 + * + * @param schSerID 学校ID + * @param isSms 是否开通 + */ + void setSchSmsAll(Integer schSerID, Boolean isSms); + + /** + * 批量设置多校多模块短信开关。 + * + * @param schSerIDs 逗号分隔的学校ID + * @param modules 逗号分隔的模块ID + * @param isSms 是否开通 + */ + void setSchModuleSms(String schSerIDs, String modules, Boolean isSms); + + /** + * 从 JSON 字符串解析并设置单校单模块短信开关。 + * 解析 SchSerID/ModuID/IsSms 三个字段。 + */ + void setSchSmsFromJson(String itemsModuJson); +} diff --git a/src/main/java/com/yunhai/homework/service/ITemplateService.java b/src/main/java/com/yunhai/homework/service/ITemplateService.java new file mode 100644 index 0000000..5594401 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ITemplateService.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板业务接口。 + * 对应前端 Temp 组接口:GetTypelist/EditType/DelType/GetTemplatelist/EditTemplate/DelTemplate/GetTemplateFist。 + */ +public interface ITemplateService { + + /** + * 获取模板类型列表(首项固定为"全部")。 + * + * @return List<Map> 每项 {TypeID, TypeName} + */ + List> getTypelist(); + + /** + * 新增/编辑模板类型。 + * + * @param typeid 类型ID(0=新增) + * @param typeName 类型名称 + */ + void editType(Integer typeid, String typeName); + + /** + * 删除模板类型(校验是否有模板引用此类型)。 + * + * @param typeid 类型ID + */ + void delType(Integer typeid); + + /** + * 分页获取模板列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param typeid 类型ID(0=全部) + * @return Map: { list, total } + */ + Map getTemplatelist(Integer pageSize, Integer pageIndex, Integer typeid); + + /** + * 新增/编辑模板。 + * + * @param serID 模板ID(null=新增) + * @param title 标题 + * @param content 内容 + * @param typeId 类型ID + * @param cover 封面文件ID + */ + void editTemplate(Integer serID, String title, String content, Integer typeId, Integer cover); + + /** + * 删除模板。 + * + * @param serID 模板ID + */ + void delTemplate(Integer serID); + + /** + * 获取单个模板详情。 + * + * @param serID 模板ID + * @return Map 模板详情 + */ + Map getTemplateFist(Integer serID); +} diff --git a/src/main/java/com/yunhai/homework/service/IUserCenterService.java b/src/main/java/com/yunhai/homework/service/IUserCenterService.java new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IUserCenterService.java @@ -0,0 +1,51 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 用户中心门面服务(对齐 UserBase {@code UserCenterService} 模式)。 + * 封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。 + */ +public interface IUserCenterService { + + /** + * 获取当前用户学校组及用户(树形结构)。 + * 组装规则(从原 BaseApiController.getSchoolAll 下沉): + * + * userbase 读取当前学校整棵组树(getGroupById(schId, withSub=true)),剔除学生组(type=8)及其子组; + * bschclass 获取该校全部教师(/oapi/DbClass/GetSchteaList?Group=schId),按 GroupIds 挂到对应组下, + * 无有效组(如所属组全被剔除)的教师兜底挂学校根节点; + * 扁平节点按 pid 递归组装为树(空组剔除,同 C# FormatUserTree)。 + * + * + * @param schId 学校ID(用户当前角色所属学校) + * @return 用户树形结构(组节点 type=1 / 教师节点 type=2) + */ + List> getSchoolAllTree(Integer schId); + + /** + * 获取指定组内用户(教师 + 管理员)。 + * 对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。 + * + * @param gid 组ID + * @param role 角色(默认 2=教师;1=管理员等) + * @return 组内用户列表 + */ + List> getGroupUser(Integer gid, Integer role); + + /** + * 获取当前用户可访问的模块树(左侧菜单)。 + * 从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。 + * + * @param userId 用户ID + * @param schId 学校ID(可为 null) + * @return 模块树(顶层 List,含 children 嵌套) + */ + List> getUserModuleTree(Integer userId, Integer schId); +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/service/impl/ClassService.java b/src/main/java/com/yunhai/homework/service/impl/ClassService.java new file mode 100644 index 0000000..8be5e2a --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/impl/ClassService.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.service.impl; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.service.IClassService; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班级管理(bschclass)开放接口实现。 + * bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。 + * 推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List
+ * 与 notice 语义的旧接口(/{platform}/homework/*)并存:旧接口冻结维护, + * 新业务一律走本控制器;返回裸对象由 ApiResponseAdvice 包装 {code,msg,data}。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/teacher/homeworks") +public class TeacherHomeworkV2Controller { + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + // ============================ 页 1:发布作业 ============================ + + /** 创建作业(落草稿) */ + @PostMapping + public Integer create(@RequestBody @Validated HomeworkV2Request request) { + return teacherHomeworkV2Service.create(request); + } + + /** 编辑作业(仅草稿) */ + @PutMapping("/{id}") + public void edit(@PathVariable("id") Integer id, + @RequestBody @Validated HomeworkV2Request request) { + teacherHomeworkV2Service.edit(id, request); + } + + /** 删除作业(仅草稿) */ + @DeleteMapping("/{id}") + public void delete(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.delete(id); + } + + /** 立即发布(草稿 → 进行中) */ + @PostMapping("/{id}/publish") + public void publish(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.publish(id); + } + + /** 定时发布 / 取消定时(body: {scheduledAt} 或 {cancel:true}) */ + @PostMapping("/{id}/schedule") + public void schedule(@PathVariable("id") Integer id, @RequestBody ScheduleRequest request) { + teacherHomeworkV2Service.schedule(id, request); + } + + // ============================ 页 2:草稿箱 ============================ + + /** 草稿箱分页 */ + @GetMapping("/drafts") + public BasePageResult drafts(TeacherHomeworkQuery query, + @SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort) { + return teacherHomeworkV2Service.drafts(query, sort); + } + + // ============================ 页 3:已发布列表 ============================ + + /** 已发布列表分页(含应交/已读/已交/未读未交聚合) */ + @GetMapping("/published") + public BasePageResult published(TeacherHomeworkQuery query, + @SortDefault(sort = "publishedAt", direction = Sort.Direction.DESC) Sort sort) { + return teacherHomeworkV2Service.published(query, sort); + } + + /** 提醒未读/未交学生,返回实际提醒人数 */ + @PostMapping("/{id}/remind") + public Map remind(@PathVariable("id") Integer id, @RequestBody RemindRequest request) { + int reminded = teacherHomeworkV2Service.remind(id, request); + return java.util.Collections.singletonMap("reminded", reminded); + } + + /** 延长截止时间 */ + @PostMapping("/{id}/extend") + public void extend(@PathVariable("id") Integer id, @RequestBody @Validated ExtendRequest request) { + teacherHomeworkV2Service.extend(id, request); + } + + /** 复制为新草稿,返回新作业 id */ + @PostMapping("/{id}/copy") + public Integer copy(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.copy(id); + } + + /** 归档(只读,不再触发任何提醒) */ + @PostMapping("/{id}/archive") + public void archive(@PathVariable("id") Integer id) { + teacherHomeworkV2Service.archive(id); + } + + /** 导出学生矩阵 Excel */ + @GetMapping("/{id}/export") + public void export(@PathVariable("id") Integer id, HttpServletResponse response) throws IOException { + teacherHomeworkV2Service.export(id, response); + } + + // ============================ 页 4:详情 / 统计 ============================ + + /** 作业详情 */ + @GetMapping("/{id}") + public HomeworkV2Vo detail(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.detail(id); + } + + /** 汇总统计卡片(总进度 + 分班进度) */ + @GetMapping("/{id}/stats") + public HomeworkStatsVo stats(@PathVariable("id") Integer id) { + return teacherHomeworkV2Service.stats(id); + } + + // ============================ 页 5:学生矩阵 ============================ + + /** 学生矩阵分页(阅读/提交/批阅状态,未交学生生成占位行) */ + @GetMapping("/{id}/students") + public BasePageResult students(@PathVariable("id") Integer id, + StudentMatrixQuery query) { + return teacherHomeworkV2Service.students(id, query); + } + + // ============================ 页 6:批阅 / 打回 / 版本 ============================ + + /** 保存批阅(→ REVIEWED,推送批阅完成通知) */ + @PostMapping("/{id}/submissions/{studentId}/review") + public void review(@PathVariable("id") Integer id, @PathVariable("studentId") Integer studentId, + @RequestBody @Validated ReviewRequest request) { + teacherHomeworkV2Service.review(id, studentId, request); + } + + /** 打回(→ RETURNED,推送打回通知) */ + @PostMapping("/{id}/submissions/{studentId}/return") + public void returnSubmission(@PathVariable("id") Integer id, @PathVariable("studentId") Integer studentId, + @RequestBody @Validated ReturnRequest request) { + teacherHomeworkV2Service.returnSubmission(id, studentId, request); + } + + /** 历史提交版本(批阅弹窗切换版本) */ + @GetMapping("/{id}/submissions/{studentId}/versions") + public List versions(@PathVariable("id") Integer id, + @PathVariable("studentId") Integer studentId) { + return teacherHomeworkV2Service.versions(id, studentId); + } + + // ============================ 辅助:学科 ============================ + + /** 我布置过的学科(本人已发布及之后作业的学科 ∪ 我的任教学科,合并去重;草稿不计) */ + @GetMapping("/subjects/assigned") + public List assignedSubjects() { + return teacherHomeworkV2Service.assignedSubjects(); + } + + /** 我的任教学科(bschclass 任教关系去重,名称取本校学科字典) */ + @GetMapping("/subjects/teaching") + public List teachingSubjects() { + return teacherHomeworkV2Service.teachingSubjects(); + } + + /** 按学科筛选我的任教班级(subId=学科编码;班级名称取学校班级列表) */ + @GetMapping("/subjects/classes") + public List teachingClasses(@RequestParam("subId") String subId) { + return teacherHomeworkV2Service.teachingClasses(subId); + } + + // ============================ 辅助:班级树 / 名册同步 ============================ + + /** 年级→班级树(发布选班;名册快照聚合) */ + @GetMapping("/classes") + public List classTree() { + return teacherHomeworkV2Service.classTree(); + } + + /** 手动触发名册同步(bschclass 接口就绪前为空操作) */ + @PostMapping("/classes/sync") + public Map syncClasses() { + int synced = classStudentService.syncAll(); + return java.util.Collections.singletonMap("synced", synced); + } +} diff --git a/src/main/java/com/yunhai/homework/controller/api/TemplateController.java b/src/main/java/com/yunhai/homework/controller/api/TemplateController.java new file mode 100644 index 0000000..e9c593c --- /dev/null +++ b/src/main/java/com/yunhai/homework/controller/api/TemplateController.java @@ -0,0 +1,88 @@ +package com.yunhai.homework.controller.api; + +import com.yunhai.homework.config.Config; +import com.yunhai.homework.service.ITemplateService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板接口(模板域)。 + * 路径前缀:{@code Config.API_PATH + "/template/"}(原 homework/Template/ 三级路径已修正)。 + * 写操作一律 POST(原 EditType/DelTemplate 为 GET 写操作,已 POST 化)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/template/") +public class TemplateController { + + @Autowired + private ITemplateService templateService; + + /** + * 获取模板类型列表。 + */ + @GetMapping("Typelist") + public List> getTypelist() { + return templateService.getTypelist(); + } + + /** + * 新增/编辑模板类型(原 GET EditType,已 POST 化)。 + */ + @PostMapping("TypeEdit") + public void typeEdit( + @RequestParam("typeid") Integer typeid, + @RequestParam("TypeName") String typeName) { + templateService.editType(typeid, typeName); + } + + /** + * 删除模板类型(原 GET DelType,已 POST 化)。 + */ + @PostMapping("TypeDelete") + public void typeDelete(@RequestParam("typeid") Integer typeid) { + templateService.delType(typeid); + } + + /** + * 分页获取模板列表。 + */ + @GetMapping("List") + public Map list( + @RequestParam("PageSize") Integer pageSize, + @RequestParam("PageIndex") Integer pageIndex, + @RequestParam(value = "typeid", defaultValue = "0") Integer typeid) { + return templateService.getTemplatelist(pageSize, pageIndex, typeid); + } + + /** + * 新增/编辑模板。 + */ + @PostMapping("TemplateEdit") + public void templateEdit( + @RequestParam(value = "SerID", required = false) Integer serID, + @RequestParam("Title") String title, + @RequestParam("Contents") String contents, + @RequestParam(value = "Type", required = false) Integer type, + @RequestParam(value = "Cover", required = false) Integer cover) { + templateService.editTemplate(serID, title, contents, type, cover); + } + + /** + * 删除模板(原 GET DelTemplate,已 POST 化)。 + */ + @PostMapping("TemplateDelete") + public void templateDelete(@RequestParam("SerID") Integer serid) { + templateService.delTemplate(serid); + } + + /** + * 获取单个模板详情。 + */ + @GetMapping("TemplateFirst") + public Map templateFirst(@RequestParam("serid") Integer serid) { + return templateService.getTemplateFist(serid); + } +} diff --git a/src/main/java/com/yunhai/homework/entity/BaseEntity.java b/src/main/java/com/yunhai/homework/entity/BaseEntity.java new file mode 100644 index 0000000..5f98e8c --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/BaseEntity.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.common.listener.HomeworkAuditListener; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 所有实体的公共基类:自增主键 + 创建人/时间 + 更新人/时间(由 {@link HomeworkAuditListener} 自动填充)。 + */ +@MappedSuperclass +@Getter +@Setter +@EntityListeners(HomeworkAuditListener.class) +public abstract class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 创建人ID(平台用户ID) + */ + @Column(name = "create_by", updatable = false) + private String createBy; + + /** + * 创建时间 + */ + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + /** + * 更新人ID(平台用户ID) + */ + @Column(name = "update_by") + private String updateBy; + + /** + * 更新时间 + */ + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/ClassStudent.java b/src/main/java/com/yunhai/homework/entity/ClassStudent.java new file mode 100644 index 0000000..793e295 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ClassStudent.java @@ -0,0 +1,59 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 班级学生名册快照(PRD class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据),每日全量刷 + 手动触发; + * 同步不物理删,转出置 status=0。学生身份口径:用户中心数字ID(UserContext.getUserId()) + * ↔ student_id(SchStudent.UserStuSerID),名册仅存学生ID + 姓名快照。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "class_student", + uniqueConstraints = @UniqueConstraint(name = "uk_class_student", columnNames = {"class_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ClassStudent extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; + + /** 年级名快照 */ + @Column(name = "grade_name", length = 50) + private String gradeName; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 在班状态:1 在班 / 0 已转出退课 */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** 离班时间 */ + @Column(name = "leave_at") + private LocalDateTime leaveAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroup.java b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java new file mode 100644 index 0000000..a002e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 联系组。对应原 .NET Contacts_Group 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroup extends BaseEntity { + + /** 组名称 */ + @Column(name = "group_name", length = 200) + private String groupName; + + /** 创建人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "creator_id") + private Integer creatorId; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java new file mode 100644 index 0000000..eb07763 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 联系组-用户关联。对应原 .NET Contacts_Tea 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group_user", + uniqueConstraints = @UniqueConstraint(name = "uk_group_user", + columnNames = {"group_id", "user_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroupUser extends BaseEntity { + + /** 联系组ID */ + @Column(name = "group_id", nullable = false) + private Integer groupId; + + /** 用户ID(基础平台用户ID) */ + @Column(name = "user_id", nullable = false) + private Integer userId; + + /** 用户姓名 */ + @Column(name = "user_name", length = 64) + private String userName; +} diff --git a/src/main/java/com/yunhai/homework/entity/Homework.java b/src/main/java/com/yunhai/homework/entity/Homework.java new file mode 100644 index 0000000..6e615f2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Homework.java @@ -0,0 +1,208 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 课程作业主表。对应原 .NET Notify_Message(已剔除短信/模板/门户等无关字段)。 + * + * 阅读状态不落主表:采用「懒标记」模型——读者首次查看时写入 {@link HomeworkRead}, + * 不存在阅读记录即视为未读,从而无需在发作业时枚举全校用户预插接收行。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Homework extends BaseEntity { + + /** + * 作业标题 + */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** + * 学校ID(多租户隔离:发布时从当前用户角色填充,查询时按校过滤)。 + */ + @Column(name = "sch_id") + private Integer schId; + + /** + * 作业内容 + */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** + * 作业类型:1 普通 / 2 重要(见 {@link com.yunhai.homework.enums.HomeworkTypeEnum}) + */ + @Column(name = "type", nullable = false) + private Integer type = 1; + + /** + * 发布人ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "publisher_id") + private Integer publisherId; + + /** + * 发布人姓名 + */ + @Column(name = "publisher_name", length = 64) + private String publisherName; + + /** + * 发布部门ID(原 DeptID) + */ + @Column(name = "dept_id", length = 64) + private String deptId; + + /** + * 发布部门名称 + */ + @Column(name = "dept_name", length = 128) + private String deptName; + + /** + * 状态:1 已发布(无草稿态,见 {@link com.yunhai.homework.enums.HomeworkStatusEnum}) + */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** + * 是否置顶:0 否 / 1 是(原 topmost) + */ + @Column(name = "topmost") + private Integer topmost = 0; + + // ==================== 迁移补全字段(对应 C# Notify_Message) ==================== + + /** + * 接收人列表(JSON 数组,如 ["10012","10013"] 或接收组ID列表)。 + * 对应 C# Notify_Message.ReceiveList + */ + @Column(name = "receive_list", columnDefinition = "LONGTEXT") + private String receiveList; + + /** + * 密码发送(对应 C# Notify_Message.PwdSend) + */ + @Column(name = "pwd_send", length = 255) + private String pwdSend; + + /** + * 抄送人列表(对应 C# Notify_Message.ChaoSend) + */ + @Column(name = "chao_send", length = 2000) + private String chaoSend; + + /** + * 已读数(发送时初始化为 0,也可由 HomeworkRead 表 COUNT 实时计算)。 + * 对应 C# Notify_Message.readcount + */ + @Column(name = "read_count") + private Integer readCount = 0; + + /** + * 应读数(接收人总数)。对应 C# Notify_Message.allowcount + */ + @Column(name = "allow_count") + private Integer allowCount = 0; + + /** + * 父作业ID(转发时关联原作业)。对应 C# Notify_Message.fkid + */ + @Column(name = "parent_id") + private Integer parentId; + + /** + * 回复ID。对应 C# Notify_Message.reid + */ + @Column(name = "reply_id", length = 255) + private String replyId; + + /** + * 是否撤回:0 否 / 1 是。对应 C# Notify_Message.ispull + */ + @Column(name = "is_pulled") + private Integer isPulled = 0; + + /** + * 定时发送时间(到时间后自动发布)。对应 C# Notify_Message.DsfsTime。 + * PRD 语义中的 scheduled_at 复用本列。 + */ + @Column(name = "timing_send_time") + private LocalDateTime timingSendTime; + + /** + * 短信发送标记:0 不发短信 / 1 发短信。对应 C# Notify_Message.dxbit + */ + @Column(name = "sms_flag") + private Integer smsFlag = 0; + + // ==================== 课堂作业 V3(PRD V1.0)扩展列 ==================== + + /** + * 学科(字典值)。 + */ + @Column(name = "subject", length = 50) + private String subject; + + /** + * 截止时间(精确到分钟;空 = 仅查阅型,不设截止,到点关闭任务跳过)。 + */ + @Column(name = "deadline_at") + private LocalDateTime deadlineAt; + + /** + * 作业流程状态(PRD 五态,见 {@link com.yunhai.homework.enums.HwStatusEnum})。 + * notice 原 status 列继续承载发布/撤回语义,两者并存。 + */ + @Column(name = "hw_status", nullable = false, length = 20) + private String hwStatus = com.yunhai.homework.enums.HwStatusEnum.DRAFT_UNPUBLISHED.getCode(); + + /** + * 发布时间(立即发布或定时到点发布时写入)。 + */ + @Column(name = "published_at") + private LocalDateTime publishedAt; + + /** + * 提交方式,JSON 数组文本,如 ["image","attach"]。 + * 取值:text / image / attach / none(PRD 的 图片/文件/两者 映射为 + * ["image"] / ["attach"] / ["image","attach"])。 + */ + @Column(name = "submit_type", length = 100) + private String submitType; + + /** + * 截止后是否允许逾期提交:0 否 / 1 是。 + */ + @Column(name = "allow_late", nullable = false) + private Integer allowLate = 0; + + /** + * 提醒设置:截止前 1 天提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1d", nullable = false) + private Integer remind1d = 1; + + /** + * 提醒设置:截止前 1 小时提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1h", nullable = false) + private Integer remind1h = 1; + + /** + * 归档时间(教师手动归档时写入;归档后只读,所有提醒任务跳过)。 + */ + @Column(name = "archived_at") + private LocalDateTime archivedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java new file mode 100644 index 0000000..6fc7804 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业附件表(PRD homework_attachment)。文件实体仍存 up_file(file_id 关联 + file_url 冗余)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_attachment") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkAttachment extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** up_file.id(可空:允许外部 URL 附件) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file / ... */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号(越小越靠前) */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkClass.java b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java new file mode 100644 index 0000000..c01cefb --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 作业-班级关联表(PRD homework_class)。 + * 发布/编辑时由 receiveList("all" 或班级 id 串)解析写入,是应交名单与统计的权威关联。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_class", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_class", columnNames = {"homework_id", "class_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkClass extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkRead.java b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java new file mode 100644 index 0000000..35b773a --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.time.LocalDateTime; + +/** + * 作业阅读记录(懒标记)。对应原 .NET Notify_ReceiveList 的「逐用户阅读状态」语义, + * 但仅在读者首次查看时落库,避免发作业时预插全员。 + * + * 唯一约束 (homework_id, reader_id):同一用户对同一作业只记一条已读。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read", + uniqueConstraints = @UniqueConstraint( + name = "uk_homework_reader", + columnNames = {"homework_id", "reader_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkRead extends BaseEntity { + + /** + * 作业ID(关联 homework.id) + */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** + * 读者ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "reader_id", nullable = false) + private Integer readerId; + + /** + * 读者姓名 + */ + @Column(name = "reader_name", length = 64) + private String readerName; + + /** + * 阅读时间 + */ + @Column(name = "read_time") + private LocalDateTime readTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java new file mode 100644 index 0000000..8179e79 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java @@ -0,0 +1,58 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 学生作业阅读记录(PRD homework_read_record)。 + * + * 学生维度,含首次/最近阅读时间,与 notice 语义的 {@link HomeworkRead}(教师/阅读者已读)互不影响。 + * 行仅在判定已读时写入;未读名单 = 名册左连本表(无记录或 is_read=0)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read_record", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student", columnNames = {"homework_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReadRecord extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", length = 64) + private String classId; + + /** 阅读状态:0 UNREAD / 1 READ */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 首次阅读时间(写入后不再变更) */ + @Column(name = "first_read_at") + private LocalDateTime firstReadAt; + + /** 最近阅读时间(每次上报刷新) */ + @Column(name = "last_read_at") + private LocalDateTime lastReadAt; + + /** 阅读次数 */ + @Column(name = "read_count", nullable = false) + private Integer readCount = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java new file mode 100644 index 0000000..97e068b --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java @@ -0,0 +1,45 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 作业接收人关联表(替代旧 receive_list JSON 字符串存储)。 + * + * 每条记录代表一个作业的一个接收人(用户ID + 姓名快照,不含账号)。 + * 全员作业(receive_list="ALL")不写明细行, + * 读取时按 receive_list="ALL" 标识走旧逻辑兜底。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_receiver", + indexes = { + @Index(name = "idx_homework_id", columnList = "homework_id"), + @Index(name = "idx_receiver", columnList = "receiver_id") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReceiver extends BaseEntity { + + /** 作业ID(关联 homework.id) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 接收人ID(用户中心数字ID) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收类型:1 接收 / 2 抄送 */ + @Column(name = "receive_type", nullable = false) + private Integer receiveType = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java new file mode 100644 index 0000000..4626044 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java @@ -0,0 +1,98 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.enums.ReviewStatusEnum; +import com.yunhai.homework.enums.SubmitStatusEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 作业提交表,多版本建模(PRD homework_submission)。 + * + * 同一学生 (homework_id, student_id) 下 version 单调递增;提交/打回重交/撤回均新增行, + * 旧行 is_latest 置 0 保留历史;is_latest=1 的行是当前有效版本。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_submission", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student_ver", + columnNames = {"homework_id", "student_id", "version"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkSubmission extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", nullable = false, length = 50) + private String className; + + /** 提交版本,同一学生单调递增(撤回/重交均 +1) */ + @Column(name = "version", nullable = false) + private Integer version = 1; + + /** 是否当前版本:1 是 / 0 历史 */ + @Column(name = "is_latest", nullable = false) + private Integer isLatest = 1; + + /** 提交状态(见 {@link SubmitStatusEnum}) */ + @Column(name = "status", nullable = false, length = 20) + private String status = SubmitStatusEnum.SUBMITTED.getCode(); + + /** 是否逾期:0 否 / 1 是 */ + @Column(name = "is_late", nullable = false) + private Integer isLate = 0; + + /** 提交时间(NOT_SUBMITTED 空行为空) */ + @Column(name = "submitted_at") + private LocalDateTime submittedAt; + + /** 文本作业内容 */ + @Column(name = "text", columnDefinition = "TEXT") + private String text; + + /** 评分 */ + @Column(name = "score", precision = 6, scale = 2) + private BigDecimal score; + + /** 评语(打回时 = 订正原因,必填) */ + @Column(name = "comment", columnDefinition = "TEXT") + private String comment; + + /** 批阅状态(见 {@link ReviewStatusEnum}) */ + @Column(name = "review_status", nullable = false, length = 20) + private String reviewStatus = ReviewStatusEnum.PENDING.getCode(); + + /** 批阅人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "reviewed_by") + private Integer reviewedBy; + + /** 批阅人姓名快照 */ + @Column(name = "reviewed_by_name", length = 64) + private String reviewedByName; + + /** 批阅时间 */ + @Column(name = "reviewed_at") + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java new file mode 100644 index 0000000..f088c92 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板。对应原 .NET Notify_Template 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_template") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkTemplate extends BaseEntity { + + /** 模板标题 */ + @Column(name = "title", length = 200) + private String title; + + /** 模板内容(HTML) */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** 封面图片文件ID */ + @Column(name = "cover") + private Integer cover; + + /** 引用次数 */ + @Column(name = "ref_count") + private Integer refCount = 0; + + /** 模板类型ID */ + @Column(name = "type_id") + private Integer typeId; +} diff --git a/src/main/java/com/yunhai/homework/entity/Notification.java b/src/main/java/com/yunhai/homework/entity/Notification.java new file mode 100644 index 0000000..2a931a1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Notification.java @@ -0,0 +1,71 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 站内通知(PRD notification)。六类通知的统一落表 + 已读状态。 + * 通知已读独立于作业阅读记录(本表 is_read 只反映通知列表已读)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "notification", + indexes = { + @Index(name = "idx_receiver", columnList = "receiver_id, is_read"), + @Index(name = "idx_hw_type", columnList = "homework_id, type") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Notification extends BaseEntity { + + /** 通知类型(见 {@link com.yunhai.homework.enums.NotificationTypeEnum}) */ + @Column(name = "type", nullable = false, length = 30) + private String type; + + /** 关联作业ID */ + @Column(name = "homework_id") + private Integer homeworkId; + + /** 关联业务ID(如 submissionId) */ + @Column(name = "related_id") + private Integer relatedId; + + /** 通知标题 */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** 通知内容 */ + @Column(name = "content", length = 1000) + private String content; + + /** 接收人ID(学生 = student_id,教师 = 用户中心数字ID,同一 ID 空间) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收人角色:student / teacher */ + @Column(name = "receiver_role", nullable = false, length = 20) + private String receiverRole = "student"; + + /** 是否已读:0 未读 / 1 已读 */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 已读时间 */ + @Column(name = "read_at") + private LocalDateTime readAt; + + /** 发送通道:inapp 站内 / sms 短信 */ + @Column(name = "send_channel", nullable = false, length = 20) + private String sendChannel = "inapp"; +} diff --git a/src/main/java/com/yunhai/homework/entity/SchClassTeach.java b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java new file mode 100644 index 0000000..d1a091f --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java @@ -0,0 +1,46 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 班级任教关系快照(bschclass sch_class_teach 本地镜像,PRD 口径同 class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据);bschclass 任教接口 + * 不可用时,任教学科链路降级读本表。教师口径:用户中心数字ID(UserContext.getUserId()); + * class_id 与 class_student.class_id 同一取值域。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sch_class_teach", + uniqueConstraints = @UniqueConstraint(name = "uk_sch_class_teach", columnNames = {"school_id", "class_id", "tea_id", "sub_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SchClassTeach extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID(与 class_student.class_id 同一取值域) */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 教师ID(用户中心数字ID) */ + @Column(name = "tea_id", nullable = false) + private Integer teaId; + + /** 学科编码(bschclass SubID;本地无字典时编码即展示名) */ + @Column(name = "sub_id", nullable = false, length = 64) + private String subId; + + /** 有效状态:1 有效 / 0 失效 */ + @Column(name = "status", nullable = false) + private Integer status = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsModule.java b/src/main/java/com/yunhai/homework/entity/SmsModule.java new file mode 100644 index 0000000..3f4d444 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsModule.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 短信模块配置。对应原 .NET SchSms 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_module") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsModule extends BaseEntity { + + /** 模块名称 */ + @Column(name = "module_name", length = 100) + private String moduleName; + + /** 模块标识 */ + @Column(name = "module_code", length = 50) + private String moduleCode; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java new file mode 100644 index 0000000..428d271 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java @@ -0,0 +1,37 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 学校短信开通配置。对应原 .NET SchSms_Set 表。 + * 记录某学校某模块是否开通短信。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_school_config") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsSchoolConfig extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 学校名称 */ + @Column(name = "school_name", length = 200) + private String schoolName; + + /** 模块ID */ + @Column(name = "module_id") + private Integer moduleId; + + /** 是否开通短信 */ + @Column(name = "is_sms") + private Boolean isSms = false; +} diff --git a/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java new file mode 100644 index 0000000..85df7ba --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java @@ -0,0 +1,54 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 提交附件表(PRD submission_attachment)。替代 JSON 方案,支撑矩阵缩略图与批阅大图预览。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "submission_attachment", + indexes = @Index(name = "idx_sub", columnList = "submission_id")) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SubmissionAttachment extends BaseEntity { + + /** 提交行ID(homework_submission.id) */ + @Column(name = "submission_id", nullable = false) + private Integer submissionId; + + /** 作业ID(冗余,便于统计与清理) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + @Column(name = "student_id") + private Integer studentId; + + /** up_file.id(可空:允许外部 URL) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号 */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/SysDict.java b/src/main/java/com/yunhai/homework/entity/SysDict.java new file mode 100644 index 0000000..3091084 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SysDict.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 系统字典(发文部门等)。对应原 .NET Common_DeptDic 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sys_dict") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SysDict extends BaseEntity { + + /** 字典类型(如 "dept"=发文部门) */ + @Column(name = "dict_type", length = 50) + private String dictType; + + /** 字典 key(原 CodeID) */ + @Column(name = "dict_key", length = 100) + private String dictKey; + + /** 字典 value(显示名称,原 Name) */ + @Column(name = "dict_value", length = 200) + private String dictValue; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; + + /** 是否启用:0 禁用 / 1 启用(原 Enable) */ + @Column(name = "enable_flag") + private Integer enableFlag = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/TemplateType.java b/src/main/java/com/yunhai/homework/entity/TemplateType.java new file mode 100644 index 0000000..aab8163 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/TemplateType.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板类型。对应原 .NET Notify_TemplateType 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "template_type") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class TemplateType extends BaseEntity { + + /** 类型名称 */ + @Column(name = "type_name", length = 100) + private String typeName; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/UpFile.java b/src/main/java/com/yunhai/homework/entity/UpFile.java new file mode 100644 index 0000000..38248c7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/UpFile.java @@ -0,0 +1,47 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 附件文件记录。对应原 .NET Common_UpFiles 表。 + * + * 存储文件元信息(真实文件名、存储路径、大小、类型),物理文件存储在 + * application.yml 的 file.upload-path 目录下。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "up_file") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class UpFile extends BaseEntity { + + /** 应用类型(如 "homework"),用于区分不同业务的附件 */ + @Column(name = "topic_type", length = 50) + private String topicType; + + /** 关联主题ID(如作业 serid) */ + @Column(name = "topic_id") + private Integer topicId; + + /** 文件真实名称(用户上传时的原始文件名) */ + @Column(name = "file_true_name", length = 255) + private String fileTrueName; + + /** 文件存储路径(相对路径,如 homework/20260823/uuid.pdf) */ + @Column(name = "file_name", length = 500) + private String fileName; + + /** 文件大小(字节) */ + @Column(name = "file_size") + private Integer fileSize; + + /** 文件后缀名(如 pdf, docx, jpg) */ + @Column(name = "file_type", length = 20) + private String fileType; +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java new file mode 100644 index 0000000..124bec9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业状态。无草稿态:发作业一律置为「已发布」, + * 定时发送通过 timingSendTime + 查询侧「已到点」过滤控制可见性。 + * 对应原 .NET Notify_Message.status(原系统已发布=2,此处归一化为 1)。 + */ +@Getter +public enum HomeworkStatusEnum { + + PUBLISHED(1, "已发布"); + + private final int code; + private final String desc; + + HomeworkStatusEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkStatusEnum of(Integer code) { + if (code == null) { + return PUBLISHED; + } + for (HomeworkStatusEnum e : values()) { + if (e.code == code) { + return e; + } + } + return PUBLISHED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java new file mode 100644 index 0000000..077396d --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业类型。对应原 .NET Notify_Message.type(原系统作业类=2,此处归一化)。 + */ +@Getter +public enum HomeworkTypeEnum { + + NORMAL(1, "普通作业"), + IMPORTANT(2, "重要作业"); + + private final int code; + private final String desc; + + HomeworkTypeEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkTypeEnum of(Integer code) { + if (code == null) { + return NORMAL; + } + for (HomeworkTypeEnum e : values()) { + if (e.code == code) { + return e; + } + } + return NORMAL; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java new file mode 100644 index 0000000..8b10083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业流程状态(PRD 五态,存 homework.hw_status)。 + * + * 与 notice 原 status(Integer,发布/撤回语义)并存:本枚举承载课堂作业业务流转, + * 流转收敛在 HomeworkStateService,条件更新保证幂等。 + */ +@Getter +public enum HwStatusEnum { + + /** 草稿 - 未发布 */ + DRAFT_UNPUBLISHED("DRAFT_UNPUBLISHED", "草稿-未发布"), + /** 草稿 - 定时待发布 */ + DRAFT_SCHEDULED("DRAFT_SCHEDULED", "草稿-定时待发布"), + /** 已发布 - 进行中 */ + PUBLISHED("PUBLISHED", "已发布-进行中"), + /** 已截止 */ + CLOSED("CLOSED", "已截止"), + /** 已归档(只读,不再触发任何提醒) */ + ARCHIVED("ARCHIVED", "已归档"); + + private final String code; + private final String desc; + + HwStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HwStatusEnum of(String code) { + if (code == null) { + return DRAFT_UNPUBLISHED; + } + for (HwStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return DRAFT_UNPUBLISHED; + } + + public boolean isDraft() { + return this == DRAFT_UNPUBLISHED || this == DRAFT_SCHEDULED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java new file mode 100644 index 0000000..df75af1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 站内通知类型(PRD 六类,存 notification.type)。 + */ +@Getter +public enum NotificationTypeEnum { + + /** 作业发布(对应班级学生) */ + HOMEWORK_PUBLISHED("HOMEWORK_PUBLISHED", "作业发布"), + /** 截止提醒(截止前 1 天 / 1 小时) */ + DEADLINE_REMINDER("DEADLINE_REMINDER", "截止提醒"), + /** 未交提醒(教师手动) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未交提醒"), + /** 未读提醒(教师手动) */ + NOT_READ("NOT_READ", "未读提醒"), + /** 批阅完成 */ + REVIEW_DONE("REVIEW_DONE", "批阅完成"), + /** 打回通知 */ + RETURNED("RETURNED", "打回通知"), + /** + * 定时发布失败回退(PRD §17.1 教师泳道要求通知教师,但 PRD 枚举表未列—— + * 按 classwork-homework-plan-v3 §13-2 建议补充,待产品确认命名)。 + */ + SCHEDULE_FAILED("SCHEDULE_FAILED", "定时发布失败"), + /** + * 截止时间变更(PRD §17.1 延长截止要求通知学生,PRD 枚举表未列——同上补充)。 + */ + DEADLINE_CHANGED("DEADLINE_CHANGED", "截止时间变更"); + + private final String code; + private final String desc; + + NotificationTypeEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static NotificationTypeEnum of(String code) { + if (code == null) { + return null; + } + for (NotificationTypeEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return null; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java new file mode 100644 index 0000000..031eddc --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java @@ -0,0 +1,39 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 批阅状态(PRD 三态,存 homework_submission.review_status)。 + * 与 SubmitStatusEnum 同源联动:批阅保存 → status=REVIEWED + review_status=REVIEWED; + * 打回 → status=RETURNED + review_status=RETURNED。 + */ +@Getter +public enum ReviewStatusEnum { + + /** 待批阅 */ + PENDING("PENDING", "待批阅"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"), + /** 已打回 */ + RETURNED("RETURNED", "已打回"); + + private final String code; + private final String desc; + + ReviewStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static ReviewStatusEnum of(String code) { + if (code == null) { + return PENDING; + } + for (ReviewStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return PENDING; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java new file mode 100644 index 0000000..7d6aaca --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java @@ -0,0 +1,50 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 学生提交状态(PRD 五态,存 homework_submission.status)。 + * + * NOT_SUBMITTED 空行仅由「撤回」产生;未提交学生本身不落行(统计时由名册左连补位)。 + */ +@Getter +public enum SubmitStatusEnum { + + /** 未提交(撤回后产生的空提交行) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未提交"), + /** 已提交(含打回后重新提交) */ + SUBMITTED("SUBMITTED", "已提交"), + /** 逾期提交(截止后提交,is_late=1) */ + LATE_SUBMITTED("LATE_SUBMITTED", "逾期提交"), + /** 已打回(待重新提交) */ + RETURNED("RETURNED", "已打回"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"); + + private final String code; + private final String desc; + + SubmitStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static SubmitStatusEnum of(String code) { + if (code == null) { + return NOT_SUBMITTED; + } + for (SubmitStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return NOT_SUBMITTED; + } + + /** + * 是否计入「已交」口径:打回未重交也算已交(PRD 学生矩阵口径)。 + */ + public boolean countsAsSubmitted() { + return this != NOT_SUBMITTED; + } +} diff --git a/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java new file mode 100644 index 0000000..5377254 --- /dev/null +++ b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java @@ -0,0 +1,161 @@ +package com.yunhai.homework.job; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.enums.HwStatusEnum; +import com.yunhai.homework.enums.NotificationTypeEnum; +import com.yunhai.homework.repository.HomeworkClassRepository; +import com.yunhai.homework.repository.HomeworkRepository; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.HomeworkStateService; +import com.yunhai.homework.service.impl.NotificationService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 课堂作业定时任务(PRD §17.1 调度泳道)。 + * + * 全部基于状态机 CAS 条件更新,天然幂等,可安全重复执行; + * ARCHIVED 作业被显式跳过(归档后不再触发任何提醒)。 + */ +@Component +public class HomeworkScheduledJobs { + + private static final Logger log = LoggerFactory.getLogger(HomeworkScheduledJobs.class); + + @Autowired + private HomeworkRepository homeworkRepository; + + @Autowired + private HomeworkClassRepository homeworkClassRepository; + + @Autowired + private HomeworkStateService stateService; + + @Autowired + private NotificationService notificationService; + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + /** + * 定时发布:每分钟扫描到点的 DRAFT_SCHEDULED 作业 → PUBLISHED + 作业发布通知; + * 失败回退 DRAFT_UNPUBLISHED 并站内通知教师。 + */ + @Scheduled(cron = "0 * * * * ?") + public void scheduledPublish() { + List due = homeworkRepository + .findByHwStatusAndTimingSendTimeLessThanEqual(HwStatusEnum.DRAFT_SCHEDULED.getCode(), + LocalDateTime.now()); + if (due.isEmpty()) { + return; + } + log.info("[scheduledPublish] 到点作业 {} 个", due.size()); + for (Homework homework : due) { + Integer id = homework.getId(); + try { + teacherHomeworkV2Service.publishInternal(id, HwStatusEnum.DRAFT_SCHEDULED); + log.info("[scheduledPublish] 发布成功 homeworkId={}", id); + } catch (Exception e) { + log.error("[scheduledPublish] 发布失败,回退草稿 homeworkId={}", id, e); + try { + boolean rolledBack = stateService.rollbackFromSchedule(id); + if (rolledBack) { + notificationService.send(NotificationTypeEnum.SCHEDULE_FAILED, id, null, + homework.getPublisherId(), homework.getPublisherName(), + NotificationService.ROLE_TEACHER, + "定时发布失败:《" + homework.getTitle() + "》", + "定时发布任务执行异常,作业已回退为「草稿-未发布」,请重新发布。"); + } + } catch (Exception ex) { + log.error("[scheduledPublish] 回退失败 homeworkId={}", id, ex); + } + } + } + } + + /** + * 到点截止:每分钟扫描已过截止时间的 PUBLISHED 作业 → CLOSED(无对应通知类型,不推送)。 + */ + @Scheduled(cron = "30 * * * * ?") + public void deadlineClose() { + List overdue = homeworkRepository + .findByHwStatusAndDeadlineAtBefore(HwStatusEnum.PUBLISHED.getCode(), LocalDateTime.now()); + for (Homework homework : overdue) { + if (stateService.cas(homework.getId(), HwStatusEnum.PUBLISHED, HwStatusEnum.CLOSED)) { + log.info("[deadlineClose] 作业已截止 homeworkId={}", homework.getId()); + } + } + } + + /** + * 截止提醒:每分钟扫描截止时间在 24h 内的 PUBLISHED 作业, + * 命中「1 天档 / 1 小时档」窗口且开关开启时向未交学生推送 DEADLINE_REMINDER; + * 档位去重(related_id:1=1 天档 2=1 小时档),每作业每档至多发一轮。 + */ + @Scheduled(cron = "40 * * * * ?") + public void deadlineReminder() { + LocalDateTime now = LocalDateTime.now(); + List window = homeworkRepository + .findByHwStatusAndDeadlineAtBetween(HwStatusEnum.PUBLISHED.getCode(), now, now.plusHours(24)); + for (Homework homework : window) { + if (homework.getDeadlineAt() == null) { + continue; + } + // 1 小时档:截止前 [55, 60) 分钟窗口 + if (homework.getRemind1h() != null && homework.getRemind1h() == 1 + && inWindow(homework.getDeadlineAt(), now, 55, 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 2)) { + sendReminder(homework, 2, "1 小时"); + } + // 1 天档:截止前 [23, 24) 小时窗口 + if (homework.getRemind1d() != null && homework.getRemind1d() == 1 + && inWindow(homework.getDeadlineAt(), now, 23 * 60, 24 * 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 1)) { + sendReminder(homework, 1, "1 天"); + } + } + } + + private boolean inWindow(LocalDateTime deadline, LocalDateTime now, int fromMinutes, int toMinutes) { + long minutes = java.time.Duration.between(now, deadline).toMinutes(); + return minutes >= fromMinutes && minutes < toMinutes; + } + + private void sendReminder(Homework homework, int slot, String slotDesc) { + // 通知接收人直接使用学生ID(名册口径:student_id = 平台用户ID) + List targets = teacherHomeworkV2Service.notSubmittedStudentIds(homework.getId()).stream() + .distinct() + .collect(Collectors.toList()); + if (targets.isEmpty()) { + return; + } + int sent = notificationService.sendBatch(NotificationTypeEnum.DEADLINE_REMINDER, homework.getId(), slot, + NotificationService.distinct(targets), + NotificationService.deadlineReminderTitle(homework), + NotificationService.deadlineReminderContent(homework) + "(剩余不足 " + slotDesc + ")"); + log.info("[deadlineReminder] homeworkId={}, slot={}, sent={}", homework.getId(), slot, sent); + } + + /** + * 名册同步:每日 02:00 全量刷新 class_student(bschclass 接口就绪前仅日志)。 + */ + @Scheduled(cron = "${homework.sync.class-data-cron:0 0 2 * * ?}") + public void classDataSync() { + try { + classStudentService.syncAll(); + } catch (Exception e) { + log.error("[classDataSync] 名册同步失败", e); + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageQuery.java b/src/main/java/com/yunhai/homework/model/BasePageQuery.java new file mode 100644 index 0000000..7f46326 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageQuery.java @@ -0,0 +1,14 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +/** + * 统一分页查询入参。pageIndex 从 1 起。 + */ +@Data +public class BasePageQuery { + private int pageIndex = 1; + private int pageSize = 10; + /** 通用模糊搜索关键词 */ + private String keyword; +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageResult.java b/src/main/java/com/yunhai/homework/model/BasePageResult.java new file mode 100644 index 0000000..058c96c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageResult.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +import java.util.List; + +/** + * 统一分页响应外壳。 + */ +@Data +public class BasePageResult { + /** 总记录数 */ + private int total; + /** 当前页数据 */ + private List content; + /** 当前页码(从 1 起) */ + private int pageIndex; + /** 每页条数 */ + private int pageSize; + /** 总页数 */ + private int totalPages; + /** 是否还有下一页 */ + private boolean hasNext; + + public BasePageResult(List content, int total, int pageIndex, int pageSize, int totalPages, boolean hasNext) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + } +} diff --git a/src/main/java/com/yunhai/homework/model/SchTeaList.java b/src/main/java/com/yunhai/homework/model/SchTeaList.java new file mode 100644 index 0000000..80f9b9c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/SchTeaList.java @@ -0,0 +1,106 @@ +package com.yunhai.homework.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * 班级管理接口-教师(bschclass {@code /oapi/DbClass/GetSchteaList} 响应项)。 + * 对应 C# BestSchool.Entity.Model.SchTeaList。 + */ +public class SchTeaList { + + /** 学校 GUID(SchSerID) */ + @JsonProperty("SchSerID") + private String schSerID; + + @JsonProperty("TeaTel") + private String teaTel; + + /** 教师姓名 */ + @JsonProperty("TeaName") + private String teaName; + + /** 教师用户ID */ + @JsonProperty("UserTeaSerID") + private Integer userTeaSerID; + + @JsonProperty("TeaIDCard") + private String teaIDCard; + + /** 教师工号 */ + @JsonProperty("TeaNo") + private String teaNo; + + /** 教师所属组ID列表(兼容旧字段 GroupIds) */ + @JsonProperty("GroupIds") + private List groupIds; + + /** 教师所属组ID(接口现行字段 GroupID,单个) */ + @JsonProperty("GroupID") + private Integer groupId; + + public Integer getGroupId() { + return groupId; + } + + public void setGroupId(Integer groupId) { + this.groupId = groupId; + } + + public String getSchSerID() { + return schSerID; + } + + public void setSchSerID(String schSerID) { + this.schSerID = schSerID; + } + + public String getTeaTel() { + return teaTel; + } + + public void setTeaTel(String teaTel) { + this.teaTel = teaTel; + } + + public String getTeaName() { + return teaName; + } + + public void setTeaName(String teaName) { + this.teaName = teaName; + } + + public Integer getUserTeaSerID() { + return userTeaSerID; + } + + public void setUserTeaSerID(Integer userTeaSerID) { + this.userTeaSerID = userTeaSerID; + } + + public String getTeaIDCard() { + return teaIDCard; + } + + public void setTeaIDCard(String teaIDCard) { + this.teaIDCard = teaIDCard; + } + + public String getTeaNo() { + return teaNo; + } + + public void setTeaNo(String teaNo) { + this.teaNo = teaNo; + } + + public List getGroupIds() { + return groupIds; + } + + public void setGroupIds(List groupIds) { + this.groupIds = groupIds; + } +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java new file mode 100644 index 0000000..cf9c417 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 班级 DTO(/oapi/VSchClass/GetVClassList,SchClass 视图)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchClassDto { + + /** 班级业务ID(sch_class.class_id,scopeType=班级 的 scopeId;学生 ClassID 指向它) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级名称 */ + @JsonProperty("ClassName") + private String className; + + /** 年级ID(= gradeId) */ + @JsonProperty("Levell") + private Integer gradeId; + + /** 排序号 */ + @JsonProperty("sx") + private Integer sort; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java new file mode 100644 index 0000000..01ff15b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 年级 DTO(/oapi/Schoolen/GetSchLenLevellist,BaseSchsystemGrade)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchGradeDto { + + /** 年级ID(=gradeId,scopeType=年级 的 scopeId;班级 Levell 指向它) */ + @JsonProperty("Id") + private Integer id; + + /** 年级名称(gradeNickname,如「初一」) */ + @JsonProperty("Name") + private String name; + + /** 所属学段(学制)ID */ + @JsonProperty("OwnerId") + private Integer ownerId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java new file mode 100644 index 0000000..06185b6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校信息 DTO(/oapi/SchInfo/GetUserSchFirst)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchInfoDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerId") + private String schSerId; + + /** 用户中心学校组ID(与 CampusNotice schId 同域) */ + @JsonProperty("UserSchSerId") + private Integer userSchSerId; + + /** 学校名称 */ + @JsonProperty("SchName") + private String schName; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java new file mode 100644 index 0000000..f96913d --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学生 DTO(/oapi/OAStu/*,SchStudent 视图)。 + * account 仅为上游接口契约字段(兼容反序列化),本系统落库/身份映射不再使用; + * 学生身份以 stuId(UserStuSerID,= 平台用户ID)+ stuName 承载。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchStudentDto { + + /** 学员登录账号(上游契约字段,仅兼容保留,不落库) */ + @JsonProperty("Account") + private String account; + + /** 学生ID */ + @JsonProperty("UserStuSerID") + private Integer stuId; + + /** 学生姓名 */ + @JsonProperty("StuName") + private String stuName; + + /** 所在班级ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 所在组ID */ + @JsonProperty("GroupID") + private Integer groupId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java new file mode 100644 index 0000000..ff7add8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校学科 DTO(/oapi/SubInfo/GetSchSublist)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSubjectDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 学科ID(学科编码;字典无编码时为字典ID字符串) */ + @JsonProperty("SubID") + private String subId; + + /** 学科名称 */ + @JsonProperty("SubName") + private String subName; + + /** 排序号 */ + @JsonProperty("No") + private Integer no; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java new file mode 100644 index 0000000..f0df157 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学段(学制)DTO(/oapi/Schoolen/GetSchLenlist,BaseSchsystem)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSystemDto { + + /** 学制ID(scopeType=学部 的 scopeId) */ + @JsonProperty("Id") + private Integer id; + + /** 学段名称(如「小学部」「初中部」) */ + @JsonProperty("Name") + private String name; + + /** 学制类型 */ + @JsonProperty("Type") + private Integer type; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java new file mode 100644 index 0000000..86ec6f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Date; + +/** + * bschclass 任教关系 DTO(/oapi/TeachInfo/*,sch_class_teach 视图)。 + * 一条记录 = 某学校某班级中某教师任教某学科。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTeachDto { + + /** 任教记录ID(sch_class_teach.id) */ + @JsonProperty("SerID") + private Integer serId; + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 教师ID(= 平台用户ID) */ + @JsonProperty("UserTeaSerID") + private Integer teaId; + + /** 学科ID(学科编码) */ + @JsonProperty("SubID") + private String subId; + + /** 班级业务ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级ID(与 ClassID 同值,上游兼容字段) */ + @JsonProperty("UserClassID") + private Integer userClassId; + + /** 创建时间 */ + @JsonProperty("CreationTime") + private Date creationTime; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java new file mode 100644 index 0000000..29623ad --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java @@ -0,0 +1,41 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * bschclass 组织树节点 DTO(/oapi/SchGroup/GetTeaTree、GetStuTree,TeaTreeVO 视图)。 + * type=1 组节点(学校/学段/年级/班级、教师组),type=2 人员叶子(学生/教师)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTreeNodeDto { + + /** 节点ID(学校/学段/年级/班级为对应业务ID,叶子为学生 stuId 或教师 teaId) */ + private Integer id; + + /** 节点名称 */ + private String name; + + /** 父节点ID(根节点为 0) */ + private Integer pid; + + /** 排序号(组取 sort,叶子固定 0) */ + private Integer no; + + /** 1=组节点,2=人员叶子 */ + private Integer type; + + /** 是否父节点(组 true,叶子 false) */ + @JsonProperty("isParent") + private Boolean isParent; + + /** 头像(当前固定空串) */ + private String photo; + + /** 子节点(叶子为 null,组节点非 null) */ + private List children; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java new file mode 100644 index 0000000..7621af2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java @@ -0,0 +1,81 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 作业详情视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class HomeworkDetailVo { + + @JsonProperty("serid") + private Integer serid; + + @JsonProperty("title") + private String title; + + @JsonProperty("Contents") + private String contents; + + @JsonProperty("DeptID") + private String deptId; + + @JsonProperty("DeptName") + private String deptName; + + @JsonProperty("TeaName") + private String publisherName; + + @JsonProperty("EditDate") + private String editDate; + + @JsonProperty("numcount") + private Integer numCount; + + @JsonProperty("readnumcount") + private Long readNumCount; + + @JsonProperty("ReceiveList") + private String receiveList; + + @JsonProperty("DsfsTime") + private String dsfsTime; + + @JsonProperty("isDsfs") + private Boolean isDsfs; + + @JsonProperty("ChaoSend") + private List chaoSend; + + /** 接收人列表:"all" 或用户ID列表 */ + @JsonProperty("tlist") + private Object tlist; + + /** + * 接收对象可读文本("全部班级" 或班级名顿号串)。 + * 详情页「接收班级」标签直接展示,避免前端拿 ReceiveList 的 id 串渲染。 + */ + @JsonProperty("receiveText") + private String receiveText; + + /** + * 附件项(旧 ChaoSend 数组元素)。 + */ + @Data + public static class FileItem { + @JsonProperty("id") + private Integer id; + @JsonProperty("name") + private String name; + @JsonProperty("size") + private Integer size; + @JsonProperty("fileid") + private Integer fileid; + @JsonProperty("filename") + private String filename; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java new file mode 100644 index 0000000..e1c2fa2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 作业分页结果(扩展 BasePageResult,额外携带 unreadCount)。 + * + * 对齐方案 M7:unreadCount 并入分页外壳扩展子类,不改基类契约。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkPageResult extends BasePageResult { + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkPageResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + super(content, total, pageIndex, pageSize, totalPages, hasNext); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java new file mode 100644 index 0000000..afea9a6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +/** + * 作业列表查询条件。 + * + * - 管理端(adminList):用 status / type / deptId / publisherId / 时间范围过滤; + * - 用户端(userList):额外用 readType 过滤未读/已读(0 全部 / 1 未读 / 2 已读)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkQuery extends BasePageQuery { + + /** 状态过滤:null 不过滤;1 已发布 */ + private Integer status; + + /** 类型过滤:1 普通 / 2 重要 */ + private Integer type; + + /** 列表范围(管理端):"user" 只看自己发布的,null 看全部 */ + private String scope; + + /** 发布部门ID */ + private String deptId; + + /** 发布人ID(用户中心数字ID,管理端按发布人查) */ + private Integer publisherId; + + /** 开始时间(创建时间 >=,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startTime; + + /** 结束时间(创建时间 <= 当天 23:59:59,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endTime; + + /** 阅读状态过滤(仅用户端):0 全部 / 1 未读 / 2 已读 */ + private Integer readType = 0; + + /** 排序:1 时间倒序(默认)/ 2 时间正序 */ + private String orderByTime = "1"; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java new file mode 100644 index 0000000..2cb560f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java @@ -0,0 +1,65 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import com.yunhai.homework.common.validation.ValidationGroups; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 作业发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。 + */ +@Data +public class HomeworkRequest { + + /** 作业标题 */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业标题不得为空") + private String title; + + /** 作业内容 */ + private String content; + + /** 作业类型:1 普通 / 2 重要 */ + @NotNull(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业类型不得为空") + private Integer type = 1; + + /** 发布部门ID(原 DeptID,必填——对应原系统「发布部门不得为空」校验) */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "发布部门不得为空") + private String deptId; + + /** 发布部门名称 */ + private String deptName; + + /** 是否置顶:0 否 / 1 是 */ + private Integer topmost = 0; + + /** 接收人列表(JSON 数组字符串,如 ["10012","10013"]——用户中心数字ID,或 "ALL" 表示全员) */ + private String receiveList; + + /** 附件文件ID列表(逗号分隔串,如 "1,2,3");旧名 chaoSend,已更名 fileIds(M6) */ + private String fileIds; + + /** 定时发送时间(为空表示立即发布) */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timingSendTime; + + /** 短信发送标记:0 不发短信 / 1 发短信(旧名 topmost 实指发短信,已正名 smsFlag,M6) */ + private Integer smsFlag = 0; + + /** 转发源作业ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */ + private Integer sourceId; + + // ==================== 课堂作业 V1/V2 扩展(前端契约字段,兼容层同步写新列) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deadline; + + /** 提交方式:text/image/attach/none 组合 */ + private java.util.List submitType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java new file mode 100644 index 0000000..c492f86 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java @@ -0,0 +1,113 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 作业统计视图(对应原系统 GetStatisData / 按发布人统计)。 + */ +@Data +public class HomeworkStatisticsVo { + + /** 作业总数(全部已发布) */ + private long total; + + /** 已发布数 */ + private long publishedCount; + + /** 阅读记录总数(累计已读人次) */ + private long totalReadRecords; + + /** 当前用户未读数(已到点的已发布 - 当前用户已读) */ + private long unreadForCurrentUser; + + /** 按发布人统计 */ + private List byPublisher; + + /** 按月统计(已发布作业数) */ + private List byMonth; + + // ==================== 课程作业口径(前端 /stats 页消费) ==================== + + /** 我布置的作业数(当前登录人发布的全部作业,含草稿) */ + private long myAssignCount; + + /** 我的作业待批改总量(已交未批的当前版本行合计) */ + private long pendingGrade; + + /** 各作业平均提交率(%,仅统计需收集提交的作业;无样本时为 0) */ + private double avgSubmitRate; + + /** 按作业的提交与批改进度明细(草稿与仅查阅型不计) */ + private List byHomework = new ArrayList<>(); + + /** 已批阅提交的分数分布(四档) */ + private List scoreDist = new ArrayList<>(); + + /** 单作业进度条目(口径对齐前端 HomeworkProgress 契约) */ + @Data + public static class HomeworkStat { + private Integer id; + private String title; + private String subject; + /** 旧契约四态派生值:draft / collecting / closed / done */ + private String hwStatus; + private String hwStatusDesc; + /** 应交人数 */ + private long should; + /** 已读人数 */ + private long read; + /** 已交人数 */ + private long submitted; + /** 已批阅人数 */ + private long graded; + /** 待批改人数 */ + private long pendingGrade; + /** 未交人数 */ + private long unsubmitted; + /** 提交率(%) */ + private double submitRate; + /** 批阅率(%) */ + private double gradeRate; + } + + /** 分数分布档(key 供前端配色映射:excellent/good/pass/fail) */ + @Data + public static class ScoreBucket { + private String label; + private String key; + private long count; + + public ScoreBucket(String label, String key, long count) { + this.label = label; + this.key = key; + this.count = count; + } + } + + @Data + public static class PublisherStat { + private String publisherName; + private long count; + + public PublisherStat(String publisherName, long count) { + this.publisherName = publisherName; + this.count = count; + } + } + + @Data + public static class MonthStat { + private int year; + private int month; + private long count; + + public MonthStat(int year, int month, long count) { + this.year = year; + this.month = month; + this.count = count; + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java new file mode 100644 index 0000000..c906e2e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.List; + +/** + * 用户端作业列表结果:在分页数据之外额外携带「当前用户未读总数」, + * 供前端展示未读角标(对应原系统 OtherData.unReadCount)。 + */ +@Data +public class HomeworkUserListResult { + + private int total; + private List content; + private int pageIndex; + private int pageSize; + private int totalPages; + private boolean hasNext; + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkUserListResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java new file mode 100644 index 0000000..333d49e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java @@ -0,0 +1,92 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 作业视图对象(列表/详情返回)。 + */ +@Data +public class HomeworkVo { + + private Integer id; + private String title; + private String content; + private Integer type; + private String typeDesc; + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + private String publisherName; + private String deptId; + private String deptName; + private Integer status; + private String statusDesc; + private Integer topmost; + + /** 已读人数(基于阅读记录实时统计) */ + private Long readCount = 0L; + + /** 应读人数 */ + private Integer allowCount; + + /** 当前用户是否已读(用户端列表使用;管理端恒为 false 或不关注) */ + private boolean read = false; + + /** 是否撤回:0 否 / 1 是 */ + private Integer isPulled; + + /** 定时发送时间 */ + private LocalDateTime timingSendTime; + + /** 父作业ID(转发时关联原作业) */ + private Integer parentId; + + /** 短信发送标记 */ + private Integer smsFlag; + + /** 接收人列表(JSON) */ + private String receiveList; + + /** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */ + private String UpFile; + + // ==================== 课堂作业 V1/V2 扩展(前端 types/homework.ts 契约) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间(前端契约字段名 deadline) */ + private LocalDateTime deadline; + + /** 提交方式数组:text/image/attach/none */ + private java.util.List submitType; + + /** 作业流程状态——旧契约四态派生值(draft/collecting/closed/done),权威五态见 homework.hw_status */ + private String hwStatus; + + private String hwStatusDesc; + + /** + * 提交/批改进度(管理端列表页「提交进度 / 未读未交 / 批改」列使用)。 + * 草稿或仅查阅型为 null;列表场景由 HomeworkProgressCalculator 批量聚合回填。 + */ + private com.yunhai.homework.model.v2.ProgressVo progress; + + private String createBy; + private LocalDateTime createTime; + private String updateBy; + private LocalDateTime updateTime; + + /** + * 阅读明细视图(阅读列表查询用)。 + */ + @Data + public static class ReadDetail { + /** 读者ID(用户中心数字ID) */ + private Integer id; + private String name; + private boolean read; + private LocalDateTime readTime; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java new file mode 100644 index 0000000..15ece01 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 一键已读结果视图对象(替代原 Map<String,Object> 返回)。 + */ +@Data +public class InitializeRedVo { + + @JsonProperty("count") + private Integer count; + + public InitializeRedVo() {} + + public InitializeRedVo(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java new file mode 100644 index 0000000..8127480 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java @@ -0,0 +1,30 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 文件上传结果视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class UpFileResultVo { + + @JsonProperty("fileid") + private Integer fileid; + + @JsonProperty("name") + private String name; + + @JsonProperty("fileurl") + private String fileurl; + + @JsonProperty("host") + private String host; + + @JsonProperty("type") + private String type; + + @JsonProperty("size") + private Integer size; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java new file mode 100644 index 0000000..76dfa2f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +/** + * 附件视图对象(对应原 .NET FileObj)。 + */ +@Data +public class UpFileVo { + + /** 文件记录ID */ + private Integer id; + + /** 文件真实名称 */ + private String fileTrueName; + + /** 文件存储路径(相对路径) */ + private String fileName; + + /** 文件大小(字节) */ + private Integer fileSize; + + /** 文件后缀名 */ + private String fileType; + + /** 关联主题ID */ + private Integer topicId; + + /** 应用类型 */ + private String topicType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java new file mode 100644 index 0000000..771eb3a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java @@ -0,0 +1,122 @@ +package com.yunhai.homework.model.homework; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户可访问模块(左菜单)视图对象。 + * 将基础平台返回的扁平 {@code List} 转换为前端 layout(参考 UserBase)期望的树结构: + * {@code modId}(路由高亮匹配)/{@code page}(跳转路径)/{@code children}(子菜单)。 + */ +public class UserModuleVo { + + /** 模块主键 id(基础平台 module id) */ + private Integer id; + + /** 菜单高亮匹配用:等价于后端模块 id */ + private Integer modId; + + /** 模块名称 */ + private String name; + + /** 父级模块 id(0 表示根节点) */ + private Integer parentId; + + /** 前端路由路径(由 BoaModule.path 映射而来) */ + private String page; + + /** 层级(0=根分组) */ + private Integer level; + + /** 菜单图标 */ + private String icon; + + /** 排序号 */ + private Integer orderNumber; + + /** 是否可用 */ + private Boolean usable; + + /** 子菜单 */ + private List children = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getModId() { + return modId; + } + + public void setModId(Integer modId) { + this.modId = modId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Integer getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(Integer orderNumber) { + this.orderNumber = orderNumber; + } + + public Boolean getUsable() { + return usable; + } + + public void setUsable(Boolean usable) { + this.usable = usable; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java new file mode 100644 index 0000000..bedf7b5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 附件条目(作业附件与提交附件共用)。fileUrl 必传;fileId 为 file/Upload 产物时回传。 + */ +@Data +public class AttachmentItem { + + /** up_file.id */ + private Integer fileId; + + /** 文件访问 URL */ + private String fileUrl; + + /** 文件类型:image / file */ + private String fileType; + + /** 文件名 */ + private String fileName; + + /** 排序号 */ + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassItem.java b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java new file mode 100644 index 0000000..459ea80 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java @@ -0,0 +1,18 @@ +package com.yunhai.homework.model.v2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 班级条目(作业参与班级展示)。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClassItem { + + private String classId; + + private String className; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java new file mode 100644 index 0000000..c0f400b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 年级→班级树(发布作业选班 + 应交名单基数来源)。 + * 叶子节点 id = 班级 id(class_student 快照聚合)。 + */ +@Data +public class ClassTreeNode { + + /** 年级节点为 gradeName,班级叶子为 classId */ + private String id; + + /** 展示名 */ + private String name; + + /** 是否父节点(年级) */ + private Boolean isParent; + + /** 叶子节点:在班学生数 */ + private Integer studentCount; + + private List children = new ArrayList<>(); +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java new file mode 100644 index 0000000..f8c5dec --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 延长截止请求(新截止时间必须晚于当前时间)。 + */ +@Data +public class ExtendRequest { + + @NotNull(message = "新截止时间不得为空") + private LocalDateTime deadlineAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java new file mode 100644 index 0000000..1ac46f8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java @@ -0,0 +1,36 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 作业详情汇总统计卡片(PRD §18 页 4 stats 接口出参)。 + */ +@Data +public class HomeworkStatsVo { + + private Integer homeworkId; + + private String title; + + private String hwStatus; + + private String hwStatusDesc; + + /** 总进度 */ + private ProgressVo total; + + /** 分班进度 */ + private List byClass; + + @Data + public static class ByClass { + + private String classId; + + private String className; + + private ProgressVo progress; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java new file mode 100644 index 0000000..9464910 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 创建/编辑作业请求(PRD §18 页 1 表单)。 + * 创建一律落草稿(DRAFT_UNPUBLISHED),发布/定时走独立接口。 + */ +@Data +public class HomeworkV2Request { + + /** 作业标题(必填 ≤50 字) */ + @NotBlank(message = "作业标题不得为空") + @Size(max = 50, message = "作业标题不超过50字") + private String title; + + /** 学科(字典值) */ + private String subject; + + /** 作业说明(富文本) */ + private String description; + + /** 参与班级 id 列表 */ + private List classIds; + + /** 附件列表 */ + private List attachments; + + /** 截止时间(精确到分钟;空 = 仅查阅型) */ + private LocalDateTime deadlineAt; + + /** 提交方式:image / attach / text / none 的组合(PRD 图片/文件/两者) */ + private List submitType; + + /** 截止后是否允许逾期提交:0 否 / 1 是 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天提醒(0 关 / 1 开) */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时提醒(0 关 / 1 开) */ + private Integer remind1h; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java new file mode 100644 index 0000000..8bf1b02 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java @@ -0,0 +1,73 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 作业视图对象 V2(草稿箱/已发布列表/详情共用)。 + * 列表场景 attachments/classes 可按需填充;progress 仅非草稿且非仅查阅型作业返回。 + */ +@Data +public class HomeworkV2Vo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 作业说明(富文本;列表场景可置空省流) */ + private String description; + + /** 作业状态(PRD 五态) */ + private String hwStatus; + + private String hwStatusDesc; + + /** 定时发布时间(复用 timing_send_time) */ + private LocalDateTime scheduledAt; + + /** 发布时间 */ + private LocalDateTime publishedAt; + + /** 截止时间 */ + private LocalDateTime deadlineAt; + + /** 归档时间 */ + private LocalDateTime archivedAt; + + /** 提交方式数组 */ + private List submitType; + + /** 截止后允许逾期提交:0/1 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天 */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时 */ + private Integer remind1h; + + /** 参与班级 */ + private List classes; + + /** 附件列表(详情场景填充) */ + private List attachments; + + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + + /** 发布人姓名 */ + private String publisherName; + + /** 创建/更新时间(审计) */ + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + /** 汇总进度(草稿或仅查阅型为 null) */ + private ProgressVo progress; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java new file mode 100644 index 0000000..0303eb8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知分页结果(含当前用户未读数)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationPageResult extends BasePageResult { + + /** 当前用户未读通知总数 */ + private long unreadCount; + + public NotificationPageResult(BasePageResult page, long unreadCount) { + super(page.getContent(), page.getTotal(), page.getPageIndex(), page.getPageSize(), + page.getTotalPages(), page.isHasNext()); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java new file mode 100644 index 0000000..f8af4a7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知列表查询。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationQuery extends BasePageQuery { + + /** 类型过滤(NotificationTypeEnum code) */ + private String type; + + /** 已读过滤:0 未读 / 1 已读(空 = 全部) */ + private Integer isRead; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java new file mode 100644 index 0000000..2a9b61a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知列表项。 + */ +@Data +public class NotificationVo { + + private Integer id; + + /** 通知类型 */ + private String type; + + private String typeDesc; + + private String title; + + private String content; + + private Integer homeworkId; + + private Integer relatedId; + + /** 是否已读:0/1 */ + private Integer isRead; + + private LocalDateTime readAt; + + private LocalDateTime createTime; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java new file mode 100644 index 0000000..201a460 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 提交/批阅/阅读进度(PRD §18 页 3 汇总口径)。 + * should(应交)为分母;打回未重交计入已交。 + */ +@Data +public class ProgressVo { + + /** 应交人数(参与班级在班学生去重) */ + private int should; + + /** 已读人数 */ + private int read; + + /** 已交人数(含逾期/打回/已批阅) */ + private int submitted; + + /** 已批阅人数 */ + private int graded; + + /** 待批阅人数(submitted - graded) */ + private int pendingGrade; + + /** 未交人数 */ + private int unsubmitted; + + /** 未读人数 */ + private int unread; + + /** 未读且未交人数(高亮口径:should - 已读∪已交) */ + private int unreadUndone; + + /** 阅读率(百分数,一位小数) */ + private double readRate; + + /** 提交率(百分数,一位小数) */ + private double submitRate; + + /** 批阅率(已批阅 / 已交,百分数一位小数;submitted=0 时为 0) */ + private double gradeRate; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java new file mode 100644 index 0000000..a97e0c1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 教师手动提醒请求:type = NOT_READ / NOT_SUBMITTED,studentIds 为空时提醒全部未读/未交学生。 + */ +@Data +public class RemindRequest { + + /** 提醒类型:NOT_READ / NOT_SUBMITTED */ + @NotBlank(message = "提醒类型不得为空") + private String type; + + /** 定向学生ID(用户中心数字ID;空 = 全部未读/未交) */ + private List studentIds; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java new file mode 100644 index 0000000..45d3f3e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 打回请求(PRD 页 6)。comment 为订正原因,必填。 + */ +@Data +public class ReturnRequest { + + @NotBlank(message = "打回须填写订正原因") + private String comment; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java new file mode 100644 index 0000000..166b2e1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 批阅保存请求(PRD 页 6)。score 与 comment 至少一项有值。 + */ +@Data +public class ReviewRequest { + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 圈划标注图片(P2,先收参) */ + private List markedImages; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java new file mode 100644 index 0000000..894b433 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 定时发布 / 取消定时请求。 + * cancel=true 时忽略 scheduledAt,作业回退草稿-未发布并清空定时时间。 + */ +@Data +public class ScheduleRequest { + + /** 定时发布时间(cancel=false 时必填且须晚于当前时间) */ + private LocalDateTime scheduledAt; + + /** 是否取消定时 */ + private Boolean cancel = false; + + @NotNull + public Boolean getCancel() { + return cancel; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java new file mode 100644 index 0000000..6372883 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 学生端作业详情(PRD §18 学生页 2)。进入详情后由前端停留 ≥3 秒再调 read 上报。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentHomeworkDetailVo extends StudentHomeworkVo { + + /** 作业说明(富文本) */ + private String description; + + /** 作业附件 */ + private List attachments; + + /** 参与班级(学生所属班级视角) */ + private List classes; + + /** 我当前版本的提交附件(未提交为 null) */ + private List myAttachments; + + /** 我的文本内容(当前版本) */ + private String myText; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java new file mode 100644 index 0000000..9d30037 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学生端作业列表项(PRD §18 学生页 1)。 + */ +@Data +public class StudentHomeworkVo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 任课教师(发布人姓名) */ + private String teacherName; + + /** 截止时间(前端做倒计时) */ + private LocalDateTime deadlineAt; + + /** 提交方式 */ + private java.util.List submitType; + + /** 作业状态 */ + private String hwStatus; + + private String hwStatusDesc; + + /** 截止后是否允许逾期补交:0 否 / 1 是(学生端据此决定是否渲染「补交」入口) */ + private Integer allowLate; + + /** 我的提交概要(未提交为 null) */ + private MySubmission my; + + @Data + public static class MySubmission { + + /** 当前版本提交行ID */ + private Integer submissionId; + + /** 提交状态(PRD 五态) */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 版本号 */ + private Integer version; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分(已批阅才有) */ + private java.math.BigDecimal score; + + /** 评语 */ + private String comment; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java new file mode 100644 index 0000000..49e3144 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学生矩阵查询(PRD §18 页 5)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentMatrixQuery extends BasePageQuery { + + /** 班级筛选(空 = 全部参与班级) */ + private String classId; + + /** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */ + private String submitStatus; + + /** 批阅状态筛选:PENDING/REVIEWED/RETURNED */ + private String reviewStatus; + + /** 只看待批阅(reviewStatus=PENDING 且已提交)快捷过滤 */ + private Boolean pendingGrade; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java new file mode 100644 index 0000000..6257285 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java @@ -0,0 +1,57 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学生矩阵行(PRD §18 页 5)。未交学生也生成占位行(submissionId=0)。 + */ +@Data +public class StudentMatrixVo { + + /** 当前版本提交行ID(未交占位为 0) */ + private Integer submissionId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + /** 姓名 */ + private String studentName; + + private String classId; + + private String className; + + /** 阅读状态:0 未读 / 1 已读 */ + private Integer isRead; + + /** 首次阅读时间 */ + private LocalDateTime firstReadAt; + + /** 最近阅读时间 */ + private LocalDateTime lastReadAt; + + /** 提交状态(PRD 五态;未交为 NOT_SUBMITTED) */ + private String submitStatus; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期:0/1 */ + private Integer isLate; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分 */ + private BigDecimal score; + + /** 最新提交附件 URL 列表(缩略图) */ + private List thumbnails; + + /** 提交内容类型摘要:image=拍照/图片、attach=上传附件、text=文字作答(未交为空列表) */ + private List contentTypes; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java new file mode 100644 index 0000000..f4d9cd1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 学生查看批阅结果(PRD §18 学生页 2 result 接口)。 + */ +@Data +public class StudentResultVo { + + private Integer homeworkId; + + private String homeworkTitle; + + /** 当前版本批阅结果(未批阅为 null) */ + private SubmissionVersionVo latest; + + /** 全部版本(含各版评语,新→旧) */ + private List versions; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java new file mode 100644 index 0000000..1fa7545 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 学生提交作业请求(PRD §18 学生页 3)。附件只接受 file/Upload 产物(fileId 或平台 URL),拒绝 dataURL。 + */ +@Data +public class StudentSubmitRequest { + + /** 文本内容(submitType 含 text 时有效) */ + @Size(max = 20000, message = "文本内容过长") + private String text; + + /** 附件列表(图片/文件) */ + private List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java new file mode 100644 index 0000000..6c576d9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 学科 DTO(布置过的学科 / 我的任教学科共用返回结构)。 + */ +@Data +public class SubjectDto { + + /** 学科编码(homework.subject 字典值 / bschclass SubID) */ + private String subId; + + /** 学科名称(本校学科字典解析;字典缺失时回退为编码) */ + private String subName; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java new file mode 100644 index 0000000..cac00dc --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 撤回重交动作(PUT /student/homeworks/{id}/submissions/{submissionId})。 + * withdraw = 撤回(截止前,产生 NOT_SUBMITTED 新版本行);resubmit = 打回后重交(走提交语义)。 + */ +@Data +public class SubmissionActionRequest { + + @NotBlank(message = "action 不得为空") + private String action; + + /** resubmit 时的提交内容(等同 StudentSubmitRequest) */ + private String text; + + /** resubmit 时的附件 */ + private java.util.List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java new file mode 100644 index 0000000..bff2972 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java @@ -0,0 +1,63 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 提交版本条目(历史版本/批阅弹窗/学生结果页共用)。 + */ +@Data +public class SubmissionVersionVo { + + private Integer submissionId; + + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + private String studentName; + + /** 版本号 */ + private Integer version; + + /** 是否当前版本:0/1 */ + private Integer isLatest; + + /** 提交状态 */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 文本内容 */ + private String text; + + /** 附件 */ + private List attachments; + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 批阅状态 */ + private String reviewStatus; + + private String reviewStatusDesc; + + /** 批阅人(展示姓名快照,缺失时回退ID) */ + private String reviewedBy; + + /** 批阅时间 */ + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java new file mode 100644 index 0000000..6d6991e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教师端作业列表查询(草稿箱/已发布列表共用)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TeacherHomeworkQuery extends BasePageQuery { + + /** 学科筛选 */ + private String subject; + + /** 作业状态筛选(已发布列表:PUBLISHED/CLOSED/ARCHIVED) */ + private String hwStatus; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java new file mode 100644 index 0000000..d9fd64e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 任教班级 VO(按学科筛选任教班级返回结构)。 + */ +@Data +public class TeachingClassVo { + + /** 班级业务ID(bschclass sch_class.class_id) */ + private String classId; + + /** 班级名称(学校班级列表映射;缺失时回退为班级ID) */ + private String className; +} diff --git a/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java new file mode 100644 index 0000000..0581e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ClassStudent; +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; + +/** + * 班级学生名册仓储。 + */ +public interface ClassStudentRepository extends JpaRepository { + + /** 按学生ID(用户中心数字ID)反查学生身份(学生端接口入口) */ + List findByStudentIdAndStatus(Integer studentId, Integer status); + + /** 班级在班学生 */ + List findByClassIdAndStatus(String classId, Integer status); + + /** 指定班级集合的全部在班学生(应交名单) */ + List findByClassIdInAndStatus(List classIds, Integer status); + + /** 指定学校的全部名册(同步任务全量刷新用) */ + List findBySchoolId(Integer schoolId); + + /** 年级→班级树:按学校去重班级(class_student 快照聚合),列序:classId, className, gradeName */ + @Query("select distinct c.classId, c.className, c.gradeName from ClassStudent c " + + "where c.schoolId = :schoolId and c.status = 1") + List findDistinctClasses(@Param("schoolId") Integer schoolId); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java new file mode 100644 index 0000000..4dc9ee4 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java @@ -0,0 +1,27 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroup; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ContactsGroupRepository extends JpaRepository { + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + + /** + * 按创建人ID(用户中心数字ID)查询联系组列表。 + */ + List findByCreatorId(Integer creatorId); + + /** + * 按创建人ID查询联系组列表(分页)。 + */ + Page findByCreatorId(Integer creatorId, Pageable pageable); + + /** + * 查询全部联系组(按创建时间降序)。 + */ + List findAllByOrderByCreateTimeDesc(); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java new file mode 100644 index 0000000..df5ab28 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java @@ -0,0 +1,42 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroupUser; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ContactsGroupUserRepository extends JpaRepository { + Page findByGroupId(Integer groupId, Pageable pageable); + List findByGroupId(Integer groupId); + + /** + * 按组ID批量删除成员(JPQL bulk delete,SQL 立即执行)。 + * 注意:不能用 derived delete——其 DELETE 延迟到 flush 才执行, + * 同事务内先 insert 会触发 uk_group_user 唯一约束冲突。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId = :groupId") + void deleteByGroupIdBulk(@Param("groupId") Integer groupId); + + /** + * 按多个组ID批量删除成员。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId in :groupIds") + void deleteByGroupIdInBulk(@Param("groupIds") List groupIds); + + /** + * 按多个组ID查询所有成员(用于统计每组人数、组树构建)。 + */ + List findByGroupIdIn(List groupIds); + + /** + * 按组ID和用户ID查询(判断是否已存在)。 + */ + boolean existsByGroupIdAndUserId(Integer groupId, Integer userId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java new file mode 100644 index 0000000..1e6af55 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 作业附件仓储。 + */ +public interface HomeworkAttachmentRepository extends JpaRepository { + + List findByHomeworkIdOrderBySortAscIdAsc(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java new file mode 100644 index 0000000..8f651f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkClass; +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; + +/** + * 作业-班级关联仓储。 + */ +public interface HomeworkClassRepository extends JpaRepository { + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 指定班级的全部作业ID(学生端列表倒查用) */ + @Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds") + List findHomeworkIdsByClassIds(@Param("classIds") List classIds); + + /** 批量查 N 个作业的班级关联(列表展示避免 N+1) */ + @Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java new file mode 100644 index 0000000..6c16a81 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java @@ -0,0 +1,23 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReadRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 学生阅读记录仓储。 + */ +public interface HomeworkReadRecordRepository extends JpaRepository { + + Optional findByHomeworkIdAndStudentId(Integer homeworkId, Integer studentId); + + List findByHomeworkId(Integer homeworkId); + + /** 批量查 N 个作业的已读记录(列表统计避免 N+1) */ + List findByHomeworkIdIn(Collection homeworkIds); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java new file mode 100644 index 0000000..20dc83c --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkRead; +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.Optional; + +/** + * 作业阅读记录仓储(懒标记模型)。 + */ +public interface HomeworkReadRepository extends JpaRepository { + + /** + * 某用户是否已读某作业。 + */ + boolean existsByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 某作业的总已读人数。 + */ + long countByHomeworkId(Integer homeworkId); + + /** + * 删除某作业的全部阅读记录(作业删除时级联清理)。 + */ + void deleteByHomeworkId(Integer homeworkId); + + /** + * 某用户已读的作业总数(用于「我的未读」= 已发布总数 - 已读总数)。 + */ + long countByReaderId(Integer readerId); + + /** + * 查询某用户已读的作业ID列表(用于列表页批量判定未读)。 + */ + @Query("select r.homeworkId from HomeworkRead r where r.readerId = :readerId") + List findHomeworkIdsByReaderId(@Param("readerId") Integer readerId); + + /** + * 查询某用户对某作业的阅读记录(标记已读时幂等判断)。 + */ + Optional findByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 查询某作业的全部阅读记录(按阅读时间倒序),用于阅读明细列表。 + */ + @Query("select r from HomeworkRead r where r.homeworkId = :homeworkId order by r.readTime desc") + List findByHomeworkIdOrderByReadTimeDesc(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java new file mode 100644 index 0000000..402d455 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReceiver; +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; + +/** + * 作业接收人关联表仓储。 + */ +public interface HomeworkReceiverRepository extends JpaRepository { + + /** 按作业ID查询全部接收人 */ + List findByHomeworkId(Integer homeworkId); + + /** 按作业ID删除全部接收人 */ + void deleteByHomeworkId(Integer homeworkId); + + /** 按作业ID和接收类型查询接收人 */ + List findByHomeworkIdAndReceiveType(Integer homeworkId, Integer receiveType); + + /** + * 批量查 N 个作业的接收人,用于列表展示(避免 N+1)。 + * @param homeworkIds 作业ID列表 + * @return 接收人关联记录列表 + */ + @Query("select r from HomeworkReceiver r where r.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java new file mode 100644 index 0000000..bf862b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java @@ -0,0 +1,139 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Homework; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 课程作业仓储。 + */ +public interface HomeworkRepository extends JpaRepository, + JpaSpecificationExecutor { + + /** + * 作业流程状态条件更新(CAS):仅当当前状态为 from 时更新为 to。 + * 状态机单点收敛用,天然幂等、并发安全(定时任务与接口共用)。 + * clearAutomatically:批量更新绕过一级缓存,必须清空以避免后续 findById 读到旧实体 + * 再被 save() 写回旧状态(同事务内必现,集成测试发现)。 + * + * @return 影响行数(0 = 状态已变化,调用方应放弃本次迁移) + */ + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update Homework h set h.hwStatus = :to where h.id = :id and h.hwStatus = :from") + int casHwStatus(@Param("id") Integer id, + @Param("from") String from, + @Param("to") String to); + + /** + * 定时发布任务扫描:定时待发布且已到点。 + */ + List findByHwStatusAndTimingSendTimeLessThanEqual(String hwStatus, LocalDateTime now); + + /** + * 到点关闭任务扫描:进行中且已过截止时间。 + */ + List findByHwStatusAndDeadlineAtBefore(String hwStatus, LocalDateTime now); + + /** + * 截止提醒任务扫描:进行中且截止时间落在指定窗口内(remind_1d/remind_1h 开关在服务侧逐单过滤)。 + */ + List findByHwStatusAndDeadlineAtBetween(String hwStatus, LocalDateTime start, LocalDateTime end); + + /** + * 按作业流程状态查询(兼容层/统计用)。 + */ + List findByHwStatus(String hwStatus); + + /** + * 当前登录人发布的全部作业(课程作业口径统计:我布置的作业 / 提交与批改进度明细)。 + */ + List findByPublisherId(Integer publisherId); + + /** + * 当前登录人布置过(已发布及之后状态)作业的学科去重清单(学科筛选用,字典序;草稿不计)。 + */ + @Query("select distinct h.subject from Homework h " + + "where h.publisherId = :publisherId and h.subject is not null and h.subject <> '' " + + "and h.hwStatus in :statuses order by h.subject") + List findAssignedSubjects(@Param("publisherId") Integer publisherId, + @Param("statuses") Collection statuses); + + /** + * 按状态计数(用于统计已发布总数)。 + */ + long countByStatus(Integer status); + + /** + * 已发布且已到点(定时发送未到点不计入)的作业数,用于用户端未读数统计。 + */ + @Query("select count(n) from Homework n where n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDue(@Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定状态、指定时间范围内的作业创建时间(用于按月统计,避免加载大字段)。 + */ + @Query("select n.createTime from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定状态、指定时间范围内的发布人姓名(用于按发布人统计)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 按学校和状态计数(多租户隔离统计)。 + */ + long countBySchIdAndStatus(Integer schId, Integer status); + + /** + * 按学校计数(多租户隔离统计:全部状态)。 + */ + long countBySchId(Integer schId); + + /** + * 已发布且已到点(定时发送未到点不计入)且属于指定学校的作业数(用户端未读数统计,多租户隔离)。 + */ + @Query("select count(n) from Homework n where n.schId = :schId and n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDueBySchId(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定学校、指定状态、指定时间范围内的作业创建时间(按月统计,多租户隔离)。 + */ + @Query("select n.createTime from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesBySchIdStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定学校、指定状态、指定时间范围内的发布人姓名(按发布人统计,多租户隔离)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesBySchStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java new file mode 100644 index 0000000..547c5ea --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkSubmission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 作业提交仓储(多版本)。统计一律基于 is_latest=1 的当前版本行。 + */ +public interface HomeworkSubmissionRepository extends JpaRepository { + + /** 学生在指定作业的当前版本行 */ + Optional findByHomeworkIdAndStudentIdAndIsLatest(Integer homeworkId, + Integer studentId, + Integer isLatest); + + /** 学生在指定作业的全部版本(新→旧) */ + List findByHomeworkIdAndStudentIdOrderByVersionDesc(Integer homeworkId, Integer studentId); + + /** 指定作业的当前版本行(统计/矩阵用) */ + List findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest); + + /** 批量查 N 个作业的当前版本行(列表统计避免 N+1) */ + List findByHomeworkIdInAndIsLatest(Collection homeworkIds, Integer isLatest); + + /** 学生矩阵分页 */ + Page findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest, Pageable pageable); + + /** 学生当前版本最大版本号 */ + @Query("select max(s.version) from HomeworkSubmission s " + + "where s.homeworkId = :homeworkId and s.studentId = :studentId") + Integer findMaxVersion(@Param("homeworkId") Integer homeworkId, @Param("studentId") Integer studentId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 附件等子表清理后按作业删除历史版本行 */ + @Modifying + @Query("delete from HomeworkSubmission s where s.homeworkId = :homeworkId") + int deleteAllByHomeworkId(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java new file mode 100644 index 0000000..ecc75cb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HomeworkTemplateRepository extends JpaRepository { + Page findByTypeId(Integer typeId, Pageable pageable); + + /** + * 查询全部模板(按引用次数降序),分页。 + */ + Page findAllByOrderByRefCountDesc(Pageable pageable); + + /** + * 按类型ID查询全部模板(不分页,用于检查类型是否被引用)。 + */ + long countByTypeId(Integer typeId); +} diff --git a/src/main/java/com/yunhai/homework/repository/NotificationRepository.java b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java new file mode 100644 index 0000000..1066c10 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + * 站内通知仓储。 + */ +public interface NotificationRepository extends JpaRepository { + + Page findByReceiverId(Integer receiverId, Pageable pageable); + + Page findByReceiverIdAndType(Integer receiverId, String type, Pageable pageable); + + Page findByReceiverIdAndIsRead(Integer receiverId, Integer isRead, Pageable pageable); + + Page findByReceiverIdAndTypeAndIsRead(Integer receiverId, String type, + Integer isRead, Pageable pageable); + + long countByReceiverIdAndIsRead(Integer receiverId, Integer isRead); + + Optional findByIdAndReceiverId(Integer id, Integer receiverId); + + /** 截止提醒去重:同作业同类型同档位已发过即跳过(related_id 存档位:1=1天档 2=1小时档) */ + boolean existsByHomeworkIdAndTypeAndRelatedId(Integer homeworkId, String type, Integer relatedId); + + /** 截止提醒去重(不分档位) */ + boolean existsByHomeworkIdAndType(Integer homeworkId, String type); + + /** 全部已读(clearAutomatically 防同事务内缓存脏读) */ + @Modifying(clearAutomatically = true) + @Query("update Notification n set n.isRead = 1, n.readAt = :now " + + "where n.receiverId = :receiverId and n.isRead = 0") + int markAllRead(@Param("receiverId") Integer receiverId, @Param("now") java.time.LocalDateTime now); + + /** 按作业删除通知(作业删除级联) */ + void deleteByHomeworkId(Integer homeworkId); + + /** 指定作业的全部通知(清理/统计用) */ + List findByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java new file mode 100644 index 0000000..8e60f47 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SchClassTeach; +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; + +/** + * 班级任教关系快照仓储。 + */ +public interface SchClassTeachRepository extends JpaRepository { + + /** 指定学校指定教师的全部有效任教记录 */ + List findBySchoolIdAndTeaIdAndStatus(Integer schoolId, Integer teaId, Integer status); + + /** 指定学校指定教师去重学科编码(字典序由调用方保证) */ + @Query("select distinct t.subId from SchClassTeach t " + + "where t.schoolId = :schoolId and t.teaId = :teaId and t.status = 1") + List findDistinctSubIds(@Param("schoolId") Integer schoolId, @Param("teaId") Integer teaId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java new file mode 100644 index 0000000..18595bb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java @@ -0,0 +1,13 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsModule; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsModuleRepository extends JpaRepository { + List findAllByOrderByIdAsc(); + + /** 按模块标识查询(如 module_code="homework")。 */ + SmsModule findByModuleCode(String moduleCode); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java new file mode 100644 index 0000000..0d277a0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsSchoolConfig; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsSchoolConfigRepository extends JpaRepository { + Page findBySchoolNameContaining(String schoolName, Pageable pageable); + Page findAll(Pageable pageable); + List findBySchoolId(Integer schoolId); + List findBySchoolIdInAndModuleIdIn(List schoolIds, List moduleIds); + + /** + * 按学校ID和模块ID查询单条配置(判断是否已存在)。 + */ + SmsSchoolConfig findBySchoolIdAndModuleId(Integer schoolId, Integer moduleId); + + /** + * 按学校ID查询全部模块配置(用于"设置某校全部模块")。 + */ + List findBySchoolIdIn(List schoolIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java new file mode 100644 index 0000000..8848b14 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SubmissionAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; + +/** + * 提交附件仓储。 + */ +public interface SubmissionAttachmentRepository extends JpaRepository { + + List findBySubmissionIdOrderBySortAscIdAsc(Integer submissionId); + + /** 批量查 N 个提交行的附件(矩阵/版本列表避免 N+1) */ + List findBySubmissionIdIn(Collection submissionIds); + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SysDictRepository.java b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java new file mode 100644 index 0000000..c083840 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SysDict; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SysDictRepository extends JpaRepository { + List findByDictTypeOrderBySortOrderAsc(String dictType); + List findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag); + + /** 按类型+key 取单条字典(发文部门名称解析用) */ + Optional findByDictTypeAndDictKey(String dictType, String dictKey); +} diff --git a/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java new file mode 100644 index 0000000..a3a0f12 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.TemplateType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TemplateTypeRepository extends JpaRepository { + List findAllByOrderBySortOrderAsc(); + + /** + * 按类型名称查询(重名校验)。 + */ + boolean existsByTypeName(String typeName); + + /** + * 按类型名称查询(重名校验,排除自身)。 + */ + boolean existsByTypeNameAndIdNot(String typeName, Integer id); +} diff --git a/src/main/java/com/yunhai/homework/repository/UpFileRepository.java b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java new file mode 100644 index 0000000..421f142 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.UpFile; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +/** + * 附件文件仓储。 + */ +public interface UpFileRepository extends JpaRepository { + + /** + * 按主题类型和主题ID查询附件列表。 + */ + List findByTopicTypeAndTopicId(String topicType, Integer topicId); + + /** + * 按主题类型和多个主题ID批量查询附件(消除 N+1)。 + */ + List findByTopicTypeAndTopicIdIn(String topicType, List topicIds); + + /** + * 按主题类型和主题ID删除附件(作业删除时级联清理)。 + */ + void deleteByTopicTypeAndTopicId(String topicType, Integer topicId); +} diff --git a/src/main/java/com/yunhai/homework/service/IClassService.java b/src/main/java/com/yunhai/homework/service/IClassService.java new file mode 100644 index 0000000..2346cd9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IClassService.java @@ -0,0 +1,116 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; + +import java.util.List; + +/** + * 班级管理(bschclass)开放接口客户端门面。 + * 基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。 + * 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。 + */ +public interface IClassService { + + /** + * 获取指定学校全部教师(含所属组 GroupIds)。 + * 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}} + * + * @param schId 学校ID(即用户当前身份的 schId) + */ + List getSchTeaList(int schId); + + /** + * 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。 + * 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}} + */ + SchInfoDto getSchFirst(int schId); + + /** + * 学校学段(学制)列表:小学部 / 初中部…。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=} + */ + List getSchLenList(String schSerId); + + /** + * 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=} + */ + List getSchGradeList(String schSerId); + + /** + * 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。 + * 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=} + */ + List getSchClassList(String schSerId); + + /** + * 按校全部学生(圈人:全校范围展开)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=} + */ + List getSchStuList(String schSerId); + + /** + * 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=} + */ + List getGradeStuList(String schSerId, Integer gradeId); + + /** + * 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=} + */ + List getSubStuList(String schSerId, Integer schSystemId); + + /** + * 按班级批量查学生(圈人:班级范围一次多班)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3} + */ + List getClaArrStuList(List classIds); + + /** + * 按学生ID批量查(圈人:指定学员范围)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3} + */ + List getStuIdsList(List stuIds); + + /** + * 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。 + * 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。 + * 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=} + */ + List getTeaTree(Integer schId, boolean toUser); + + List getStuTree(Integer schId,boolean toUser); + + /** + * 学校学科列表(含学科编码、名称、排序号)。 + * 对应:GET {@code {bschclass}/oapi/SubInfo/GetSchSublist?SchID=} + */ + List getSchSubList(String schSerId); + + /** + * 指定教师的全部任教(校验教师归属该校;SchID 过滤学校、UserTeaSerID 过滤教师)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=&UserTeaSerID=} + */ + List getSchTeachList(String schSerId, Integer teaId); + + /** + * 指定班级的全部任教(班级维度:该班所有教师与学科)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchClaTeachlist?ClassID=} + */ + List getSchClaTeachList(Integer classId); + + /** + * 学校全部任教(全校所有教师的班级任教关系)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=} + */ + List getSchAllTeachList(String schSerId); +} diff --git a/src/main/java/com/yunhai/homework/service/IContactsService.java b/src/main/java/com/yunhai/homework/service/IContactsService.java new file mode 100644 index 0000000..0e1fdce --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IContactsService.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 联系组业务接口。 + * 对应前端 Contacts 组接口:AddGroup/GetGroup/EditGroup/DelGroup/SetContactsTea/GetContactsTea/GetGroupTree/GetDeptRoleGroupTree/Import/Export。 + */ +public interface IContactsService { + + /** + * 添加联系组。 + * + * @param name 组名 + * @param type 类型(1 自定义组, 2 明信片) + */ + void addGroup(String name, Integer type); + + /** + * 获取联系组列表(含每组人数,分页)。 + * + * @param page 页码(从1起) + * @param count 每页条数 + * @param type 类型(1 自定义组, 2 明信片) + * @return Map: { list, total } + */ + Map getGroup(Integer page, Integer count, Integer type); + + /** + * 编辑联系组。 + * + * @param id 组ID + * @param name 组名 + * @param type 类型 + */ + void editGroup(Integer id, String name, Integer type); + + /** + * 删除联系组(支持批量,同时删除组内成员)。 + * + * @param ids 逗号分隔的组ID + */ + void delGroup(String ids); + + /** + * 设置联系组教师(全量替换:先删后增)。 + * + * @param gid 组ID + * @param ids 逗号分隔的用户ID + */ + void setContactsTea(Integer gid, String ids); + + /** + * 获取联系组教师列表(分页)。 + * + * @param gid 组ID + * @param page 页码 + * @param count 每页条数 + * @return Map: { list, total } + */ + Map getContactsTea(String gid, Integer page, Integer count); + + /** + * 获取联系组树(组节点+用户节点,递归构建)。 + * + * @param type 类型(1 自定义组, 2 明信片) + * @return 树形结构 List<Map> + */ + List> getGroupTree(Integer type); + + /** + * 获取部门角色组树。 + * C# 从外部API获取部门树+用户角色映射。Java版预留。 + * + * @return 树形结构 List<Map> + */ + List> getDeptRoleGroupTree(); +} diff --git a/src/main/java/com/yunhai/homework/service/IDictService.java b/src/main/java/com/yunhai/homework/service/IDictService.java new file mode 100644 index 0000000..31ef515 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IDictService.java @@ -0,0 +1,75 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 系统字典业务接口。 + * 对应前端 Base 组接口:GetSysDic/GetDictMangeList/AddDeptDic/UpdateDeptDic/DelDept/OrdbyDicIds/GetDeptSync/GetSchModuleIsSms。 + */ +public interface IDictService { + + /** + * 获取系统字典(下拉框用)。 + * C# GetSysDic: Type=1 部门类别, 其余类型略。 + * + * @param type 字典类型编号(1=部门类别) + * @return List<Map> 每项 {k, v} + */ + List> getSysDic(Integer type); + + /** + * 获取系统字典管理列表(含 SerID/CodeID/Name/Des/Enable)。 + * + * @param type 字典类型编号(0=部门类别) + * @return List<Map> + */ + List> getDictMangeList(String type); + + /** + * 添加发文单位。 + * + * @param name 单位名称 + */ + void addDeptDic(String name); + + /** + * 更新发文单位。 + * + * @param serID 记录ID + * @param name 名称 + * @param enable 启用状态 + */ + void updateDeptDic(Integer serID, String name, Integer enable); + + /** + * 删除发文单位(校验是否被作业引用)。 + * + * @param serID 记录ID + */ + void delDept(Integer serID); + + /** + * 发文单位排序。 + * + * @param ids 逗号分隔的 SerID 列表(按前端顺序) + */ + void ordbyDicIds(String ids); + + /** + * 本校是否开通短信模块(前端 dict/Sync,用于"本校是否开通短信"判断)。 + * + * @param schId 当前学校ID(取当前登录用户角色所属学校) + * @return 开通返回 true,未开通/未配置返回 false + */ + Boolean getSchModuleIsSms(Integer schId); + + /** + * 按发文部门字典 key(CodeID)解析部门名称;未命中返回 null。 + * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 + * + * @param deptId 作业 deptId(存的是 sys_dict.dict_key) + * @return 部门名称;无匹配时返回 null + */ + String resolveDeptName(String deptId); +} diff --git a/src/main/java/com/yunhai/homework/service/IFileService.java b/src/main/java/com/yunhai/homework/service/IFileService.java new file mode 100644 index 0000000..1b01bbf --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IFileService.java @@ -0,0 +1,94 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.UpFile; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.model.homework.UpFileResultVo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件上传/下载/删除业务接口。 + */ +public interface IFileService { + /** + * 上传单个文件。 + * + * @param file 上传的文件 + * @param appType 应用类型(如 "homework") + * @param topicId 关联主题ID(可为空,作业保存后再绑定) + * @return 文件视图对象 + */ + UpFileVo upload(MultipartFile file, String appType, Integer topicId); + + /** + * 将已上传但未绑定主题的附件绑定到指定作业。 + * + * @param fileIds 文件记录ID列表 + * @param topicId 作业ID + */ + void bindToTopic(List fileIds, Integer topicId); + + /** + * 查询某主题下的附件列表。 + * + * @param appType 应用类型 + * @param topicId 主题ID + * @return 附件列表 + */ + List listByTopic(String appType, Integer topicId); + + /** + * 下载文件。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void download(Integer fileId, HttpServletResponse response); + + /** + * 在线预览文件(Content-Disposition: inline,按扩展名输出真实 Content-Type, + * 供前端 open-file-viewer 取 Blob / video 播放)。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void preview(Integer fileId, HttpServletResponse response); + + /** + * 删除文件(同时删除物理文件和数据库记录)。 + * + * @param fileId 文件记录ID + */ + void delete(Integer fileId); + + /** + * 打包下载多个附件为 ZIP。 + * + * @param files 附件记录列表(调用方按 fileIds 查好) + * @param zipName 压缩包显示名(自动追加 .zip) + * @param response HTTP 响应 + */ + void downloadZip(List files, String zipName, HttpServletResponse response); + + /** + * 按主题类型和主题ID打包下载附件为 ZIP(Controller 便捷方法,内部自行查附件列表)。 + * + * @param topicType 主题类型(如 "homework") + * @param topicId 主题ID + * @param zipName 压缩包显示名 + * @param response HTTP 响应 + */ + void downloadZipByTopic(String topicType, Integer topicId, String zipName, HttpServletResponse response); + + /** + * 批量上传图片(循环上传,返回每个文件的视图对象列表)。 + */ + List uploadImages(MultipartFile[] files, String appType); + + /** + * 上传单个文件并返回前端兼容的结果 VO(含 fileid/name/fileurl/host/type/size)。 + */ + UpFileResultVo uploadToResult(MultipartFile file, String appType); +} diff --git a/src/main/java/com/yunhai/homework/service/IHomeworkService.java b/src/main/java/com/yunhai/homework/service/IHomeworkService.java new file mode 100644 index 0000000..ed50167 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IHomeworkService.java @@ -0,0 +1,142 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业业务接口。 + */ +public interface IHomeworkService { + + /** + * 发作业:以当前登录用户为发布人,状态置为「已发布」入库。 + * 若 request.timingSendTime 不为空且晚于当前时间,则作为定时发送, + * 未到点前用户端收件箱不可见(到点后由查询过滤自然可见)。 + * + * @return 新建作业 ID(用于后续回填附件 TopicID 等) + */ + Integer publish(HomeworkRequest request); + + /** + * 编辑作业:按 id 更新标题/内容/类型/部门/置顶,发布人与状态保持不变。 + */ + void edit(Integer id, HomeworkRequest request); + + /** + * 批量删除作业:删除主记录并级联清理阅读记录、接收人、附件关联。 + */ + void deleteBatch(List ids); + + /** + * 撤回作业:仅已发布且未撤回的作业可撤回,置 isPulled=1。 + */ + void pull(Integer id); + + /** + * 置顶/取消置顶。 + */ + void setTopmost(Integer id, Integer topmost); + + /** + * 转发作业:基于源作业创建新作业(已发布),parentId 关联源作业。 + */ + Integer forward(Integer sourceId, HomeworkRequest request); + + /** + * 用户端列表:仅展示已发布且已到点的作业,并标记当前用户是否已读,附未读总数。 + * 返回 HomeworkPageResult(扩展 unreadCount)。 + */ + HomeworkPageResult userList(HomeworkQuery query, Pageable pageable); + + /** + * 管理端列表:按条件查询全部作业(管理员查看全部)。 + */ + BasePageResult adminList(HomeworkQuery query, Pageable pageable); + + /** + * 作业详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。 + */ + HomeworkVo detail(Integer id); + + /** + * 编辑回显详情(不标记已读)。 + */ + HomeworkVo getDetail(Integer id); + + /** + * 标记已读:为当前用户写入(幂等)该作业的阅读记录。 + */ + void markRead(Integer id); + + /** + * 一键全部已读(批量化):当前用户所有未读的已发布作业批量插入阅读记录。 + * @return 已读条数 + */ + int initializeRed(); + + /** + * 追加接收人(事务化):并入接收人列表并重算 allowcount。 + */ + void receiveUsers(Integer homeworkId, String users); + + /** + * 上一篇/下一篇作业ID(SQL 相邻查询)。 + * @param serid 当前作业ID + * @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列) + * @return {prevId, nextId} + */ + Map getNextPrev(Integer serid, String type); + + /** + * 获取某作业的阅读明细列表。 + */ + List getReadDetail(Integer homeworkId); + + /** + * 阅读情况分组(已读/未读)。 + */ + List> getReadDetailGrouped(Integer serid); + + /** + * 催收未读:枚举未读接收人,后续接入消息推送。 + */ + void expedite(Integer serid); + + /** + * 作业统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。 + */ + HomeworkStatisticsVo statistics(HomeworkQuery query, LocalDateTime start, LocalDateTime end); + + /** + * 作业详情视图(含附件、接收人信息),供 Controller 返回。 + */ + HomeworkDetailVo getDetailMap(Integer serid); + + /** + * 已发列表附件批量查询(消除 N+1)。 + */ + Map> batchFileIds(List homeworkIds); + + /** + * 查看作业详情视图(含附件、接收人)。 + * type=1 时先标记已读再返回,type=0 时直接返回不标记。 + */ + HomeworkDetailVo detailMap(Integer serid, Integer type); + + /** + * 一键全部已读并返回结果(count = 已读条数)。 + */ + InitializeRedVo initializeRedResult(); + + /** + * 批量下载作业全部附件(打包 zip):查作业标题后调文件域打包下载。 + */ + void downloadAllFiles(Integer dataid, HttpServletResponse response); +} diff --git a/src/main/java/com/yunhai/homework/service/ISmsService.java b/src/main/java/com/yunhai/homework/service/ISmsService.java new file mode 100644 index 0000000..09b9b5f --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ISmsService.java @@ -0,0 +1,77 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置业务接口。 + * 对应前端 setSms 组接口:getmodule/getgroup/getschsms/getsch/setschsms/setschallsms/setschmodulesms。 + */ +public interface ISmsService { + + /** + * 获取短信模块列表。 + * C# 从 AuthConfig.AppKeyMap 读取。Java版从 sms_module 表读取。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getModuleList(); + + /** + * 获取学校父类组列表。 + * C# 从外部BLL获取学校组。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchParentGroup(); + + /** + * 分页获取学校短信开关列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param schName 学校名称(模糊搜索) + * @return Map: { list, total } + */ + Map getSchIsSmsList(Integer pageSize, Integer pageIndex, String schName); + + /** + * 获取学校列表。 + * C# 从外部BLL获取学校列表。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchList(); + + /** + * 设置单校单模块短信开关。 + * + * @param schSerID 学校ID + * @param moduleId 模块ID + * @param isSms 是否开通 + */ + void setSchSms(Integer schSerID, Integer moduleId, Boolean isSms); + + /** + * 设置某校全部模块短信开关。 + * + * @param schSerID 学校ID + * @param isSms 是否开通 + */ + void setSchSmsAll(Integer schSerID, Boolean isSms); + + /** + * 批量设置多校多模块短信开关。 + * + * @param schSerIDs 逗号分隔的学校ID + * @param modules 逗号分隔的模块ID + * @param isSms 是否开通 + */ + void setSchModuleSms(String schSerIDs, String modules, Boolean isSms); + + /** + * 从 JSON 字符串解析并设置单校单模块短信开关。 + * 解析 SchSerID/ModuID/IsSms 三个字段。 + */ + void setSchSmsFromJson(String itemsModuJson); +} diff --git a/src/main/java/com/yunhai/homework/service/ITemplateService.java b/src/main/java/com/yunhai/homework/service/ITemplateService.java new file mode 100644 index 0000000..5594401 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ITemplateService.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板业务接口。 + * 对应前端 Temp 组接口:GetTypelist/EditType/DelType/GetTemplatelist/EditTemplate/DelTemplate/GetTemplateFist。 + */ +public interface ITemplateService { + + /** + * 获取模板类型列表(首项固定为"全部")。 + * + * @return List<Map> 每项 {TypeID, TypeName} + */ + List> getTypelist(); + + /** + * 新增/编辑模板类型。 + * + * @param typeid 类型ID(0=新增) + * @param typeName 类型名称 + */ + void editType(Integer typeid, String typeName); + + /** + * 删除模板类型(校验是否有模板引用此类型)。 + * + * @param typeid 类型ID + */ + void delType(Integer typeid); + + /** + * 分页获取模板列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param typeid 类型ID(0=全部) + * @return Map: { list, total } + */ + Map getTemplatelist(Integer pageSize, Integer pageIndex, Integer typeid); + + /** + * 新增/编辑模板。 + * + * @param serID 模板ID(null=新增) + * @param title 标题 + * @param content 内容 + * @param typeId 类型ID + * @param cover 封面文件ID + */ + void editTemplate(Integer serID, String title, String content, Integer typeId, Integer cover); + + /** + * 删除模板。 + * + * @param serID 模板ID + */ + void delTemplate(Integer serID); + + /** + * 获取单个模板详情。 + * + * @param serID 模板ID + * @return Map 模板详情 + */ + Map getTemplateFist(Integer serID); +} diff --git a/src/main/java/com/yunhai/homework/service/IUserCenterService.java b/src/main/java/com/yunhai/homework/service/IUserCenterService.java new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IUserCenterService.java @@ -0,0 +1,51 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 用户中心门面服务(对齐 UserBase {@code UserCenterService} 模式)。 + * 封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。 + */ +public interface IUserCenterService { + + /** + * 获取当前用户学校组及用户(树形结构)。 + * 组装规则(从原 BaseApiController.getSchoolAll 下沉): + * + * userbase 读取当前学校整棵组树(getGroupById(schId, withSub=true)),剔除学生组(type=8)及其子组; + * bschclass 获取该校全部教师(/oapi/DbClass/GetSchteaList?Group=schId),按 GroupIds 挂到对应组下, + * 无有效组(如所属组全被剔除)的教师兜底挂学校根节点; + * 扁平节点按 pid 递归组装为树(空组剔除,同 C# FormatUserTree)。 + * + * + * @param schId 学校ID(用户当前角色所属学校) + * @return 用户树形结构(组节点 type=1 / 教师节点 type=2) + */ + List> getSchoolAllTree(Integer schId); + + /** + * 获取指定组内用户(教师 + 管理员)。 + * 对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。 + * + * @param gid 组ID + * @param role 角色(默认 2=教师;1=管理员等) + * @return 组内用户列表 + */ + List> getGroupUser(Integer gid, Integer role); + + /** + * 获取当前用户可访问的模块树(左侧菜单)。 + * 从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。 + * + * @param userId 用户ID + * @param schId 学校ID(可为 null) + * @return 模块树(顶层 List,含 children 嵌套) + */ + List> getUserModuleTree(Integer userId, Integer schId); +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/service/impl/ClassService.java b/src/main/java/com/yunhai/homework/service/impl/ClassService.java new file mode 100644 index 0000000..8be5e2a --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/impl/ClassService.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.service.impl; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.service.IClassService; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班级管理(bschclass)开放接口实现。 + * bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。 + * 推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List
路径前缀:{@code Config.API_PATH + "/template/"}(原 homework/Template/ 三级路径已修正)。 + * 写操作一律 POST(原 EditType/DelTemplate 为 GET 写操作,已 POST 化)。 + */ +@RestController +@RequestMapping(value = Config.API_PATH + "/template/") +public class TemplateController { + + @Autowired + private ITemplateService templateService; + + /** + * 获取模板类型列表。 + */ + @GetMapping("Typelist") + public List> getTypelist() { + return templateService.getTypelist(); + } + + /** + * 新增/编辑模板类型(原 GET EditType,已 POST 化)。 + */ + @PostMapping("TypeEdit") + public void typeEdit( + @RequestParam("typeid") Integer typeid, + @RequestParam("TypeName") String typeName) { + templateService.editType(typeid, typeName); + } + + /** + * 删除模板类型(原 GET DelType,已 POST 化)。 + */ + @PostMapping("TypeDelete") + public void typeDelete(@RequestParam("typeid") Integer typeid) { + templateService.delType(typeid); + } + + /** + * 分页获取模板列表。 + */ + @GetMapping("List") + public Map list( + @RequestParam("PageSize") Integer pageSize, + @RequestParam("PageIndex") Integer pageIndex, + @RequestParam(value = "typeid", defaultValue = "0") Integer typeid) { + return templateService.getTemplatelist(pageSize, pageIndex, typeid); + } + + /** + * 新增/编辑模板。 + */ + @PostMapping("TemplateEdit") + public void templateEdit( + @RequestParam(value = "SerID", required = false) Integer serID, + @RequestParam("Title") String title, + @RequestParam("Contents") String contents, + @RequestParam(value = "Type", required = false) Integer type, + @RequestParam(value = "Cover", required = false) Integer cover) { + templateService.editTemplate(serID, title, contents, type, cover); + } + + /** + * 删除模板(原 GET DelTemplate,已 POST 化)。 + */ + @PostMapping("TemplateDelete") + public void templateDelete(@RequestParam("SerID") Integer serid) { + templateService.delTemplate(serid); + } + + /** + * 获取单个模板详情。 + */ + @GetMapping("TemplateFirst") + public Map templateFirst(@RequestParam("serid") Integer serid) { + return templateService.getTemplateFist(serid); + } +} diff --git a/src/main/java/com/yunhai/homework/entity/BaseEntity.java b/src/main/java/com/yunhai/homework/entity/BaseEntity.java new file mode 100644 index 0000000..5f98e8c --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/BaseEntity.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.common.listener.HomeworkAuditListener; +import lombok.Getter; +import lombok.Setter; + +import javax.persistence.*; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 所有实体的公共基类:自增主键 + 创建人/时间 + 更新人/时间(由 {@link HomeworkAuditListener} 自动填充)。 + */ +@MappedSuperclass +@Getter +@Setter +@EntityListeners(HomeworkAuditListener.class) +public abstract class BaseEntity implements Serializable { + + private static final long serialVersionUID = 1L; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + /** + * 创建人ID(平台用户ID) + */ + @Column(name = "create_by", updatable = false) + private String createBy; + + /** + * 创建时间 + */ + @Column(name = "create_time", updatable = false) + private LocalDateTime createTime; + + /** + * 更新人ID(平台用户ID) + */ + @Column(name = "update_by") + private String updateBy; + + /** + * 更新时间 + */ + @Column(name = "update_time") + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/ClassStudent.java b/src/main/java/com/yunhai/homework/entity/ClassStudent.java new file mode 100644 index 0000000..793e295 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ClassStudent.java @@ -0,0 +1,59 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 班级学生名册快照(PRD class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据),每日全量刷 + 手动触发; + * 同步不物理删,转出置 status=0。学生身份口径:用户中心数字ID(UserContext.getUserId()) + * ↔ student_id(SchStudent.UserStuSerID),名册仅存学生ID + 姓名快照。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "class_student", + uniqueConstraints = @UniqueConstraint(name = "uk_class_student", columnNames = {"class_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ClassStudent extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; + + /** 年级名快照 */ + @Column(name = "grade_name", length = 50) + private String gradeName; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 在班状态:1 在班 / 0 已转出退课 */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** 离班时间 */ + @Column(name = "leave_at") + private LocalDateTime leaveAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroup.java b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java new file mode 100644 index 0000000..a002e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 联系组。对应原 .NET Contacts_Group 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroup extends BaseEntity { + + /** 组名称 */ + @Column(name = "group_name", length = 200) + private String groupName; + + /** 创建人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "creator_id") + private Integer creatorId; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java new file mode 100644 index 0000000..eb07763 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 联系组-用户关联。对应原 .NET Contacts_Tea 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group_user", + uniqueConstraints = @UniqueConstraint(name = "uk_group_user", + columnNames = {"group_id", "user_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroupUser extends BaseEntity { + + /** 联系组ID */ + @Column(name = "group_id", nullable = false) + private Integer groupId; + + /** 用户ID(基础平台用户ID) */ + @Column(name = "user_id", nullable = false) + private Integer userId; + + /** 用户姓名 */ + @Column(name = "user_name", length = 64) + private String userName; +} diff --git a/src/main/java/com/yunhai/homework/entity/Homework.java b/src/main/java/com/yunhai/homework/entity/Homework.java new file mode 100644 index 0000000..6e615f2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Homework.java @@ -0,0 +1,208 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 课程作业主表。对应原 .NET Notify_Message(已剔除短信/模板/门户等无关字段)。 + * + * 阅读状态不落主表:采用「懒标记」模型——读者首次查看时写入 {@link HomeworkRead}, + * 不存在阅读记录即视为未读,从而无需在发作业时枚举全校用户预插接收行。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Homework extends BaseEntity { + + /** + * 作业标题 + */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** + * 学校ID(多租户隔离:发布时从当前用户角色填充,查询时按校过滤)。 + */ + @Column(name = "sch_id") + private Integer schId; + + /** + * 作业内容 + */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** + * 作业类型:1 普通 / 2 重要(见 {@link com.yunhai.homework.enums.HomeworkTypeEnum}) + */ + @Column(name = "type", nullable = false) + private Integer type = 1; + + /** + * 发布人ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "publisher_id") + private Integer publisherId; + + /** + * 发布人姓名 + */ + @Column(name = "publisher_name", length = 64) + private String publisherName; + + /** + * 发布部门ID(原 DeptID) + */ + @Column(name = "dept_id", length = 64) + private String deptId; + + /** + * 发布部门名称 + */ + @Column(name = "dept_name", length = 128) + private String deptName; + + /** + * 状态:1 已发布(无草稿态,见 {@link com.yunhai.homework.enums.HomeworkStatusEnum}) + */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** + * 是否置顶:0 否 / 1 是(原 topmost) + */ + @Column(name = "topmost") + private Integer topmost = 0; + + // ==================== 迁移补全字段(对应 C# Notify_Message) ==================== + + /** + * 接收人列表(JSON 数组,如 ["10012","10013"] 或接收组ID列表)。 + * 对应 C# Notify_Message.ReceiveList + */ + @Column(name = "receive_list", columnDefinition = "LONGTEXT") + private String receiveList; + + /** + * 密码发送(对应 C# Notify_Message.PwdSend) + */ + @Column(name = "pwd_send", length = 255) + private String pwdSend; + + /** + * 抄送人列表(对应 C# Notify_Message.ChaoSend) + */ + @Column(name = "chao_send", length = 2000) + private String chaoSend; + + /** + * 已读数(发送时初始化为 0,也可由 HomeworkRead 表 COUNT 实时计算)。 + * 对应 C# Notify_Message.readcount + */ + @Column(name = "read_count") + private Integer readCount = 0; + + /** + * 应读数(接收人总数)。对应 C# Notify_Message.allowcount + */ + @Column(name = "allow_count") + private Integer allowCount = 0; + + /** + * 父作业ID(转发时关联原作业)。对应 C# Notify_Message.fkid + */ + @Column(name = "parent_id") + private Integer parentId; + + /** + * 回复ID。对应 C# Notify_Message.reid + */ + @Column(name = "reply_id", length = 255) + private String replyId; + + /** + * 是否撤回:0 否 / 1 是。对应 C# Notify_Message.ispull + */ + @Column(name = "is_pulled") + private Integer isPulled = 0; + + /** + * 定时发送时间(到时间后自动发布)。对应 C# Notify_Message.DsfsTime。 + * PRD 语义中的 scheduled_at 复用本列。 + */ + @Column(name = "timing_send_time") + private LocalDateTime timingSendTime; + + /** + * 短信发送标记:0 不发短信 / 1 发短信。对应 C# Notify_Message.dxbit + */ + @Column(name = "sms_flag") + private Integer smsFlag = 0; + + // ==================== 课堂作业 V3(PRD V1.0)扩展列 ==================== + + /** + * 学科(字典值)。 + */ + @Column(name = "subject", length = 50) + private String subject; + + /** + * 截止时间(精确到分钟;空 = 仅查阅型,不设截止,到点关闭任务跳过)。 + */ + @Column(name = "deadline_at") + private LocalDateTime deadlineAt; + + /** + * 作业流程状态(PRD 五态,见 {@link com.yunhai.homework.enums.HwStatusEnum})。 + * notice 原 status 列继续承载发布/撤回语义,两者并存。 + */ + @Column(name = "hw_status", nullable = false, length = 20) + private String hwStatus = com.yunhai.homework.enums.HwStatusEnum.DRAFT_UNPUBLISHED.getCode(); + + /** + * 发布时间(立即发布或定时到点发布时写入)。 + */ + @Column(name = "published_at") + private LocalDateTime publishedAt; + + /** + * 提交方式,JSON 数组文本,如 ["image","attach"]。 + * 取值:text / image / attach / none(PRD 的 图片/文件/两者 映射为 + * ["image"] / ["attach"] / ["image","attach"])。 + */ + @Column(name = "submit_type", length = 100) + private String submitType; + + /** + * 截止后是否允许逾期提交:0 否 / 1 是。 + */ + @Column(name = "allow_late", nullable = false) + private Integer allowLate = 0; + + /** + * 提醒设置:截止前 1 天提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1d", nullable = false) + private Integer remind1d = 1; + + /** + * 提醒设置:截止前 1 小时提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1h", nullable = false) + private Integer remind1h = 1; + + /** + * 归档时间(教师手动归档时写入;归档后只读,所有提醒任务跳过)。 + */ + @Column(name = "archived_at") + private LocalDateTime archivedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java new file mode 100644 index 0000000..6fc7804 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业附件表(PRD homework_attachment)。文件实体仍存 up_file(file_id 关联 + file_url 冗余)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_attachment") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkAttachment extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** up_file.id(可空:允许外部 URL 附件) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file / ... */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号(越小越靠前) */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkClass.java b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java new file mode 100644 index 0000000..c01cefb --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 作业-班级关联表(PRD homework_class)。 + * 发布/编辑时由 receiveList("all" 或班级 id 串)解析写入,是应交名单与统计的权威关联。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_class", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_class", columnNames = {"homework_id", "class_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkClass extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkRead.java b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java new file mode 100644 index 0000000..35b773a --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.time.LocalDateTime; + +/** + * 作业阅读记录(懒标记)。对应原 .NET Notify_ReceiveList 的「逐用户阅读状态」语义, + * 但仅在读者首次查看时落库,避免发作业时预插全员。 + * + * 唯一约束 (homework_id, reader_id):同一用户对同一作业只记一条已读。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read", + uniqueConstraints = @UniqueConstraint( + name = "uk_homework_reader", + columnNames = {"homework_id", "reader_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkRead extends BaseEntity { + + /** + * 作业ID(关联 homework.id) + */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** + * 读者ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "reader_id", nullable = false) + private Integer readerId; + + /** + * 读者姓名 + */ + @Column(name = "reader_name", length = 64) + private String readerName; + + /** + * 阅读时间 + */ + @Column(name = "read_time") + private LocalDateTime readTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java new file mode 100644 index 0000000..8179e79 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java @@ -0,0 +1,58 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 学生作业阅读记录(PRD homework_read_record)。 + * + * 学生维度,含首次/最近阅读时间,与 notice 语义的 {@link HomeworkRead}(教师/阅读者已读)互不影响。 + * 行仅在判定已读时写入;未读名单 = 名册左连本表(无记录或 is_read=0)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read_record", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student", columnNames = {"homework_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReadRecord extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", length = 64) + private String classId; + + /** 阅读状态:0 UNREAD / 1 READ */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 首次阅读时间(写入后不再变更) */ + @Column(name = "first_read_at") + private LocalDateTime firstReadAt; + + /** 最近阅读时间(每次上报刷新) */ + @Column(name = "last_read_at") + private LocalDateTime lastReadAt; + + /** 阅读次数 */ + @Column(name = "read_count", nullable = false) + private Integer readCount = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java new file mode 100644 index 0000000..97e068b --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java @@ -0,0 +1,45 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 作业接收人关联表(替代旧 receive_list JSON 字符串存储)。 + * + * 每条记录代表一个作业的一个接收人(用户ID + 姓名快照,不含账号)。 + * 全员作业(receive_list="ALL")不写明细行, + * 读取时按 receive_list="ALL" 标识走旧逻辑兜底。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_receiver", + indexes = { + @Index(name = "idx_homework_id", columnList = "homework_id"), + @Index(name = "idx_receiver", columnList = "receiver_id") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReceiver extends BaseEntity { + + /** 作业ID(关联 homework.id) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 接收人ID(用户中心数字ID) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收类型:1 接收 / 2 抄送 */ + @Column(name = "receive_type", nullable = false) + private Integer receiveType = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java new file mode 100644 index 0000000..4626044 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java @@ -0,0 +1,98 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.enums.ReviewStatusEnum; +import com.yunhai.homework.enums.SubmitStatusEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 作业提交表,多版本建模(PRD homework_submission)。 + * + * 同一学生 (homework_id, student_id) 下 version 单调递增;提交/打回重交/撤回均新增行, + * 旧行 is_latest 置 0 保留历史;is_latest=1 的行是当前有效版本。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_submission", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student_ver", + columnNames = {"homework_id", "student_id", "version"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkSubmission extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", nullable = false, length = 50) + private String className; + + /** 提交版本,同一学生单调递增(撤回/重交均 +1) */ + @Column(name = "version", nullable = false) + private Integer version = 1; + + /** 是否当前版本:1 是 / 0 历史 */ + @Column(name = "is_latest", nullable = false) + private Integer isLatest = 1; + + /** 提交状态(见 {@link SubmitStatusEnum}) */ + @Column(name = "status", nullable = false, length = 20) + private String status = SubmitStatusEnum.SUBMITTED.getCode(); + + /** 是否逾期:0 否 / 1 是 */ + @Column(name = "is_late", nullable = false) + private Integer isLate = 0; + + /** 提交时间(NOT_SUBMITTED 空行为空) */ + @Column(name = "submitted_at") + private LocalDateTime submittedAt; + + /** 文本作业内容 */ + @Column(name = "text", columnDefinition = "TEXT") + private String text; + + /** 评分 */ + @Column(name = "score", precision = 6, scale = 2) + private BigDecimal score; + + /** 评语(打回时 = 订正原因,必填) */ + @Column(name = "comment", columnDefinition = "TEXT") + private String comment; + + /** 批阅状态(见 {@link ReviewStatusEnum}) */ + @Column(name = "review_status", nullable = false, length = 20) + private String reviewStatus = ReviewStatusEnum.PENDING.getCode(); + + /** 批阅人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "reviewed_by") + private Integer reviewedBy; + + /** 批阅人姓名快照 */ + @Column(name = "reviewed_by_name", length = 64) + private String reviewedByName; + + /** 批阅时间 */ + @Column(name = "reviewed_at") + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java new file mode 100644 index 0000000..f088c92 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板。对应原 .NET Notify_Template 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_template") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkTemplate extends BaseEntity { + + /** 模板标题 */ + @Column(name = "title", length = 200) + private String title; + + /** 模板内容(HTML) */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** 封面图片文件ID */ + @Column(name = "cover") + private Integer cover; + + /** 引用次数 */ + @Column(name = "ref_count") + private Integer refCount = 0; + + /** 模板类型ID */ + @Column(name = "type_id") + private Integer typeId; +} diff --git a/src/main/java/com/yunhai/homework/entity/Notification.java b/src/main/java/com/yunhai/homework/entity/Notification.java new file mode 100644 index 0000000..2a931a1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Notification.java @@ -0,0 +1,71 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 站内通知(PRD notification)。六类通知的统一落表 + 已读状态。 + * 通知已读独立于作业阅读记录(本表 is_read 只反映通知列表已读)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "notification", + indexes = { + @Index(name = "idx_receiver", columnList = "receiver_id, is_read"), + @Index(name = "idx_hw_type", columnList = "homework_id, type") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Notification extends BaseEntity { + + /** 通知类型(见 {@link com.yunhai.homework.enums.NotificationTypeEnum}) */ + @Column(name = "type", nullable = false, length = 30) + private String type; + + /** 关联作业ID */ + @Column(name = "homework_id") + private Integer homeworkId; + + /** 关联业务ID(如 submissionId) */ + @Column(name = "related_id") + private Integer relatedId; + + /** 通知标题 */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** 通知内容 */ + @Column(name = "content", length = 1000) + private String content; + + /** 接收人ID(学生 = student_id,教师 = 用户中心数字ID,同一 ID 空间) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收人角色:student / teacher */ + @Column(name = "receiver_role", nullable = false, length = 20) + private String receiverRole = "student"; + + /** 是否已读:0 未读 / 1 已读 */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 已读时间 */ + @Column(name = "read_at") + private LocalDateTime readAt; + + /** 发送通道:inapp 站内 / sms 短信 */ + @Column(name = "send_channel", nullable = false, length = 20) + private String sendChannel = "inapp"; +} diff --git a/src/main/java/com/yunhai/homework/entity/SchClassTeach.java b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java new file mode 100644 index 0000000..d1a091f --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java @@ -0,0 +1,46 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 班级任教关系快照(bschclass sch_class_teach 本地镜像,PRD 口径同 class_student)。 + * + * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据);bschclass 任教接口 + * 不可用时,任教学科链路降级读本表。教师口径:用户中心数字ID(UserContext.getUserId()); + * class_id 与 class_student.class_id 同一取值域。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sch_class_teach", + uniqueConstraints = @UniqueConstraint(name = "uk_sch_class_teach", columnNames = {"school_id", "class_id", "tea_id", "sub_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SchClassTeach extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID(与 class_student.class_id 同一取值域) */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 教师ID(用户中心数字ID) */ + @Column(name = "tea_id", nullable = false) + private Integer teaId; + + /** 学科编码(bschclass SubID;本地无字典时编码即展示名) */ + @Column(name = "sub_id", nullable = false, length = 64) + private String subId; + + /** 有效状态:1 有效 / 0 失效 */ + @Column(name = "status", nullable = false) + private Integer status = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsModule.java b/src/main/java/com/yunhai/homework/entity/SmsModule.java new file mode 100644 index 0000000..3f4d444 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsModule.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 短信模块配置。对应原 .NET SchSms 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_module") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsModule extends BaseEntity { + + /** 模块名称 */ + @Column(name = "module_name", length = 100) + private String moduleName; + + /** 模块标识 */ + @Column(name = "module_code", length = 50) + private String moduleCode; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java new file mode 100644 index 0000000..428d271 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java @@ -0,0 +1,37 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 学校短信开通配置。对应原 .NET SchSms_Set 表。 + * 记录某学校某模块是否开通短信。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_school_config") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsSchoolConfig extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 学校名称 */ + @Column(name = "school_name", length = 200) + private String schoolName; + + /** 模块ID */ + @Column(name = "module_id") + private Integer moduleId; + + /** 是否开通短信 */ + @Column(name = "is_sms") + private Boolean isSms = false; +} diff --git a/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java new file mode 100644 index 0000000..85df7ba --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java @@ -0,0 +1,54 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 提交附件表(PRD submission_attachment)。替代 JSON 方案,支撑矩阵缩略图与批阅大图预览。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "submission_attachment", + indexes = @Index(name = "idx_sub", columnList = "submission_id")) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SubmissionAttachment extends BaseEntity { + + /** 提交行ID(homework_submission.id) */ + @Column(name = "submission_id", nullable = false) + private Integer submissionId; + + /** 作业ID(冗余,便于统计与清理) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + @Column(name = "student_id") + private Integer studentId; + + /** up_file.id(可空:允许外部 URL) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号 */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/SysDict.java b/src/main/java/com/yunhai/homework/entity/SysDict.java new file mode 100644 index 0000000..3091084 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SysDict.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 系统字典(发文部门等)。对应原 .NET Common_DeptDic 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sys_dict") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SysDict extends BaseEntity { + + /** 字典类型(如 "dept"=发文部门) */ + @Column(name = "dict_type", length = 50) + private String dictType; + + /** 字典 key(原 CodeID) */ + @Column(name = "dict_key", length = 100) + private String dictKey; + + /** 字典 value(显示名称,原 Name) */ + @Column(name = "dict_value", length = 200) + private String dictValue; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; + + /** 是否启用:0 禁用 / 1 启用(原 Enable) */ + @Column(name = "enable_flag") + private Integer enableFlag = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/TemplateType.java b/src/main/java/com/yunhai/homework/entity/TemplateType.java new file mode 100644 index 0000000..aab8163 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/TemplateType.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板类型。对应原 .NET Notify_TemplateType 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "template_type") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class TemplateType extends BaseEntity { + + /** 类型名称 */ + @Column(name = "type_name", length = 100) + private String typeName; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/UpFile.java b/src/main/java/com/yunhai/homework/entity/UpFile.java new file mode 100644 index 0000000..38248c7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/UpFile.java @@ -0,0 +1,47 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 附件文件记录。对应原 .NET Common_UpFiles 表。 + * + * 存储文件元信息(真实文件名、存储路径、大小、类型),物理文件存储在 + * application.yml 的 file.upload-path 目录下。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "up_file") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class UpFile extends BaseEntity { + + /** 应用类型(如 "homework"),用于区分不同业务的附件 */ + @Column(name = "topic_type", length = 50) + private String topicType; + + /** 关联主题ID(如作业 serid) */ + @Column(name = "topic_id") + private Integer topicId; + + /** 文件真实名称(用户上传时的原始文件名) */ + @Column(name = "file_true_name", length = 255) + private String fileTrueName; + + /** 文件存储路径(相对路径,如 homework/20260823/uuid.pdf) */ + @Column(name = "file_name", length = 500) + private String fileName; + + /** 文件大小(字节) */ + @Column(name = "file_size") + private Integer fileSize; + + /** 文件后缀名(如 pdf, docx, jpg) */ + @Column(name = "file_type", length = 20) + private String fileType; +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java new file mode 100644 index 0000000..124bec9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业状态。无草稿态:发作业一律置为「已发布」, + * 定时发送通过 timingSendTime + 查询侧「已到点」过滤控制可见性。 + * 对应原 .NET Notify_Message.status(原系统已发布=2,此处归一化为 1)。 + */ +@Getter +public enum HomeworkStatusEnum { + + PUBLISHED(1, "已发布"); + + private final int code; + private final String desc; + + HomeworkStatusEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkStatusEnum of(Integer code) { + if (code == null) { + return PUBLISHED; + } + for (HomeworkStatusEnum e : values()) { + if (e.code == code) { + return e; + } + } + return PUBLISHED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java new file mode 100644 index 0000000..077396d --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业类型。对应原 .NET Notify_Message.type(原系统作业类=2,此处归一化)。 + */ +@Getter +public enum HomeworkTypeEnum { + + NORMAL(1, "普通作业"), + IMPORTANT(2, "重要作业"); + + private final int code; + private final String desc; + + HomeworkTypeEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkTypeEnum of(Integer code) { + if (code == null) { + return NORMAL; + } + for (HomeworkTypeEnum e : values()) { + if (e.code == code) { + return e; + } + } + return NORMAL; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java new file mode 100644 index 0000000..8b10083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业流程状态(PRD 五态,存 homework.hw_status)。 + * + * 与 notice 原 status(Integer,发布/撤回语义)并存:本枚举承载课堂作业业务流转, + * 流转收敛在 HomeworkStateService,条件更新保证幂等。 + */ +@Getter +public enum HwStatusEnum { + + /** 草稿 - 未发布 */ + DRAFT_UNPUBLISHED("DRAFT_UNPUBLISHED", "草稿-未发布"), + /** 草稿 - 定时待发布 */ + DRAFT_SCHEDULED("DRAFT_SCHEDULED", "草稿-定时待发布"), + /** 已发布 - 进行中 */ + PUBLISHED("PUBLISHED", "已发布-进行中"), + /** 已截止 */ + CLOSED("CLOSED", "已截止"), + /** 已归档(只读,不再触发任何提醒) */ + ARCHIVED("ARCHIVED", "已归档"); + + private final String code; + private final String desc; + + HwStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HwStatusEnum of(String code) { + if (code == null) { + return DRAFT_UNPUBLISHED; + } + for (HwStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return DRAFT_UNPUBLISHED; + } + + public boolean isDraft() { + return this == DRAFT_UNPUBLISHED || this == DRAFT_SCHEDULED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java new file mode 100644 index 0000000..df75af1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 站内通知类型(PRD 六类,存 notification.type)。 + */ +@Getter +public enum NotificationTypeEnum { + + /** 作业发布(对应班级学生) */ + HOMEWORK_PUBLISHED("HOMEWORK_PUBLISHED", "作业发布"), + /** 截止提醒(截止前 1 天 / 1 小时) */ + DEADLINE_REMINDER("DEADLINE_REMINDER", "截止提醒"), + /** 未交提醒(教师手动) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未交提醒"), + /** 未读提醒(教师手动) */ + NOT_READ("NOT_READ", "未读提醒"), + /** 批阅完成 */ + REVIEW_DONE("REVIEW_DONE", "批阅完成"), + /** 打回通知 */ + RETURNED("RETURNED", "打回通知"), + /** + * 定时发布失败回退(PRD §17.1 教师泳道要求通知教师,但 PRD 枚举表未列—— + * 按 classwork-homework-plan-v3 §13-2 建议补充,待产品确认命名)。 + */ + SCHEDULE_FAILED("SCHEDULE_FAILED", "定时发布失败"), + /** + * 截止时间变更(PRD §17.1 延长截止要求通知学生,PRD 枚举表未列——同上补充)。 + */ + DEADLINE_CHANGED("DEADLINE_CHANGED", "截止时间变更"); + + private final String code; + private final String desc; + + NotificationTypeEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static NotificationTypeEnum of(String code) { + if (code == null) { + return null; + } + for (NotificationTypeEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return null; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java new file mode 100644 index 0000000..031eddc --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java @@ -0,0 +1,39 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 批阅状态(PRD 三态,存 homework_submission.review_status)。 + * 与 SubmitStatusEnum 同源联动:批阅保存 → status=REVIEWED + review_status=REVIEWED; + * 打回 → status=RETURNED + review_status=RETURNED。 + */ +@Getter +public enum ReviewStatusEnum { + + /** 待批阅 */ + PENDING("PENDING", "待批阅"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"), + /** 已打回 */ + RETURNED("RETURNED", "已打回"); + + private final String code; + private final String desc; + + ReviewStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static ReviewStatusEnum of(String code) { + if (code == null) { + return PENDING; + } + for (ReviewStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return PENDING; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java new file mode 100644 index 0000000..7d6aaca --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java @@ -0,0 +1,50 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 学生提交状态(PRD 五态,存 homework_submission.status)。 + * + * NOT_SUBMITTED 空行仅由「撤回」产生;未提交学生本身不落行(统计时由名册左连补位)。 + */ +@Getter +public enum SubmitStatusEnum { + + /** 未提交(撤回后产生的空提交行) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未提交"), + /** 已提交(含打回后重新提交) */ + SUBMITTED("SUBMITTED", "已提交"), + /** 逾期提交(截止后提交,is_late=1) */ + LATE_SUBMITTED("LATE_SUBMITTED", "逾期提交"), + /** 已打回(待重新提交) */ + RETURNED("RETURNED", "已打回"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"); + + private final String code; + private final String desc; + + SubmitStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static SubmitStatusEnum of(String code) { + if (code == null) { + return NOT_SUBMITTED; + } + for (SubmitStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return NOT_SUBMITTED; + } + + /** + * 是否计入「已交」口径:打回未重交也算已交(PRD 学生矩阵口径)。 + */ + public boolean countsAsSubmitted() { + return this != NOT_SUBMITTED; + } +} diff --git a/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java new file mode 100644 index 0000000..5377254 --- /dev/null +++ b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java @@ -0,0 +1,161 @@ +package com.yunhai.homework.job; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.enums.HwStatusEnum; +import com.yunhai.homework.enums.NotificationTypeEnum; +import com.yunhai.homework.repository.HomeworkClassRepository; +import com.yunhai.homework.repository.HomeworkRepository; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.HomeworkStateService; +import com.yunhai.homework.service.impl.NotificationService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 课堂作业定时任务(PRD §17.1 调度泳道)。 + * + * 全部基于状态机 CAS 条件更新,天然幂等,可安全重复执行; + * ARCHIVED 作业被显式跳过(归档后不再触发任何提醒)。 + */ +@Component +public class HomeworkScheduledJobs { + + private static final Logger log = LoggerFactory.getLogger(HomeworkScheduledJobs.class); + + @Autowired + private HomeworkRepository homeworkRepository; + + @Autowired + private HomeworkClassRepository homeworkClassRepository; + + @Autowired + private HomeworkStateService stateService; + + @Autowired + private NotificationService notificationService; + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + /** + * 定时发布:每分钟扫描到点的 DRAFT_SCHEDULED 作业 → PUBLISHED + 作业发布通知; + * 失败回退 DRAFT_UNPUBLISHED 并站内通知教师。 + */ + @Scheduled(cron = "0 * * * * ?") + public void scheduledPublish() { + List due = homeworkRepository + .findByHwStatusAndTimingSendTimeLessThanEqual(HwStatusEnum.DRAFT_SCHEDULED.getCode(), + LocalDateTime.now()); + if (due.isEmpty()) { + return; + } + log.info("[scheduledPublish] 到点作业 {} 个", due.size()); + for (Homework homework : due) { + Integer id = homework.getId(); + try { + teacherHomeworkV2Service.publishInternal(id, HwStatusEnum.DRAFT_SCHEDULED); + log.info("[scheduledPublish] 发布成功 homeworkId={}", id); + } catch (Exception e) { + log.error("[scheduledPublish] 发布失败,回退草稿 homeworkId={}", id, e); + try { + boolean rolledBack = stateService.rollbackFromSchedule(id); + if (rolledBack) { + notificationService.send(NotificationTypeEnum.SCHEDULE_FAILED, id, null, + homework.getPublisherId(), homework.getPublisherName(), + NotificationService.ROLE_TEACHER, + "定时发布失败:《" + homework.getTitle() + "》", + "定时发布任务执行异常,作业已回退为「草稿-未发布」,请重新发布。"); + } + } catch (Exception ex) { + log.error("[scheduledPublish] 回退失败 homeworkId={}", id, ex); + } + } + } + } + + /** + * 到点截止:每分钟扫描已过截止时间的 PUBLISHED 作业 → CLOSED(无对应通知类型,不推送)。 + */ + @Scheduled(cron = "30 * * * * ?") + public void deadlineClose() { + List overdue = homeworkRepository + .findByHwStatusAndDeadlineAtBefore(HwStatusEnum.PUBLISHED.getCode(), LocalDateTime.now()); + for (Homework homework : overdue) { + if (stateService.cas(homework.getId(), HwStatusEnum.PUBLISHED, HwStatusEnum.CLOSED)) { + log.info("[deadlineClose] 作业已截止 homeworkId={}", homework.getId()); + } + } + } + + /** + * 截止提醒:每分钟扫描截止时间在 24h 内的 PUBLISHED 作业, + * 命中「1 天档 / 1 小时档」窗口且开关开启时向未交学生推送 DEADLINE_REMINDER; + * 档位去重(related_id:1=1 天档 2=1 小时档),每作业每档至多发一轮。 + */ + @Scheduled(cron = "40 * * * * ?") + public void deadlineReminder() { + LocalDateTime now = LocalDateTime.now(); + List window = homeworkRepository + .findByHwStatusAndDeadlineAtBetween(HwStatusEnum.PUBLISHED.getCode(), now, now.plusHours(24)); + for (Homework homework : window) { + if (homework.getDeadlineAt() == null) { + continue; + } + // 1 小时档:截止前 [55, 60) 分钟窗口 + if (homework.getRemind1h() != null && homework.getRemind1h() == 1 + && inWindow(homework.getDeadlineAt(), now, 55, 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 2)) { + sendReminder(homework, 2, "1 小时"); + } + // 1 天档:截止前 [23, 24) 小时窗口 + if (homework.getRemind1d() != null && homework.getRemind1d() == 1 + && inWindow(homework.getDeadlineAt(), now, 23 * 60, 24 * 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 1)) { + sendReminder(homework, 1, "1 天"); + } + } + } + + private boolean inWindow(LocalDateTime deadline, LocalDateTime now, int fromMinutes, int toMinutes) { + long minutes = java.time.Duration.between(now, deadline).toMinutes(); + return minutes >= fromMinutes && minutes < toMinutes; + } + + private void sendReminder(Homework homework, int slot, String slotDesc) { + // 通知接收人直接使用学生ID(名册口径:student_id = 平台用户ID) + List targets = teacherHomeworkV2Service.notSubmittedStudentIds(homework.getId()).stream() + .distinct() + .collect(Collectors.toList()); + if (targets.isEmpty()) { + return; + } + int sent = notificationService.sendBatch(NotificationTypeEnum.DEADLINE_REMINDER, homework.getId(), slot, + NotificationService.distinct(targets), + NotificationService.deadlineReminderTitle(homework), + NotificationService.deadlineReminderContent(homework) + "(剩余不足 " + slotDesc + ")"); + log.info("[deadlineReminder] homeworkId={}, slot={}, sent={}", homework.getId(), slot, sent); + } + + /** + * 名册同步:每日 02:00 全量刷新 class_student(bschclass 接口就绪前仅日志)。 + */ + @Scheduled(cron = "${homework.sync.class-data-cron:0 0 2 * * ?}") + public void classDataSync() { + try { + classStudentService.syncAll(); + } catch (Exception e) { + log.error("[classDataSync] 名册同步失败", e); + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageQuery.java b/src/main/java/com/yunhai/homework/model/BasePageQuery.java new file mode 100644 index 0000000..7f46326 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageQuery.java @@ -0,0 +1,14 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +/** + * 统一分页查询入参。pageIndex 从 1 起。 + */ +@Data +public class BasePageQuery { + private int pageIndex = 1; + private int pageSize = 10; + /** 通用模糊搜索关键词 */ + private String keyword; +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageResult.java b/src/main/java/com/yunhai/homework/model/BasePageResult.java new file mode 100644 index 0000000..058c96c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageResult.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +import java.util.List; + +/** + * 统一分页响应外壳。 + */ +@Data +public class BasePageResult { + /** 总记录数 */ + private int total; + /** 当前页数据 */ + private List content; + /** 当前页码(从 1 起) */ + private int pageIndex; + /** 每页条数 */ + private int pageSize; + /** 总页数 */ + private int totalPages; + /** 是否还有下一页 */ + private boolean hasNext; + + public BasePageResult(List content, int total, int pageIndex, int pageSize, int totalPages, boolean hasNext) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + } +} diff --git a/src/main/java/com/yunhai/homework/model/SchTeaList.java b/src/main/java/com/yunhai/homework/model/SchTeaList.java new file mode 100644 index 0000000..80f9b9c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/SchTeaList.java @@ -0,0 +1,106 @@ +package com.yunhai.homework.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * 班级管理接口-教师(bschclass {@code /oapi/DbClass/GetSchteaList} 响应项)。 + * 对应 C# BestSchool.Entity.Model.SchTeaList。 + */ +public class SchTeaList { + + /** 学校 GUID(SchSerID) */ + @JsonProperty("SchSerID") + private String schSerID; + + @JsonProperty("TeaTel") + private String teaTel; + + /** 教师姓名 */ + @JsonProperty("TeaName") + private String teaName; + + /** 教师用户ID */ + @JsonProperty("UserTeaSerID") + private Integer userTeaSerID; + + @JsonProperty("TeaIDCard") + private String teaIDCard; + + /** 教师工号 */ + @JsonProperty("TeaNo") + private String teaNo; + + /** 教师所属组ID列表(兼容旧字段 GroupIds) */ + @JsonProperty("GroupIds") + private List groupIds; + + /** 教师所属组ID(接口现行字段 GroupID,单个) */ + @JsonProperty("GroupID") + private Integer groupId; + + public Integer getGroupId() { + return groupId; + } + + public void setGroupId(Integer groupId) { + this.groupId = groupId; + } + + public String getSchSerID() { + return schSerID; + } + + public void setSchSerID(String schSerID) { + this.schSerID = schSerID; + } + + public String getTeaTel() { + return teaTel; + } + + public void setTeaTel(String teaTel) { + this.teaTel = teaTel; + } + + public String getTeaName() { + return teaName; + } + + public void setTeaName(String teaName) { + this.teaName = teaName; + } + + public Integer getUserTeaSerID() { + return userTeaSerID; + } + + public void setUserTeaSerID(Integer userTeaSerID) { + this.userTeaSerID = userTeaSerID; + } + + public String getTeaIDCard() { + return teaIDCard; + } + + public void setTeaIDCard(String teaIDCard) { + this.teaIDCard = teaIDCard; + } + + public String getTeaNo() { + return teaNo; + } + + public void setTeaNo(String teaNo) { + this.teaNo = teaNo; + } + + public List getGroupIds() { + return groupIds; + } + + public void setGroupIds(List groupIds) { + this.groupIds = groupIds; + } +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java new file mode 100644 index 0000000..cf9c417 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 班级 DTO(/oapi/VSchClass/GetVClassList,SchClass 视图)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchClassDto { + + /** 班级业务ID(sch_class.class_id,scopeType=班级 的 scopeId;学生 ClassID 指向它) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级名称 */ + @JsonProperty("ClassName") + private String className; + + /** 年级ID(= gradeId) */ + @JsonProperty("Levell") + private Integer gradeId; + + /** 排序号 */ + @JsonProperty("sx") + private Integer sort; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java new file mode 100644 index 0000000..01ff15b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 年级 DTO(/oapi/Schoolen/GetSchLenLevellist,BaseSchsystemGrade)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchGradeDto { + + /** 年级ID(=gradeId,scopeType=年级 的 scopeId;班级 Levell 指向它) */ + @JsonProperty("Id") + private Integer id; + + /** 年级名称(gradeNickname,如「初一」) */ + @JsonProperty("Name") + private String name; + + /** 所属学段(学制)ID */ + @JsonProperty("OwnerId") + private Integer ownerId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java new file mode 100644 index 0000000..06185b6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校信息 DTO(/oapi/SchInfo/GetUserSchFirst)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchInfoDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerId") + private String schSerId; + + /** 用户中心学校组ID(与 CampusNotice schId 同域) */ + @JsonProperty("UserSchSerId") + private Integer userSchSerId; + + /** 学校名称 */ + @JsonProperty("SchName") + private String schName; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java new file mode 100644 index 0000000..f96913d --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学生 DTO(/oapi/OAStu/*,SchStudent 视图)。 + * account 仅为上游接口契约字段(兼容反序列化),本系统落库/身份映射不再使用; + * 学生身份以 stuId(UserStuSerID,= 平台用户ID)+ stuName 承载。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchStudentDto { + + /** 学员登录账号(上游契约字段,仅兼容保留,不落库) */ + @JsonProperty("Account") + private String account; + + /** 学生ID */ + @JsonProperty("UserStuSerID") + private Integer stuId; + + /** 学生姓名 */ + @JsonProperty("StuName") + private String stuName; + + /** 所在班级ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 所在组ID */ + @JsonProperty("GroupID") + private Integer groupId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java new file mode 100644 index 0000000..ff7add8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校学科 DTO(/oapi/SubInfo/GetSchSublist)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSubjectDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 学科ID(学科编码;字典无编码时为字典ID字符串) */ + @JsonProperty("SubID") + private String subId; + + /** 学科名称 */ + @JsonProperty("SubName") + private String subName; + + /** 排序号 */ + @JsonProperty("No") + private Integer no; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java new file mode 100644 index 0000000..f0df157 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学段(学制)DTO(/oapi/Schoolen/GetSchLenlist,BaseSchsystem)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSystemDto { + + /** 学制ID(scopeType=学部 的 scopeId) */ + @JsonProperty("Id") + private Integer id; + + /** 学段名称(如「小学部」「初中部」) */ + @JsonProperty("Name") + private String name; + + /** 学制类型 */ + @JsonProperty("Type") + private Integer type; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java new file mode 100644 index 0000000..86ec6f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Date; + +/** + * bschclass 任教关系 DTO(/oapi/TeachInfo/*,sch_class_teach 视图)。 + * 一条记录 = 某学校某班级中某教师任教某学科。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTeachDto { + + /** 任教记录ID(sch_class_teach.id) */ + @JsonProperty("SerID") + private Integer serId; + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 教师ID(= 平台用户ID) */ + @JsonProperty("UserTeaSerID") + private Integer teaId; + + /** 学科ID(学科编码) */ + @JsonProperty("SubID") + private String subId; + + /** 班级业务ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级ID(与 ClassID 同值,上游兼容字段) */ + @JsonProperty("UserClassID") + private Integer userClassId; + + /** 创建时间 */ + @JsonProperty("CreationTime") + private Date creationTime; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java new file mode 100644 index 0000000..29623ad --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java @@ -0,0 +1,41 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * bschclass 组织树节点 DTO(/oapi/SchGroup/GetTeaTree、GetStuTree,TeaTreeVO 视图)。 + * type=1 组节点(学校/学段/年级/班级、教师组),type=2 人员叶子(学生/教师)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTreeNodeDto { + + /** 节点ID(学校/学段/年级/班级为对应业务ID,叶子为学生 stuId 或教师 teaId) */ + private Integer id; + + /** 节点名称 */ + private String name; + + /** 父节点ID(根节点为 0) */ + private Integer pid; + + /** 排序号(组取 sort,叶子固定 0) */ + private Integer no; + + /** 1=组节点,2=人员叶子 */ + private Integer type; + + /** 是否父节点(组 true,叶子 false) */ + @JsonProperty("isParent") + private Boolean isParent; + + /** 头像(当前固定空串) */ + private String photo; + + /** 子节点(叶子为 null,组节点非 null) */ + private List children; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java new file mode 100644 index 0000000..7621af2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java @@ -0,0 +1,81 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 作业详情视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class HomeworkDetailVo { + + @JsonProperty("serid") + private Integer serid; + + @JsonProperty("title") + private String title; + + @JsonProperty("Contents") + private String contents; + + @JsonProperty("DeptID") + private String deptId; + + @JsonProperty("DeptName") + private String deptName; + + @JsonProperty("TeaName") + private String publisherName; + + @JsonProperty("EditDate") + private String editDate; + + @JsonProperty("numcount") + private Integer numCount; + + @JsonProperty("readnumcount") + private Long readNumCount; + + @JsonProperty("ReceiveList") + private String receiveList; + + @JsonProperty("DsfsTime") + private String dsfsTime; + + @JsonProperty("isDsfs") + private Boolean isDsfs; + + @JsonProperty("ChaoSend") + private List chaoSend; + + /** 接收人列表:"all" 或用户ID列表 */ + @JsonProperty("tlist") + private Object tlist; + + /** + * 接收对象可读文本("全部班级" 或班级名顿号串)。 + * 详情页「接收班级」标签直接展示,避免前端拿 ReceiveList 的 id 串渲染。 + */ + @JsonProperty("receiveText") + private String receiveText; + + /** + * 附件项(旧 ChaoSend 数组元素)。 + */ + @Data + public static class FileItem { + @JsonProperty("id") + private Integer id; + @JsonProperty("name") + private String name; + @JsonProperty("size") + private Integer size; + @JsonProperty("fileid") + private Integer fileid; + @JsonProperty("filename") + private String filename; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java new file mode 100644 index 0000000..e1c2fa2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 作业分页结果(扩展 BasePageResult,额外携带 unreadCount)。 + * + * 对齐方案 M7:unreadCount 并入分页外壳扩展子类,不改基类契约。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkPageResult extends BasePageResult { + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkPageResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + super(content, total, pageIndex, pageSize, totalPages, hasNext); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java new file mode 100644 index 0000000..afea9a6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +/** + * 作业列表查询条件。 + * + * - 管理端(adminList):用 status / type / deptId / publisherId / 时间范围过滤; + * - 用户端(userList):额外用 readType 过滤未读/已读(0 全部 / 1 未读 / 2 已读)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkQuery extends BasePageQuery { + + /** 状态过滤:null 不过滤;1 已发布 */ + private Integer status; + + /** 类型过滤:1 普通 / 2 重要 */ + private Integer type; + + /** 列表范围(管理端):"user" 只看自己发布的,null 看全部 */ + private String scope; + + /** 发布部门ID */ + private String deptId; + + /** 发布人ID(用户中心数字ID,管理端按发布人查) */ + private Integer publisherId; + + /** 开始时间(创建时间 >=,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startTime; + + /** 结束时间(创建时间 <= 当天 23:59:59,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endTime; + + /** 阅读状态过滤(仅用户端):0 全部 / 1 未读 / 2 已读 */ + private Integer readType = 0; + + /** 排序:1 时间倒序(默认)/ 2 时间正序 */ + private String orderByTime = "1"; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java new file mode 100644 index 0000000..2cb560f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java @@ -0,0 +1,65 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import com.yunhai.homework.common.validation.ValidationGroups; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 作业发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。 + */ +@Data +public class HomeworkRequest { + + /** 作业标题 */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业标题不得为空") + private String title; + + /** 作业内容 */ + private String content; + + /** 作业类型:1 普通 / 2 重要 */ + @NotNull(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业类型不得为空") + private Integer type = 1; + + /** 发布部门ID(原 DeptID,必填——对应原系统「发布部门不得为空」校验) */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "发布部门不得为空") + private String deptId; + + /** 发布部门名称 */ + private String deptName; + + /** 是否置顶:0 否 / 1 是 */ + private Integer topmost = 0; + + /** 接收人列表(JSON 数组字符串,如 ["10012","10013"]——用户中心数字ID,或 "ALL" 表示全员) */ + private String receiveList; + + /** 附件文件ID列表(逗号分隔串,如 "1,2,3");旧名 chaoSend,已更名 fileIds(M6) */ + private String fileIds; + + /** 定时发送时间(为空表示立即发布) */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timingSendTime; + + /** 短信发送标记:0 不发短信 / 1 发短信(旧名 topmost 实指发短信,已正名 smsFlag,M6) */ + private Integer smsFlag = 0; + + /** 转发源作业ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */ + private Integer sourceId; + + // ==================== 课堂作业 V1/V2 扩展(前端契约字段,兼容层同步写新列) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deadline; + + /** 提交方式:text/image/attach/none 组合 */ + private java.util.List submitType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java new file mode 100644 index 0000000..c492f86 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java @@ -0,0 +1,113 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 作业统计视图(对应原系统 GetStatisData / 按发布人统计)。 + */ +@Data +public class HomeworkStatisticsVo { + + /** 作业总数(全部已发布) */ + private long total; + + /** 已发布数 */ + private long publishedCount; + + /** 阅读记录总数(累计已读人次) */ + private long totalReadRecords; + + /** 当前用户未读数(已到点的已发布 - 当前用户已读) */ + private long unreadForCurrentUser; + + /** 按发布人统计 */ + private List byPublisher; + + /** 按月统计(已发布作业数) */ + private List byMonth; + + // ==================== 课程作业口径(前端 /stats 页消费) ==================== + + /** 我布置的作业数(当前登录人发布的全部作业,含草稿) */ + private long myAssignCount; + + /** 我的作业待批改总量(已交未批的当前版本行合计) */ + private long pendingGrade; + + /** 各作业平均提交率(%,仅统计需收集提交的作业;无样本时为 0) */ + private double avgSubmitRate; + + /** 按作业的提交与批改进度明细(草稿与仅查阅型不计) */ + private List byHomework = new ArrayList<>(); + + /** 已批阅提交的分数分布(四档) */ + private List scoreDist = new ArrayList<>(); + + /** 单作业进度条目(口径对齐前端 HomeworkProgress 契约) */ + @Data + public static class HomeworkStat { + private Integer id; + private String title; + private String subject; + /** 旧契约四态派生值:draft / collecting / closed / done */ + private String hwStatus; + private String hwStatusDesc; + /** 应交人数 */ + private long should; + /** 已读人数 */ + private long read; + /** 已交人数 */ + private long submitted; + /** 已批阅人数 */ + private long graded; + /** 待批改人数 */ + private long pendingGrade; + /** 未交人数 */ + private long unsubmitted; + /** 提交率(%) */ + private double submitRate; + /** 批阅率(%) */ + private double gradeRate; + } + + /** 分数分布档(key 供前端配色映射:excellent/good/pass/fail) */ + @Data + public static class ScoreBucket { + private String label; + private String key; + private long count; + + public ScoreBucket(String label, String key, long count) { + this.label = label; + this.key = key; + this.count = count; + } + } + + @Data + public static class PublisherStat { + private String publisherName; + private long count; + + public PublisherStat(String publisherName, long count) { + this.publisherName = publisherName; + this.count = count; + } + } + + @Data + public static class MonthStat { + private int year; + private int month; + private long count; + + public MonthStat(int year, int month, long count) { + this.year = year; + this.month = month; + this.count = count; + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java new file mode 100644 index 0000000..c906e2e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.List; + +/** + * 用户端作业列表结果:在分页数据之外额外携带「当前用户未读总数」, + * 供前端展示未读角标(对应原系统 OtherData.unReadCount)。 + */ +@Data +public class HomeworkUserListResult { + + private int total; + private List content; + private int pageIndex; + private int pageSize; + private int totalPages; + private boolean hasNext; + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkUserListResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java new file mode 100644 index 0000000..333d49e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java @@ -0,0 +1,92 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 作业视图对象(列表/详情返回)。 + */ +@Data +public class HomeworkVo { + + private Integer id; + private String title; + private String content; + private Integer type; + private String typeDesc; + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + private String publisherName; + private String deptId; + private String deptName; + private Integer status; + private String statusDesc; + private Integer topmost; + + /** 已读人数(基于阅读记录实时统计) */ + private Long readCount = 0L; + + /** 应读人数 */ + private Integer allowCount; + + /** 当前用户是否已读(用户端列表使用;管理端恒为 false 或不关注) */ + private boolean read = false; + + /** 是否撤回:0 否 / 1 是 */ + private Integer isPulled; + + /** 定时发送时间 */ + private LocalDateTime timingSendTime; + + /** 父作业ID(转发时关联原作业) */ + private Integer parentId; + + /** 短信发送标记 */ + private Integer smsFlag; + + /** 接收人列表(JSON) */ + private String receiveList; + + /** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */ + private String UpFile; + + // ==================== 课堂作业 V1/V2 扩展(前端 types/homework.ts 契约) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间(前端契约字段名 deadline) */ + private LocalDateTime deadline; + + /** 提交方式数组:text/image/attach/none */ + private java.util.List submitType; + + /** 作业流程状态——旧契约四态派生值(draft/collecting/closed/done),权威五态见 homework.hw_status */ + private String hwStatus; + + private String hwStatusDesc; + + /** + * 提交/批改进度(管理端列表页「提交进度 / 未读未交 / 批改」列使用)。 + * 草稿或仅查阅型为 null;列表场景由 HomeworkProgressCalculator 批量聚合回填。 + */ + private com.yunhai.homework.model.v2.ProgressVo progress; + + private String createBy; + private LocalDateTime createTime; + private String updateBy; + private LocalDateTime updateTime; + + /** + * 阅读明细视图(阅读列表查询用)。 + */ + @Data + public static class ReadDetail { + /** 读者ID(用户中心数字ID) */ + private Integer id; + private String name; + private boolean read; + private LocalDateTime readTime; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java new file mode 100644 index 0000000..15ece01 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 一键已读结果视图对象(替代原 Map<String,Object> 返回)。 + */ +@Data +public class InitializeRedVo { + + @JsonProperty("count") + private Integer count; + + public InitializeRedVo() {} + + public InitializeRedVo(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java new file mode 100644 index 0000000..8127480 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java @@ -0,0 +1,30 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 文件上传结果视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class UpFileResultVo { + + @JsonProperty("fileid") + private Integer fileid; + + @JsonProperty("name") + private String name; + + @JsonProperty("fileurl") + private String fileurl; + + @JsonProperty("host") + private String host; + + @JsonProperty("type") + private String type; + + @JsonProperty("size") + private Integer size; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java new file mode 100644 index 0000000..76dfa2f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +/** + * 附件视图对象(对应原 .NET FileObj)。 + */ +@Data +public class UpFileVo { + + /** 文件记录ID */ + private Integer id; + + /** 文件真实名称 */ + private String fileTrueName; + + /** 文件存储路径(相对路径) */ + private String fileName; + + /** 文件大小(字节) */ + private Integer fileSize; + + /** 文件后缀名 */ + private String fileType; + + /** 关联主题ID */ + private Integer topicId; + + /** 应用类型 */ + private String topicType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java new file mode 100644 index 0000000..771eb3a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java @@ -0,0 +1,122 @@ +package com.yunhai.homework.model.homework; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户可访问模块(左菜单)视图对象。 + * 将基础平台返回的扁平 {@code List} 转换为前端 layout(参考 UserBase)期望的树结构: + * {@code modId}(路由高亮匹配)/{@code page}(跳转路径)/{@code children}(子菜单)。 + */ +public class UserModuleVo { + + /** 模块主键 id(基础平台 module id) */ + private Integer id; + + /** 菜单高亮匹配用:等价于后端模块 id */ + private Integer modId; + + /** 模块名称 */ + private String name; + + /** 父级模块 id(0 表示根节点) */ + private Integer parentId; + + /** 前端路由路径(由 BoaModule.path 映射而来) */ + private String page; + + /** 层级(0=根分组) */ + private Integer level; + + /** 菜单图标 */ + private String icon; + + /** 排序号 */ + private Integer orderNumber; + + /** 是否可用 */ + private Boolean usable; + + /** 子菜单 */ + private List children = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getModId() { + return modId; + } + + public void setModId(Integer modId) { + this.modId = modId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Integer getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(Integer orderNumber) { + this.orderNumber = orderNumber; + } + + public Boolean getUsable() { + return usable; + } + + public void setUsable(Boolean usable) { + this.usable = usable; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java new file mode 100644 index 0000000..bedf7b5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 附件条目(作业附件与提交附件共用)。fileUrl 必传;fileId 为 file/Upload 产物时回传。 + */ +@Data +public class AttachmentItem { + + /** up_file.id */ + private Integer fileId; + + /** 文件访问 URL */ + private String fileUrl; + + /** 文件类型:image / file */ + private String fileType; + + /** 文件名 */ + private String fileName; + + /** 排序号 */ + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassItem.java b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java new file mode 100644 index 0000000..459ea80 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java @@ -0,0 +1,18 @@ +package com.yunhai.homework.model.v2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 班级条目(作业参与班级展示)。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClassItem { + + private String classId; + + private String className; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java new file mode 100644 index 0000000..c0f400b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 年级→班级树(发布作业选班 + 应交名单基数来源)。 + * 叶子节点 id = 班级 id(class_student 快照聚合)。 + */ +@Data +public class ClassTreeNode { + + /** 年级节点为 gradeName,班级叶子为 classId */ + private String id; + + /** 展示名 */ + private String name; + + /** 是否父节点(年级) */ + private Boolean isParent; + + /** 叶子节点:在班学生数 */ + private Integer studentCount; + + private List children = new ArrayList<>(); +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java new file mode 100644 index 0000000..f8c5dec --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 延长截止请求(新截止时间必须晚于当前时间)。 + */ +@Data +public class ExtendRequest { + + @NotNull(message = "新截止时间不得为空") + private LocalDateTime deadlineAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java new file mode 100644 index 0000000..1ac46f8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java @@ -0,0 +1,36 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 作业详情汇总统计卡片(PRD §18 页 4 stats 接口出参)。 + */ +@Data +public class HomeworkStatsVo { + + private Integer homeworkId; + + private String title; + + private String hwStatus; + + private String hwStatusDesc; + + /** 总进度 */ + private ProgressVo total; + + /** 分班进度 */ + private List byClass; + + @Data + public static class ByClass { + + private String classId; + + private String className; + + private ProgressVo progress; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java new file mode 100644 index 0000000..9464910 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 创建/编辑作业请求(PRD §18 页 1 表单)。 + * 创建一律落草稿(DRAFT_UNPUBLISHED),发布/定时走独立接口。 + */ +@Data +public class HomeworkV2Request { + + /** 作业标题(必填 ≤50 字) */ + @NotBlank(message = "作业标题不得为空") + @Size(max = 50, message = "作业标题不超过50字") + private String title; + + /** 学科(字典值) */ + private String subject; + + /** 作业说明(富文本) */ + private String description; + + /** 参与班级 id 列表 */ + private List classIds; + + /** 附件列表 */ + private List attachments; + + /** 截止时间(精确到分钟;空 = 仅查阅型) */ + private LocalDateTime deadlineAt; + + /** 提交方式:image / attach / text / none 的组合(PRD 图片/文件/两者) */ + private List submitType; + + /** 截止后是否允许逾期提交:0 否 / 1 是 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天提醒(0 关 / 1 开) */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时提醒(0 关 / 1 开) */ + private Integer remind1h; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java new file mode 100644 index 0000000..8bf1b02 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java @@ -0,0 +1,73 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 作业视图对象 V2(草稿箱/已发布列表/详情共用)。 + * 列表场景 attachments/classes 可按需填充;progress 仅非草稿且非仅查阅型作业返回。 + */ +@Data +public class HomeworkV2Vo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 作业说明(富文本;列表场景可置空省流) */ + private String description; + + /** 作业状态(PRD 五态) */ + private String hwStatus; + + private String hwStatusDesc; + + /** 定时发布时间(复用 timing_send_time) */ + private LocalDateTime scheduledAt; + + /** 发布时间 */ + private LocalDateTime publishedAt; + + /** 截止时间 */ + private LocalDateTime deadlineAt; + + /** 归档时间 */ + private LocalDateTime archivedAt; + + /** 提交方式数组 */ + private List submitType; + + /** 截止后允许逾期提交:0/1 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天 */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时 */ + private Integer remind1h; + + /** 参与班级 */ + private List classes; + + /** 附件列表(详情场景填充) */ + private List attachments; + + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + + /** 发布人姓名 */ + private String publisherName; + + /** 创建/更新时间(审计) */ + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + /** 汇总进度(草稿或仅查阅型为 null) */ + private ProgressVo progress; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java new file mode 100644 index 0000000..0303eb8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知分页结果(含当前用户未读数)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationPageResult extends BasePageResult { + + /** 当前用户未读通知总数 */ + private long unreadCount; + + public NotificationPageResult(BasePageResult page, long unreadCount) { + super(page.getContent(), page.getTotal(), page.getPageIndex(), page.getPageSize(), + page.getTotalPages(), page.isHasNext()); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java new file mode 100644 index 0000000..f8af4a7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知列表查询。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationQuery extends BasePageQuery { + + /** 类型过滤(NotificationTypeEnum code) */ + private String type; + + /** 已读过滤:0 未读 / 1 已读(空 = 全部) */ + private Integer isRead; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java new file mode 100644 index 0000000..2a9b61a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知列表项。 + */ +@Data +public class NotificationVo { + + private Integer id; + + /** 通知类型 */ + private String type; + + private String typeDesc; + + private String title; + + private String content; + + private Integer homeworkId; + + private Integer relatedId; + + /** 是否已读:0/1 */ + private Integer isRead; + + private LocalDateTime readAt; + + private LocalDateTime createTime; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java new file mode 100644 index 0000000..201a460 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 提交/批阅/阅读进度(PRD §18 页 3 汇总口径)。 + * should(应交)为分母;打回未重交计入已交。 + */ +@Data +public class ProgressVo { + + /** 应交人数(参与班级在班学生去重) */ + private int should; + + /** 已读人数 */ + private int read; + + /** 已交人数(含逾期/打回/已批阅) */ + private int submitted; + + /** 已批阅人数 */ + private int graded; + + /** 待批阅人数(submitted - graded) */ + private int pendingGrade; + + /** 未交人数 */ + private int unsubmitted; + + /** 未读人数 */ + private int unread; + + /** 未读且未交人数(高亮口径:should - 已读∪已交) */ + private int unreadUndone; + + /** 阅读率(百分数,一位小数) */ + private double readRate; + + /** 提交率(百分数,一位小数) */ + private double submitRate; + + /** 批阅率(已批阅 / 已交,百分数一位小数;submitted=0 时为 0) */ + private double gradeRate; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java new file mode 100644 index 0000000..a97e0c1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 教师手动提醒请求:type = NOT_READ / NOT_SUBMITTED,studentIds 为空时提醒全部未读/未交学生。 + */ +@Data +public class RemindRequest { + + /** 提醒类型:NOT_READ / NOT_SUBMITTED */ + @NotBlank(message = "提醒类型不得为空") + private String type; + + /** 定向学生ID(用户中心数字ID;空 = 全部未读/未交) */ + private List studentIds; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java new file mode 100644 index 0000000..45d3f3e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 打回请求(PRD 页 6)。comment 为订正原因,必填。 + */ +@Data +public class ReturnRequest { + + @NotBlank(message = "打回须填写订正原因") + private String comment; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java new file mode 100644 index 0000000..166b2e1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 批阅保存请求(PRD 页 6)。score 与 comment 至少一项有值。 + */ +@Data +public class ReviewRequest { + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 圈划标注图片(P2,先收参) */ + private List markedImages; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java new file mode 100644 index 0000000..894b433 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 定时发布 / 取消定时请求。 + * cancel=true 时忽略 scheduledAt,作业回退草稿-未发布并清空定时时间。 + */ +@Data +public class ScheduleRequest { + + /** 定时发布时间(cancel=false 时必填且须晚于当前时间) */ + private LocalDateTime scheduledAt; + + /** 是否取消定时 */ + private Boolean cancel = false; + + @NotNull + public Boolean getCancel() { + return cancel; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java new file mode 100644 index 0000000..6372883 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 学生端作业详情(PRD §18 学生页 2)。进入详情后由前端停留 ≥3 秒再调 read 上报。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentHomeworkDetailVo extends StudentHomeworkVo { + + /** 作业说明(富文本) */ + private String description; + + /** 作业附件 */ + private List attachments; + + /** 参与班级(学生所属班级视角) */ + private List classes; + + /** 我当前版本的提交附件(未提交为 null) */ + private List myAttachments; + + /** 我的文本内容(当前版本) */ + private String myText; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java new file mode 100644 index 0000000..9d30037 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学生端作业列表项(PRD §18 学生页 1)。 + */ +@Data +public class StudentHomeworkVo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 任课教师(发布人姓名) */ + private String teacherName; + + /** 截止时间(前端做倒计时) */ + private LocalDateTime deadlineAt; + + /** 提交方式 */ + private java.util.List submitType; + + /** 作业状态 */ + private String hwStatus; + + private String hwStatusDesc; + + /** 截止后是否允许逾期补交:0 否 / 1 是(学生端据此决定是否渲染「补交」入口) */ + private Integer allowLate; + + /** 我的提交概要(未提交为 null) */ + private MySubmission my; + + @Data + public static class MySubmission { + + /** 当前版本提交行ID */ + private Integer submissionId; + + /** 提交状态(PRD 五态) */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 版本号 */ + private Integer version; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分(已批阅才有) */ + private java.math.BigDecimal score; + + /** 评语 */ + private String comment; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java new file mode 100644 index 0000000..49e3144 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学生矩阵查询(PRD §18 页 5)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentMatrixQuery extends BasePageQuery { + + /** 班级筛选(空 = 全部参与班级) */ + private String classId; + + /** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */ + private String submitStatus; + + /** 批阅状态筛选:PENDING/REVIEWED/RETURNED */ + private String reviewStatus; + + /** 只看待批阅(reviewStatus=PENDING 且已提交)快捷过滤 */ + private Boolean pendingGrade; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java new file mode 100644 index 0000000..6257285 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java @@ -0,0 +1,57 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学生矩阵行(PRD §18 页 5)。未交学生也生成占位行(submissionId=0)。 + */ +@Data +public class StudentMatrixVo { + + /** 当前版本提交行ID(未交占位为 0) */ + private Integer submissionId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + /** 姓名 */ + private String studentName; + + private String classId; + + private String className; + + /** 阅读状态:0 未读 / 1 已读 */ + private Integer isRead; + + /** 首次阅读时间 */ + private LocalDateTime firstReadAt; + + /** 最近阅读时间 */ + private LocalDateTime lastReadAt; + + /** 提交状态(PRD 五态;未交为 NOT_SUBMITTED) */ + private String submitStatus; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期:0/1 */ + private Integer isLate; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分 */ + private BigDecimal score; + + /** 最新提交附件 URL 列表(缩略图) */ + private List thumbnails; + + /** 提交内容类型摘要:image=拍照/图片、attach=上传附件、text=文字作答(未交为空列表) */ + private List contentTypes; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java new file mode 100644 index 0000000..f4d9cd1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 学生查看批阅结果(PRD §18 学生页 2 result 接口)。 + */ +@Data +public class StudentResultVo { + + private Integer homeworkId; + + private String homeworkTitle; + + /** 当前版本批阅结果(未批阅为 null) */ + private SubmissionVersionVo latest; + + /** 全部版本(含各版评语,新→旧) */ + private List versions; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java new file mode 100644 index 0000000..1fa7545 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 学生提交作业请求(PRD §18 学生页 3)。附件只接受 file/Upload 产物(fileId 或平台 URL),拒绝 dataURL。 + */ +@Data +public class StudentSubmitRequest { + + /** 文本内容(submitType 含 text 时有效) */ + @Size(max = 20000, message = "文本内容过长") + private String text; + + /** 附件列表(图片/文件) */ + private List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java new file mode 100644 index 0000000..6c576d9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 学科 DTO(布置过的学科 / 我的任教学科共用返回结构)。 + */ +@Data +public class SubjectDto { + + /** 学科编码(homework.subject 字典值 / bschclass SubID) */ + private String subId; + + /** 学科名称(本校学科字典解析;字典缺失时回退为编码) */ + private String subName; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java new file mode 100644 index 0000000..cac00dc --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 撤回重交动作(PUT /student/homeworks/{id}/submissions/{submissionId})。 + * withdraw = 撤回(截止前,产生 NOT_SUBMITTED 新版本行);resubmit = 打回后重交(走提交语义)。 + */ +@Data +public class SubmissionActionRequest { + + @NotBlank(message = "action 不得为空") + private String action; + + /** resubmit 时的提交内容(等同 StudentSubmitRequest) */ + private String text; + + /** resubmit 时的附件 */ + private java.util.List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java new file mode 100644 index 0000000..bff2972 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java @@ -0,0 +1,63 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 提交版本条目(历史版本/批阅弹窗/学生结果页共用)。 + */ +@Data +public class SubmissionVersionVo { + + private Integer submissionId; + + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + private String studentName; + + /** 版本号 */ + private Integer version; + + /** 是否当前版本:0/1 */ + private Integer isLatest; + + /** 提交状态 */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 文本内容 */ + private String text; + + /** 附件 */ + private List attachments; + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 批阅状态 */ + private String reviewStatus; + + private String reviewStatusDesc; + + /** 批阅人(展示姓名快照,缺失时回退ID) */ + private String reviewedBy; + + /** 批阅时间 */ + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java new file mode 100644 index 0000000..6d6991e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教师端作业列表查询(草稿箱/已发布列表共用)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TeacherHomeworkQuery extends BasePageQuery { + + /** 学科筛选 */ + private String subject; + + /** 作业状态筛选(已发布列表:PUBLISHED/CLOSED/ARCHIVED) */ + private String hwStatus; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java new file mode 100644 index 0000000..d9fd64e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 任教班级 VO(按学科筛选任教班级返回结构)。 + */ +@Data +public class TeachingClassVo { + + /** 班级业务ID(bschclass sch_class.class_id) */ + private String classId; + + /** 班级名称(学校班级列表映射;缺失时回退为班级ID) */ + private String className; +} diff --git a/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java new file mode 100644 index 0000000..0581e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ClassStudent; +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; + +/** + * 班级学生名册仓储。 + */ +public interface ClassStudentRepository extends JpaRepository { + + /** 按学生ID(用户中心数字ID)反查学生身份(学生端接口入口) */ + List findByStudentIdAndStatus(Integer studentId, Integer status); + + /** 班级在班学生 */ + List findByClassIdAndStatus(String classId, Integer status); + + /** 指定班级集合的全部在班学生(应交名单) */ + List findByClassIdInAndStatus(List classIds, Integer status); + + /** 指定学校的全部名册(同步任务全量刷新用) */ + List findBySchoolId(Integer schoolId); + + /** 年级→班级树:按学校去重班级(class_student 快照聚合),列序:classId, className, gradeName */ + @Query("select distinct c.classId, c.className, c.gradeName from ClassStudent c " + + "where c.schoolId = :schoolId and c.status = 1") + List findDistinctClasses(@Param("schoolId") Integer schoolId); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java new file mode 100644 index 0000000..4dc9ee4 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java @@ -0,0 +1,27 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroup; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ContactsGroupRepository extends JpaRepository { + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + + /** + * 按创建人ID(用户中心数字ID)查询联系组列表。 + */ + List findByCreatorId(Integer creatorId); + + /** + * 按创建人ID查询联系组列表(分页)。 + */ + Page findByCreatorId(Integer creatorId, Pageable pageable); + + /** + * 查询全部联系组(按创建时间降序)。 + */ + List findAllByOrderByCreateTimeDesc(); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java new file mode 100644 index 0000000..df5ab28 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java @@ -0,0 +1,42 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroupUser; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ContactsGroupUserRepository extends JpaRepository { + Page findByGroupId(Integer groupId, Pageable pageable); + List findByGroupId(Integer groupId); + + /** + * 按组ID批量删除成员(JPQL bulk delete,SQL 立即执行)。 + * 注意:不能用 derived delete——其 DELETE 延迟到 flush 才执行, + * 同事务内先 insert 会触发 uk_group_user 唯一约束冲突。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId = :groupId") + void deleteByGroupIdBulk(@Param("groupId") Integer groupId); + + /** + * 按多个组ID批量删除成员。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId in :groupIds") + void deleteByGroupIdInBulk(@Param("groupIds") List groupIds); + + /** + * 按多个组ID查询所有成员(用于统计每组人数、组树构建)。 + */ + List findByGroupIdIn(List groupIds); + + /** + * 按组ID和用户ID查询(判断是否已存在)。 + */ + boolean existsByGroupIdAndUserId(Integer groupId, Integer userId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java new file mode 100644 index 0000000..1e6af55 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 作业附件仓储。 + */ +public interface HomeworkAttachmentRepository extends JpaRepository { + + List findByHomeworkIdOrderBySortAscIdAsc(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java new file mode 100644 index 0000000..8f651f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkClass; +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; + +/** + * 作业-班级关联仓储。 + */ +public interface HomeworkClassRepository extends JpaRepository { + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 指定班级的全部作业ID(学生端列表倒查用) */ + @Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds") + List findHomeworkIdsByClassIds(@Param("classIds") List classIds); + + /** 批量查 N 个作业的班级关联(列表展示避免 N+1) */ + @Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java new file mode 100644 index 0000000..6c16a81 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java @@ -0,0 +1,23 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReadRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 学生阅读记录仓储。 + */ +public interface HomeworkReadRecordRepository extends JpaRepository { + + Optional findByHomeworkIdAndStudentId(Integer homeworkId, Integer studentId); + + List findByHomeworkId(Integer homeworkId); + + /** 批量查 N 个作业的已读记录(列表统计避免 N+1) */ + List findByHomeworkIdIn(Collection homeworkIds); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java new file mode 100644 index 0000000..20dc83c --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkRead; +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.Optional; + +/** + * 作业阅读记录仓储(懒标记模型)。 + */ +public interface HomeworkReadRepository extends JpaRepository { + + /** + * 某用户是否已读某作业。 + */ + boolean existsByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 某作业的总已读人数。 + */ + long countByHomeworkId(Integer homeworkId); + + /** + * 删除某作业的全部阅读记录(作业删除时级联清理)。 + */ + void deleteByHomeworkId(Integer homeworkId); + + /** + * 某用户已读的作业总数(用于「我的未读」= 已发布总数 - 已读总数)。 + */ + long countByReaderId(Integer readerId); + + /** + * 查询某用户已读的作业ID列表(用于列表页批量判定未读)。 + */ + @Query("select r.homeworkId from HomeworkRead r where r.readerId = :readerId") + List findHomeworkIdsByReaderId(@Param("readerId") Integer readerId); + + /** + * 查询某用户对某作业的阅读记录(标记已读时幂等判断)。 + */ + Optional findByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 查询某作业的全部阅读记录(按阅读时间倒序),用于阅读明细列表。 + */ + @Query("select r from HomeworkRead r where r.homeworkId = :homeworkId order by r.readTime desc") + List findByHomeworkIdOrderByReadTimeDesc(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java new file mode 100644 index 0000000..402d455 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReceiver; +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; + +/** + * 作业接收人关联表仓储。 + */ +public interface HomeworkReceiverRepository extends JpaRepository { + + /** 按作业ID查询全部接收人 */ + List findByHomeworkId(Integer homeworkId); + + /** 按作业ID删除全部接收人 */ + void deleteByHomeworkId(Integer homeworkId); + + /** 按作业ID和接收类型查询接收人 */ + List findByHomeworkIdAndReceiveType(Integer homeworkId, Integer receiveType); + + /** + * 批量查 N 个作业的接收人,用于列表展示(避免 N+1)。 + * @param homeworkIds 作业ID列表 + * @return 接收人关联记录列表 + */ + @Query("select r from HomeworkReceiver r where r.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java new file mode 100644 index 0000000..bf862b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java @@ -0,0 +1,139 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Homework; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 课程作业仓储。 + */ +public interface HomeworkRepository extends JpaRepository, + JpaSpecificationExecutor { + + /** + * 作业流程状态条件更新(CAS):仅当当前状态为 from 时更新为 to。 + * 状态机单点收敛用,天然幂等、并发安全(定时任务与接口共用)。 + * clearAutomatically:批量更新绕过一级缓存,必须清空以避免后续 findById 读到旧实体 + * 再被 save() 写回旧状态(同事务内必现,集成测试发现)。 + * + * @return 影响行数(0 = 状态已变化,调用方应放弃本次迁移) + */ + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update Homework h set h.hwStatus = :to where h.id = :id and h.hwStatus = :from") + int casHwStatus(@Param("id") Integer id, + @Param("from") String from, + @Param("to") String to); + + /** + * 定时发布任务扫描:定时待发布且已到点。 + */ + List findByHwStatusAndTimingSendTimeLessThanEqual(String hwStatus, LocalDateTime now); + + /** + * 到点关闭任务扫描:进行中且已过截止时间。 + */ + List findByHwStatusAndDeadlineAtBefore(String hwStatus, LocalDateTime now); + + /** + * 截止提醒任务扫描:进行中且截止时间落在指定窗口内(remind_1d/remind_1h 开关在服务侧逐单过滤)。 + */ + List findByHwStatusAndDeadlineAtBetween(String hwStatus, LocalDateTime start, LocalDateTime end); + + /** + * 按作业流程状态查询(兼容层/统计用)。 + */ + List findByHwStatus(String hwStatus); + + /** + * 当前登录人发布的全部作业(课程作业口径统计:我布置的作业 / 提交与批改进度明细)。 + */ + List findByPublisherId(Integer publisherId); + + /** + * 当前登录人布置过(已发布及之后状态)作业的学科去重清单(学科筛选用,字典序;草稿不计)。 + */ + @Query("select distinct h.subject from Homework h " + + "where h.publisherId = :publisherId and h.subject is not null and h.subject <> '' " + + "and h.hwStatus in :statuses order by h.subject") + List findAssignedSubjects(@Param("publisherId") Integer publisherId, + @Param("statuses") Collection statuses); + + /** + * 按状态计数(用于统计已发布总数)。 + */ + long countByStatus(Integer status); + + /** + * 已发布且已到点(定时发送未到点不计入)的作业数,用于用户端未读数统计。 + */ + @Query("select count(n) from Homework n where n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDue(@Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定状态、指定时间范围内的作业创建时间(用于按月统计,避免加载大字段)。 + */ + @Query("select n.createTime from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定状态、指定时间范围内的发布人姓名(用于按发布人统计)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 按学校和状态计数(多租户隔离统计)。 + */ + long countBySchIdAndStatus(Integer schId, Integer status); + + /** + * 按学校计数(多租户隔离统计:全部状态)。 + */ + long countBySchId(Integer schId); + + /** + * 已发布且已到点(定时发送未到点不计入)且属于指定学校的作业数(用户端未读数统计,多租户隔离)。 + */ + @Query("select count(n) from Homework n where n.schId = :schId and n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDueBySchId(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定学校、指定状态、指定时间范围内的作业创建时间(按月统计,多租户隔离)。 + */ + @Query("select n.createTime from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesBySchIdStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定学校、指定状态、指定时间范围内的发布人姓名(按发布人统计,多租户隔离)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesBySchStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java new file mode 100644 index 0000000..547c5ea --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkSubmission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 作业提交仓储(多版本)。统计一律基于 is_latest=1 的当前版本行。 + */ +public interface HomeworkSubmissionRepository extends JpaRepository { + + /** 学生在指定作业的当前版本行 */ + Optional findByHomeworkIdAndStudentIdAndIsLatest(Integer homeworkId, + Integer studentId, + Integer isLatest); + + /** 学生在指定作业的全部版本(新→旧) */ + List findByHomeworkIdAndStudentIdOrderByVersionDesc(Integer homeworkId, Integer studentId); + + /** 指定作业的当前版本行(统计/矩阵用) */ + List findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest); + + /** 批量查 N 个作业的当前版本行(列表统计避免 N+1) */ + List findByHomeworkIdInAndIsLatest(Collection homeworkIds, Integer isLatest); + + /** 学生矩阵分页 */ + Page findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest, Pageable pageable); + + /** 学生当前版本最大版本号 */ + @Query("select max(s.version) from HomeworkSubmission s " + + "where s.homeworkId = :homeworkId and s.studentId = :studentId") + Integer findMaxVersion(@Param("homeworkId") Integer homeworkId, @Param("studentId") Integer studentId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 附件等子表清理后按作业删除历史版本行 */ + @Modifying + @Query("delete from HomeworkSubmission s where s.homeworkId = :homeworkId") + int deleteAllByHomeworkId(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java new file mode 100644 index 0000000..ecc75cb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HomeworkTemplateRepository extends JpaRepository { + Page findByTypeId(Integer typeId, Pageable pageable); + + /** + * 查询全部模板(按引用次数降序),分页。 + */ + Page findAllByOrderByRefCountDesc(Pageable pageable); + + /** + * 按类型ID查询全部模板(不分页,用于检查类型是否被引用)。 + */ + long countByTypeId(Integer typeId); +} diff --git a/src/main/java/com/yunhai/homework/repository/NotificationRepository.java b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java new file mode 100644 index 0000000..1066c10 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + * 站内通知仓储。 + */ +public interface NotificationRepository extends JpaRepository { + + Page findByReceiverId(Integer receiverId, Pageable pageable); + + Page findByReceiverIdAndType(Integer receiverId, String type, Pageable pageable); + + Page findByReceiverIdAndIsRead(Integer receiverId, Integer isRead, Pageable pageable); + + Page findByReceiverIdAndTypeAndIsRead(Integer receiverId, String type, + Integer isRead, Pageable pageable); + + long countByReceiverIdAndIsRead(Integer receiverId, Integer isRead); + + Optional findByIdAndReceiverId(Integer id, Integer receiverId); + + /** 截止提醒去重:同作业同类型同档位已发过即跳过(related_id 存档位:1=1天档 2=1小时档) */ + boolean existsByHomeworkIdAndTypeAndRelatedId(Integer homeworkId, String type, Integer relatedId); + + /** 截止提醒去重(不分档位) */ + boolean existsByHomeworkIdAndType(Integer homeworkId, String type); + + /** 全部已读(clearAutomatically 防同事务内缓存脏读) */ + @Modifying(clearAutomatically = true) + @Query("update Notification n set n.isRead = 1, n.readAt = :now " + + "where n.receiverId = :receiverId and n.isRead = 0") + int markAllRead(@Param("receiverId") Integer receiverId, @Param("now") java.time.LocalDateTime now); + + /** 按作业删除通知(作业删除级联) */ + void deleteByHomeworkId(Integer homeworkId); + + /** 指定作业的全部通知(清理/统计用) */ + List findByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java new file mode 100644 index 0000000..8e60f47 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SchClassTeach; +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; + +/** + * 班级任教关系快照仓储。 + */ +public interface SchClassTeachRepository extends JpaRepository { + + /** 指定学校指定教师的全部有效任教记录 */ + List findBySchoolIdAndTeaIdAndStatus(Integer schoolId, Integer teaId, Integer status); + + /** 指定学校指定教师去重学科编码(字典序由调用方保证) */ + @Query("select distinct t.subId from SchClassTeach t " + + "where t.schoolId = :schoolId and t.teaId = :teaId and t.status = 1") + List findDistinctSubIds(@Param("schoolId") Integer schoolId, @Param("teaId") Integer teaId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java new file mode 100644 index 0000000..18595bb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java @@ -0,0 +1,13 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsModule; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsModuleRepository extends JpaRepository { + List findAllByOrderByIdAsc(); + + /** 按模块标识查询(如 module_code="homework")。 */ + SmsModule findByModuleCode(String moduleCode); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java new file mode 100644 index 0000000..0d277a0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsSchoolConfig; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsSchoolConfigRepository extends JpaRepository { + Page findBySchoolNameContaining(String schoolName, Pageable pageable); + Page findAll(Pageable pageable); + List findBySchoolId(Integer schoolId); + List findBySchoolIdInAndModuleIdIn(List schoolIds, List moduleIds); + + /** + * 按学校ID和模块ID查询单条配置(判断是否已存在)。 + */ + SmsSchoolConfig findBySchoolIdAndModuleId(Integer schoolId, Integer moduleId); + + /** + * 按学校ID查询全部模块配置(用于"设置某校全部模块")。 + */ + List findBySchoolIdIn(List schoolIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java new file mode 100644 index 0000000..8848b14 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SubmissionAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; + +/** + * 提交附件仓储。 + */ +public interface SubmissionAttachmentRepository extends JpaRepository { + + List findBySubmissionIdOrderBySortAscIdAsc(Integer submissionId); + + /** 批量查 N 个提交行的附件(矩阵/版本列表避免 N+1) */ + List findBySubmissionIdIn(Collection submissionIds); + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SysDictRepository.java b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java new file mode 100644 index 0000000..c083840 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SysDict; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SysDictRepository extends JpaRepository { + List findByDictTypeOrderBySortOrderAsc(String dictType); + List findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag); + + /** 按类型+key 取单条字典(发文部门名称解析用) */ + Optional findByDictTypeAndDictKey(String dictType, String dictKey); +} diff --git a/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java new file mode 100644 index 0000000..a3a0f12 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.TemplateType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TemplateTypeRepository extends JpaRepository { + List findAllByOrderBySortOrderAsc(); + + /** + * 按类型名称查询(重名校验)。 + */ + boolean existsByTypeName(String typeName); + + /** + * 按类型名称查询(重名校验,排除自身)。 + */ + boolean existsByTypeNameAndIdNot(String typeName, Integer id); +} diff --git a/src/main/java/com/yunhai/homework/repository/UpFileRepository.java b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java new file mode 100644 index 0000000..421f142 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.UpFile; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +/** + * 附件文件仓储。 + */ +public interface UpFileRepository extends JpaRepository { + + /** + * 按主题类型和主题ID查询附件列表。 + */ + List findByTopicTypeAndTopicId(String topicType, Integer topicId); + + /** + * 按主题类型和多个主题ID批量查询附件(消除 N+1)。 + */ + List findByTopicTypeAndTopicIdIn(String topicType, List topicIds); + + /** + * 按主题类型和主题ID删除附件(作业删除时级联清理)。 + */ + void deleteByTopicTypeAndTopicId(String topicType, Integer topicId); +} diff --git a/src/main/java/com/yunhai/homework/service/IClassService.java b/src/main/java/com/yunhai/homework/service/IClassService.java new file mode 100644 index 0000000..2346cd9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IClassService.java @@ -0,0 +1,116 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; + +import java.util.List; + +/** + * 班级管理(bschclass)开放接口客户端门面。 + * 基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。 + * 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。 + */ +public interface IClassService { + + /** + * 获取指定学校全部教师(含所属组 GroupIds)。 + * 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}} + * + * @param schId 学校ID(即用户当前身份的 schId) + */ + List getSchTeaList(int schId); + + /** + * 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。 + * 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}} + */ + SchInfoDto getSchFirst(int schId); + + /** + * 学校学段(学制)列表:小学部 / 初中部…。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=} + */ + List getSchLenList(String schSerId); + + /** + * 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=} + */ + List getSchGradeList(String schSerId); + + /** + * 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。 + * 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=} + */ + List getSchClassList(String schSerId); + + /** + * 按校全部学生(圈人:全校范围展开)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=} + */ + List getSchStuList(String schSerId); + + /** + * 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=} + */ + List getGradeStuList(String schSerId, Integer gradeId); + + /** + * 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=} + */ + List getSubStuList(String schSerId, Integer schSystemId); + + /** + * 按班级批量查学生(圈人:班级范围一次多班)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3} + */ + List getClaArrStuList(List classIds); + + /** + * 按学生ID批量查(圈人:指定学员范围)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3} + */ + List getStuIdsList(List stuIds); + + /** + * 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。 + * 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。 + * 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=} + */ + List getTeaTree(Integer schId, boolean toUser); + + List getStuTree(Integer schId,boolean toUser); + + /** + * 学校学科列表(含学科编码、名称、排序号)。 + * 对应:GET {@code {bschclass}/oapi/SubInfo/GetSchSublist?SchID=} + */ + List getSchSubList(String schSerId); + + /** + * 指定教师的全部任教(校验教师归属该校;SchID 过滤学校、UserTeaSerID 过滤教师)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=&UserTeaSerID=} + */ + List getSchTeachList(String schSerId, Integer teaId); + + /** + * 指定班级的全部任教(班级维度:该班所有教师与学科)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchClaTeachlist?ClassID=} + */ + List getSchClaTeachList(Integer classId); + + /** + * 学校全部任教(全校所有教师的班级任教关系)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=} + */ + List getSchAllTeachList(String schSerId); +} diff --git a/src/main/java/com/yunhai/homework/service/IContactsService.java b/src/main/java/com/yunhai/homework/service/IContactsService.java new file mode 100644 index 0000000..0e1fdce --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IContactsService.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 联系组业务接口。 + * 对应前端 Contacts 组接口:AddGroup/GetGroup/EditGroup/DelGroup/SetContactsTea/GetContactsTea/GetGroupTree/GetDeptRoleGroupTree/Import/Export。 + */ +public interface IContactsService { + + /** + * 添加联系组。 + * + * @param name 组名 + * @param type 类型(1 自定义组, 2 明信片) + */ + void addGroup(String name, Integer type); + + /** + * 获取联系组列表(含每组人数,分页)。 + * + * @param page 页码(从1起) + * @param count 每页条数 + * @param type 类型(1 自定义组, 2 明信片) + * @return Map: { list, total } + */ + Map getGroup(Integer page, Integer count, Integer type); + + /** + * 编辑联系组。 + * + * @param id 组ID + * @param name 组名 + * @param type 类型 + */ + void editGroup(Integer id, String name, Integer type); + + /** + * 删除联系组(支持批量,同时删除组内成员)。 + * + * @param ids 逗号分隔的组ID + */ + void delGroup(String ids); + + /** + * 设置联系组教师(全量替换:先删后增)。 + * + * @param gid 组ID + * @param ids 逗号分隔的用户ID + */ + void setContactsTea(Integer gid, String ids); + + /** + * 获取联系组教师列表(分页)。 + * + * @param gid 组ID + * @param page 页码 + * @param count 每页条数 + * @return Map: { list, total } + */ + Map getContactsTea(String gid, Integer page, Integer count); + + /** + * 获取联系组树(组节点+用户节点,递归构建)。 + * + * @param type 类型(1 自定义组, 2 明信片) + * @return 树形结构 List<Map> + */ + List> getGroupTree(Integer type); + + /** + * 获取部门角色组树。 + * C# 从外部API获取部门树+用户角色映射。Java版预留。 + * + * @return 树形结构 List<Map> + */ + List> getDeptRoleGroupTree(); +} diff --git a/src/main/java/com/yunhai/homework/service/IDictService.java b/src/main/java/com/yunhai/homework/service/IDictService.java new file mode 100644 index 0000000..31ef515 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IDictService.java @@ -0,0 +1,75 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 系统字典业务接口。 + * 对应前端 Base 组接口:GetSysDic/GetDictMangeList/AddDeptDic/UpdateDeptDic/DelDept/OrdbyDicIds/GetDeptSync/GetSchModuleIsSms。 + */ +public interface IDictService { + + /** + * 获取系统字典(下拉框用)。 + * C# GetSysDic: Type=1 部门类别, 其余类型略。 + * + * @param type 字典类型编号(1=部门类别) + * @return List<Map> 每项 {k, v} + */ + List> getSysDic(Integer type); + + /** + * 获取系统字典管理列表(含 SerID/CodeID/Name/Des/Enable)。 + * + * @param type 字典类型编号(0=部门类别) + * @return List<Map> + */ + List> getDictMangeList(String type); + + /** + * 添加发文单位。 + * + * @param name 单位名称 + */ + void addDeptDic(String name); + + /** + * 更新发文单位。 + * + * @param serID 记录ID + * @param name 名称 + * @param enable 启用状态 + */ + void updateDeptDic(Integer serID, String name, Integer enable); + + /** + * 删除发文单位(校验是否被作业引用)。 + * + * @param serID 记录ID + */ + void delDept(Integer serID); + + /** + * 发文单位排序。 + * + * @param ids 逗号分隔的 SerID 列表(按前端顺序) + */ + void ordbyDicIds(String ids); + + /** + * 本校是否开通短信模块(前端 dict/Sync,用于"本校是否开通短信"判断)。 + * + * @param schId 当前学校ID(取当前登录用户角色所属学校) + * @return 开通返回 true,未开通/未配置返回 false + */ + Boolean getSchModuleIsSms(Integer schId); + + /** + * 按发文部门字典 key(CodeID)解析部门名称;未命中返回 null。 + * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 + * + * @param deptId 作业 deptId(存的是 sys_dict.dict_key) + * @return 部门名称;无匹配时返回 null + */ + String resolveDeptName(String deptId); +} diff --git a/src/main/java/com/yunhai/homework/service/IFileService.java b/src/main/java/com/yunhai/homework/service/IFileService.java new file mode 100644 index 0000000..1b01bbf --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IFileService.java @@ -0,0 +1,94 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.UpFile; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.model.homework.UpFileResultVo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件上传/下载/删除业务接口。 + */ +public interface IFileService { + /** + * 上传单个文件。 + * + * @param file 上传的文件 + * @param appType 应用类型(如 "homework") + * @param topicId 关联主题ID(可为空,作业保存后再绑定) + * @return 文件视图对象 + */ + UpFileVo upload(MultipartFile file, String appType, Integer topicId); + + /** + * 将已上传但未绑定主题的附件绑定到指定作业。 + * + * @param fileIds 文件记录ID列表 + * @param topicId 作业ID + */ + void bindToTopic(List fileIds, Integer topicId); + + /** + * 查询某主题下的附件列表。 + * + * @param appType 应用类型 + * @param topicId 主题ID + * @return 附件列表 + */ + List listByTopic(String appType, Integer topicId); + + /** + * 下载文件。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void download(Integer fileId, HttpServletResponse response); + + /** + * 在线预览文件(Content-Disposition: inline,按扩展名输出真实 Content-Type, + * 供前端 open-file-viewer 取 Blob / video 播放)。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void preview(Integer fileId, HttpServletResponse response); + + /** + * 删除文件(同时删除物理文件和数据库记录)。 + * + * @param fileId 文件记录ID + */ + void delete(Integer fileId); + + /** + * 打包下载多个附件为 ZIP。 + * + * @param files 附件记录列表(调用方按 fileIds 查好) + * @param zipName 压缩包显示名(自动追加 .zip) + * @param response HTTP 响应 + */ + void downloadZip(List files, String zipName, HttpServletResponse response); + + /** + * 按主题类型和主题ID打包下载附件为 ZIP(Controller 便捷方法,内部自行查附件列表)。 + * + * @param topicType 主题类型(如 "homework") + * @param topicId 主题ID + * @param zipName 压缩包显示名 + * @param response HTTP 响应 + */ + void downloadZipByTopic(String topicType, Integer topicId, String zipName, HttpServletResponse response); + + /** + * 批量上传图片(循环上传,返回每个文件的视图对象列表)。 + */ + List uploadImages(MultipartFile[] files, String appType); + + /** + * 上传单个文件并返回前端兼容的结果 VO(含 fileid/name/fileurl/host/type/size)。 + */ + UpFileResultVo uploadToResult(MultipartFile file, String appType); +} diff --git a/src/main/java/com/yunhai/homework/service/IHomeworkService.java b/src/main/java/com/yunhai/homework/service/IHomeworkService.java new file mode 100644 index 0000000..ed50167 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IHomeworkService.java @@ -0,0 +1,142 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业业务接口。 + */ +public interface IHomeworkService { + + /** + * 发作业:以当前登录用户为发布人,状态置为「已发布」入库。 + * 若 request.timingSendTime 不为空且晚于当前时间,则作为定时发送, + * 未到点前用户端收件箱不可见(到点后由查询过滤自然可见)。 + * + * @return 新建作业 ID(用于后续回填附件 TopicID 等) + */ + Integer publish(HomeworkRequest request); + + /** + * 编辑作业:按 id 更新标题/内容/类型/部门/置顶,发布人与状态保持不变。 + */ + void edit(Integer id, HomeworkRequest request); + + /** + * 批量删除作业:删除主记录并级联清理阅读记录、接收人、附件关联。 + */ + void deleteBatch(List ids); + + /** + * 撤回作业:仅已发布且未撤回的作业可撤回,置 isPulled=1。 + */ + void pull(Integer id); + + /** + * 置顶/取消置顶。 + */ + void setTopmost(Integer id, Integer topmost); + + /** + * 转发作业:基于源作业创建新作业(已发布),parentId 关联源作业。 + */ + Integer forward(Integer sourceId, HomeworkRequest request); + + /** + * 用户端列表:仅展示已发布且已到点的作业,并标记当前用户是否已读,附未读总数。 + * 返回 HomeworkPageResult(扩展 unreadCount)。 + */ + HomeworkPageResult userList(HomeworkQuery query, Pageable pageable); + + /** + * 管理端列表:按条件查询全部作业(管理员查看全部)。 + */ + BasePageResult adminList(HomeworkQuery query, Pageable pageable); + + /** + * 作业详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。 + */ + HomeworkVo detail(Integer id); + + /** + * 编辑回显详情(不标记已读)。 + */ + HomeworkVo getDetail(Integer id); + + /** + * 标记已读:为当前用户写入(幂等)该作业的阅读记录。 + */ + void markRead(Integer id); + + /** + * 一键全部已读(批量化):当前用户所有未读的已发布作业批量插入阅读记录。 + * @return 已读条数 + */ + int initializeRed(); + + /** + * 追加接收人(事务化):并入接收人列表并重算 allowcount。 + */ + void receiveUsers(Integer homeworkId, String users); + + /** + * 上一篇/下一篇作业ID(SQL 相邻查询)。 + * @param serid 当前作业ID + * @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列) + * @return {prevId, nextId} + */ + Map getNextPrev(Integer serid, String type); + + /** + * 获取某作业的阅读明细列表。 + */ + List getReadDetail(Integer homeworkId); + + /** + * 阅读情况分组(已读/未读)。 + */ + List> getReadDetailGrouped(Integer serid); + + /** + * 催收未读:枚举未读接收人,后续接入消息推送。 + */ + void expedite(Integer serid); + + /** + * 作业统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。 + */ + HomeworkStatisticsVo statistics(HomeworkQuery query, LocalDateTime start, LocalDateTime end); + + /** + * 作业详情视图(含附件、接收人信息),供 Controller 返回。 + */ + HomeworkDetailVo getDetailMap(Integer serid); + + /** + * 已发列表附件批量查询(消除 N+1)。 + */ + Map> batchFileIds(List homeworkIds); + + /** + * 查看作业详情视图(含附件、接收人)。 + * type=1 时先标记已读再返回,type=0 时直接返回不标记。 + */ + HomeworkDetailVo detailMap(Integer serid, Integer type); + + /** + * 一键全部已读并返回结果(count = 已读条数)。 + */ + InitializeRedVo initializeRedResult(); + + /** + * 批量下载作业全部附件(打包 zip):查作业标题后调文件域打包下载。 + */ + void downloadAllFiles(Integer dataid, HttpServletResponse response); +} diff --git a/src/main/java/com/yunhai/homework/service/ISmsService.java b/src/main/java/com/yunhai/homework/service/ISmsService.java new file mode 100644 index 0000000..09b9b5f --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ISmsService.java @@ -0,0 +1,77 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置业务接口。 + * 对应前端 setSms 组接口:getmodule/getgroup/getschsms/getsch/setschsms/setschallsms/setschmodulesms。 + */ +public interface ISmsService { + + /** + * 获取短信模块列表。 + * C# 从 AuthConfig.AppKeyMap 读取。Java版从 sms_module 表读取。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getModuleList(); + + /** + * 获取学校父类组列表。 + * C# 从外部BLL获取学校组。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchParentGroup(); + + /** + * 分页获取学校短信开关列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param schName 学校名称(模糊搜索) + * @return Map: { list, total } + */ + Map getSchIsSmsList(Integer pageSize, Integer pageIndex, String schName); + + /** + * 获取学校列表。 + * C# 从外部BLL获取学校列表。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchList(); + + /** + * 设置单校单模块短信开关。 + * + * @param schSerID 学校ID + * @param moduleId 模块ID + * @param isSms 是否开通 + */ + void setSchSms(Integer schSerID, Integer moduleId, Boolean isSms); + + /** + * 设置某校全部模块短信开关。 + * + * @param schSerID 学校ID + * @param isSms 是否开通 + */ + void setSchSmsAll(Integer schSerID, Boolean isSms); + + /** + * 批量设置多校多模块短信开关。 + * + * @param schSerIDs 逗号分隔的学校ID + * @param modules 逗号分隔的模块ID + * @param isSms 是否开通 + */ + void setSchModuleSms(String schSerIDs, String modules, Boolean isSms); + + /** + * 从 JSON 字符串解析并设置单校单模块短信开关。 + * 解析 SchSerID/ModuID/IsSms 三个字段。 + */ + void setSchSmsFromJson(String itemsModuJson); +} diff --git a/src/main/java/com/yunhai/homework/service/ITemplateService.java b/src/main/java/com/yunhai/homework/service/ITemplateService.java new file mode 100644 index 0000000..5594401 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ITemplateService.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板业务接口。 + * 对应前端 Temp 组接口:GetTypelist/EditType/DelType/GetTemplatelist/EditTemplate/DelTemplate/GetTemplateFist。 + */ +public interface ITemplateService { + + /** + * 获取模板类型列表(首项固定为"全部")。 + * + * @return List<Map> 每项 {TypeID, TypeName} + */ + List> getTypelist(); + + /** + * 新增/编辑模板类型。 + * + * @param typeid 类型ID(0=新增) + * @param typeName 类型名称 + */ + void editType(Integer typeid, String typeName); + + /** + * 删除模板类型(校验是否有模板引用此类型)。 + * + * @param typeid 类型ID + */ + void delType(Integer typeid); + + /** + * 分页获取模板列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param typeid 类型ID(0=全部) + * @return Map: { list, total } + */ + Map getTemplatelist(Integer pageSize, Integer pageIndex, Integer typeid); + + /** + * 新增/编辑模板。 + * + * @param serID 模板ID(null=新增) + * @param title 标题 + * @param content 内容 + * @param typeId 类型ID + * @param cover 封面文件ID + */ + void editTemplate(Integer serID, String title, String content, Integer typeId, Integer cover); + + /** + * 删除模板。 + * + * @param serID 模板ID + */ + void delTemplate(Integer serID); + + /** + * 获取单个模板详情。 + * + * @param serID 模板ID + * @return Map 模板详情 + */ + Map getTemplateFist(Integer serID); +} diff --git a/src/main/java/com/yunhai/homework/service/IUserCenterService.java b/src/main/java/com/yunhai/homework/service/IUserCenterService.java new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IUserCenterService.java @@ -0,0 +1,51 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 用户中心门面服务(对齐 UserBase {@code UserCenterService} 模式)。 + * 封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。 + */ +public interface IUserCenterService { + + /** + * 获取当前用户学校组及用户(树形结构)。 + * 组装规则(从原 BaseApiController.getSchoolAll 下沉): + * + * userbase 读取当前学校整棵组树(getGroupById(schId, withSub=true)),剔除学生组(type=8)及其子组; + * bschclass 获取该校全部教师(/oapi/DbClass/GetSchteaList?Group=schId),按 GroupIds 挂到对应组下, + * 无有效组(如所属组全被剔除)的教师兜底挂学校根节点; + * 扁平节点按 pid 递归组装为树(空组剔除,同 C# FormatUserTree)。 + * + * + * @param schId 学校ID(用户当前角色所属学校) + * @return 用户树形结构(组节点 type=1 / 教师节点 type=2) + */ + List> getSchoolAllTree(Integer schId); + + /** + * 获取指定组内用户(教师 + 管理员)。 + * 对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。 + * + * @param gid 组ID + * @param role 角色(默认 2=教师;1=管理员等) + * @return 组内用户列表 + */ + List> getGroupUser(Integer gid, Integer role); + + /** + * 获取当前用户可访问的模块树(左侧菜单)。 + * 从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。 + * + * @param userId 用户ID + * @param schId 学校ID(可为 null) + * @return 模块树(顶层 List,含 children 嵌套) + */ + List> getUserModuleTree(Integer userId, Integer schId); +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/service/impl/ClassService.java b/src/main/java/com/yunhai/homework/service/impl/ClassService.java new file mode 100644 index 0000000..8be5e2a --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/impl/ClassService.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.service.impl; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.service.IClassService; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班级管理(bschclass)开放接口实现。 + * bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。 + * 推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List
+ * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据),每日全量刷 + 手动触发; + * 同步不物理删,转出置 status=0。学生身份口径:用户中心数字ID(UserContext.getUserId()) + * ↔ student_id(SchStudent.UserStuSerID),名册仅存学生ID + 姓名快照。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "class_student", + uniqueConstraints = @UniqueConstraint(name = "uk_class_student", columnNames = {"class_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ClassStudent extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; + + /** 年级名快照 */ + @Column(name = "grade_name", length = 50) + private String gradeName; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 在班状态:1 在班 / 0 已转出退课 */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** 离班时间 */ + @Column(name = "leave_at") + private LocalDateTime leaveAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroup.java b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java new file mode 100644 index 0000000..a002e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroup.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 联系组。对应原 .NET Contacts_Group 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroup extends BaseEntity { + + /** 组名称 */ + @Column(name = "group_name", length = 200) + private String groupName; + + /** 创建人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "creator_id") + private Integer creatorId; +} diff --git a/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java new file mode 100644 index 0000000..eb07763 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/ContactsGroupUser.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 联系组-用户关联。对应原 .NET Contacts_Tea 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "contacts_group_user", + uniqueConstraints = @UniqueConstraint(name = "uk_group_user", + columnNames = {"group_id", "user_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class ContactsGroupUser extends BaseEntity { + + /** 联系组ID */ + @Column(name = "group_id", nullable = false) + private Integer groupId; + + /** 用户ID(基础平台用户ID) */ + @Column(name = "user_id", nullable = false) + private Integer userId; + + /** 用户姓名 */ + @Column(name = "user_name", length = 64) + private String userName; +} diff --git a/src/main/java/com/yunhai/homework/entity/Homework.java b/src/main/java/com/yunhai/homework/entity/Homework.java new file mode 100644 index 0000000..6e615f2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Homework.java @@ -0,0 +1,208 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 课程作业主表。对应原 .NET Notify_Message(已剔除短信/模板/门户等无关字段)。 + *
+ * 阅读状态不落主表:采用「懒标记」模型——读者首次查看时写入 {@link HomeworkRead}, + * 不存在阅读记录即视为未读,从而无需在发作业时枚举全校用户预插接收行。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Homework extends BaseEntity { + + /** + * 作业标题 + */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** + * 学校ID(多租户隔离:发布时从当前用户角色填充,查询时按校过滤)。 + */ + @Column(name = "sch_id") + private Integer schId; + + /** + * 作业内容 + */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** + * 作业类型:1 普通 / 2 重要(见 {@link com.yunhai.homework.enums.HomeworkTypeEnum}) + */ + @Column(name = "type", nullable = false) + private Integer type = 1; + + /** + * 发布人ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "publisher_id") + private Integer publisherId; + + /** + * 发布人姓名 + */ + @Column(name = "publisher_name", length = 64) + private String publisherName; + + /** + * 发布部门ID(原 DeptID) + */ + @Column(name = "dept_id", length = 64) + private String deptId; + + /** + * 发布部门名称 + */ + @Column(name = "dept_name", length = 128) + private String deptName; + + /** + * 状态:1 已发布(无草稿态,见 {@link com.yunhai.homework.enums.HomeworkStatusEnum}) + */ + @Column(name = "status", nullable = false) + private Integer status = 1; + + /** + * 是否置顶:0 否 / 1 是(原 topmost) + */ + @Column(name = "topmost") + private Integer topmost = 0; + + // ==================== 迁移补全字段(对应 C# Notify_Message) ==================== + + /** + * 接收人列表(JSON 数组,如 ["10012","10013"] 或接收组ID列表)。 + * 对应 C# Notify_Message.ReceiveList + */ + @Column(name = "receive_list", columnDefinition = "LONGTEXT") + private String receiveList; + + /** + * 密码发送(对应 C# Notify_Message.PwdSend) + */ + @Column(name = "pwd_send", length = 255) + private String pwdSend; + + /** + * 抄送人列表(对应 C# Notify_Message.ChaoSend) + */ + @Column(name = "chao_send", length = 2000) + private String chaoSend; + + /** + * 已读数(发送时初始化为 0,也可由 HomeworkRead 表 COUNT 实时计算)。 + * 对应 C# Notify_Message.readcount + */ + @Column(name = "read_count") + private Integer readCount = 0; + + /** + * 应读数(接收人总数)。对应 C# Notify_Message.allowcount + */ + @Column(name = "allow_count") + private Integer allowCount = 0; + + /** + * 父作业ID(转发时关联原作业)。对应 C# Notify_Message.fkid + */ + @Column(name = "parent_id") + private Integer parentId; + + /** + * 回复ID。对应 C# Notify_Message.reid + */ + @Column(name = "reply_id", length = 255) + private String replyId; + + /** + * 是否撤回:0 否 / 1 是。对应 C# Notify_Message.ispull + */ + @Column(name = "is_pulled") + private Integer isPulled = 0; + + /** + * 定时发送时间(到时间后自动发布)。对应 C# Notify_Message.DsfsTime。 + * PRD 语义中的 scheduled_at 复用本列。 + */ + @Column(name = "timing_send_time") + private LocalDateTime timingSendTime; + + /** + * 短信发送标记:0 不发短信 / 1 发短信。对应 C# Notify_Message.dxbit + */ + @Column(name = "sms_flag") + private Integer smsFlag = 0; + + // ==================== 课堂作业 V3(PRD V1.0)扩展列 ==================== + + /** + * 学科(字典值)。 + */ + @Column(name = "subject", length = 50) + private String subject; + + /** + * 截止时间(精确到分钟;空 = 仅查阅型,不设截止,到点关闭任务跳过)。 + */ + @Column(name = "deadline_at") + private LocalDateTime deadlineAt; + + /** + * 作业流程状态(PRD 五态,见 {@link com.yunhai.homework.enums.HwStatusEnum})。 + * notice 原 status 列继续承载发布/撤回语义,两者并存。 + */ + @Column(name = "hw_status", nullable = false, length = 20) + private String hwStatus = com.yunhai.homework.enums.HwStatusEnum.DRAFT_UNPUBLISHED.getCode(); + + /** + * 发布时间(立即发布或定时到点发布时写入)。 + */ + @Column(name = "published_at") + private LocalDateTime publishedAt; + + /** + * 提交方式,JSON 数组文本,如 ["image","attach"]。 + * 取值:text / image / attach / none(PRD 的 图片/文件/两者 映射为 + * ["image"] / ["attach"] / ["image","attach"])。 + */ + @Column(name = "submit_type", length = 100) + private String submitType; + + /** + * 截止后是否允许逾期提交:0 否 / 1 是。 + */ + @Column(name = "allow_late", nullable = false) + private Integer allowLate = 0; + + /** + * 提醒设置:截止前 1 天提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1d", nullable = false) + private Integer remind1d = 1; + + /** + * 提醒设置:截止前 1 小时提醒(0 关 / 1 开)。 + */ + @Column(name = "remind_1h", nullable = false) + private Integer remind1h = 1; + + /** + * 归档时间(教师手动归档时写入;归档后只读,所有提醒任务跳过)。 + */ + @Column(name = "archived_at") + private LocalDateTime archivedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java new file mode 100644 index 0000000..6fc7804 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkAttachment.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业附件表(PRD homework_attachment)。文件实体仍存 up_file(file_id 关联 + file_url 冗余)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_attachment") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkAttachment extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** up_file.id(可空:允许外部 URL 附件) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file / ... */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号(越小越靠前) */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkClass.java b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java new file mode 100644 index 0000000..c01cefb --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkClass.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 作业-班级关联表(PRD homework_class)。 + * 发布/编辑时由 receiveList("all" 或班级 id 串)解析写入,是应交名单与统计的权威关联。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_class", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_class", columnNames = {"homework_id", "class_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkClass extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", length = 50) + private String className; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkRead.java b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java new file mode 100644 index 0000000..35b773a --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkRead.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.*; +import java.time.LocalDateTime; + +/** + * 作业阅读记录(懒标记)。对应原 .NET Notify_ReceiveList 的「逐用户阅读状态」语义, + * 但仅在读者首次查看时落库,避免发作业时预插全员。 + *
+ * 唯一约束 (homework_id, reader_id):同一用户对同一作业只记一条已读。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read", + uniqueConstraints = @UniqueConstraint( + name = "uk_homework_reader", + columnNames = {"homework_id", "reader_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkRead extends BaseEntity { + + /** + * 作业ID(关联 homework.id) + */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** + * 读者ID(用户中心数字ID,同教师ID空间) + */ + @Column(name = "reader_id", nullable = false) + private Integer readerId; + + /** + * 读者姓名 + */ + @Column(name = "reader_name", length = 64) + private String readerName; + + /** + * 阅读时间 + */ + @Column(name = "read_time") + private LocalDateTime readTime; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java new file mode 100644 index 0000000..8179e79 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReadRecord.java @@ -0,0 +1,58 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.time.LocalDateTime; + +/** + * 学生作业阅读记录(PRD homework_read_record)。 + *
+ * 学生维度,含首次/最近阅读时间,与 notice 语义的 {@link HomeworkRead}(教师/阅读者已读)互不影响。 + * 行仅在判定已读时写入;未读名单 = 名册左连本表(无记录或 is_read=0)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_read_record", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student", columnNames = {"homework_id", "student_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReadRecord extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", length = 64) + private String classId; + + /** 阅读状态:0 UNREAD / 1 READ */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 首次阅读时间(写入后不再变更) */ + @Column(name = "first_read_at") + private LocalDateTime firstReadAt; + + /** 最近阅读时间(每次上报刷新) */ + @Column(name = "last_read_at") + private LocalDateTime lastReadAt; + + /** 阅读次数 */ + @Column(name = "read_count", nullable = false) + private Integer readCount = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java new file mode 100644 index 0000000..97e068b --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkReceiver.java @@ -0,0 +1,45 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 作业接收人关联表(替代旧 receive_list JSON 字符串存储)。 + *
+ * 每条记录代表一个作业的一个接收人(用户ID + 姓名快照,不含账号)。 + * 全员作业(receive_list="ALL")不写明细行, + * 读取时按 receive_list="ALL" 标识走旧逻辑兜底。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_receiver", + indexes = { + @Index(name = "idx_homework_id", columnList = "homework_id"), + @Index(name = "idx_receiver", columnList = "receiver_id") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkReceiver extends BaseEntity { + + /** 作业ID(关联 homework.id) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 接收人ID(用户中心数字ID) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收类型:1 接收 / 2 抄送 */ + @Column(name = "receive_type", nullable = false) + private Integer receiveType = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java new file mode 100644 index 0000000..4626044 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkSubmission.java @@ -0,0 +1,98 @@ +package com.yunhai.homework.entity; + +import com.yunhai.homework.enums.ReviewStatusEnum; +import com.yunhai.homework.enums.SubmitStatusEnum; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 作业提交表,多版本建模(PRD homework_submission)。 + *
+ * 同一学生 (homework_id, student_id) 下 version 单调递增;提交/打回重交/撤回均新增行, + * 旧行 is_latest 置 0 保留历史;is_latest=1 的行是当前有效版本。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_submission", + uniqueConstraints = @UniqueConstraint(name = "uk_hw_student_ver", + columnNames = {"homework_id", "student_id", "version"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkSubmission extends BaseEntity { + + /** 作业ID */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID,即 SchStudent.UserStuSerID) */ + @Column(name = "student_id", nullable = false) + private Integer studentId; + + /** 学生姓名快照 */ + @Column(name = "student_name", nullable = false, length = 50) + private String studentName; + + /** 班级ID */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 班级名快照 */ + @Column(name = "class_name", nullable = false, length = 50) + private String className; + + /** 提交版本,同一学生单调递增(撤回/重交均 +1) */ + @Column(name = "version", nullable = false) + private Integer version = 1; + + /** 是否当前版本:1 是 / 0 历史 */ + @Column(name = "is_latest", nullable = false) + private Integer isLatest = 1; + + /** 提交状态(见 {@link SubmitStatusEnum}) */ + @Column(name = "status", nullable = false, length = 20) + private String status = SubmitStatusEnum.SUBMITTED.getCode(); + + /** 是否逾期:0 否 / 1 是 */ + @Column(name = "is_late", nullable = false) + private Integer isLate = 0; + + /** 提交时间(NOT_SUBMITTED 空行为空) */ + @Column(name = "submitted_at") + private LocalDateTime submittedAt; + + /** 文本作业内容 */ + @Column(name = "text", columnDefinition = "TEXT") + private String text; + + /** 评分 */ + @Column(name = "score", precision = 6, scale = 2) + private BigDecimal score; + + /** 评语(打回时 = 订正原因,必填) */ + @Column(name = "comment", columnDefinition = "TEXT") + private String comment; + + /** 批阅状态(见 {@link ReviewStatusEnum}) */ + @Column(name = "review_status", nullable = false, length = 20) + private String reviewStatus = ReviewStatusEnum.PENDING.getCode(); + + /** 批阅人ID(用户中心数字ID,同教师ID空间) */ + @Column(name = "reviewed_by") + private Integer reviewedBy; + + /** 批阅人姓名快照 */ + @Column(name = "reviewed_by_name", length = 64) + private String reviewedByName; + + /** 批阅时间 */ + @Column(name = "reviewed_at") + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java new file mode 100644 index 0000000..f088c92 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/HomeworkTemplate.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板。对应原 .NET Notify_Template 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "homework_template") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class HomeworkTemplate extends BaseEntity { + + /** 模板标题 */ + @Column(name = "title", length = 200) + private String title; + + /** 模板内容(HTML) */ + @Column(name = "content", columnDefinition = "LONGTEXT") + private String content; + + /** 封面图片文件ID */ + @Column(name = "cover") + private Integer cover; + + /** 引用次数 */ + @Column(name = "ref_count") + private Integer refCount = 0; + + /** 模板类型ID */ + @Column(name = "type_id") + private Integer typeId; +} diff --git a/src/main/java/com/yunhai/homework/entity/Notification.java b/src/main/java/com/yunhai/homework/entity/Notification.java new file mode 100644 index 0000000..2a931a1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/Notification.java @@ -0,0 +1,71 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; +import java.time.LocalDateTime; + +/** + * 站内通知(PRD notification)。六类通知的统一落表 + 已读状态。 + * 通知已读独立于作业阅读记录(本表 is_read 只反映通知列表已读)。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "notification", + indexes = { + @Index(name = "idx_receiver", columnList = "receiver_id, is_read"), + @Index(name = "idx_hw_type", columnList = "homework_id, type") + }) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class Notification extends BaseEntity { + + /** 通知类型(见 {@link com.yunhai.homework.enums.NotificationTypeEnum}) */ + @Column(name = "type", nullable = false, length = 30) + private String type; + + /** 关联作业ID */ + @Column(name = "homework_id") + private Integer homeworkId; + + /** 关联业务ID(如 submissionId) */ + @Column(name = "related_id") + private Integer relatedId; + + /** 通知标题 */ + @Column(name = "title", nullable = false, length = 200) + private String title; + + /** 通知内容 */ + @Column(name = "content", length = 1000) + private String content; + + /** 接收人ID(学生 = student_id,教师 = 用户中心数字ID,同一 ID 空间) */ + @Column(name = "receiver_id", nullable = false) + private Integer receiverId; + + /** 接收人姓名快照 */ + @Column(name = "receiver_name", length = 64) + private String receiverName; + + /** 接收人角色:student / teacher */ + @Column(name = "receiver_role", nullable = false, length = 20) + private String receiverRole = "student"; + + /** 是否已读:0 未读 / 1 已读 */ + @Column(name = "is_read", nullable = false) + private Integer isRead = 0; + + /** 已读时间 */ + @Column(name = "read_at") + private LocalDateTime readAt; + + /** 发送通道:inapp 站内 / sms 短信 */ + @Column(name = "send_channel", nullable = false, length = 20) + private String sendChannel = "inapp"; +} diff --git a/src/main/java/com/yunhai/homework/entity/SchClassTeach.java b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java new file mode 100644 index 0000000..d1a091f --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SchClassTeach.java @@ -0,0 +1,46 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; + +/** + * 班级任教关系快照(bschclass sch_class_teach 本地镜像,PRD 口径同 class_student)。 + *
+ * 由同步任务从 bschclass 拉取(接口未就绪前用种子数据);bschclass 任教接口 + * 不可用时,任教学科链路降级读本表。教师口径:用户中心数字ID(UserContext.getUserId()); + * class_id 与 class_student.class_id 同一取值域。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sch_class_teach", + uniqueConstraints = @UniqueConstraint(name = "uk_sch_class_teach", columnNames = {"school_id", "class_id", "tea_id", "sub_id"})) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SchClassTeach extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 班级ID(与 class_student.class_id 同一取值域) */ + @Column(name = "class_id", nullable = false, length = 64) + private String classId; + + /** 教师ID(用户中心数字ID) */ + @Column(name = "tea_id", nullable = false) + private Integer teaId; + + /** 学科编码(bschclass SubID;本地无字典时编码即展示名) */ + @Column(name = "sub_id", nullable = false, length = 64) + private String subId; + + /** 有效状态:1 有效 / 0 失效 */ + @Column(name = "status", nullable = false) + private Integer status = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsModule.java b/src/main/java/com/yunhai/homework/entity/SmsModule.java new file mode 100644 index 0000000..3f4d444 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsModule.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 短信模块配置。对应原 .NET SchSms 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_module") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsModule extends BaseEntity { + + /** 模块名称 */ + @Column(name = "module_name", length = 100) + private String moduleName; + + /** 模块标识 */ + @Column(name = "module_code", length = 50) + private String moduleCode; +} diff --git a/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java new file mode 100644 index 0000000..428d271 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SmsSchoolConfig.java @@ -0,0 +1,37 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 学校短信开通配置。对应原 .NET SchSms_Set 表。 + * 记录某学校某模块是否开通短信。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sms_school_config") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SmsSchoolConfig extends BaseEntity { + + /** 学校ID */ + @Column(name = "school_id", nullable = false) + private Integer schoolId; + + /** 学校名称 */ + @Column(name = "school_name", length = 200) + private String schoolName; + + /** 模块ID */ + @Column(name = "module_id") + private Integer moduleId; + + /** 是否开通短信 */ + @Column(name = "is_sms") + private Boolean isSms = false; +} diff --git a/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java new file mode 100644 index 0000000..85df7ba --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SubmissionAttachment.java @@ -0,0 +1,54 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +/** + * 提交附件表(PRD submission_attachment)。替代 JSON 方案,支撑矩阵缩略图与批阅大图预览。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "submission_attachment", + indexes = @Index(name = "idx_sub", columnList = "submission_id")) +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SubmissionAttachment extends BaseEntity { + + /** 提交行ID(homework_submission.id) */ + @Column(name = "submission_id", nullable = false) + private Integer submissionId; + + /** 作业ID(冗余,便于统计与清理) */ + @Column(name = "homework_id", nullable = false) + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + @Column(name = "student_id") + private Integer studentId; + + /** up_file.id(可空:允许外部 URL) */ + @Column(name = "file_id") + private Integer fileId; + + /** 文件访问 URL */ + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + /** 文件类型:image / file */ + @Column(name = "file_type", length = 20) + private String fileType; + + /** 文件名 */ + @Column(name = "file_name", length = 200) + private String fileName; + + /** 排序号 */ + @Column(name = "sort", nullable = false) + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/SysDict.java b/src/main/java/com/yunhai/homework/entity/SysDict.java new file mode 100644 index 0000000..3091084 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/SysDict.java @@ -0,0 +1,40 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 系统字典(发文部门等)。对应原 .NET Common_DeptDic 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "sys_dict") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class SysDict extends BaseEntity { + + /** 字典类型(如 "dept"=发文部门) */ + @Column(name = "dict_type", length = 50) + private String dictType; + + /** 字典 key(原 CodeID) */ + @Column(name = "dict_key", length = 100) + private String dictKey; + + /** 字典 value(显示名称,原 Name) */ + @Column(name = "dict_value", length = 200) + private String dictValue; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; + + /** 是否启用:0 禁用 / 1 启用(原 Enable) */ + @Column(name = "enable_flag") + private Integer enableFlag = 1; +} diff --git a/src/main/java/com/yunhai/homework/entity/TemplateType.java b/src/main/java/com/yunhai/homework/entity/TemplateType.java new file mode 100644 index 0000000..aab8163 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/TemplateType.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 作业模板类型。对应原 .NET Notify_TemplateType 表。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "template_type") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class TemplateType extends BaseEntity { + + /** 类型名称 */ + @Column(name = "type_name", length = 100) + private String typeName; + + /** 排序号 */ + @Column(name = "sort_order") + private Integer sortOrder = 0; +} diff --git a/src/main/java/com/yunhai/homework/entity/UpFile.java b/src/main/java/com/yunhai/homework/entity/UpFile.java new file mode 100644 index 0000000..38248c7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/entity/UpFile.java @@ -0,0 +1,47 @@ +package com.yunhai.homework.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +/** + * 附件文件记录。对应原 .NET Common_UpFiles 表。 + *
+ * 存储文件元信息(真实文件名、存储路径、大小、类型),物理文件存储在 + * application.yml 的 file.upload-path 目录下。 + */ +@NoArgsConstructor +@Data +@Entity +@Table(name = "up_file") +@EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false) +public class UpFile extends BaseEntity { + + /** 应用类型(如 "homework"),用于区分不同业务的附件 */ + @Column(name = "topic_type", length = 50) + private String topicType; + + /** 关联主题ID(如作业 serid) */ + @Column(name = "topic_id") + private Integer topicId; + + /** 文件真实名称(用户上传时的原始文件名) */ + @Column(name = "file_true_name", length = 255) + private String fileTrueName; + + /** 文件存储路径(相对路径,如 homework/20260823/uuid.pdf) */ + @Column(name = "file_name", length = 500) + private String fileName; + + /** 文件大小(字节) */ + @Column(name = "file_size") + private Integer fileSize; + + /** 文件后缀名(如 pdf, docx, jpg) */ + @Column(name = "file_type", length = 20) + private String fileType; +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java new file mode 100644 index 0000000..124bec9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkStatusEnum.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业状态。无草稿态:发作业一律置为「已发布」, + * 定时发送通过 timingSendTime + 查询侧「已到点」过滤控制可见性。 + * 对应原 .NET Notify_Message.status(原系统已发布=2,此处归一化为 1)。 + */ +@Getter +public enum HomeworkStatusEnum { + + PUBLISHED(1, "已发布"); + + private final int code; + private final String desc; + + HomeworkStatusEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkStatusEnum of(Integer code) { + if (code == null) { + return PUBLISHED; + } + for (HomeworkStatusEnum e : values()) { + if (e.code == code) { + return e; + } + } + return PUBLISHED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java new file mode 100644 index 0000000..077396d --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HomeworkTypeEnum.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业类型。对应原 .NET Notify_Message.type(原系统作业类=2,此处归一化)。 + */ +@Getter +public enum HomeworkTypeEnum { + + NORMAL(1, "普通作业"), + IMPORTANT(2, "重要作业"); + + private final int code; + private final String desc; + + HomeworkTypeEnum(int code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HomeworkTypeEnum of(Integer code) { + if (code == null) { + return NORMAL; + } + for (HomeworkTypeEnum e : values()) { + if (e.code == code) { + return e; + } + } + return NORMAL; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java new file mode 100644 index 0000000..8b10083 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/HwStatusEnum.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 作业流程状态(PRD 五态,存 homework.hw_status)。 + *
+ * 与 notice 原 status(Integer,发布/撤回语义)并存:本枚举承载课堂作业业务流转, + * 流转收敛在 HomeworkStateService,条件更新保证幂等。 + */ +@Getter +public enum HwStatusEnum { + + /** 草稿 - 未发布 */ + DRAFT_UNPUBLISHED("DRAFT_UNPUBLISHED", "草稿-未发布"), + /** 草稿 - 定时待发布 */ + DRAFT_SCHEDULED("DRAFT_SCHEDULED", "草稿-定时待发布"), + /** 已发布 - 进行中 */ + PUBLISHED("PUBLISHED", "已发布-进行中"), + /** 已截止 */ + CLOSED("CLOSED", "已截止"), + /** 已归档(只读,不再触发任何提醒) */ + ARCHIVED("ARCHIVED", "已归档"); + + private final String code; + private final String desc; + + HwStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static HwStatusEnum of(String code) { + if (code == null) { + return DRAFT_UNPUBLISHED; + } + for (HwStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return DRAFT_UNPUBLISHED; + } + + public boolean isDraft() { + return this == DRAFT_UNPUBLISHED || this == DRAFT_SCHEDULED; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java new file mode 100644 index 0000000..df75af1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/NotificationTypeEnum.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 站内通知类型(PRD 六类,存 notification.type)。 + */ +@Getter +public enum NotificationTypeEnum { + + /** 作业发布(对应班级学生) */ + HOMEWORK_PUBLISHED("HOMEWORK_PUBLISHED", "作业发布"), + /** 截止提醒(截止前 1 天 / 1 小时) */ + DEADLINE_REMINDER("DEADLINE_REMINDER", "截止提醒"), + /** 未交提醒(教师手动) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未交提醒"), + /** 未读提醒(教师手动) */ + NOT_READ("NOT_READ", "未读提醒"), + /** 批阅完成 */ + REVIEW_DONE("REVIEW_DONE", "批阅完成"), + /** 打回通知 */ + RETURNED("RETURNED", "打回通知"), + /** + * 定时发布失败回退(PRD §17.1 教师泳道要求通知教师,但 PRD 枚举表未列—— + * 按 classwork-homework-plan-v3 §13-2 建议补充,待产品确认命名)。 + */ + SCHEDULE_FAILED("SCHEDULE_FAILED", "定时发布失败"), + /** + * 截止时间变更(PRD §17.1 延长截止要求通知学生,PRD 枚举表未列——同上补充)。 + */ + DEADLINE_CHANGED("DEADLINE_CHANGED", "截止时间变更"); + + private final String code; + private final String desc; + + NotificationTypeEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static NotificationTypeEnum of(String code) { + if (code == null) { + return null; + } + for (NotificationTypeEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return null; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java new file mode 100644 index 0000000..031eddc --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/ReviewStatusEnum.java @@ -0,0 +1,39 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 批阅状态(PRD 三态,存 homework_submission.review_status)。 + * 与 SubmitStatusEnum 同源联动:批阅保存 → status=REVIEWED + review_status=REVIEWED; + * 打回 → status=RETURNED + review_status=RETURNED。 + */ +@Getter +public enum ReviewStatusEnum { + + /** 待批阅 */ + PENDING("PENDING", "待批阅"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"), + /** 已打回 */ + RETURNED("RETURNED", "已打回"); + + private final String code; + private final String desc; + + ReviewStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static ReviewStatusEnum of(String code) { + if (code == null) { + return PENDING; + } + for (ReviewStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return PENDING; + } +} diff --git a/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java new file mode 100644 index 0000000..7d6aaca --- /dev/null +++ b/src/main/java/com/yunhai/homework/enums/SubmitStatusEnum.java @@ -0,0 +1,50 @@ +package com.yunhai.homework.enums; + +import lombok.Getter; + +/** + * 学生提交状态(PRD 五态,存 homework_submission.status)。 + *
+ * NOT_SUBMITTED 空行仅由「撤回」产生;未提交学生本身不落行(统计时由名册左连补位)。 + */ +@Getter +public enum SubmitStatusEnum { + + /** 未提交(撤回后产生的空提交行) */ + NOT_SUBMITTED("NOT_SUBMITTED", "未提交"), + /** 已提交(含打回后重新提交) */ + SUBMITTED("SUBMITTED", "已提交"), + /** 逾期提交(截止后提交,is_late=1) */ + LATE_SUBMITTED("LATE_SUBMITTED", "逾期提交"), + /** 已打回(待重新提交) */ + RETURNED("RETURNED", "已打回"), + /** 已批阅 */ + REVIEWED("REVIEWED", "已批阅"); + + private final String code; + private final String desc; + + SubmitStatusEnum(String code, String desc) { + this.code = code; + this.desc = desc; + } + + public static SubmitStatusEnum of(String code) { + if (code == null) { + return NOT_SUBMITTED; + } + for (SubmitStatusEnum e : values()) { + if (e.code.equals(code)) { + return e; + } + } + return NOT_SUBMITTED; + } + + /** + * 是否计入「已交」口径:打回未重交也算已交(PRD 学生矩阵口径)。 + */ + public boolean countsAsSubmitted() { + return this != NOT_SUBMITTED; + } +} diff --git a/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java new file mode 100644 index 0000000..5377254 --- /dev/null +++ b/src/main/java/com/yunhai/homework/job/HomeworkScheduledJobs.java @@ -0,0 +1,161 @@ +package com.yunhai.homework.job; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.enums.HwStatusEnum; +import com.yunhai.homework.enums.NotificationTypeEnum; +import com.yunhai.homework.repository.HomeworkClassRepository; +import com.yunhai.homework.repository.HomeworkRepository; +import com.yunhai.homework.service.impl.ClassStudentService; +import com.yunhai.homework.service.impl.HomeworkStateService; +import com.yunhai.homework.service.impl.NotificationService; +import com.yunhai.homework.service.impl.TeacherHomeworkV2Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 课堂作业定时任务(PRD §17.1 调度泳道)。 + *
+ * 全部基于状态机 CAS 条件更新,天然幂等,可安全重复执行; + * ARCHIVED 作业被显式跳过(归档后不再触发任何提醒)。 + */ +@Component +public class HomeworkScheduledJobs { + + private static final Logger log = LoggerFactory.getLogger(HomeworkScheduledJobs.class); + + @Autowired + private HomeworkRepository homeworkRepository; + + @Autowired + private HomeworkClassRepository homeworkClassRepository; + + @Autowired + private HomeworkStateService stateService; + + @Autowired + private NotificationService notificationService; + + @Autowired + private TeacherHomeworkV2Service teacherHomeworkV2Service; + + @Autowired + private ClassStudentService classStudentService; + + /** + * 定时发布:每分钟扫描到点的 DRAFT_SCHEDULED 作业 → PUBLISHED + 作业发布通知; + * 失败回退 DRAFT_UNPUBLISHED 并站内通知教师。 + */ + @Scheduled(cron = "0 * * * * ?") + public void scheduledPublish() { + List due = homeworkRepository + .findByHwStatusAndTimingSendTimeLessThanEqual(HwStatusEnum.DRAFT_SCHEDULED.getCode(), + LocalDateTime.now()); + if (due.isEmpty()) { + return; + } + log.info("[scheduledPublish] 到点作业 {} 个", due.size()); + for (Homework homework : due) { + Integer id = homework.getId(); + try { + teacherHomeworkV2Service.publishInternal(id, HwStatusEnum.DRAFT_SCHEDULED); + log.info("[scheduledPublish] 发布成功 homeworkId={}", id); + } catch (Exception e) { + log.error("[scheduledPublish] 发布失败,回退草稿 homeworkId={}", id, e); + try { + boolean rolledBack = stateService.rollbackFromSchedule(id); + if (rolledBack) { + notificationService.send(NotificationTypeEnum.SCHEDULE_FAILED, id, null, + homework.getPublisherId(), homework.getPublisherName(), + NotificationService.ROLE_TEACHER, + "定时发布失败:《" + homework.getTitle() + "》", + "定时发布任务执行异常,作业已回退为「草稿-未发布」,请重新发布。"); + } + } catch (Exception ex) { + log.error("[scheduledPublish] 回退失败 homeworkId={}", id, ex); + } + } + } + } + + /** + * 到点截止:每分钟扫描已过截止时间的 PUBLISHED 作业 → CLOSED(无对应通知类型,不推送)。 + */ + @Scheduled(cron = "30 * * * * ?") + public void deadlineClose() { + List overdue = homeworkRepository + .findByHwStatusAndDeadlineAtBefore(HwStatusEnum.PUBLISHED.getCode(), LocalDateTime.now()); + for (Homework homework : overdue) { + if (stateService.cas(homework.getId(), HwStatusEnum.PUBLISHED, HwStatusEnum.CLOSED)) { + log.info("[deadlineClose] 作业已截止 homeworkId={}", homework.getId()); + } + } + } + + /** + * 截止提醒:每分钟扫描截止时间在 24h 内的 PUBLISHED 作业, + * 命中「1 天档 / 1 小时档」窗口且开关开启时向未交学生推送 DEADLINE_REMINDER; + * 档位去重(related_id:1=1 天档 2=1 小时档),每作业每档至多发一轮。 + */ + @Scheduled(cron = "40 * * * * ?") + public void deadlineReminder() { + LocalDateTime now = LocalDateTime.now(); + List window = homeworkRepository + .findByHwStatusAndDeadlineAtBetween(HwStatusEnum.PUBLISHED.getCode(), now, now.plusHours(24)); + for (Homework homework : window) { + if (homework.getDeadlineAt() == null) { + continue; + } + // 1 小时档:截止前 [55, 60) 分钟窗口 + if (homework.getRemind1h() != null && homework.getRemind1h() == 1 + && inWindow(homework.getDeadlineAt(), now, 55, 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 2)) { + sendReminder(homework, 2, "1 小时"); + } + // 1 天档:截止前 [23, 24) 小时窗口 + if (homework.getRemind1d() != null && homework.getRemind1d() == 1 + && inWindow(homework.getDeadlineAt(), now, 23 * 60, 24 * 60) + && !notificationService.alreadySentSlot(homework.getId(), NotificationTypeEnum.DEADLINE_REMINDER, 1)) { + sendReminder(homework, 1, "1 天"); + } + } + } + + private boolean inWindow(LocalDateTime deadline, LocalDateTime now, int fromMinutes, int toMinutes) { + long minutes = java.time.Duration.between(now, deadline).toMinutes(); + return minutes >= fromMinutes && minutes < toMinutes; + } + + private void sendReminder(Homework homework, int slot, String slotDesc) { + // 通知接收人直接使用学生ID(名册口径:student_id = 平台用户ID) + List targets = teacherHomeworkV2Service.notSubmittedStudentIds(homework.getId()).stream() + .distinct() + .collect(Collectors.toList()); + if (targets.isEmpty()) { + return; + } + int sent = notificationService.sendBatch(NotificationTypeEnum.DEADLINE_REMINDER, homework.getId(), slot, + NotificationService.distinct(targets), + NotificationService.deadlineReminderTitle(homework), + NotificationService.deadlineReminderContent(homework) + "(剩余不足 " + slotDesc + ")"); + log.info("[deadlineReminder] homeworkId={}, slot={}, sent={}", homework.getId(), slot, sent); + } + + /** + * 名册同步:每日 02:00 全量刷新 class_student(bschclass 接口就绪前仅日志)。 + */ + @Scheduled(cron = "${homework.sync.class-data-cron:0 0 2 * * ?}") + public void classDataSync() { + try { + classStudentService.syncAll(); + } catch (Exception e) { + log.error("[classDataSync] 名册同步失败", e); + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageQuery.java b/src/main/java/com/yunhai/homework/model/BasePageQuery.java new file mode 100644 index 0000000..7f46326 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageQuery.java @@ -0,0 +1,14 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +/** + * 统一分页查询入参。pageIndex 从 1 起。 + */ +@Data +public class BasePageQuery { + private int pageIndex = 1; + private int pageSize = 10; + /** 通用模糊搜索关键词 */ + private String keyword; +} diff --git a/src/main/java/com/yunhai/homework/model/BasePageResult.java b/src/main/java/com/yunhai/homework/model/BasePageResult.java new file mode 100644 index 0000000..058c96c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/BasePageResult.java @@ -0,0 +1,33 @@ +package com.yunhai.homework.model; + +import lombok.Data; + +import java.util.List; + +/** + * 统一分页响应外壳。 + */ +@Data +public class BasePageResult { + /** 总记录数 */ + private int total; + /** 当前页数据 */ + private List content; + /** 当前页码(从 1 起) */ + private int pageIndex; + /** 每页条数 */ + private int pageSize; + /** 总页数 */ + private int totalPages; + /** 是否还有下一页 */ + private boolean hasNext; + + public BasePageResult(List content, int total, int pageIndex, int pageSize, int totalPages, boolean hasNext) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + } +} diff --git a/src/main/java/com/yunhai/homework/model/SchTeaList.java b/src/main/java/com/yunhai/homework/model/SchTeaList.java new file mode 100644 index 0000000..80f9b9c --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/SchTeaList.java @@ -0,0 +1,106 @@ +package com.yunhai.homework.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * 班级管理接口-教师(bschclass {@code /oapi/DbClass/GetSchteaList} 响应项)。 + * 对应 C# BestSchool.Entity.Model.SchTeaList。 + */ +public class SchTeaList { + + /** 学校 GUID(SchSerID) */ + @JsonProperty("SchSerID") + private String schSerID; + + @JsonProperty("TeaTel") + private String teaTel; + + /** 教师姓名 */ + @JsonProperty("TeaName") + private String teaName; + + /** 教师用户ID */ + @JsonProperty("UserTeaSerID") + private Integer userTeaSerID; + + @JsonProperty("TeaIDCard") + private String teaIDCard; + + /** 教师工号 */ + @JsonProperty("TeaNo") + private String teaNo; + + /** 教师所属组ID列表(兼容旧字段 GroupIds) */ + @JsonProperty("GroupIds") + private List groupIds; + + /** 教师所属组ID(接口现行字段 GroupID,单个) */ + @JsonProperty("GroupID") + private Integer groupId; + + public Integer getGroupId() { + return groupId; + } + + public void setGroupId(Integer groupId) { + this.groupId = groupId; + } + + public String getSchSerID() { + return schSerID; + } + + public void setSchSerID(String schSerID) { + this.schSerID = schSerID; + } + + public String getTeaTel() { + return teaTel; + } + + public void setTeaTel(String teaTel) { + this.teaTel = teaTel; + } + + public String getTeaName() { + return teaName; + } + + public void setTeaName(String teaName) { + this.teaName = teaName; + } + + public Integer getUserTeaSerID() { + return userTeaSerID; + } + + public void setUserTeaSerID(Integer userTeaSerID) { + this.userTeaSerID = userTeaSerID; + } + + public String getTeaIDCard() { + return teaIDCard; + } + + public void setTeaIDCard(String teaIDCard) { + this.teaIDCard = teaIDCard; + } + + public String getTeaNo() { + return teaNo; + } + + public void setTeaNo(String teaNo) { + this.teaNo = teaNo; + } + + public List getGroupIds() { + return groupIds; + } + + public void setGroupIds(List groupIds) { + this.groupIds = groupIds; + } +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java new file mode 100644 index 0000000..cf9c417 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchClassDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 班级 DTO(/oapi/VSchClass/GetVClassList,SchClass 视图)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchClassDto { + + /** 班级业务ID(sch_class.class_id,scopeType=班级 的 scopeId;学生 ClassID 指向它) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级名称 */ + @JsonProperty("ClassName") + private String className; + + /** 年级ID(= gradeId) */ + @JsonProperty("Levell") + private Integer gradeId; + + /** 排序号 */ + @JsonProperty("sx") + private Integer sort; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java new file mode 100644 index 0000000..01ff15b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchGradeDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 年级 DTO(/oapi/Schoolen/GetSchLenLevellist,BaseSchsystemGrade)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchGradeDto { + + /** 年级ID(=gradeId,scopeType=年级 的 scopeId;班级 Levell 指向它) */ + @JsonProperty("Id") + private Integer id; + + /** 年级名称(gradeNickname,如「初一」) */ + @JsonProperty("Name") + private String name; + + /** 所属学段(学制)ID */ + @JsonProperty("OwnerId") + private Integer ownerId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java new file mode 100644 index 0000000..06185b6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchInfoDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校信息 DTO(/oapi/SchInfo/GetUserSchFirst)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchInfoDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerId") + private String schSerId; + + /** 用户中心学校组ID(与 CampusNotice schId 同域) */ + @JsonProperty("UserSchSerId") + private Integer userSchSerId; + + /** 学校名称 */ + @JsonProperty("SchName") + private String schName; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java new file mode 100644 index 0000000..f96913d --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchStudentDto.java @@ -0,0 +1,35 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学生 DTO(/oapi/OAStu/*,SchStudent 视图)。 + * account 仅为上游接口契约字段(兼容反序列化),本系统落库/身份映射不再使用; + * 学生身份以 stuId(UserStuSerID,= 平台用户ID)+ stuName 承载。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchStudentDto { + + /** 学员登录账号(上游契约字段,仅兼容保留,不落库) */ + @JsonProperty("Account") + private String account; + + /** 学生ID */ + @JsonProperty("UserStuSerID") + private Integer stuId; + + /** 学生姓名 */ + @JsonProperty("StuName") + private String stuName; + + /** 所在班级ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 所在组ID */ + @JsonProperty("GroupID") + private Integer groupId; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java new file mode 100644 index 0000000..ff7add8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSubjectDto.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学校学科 DTO(/oapi/SubInfo/GetSchSublist)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSubjectDto { + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 学科ID(学科编码;字典无编码时为字典ID字符串) */ + @JsonProperty("SubID") + private String subId; + + /** 学科名称 */ + @JsonProperty("SubName") + private String subName; + + /** 排序号 */ + @JsonProperty("No") + private Integer no; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java new file mode 100644 index 0000000..f0df157 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchSystemDto.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * bschclass 学段(学制)DTO(/oapi/Schoolen/GetSchLenlist,BaseSchsystem)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchSystemDto { + + /** 学制ID(scopeType=学部 的 scopeId) */ + @JsonProperty("Id") + private Integer id; + + /** 学段名称(如「小学部」「初中部」) */ + @JsonProperty("Name") + private String name; + + /** 学制类型 */ + @JsonProperty("Type") + private Integer type; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java new file mode 100644 index 0000000..86ec6f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTeachDto.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Date; + +/** + * bschclass 任教关系 DTO(/oapi/TeachInfo/*,sch_class_teach 视图)。 + * 一条记录 = 某学校某班级中某教师任教某学科。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTeachDto { + + /** 任教记录ID(sch_class_teach.id) */ + @JsonProperty("SerID") + private Integer serId; + + /** 学校 GUID(bschclass 侧 SchSerId) */ + @JsonProperty("SchSerID") + private String schSerId; + + /** 教师ID(= 平台用户ID) */ + @JsonProperty("UserTeaSerID") + private Integer teaId; + + /** 学科ID(学科编码) */ + @JsonProperty("SubID") + private String subId; + + /** 班级业务ID(sch_class.class_id) */ + @JsonProperty("ClassID") + private Integer classId; + + /** 班级ID(与 ClassID 同值,上游兼容字段) */ + @JsonProperty("UserClassID") + private Integer userClassId; + + /** 创建时间 */ + @JsonProperty("CreationTime") + private Date creationTime; +} diff --git a/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java new file mode 100644 index 0000000..29623ad --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/bsch/SchTreeNodeDto.java @@ -0,0 +1,41 @@ +package com.yunhai.homework.model.bsch; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * bschclass 组织树节点 DTO(/oapi/SchGroup/GetTeaTree、GetStuTree,TeaTreeVO 视图)。 + * type=1 组节点(学校/学段/年级/班级、教师组),type=2 人员叶子(学生/教师)。 + */ +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SchTreeNodeDto { + + /** 节点ID(学校/学段/年级/班级为对应业务ID,叶子为学生 stuId 或教师 teaId) */ + private Integer id; + + /** 节点名称 */ + private String name; + + /** 父节点ID(根节点为 0) */ + private Integer pid; + + /** 排序号(组取 sort,叶子固定 0) */ + private Integer no; + + /** 1=组节点,2=人员叶子 */ + private Integer type; + + /** 是否父节点(组 true,叶子 false) */ + @JsonProperty("isParent") + private Boolean isParent; + + /** 头像(当前固定空串) */ + private String photo; + + /** 子节点(叶子为 null,组节点非 null) */ + private List children; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java new file mode 100644 index 0000000..7621af2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkDetailVo.java @@ -0,0 +1,81 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * 作业详情视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class HomeworkDetailVo { + + @JsonProperty("serid") + private Integer serid; + + @JsonProperty("title") + private String title; + + @JsonProperty("Contents") + private String contents; + + @JsonProperty("DeptID") + private String deptId; + + @JsonProperty("DeptName") + private String deptName; + + @JsonProperty("TeaName") + private String publisherName; + + @JsonProperty("EditDate") + private String editDate; + + @JsonProperty("numcount") + private Integer numCount; + + @JsonProperty("readnumcount") + private Long readNumCount; + + @JsonProperty("ReceiveList") + private String receiveList; + + @JsonProperty("DsfsTime") + private String dsfsTime; + + @JsonProperty("isDsfs") + private Boolean isDsfs; + + @JsonProperty("ChaoSend") + private List chaoSend; + + /** 接收人列表:"all" 或用户ID列表 */ + @JsonProperty("tlist") + private Object tlist; + + /** + * 接收对象可读文本("全部班级" 或班级名顿号串)。 + * 详情页「接收班级」标签直接展示,避免前端拿 ReceiveList 的 id 串渲染。 + */ + @JsonProperty("receiveText") + private String receiveText; + + /** + * 附件项(旧 ChaoSend 数组元素)。 + */ + @Data + public static class FileItem { + @JsonProperty("id") + private Integer id; + @JsonProperty("name") + private String name; + @JsonProperty("size") + private Integer size; + @JsonProperty("fileid") + private Integer fileid; + @JsonProperty("filename") + private String filename; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java new file mode 100644 index 0000000..e1c2fa2 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkPageResult.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 作业分页结果(扩展 BasePageResult,额外携带 unreadCount)。 + * + * 对齐方案 M7:unreadCount 并入分页外壳扩展子类,不改基类契约。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkPageResult extends BasePageResult { + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkPageResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + super(content, total, pageIndex, pageSize, totalPages, hasNext); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java new file mode 100644 index 0000000..afea9a6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +/** + * 作业列表查询条件。 + * + * - 管理端(adminList):用 status / type / deptId / publisherId / 时间范围过滤; + * - 用户端(userList):额外用 readType 过滤未读/已读(0 全部 / 1 未读 / 2 已读)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkQuery extends BasePageQuery { + + /** 状态过滤:null 不过滤;1 已发布 */ + private Integer status; + + /** 类型过滤:1 普通 / 2 重要 */ + private Integer type; + + /** 列表范围(管理端):"user" 只看自己发布的,null 看全部 */ + private String scope; + + /** 发布部门ID */ + private String deptId; + + /** 发布人ID(用户中心数字ID,管理端按发布人查) */ + private Integer publisherId; + + /** 开始时间(创建时间 >=,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startTime; + + /** 结束时间(创建时间 <= 当天 23:59:59,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endTime; + + /** 阅读状态过滤(仅用户端):0 全部 / 1 未读 / 2 已读 */ + private Integer readType = 0; + + /** 排序:1 时间倒序(默认)/ 2 时间正序 */ + private String orderByTime = "1"; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java new file mode 100644 index 0000000..2cb560f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java @@ -0,0 +1,65 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import com.yunhai.homework.common.validation.ValidationGroups; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 作业发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。 + */ +@Data +public class HomeworkRequest { + + /** 作业标题 */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业标题不得为空") + private String title; + + /** 作业内容 */ + private String content; + + /** 作业类型:1 普通 / 2 重要 */ + @NotNull(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业类型不得为空") + private Integer type = 1; + + /** 发布部门ID(原 DeptID,必填——对应原系统「发布部门不得为空」校验) */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "发布部门不得为空") + private String deptId; + + /** 发布部门名称 */ + private String deptName; + + /** 是否置顶:0 否 / 1 是 */ + private Integer topmost = 0; + + /** 接收人列表(JSON 数组字符串,如 ["10012","10013"]——用户中心数字ID,或 "ALL" 表示全员) */ + private String receiveList; + + /** 附件文件ID列表(逗号分隔串,如 "1,2,3");旧名 chaoSend,已更名 fileIds(M6) */ + private String fileIds; + + /** 定时发送时间(为空表示立即发布) */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timingSendTime; + + /** 短信发送标记:0 不发短信 / 1 发短信(旧名 topmost 实指发短信,已正名 smsFlag,M6) */ + private Integer smsFlag = 0; + + /** 转发源作业ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */ + private Integer sourceId; + + // ==================== 课堂作业 V1/V2 扩展(前端契约字段,兼容层同步写新列) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deadline; + + /** 提交方式:text/image/attach/none 组合 */ + private java.util.List submitType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java new file mode 100644 index 0000000..c492f86 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java @@ -0,0 +1,113 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 作业统计视图(对应原系统 GetStatisData / 按发布人统计)。 + */ +@Data +public class HomeworkStatisticsVo { + + /** 作业总数(全部已发布) */ + private long total; + + /** 已发布数 */ + private long publishedCount; + + /** 阅读记录总数(累计已读人次) */ + private long totalReadRecords; + + /** 当前用户未读数(已到点的已发布 - 当前用户已读) */ + private long unreadForCurrentUser; + + /** 按发布人统计 */ + private List byPublisher; + + /** 按月统计(已发布作业数) */ + private List byMonth; + + // ==================== 课程作业口径(前端 /stats 页消费) ==================== + + /** 我布置的作业数(当前登录人发布的全部作业,含草稿) */ + private long myAssignCount; + + /** 我的作业待批改总量(已交未批的当前版本行合计) */ + private long pendingGrade; + + /** 各作业平均提交率(%,仅统计需收集提交的作业;无样本时为 0) */ + private double avgSubmitRate; + + /** 按作业的提交与批改进度明细(草稿与仅查阅型不计) */ + private List byHomework = new ArrayList<>(); + + /** 已批阅提交的分数分布(四档) */ + private List scoreDist = new ArrayList<>(); + + /** 单作业进度条目(口径对齐前端 HomeworkProgress 契约) */ + @Data + public static class HomeworkStat { + private Integer id; + private String title; + private String subject; + /** 旧契约四态派生值:draft / collecting / closed / done */ + private String hwStatus; + private String hwStatusDesc; + /** 应交人数 */ + private long should; + /** 已读人数 */ + private long read; + /** 已交人数 */ + private long submitted; + /** 已批阅人数 */ + private long graded; + /** 待批改人数 */ + private long pendingGrade; + /** 未交人数 */ + private long unsubmitted; + /** 提交率(%) */ + private double submitRate; + /** 批阅率(%) */ + private double gradeRate; + } + + /** 分数分布档(key 供前端配色映射:excellent/good/pass/fail) */ + @Data + public static class ScoreBucket { + private String label; + private String key; + private long count; + + public ScoreBucket(String label, String key, long count) { + this.label = label; + this.key = key; + this.count = count; + } + } + + @Data + public static class PublisherStat { + private String publisherName; + private long count; + + public PublisherStat(String publisherName, long count) { + this.publisherName = publisherName; + this.count = count; + } + } + + @Data + public static class MonthStat { + private int year; + private int month; + private long count; + + public MonthStat(int year, int month, long count) { + this.year = year; + this.month = month; + this.count = count; + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java new file mode 100644 index 0000000..c906e2e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.List; + +/** + * 用户端作业列表结果:在分页数据之外额外携带「当前用户未读总数」, + * 供前端展示未读角标(对应原系统 OtherData.unReadCount)。 + */ +@Data +public class HomeworkUserListResult { + + private int total; + private List content; + private int pageIndex; + private int pageSize; + private int totalPages; + private boolean hasNext; + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkUserListResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java new file mode 100644 index 0000000..333d49e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java @@ -0,0 +1,92 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 作业视图对象(列表/详情返回)。 + */ +@Data +public class HomeworkVo { + + private Integer id; + private String title; + private String content; + private Integer type; + private String typeDesc; + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + private String publisherName; + private String deptId; + private String deptName; + private Integer status; + private String statusDesc; + private Integer topmost; + + /** 已读人数(基于阅读记录实时统计) */ + private Long readCount = 0L; + + /** 应读人数 */ + private Integer allowCount; + + /** 当前用户是否已读(用户端列表使用;管理端恒为 false 或不关注) */ + private boolean read = false; + + /** 是否撤回:0 否 / 1 是 */ + private Integer isPulled; + + /** 定时发送时间 */ + private LocalDateTime timingSendTime; + + /** 父作业ID(转发时关联原作业) */ + private Integer parentId; + + /** 短信发送标记 */ + private Integer smsFlag; + + /** 接收人列表(JSON) */ + private String receiveList; + + /** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */ + private String UpFile; + + // ==================== 课堂作业 V1/V2 扩展(前端 types/homework.ts 契约) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间(前端契约字段名 deadline) */ + private LocalDateTime deadline; + + /** 提交方式数组:text/image/attach/none */ + private java.util.List submitType; + + /** 作业流程状态——旧契约四态派生值(draft/collecting/closed/done),权威五态见 homework.hw_status */ + private String hwStatus; + + private String hwStatusDesc; + + /** + * 提交/批改进度(管理端列表页「提交进度 / 未读未交 / 批改」列使用)。 + * 草稿或仅查阅型为 null;列表场景由 HomeworkProgressCalculator 批量聚合回填。 + */ + private com.yunhai.homework.model.v2.ProgressVo progress; + + private String createBy; + private LocalDateTime createTime; + private String updateBy; + private LocalDateTime updateTime; + + /** + * 阅读明细视图(阅读列表查询用)。 + */ + @Data + public static class ReadDetail { + /** 读者ID(用户中心数字ID) */ + private Integer id; + private String name; + private boolean read; + private LocalDateTime readTime; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java new file mode 100644 index 0000000..15ece01 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 一键已读结果视图对象(替代原 Map<String,Object> 返回)。 + */ +@Data +public class InitializeRedVo { + + @JsonProperty("count") + private Integer count; + + public InitializeRedVo() {} + + public InitializeRedVo(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java new file mode 100644 index 0000000..8127480 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java @@ -0,0 +1,30 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 文件上传结果视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class UpFileResultVo { + + @JsonProperty("fileid") + private Integer fileid; + + @JsonProperty("name") + private String name; + + @JsonProperty("fileurl") + private String fileurl; + + @JsonProperty("host") + private String host; + + @JsonProperty("type") + private String type; + + @JsonProperty("size") + private Integer size; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java new file mode 100644 index 0000000..76dfa2f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +/** + * 附件视图对象(对应原 .NET FileObj)。 + */ +@Data +public class UpFileVo { + + /** 文件记录ID */ + private Integer id; + + /** 文件真实名称 */ + private String fileTrueName; + + /** 文件存储路径(相对路径) */ + private String fileName; + + /** 文件大小(字节) */ + private Integer fileSize; + + /** 文件后缀名 */ + private String fileType; + + /** 关联主题ID */ + private Integer topicId; + + /** 应用类型 */ + private String topicType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java new file mode 100644 index 0000000..771eb3a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java @@ -0,0 +1,122 @@ +package com.yunhai.homework.model.homework; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户可访问模块(左菜单)视图对象。 + * 将基础平台返回的扁平 {@code List} 转换为前端 layout(参考 UserBase)期望的树结构: + * {@code modId}(路由高亮匹配)/{@code page}(跳转路径)/{@code children}(子菜单)。 + */ +public class UserModuleVo { + + /** 模块主键 id(基础平台 module id) */ + private Integer id; + + /** 菜单高亮匹配用:等价于后端模块 id */ + private Integer modId; + + /** 模块名称 */ + private String name; + + /** 父级模块 id(0 表示根节点) */ + private Integer parentId; + + /** 前端路由路径(由 BoaModule.path 映射而来) */ + private String page; + + /** 层级(0=根分组) */ + private Integer level; + + /** 菜单图标 */ + private String icon; + + /** 排序号 */ + private Integer orderNumber; + + /** 是否可用 */ + private Boolean usable; + + /** 子菜单 */ + private List children = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getModId() { + return modId; + } + + public void setModId(Integer modId) { + this.modId = modId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Integer getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(Integer orderNumber) { + this.orderNumber = orderNumber; + } + + public Boolean getUsable() { + return usable; + } + + public void setUsable(Boolean usable) { + this.usable = usable; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java new file mode 100644 index 0000000..bedf7b5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 附件条目(作业附件与提交附件共用)。fileUrl 必传;fileId 为 file/Upload 产物时回传。 + */ +@Data +public class AttachmentItem { + + /** up_file.id */ + private Integer fileId; + + /** 文件访问 URL */ + private String fileUrl; + + /** 文件类型:image / file */ + private String fileType; + + /** 文件名 */ + private String fileName; + + /** 排序号 */ + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassItem.java b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java new file mode 100644 index 0000000..459ea80 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java @@ -0,0 +1,18 @@ +package com.yunhai.homework.model.v2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 班级条目(作业参与班级展示)。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClassItem { + + private String classId; + + private String className; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java new file mode 100644 index 0000000..c0f400b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 年级→班级树(发布作业选班 + 应交名单基数来源)。 + * 叶子节点 id = 班级 id(class_student 快照聚合)。 + */ +@Data +public class ClassTreeNode { + + /** 年级节点为 gradeName,班级叶子为 classId */ + private String id; + + /** 展示名 */ + private String name; + + /** 是否父节点(年级) */ + private Boolean isParent; + + /** 叶子节点:在班学生数 */ + private Integer studentCount; + + private List children = new ArrayList<>(); +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java new file mode 100644 index 0000000..f8c5dec --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 延长截止请求(新截止时间必须晚于当前时间)。 + */ +@Data +public class ExtendRequest { + + @NotNull(message = "新截止时间不得为空") + private LocalDateTime deadlineAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java new file mode 100644 index 0000000..1ac46f8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java @@ -0,0 +1,36 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 作业详情汇总统计卡片(PRD §18 页 4 stats 接口出参)。 + */ +@Data +public class HomeworkStatsVo { + + private Integer homeworkId; + + private String title; + + private String hwStatus; + + private String hwStatusDesc; + + /** 总进度 */ + private ProgressVo total; + + /** 分班进度 */ + private List byClass; + + @Data + public static class ByClass { + + private String classId; + + private String className; + + private ProgressVo progress; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java new file mode 100644 index 0000000..9464910 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 创建/编辑作业请求(PRD §18 页 1 表单)。 + * 创建一律落草稿(DRAFT_UNPUBLISHED),发布/定时走独立接口。 + */ +@Data +public class HomeworkV2Request { + + /** 作业标题(必填 ≤50 字) */ + @NotBlank(message = "作业标题不得为空") + @Size(max = 50, message = "作业标题不超过50字") + private String title; + + /** 学科(字典值) */ + private String subject; + + /** 作业说明(富文本) */ + private String description; + + /** 参与班级 id 列表 */ + private List classIds; + + /** 附件列表 */ + private List attachments; + + /** 截止时间(精确到分钟;空 = 仅查阅型) */ + private LocalDateTime deadlineAt; + + /** 提交方式:image / attach / text / none 的组合(PRD 图片/文件/两者) */ + private List submitType; + + /** 截止后是否允许逾期提交:0 否 / 1 是 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天提醒(0 关 / 1 开) */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时提醒(0 关 / 1 开) */ + private Integer remind1h; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java new file mode 100644 index 0000000..8bf1b02 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java @@ -0,0 +1,73 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 作业视图对象 V2(草稿箱/已发布列表/详情共用)。 + * 列表场景 attachments/classes 可按需填充;progress 仅非草稿且非仅查阅型作业返回。 + */ +@Data +public class HomeworkV2Vo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 作业说明(富文本;列表场景可置空省流) */ + private String description; + + /** 作业状态(PRD 五态) */ + private String hwStatus; + + private String hwStatusDesc; + + /** 定时发布时间(复用 timing_send_time) */ + private LocalDateTime scheduledAt; + + /** 发布时间 */ + private LocalDateTime publishedAt; + + /** 截止时间 */ + private LocalDateTime deadlineAt; + + /** 归档时间 */ + private LocalDateTime archivedAt; + + /** 提交方式数组 */ + private List submitType; + + /** 截止后允许逾期提交:0/1 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天 */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时 */ + private Integer remind1h; + + /** 参与班级 */ + private List classes; + + /** 附件列表(详情场景填充) */ + private List attachments; + + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + + /** 发布人姓名 */ + private String publisherName; + + /** 创建/更新时间(审计) */ + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + /** 汇总进度(草稿或仅查阅型为 null) */ + private ProgressVo progress; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java new file mode 100644 index 0000000..0303eb8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知分页结果(含当前用户未读数)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationPageResult extends BasePageResult { + + /** 当前用户未读通知总数 */ + private long unreadCount; + + public NotificationPageResult(BasePageResult page, long unreadCount) { + super(page.getContent(), page.getTotal(), page.getPageIndex(), page.getPageSize(), + page.getTotalPages(), page.isHasNext()); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java new file mode 100644 index 0000000..f8af4a7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知列表查询。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationQuery extends BasePageQuery { + + /** 类型过滤(NotificationTypeEnum code) */ + private String type; + + /** 已读过滤:0 未读 / 1 已读(空 = 全部) */ + private Integer isRead; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java new file mode 100644 index 0000000..2a9b61a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知列表项。 + */ +@Data +public class NotificationVo { + + private Integer id; + + /** 通知类型 */ + private String type; + + private String typeDesc; + + private String title; + + private String content; + + private Integer homeworkId; + + private Integer relatedId; + + /** 是否已读:0/1 */ + private Integer isRead; + + private LocalDateTime readAt; + + private LocalDateTime createTime; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java new file mode 100644 index 0000000..201a460 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 提交/批阅/阅读进度(PRD §18 页 3 汇总口径)。 + * should(应交)为分母;打回未重交计入已交。 + */ +@Data +public class ProgressVo { + + /** 应交人数(参与班级在班学生去重) */ + private int should; + + /** 已读人数 */ + private int read; + + /** 已交人数(含逾期/打回/已批阅) */ + private int submitted; + + /** 已批阅人数 */ + private int graded; + + /** 待批阅人数(submitted - graded) */ + private int pendingGrade; + + /** 未交人数 */ + private int unsubmitted; + + /** 未读人数 */ + private int unread; + + /** 未读且未交人数(高亮口径:should - 已读∪已交) */ + private int unreadUndone; + + /** 阅读率(百分数,一位小数) */ + private double readRate; + + /** 提交率(百分数,一位小数) */ + private double submitRate; + + /** 批阅率(已批阅 / 已交,百分数一位小数;submitted=0 时为 0) */ + private double gradeRate; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java new file mode 100644 index 0000000..a97e0c1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 教师手动提醒请求:type = NOT_READ / NOT_SUBMITTED,studentIds 为空时提醒全部未读/未交学生。 + */ +@Data +public class RemindRequest { + + /** 提醒类型:NOT_READ / NOT_SUBMITTED */ + @NotBlank(message = "提醒类型不得为空") + private String type; + + /** 定向学生ID(用户中心数字ID;空 = 全部未读/未交) */ + private List studentIds; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java new file mode 100644 index 0000000..45d3f3e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 打回请求(PRD 页 6)。comment 为订正原因,必填。 + */ +@Data +public class ReturnRequest { + + @NotBlank(message = "打回须填写订正原因") + private String comment; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java new file mode 100644 index 0000000..166b2e1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 批阅保存请求(PRD 页 6)。score 与 comment 至少一项有值。 + */ +@Data +public class ReviewRequest { + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 圈划标注图片(P2,先收参) */ + private List markedImages; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java new file mode 100644 index 0000000..894b433 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 定时发布 / 取消定时请求。 + * cancel=true 时忽略 scheduledAt,作业回退草稿-未发布并清空定时时间。 + */ +@Data +public class ScheduleRequest { + + /** 定时发布时间(cancel=false 时必填且须晚于当前时间) */ + private LocalDateTime scheduledAt; + + /** 是否取消定时 */ + private Boolean cancel = false; + + @NotNull + public Boolean getCancel() { + return cancel; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java new file mode 100644 index 0000000..6372883 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 学生端作业详情(PRD §18 学生页 2)。进入详情后由前端停留 ≥3 秒再调 read 上报。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentHomeworkDetailVo extends StudentHomeworkVo { + + /** 作业说明(富文本) */ + private String description; + + /** 作业附件 */ + private List attachments; + + /** 参与班级(学生所属班级视角) */ + private List classes; + + /** 我当前版本的提交附件(未提交为 null) */ + private List myAttachments; + + /** 我的文本内容(当前版本) */ + private String myText; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java new file mode 100644 index 0000000..9d30037 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学生端作业列表项(PRD §18 学生页 1)。 + */ +@Data +public class StudentHomeworkVo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 任课教师(发布人姓名) */ + private String teacherName; + + /** 截止时间(前端做倒计时) */ + private LocalDateTime deadlineAt; + + /** 提交方式 */ + private java.util.List submitType; + + /** 作业状态 */ + private String hwStatus; + + private String hwStatusDesc; + + /** 截止后是否允许逾期补交:0 否 / 1 是(学生端据此决定是否渲染「补交」入口) */ + private Integer allowLate; + + /** 我的提交概要(未提交为 null) */ + private MySubmission my; + + @Data + public static class MySubmission { + + /** 当前版本提交行ID */ + private Integer submissionId; + + /** 提交状态(PRD 五态) */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 版本号 */ + private Integer version; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分(已批阅才有) */ + private java.math.BigDecimal score; + + /** 评语 */ + private String comment; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java new file mode 100644 index 0000000..49e3144 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学生矩阵查询(PRD §18 页 5)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentMatrixQuery extends BasePageQuery { + + /** 班级筛选(空 = 全部参与班级) */ + private String classId; + + /** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */ + private String submitStatus; + + /** 批阅状态筛选:PENDING/REVIEWED/RETURNED */ + private String reviewStatus; + + /** 只看待批阅(reviewStatus=PENDING 且已提交)快捷过滤 */ + private Boolean pendingGrade; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java new file mode 100644 index 0000000..6257285 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java @@ -0,0 +1,57 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学生矩阵行(PRD §18 页 5)。未交学生也生成占位行(submissionId=0)。 + */ +@Data +public class StudentMatrixVo { + + /** 当前版本提交行ID(未交占位为 0) */ + private Integer submissionId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + /** 姓名 */ + private String studentName; + + private String classId; + + private String className; + + /** 阅读状态:0 未读 / 1 已读 */ + private Integer isRead; + + /** 首次阅读时间 */ + private LocalDateTime firstReadAt; + + /** 最近阅读时间 */ + private LocalDateTime lastReadAt; + + /** 提交状态(PRD 五态;未交为 NOT_SUBMITTED) */ + private String submitStatus; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期:0/1 */ + private Integer isLate; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分 */ + private BigDecimal score; + + /** 最新提交附件 URL 列表(缩略图) */ + private List thumbnails; + + /** 提交内容类型摘要:image=拍照/图片、attach=上传附件、text=文字作答(未交为空列表) */ + private List contentTypes; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java new file mode 100644 index 0000000..f4d9cd1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 学生查看批阅结果(PRD §18 学生页 2 result 接口)。 + */ +@Data +public class StudentResultVo { + + private Integer homeworkId; + + private String homeworkTitle; + + /** 当前版本批阅结果(未批阅为 null) */ + private SubmissionVersionVo latest; + + /** 全部版本(含各版评语,新→旧) */ + private List versions; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java new file mode 100644 index 0000000..1fa7545 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 学生提交作业请求(PRD §18 学生页 3)。附件只接受 file/Upload 产物(fileId 或平台 URL),拒绝 dataURL。 + */ +@Data +public class StudentSubmitRequest { + + /** 文本内容(submitType 含 text 时有效) */ + @Size(max = 20000, message = "文本内容过长") + private String text; + + /** 附件列表(图片/文件) */ + private List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java new file mode 100644 index 0000000..6c576d9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 学科 DTO(布置过的学科 / 我的任教学科共用返回结构)。 + */ +@Data +public class SubjectDto { + + /** 学科编码(homework.subject 字典值 / bschclass SubID) */ + private String subId; + + /** 学科名称(本校学科字典解析;字典缺失时回退为编码) */ + private String subName; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java new file mode 100644 index 0000000..cac00dc --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 撤回重交动作(PUT /student/homeworks/{id}/submissions/{submissionId})。 + * withdraw = 撤回(截止前,产生 NOT_SUBMITTED 新版本行);resubmit = 打回后重交(走提交语义)。 + */ +@Data +public class SubmissionActionRequest { + + @NotBlank(message = "action 不得为空") + private String action; + + /** resubmit 时的提交内容(等同 StudentSubmitRequest) */ + private String text; + + /** resubmit 时的附件 */ + private java.util.List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java new file mode 100644 index 0000000..bff2972 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java @@ -0,0 +1,63 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 提交版本条目(历史版本/批阅弹窗/学生结果页共用)。 + */ +@Data +public class SubmissionVersionVo { + + private Integer submissionId; + + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + private String studentName; + + /** 版本号 */ + private Integer version; + + /** 是否当前版本:0/1 */ + private Integer isLatest; + + /** 提交状态 */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 文本内容 */ + private String text; + + /** 附件 */ + private List attachments; + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 批阅状态 */ + private String reviewStatus; + + private String reviewStatusDesc; + + /** 批阅人(展示姓名快照,缺失时回退ID) */ + private String reviewedBy; + + /** 批阅时间 */ + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java new file mode 100644 index 0000000..6d6991e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教师端作业列表查询(草稿箱/已发布列表共用)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TeacherHomeworkQuery extends BasePageQuery { + + /** 学科筛选 */ + private String subject; + + /** 作业状态筛选(已发布列表:PUBLISHED/CLOSED/ARCHIVED) */ + private String hwStatus; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java new file mode 100644 index 0000000..d9fd64e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 任教班级 VO(按学科筛选任教班级返回结构)。 + */ +@Data +public class TeachingClassVo { + + /** 班级业务ID(bschclass sch_class.class_id) */ + private String classId; + + /** 班级名称(学校班级列表映射;缺失时回退为班级ID) */ + private String className; +} diff --git a/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java new file mode 100644 index 0000000..0581e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ClassStudent; +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; + +/** + * 班级学生名册仓储。 + */ +public interface ClassStudentRepository extends JpaRepository { + + /** 按学生ID(用户中心数字ID)反查学生身份(学生端接口入口) */ + List findByStudentIdAndStatus(Integer studentId, Integer status); + + /** 班级在班学生 */ + List findByClassIdAndStatus(String classId, Integer status); + + /** 指定班级集合的全部在班学生(应交名单) */ + List findByClassIdInAndStatus(List classIds, Integer status); + + /** 指定学校的全部名册(同步任务全量刷新用) */ + List findBySchoolId(Integer schoolId); + + /** 年级→班级树:按学校去重班级(class_student 快照聚合),列序:classId, className, gradeName */ + @Query("select distinct c.classId, c.className, c.gradeName from ClassStudent c " + + "where c.schoolId = :schoolId and c.status = 1") + List findDistinctClasses(@Param("schoolId") Integer schoolId); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java new file mode 100644 index 0000000..4dc9ee4 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java @@ -0,0 +1,27 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroup; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ContactsGroupRepository extends JpaRepository { + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + + /** + * 按创建人ID(用户中心数字ID)查询联系组列表。 + */ + List findByCreatorId(Integer creatorId); + + /** + * 按创建人ID查询联系组列表(分页)。 + */ + Page findByCreatorId(Integer creatorId, Pageable pageable); + + /** + * 查询全部联系组(按创建时间降序)。 + */ + List findAllByOrderByCreateTimeDesc(); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java new file mode 100644 index 0000000..df5ab28 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java @@ -0,0 +1,42 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroupUser; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ContactsGroupUserRepository extends JpaRepository { + Page findByGroupId(Integer groupId, Pageable pageable); + List findByGroupId(Integer groupId); + + /** + * 按组ID批量删除成员(JPQL bulk delete,SQL 立即执行)。 + * 注意:不能用 derived delete——其 DELETE 延迟到 flush 才执行, + * 同事务内先 insert 会触发 uk_group_user 唯一约束冲突。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId = :groupId") + void deleteByGroupIdBulk(@Param("groupId") Integer groupId); + + /** + * 按多个组ID批量删除成员。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId in :groupIds") + void deleteByGroupIdInBulk(@Param("groupIds") List groupIds); + + /** + * 按多个组ID查询所有成员(用于统计每组人数、组树构建)。 + */ + List findByGroupIdIn(List groupIds); + + /** + * 按组ID和用户ID查询(判断是否已存在)。 + */ + boolean existsByGroupIdAndUserId(Integer groupId, Integer userId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java new file mode 100644 index 0000000..1e6af55 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 作业附件仓储。 + */ +public interface HomeworkAttachmentRepository extends JpaRepository { + + List findByHomeworkIdOrderBySortAscIdAsc(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java new file mode 100644 index 0000000..8f651f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkClass; +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; + +/** + * 作业-班级关联仓储。 + */ +public interface HomeworkClassRepository extends JpaRepository { + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 指定班级的全部作业ID(学生端列表倒查用) */ + @Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds") + List findHomeworkIdsByClassIds(@Param("classIds") List classIds); + + /** 批量查 N 个作业的班级关联(列表展示避免 N+1) */ + @Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java new file mode 100644 index 0000000..6c16a81 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java @@ -0,0 +1,23 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReadRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 学生阅读记录仓储。 + */ +public interface HomeworkReadRecordRepository extends JpaRepository { + + Optional findByHomeworkIdAndStudentId(Integer homeworkId, Integer studentId); + + List findByHomeworkId(Integer homeworkId); + + /** 批量查 N 个作业的已读记录(列表统计避免 N+1) */ + List findByHomeworkIdIn(Collection homeworkIds); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java new file mode 100644 index 0000000..20dc83c --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkRead; +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.Optional; + +/** + * 作业阅读记录仓储(懒标记模型)。 + */ +public interface HomeworkReadRepository extends JpaRepository { + + /** + * 某用户是否已读某作业。 + */ + boolean existsByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 某作业的总已读人数。 + */ + long countByHomeworkId(Integer homeworkId); + + /** + * 删除某作业的全部阅读记录(作业删除时级联清理)。 + */ + void deleteByHomeworkId(Integer homeworkId); + + /** + * 某用户已读的作业总数(用于「我的未读」= 已发布总数 - 已读总数)。 + */ + long countByReaderId(Integer readerId); + + /** + * 查询某用户已读的作业ID列表(用于列表页批量判定未读)。 + */ + @Query("select r.homeworkId from HomeworkRead r where r.readerId = :readerId") + List findHomeworkIdsByReaderId(@Param("readerId") Integer readerId); + + /** + * 查询某用户对某作业的阅读记录(标记已读时幂等判断)。 + */ + Optional findByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 查询某作业的全部阅读记录(按阅读时间倒序),用于阅读明细列表。 + */ + @Query("select r from HomeworkRead r where r.homeworkId = :homeworkId order by r.readTime desc") + List findByHomeworkIdOrderByReadTimeDesc(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java new file mode 100644 index 0000000..402d455 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReceiver; +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; + +/** + * 作业接收人关联表仓储。 + */ +public interface HomeworkReceiverRepository extends JpaRepository { + + /** 按作业ID查询全部接收人 */ + List findByHomeworkId(Integer homeworkId); + + /** 按作业ID删除全部接收人 */ + void deleteByHomeworkId(Integer homeworkId); + + /** 按作业ID和接收类型查询接收人 */ + List findByHomeworkIdAndReceiveType(Integer homeworkId, Integer receiveType); + + /** + * 批量查 N 个作业的接收人,用于列表展示(避免 N+1)。 + * @param homeworkIds 作业ID列表 + * @return 接收人关联记录列表 + */ + @Query("select r from HomeworkReceiver r where r.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java new file mode 100644 index 0000000..bf862b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java @@ -0,0 +1,139 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Homework; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 课程作业仓储。 + */ +public interface HomeworkRepository extends JpaRepository, + JpaSpecificationExecutor { + + /** + * 作业流程状态条件更新(CAS):仅当当前状态为 from 时更新为 to。 + * 状态机单点收敛用,天然幂等、并发安全(定时任务与接口共用)。 + * clearAutomatically:批量更新绕过一级缓存,必须清空以避免后续 findById 读到旧实体 + * 再被 save() 写回旧状态(同事务内必现,集成测试发现)。 + * + * @return 影响行数(0 = 状态已变化,调用方应放弃本次迁移) + */ + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update Homework h set h.hwStatus = :to where h.id = :id and h.hwStatus = :from") + int casHwStatus(@Param("id") Integer id, + @Param("from") String from, + @Param("to") String to); + + /** + * 定时发布任务扫描:定时待发布且已到点。 + */ + List findByHwStatusAndTimingSendTimeLessThanEqual(String hwStatus, LocalDateTime now); + + /** + * 到点关闭任务扫描:进行中且已过截止时间。 + */ + List findByHwStatusAndDeadlineAtBefore(String hwStatus, LocalDateTime now); + + /** + * 截止提醒任务扫描:进行中且截止时间落在指定窗口内(remind_1d/remind_1h 开关在服务侧逐单过滤)。 + */ + List findByHwStatusAndDeadlineAtBetween(String hwStatus, LocalDateTime start, LocalDateTime end); + + /** + * 按作业流程状态查询(兼容层/统计用)。 + */ + List findByHwStatus(String hwStatus); + + /** + * 当前登录人发布的全部作业(课程作业口径统计:我布置的作业 / 提交与批改进度明细)。 + */ + List findByPublisherId(Integer publisherId); + + /** + * 当前登录人布置过(已发布及之后状态)作业的学科去重清单(学科筛选用,字典序;草稿不计)。 + */ + @Query("select distinct h.subject from Homework h " + + "where h.publisherId = :publisherId and h.subject is not null and h.subject <> '' " + + "and h.hwStatus in :statuses order by h.subject") + List findAssignedSubjects(@Param("publisherId") Integer publisherId, + @Param("statuses") Collection statuses); + + /** + * 按状态计数(用于统计已发布总数)。 + */ + long countByStatus(Integer status); + + /** + * 已发布且已到点(定时发送未到点不计入)的作业数,用于用户端未读数统计。 + */ + @Query("select count(n) from Homework n where n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDue(@Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定状态、指定时间范围内的作业创建时间(用于按月统计,避免加载大字段)。 + */ + @Query("select n.createTime from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定状态、指定时间范围内的发布人姓名(用于按发布人统计)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 按学校和状态计数(多租户隔离统计)。 + */ + long countBySchIdAndStatus(Integer schId, Integer status); + + /** + * 按学校计数(多租户隔离统计:全部状态)。 + */ + long countBySchId(Integer schId); + + /** + * 已发布且已到点(定时发送未到点不计入)且属于指定学校的作业数(用户端未读数统计,多租户隔离)。 + */ + @Query("select count(n) from Homework n where n.schId = :schId and n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDueBySchId(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定学校、指定状态、指定时间范围内的作业创建时间(按月统计,多租户隔离)。 + */ + @Query("select n.createTime from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesBySchIdStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定学校、指定状态、指定时间范围内的发布人姓名(按发布人统计,多租户隔离)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesBySchStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java new file mode 100644 index 0000000..547c5ea --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkSubmission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 作业提交仓储(多版本)。统计一律基于 is_latest=1 的当前版本行。 + */ +public interface HomeworkSubmissionRepository extends JpaRepository { + + /** 学生在指定作业的当前版本行 */ + Optional findByHomeworkIdAndStudentIdAndIsLatest(Integer homeworkId, + Integer studentId, + Integer isLatest); + + /** 学生在指定作业的全部版本(新→旧) */ + List findByHomeworkIdAndStudentIdOrderByVersionDesc(Integer homeworkId, Integer studentId); + + /** 指定作业的当前版本行(统计/矩阵用) */ + List findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest); + + /** 批量查 N 个作业的当前版本行(列表统计避免 N+1) */ + List findByHomeworkIdInAndIsLatest(Collection homeworkIds, Integer isLatest); + + /** 学生矩阵分页 */ + Page findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest, Pageable pageable); + + /** 学生当前版本最大版本号 */ + @Query("select max(s.version) from HomeworkSubmission s " + + "where s.homeworkId = :homeworkId and s.studentId = :studentId") + Integer findMaxVersion(@Param("homeworkId") Integer homeworkId, @Param("studentId") Integer studentId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 附件等子表清理后按作业删除历史版本行 */ + @Modifying + @Query("delete from HomeworkSubmission s where s.homeworkId = :homeworkId") + int deleteAllByHomeworkId(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java new file mode 100644 index 0000000..ecc75cb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HomeworkTemplateRepository extends JpaRepository { + Page findByTypeId(Integer typeId, Pageable pageable); + + /** + * 查询全部模板(按引用次数降序),分页。 + */ + Page findAllByOrderByRefCountDesc(Pageable pageable); + + /** + * 按类型ID查询全部模板(不分页,用于检查类型是否被引用)。 + */ + long countByTypeId(Integer typeId); +} diff --git a/src/main/java/com/yunhai/homework/repository/NotificationRepository.java b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java new file mode 100644 index 0000000..1066c10 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + * 站内通知仓储。 + */ +public interface NotificationRepository extends JpaRepository { + + Page findByReceiverId(Integer receiverId, Pageable pageable); + + Page findByReceiverIdAndType(Integer receiverId, String type, Pageable pageable); + + Page findByReceiverIdAndIsRead(Integer receiverId, Integer isRead, Pageable pageable); + + Page findByReceiverIdAndTypeAndIsRead(Integer receiverId, String type, + Integer isRead, Pageable pageable); + + long countByReceiverIdAndIsRead(Integer receiverId, Integer isRead); + + Optional findByIdAndReceiverId(Integer id, Integer receiverId); + + /** 截止提醒去重:同作业同类型同档位已发过即跳过(related_id 存档位:1=1天档 2=1小时档) */ + boolean existsByHomeworkIdAndTypeAndRelatedId(Integer homeworkId, String type, Integer relatedId); + + /** 截止提醒去重(不分档位) */ + boolean existsByHomeworkIdAndType(Integer homeworkId, String type); + + /** 全部已读(clearAutomatically 防同事务内缓存脏读) */ + @Modifying(clearAutomatically = true) + @Query("update Notification n set n.isRead = 1, n.readAt = :now " + + "where n.receiverId = :receiverId and n.isRead = 0") + int markAllRead(@Param("receiverId") Integer receiverId, @Param("now") java.time.LocalDateTime now); + + /** 按作业删除通知(作业删除级联) */ + void deleteByHomeworkId(Integer homeworkId); + + /** 指定作业的全部通知(清理/统计用) */ + List findByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java new file mode 100644 index 0000000..8e60f47 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SchClassTeach; +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; + +/** + * 班级任教关系快照仓储。 + */ +public interface SchClassTeachRepository extends JpaRepository { + + /** 指定学校指定教师的全部有效任教记录 */ + List findBySchoolIdAndTeaIdAndStatus(Integer schoolId, Integer teaId, Integer status); + + /** 指定学校指定教师去重学科编码(字典序由调用方保证) */ + @Query("select distinct t.subId from SchClassTeach t " + + "where t.schoolId = :schoolId and t.teaId = :teaId and t.status = 1") + List findDistinctSubIds(@Param("schoolId") Integer schoolId, @Param("teaId") Integer teaId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java new file mode 100644 index 0000000..18595bb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java @@ -0,0 +1,13 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsModule; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsModuleRepository extends JpaRepository { + List findAllByOrderByIdAsc(); + + /** 按模块标识查询(如 module_code="homework")。 */ + SmsModule findByModuleCode(String moduleCode); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java new file mode 100644 index 0000000..0d277a0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsSchoolConfig; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsSchoolConfigRepository extends JpaRepository { + Page findBySchoolNameContaining(String schoolName, Pageable pageable); + Page findAll(Pageable pageable); + List findBySchoolId(Integer schoolId); + List findBySchoolIdInAndModuleIdIn(List schoolIds, List moduleIds); + + /** + * 按学校ID和模块ID查询单条配置(判断是否已存在)。 + */ + SmsSchoolConfig findBySchoolIdAndModuleId(Integer schoolId, Integer moduleId); + + /** + * 按学校ID查询全部模块配置(用于"设置某校全部模块")。 + */ + List findBySchoolIdIn(List schoolIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java new file mode 100644 index 0000000..8848b14 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SubmissionAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; + +/** + * 提交附件仓储。 + */ +public interface SubmissionAttachmentRepository extends JpaRepository { + + List findBySubmissionIdOrderBySortAscIdAsc(Integer submissionId); + + /** 批量查 N 个提交行的附件(矩阵/版本列表避免 N+1) */ + List findBySubmissionIdIn(Collection submissionIds); + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SysDictRepository.java b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java new file mode 100644 index 0000000..c083840 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SysDict; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SysDictRepository extends JpaRepository { + List findByDictTypeOrderBySortOrderAsc(String dictType); + List findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag); + + /** 按类型+key 取单条字典(发文部门名称解析用) */ + Optional findByDictTypeAndDictKey(String dictType, String dictKey); +} diff --git a/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java new file mode 100644 index 0000000..a3a0f12 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.TemplateType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TemplateTypeRepository extends JpaRepository { + List findAllByOrderBySortOrderAsc(); + + /** + * 按类型名称查询(重名校验)。 + */ + boolean existsByTypeName(String typeName); + + /** + * 按类型名称查询(重名校验,排除自身)。 + */ + boolean existsByTypeNameAndIdNot(String typeName, Integer id); +} diff --git a/src/main/java/com/yunhai/homework/repository/UpFileRepository.java b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java new file mode 100644 index 0000000..421f142 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.UpFile; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +/** + * 附件文件仓储。 + */ +public interface UpFileRepository extends JpaRepository { + + /** + * 按主题类型和主题ID查询附件列表。 + */ + List findByTopicTypeAndTopicId(String topicType, Integer topicId); + + /** + * 按主题类型和多个主题ID批量查询附件(消除 N+1)。 + */ + List findByTopicTypeAndTopicIdIn(String topicType, List topicIds); + + /** + * 按主题类型和主题ID删除附件(作业删除时级联清理)。 + */ + void deleteByTopicTypeAndTopicId(String topicType, Integer topicId); +} diff --git a/src/main/java/com/yunhai/homework/service/IClassService.java b/src/main/java/com/yunhai/homework/service/IClassService.java new file mode 100644 index 0000000..2346cd9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IClassService.java @@ -0,0 +1,116 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; + +import java.util.List; + +/** + * 班级管理(bschclass)开放接口客户端门面。 + * 基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。 + * 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。 + */ +public interface IClassService { + + /** + * 获取指定学校全部教师(含所属组 GroupIds)。 + * 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}} + * + * @param schId 学校ID(即用户当前身份的 schId) + */ + List getSchTeaList(int schId); + + /** + * 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。 + * 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}} + */ + SchInfoDto getSchFirst(int schId); + + /** + * 学校学段(学制)列表:小学部 / 初中部…。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=} + */ + List getSchLenList(String schSerId); + + /** + * 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=} + */ + List getSchGradeList(String schSerId); + + /** + * 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。 + * 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=} + */ + List getSchClassList(String schSerId); + + /** + * 按校全部学生(圈人:全校范围展开)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=} + */ + List getSchStuList(String schSerId); + + /** + * 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=} + */ + List getGradeStuList(String schSerId, Integer gradeId); + + /** + * 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=} + */ + List getSubStuList(String schSerId, Integer schSystemId); + + /** + * 按班级批量查学生(圈人:班级范围一次多班)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3} + */ + List getClaArrStuList(List classIds); + + /** + * 按学生ID批量查(圈人:指定学员范围)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3} + */ + List getStuIdsList(List stuIds); + + /** + * 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。 + * 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。 + * 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=} + */ + List getTeaTree(Integer schId, boolean toUser); + + List getStuTree(Integer schId,boolean toUser); + + /** + * 学校学科列表(含学科编码、名称、排序号)。 + * 对应:GET {@code {bschclass}/oapi/SubInfo/GetSchSublist?SchID=} + */ + List getSchSubList(String schSerId); + + /** + * 指定教师的全部任教(校验教师归属该校;SchID 过滤学校、UserTeaSerID 过滤教师)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=&UserTeaSerID=} + */ + List getSchTeachList(String schSerId, Integer teaId); + + /** + * 指定班级的全部任教(班级维度:该班所有教师与学科)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchClaTeachlist?ClassID=} + */ + List getSchClaTeachList(Integer classId); + + /** + * 学校全部任教(全校所有教师的班级任教关系)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=} + */ + List getSchAllTeachList(String schSerId); +} diff --git a/src/main/java/com/yunhai/homework/service/IContactsService.java b/src/main/java/com/yunhai/homework/service/IContactsService.java new file mode 100644 index 0000000..0e1fdce --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IContactsService.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 联系组业务接口。 + * 对应前端 Contacts 组接口:AddGroup/GetGroup/EditGroup/DelGroup/SetContactsTea/GetContactsTea/GetGroupTree/GetDeptRoleGroupTree/Import/Export。 + */ +public interface IContactsService { + + /** + * 添加联系组。 + * + * @param name 组名 + * @param type 类型(1 自定义组, 2 明信片) + */ + void addGroup(String name, Integer type); + + /** + * 获取联系组列表(含每组人数,分页)。 + * + * @param page 页码(从1起) + * @param count 每页条数 + * @param type 类型(1 自定义组, 2 明信片) + * @return Map: { list, total } + */ + Map getGroup(Integer page, Integer count, Integer type); + + /** + * 编辑联系组。 + * + * @param id 组ID + * @param name 组名 + * @param type 类型 + */ + void editGroup(Integer id, String name, Integer type); + + /** + * 删除联系组(支持批量,同时删除组内成员)。 + * + * @param ids 逗号分隔的组ID + */ + void delGroup(String ids); + + /** + * 设置联系组教师(全量替换:先删后增)。 + * + * @param gid 组ID + * @param ids 逗号分隔的用户ID + */ + void setContactsTea(Integer gid, String ids); + + /** + * 获取联系组教师列表(分页)。 + * + * @param gid 组ID + * @param page 页码 + * @param count 每页条数 + * @return Map: { list, total } + */ + Map getContactsTea(String gid, Integer page, Integer count); + + /** + * 获取联系组树(组节点+用户节点,递归构建)。 + * + * @param type 类型(1 自定义组, 2 明信片) + * @return 树形结构 List<Map> + */ + List> getGroupTree(Integer type); + + /** + * 获取部门角色组树。 + * C# 从外部API获取部门树+用户角色映射。Java版预留。 + * + * @return 树形结构 List<Map> + */ + List> getDeptRoleGroupTree(); +} diff --git a/src/main/java/com/yunhai/homework/service/IDictService.java b/src/main/java/com/yunhai/homework/service/IDictService.java new file mode 100644 index 0000000..31ef515 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IDictService.java @@ -0,0 +1,75 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 系统字典业务接口。 + * 对应前端 Base 组接口:GetSysDic/GetDictMangeList/AddDeptDic/UpdateDeptDic/DelDept/OrdbyDicIds/GetDeptSync/GetSchModuleIsSms。 + */ +public interface IDictService { + + /** + * 获取系统字典(下拉框用)。 + * C# GetSysDic: Type=1 部门类别, 其余类型略。 + * + * @param type 字典类型编号(1=部门类别) + * @return List<Map> 每项 {k, v} + */ + List> getSysDic(Integer type); + + /** + * 获取系统字典管理列表(含 SerID/CodeID/Name/Des/Enable)。 + * + * @param type 字典类型编号(0=部门类别) + * @return List<Map> + */ + List> getDictMangeList(String type); + + /** + * 添加发文单位。 + * + * @param name 单位名称 + */ + void addDeptDic(String name); + + /** + * 更新发文单位。 + * + * @param serID 记录ID + * @param name 名称 + * @param enable 启用状态 + */ + void updateDeptDic(Integer serID, String name, Integer enable); + + /** + * 删除发文单位(校验是否被作业引用)。 + * + * @param serID 记录ID + */ + void delDept(Integer serID); + + /** + * 发文单位排序。 + * + * @param ids 逗号分隔的 SerID 列表(按前端顺序) + */ + void ordbyDicIds(String ids); + + /** + * 本校是否开通短信模块(前端 dict/Sync,用于"本校是否开通短信"判断)。 + * + * @param schId 当前学校ID(取当前登录用户角色所属学校) + * @return 开通返回 true,未开通/未配置返回 false + */ + Boolean getSchModuleIsSms(Integer schId); + + /** + * 按发文部门字典 key(CodeID)解析部门名称;未命中返回 null。 + * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 + * + * @param deptId 作业 deptId(存的是 sys_dict.dict_key) + * @return 部门名称;无匹配时返回 null + */ + String resolveDeptName(String deptId); +} diff --git a/src/main/java/com/yunhai/homework/service/IFileService.java b/src/main/java/com/yunhai/homework/service/IFileService.java new file mode 100644 index 0000000..1b01bbf --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IFileService.java @@ -0,0 +1,94 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.UpFile; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.model.homework.UpFileResultVo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件上传/下载/删除业务接口。 + */ +public interface IFileService { + /** + * 上传单个文件。 + * + * @param file 上传的文件 + * @param appType 应用类型(如 "homework") + * @param topicId 关联主题ID(可为空,作业保存后再绑定) + * @return 文件视图对象 + */ + UpFileVo upload(MultipartFile file, String appType, Integer topicId); + + /** + * 将已上传但未绑定主题的附件绑定到指定作业。 + * + * @param fileIds 文件记录ID列表 + * @param topicId 作业ID + */ + void bindToTopic(List fileIds, Integer topicId); + + /** + * 查询某主题下的附件列表。 + * + * @param appType 应用类型 + * @param topicId 主题ID + * @return 附件列表 + */ + List listByTopic(String appType, Integer topicId); + + /** + * 下载文件。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void download(Integer fileId, HttpServletResponse response); + + /** + * 在线预览文件(Content-Disposition: inline,按扩展名输出真实 Content-Type, + * 供前端 open-file-viewer 取 Blob / video 播放)。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void preview(Integer fileId, HttpServletResponse response); + + /** + * 删除文件(同时删除物理文件和数据库记录)。 + * + * @param fileId 文件记录ID + */ + void delete(Integer fileId); + + /** + * 打包下载多个附件为 ZIP。 + * + * @param files 附件记录列表(调用方按 fileIds 查好) + * @param zipName 压缩包显示名(自动追加 .zip) + * @param response HTTP 响应 + */ + void downloadZip(List files, String zipName, HttpServletResponse response); + + /** + * 按主题类型和主题ID打包下载附件为 ZIP(Controller 便捷方法,内部自行查附件列表)。 + * + * @param topicType 主题类型(如 "homework") + * @param topicId 主题ID + * @param zipName 压缩包显示名 + * @param response HTTP 响应 + */ + void downloadZipByTopic(String topicType, Integer topicId, String zipName, HttpServletResponse response); + + /** + * 批量上传图片(循环上传,返回每个文件的视图对象列表)。 + */ + List uploadImages(MultipartFile[] files, String appType); + + /** + * 上传单个文件并返回前端兼容的结果 VO(含 fileid/name/fileurl/host/type/size)。 + */ + UpFileResultVo uploadToResult(MultipartFile file, String appType); +} diff --git a/src/main/java/com/yunhai/homework/service/IHomeworkService.java b/src/main/java/com/yunhai/homework/service/IHomeworkService.java new file mode 100644 index 0000000..ed50167 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IHomeworkService.java @@ -0,0 +1,142 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业业务接口。 + */ +public interface IHomeworkService { + + /** + * 发作业:以当前登录用户为发布人,状态置为「已发布」入库。 + * 若 request.timingSendTime 不为空且晚于当前时间,则作为定时发送, + * 未到点前用户端收件箱不可见(到点后由查询过滤自然可见)。 + * + * @return 新建作业 ID(用于后续回填附件 TopicID 等) + */ + Integer publish(HomeworkRequest request); + + /** + * 编辑作业:按 id 更新标题/内容/类型/部门/置顶,发布人与状态保持不变。 + */ + void edit(Integer id, HomeworkRequest request); + + /** + * 批量删除作业:删除主记录并级联清理阅读记录、接收人、附件关联。 + */ + void deleteBatch(List ids); + + /** + * 撤回作业:仅已发布且未撤回的作业可撤回,置 isPulled=1。 + */ + void pull(Integer id); + + /** + * 置顶/取消置顶。 + */ + void setTopmost(Integer id, Integer topmost); + + /** + * 转发作业:基于源作业创建新作业(已发布),parentId 关联源作业。 + */ + Integer forward(Integer sourceId, HomeworkRequest request); + + /** + * 用户端列表:仅展示已发布且已到点的作业,并标记当前用户是否已读,附未读总数。 + * 返回 HomeworkPageResult(扩展 unreadCount)。 + */ + HomeworkPageResult userList(HomeworkQuery query, Pageable pageable); + + /** + * 管理端列表:按条件查询全部作业(管理员查看全部)。 + */ + BasePageResult adminList(HomeworkQuery query, Pageable pageable); + + /** + * 作业详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。 + */ + HomeworkVo detail(Integer id); + + /** + * 编辑回显详情(不标记已读)。 + */ + HomeworkVo getDetail(Integer id); + + /** + * 标记已读:为当前用户写入(幂等)该作业的阅读记录。 + */ + void markRead(Integer id); + + /** + * 一键全部已读(批量化):当前用户所有未读的已发布作业批量插入阅读记录。 + * @return 已读条数 + */ + int initializeRed(); + + /** + * 追加接收人(事务化):并入接收人列表并重算 allowcount。 + */ + void receiveUsers(Integer homeworkId, String users); + + /** + * 上一篇/下一篇作业ID(SQL 相邻查询)。 + * @param serid 当前作业ID + * @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列) + * @return {prevId, nextId} + */ + Map getNextPrev(Integer serid, String type); + + /** + * 获取某作业的阅读明细列表。 + */ + List getReadDetail(Integer homeworkId); + + /** + * 阅读情况分组(已读/未读)。 + */ + List> getReadDetailGrouped(Integer serid); + + /** + * 催收未读:枚举未读接收人,后续接入消息推送。 + */ + void expedite(Integer serid); + + /** + * 作业统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。 + */ + HomeworkStatisticsVo statistics(HomeworkQuery query, LocalDateTime start, LocalDateTime end); + + /** + * 作业详情视图(含附件、接收人信息),供 Controller 返回。 + */ + HomeworkDetailVo getDetailMap(Integer serid); + + /** + * 已发列表附件批量查询(消除 N+1)。 + */ + Map> batchFileIds(List homeworkIds); + + /** + * 查看作业详情视图(含附件、接收人)。 + * type=1 时先标记已读再返回,type=0 时直接返回不标记。 + */ + HomeworkDetailVo detailMap(Integer serid, Integer type); + + /** + * 一键全部已读并返回结果(count = 已读条数)。 + */ + InitializeRedVo initializeRedResult(); + + /** + * 批量下载作业全部附件(打包 zip):查作业标题后调文件域打包下载。 + */ + void downloadAllFiles(Integer dataid, HttpServletResponse response); +} diff --git a/src/main/java/com/yunhai/homework/service/ISmsService.java b/src/main/java/com/yunhai/homework/service/ISmsService.java new file mode 100644 index 0000000..09b9b5f --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ISmsService.java @@ -0,0 +1,77 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置业务接口。 + * 对应前端 setSms 组接口:getmodule/getgroup/getschsms/getsch/setschsms/setschallsms/setschmodulesms。 + */ +public interface ISmsService { + + /** + * 获取短信模块列表。 + * C# 从 AuthConfig.AppKeyMap 读取。Java版从 sms_module 表读取。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getModuleList(); + + /** + * 获取学校父类组列表。 + * C# 从外部BLL获取学校组。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchParentGroup(); + + /** + * 分页获取学校短信开关列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param schName 学校名称(模糊搜索) + * @return Map: { list, total } + */ + Map getSchIsSmsList(Integer pageSize, Integer pageIndex, String schName); + + /** + * 获取学校列表。 + * C# 从外部BLL获取学校列表。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchList(); + + /** + * 设置单校单模块短信开关。 + * + * @param schSerID 学校ID + * @param moduleId 模块ID + * @param isSms 是否开通 + */ + void setSchSms(Integer schSerID, Integer moduleId, Boolean isSms); + + /** + * 设置某校全部模块短信开关。 + * + * @param schSerID 学校ID + * @param isSms 是否开通 + */ + void setSchSmsAll(Integer schSerID, Boolean isSms); + + /** + * 批量设置多校多模块短信开关。 + * + * @param schSerIDs 逗号分隔的学校ID + * @param modules 逗号分隔的模块ID + * @param isSms 是否开通 + */ + void setSchModuleSms(String schSerIDs, String modules, Boolean isSms); + + /** + * 从 JSON 字符串解析并设置单校单模块短信开关。 + * 解析 SchSerID/ModuID/IsSms 三个字段。 + */ + void setSchSmsFromJson(String itemsModuJson); +} diff --git a/src/main/java/com/yunhai/homework/service/ITemplateService.java b/src/main/java/com/yunhai/homework/service/ITemplateService.java new file mode 100644 index 0000000..5594401 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ITemplateService.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板业务接口。 + * 对应前端 Temp 组接口:GetTypelist/EditType/DelType/GetTemplatelist/EditTemplate/DelTemplate/GetTemplateFist。 + */ +public interface ITemplateService { + + /** + * 获取模板类型列表(首项固定为"全部")。 + * + * @return List<Map> 每项 {TypeID, TypeName} + */ + List> getTypelist(); + + /** + * 新增/编辑模板类型。 + * + * @param typeid 类型ID(0=新增) + * @param typeName 类型名称 + */ + void editType(Integer typeid, String typeName); + + /** + * 删除模板类型(校验是否有模板引用此类型)。 + * + * @param typeid 类型ID + */ + void delType(Integer typeid); + + /** + * 分页获取模板列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param typeid 类型ID(0=全部) + * @return Map: { list, total } + */ + Map getTemplatelist(Integer pageSize, Integer pageIndex, Integer typeid); + + /** + * 新增/编辑模板。 + * + * @param serID 模板ID(null=新增) + * @param title 标题 + * @param content 内容 + * @param typeId 类型ID + * @param cover 封面文件ID + */ + void editTemplate(Integer serID, String title, String content, Integer typeId, Integer cover); + + /** + * 删除模板。 + * + * @param serID 模板ID + */ + void delTemplate(Integer serID); + + /** + * 获取单个模板详情。 + * + * @param serID 模板ID + * @return Map 模板详情 + */ + Map getTemplateFist(Integer serID); +} diff --git a/src/main/java/com/yunhai/homework/service/IUserCenterService.java b/src/main/java/com/yunhai/homework/service/IUserCenterService.java new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IUserCenterService.java @@ -0,0 +1,51 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 用户中心门面服务(对齐 UserBase {@code UserCenterService} 模式)。 + * 封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。 + */ +public interface IUserCenterService { + + /** + * 获取当前用户学校组及用户(树形结构)。 + * 组装规则(从原 BaseApiController.getSchoolAll 下沉): + * + * userbase 读取当前学校整棵组树(getGroupById(schId, withSub=true)),剔除学生组(type=8)及其子组; + * bschclass 获取该校全部教师(/oapi/DbClass/GetSchteaList?Group=schId),按 GroupIds 挂到对应组下, + * 无有效组(如所属组全被剔除)的教师兜底挂学校根节点; + * 扁平节点按 pid 递归组装为树(空组剔除,同 C# FormatUserTree)。 + * + * + * @param schId 学校ID(用户当前角色所属学校) + * @return 用户树形结构(组节点 type=1 / 教师节点 type=2) + */ + List> getSchoolAllTree(Integer schId); + + /** + * 获取指定组内用户(教师 + 管理员)。 + * 对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。 + * + * @param gid 组ID + * @param role 角色(默认 2=教师;1=管理员等) + * @return 组内用户列表 + */ + List> getGroupUser(Integer gid, Integer role); + + /** + * 获取当前用户可访问的模块树(左侧菜单)。 + * 从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。 + * + * @param userId 用户ID + * @param schId 学校ID(可为 null) + * @return 模块树(顶层 List,含 children 嵌套) + */ + List> getUserModuleTree(Integer userId, Integer schId); +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/service/impl/ClassService.java b/src/main/java/com/yunhai/homework/service/impl/ClassService.java new file mode 100644 index 0000000..8be5e2a --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/impl/ClassService.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.service.impl; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.service.IClassService; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班级管理(bschclass)开放接口实现。 + * bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。 + * 推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List
+ * 对齐方案 M7:unreadCount 并入分页外壳扩展子类,不改基类契约。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkPageResult extends BasePageResult { + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkPageResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + super(content, total, pageIndex, pageSize, totalPages, hasNext); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java new file mode 100644 index 0000000..afea9a6 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkQuery.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.homework; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; + +/** + * 作业列表查询条件。 + * + * - 管理端(adminList):用 status / type / deptId / publisherId / 时间范围过滤; + * - 用户端(userList):额外用 readType 过滤未读/已读(0 全部 / 1 未读 / 2 已读)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkQuery extends BasePageQuery { + + /** 状态过滤:null 不过滤;1 已发布 */ + private Integer status; + + /** 类型过滤:1 普通 / 2 重要 */ + private Integer type; + + /** 列表范围(管理端):"user" 只看自己发布的,null 看全部 */ + private String scope; + + /** 发布部门ID */ + private String deptId; + + /** 发布人ID(用户中心数字ID,管理端按发布人查) */ + private Integer publisherId; + + /** 开始时间(创建时间 >=,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startTime; + + /** 结束时间(创建时间 <= 当天 23:59:59,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endTime; + + /** 阅读状态过滤(仅用户端):0 全部 / 1 未读 / 2 已读 */ + private Integer readType = 0; + + /** 排序:1 时间倒序(默认)/ 2 时间正序 */ + private String orderByTime = "1"; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java new file mode 100644 index 0000000..2cb560f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java @@ -0,0 +1,65 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import com.yunhai.homework.common.validation.ValidationGroups; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 作业发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。 + */ +@Data +public class HomeworkRequest { + + /** 作业标题 */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业标题不得为空") + private String title; + + /** 作业内容 */ + private String content; + + /** 作业类型:1 普通 / 2 重要 */ + @NotNull(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业类型不得为空") + private Integer type = 1; + + /** 发布部门ID(原 DeptID,必填——对应原系统「发布部门不得为空」校验) */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "发布部门不得为空") + private String deptId; + + /** 发布部门名称 */ + private String deptName; + + /** 是否置顶:0 否 / 1 是 */ + private Integer topmost = 0; + + /** 接收人列表(JSON 数组字符串,如 ["10012","10013"]——用户中心数字ID,或 "ALL" 表示全员) */ + private String receiveList; + + /** 附件文件ID列表(逗号分隔串,如 "1,2,3");旧名 chaoSend,已更名 fileIds(M6) */ + private String fileIds; + + /** 定时发送时间(为空表示立即发布) */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timingSendTime; + + /** 短信发送标记:0 不发短信 / 1 发短信(旧名 topmost 实指发短信,已正名 smsFlag,M6) */ + private Integer smsFlag = 0; + + /** 转发源作业ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */ + private Integer sourceId; + + // ==================== 课堂作业 V1/V2 扩展(前端契约字段,兼容层同步写新列) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deadline; + + /** 提交方式:text/image/attach/none 组合 */ + private java.util.List submitType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java new file mode 100644 index 0000000..c492f86 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java @@ -0,0 +1,113 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 作业统计视图(对应原系统 GetStatisData / 按发布人统计)。 + */ +@Data +public class HomeworkStatisticsVo { + + /** 作业总数(全部已发布) */ + private long total; + + /** 已发布数 */ + private long publishedCount; + + /** 阅读记录总数(累计已读人次) */ + private long totalReadRecords; + + /** 当前用户未读数(已到点的已发布 - 当前用户已读) */ + private long unreadForCurrentUser; + + /** 按发布人统计 */ + private List byPublisher; + + /** 按月统计(已发布作业数) */ + private List byMonth; + + // ==================== 课程作业口径(前端 /stats 页消费) ==================== + + /** 我布置的作业数(当前登录人发布的全部作业,含草稿) */ + private long myAssignCount; + + /** 我的作业待批改总量(已交未批的当前版本行合计) */ + private long pendingGrade; + + /** 各作业平均提交率(%,仅统计需收集提交的作业;无样本时为 0) */ + private double avgSubmitRate; + + /** 按作业的提交与批改进度明细(草稿与仅查阅型不计) */ + private List byHomework = new ArrayList<>(); + + /** 已批阅提交的分数分布(四档) */ + private List scoreDist = new ArrayList<>(); + + /** 单作业进度条目(口径对齐前端 HomeworkProgress 契约) */ + @Data + public static class HomeworkStat { + private Integer id; + private String title; + private String subject; + /** 旧契约四态派生值:draft / collecting / closed / done */ + private String hwStatus; + private String hwStatusDesc; + /** 应交人数 */ + private long should; + /** 已读人数 */ + private long read; + /** 已交人数 */ + private long submitted; + /** 已批阅人数 */ + private long graded; + /** 待批改人数 */ + private long pendingGrade; + /** 未交人数 */ + private long unsubmitted; + /** 提交率(%) */ + private double submitRate; + /** 批阅率(%) */ + private double gradeRate; + } + + /** 分数分布档(key 供前端配色映射:excellent/good/pass/fail) */ + @Data + public static class ScoreBucket { + private String label; + private String key; + private long count; + + public ScoreBucket(String label, String key, long count) { + this.label = label; + this.key = key; + this.count = count; + } + } + + @Data + public static class PublisherStat { + private String publisherName; + private long count; + + public PublisherStat(String publisherName, long count) { + this.publisherName = publisherName; + this.count = count; + } + } + + @Data + public static class MonthStat { + private int year; + private int month; + private long count; + + public MonthStat(int year, int month, long count) { + this.year = year; + this.month = month; + this.count = count; + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java new file mode 100644 index 0000000..c906e2e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.List; + +/** + * 用户端作业列表结果:在分页数据之外额外携带「当前用户未读总数」, + * 供前端展示未读角标(对应原系统 OtherData.unReadCount)。 + */ +@Data +public class HomeworkUserListResult { + + private int total; + private List content; + private int pageIndex; + private int pageSize; + private int totalPages; + private boolean hasNext; + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkUserListResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java new file mode 100644 index 0000000..333d49e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java @@ -0,0 +1,92 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 作业视图对象(列表/详情返回)。 + */ +@Data +public class HomeworkVo { + + private Integer id; + private String title; + private String content; + private Integer type; + private String typeDesc; + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + private String publisherName; + private String deptId; + private String deptName; + private Integer status; + private String statusDesc; + private Integer topmost; + + /** 已读人数(基于阅读记录实时统计) */ + private Long readCount = 0L; + + /** 应读人数 */ + private Integer allowCount; + + /** 当前用户是否已读(用户端列表使用;管理端恒为 false 或不关注) */ + private boolean read = false; + + /** 是否撤回:0 否 / 1 是 */ + private Integer isPulled; + + /** 定时发送时间 */ + private LocalDateTime timingSendTime; + + /** 父作业ID(转发时关联原作业) */ + private Integer parentId; + + /** 短信发送标记 */ + private Integer smsFlag; + + /** 接收人列表(JSON) */ + private String receiveList; + + /** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */ + private String UpFile; + + // ==================== 课堂作业 V1/V2 扩展(前端 types/homework.ts 契约) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间(前端契约字段名 deadline) */ + private LocalDateTime deadline; + + /** 提交方式数组:text/image/attach/none */ + private java.util.List submitType; + + /** 作业流程状态——旧契约四态派生值(draft/collecting/closed/done),权威五态见 homework.hw_status */ + private String hwStatus; + + private String hwStatusDesc; + + /** + * 提交/批改进度(管理端列表页「提交进度 / 未读未交 / 批改」列使用)。 + * 草稿或仅查阅型为 null;列表场景由 HomeworkProgressCalculator 批量聚合回填。 + */ + private com.yunhai.homework.model.v2.ProgressVo progress; + + private String createBy; + private LocalDateTime createTime; + private String updateBy; + private LocalDateTime updateTime; + + /** + * 阅读明细视图(阅读列表查询用)。 + */ + @Data + public static class ReadDetail { + /** 读者ID(用户中心数字ID) */ + private Integer id; + private String name; + private boolean read; + private LocalDateTime readTime; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java new file mode 100644 index 0000000..15ece01 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 一键已读结果视图对象(替代原 Map<String,Object> 返回)。 + */ +@Data +public class InitializeRedVo { + + @JsonProperty("count") + private Integer count; + + public InitializeRedVo() {} + + public InitializeRedVo(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java new file mode 100644 index 0000000..8127480 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java @@ -0,0 +1,30 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 文件上传结果视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class UpFileResultVo { + + @JsonProperty("fileid") + private Integer fileid; + + @JsonProperty("name") + private String name; + + @JsonProperty("fileurl") + private String fileurl; + + @JsonProperty("host") + private String host; + + @JsonProperty("type") + private String type; + + @JsonProperty("size") + private Integer size; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java new file mode 100644 index 0000000..76dfa2f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +/** + * 附件视图对象(对应原 .NET FileObj)。 + */ +@Data +public class UpFileVo { + + /** 文件记录ID */ + private Integer id; + + /** 文件真实名称 */ + private String fileTrueName; + + /** 文件存储路径(相对路径) */ + private String fileName; + + /** 文件大小(字节) */ + private Integer fileSize; + + /** 文件后缀名 */ + private String fileType; + + /** 关联主题ID */ + private Integer topicId; + + /** 应用类型 */ + private String topicType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java new file mode 100644 index 0000000..771eb3a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java @@ -0,0 +1,122 @@ +package com.yunhai.homework.model.homework; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户可访问模块(左菜单)视图对象。 + * 将基础平台返回的扁平 {@code List} 转换为前端 layout(参考 UserBase)期望的树结构: + * {@code modId}(路由高亮匹配)/{@code page}(跳转路径)/{@code children}(子菜单)。 + */ +public class UserModuleVo { + + /** 模块主键 id(基础平台 module id) */ + private Integer id; + + /** 菜单高亮匹配用:等价于后端模块 id */ + private Integer modId; + + /** 模块名称 */ + private String name; + + /** 父级模块 id(0 表示根节点) */ + private Integer parentId; + + /** 前端路由路径(由 BoaModule.path 映射而来) */ + private String page; + + /** 层级(0=根分组) */ + private Integer level; + + /** 菜单图标 */ + private String icon; + + /** 排序号 */ + private Integer orderNumber; + + /** 是否可用 */ + private Boolean usable; + + /** 子菜单 */ + private List children = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getModId() { + return modId; + } + + public void setModId(Integer modId) { + this.modId = modId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Integer getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(Integer orderNumber) { + this.orderNumber = orderNumber; + } + + public Boolean getUsable() { + return usable; + } + + public void setUsable(Boolean usable) { + this.usable = usable; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java new file mode 100644 index 0000000..bedf7b5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 附件条目(作业附件与提交附件共用)。fileUrl 必传;fileId 为 file/Upload 产物时回传。 + */ +@Data +public class AttachmentItem { + + /** up_file.id */ + private Integer fileId; + + /** 文件访问 URL */ + private String fileUrl; + + /** 文件类型:image / file */ + private String fileType; + + /** 文件名 */ + private String fileName; + + /** 排序号 */ + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassItem.java b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java new file mode 100644 index 0000000..459ea80 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java @@ -0,0 +1,18 @@ +package com.yunhai.homework.model.v2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 班级条目(作业参与班级展示)。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClassItem { + + private String classId; + + private String className; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java new file mode 100644 index 0000000..c0f400b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 年级→班级树(发布作业选班 + 应交名单基数来源)。 + * 叶子节点 id = 班级 id(class_student 快照聚合)。 + */ +@Data +public class ClassTreeNode { + + /** 年级节点为 gradeName,班级叶子为 classId */ + private String id; + + /** 展示名 */ + private String name; + + /** 是否父节点(年级) */ + private Boolean isParent; + + /** 叶子节点:在班学生数 */ + private Integer studentCount; + + private List children = new ArrayList<>(); +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java new file mode 100644 index 0000000..f8c5dec --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 延长截止请求(新截止时间必须晚于当前时间)。 + */ +@Data +public class ExtendRequest { + + @NotNull(message = "新截止时间不得为空") + private LocalDateTime deadlineAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java new file mode 100644 index 0000000..1ac46f8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java @@ -0,0 +1,36 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 作业详情汇总统计卡片(PRD §18 页 4 stats 接口出参)。 + */ +@Data +public class HomeworkStatsVo { + + private Integer homeworkId; + + private String title; + + private String hwStatus; + + private String hwStatusDesc; + + /** 总进度 */ + private ProgressVo total; + + /** 分班进度 */ + private List byClass; + + @Data + public static class ByClass { + + private String classId; + + private String className; + + private ProgressVo progress; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java new file mode 100644 index 0000000..9464910 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 创建/编辑作业请求(PRD §18 页 1 表单)。 + * 创建一律落草稿(DRAFT_UNPUBLISHED),发布/定时走独立接口。 + */ +@Data +public class HomeworkV2Request { + + /** 作业标题(必填 ≤50 字) */ + @NotBlank(message = "作业标题不得为空") + @Size(max = 50, message = "作业标题不超过50字") + private String title; + + /** 学科(字典值) */ + private String subject; + + /** 作业说明(富文本) */ + private String description; + + /** 参与班级 id 列表 */ + private List classIds; + + /** 附件列表 */ + private List attachments; + + /** 截止时间(精确到分钟;空 = 仅查阅型) */ + private LocalDateTime deadlineAt; + + /** 提交方式:image / attach / text / none 的组合(PRD 图片/文件/两者) */ + private List submitType; + + /** 截止后是否允许逾期提交:0 否 / 1 是 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天提醒(0 关 / 1 开) */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时提醒(0 关 / 1 开) */ + private Integer remind1h; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java new file mode 100644 index 0000000..8bf1b02 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java @@ -0,0 +1,73 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 作业视图对象 V2(草稿箱/已发布列表/详情共用)。 + * 列表场景 attachments/classes 可按需填充;progress 仅非草稿且非仅查阅型作业返回。 + */ +@Data +public class HomeworkV2Vo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 作业说明(富文本;列表场景可置空省流) */ + private String description; + + /** 作业状态(PRD 五态) */ + private String hwStatus; + + private String hwStatusDesc; + + /** 定时发布时间(复用 timing_send_time) */ + private LocalDateTime scheduledAt; + + /** 发布时间 */ + private LocalDateTime publishedAt; + + /** 截止时间 */ + private LocalDateTime deadlineAt; + + /** 归档时间 */ + private LocalDateTime archivedAt; + + /** 提交方式数组 */ + private List submitType; + + /** 截止后允许逾期提交:0/1 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天 */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时 */ + private Integer remind1h; + + /** 参与班级 */ + private List classes; + + /** 附件列表(详情场景填充) */ + private List attachments; + + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + + /** 发布人姓名 */ + private String publisherName; + + /** 创建/更新时间(审计) */ + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + /** 汇总进度(草稿或仅查阅型为 null) */ + private ProgressVo progress; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java new file mode 100644 index 0000000..0303eb8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知分页结果(含当前用户未读数)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationPageResult extends BasePageResult { + + /** 当前用户未读通知总数 */ + private long unreadCount; + + public NotificationPageResult(BasePageResult page, long unreadCount) { + super(page.getContent(), page.getTotal(), page.getPageIndex(), page.getPageSize(), + page.getTotalPages(), page.isHasNext()); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java new file mode 100644 index 0000000..f8af4a7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知列表查询。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationQuery extends BasePageQuery { + + /** 类型过滤(NotificationTypeEnum code) */ + private String type; + + /** 已读过滤:0 未读 / 1 已读(空 = 全部) */ + private Integer isRead; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java new file mode 100644 index 0000000..2a9b61a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知列表项。 + */ +@Data +public class NotificationVo { + + private Integer id; + + /** 通知类型 */ + private String type; + + private String typeDesc; + + private String title; + + private String content; + + private Integer homeworkId; + + private Integer relatedId; + + /** 是否已读:0/1 */ + private Integer isRead; + + private LocalDateTime readAt; + + private LocalDateTime createTime; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java new file mode 100644 index 0000000..201a460 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 提交/批阅/阅读进度(PRD §18 页 3 汇总口径)。 + * should(应交)为分母;打回未重交计入已交。 + */ +@Data +public class ProgressVo { + + /** 应交人数(参与班级在班学生去重) */ + private int should; + + /** 已读人数 */ + private int read; + + /** 已交人数(含逾期/打回/已批阅) */ + private int submitted; + + /** 已批阅人数 */ + private int graded; + + /** 待批阅人数(submitted - graded) */ + private int pendingGrade; + + /** 未交人数 */ + private int unsubmitted; + + /** 未读人数 */ + private int unread; + + /** 未读且未交人数(高亮口径:should - 已读∪已交) */ + private int unreadUndone; + + /** 阅读率(百分数,一位小数) */ + private double readRate; + + /** 提交率(百分数,一位小数) */ + private double submitRate; + + /** 批阅率(已批阅 / 已交,百分数一位小数;submitted=0 时为 0) */ + private double gradeRate; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java new file mode 100644 index 0000000..a97e0c1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 教师手动提醒请求:type = NOT_READ / NOT_SUBMITTED,studentIds 为空时提醒全部未读/未交学生。 + */ +@Data +public class RemindRequest { + + /** 提醒类型:NOT_READ / NOT_SUBMITTED */ + @NotBlank(message = "提醒类型不得为空") + private String type; + + /** 定向学生ID(用户中心数字ID;空 = 全部未读/未交) */ + private List studentIds; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java new file mode 100644 index 0000000..45d3f3e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 打回请求(PRD 页 6)。comment 为订正原因,必填。 + */ +@Data +public class ReturnRequest { + + @NotBlank(message = "打回须填写订正原因") + private String comment; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java new file mode 100644 index 0000000..166b2e1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 批阅保存请求(PRD 页 6)。score 与 comment 至少一项有值。 + */ +@Data +public class ReviewRequest { + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 圈划标注图片(P2,先收参) */ + private List markedImages; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java new file mode 100644 index 0000000..894b433 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 定时发布 / 取消定时请求。 + * cancel=true 时忽略 scheduledAt,作业回退草稿-未发布并清空定时时间。 + */ +@Data +public class ScheduleRequest { + + /** 定时发布时间(cancel=false 时必填且须晚于当前时间) */ + private LocalDateTime scheduledAt; + + /** 是否取消定时 */ + private Boolean cancel = false; + + @NotNull + public Boolean getCancel() { + return cancel; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java new file mode 100644 index 0000000..6372883 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 学生端作业详情(PRD §18 学生页 2)。进入详情后由前端停留 ≥3 秒再调 read 上报。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentHomeworkDetailVo extends StudentHomeworkVo { + + /** 作业说明(富文本) */ + private String description; + + /** 作业附件 */ + private List attachments; + + /** 参与班级(学生所属班级视角) */ + private List classes; + + /** 我当前版本的提交附件(未提交为 null) */ + private List myAttachments; + + /** 我的文本内容(当前版本) */ + private String myText; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java new file mode 100644 index 0000000..9d30037 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学生端作业列表项(PRD §18 学生页 1)。 + */ +@Data +public class StudentHomeworkVo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 任课教师(发布人姓名) */ + private String teacherName; + + /** 截止时间(前端做倒计时) */ + private LocalDateTime deadlineAt; + + /** 提交方式 */ + private java.util.List submitType; + + /** 作业状态 */ + private String hwStatus; + + private String hwStatusDesc; + + /** 截止后是否允许逾期补交:0 否 / 1 是(学生端据此决定是否渲染「补交」入口) */ + private Integer allowLate; + + /** 我的提交概要(未提交为 null) */ + private MySubmission my; + + @Data + public static class MySubmission { + + /** 当前版本提交行ID */ + private Integer submissionId; + + /** 提交状态(PRD 五态) */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 版本号 */ + private Integer version; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分(已批阅才有) */ + private java.math.BigDecimal score; + + /** 评语 */ + private String comment; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java new file mode 100644 index 0000000..49e3144 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学生矩阵查询(PRD §18 页 5)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentMatrixQuery extends BasePageQuery { + + /** 班级筛选(空 = 全部参与班级) */ + private String classId; + + /** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */ + private String submitStatus; + + /** 批阅状态筛选:PENDING/REVIEWED/RETURNED */ + private String reviewStatus; + + /** 只看待批阅(reviewStatus=PENDING 且已提交)快捷过滤 */ + private Boolean pendingGrade; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java new file mode 100644 index 0000000..6257285 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java @@ -0,0 +1,57 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学生矩阵行(PRD §18 页 5)。未交学生也生成占位行(submissionId=0)。 + */ +@Data +public class StudentMatrixVo { + + /** 当前版本提交行ID(未交占位为 0) */ + private Integer submissionId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + /** 姓名 */ + private String studentName; + + private String classId; + + private String className; + + /** 阅读状态:0 未读 / 1 已读 */ + private Integer isRead; + + /** 首次阅读时间 */ + private LocalDateTime firstReadAt; + + /** 最近阅读时间 */ + private LocalDateTime lastReadAt; + + /** 提交状态(PRD 五态;未交为 NOT_SUBMITTED) */ + private String submitStatus; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期:0/1 */ + private Integer isLate; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分 */ + private BigDecimal score; + + /** 最新提交附件 URL 列表(缩略图) */ + private List thumbnails; + + /** 提交内容类型摘要:image=拍照/图片、attach=上传附件、text=文字作答(未交为空列表) */ + private List contentTypes; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java new file mode 100644 index 0000000..f4d9cd1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 学生查看批阅结果(PRD §18 学生页 2 result 接口)。 + */ +@Data +public class StudentResultVo { + + private Integer homeworkId; + + private String homeworkTitle; + + /** 当前版本批阅结果(未批阅为 null) */ + private SubmissionVersionVo latest; + + /** 全部版本(含各版评语,新→旧) */ + private List versions; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java new file mode 100644 index 0000000..1fa7545 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 学生提交作业请求(PRD §18 学生页 3)。附件只接受 file/Upload 产物(fileId 或平台 URL),拒绝 dataURL。 + */ +@Data +public class StudentSubmitRequest { + + /** 文本内容(submitType 含 text 时有效) */ + @Size(max = 20000, message = "文本内容过长") + private String text; + + /** 附件列表(图片/文件) */ + private List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java new file mode 100644 index 0000000..6c576d9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 学科 DTO(布置过的学科 / 我的任教学科共用返回结构)。 + */ +@Data +public class SubjectDto { + + /** 学科编码(homework.subject 字典值 / bschclass SubID) */ + private String subId; + + /** 学科名称(本校学科字典解析;字典缺失时回退为编码) */ + private String subName; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java new file mode 100644 index 0000000..cac00dc --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 撤回重交动作(PUT /student/homeworks/{id}/submissions/{submissionId})。 + * withdraw = 撤回(截止前,产生 NOT_SUBMITTED 新版本行);resubmit = 打回后重交(走提交语义)。 + */ +@Data +public class SubmissionActionRequest { + + @NotBlank(message = "action 不得为空") + private String action; + + /** resubmit 时的提交内容(等同 StudentSubmitRequest) */ + private String text; + + /** resubmit 时的附件 */ + private java.util.List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java new file mode 100644 index 0000000..bff2972 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java @@ -0,0 +1,63 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 提交版本条目(历史版本/批阅弹窗/学生结果页共用)。 + */ +@Data +public class SubmissionVersionVo { + + private Integer submissionId; + + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + private String studentName; + + /** 版本号 */ + private Integer version; + + /** 是否当前版本:0/1 */ + private Integer isLatest; + + /** 提交状态 */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 文本内容 */ + private String text; + + /** 附件 */ + private List attachments; + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 批阅状态 */ + private String reviewStatus; + + private String reviewStatusDesc; + + /** 批阅人(展示姓名快照,缺失时回退ID) */ + private String reviewedBy; + + /** 批阅时间 */ + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java new file mode 100644 index 0000000..6d6991e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教师端作业列表查询(草稿箱/已发布列表共用)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TeacherHomeworkQuery extends BasePageQuery { + + /** 学科筛选 */ + private String subject; + + /** 作业状态筛选(已发布列表:PUBLISHED/CLOSED/ARCHIVED) */ + private String hwStatus; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java new file mode 100644 index 0000000..d9fd64e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 任教班级 VO(按学科筛选任教班级返回结构)。 + */ +@Data +public class TeachingClassVo { + + /** 班级业务ID(bschclass sch_class.class_id) */ + private String classId; + + /** 班级名称(学校班级列表映射;缺失时回退为班级ID) */ + private String className; +} diff --git a/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java new file mode 100644 index 0000000..0581e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ClassStudent; +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; + +/** + * 班级学生名册仓储。 + */ +public interface ClassStudentRepository extends JpaRepository { + + /** 按学生ID(用户中心数字ID)反查学生身份(学生端接口入口) */ + List findByStudentIdAndStatus(Integer studentId, Integer status); + + /** 班级在班学生 */ + List findByClassIdAndStatus(String classId, Integer status); + + /** 指定班级集合的全部在班学生(应交名单) */ + List findByClassIdInAndStatus(List classIds, Integer status); + + /** 指定学校的全部名册(同步任务全量刷新用) */ + List findBySchoolId(Integer schoolId); + + /** 年级→班级树:按学校去重班级(class_student 快照聚合),列序:classId, className, gradeName */ + @Query("select distinct c.classId, c.className, c.gradeName from ClassStudent c " + + "where c.schoolId = :schoolId and c.status = 1") + List findDistinctClasses(@Param("schoolId") Integer schoolId); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java new file mode 100644 index 0000000..4dc9ee4 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java @@ -0,0 +1,27 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroup; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ContactsGroupRepository extends JpaRepository { + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + + /** + * 按创建人ID(用户中心数字ID)查询联系组列表。 + */ + List findByCreatorId(Integer creatorId); + + /** + * 按创建人ID查询联系组列表(分页)。 + */ + Page findByCreatorId(Integer creatorId, Pageable pageable); + + /** + * 查询全部联系组(按创建时间降序)。 + */ + List findAllByOrderByCreateTimeDesc(); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java new file mode 100644 index 0000000..df5ab28 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java @@ -0,0 +1,42 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroupUser; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ContactsGroupUserRepository extends JpaRepository { + Page findByGroupId(Integer groupId, Pageable pageable); + List findByGroupId(Integer groupId); + + /** + * 按组ID批量删除成员(JPQL bulk delete,SQL 立即执行)。 + * 注意:不能用 derived delete——其 DELETE 延迟到 flush 才执行, + * 同事务内先 insert 会触发 uk_group_user 唯一约束冲突。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId = :groupId") + void deleteByGroupIdBulk(@Param("groupId") Integer groupId); + + /** + * 按多个组ID批量删除成员。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId in :groupIds") + void deleteByGroupIdInBulk(@Param("groupIds") List groupIds); + + /** + * 按多个组ID查询所有成员(用于统计每组人数、组树构建)。 + */ + List findByGroupIdIn(List groupIds); + + /** + * 按组ID和用户ID查询(判断是否已存在)。 + */ + boolean existsByGroupIdAndUserId(Integer groupId, Integer userId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java new file mode 100644 index 0000000..1e6af55 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 作业附件仓储。 + */ +public interface HomeworkAttachmentRepository extends JpaRepository { + + List findByHomeworkIdOrderBySortAscIdAsc(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java new file mode 100644 index 0000000..8f651f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkClass; +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; + +/** + * 作业-班级关联仓储。 + */ +public interface HomeworkClassRepository extends JpaRepository { + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 指定班级的全部作业ID(学生端列表倒查用) */ + @Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds") + List findHomeworkIdsByClassIds(@Param("classIds") List classIds); + + /** 批量查 N 个作业的班级关联(列表展示避免 N+1) */ + @Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java new file mode 100644 index 0000000..6c16a81 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java @@ -0,0 +1,23 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReadRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 学生阅读记录仓储。 + */ +public interface HomeworkReadRecordRepository extends JpaRepository { + + Optional findByHomeworkIdAndStudentId(Integer homeworkId, Integer studentId); + + List findByHomeworkId(Integer homeworkId); + + /** 批量查 N 个作业的已读记录(列表统计避免 N+1) */ + List findByHomeworkIdIn(Collection homeworkIds); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java new file mode 100644 index 0000000..20dc83c --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkRead; +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.Optional; + +/** + * 作业阅读记录仓储(懒标记模型)。 + */ +public interface HomeworkReadRepository extends JpaRepository { + + /** + * 某用户是否已读某作业。 + */ + boolean existsByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 某作业的总已读人数。 + */ + long countByHomeworkId(Integer homeworkId); + + /** + * 删除某作业的全部阅读记录(作业删除时级联清理)。 + */ + void deleteByHomeworkId(Integer homeworkId); + + /** + * 某用户已读的作业总数(用于「我的未读」= 已发布总数 - 已读总数)。 + */ + long countByReaderId(Integer readerId); + + /** + * 查询某用户已读的作业ID列表(用于列表页批量判定未读)。 + */ + @Query("select r.homeworkId from HomeworkRead r where r.readerId = :readerId") + List findHomeworkIdsByReaderId(@Param("readerId") Integer readerId); + + /** + * 查询某用户对某作业的阅读记录(标记已读时幂等判断)。 + */ + Optional findByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 查询某作业的全部阅读记录(按阅读时间倒序),用于阅读明细列表。 + */ + @Query("select r from HomeworkRead r where r.homeworkId = :homeworkId order by r.readTime desc") + List findByHomeworkIdOrderByReadTimeDesc(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java new file mode 100644 index 0000000..402d455 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReceiver; +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; + +/** + * 作业接收人关联表仓储。 + */ +public interface HomeworkReceiverRepository extends JpaRepository { + + /** 按作业ID查询全部接收人 */ + List findByHomeworkId(Integer homeworkId); + + /** 按作业ID删除全部接收人 */ + void deleteByHomeworkId(Integer homeworkId); + + /** 按作业ID和接收类型查询接收人 */ + List findByHomeworkIdAndReceiveType(Integer homeworkId, Integer receiveType); + + /** + * 批量查 N 个作业的接收人,用于列表展示(避免 N+1)。 + * @param homeworkIds 作业ID列表 + * @return 接收人关联记录列表 + */ + @Query("select r from HomeworkReceiver r where r.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java new file mode 100644 index 0000000..bf862b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java @@ -0,0 +1,139 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Homework; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 课程作业仓储。 + */ +public interface HomeworkRepository extends JpaRepository, + JpaSpecificationExecutor { + + /** + * 作业流程状态条件更新(CAS):仅当当前状态为 from 时更新为 to。 + * 状态机单点收敛用,天然幂等、并发安全(定时任务与接口共用)。 + * clearAutomatically:批量更新绕过一级缓存,必须清空以避免后续 findById 读到旧实体 + * 再被 save() 写回旧状态(同事务内必现,集成测试发现)。 + * + * @return 影响行数(0 = 状态已变化,调用方应放弃本次迁移) + */ + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update Homework h set h.hwStatus = :to where h.id = :id and h.hwStatus = :from") + int casHwStatus(@Param("id") Integer id, + @Param("from") String from, + @Param("to") String to); + + /** + * 定时发布任务扫描:定时待发布且已到点。 + */ + List findByHwStatusAndTimingSendTimeLessThanEqual(String hwStatus, LocalDateTime now); + + /** + * 到点关闭任务扫描:进行中且已过截止时间。 + */ + List findByHwStatusAndDeadlineAtBefore(String hwStatus, LocalDateTime now); + + /** + * 截止提醒任务扫描:进行中且截止时间落在指定窗口内(remind_1d/remind_1h 开关在服务侧逐单过滤)。 + */ + List findByHwStatusAndDeadlineAtBetween(String hwStatus, LocalDateTime start, LocalDateTime end); + + /** + * 按作业流程状态查询(兼容层/统计用)。 + */ + List findByHwStatus(String hwStatus); + + /** + * 当前登录人发布的全部作业(课程作业口径统计:我布置的作业 / 提交与批改进度明细)。 + */ + List findByPublisherId(Integer publisherId); + + /** + * 当前登录人布置过(已发布及之后状态)作业的学科去重清单(学科筛选用,字典序;草稿不计)。 + */ + @Query("select distinct h.subject from Homework h " + + "where h.publisherId = :publisherId and h.subject is not null and h.subject <> '' " + + "and h.hwStatus in :statuses order by h.subject") + List findAssignedSubjects(@Param("publisherId") Integer publisherId, + @Param("statuses") Collection statuses); + + /** + * 按状态计数(用于统计已发布总数)。 + */ + long countByStatus(Integer status); + + /** + * 已发布且已到点(定时发送未到点不计入)的作业数,用于用户端未读数统计。 + */ + @Query("select count(n) from Homework n where n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDue(@Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定状态、指定时间范围内的作业创建时间(用于按月统计,避免加载大字段)。 + */ + @Query("select n.createTime from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定状态、指定时间范围内的发布人姓名(用于按发布人统计)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 按学校和状态计数(多租户隔离统计)。 + */ + long countBySchIdAndStatus(Integer schId, Integer status); + + /** + * 按学校计数(多租户隔离统计:全部状态)。 + */ + long countBySchId(Integer schId); + + /** + * 已发布且已到点(定时发送未到点不计入)且属于指定学校的作业数(用户端未读数统计,多租户隔离)。 + */ + @Query("select count(n) from Homework n where n.schId = :schId and n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDueBySchId(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定学校、指定状态、指定时间范围内的作业创建时间(按月统计,多租户隔离)。 + */ + @Query("select n.createTime from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesBySchIdStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定学校、指定状态、指定时间范围内的发布人姓名(按发布人统计,多租户隔离)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesBySchStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java new file mode 100644 index 0000000..547c5ea --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkSubmission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 作业提交仓储(多版本)。统计一律基于 is_latest=1 的当前版本行。 + */ +public interface HomeworkSubmissionRepository extends JpaRepository { + + /** 学生在指定作业的当前版本行 */ + Optional findByHomeworkIdAndStudentIdAndIsLatest(Integer homeworkId, + Integer studentId, + Integer isLatest); + + /** 学生在指定作业的全部版本(新→旧) */ + List findByHomeworkIdAndStudentIdOrderByVersionDesc(Integer homeworkId, Integer studentId); + + /** 指定作业的当前版本行(统计/矩阵用) */ + List findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest); + + /** 批量查 N 个作业的当前版本行(列表统计避免 N+1) */ + List findByHomeworkIdInAndIsLatest(Collection homeworkIds, Integer isLatest); + + /** 学生矩阵分页 */ + Page findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest, Pageable pageable); + + /** 学生当前版本最大版本号 */ + @Query("select max(s.version) from HomeworkSubmission s " + + "where s.homeworkId = :homeworkId and s.studentId = :studentId") + Integer findMaxVersion(@Param("homeworkId") Integer homeworkId, @Param("studentId") Integer studentId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 附件等子表清理后按作业删除历史版本行 */ + @Modifying + @Query("delete from HomeworkSubmission s where s.homeworkId = :homeworkId") + int deleteAllByHomeworkId(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java new file mode 100644 index 0000000..ecc75cb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HomeworkTemplateRepository extends JpaRepository { + Page findByTypeId(Integer typeId, Pageable pageable); + + /** + * 查询全部模板(按引用次数降序),分页。 + */ + Page findAllByOrderByRefCountDesc(Pageable pageable); + + /** + * 按类型ID查询全部模板(不分页,用于检查类型是否被引用)。 + */ + long countByTypeId(Integer typeId); +} diff --git a/src/main/java/com/yunhai/homework/repository/NotificationRepository.java b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java new file mode 100644 index 0000000..1066c10 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + * 站内通知仓储。 + */ +public interface NotificationRepository extends JpaRepository { + + Page findByReceiverId(Integer receiverId, Pageable pageable); + + Page findByReceiverIdAndType(Integer receiverId, String type, Pageable pageable); + + Page findByReceiverIdAndIsRead(Integer receiverId, Integer isRead, Pageable pageable); + + Page findByReceiverIdAndTypeAndIsRead(Integer receiverId, String type, + Integer isRead, Pageable pageable); + + long countByReceiverIdAndIsRead(Integer receiverId, Integer isRead); + + Optional findByIdAndReceiverId(Integer id, Integer receiverId); + + /** 截止提醒去重:同作业同类型同档位已发过即跳过(related_id 存档位:1=1天档 2=1小时档) */ + boolean existsByHomeworkIdAndTypeAndRelatedId(Integer homeworkId, String type, Integer relatedId); + + /** 截止提醒去重(不分档位) */ + boolean existsByHomeworkIdAndType(Integer homeworkId, String type); + + /** 全部已读(clearAutomatically 防同事务内缓存脏读) */ + @Modifying(clearAutomatically = true) + @Query("update Notification n set n.isRead = 1, n.readAt = :now " + + "where n.receiverId = :receiverId and n.isRead = 0") + int markAllRead(@Param("receiverId") Integer receiverId, @Param("now") java.time.LocalDateTime now); + + /** 按作业删除通知(作业删除级联) */ + void deleteByHomeworkId(Integer homeworkId); + + /** 指定作业的全部通知(清理/统计用) */ + List findByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java new file mode 100644 index 0000000..8e60f47 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SchClassTeach; +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; + +/** + * 班级任教关系快照仓储。 + */ +public interface SchClassTeachRepository extends JpaRepository { + + /** 指定学校指定教师的全部有效任教记录 */ + List findBySchoolIdAndTeaIdAndStatus(Integer schoolId, Integer teaId, Integer status); + + /** 指定学校指定教师去重学科编码(字典序由调用方保证) */ + @Query("select distinct t.subId from SchClassTeach t " + + "where t.schoolId = :schoolId and t.teaId = :teaId and t.status = 1") + List findDistinctSubIds(@Param("schoolId") Integer schoolId, @Param("teaId") Integer teaId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java new file mode 100644 index 0000000..18595bb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java @@ -0,0 +1,13 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsModule; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsModuleRepository extends JpaRepository { + List findAllByOrderByIdAsc(); + + /** 按模块标识查询(如 module_code="homework")。 */ + SmsModule findByModuleCode(String moduleCode); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java new file mode 100644 index 0000000..0d277a0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsSchoolConfig; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsSchoolConfigRepository extends JpaRepository { + Page findBySchoolNameContaining(String schoolName, Pageable pageable); + Page findAll(Pageable pageable); + List findBySchoolId(Integer schoolId); + List findBySchoolIdInAndModuleIdIn(List schoolIds, List moduleIds); + + /** + * 按学校ID和模块ID查询单条配置(判断是否已存在)。 + */ + SmsSchoolConfig findBySchoolIdAndModuleId(Integer schoolId, Integer moduleId); + + /** + * 按学校ID查询全部模块配置(用于"设置某校全部模块")。 + */ + List findBySchoolIdIn(List schoolIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java new file mode 100644 index 0000000..8848b14 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SubmissionAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; + +/** + * 提交附件仓储。 + */ +public interface SubmissionAttachmentRepository extends JpaRepository { + + List findBySubmissionIdOrderBySortAscIdAsc(Integer submissionId); + + /** 批量查 N 个提交行的附件(矩阵/版本列表避免 N+1) */ + List findBySubmissionIdIn(Collection submissionIds); + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SysDictRepository.java b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java new file mode 100644 index 0000000..c083840 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SysDict; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SysDictRepository extends JpaRepository { + List findByDictTypeOrderBySortOrderAsc(String dictType); + List findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag); + + /** 按类型+key 取单条字典(发文部门名称解析用) */ + Optional findByDictTypeAndDictKey(String dictType, String dictKey); +} diff --git a/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java new file mode 100644 index 0000000..a3a0f12 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.TemplateType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TemplateTypeRepository extends JpaRepository { + List findAllByOrderBySortOrderAsc(); + + /** + * 按类型名称查询(重名校验)。 + */ + boolean existsByTypeName(String typeName); + + /** + * 按类型名称查询(重名校验,排除自身)。 + */ + boolean existsByTypeNameAndIdNot(String typeName, Integer id); +} diff --git a/src/main/java/com/yunhai/homework/repository/UpFileRepository.java b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java new file mode 100644 index 0000000..421f142 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.UpFile; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +/** + * 附件文件仓储。 + */ +public interface UpFileRepository extends JpaRepository { + + /** + * 按主题类型和主题ID查询附件列表。 + */ + List findByTopicTypeAndTopicId(String topicType, Integer topicId); + + /** + * 按主题类型和多个主题ID批量查询附件(消除 N+1)。 + */ + List findByTopicTypeAndTopicIdIn(String topicType, List topicIds); + + /** + * 按主题类型和主题ID删除附件(作业删除时级联清理)。 + */ + void deleteByTopicTypeAndTopicId(String topicType, Integer topicId); +} diff --git a/src/main/java/com/yunhai/homework/service/IClassService.java b/src/main/java/com/yunhai/homework/service/IClassService.java new file mode 100644 index 0000000..2346cd9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IClassService.java @@ -0,0 +1,116 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; + +import java.util.List; + +/** + * 班级管理(bschclass)开放接口客户端门面。 + * 基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。 + * 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。 + */ +public interface IClassService { + + /** + * 获取指定学校全部教师(含所属组 GroupIds)。 + * 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}} + * + * @param schId 学校ID(即用户当前身份的 schId) + */ + List getSchTeaList(int schId); + + /** + * 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。 + * 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}} + */ + SchInfoDto getSchFirst(int schId); + + /** + * 学校学段(学制)列表:小学部 / 初中部…。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=} + */ + List getSchLenList(String schSerId); + + /** + * 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=} + */ + List getSchGradeList(String schSerId); + + /** + * 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。 + * 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=} + */ + List getSchClassList(String schSerId); + + /** + * 按校全部学生(圈人:全校范围展开)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=} + */ + List getSchStuList(String schSerId); + + /** + * 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=} + */ + List getGradeStuList(String schSerId, Integer gradeId); + + /** + * 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=} + */ + List getSubStuList(String schSerId, Integer schSystemId); + + /** + * 按班级批量查学生(圈人:班级范围一次多班)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3} + */ + List getClaArrStuList(List classIds); + + /** + * 按学生ID批量查(圈人:指定学员范围)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3} + */ + List getStuIdsList(List stuIds); + + /** + * 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。 + * 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。 + * 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=} + */ + List getTeaTree(Integer schId, boolean toUser); + + List getStuTree(Integer schId,boolean toUser); + + /** + * 学校学科列表(含学科编码、名称、排序号)。 + * 对应:GET {@code {bschclass}/oapi/SubInfo/GetSchSublist?SchID=} + */ + List getSchSubList(String schSerId); + + /** + * 指定教师的全部任教(校验教师归属该校;SchID 过滤学校、UserTeaSerID 过滤教师)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=&UserTeaSerID=} + */ + List getSchTeachList(String schSerId, Integer teaId); + + /** + * 指定班级的全部任教(班级维度:该班所有教师与学科)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchClaTeachlist?ClassID=} + */ + List getSchClaTeachList(Integer classId); + + /** + * 学校全部任教(全校所有教师的班级任教关系)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=} + */ + List getSchAllTeachList(String schSerId); +} diff --git a/src/main/java/com/yunhai/homework/service/IContactsService.java b/src/main/java/com/yunhai/homework/service/IContactsService.java new file mode 100644 index 0000000..0e1fdce --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IContactsService.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 联系组业务接口。 + * 对应前端 Contacts 组接口:AddGroup/GetGroup/EditGroup/DelGroup/SetContactsTea/GetContactsTea/GetGroupTree/GetDeptRoleGroupTree/Import/Export。 + */ +public interface IContactsService { + + /** + * 添加联系组。 + * + * @param name 组名 + * @param type 类型(1 自定义组, 2 明信片) + */ + void addGroup(String name, Integer type); + + /** + * 获取联系组列表(含每组人数,分页)。 + * + * @param page 页码(从1起) + * @param count 每页条数 + * @param type 类型(1 自定义组, 2 明信片) + * @return Map: { list, total } + */ + Map getGroup(Integer page, Integer count, Integer type); + + /** + * 编辑联系组。 + * + * @param id 组ID + * @param name 组名 + * @param type 类型 + */ + void editGroup(Integer id, String name, Integer type); + + /** + * 删除联系组(支持批量,同时删除组内成员)。 + * + * @param ids 逗号分隔的组ID + */ + void delGroup(String ids); + + /** + * 设置联系组教师(全量替换:先删后增)。 + * + * @param gid 组ID + * @param ids 逗号分隔的用户ID + */ + void setContactsTea(Integer gid, String ids); + + /** + * 获取联系组教师列表(分页)。 + * + * @param gid 组ID + * @param page 页码 + * @param count 每页条数 + * @return Map: { list, total } + */ + Map getContactsTea(String gid, Integer page, Integer count); + + /** + * 获取联系组树(组节点+用户节点,递归构建)。 + * + * @param type 类型(1 自定义组, 2 明信片) + * @return 树形结构 List<Map> + */ + List> getGroupTree(Integer type); + + /** + * 获取部门角色组树。 + * C# 从外部API获取部门树+用户角色映射。Java版预留。 + * + * @return 树形结构 List<Map> + */ + List> getDeptRoleGroupTree(); +} diff --git a/src/main/java/com/yunhai/homework/service/IDictService.java b/src/main/java/com/yunhai/homework/service/IDictService.java new file mode 100644 index 0000000..31ef515 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IDictService.java @@ -0,0 +1,75 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 系统字典业务接口。 + * 对应前端 Base 组接口:GetSysDic/GetDictMangeList/AddDeptDic/UpdateDeptDic/DelDept/OrdbyDicIds/GetDeptSync/GetSchModuleIsSms。 + */ +public interface IDictService { + + /** + * 获取系统字典(下拉框用)。 + * C# GetSysDic: Type=1 部门类别, 其余类型略。 + * + * @param type 字典类型编号(1=部门类别) + * @return List<Map> 每项 {k, v} + */ + List> getSysDic(Integer type); + + /** + * 获取系统字典管理列表(含 SerID/CodeID/Name/Des/Enable)。 + * + * @param type 字典类型编号(0=部门类别) + * @return List<Map> + */ + List> getDictMangeList(String type); + + /** + * 添加发文单位。 + * + * @param name 单位名称 + */ + void addDeptDic(String name); + + /** + * 更新发文单位。 + * + * @param serID 记录ID + * @param name 名称 + * @param enable 启用状态 + */ + void updateDeptDic(Integer serID, String name, Integer enable); + + /** + * 删除发文单位(校验是否被作业引用)。 + * + * @param serID 记录ID + */ + void delDept(Integer serID); + + /** + * 发文单位排序。 + * + * @param ids 逗号分隔的 SerID 列表(按前端顺序) + */ + void ordbyDicIds(String ids); + + /** + * 本校是否开通短信模块(前端 dict/Sync,用于"本校是否开通短信"判断)。 + * + * @param schId 当前学校ID(取当前登录用户角色所属学校) + * @return 开通返回 true,未开通/未配置返回 false + */ + Boolean getSchModuleIsSms(Integer schId); + + /** + * 按发文部门字典 key(CodeID)解析部门名称;未命中返回 null。 + * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 + * + * @param deptId 作业 deptId(存的是 sys_dict.dict_key) + * @return 部门名称;无匹配时返回 null + */ + String resolveDeptName(String deptId); +} diff --git a/src/main/java/com/yunhai/homework/service/IFileService.java b/src/main/java/com/yunhai/homework/service/IFileService.java new file mode 100644 index 0000000..1b01bbf --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IFileService.java @@ -0,0 +1,94 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.UpFile; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.model.homework.UpFileResultVo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件上传/下载/删除业务接口。 + */ +public interface IFileService { + /** + * 上传单个文件。 + * + * @param file 上传的文件 + * @param appType 应用类型(如 "homework") + * @param topicId 关联主题ID(可为空,作业保存后再绑定) + * @return 文件视图对象 + */ + UpFileVo upload(MultipartFile file, String appType, Integer topicId); + + /** + * 将已上传但未绑定主题的附件绑定到指定作业。 + * + * @param fileIds 文件记录ID列表 + * @param topicId 作业ID + */ + void bindToTopic(List fileIds, Integer topicId); + + /** + * 查询某主题下的附件列表。 + * + * @param appType 应用类型 + * @param topicId 主题ID + * @return 附件列表 + */ + List listByTopic(String appType, Integer topicId); + + /** + * 下载文件。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void download(Integer fileId, HttpServletResponse response); + + /** + * 在线预览文件(Content-Disposition: inline,按扩展名输出真实 Content-Type, + * 供前端 open-file-viewer 取 Blob / video 播放)。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void preview(Integer fileId, HttpServletResponse response); + + /** + * 删除文件(同时删除物理文件和数据库记录)。 + * + * @param fileId 文件记录ID + */ + void delete(Integer fileId); + + /** + * 打包下载多个附件为 ZIP。 + * + * @param files 附件记录列表(调用方按 fileIds 查好) + * @param zipName 压缩包显示名(自动追加 .zip) + * @param response HTTP 响应 + */ + void downloadZip(List files, String zipName, HttpServletResponse response); + + /** + * 按主题类型和主题ID打包下载附件为 ZIP(Controller 便捷方法,内部自行查附件列表)。 + * + * @param topicType 主题类型(如 "homework") + * @param topicId 主题ID + * @param zipName 压缩包显示名 + * @param response HTTP 响应 + */ + void downloadZipByTopic(String topicType, Integer topicId, String zipName, HttpServletResponse response); + + /** + * 批量上传图片(循环上传,返回每个文件的视图对象列表)。 + */ + List uploadImages(MultipartFile[] files, String appType); + + /** + * 上传单个文件并返回前端兼容的结果 VO(含 fileid/name/fileurl/host/type/size)。 + */ + UpFileResultVo uploadToResult(MultipartFile file, String appType); +} diff --git a/src/main/java/com/yunhai/homework/service/IHomeworkService.java b/src/main/java/com/yunhai/homework/service/IHomeworkService.java new file mode 100644 index 0000000..ed50167 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IHomeworkService.java @@ -0,0 +1,142 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业业务接口。 + */ +public interface IHomeworkService { + + /** + * 发作业:以当前登录用户为发布人,状态置为「已发布」入库。 + * 若 request.timingSendTime 不为空且晚于当前时间,则作为定时发送, + * 未到点前用户端收件箱不可见(到点后由查询过滤自然可见)。 + * + * @return 新建作业 ID(用于后续回填附件 TopicID 等) + */ + Integer publish(HomeworkRequest request); + + /** + * 编辑作业:按 id 更新标题/内容/类型/部门/置顶,发布人与状态保持不变。 + */ + void edit(Integer id, HomeworkRequest request); + + /** + * 批量删除作业:删除主记录并级联清理阅读记录、接收人、附件关联。 + */ + void deleteBatch(List ids); + + /** + * 撤回作业:仅已发布且未撤回的作业可撤回,置 isPulled=1。 + */ + void pull(Integer id); + + /** + * 置顶/取消置顶。 + */ + void setTopmost(Integer id, Integer topmost); + + /** + * 转发作业:基于源作业创建新作业(已发布),parentId 关联源作业。 + */ + Integer forward(Integer sourceId, HomeworkRequest request); + + /** + * 用户端列表:仅展示已发布且已到点的作业,并标记当前用户是否已读,附未读总数。 + * 返回 HomeworkPageResult(扩展 unreadCount)。 + */ + HomeworkPageResult userList(HomeworkQuery query, Pageable pageable); + + /** + * 管理端列表:按条件查询全部作业(管理员查看全部)。 + */ + BasePageResult adminList(HomeworkQuery query, Pageable pageable); + + /** + * 作业详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。 + */ + HomeworkVo detail(Integer id); + + /** + * 编辑回显详情(不标记已读)。 + */ + HomeworkVo getDetail(Integer id); + + /** + * 标记已读:为当前用户写入(幂等)该作业的阅读记录。 + */ + void markRead(Integer id); + + /** + * 一键全部已读(批量化):当前用户所有未读的已发布作业批量插入阅读记录。 + * @return 已读条数 + */ + int initializeRed(); + + /** + * 追加接收人(事务化):并入接收人列表并重算 allowcount。 + */ + void receiveUsers(Integer homeworkId, String users); + + /** + * 上一篇/下一篇作业ID(SQL 相邻查询)。 + * @param serid 当前作业ID + * @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列) + * @return {prevId, nextId} + */ + Map getNextPrev(Integer serid, String type); + + /** + * 获取某作业的阅读明细列表。 + */ + List getReadDetail(Integer homeworkId); + + /** + * 阅读情况分组(已读/未读)。 + */ + List> getReadDetailGrouped(Integer serid); + + /** + * 催收未读:枚举未读接收人,后续接入消息推送。 + */ + void expedite(Integer serid); + + /** + * 作业统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。 + */ + HomeworkStatisticsVo statistics(HomeworkQuery query, LocalDateTime start, LocalDateTime end); + + /** + * 作业详情视图(含附件、接收人信息),供 Controller 返回。 + */ + HomeworkDetailVo getDetailMap(Integer serid); + + /** + * 已发列表附件批量查询(消除 N+1)。 + */ + Map> batchFileIds(List homeworkIds); + + /** + * 查看作业详情视图(含附件、接收人)。 + * type=1 时先标记已读再返回,type=0 时直接返回不标记。 + */ + HomeworkDetailVo detailMap(Integer serid, Integer type); + + /** + * 一键全部已读并返回结果(count = 已读条数)。 + */ + InitializeRedVo initializeRedResult(); + + /** + * 批量下载作业全部附件(打包 zip):查作业标题后调文件域打包下载。 + */ + void downloadAllFiles(Integer dataid, HttpServletResponse response); +} diff --git a/src/main/java/com/yunhai/homework/service/ISmsService.java b/src/main/java/com/yunhai/homework/service/ISmsService.java new file mode 100644 index 0000000..09b9b5f --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ISmsService.java @@ -0,0 +1,77 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置业务接口。 + * 对应前端 setSms 组接口:getmodule/getgroup/getschsms/getsch/setschsms/setschallsms/setschmodulesms。 + */ +public interface ISmsService { + + /** + * 获取短信模块列表。 + * C# 从 AuthConfig.AppKeyMap 读取。Java版从 sms_module 表读取。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getModuleList(); + + /** + * 获取学校父类组列表。 + * C# 从外部BLL获取学校组。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchParentGroup(); + + /** + * 分页获取学校短信开关列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param schName 学校名称(模糊搜索) + * @return Map: { list, total } + */ + Map getSchIsSmsList(Integer pageSize, Integer pageIndex, String schName); + + /** + * 获取学校列表。 + * C# 从外部BLL获取学校列表。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchList(); + + /** + * 设置单校单模块短信开关。 + * + * @param schSerID 学校ID + * @param moduleId 模块ID + * @param isSms 是否开通 + */ + void setSchSms(Integer schSerID, Integer moduleId, Boolean isSms); + + /** + * 设置某校全部模块短信开关。 + * + * @param schSerID 学校ID + * @param isSms 是否开通 + */ + void setSchSmsAll(Integer schSerID, Boolean isSms); + + /** + * 批量设置多校多模块短信开关。 + * + * @param schSerIDs 逗号分隔的学校ID + * @param modules 逗号分隔的模块ID + * @param isSms 是否开通 + */ + void setSchModuleSms(String schSerIDs, String modules, Boolean isSms); + + /** + * 从 JSON 字符串解析并设置单校单模块短信开关。 + * 解析 SchSerID/ModuID/IsSms 三个字段。 + */ + void setSchSmsFromJson(String itemsModuJson); +} diff --git a/src/main/java/com/yunhai/homework/service/ITemplateService.java b/src/main/java/com/yunhai/homework/service/ITemplateService.java new file mode 100644 index 0000000..5594401 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ITemplateService.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板业务接口。 + * 对应前端 Temp 组接口:GetTypelist/EditType/DelType/GetTemplatelist/EditTemplate/DelTemplate/GetTemplateFist。 + */ +public interface ITemplateService { + + /** + * 获取模板类型列表(首项固定为"全部")。 + * + * @return List<Map> 每项 {TypeID, TypeName} + */ + List> getTypelist(); + + /** + * 新增/编辑模板类型。 + * + * @param typeid 类型ID(0=新增) + * @param typeName 类型名称 + */ + void editType(Integer typeid, String typeName); + + /** + * 删除模板类型(校验是否有模板引用此类型)。 + * + * @param typeid 类型ID + */ + void delType(Integer typeid); + + /** + * 分页获取模板列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param typeid 类型ID(0=全部) + * @return Map: { list, total } + */ + Map getTemplatelist(Integer pageSize, Integer pageIndex, Integer typeid); + + /** + * 新增/编辑模板。 + * + * @param serID 模板ID(null=新增) + * @param title 标题 + * @param content 内容 + * @param typeId 类型ID + * @param cover 封面文件ID + */ + void editTemplate(Integer serID, String title, String content, Integer typeId, Integer cover); + + /** + * 删除模板。 + * + * @param serID 模板ID + */ + void delTemplate(Integer serID); + + /** + * 获取单个模板详情。 + * + * @param serID 模板ID + * @return Map 模板详情 + */ + Map getTemplateFist(Integer serID); +} diff --git a/src/main/java/com/yunhai/homework/service/IUserCenterService.java b/src/main/java/com/yunhai/homework/service/IUserCenterService.java new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IUserCenterService.java @@ -0,0 +1,51 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 用户中心门面服务(对齐 UserBase {@code UserCenterService} 模式)。 + * 封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。 + */ +public interface IUserCenterService { + + /** + * 获取当前用户学校组及用户(树形结构)。 + * 组装规则(从原 BaseApiController.getSchoolAll 下沉): + * + * userbase 读取当前学校整棵组树(getGroupById(schId, withSub=true)),剔除学生组(type=8)及其子组; + * bschclass 获取该校全部教师(/oapi/DbClass/GetSchteaList?Group=schId),按 GroupIds 挂到对应组下, + * 无有效组(如所属组全被剔除)的教师兜底挂学校根节点; + * 扁平节点按 pid 递归组装为树(空组剔除,同 C# FormatUserTree)。 + * + * + * @param schId 学校ID(用户当前角色所属学校) + * @return 用户树形结构(组节点 type=1 / 教师节点 type=2) + */ + List> getSchoolAllTree(Integer schId); + + /** + * 获取指定组内用户(教师 + 管理员)。 + * 对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。 + * + * @param gid 组ID + * @param role 角色(默认 2=教师;1=管理员等) + * @return 组内用户列表 + */ + List> getGroupUser(Integer gid, Integer role); + + /** + * 获取当前用户可访问的模块树(左侧菜单)。 + * 从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。 + * + * @param userId 用户ID + * @param schId 学校ID(可为 null) + * @return 模块树(顶层 List,含 children 嵌套) + */ + List> getUserModuleTree(Integer userId, Integer schId); +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/service/impl/ClassService.java b/src/main/java/com/yunhai/homework/service/impl/ClassService.java new file mode 100644 index 0000000..8be5e2a --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/impl/ClassService.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.service.impl; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.service.IClassService; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班级管理(bschclass)开放接口实现。 + * bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。 + * 推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List
+ * - 管理端(adminList):用 status / type / deptId / publisherId / 时间范围过滤; + * - 用户端(userList):额外用 readType 过滤未读/已读(0 全部 / 1 未读 / 2 已读)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class HomeworkQuery extends BasePageQuery { + + /** 状态过滤:null 不过滤;1 已发布 */ + private Integer status; + + /** 类型过滤:1 普通 / 2 重要 */ + private Integer type; + + /** 列表范围(管理端):"user" 只看自己发布的,null 看全部 */ + private String scope; + + /** 发布部门ID */ + private String deptId; + + /** 发布人ID(用户中心数字ID,管理端按发布人查) */ + private Integer publisherId; + + /** 开始时间(创建时间 >=,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate startTime; + + /** 结束时间(创建时间 <= 当天 23:59:59,前端传 yyyy-MM-dd 短日期) */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate endTime; + + /** 阅读状态过滤(仅用户端):0 全部 / 1 未读 / 2 已读 */ + private Integer readType = 0; + + /** 排序:1 时间倒序(默认)/ 2 时间正序 */ + private String orderByTime = "1"; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java new file mode 100644 index 0000000..2cb560f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkRequest.java @@ -0,0 +1,65 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import com.yunhai.homework.common.validation.ValidationGroups; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 作业发布/编辑入参。发布人由当前登录用户上下文填充,不在此入参中传递。 + */ +@Data +public class HomeworkRequest { + + /** 作业标题 */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业标题不得为空") + private String title; + + /** 作业内容 */ + private String content; + + /** 作业类型:1 普通 / 2 重要 */ + @NotNull(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "作业类型不得为空") + private Integer type = 1; + + /** 发布部门ID(原 DeptID,必填——对应原系统「发布部门不得为空」校验) */ + @NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class}, message = "发布部门不得为空") + private String deptId; + + /** 发布部门名称 */ + private String deptName; + + /** 是否置顶:0 否 / 1 是 */ + private Integer topmost = 0; + + /** 接收人列表(JSON 数组字符串,如 ["10012","10013"]——用户中心数字ID,或 "ALL" 表示全员) */ + private String receiveList; + + /** 附件文件ID列表(逗号分隔串,如 "1,2,3");旧名 chaoSend,已更名 fileIds(M6) */ + private String fileIds; + + /** 定时发送时间(为空表示立即发布) */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime timingSendTime; + + /** 短信发送标记:0 不发短信 / 1 发短信(旧名 topmost 实指发短信,已正名 smsFlag,M6) */ + private Integer smsFlag = 0; + + /** 转发源作业ID(非空时表示转发创建,对应旧 REST 壳 forward/{sourceId}) */ + private Integer sourceId; + + // ==================== 课堂作业 V1/V2 扩展(前端契约字段,兼容层同步写新列) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间 */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime deadline; + + /** 提交方式:text/image/attach/none 组合 */ + private java.util.List submitType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java new file mode 100644 index 0000000..c492f86 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkStatisticsVo.java @@ -0,0 +1,113 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 作业统计视图(对应原系统 GetStatisData / 按发布人统计)。 + */ +@Data +public class HomeworkStatisticsVo { + + /** 作业总数(全部已发布) */ + private long total; + + /** 已发布数 */ + private long publishedCount; + + /** 阅读记录总数(累计已读人次) */ + private long totalReadRecords; + + /** 当前用户未读数(已到点的已发布 - 当前用户已读) */ + private long unreadForCurrentUser; + + /** 按发布人统计 */ + private List byPublisher; + + /** 按月统计(已发布作业数) */ + private List byMonth; + + // ==================== 课程作业口径(前端 /stats 页消费) ==================== + + /** 我布置的作业数(当前登录人发布的全部作业,含草稿) */ + private long myAssignCount; + + /** 我的作业待批改总量(已交未批的当前版本行合计) */ + private long pendingGrade; + + /** 各作业平均提交率(%,仅统计需收集提交的作业;无样本时为 0) */ + private double avgSubmitRate; + + /** 按作业的提交与批改进度明细(草稿与仅查阅型不计) */ + private List byHomework = new ArrayList<>(); + + /** 已批阅提交的分数分布(四档) */ + private List scoreDist = new ArrayList<>(); + + /** 单作业进度条目(口径对齐前端 HomeworkProgress 契约) */ + @Data + public static class HomeworkStat { + private Integer id; + private String title; + private String subject; + /** 旧契约四态派生值:draft / collecting / closed / done */ + private String hwStatus; + private String hwStatusDesc; + /** 应交人数 */ + private long should; + /** 已读人数 */ + private long read; + /** 已交人数 */ + private long submitted; + /** 已批阅人数 */ + private long graded; + /** 待批改人数 */ + private long pendingGrade; + /** 未交人数 */ + private long unsubmitted; + /** 提交率(%) */ + private double submitRate; + /** 批阅率(%) */ + private double gradeRate; + } + + /** 分数分布档(key 供前端配色映射:excellent/good/pass/fail) */ + @Data + public static class ScoreBucket { + private String label; + private String key; + private long count; + + public ScoreBucket(String label, String key, long count) { + this.label = label; + this.key = key; + this.count = count; + } + } + + @Data + public static class PublisherStat { + private String publisherName; + private long count; + + public PublisherStat(String publisherName, long count) { + this.publisherName = publisherName; + this.count = count; + } + } + + @Data + public static class MonthStat { + private int year; + private int month; + private long count; + + public MonthStat(int year, int month, long count) { + this.year = year; + this.month = month; + this.count = count; + } + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java new file mode 100644 index 0000000..c906e2e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkUserListResult.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.util.List; + +/** + * 用户端作业列表结果:在分页数据之外额外携带「当前用户未读总数」, + * 供前端展示未读角标(对应原系统 OtherData.unReadCount)。 + */ +@Data +public class HomeworkUserListResult { + + private int total; + private List content; + private int pageIndex; + private int pageSize; + private int totalPages; + private boolean hasNext; + + /** 当前用户未读作业总数 */ + private long unreadCount; + + public HomeworkUserListResult(List content, int total, int pageIndex, int pageSize, + int totalPages, boolean hasNext, long unreadCount) { + this.content = content; + this.total = total; + this.pageIndex = pageIndex; + this.pageSize = pageSize; + this.totalPages = totalPages; + this.hasNext = hasNext; + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java new file mode 100644 index 0000000..333d49e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/HomeworkVo.java @@ -0,0 +1,92 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 作业视图对象(列表/详情返回)。 + */ +@Data +public class HomeworkVo { + + private Integer id; + private String title; + private String content; + private Integer type; + private String typeDesc; + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + private String publisherName; + private String deptId; + private String deptName; + private Integer status; + private String statusDesc; + private Integer topmost; + + /** 已读人数(基于阅读记录实时统计) */ + private Long readCount = 0L; + + /** 应读人数 */ + private Integer allowCount; + + /** 当前用户是否已读(用户端列表使用;管理端恒为 false 或不关注) */ + private boolean read = false; + + /** 是否撤回:0 否 / 1 是 */ + private Integer isPulled; + + /** 定时发送时间 */ + private LocalDateTime timingSendTime; + + /** 父作业ID(转发时关联原作业) */ + private Integer parentId; + + /** 短信发送标记 */ + private Integer smsFlag; + + /** 接收人列表(JSON) */ + private String receiveList; + + /** 附件ID串("1,2,3",列表页下载入口使用;对齐旧 .NET UpFile 字段) */ + private String UpFile; + + // ==================== 课堂作业 V1/V2 扩展(前端 types/homework.ts 契约) ==================== + + /** 学科(字典值) */ + private String subject; + + /** 截止时间(前端契约字段名 deadline) */ + private LocalDateTime deadline; + + /** 提交方式数组:text/image/attach/none */ + private java.util.List submitType; + + /** 作业流程状态——旧契约四态派生值(draft/collecting/closed/done),权威五态见 homework.hw_status */ + private String hwStatus; + + private String hwStatusDesc; + + /** + * 提交/批改进度(管理端列表页「提交进度 / 未读未交 / 批改」列使用)。 + * 草稿或仅查阅型为 null;列表场景由 HomeworkProgressCalculator 批量聚合回填。 + */ + private com.yunhai.homework.model.v2.ProgressVo progress; + + private String createBy; + private LocalDateTime createTime; + private String updateBy; + private LocalDateTime updateTime; + + /** + * 阅读明细视图(阅读列表查询用)。 + */ + @Data + public static class ReadDetail { + /** 读者ID(用户中心数字ID) */ + private Integer id; + private String name; + private boolean read; + private LocalDateTime readTime; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java new file mode 100644 index 0000000..15ece01 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/InitializeRedVo.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 一键已读结果视图对象(替代原 Map<String,Object> 返回)。 + */ +@Data +public class InitializeRedVo { + + @JsonProperty("count") + private Integer count; + + public InitializeRedVo() {} + + public InitializeRedVo(Integer count) { + this.count = count; + } +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java new file mode 100644 index 0000000..8127480 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileResultVo.java @@ -0,0 +1,30 @@ +package com.yunhai.homework.model.homework; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 文件上传结果视图对象(替代原 Map<String,Object> 返回)。 + * 使用 @JsonProperty 保持旧字段名兼容前端渲染。 + */ +@Data +public class UpFileResultVo { + + @JsonProperty("fileid") + private Integer fileid; + + @JsonProperty("name") + private String name; + + @JsonProperty("fileurl") + private String fileurl; + + @JsonProperty("host") + private String host; + + @JsonProperty("type") + private String type; + + @JsonProperty("size") + private Integer size; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java new file mode 100644 index 0000000..76dfa2f --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UpFileVo.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.model.homework; + +import lombok.Data; + +/** + * 附件视图对象(对应原 .NET FileObj)。 + */ +@Data +public class UpFileVo { + + /** 文件记录ID */ + private Integer id; + + /** 文件真实名称 */ + private String fileTrueName; + + /** 文件存储路径(相对路径) */ + private String fileName; + + /** 文件大小(字节) */ + private Integer fileSize; + + /** 文件后缀名 */ + private String fileType; + + /** 关联主题ID */ + private Integer topicId; + + /** 应用类型 */ + private String topicType; +} diff --git a/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java new file mode 100644 index 0000000..771eb3a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/homework/UserModuleVo.java @@ -0,0 +1,122 @@ +package com.yunhai.homework.model.homework; + +import java.util.ArrayList; +import java.util.List; + +/** + * 用户可访问模块(左菜单)视图对象。 + * 将基础平台返回的扁平 {@code List} 转换为前端 layout(参考 UserBase)期望的树结构: + * {@code modId}(路由高亮匹配)/{@code page}(跳转路径)/{@code children}(子菜单)。 + */ +public class UserModuleVo { + + /** 模块主键 id(基础平台 module id) */ + private Integer id; + + /** 菜单高亮匹配用:等价于后端模块 id */ + private Integer modId; + + /** 模块名称 */ + private String name; + + /** 父级模块 id(0 表示根节点) */ + private Integer parentId; + + /** 前端路由路径(由 BoaModule.path 映射而来) */ + private String page; + + /** 层级(0=根分组) */ + private Integer level; + + /** 菜单图标 */ + private String icon; + + /** 排序号 */ + private Integer orderNumber; + + /** 是否可用 */ + private Boolean usable; + + /** 子菜单 */ + private List children = new ArrayList<>(); + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Integer getModId() { + return modId; + } + + public void setModId(Integer modId) { + this.modId = modId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public String getPage() { + return page; + } + + public void setPage(String page) { + this.page = page; + } + + public Integer getLevel() { + return level; + } + + public void setLevel(Integer level) { + this.level = level; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public Integer getOrderNumber() { + return orderNumber; + } + + public void setOrderNumber(Integer orderNumber) { + this.orderNumber = orderNumber; + } + + public Boolean getUsable() { + return usable; + } + + public void setUsable(Boolean usable) { + this.usable = usable; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java new file mode 100644 index 0000000..bedf7b5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/AttachmentItem.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 附件条目(作业附件与提交附件共用)。fileUrl 必传;fileId 为 file/Upload 产物时回传。 + */ +@Data +public class AttachmentItem { + + /** up_file.id */ + private Integer fileId; + + /** 文件访问 URL */ + private String fileUrl; + + /** 文件类型:image / file */ + private String fileType; + + /** 文件名 */ + private String fileName; + + /** 排序号 */ + private Integer sort = 0; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassItem.java b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java new file mode 100644 index 0000000..459ea80 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassItem.java @@ -0,0 +1,18 @@ +package com.yunhai.homework.model.v2; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 班级条目(作业参与班级展示)。 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +public class ClassItem { + + private String classId; + + private String className; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java new file mode 100644 index 0000000..c0f400b --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ClassTreeNode.java @@ -0,0 +1,28 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 年级→班级树(发布作业选班 + 应交名单基数来源)。 + * 叶子节点 id = 班级 id(class_student 快照聚合)。 + */ +@Data +public class ClassTreeNode { + + /** 年级节点为 gradeName,班级叶子为 classId */ + private String id; + + /** 展示名 */ + private String name; + + /** 是否父节点(年级) */ + private Boolean isParent; + + /** 叶子节点:在班学生数 */ + private Integer studentCount; + + private List children = new ArrayList<>(); +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java new file mode 100644 index 0000000..f8c5dec --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ExtendRequest.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 延长截止请求(新截止时间必须晚于当前时间)。 + */ +@Data +public class ExtendRequest { + + @NotNull(message = "新截止时间不得为空") + private LocalDateTime deadlineAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java new file mode 100644 index 0000000..1ac46f8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkStatsVo.java @@ -0,0 +1,36 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 作业详情汇总统计卡片(PRD §18 页 4 stats 接口出参)。 + */ +@Data +public class HomeworkStatsVo { + + private Integer homeworkId; + + private String title; + + private String hwStatus; + + private String hwStatusDesc; + + /** 总进度 */ + private ProgressVo total; + + /** 分班进度 */ + private List byClass; + + @Data + public static class ByClass { + + private String classId; + + private String className; + + private ProgressVo progress; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java new file mode 100644 index 0000000..9464910 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Request.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 创建/编辑作业请求(PRD §18 页 1 表单)。 + * 创建一律落草稿(DRAFT_UNPUBLISHED),发布/定时走独立接口。 + */ +@Data +public class HomeworkV2Request { + + /** 作业标题(必填 ≤50 字) */ + @NotBlank(message = "作业标题不得为空") + @Size(max = 50, message = "作业标题不超过50字") + private String title; + + /** 学科(字典值) */ + private String subject; + + /** 作业说明(富文本) */ + private String description; + + /** 参与班级 id 列表 */ + private List classIds; + + /** 附件列表 */ + private List attachments; + + /** 截止时间(精确到分钟;空 = 仅查阅型) */ + private LocalDateTime deadlineAt; + + /** 提交方式:image / attach / text / none 的组合(PRD 图片/文件/两者) */ + private List submitType; + + /** 截止后是否允许逾期提交:0 否 / 1 是 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天提醒(0 关 / 1 开) */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时提醒(0 关 / 1 开) */ + private Integer remind1h; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java new file mode 100644 index 0000000..8bf1b02 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/HomeworkV2Vo.java @@ -0,0 +1,73 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 作业视图对象 V2(草稿箱/已发布列表/详情共用)。 + * 列表场景 attachments/classes 可按需填充;progress 仅非草稿且非仅查阅型作业返回。 + */ +@Data +public class HomeworkV2Vo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 作业说明(富文本;列表场景可置空省流) */ + private String description; + + /** 作业状态(PRD 五态) */ + private String hwStatus; + + private String hwStatusDesc; + + /** 定时发布时间(复用 timing_send_time) */ + private LocalDateTime scheduledAt; + + /** 发布时间 */ + private LocalDateTime publishedAt; + + /** 截止时间 */ + private LocalDateTime deadlineAt; + + /** 归档时间 */ + private LocalDateTime archivedAt; + + /** 提交方式数组 */ + private List submitType; + + /** 截止后允许逾期提交:0/1 */ + private Integer allowLate; + + /** 提醒设置:截止前 1 天 */ + private Integer remind1d; + + /** 提醒设置:截止前 1 小时 */ + private Integer remind1h; + + /** 参与班级 */ + private List classes; + + /** 附件列表(详情场景填充) */ + private List attachments; + + /** 发布人ID(用户中心数字ID) */ + private Integer publisherId; + + /** 发布人姓名 */ + private String publisherName; + + /** 创建/更新时间(审计) */ + private LocalDateTime createTime; + + private LocalDateTime updateTime; + + /** 汇总进度(草稿或仅查阅型为 null) */ + private ProgressVo progress; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java new file mode 100644 index 0000000..0303eb8 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationPageResult.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageResult; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知分页结果(含当前用户未读数)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationPageResult extends BasePageResult { + + /** 当前用户未读通知总数 */ + private long unreadCount; + + public NotificationPageResult(BasePageResult page, long unreadCount) { + super(page.getContent(), page.getTotal(), page.getPageIndex(), page.getPageSize(), + page.getTotalPages(), page.isHasNext()); + this.unreadCount = unreadCount; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java new file mode 100644 index 0000000..f8af4a7 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 通知列表查询。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationQuery extends BasePageQuery { + + /** 类型过滤(NotificationTypeEnum code) */ + private String type; + + /** 已读过滤:0 未读 / 1 已读(空 = 全部) */ + private Integer isRead; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java new file mode 100644 index 0000000..2a9b61a --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/NotificationVo.java @@ -0,0 +1,34 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知列表项。 + */ +@Data +public class NotificationVo { + + private Integer id; + + /** 通知类型 */ + private String type; + + private String typeDesc; + + private String title; + + private String content; + + private Integer homeworkId; + + private Integer relatedId; + + /** 是否已读:0/1 */ + private Integer isRead; + + private LocalDateTime readAt; + + private LocalDateTime createTime; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java new file mode 100644 index 0000000..201a460 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ProgressVo.java @@ -0,0 +1,44 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 提交/批阅/阅读进度(PRD §18 页 3 汇总口径)。 + * should(应交)为分母;打回未重交计入已交。 + */ +@Data +public class ProgressVo { + + /** 应交人数(参与班级在班学生去重) */ + private int should; + + /** 已读人数 */ + private int read; + + /** 已交人数(含逾期/打回/已批阅) */ + private int submitted; + + /** 已批阅人数 */ + private int graded; + + /** 待批阅人数(submitted - graded) */ + private int pendingGrade; + + /** 未交人数 */ + private int unsubmitted; + + /** 未读人数 */ + private int unread; + + /** 未读且未交人数(高亮口径:should - 已读∪已交) */ + private int unreadUndone; + + /** 阅读率(百分数,一位小数) */ + private double readRate; + + /** 提交率(百分数,一位小数) */ + private double submitRate; + + /** 批阅率(已批阅 / 已交,百分数一位小数;submitted=0 时为 0) */ + private double gradeRate; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java new file mode 100644 index 0000000..a97e0c1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/RemindRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.util.List; + +/** + * 教师手动提醒请求:type = NOT_READ / NOT_SUBMITTED,studentIds 为空时提醒全部未读/未交学生。 + */ +@Data +public class RemindRequest { + + /** 提醒类型:NOT_READ / NOT_SUBMITTED */ + @NotBlank(message = "提醒类型不得为空") + private String type; + + /** 定向学生ID(用户中心数字ID;空 = 全部未读/未交) */ + private List studentIds; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java new file mode 100644 index 0000000..45d3f3e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReturnRequest.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 打回请求(PRD 页 6)。comment 为订正原因,必填。 + */ +@Data +public class ReturnRequest { + + @NotBlank(message = "打回须填写订正原因") + private String comment; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java new file mode 100644 index 0000000..166b2e1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ReviewRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.util.List; + +/** + * 批阅保存请求(PRD 页 6)。score 与 comment 至少一项有值。 + */ +@Data +public class ReviewRequest { + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 圈划标注图片(P2,先收参) */ + private List markedImages; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java new file mode 100644 index 0000000..894b433 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/ScheduleRequest.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import java.time.LocalDateTime; + +/** + * 定时发布 / 取消定时请求。 + * cancel=true 时忽略 scheduledAt,作业回退草稿-未发布并清空定时时间。 + */ +@Data +public class ScheduleRequest { + + /** 定时发布时间(cancel=false 时必填且须晚于当前时间) */ + private LocalDateTime scheduledAt; + + /** 是否取消定时 */ + private Boolean cancel = false; + + @NotNull + public Boolean getCancel() { + return cancel; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java new file mode 100644 index 0000000..6372883 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkDetailVo.java @@ -0,0 +1,29 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; + +/** + * 学生端作业详情(PRD §18 学生页 2)。进入详情后由前端停留 ≥3 秒再调 read 上报。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentHomeworkDetailVo extends StudentHomeworkVo { + + /** 作业说明(富文本) */ + private String description; + + /** 作业附件 */ + private List attachments; + + /** 参与班级(学生所属班级视角) */ + private List classes; + + /** 我当前版本的提交附件(未提交为 null) */ + private List myAttachments; + + /** 我的文本内容(当前版本) */ + private String myText; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java new file mode 100644 index 0000000..9d30037 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentHomeworkVo.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学生端作业列表项(PRD §18 学生页 1)。 + */ +@Data +public class StudentHomeworkVo { + + private Integer id; + + private String title; + + /** 学科 */ + private String subject; + + /** 任课教师(发布人姓名) */ + private String teacherName; + + /** 截止时间(前端做倒计时) */ + private LocalDateTime deadlineAt; + + /** 提交方式 */ + private java.util.List submitType; + + /** 作业状态 */ + private String hwStatus; + + private String hwStatusDesc; + + /** 截止后是否允许逾期补交:0 否 / 1 是(学生端据此决定是否渲染「补交」入口) */ + private Integer allowLate; + + /** 我的提交概要(未提交为 null) */ + private MySubmission my; + + @Data + public static class MySubmission { + + /** 当前版本提交行ID */ + private Integer submissionId; + + /** 提交状态(PRD 五态) */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 版本号 */ + private Integer version; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分(已批阅才有) */ + private java.math.BigDecimal score; + + /** 评语 */ + private String comment; + } +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java new file mode 100644 index 0000000..49e3144 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixQuery.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学生矩阵查询(PRD §18 页 5)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class StudentMatrixQuery extends BasePageQuery { + + /** 班级筛选(空 = 全部参与班级) */ + private String classId; + + /** 提交状态筛选:NOT_SUBMITTED/SUBMITTED/LATE_SUBMITTED/RETURNED/REVIEWED */ + private String submitStatus; + + /** 批阅状态筛选:PENDING/REVIEWED/RETURNED */ + private String reviewStatus; + + /** 只看待批阅(reviewStatus=PENDING 且已提交)快捷过滤 */ + private Boolean pendingGrade; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java new file mode 100644 index 0000000..6257285 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentMatrixVo.java @@ -0,0 +1,57 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学生矩阵行(PRD §18 页 5)。未交学生也生成占位行(submissionId=0)。 + */ +@Data +public class StudentMatrixVo { + + /** 当前版本提交行ID(未交占位为 0) */ + private Integer submissionId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + /** 姓名 */ + private String studentName; + + private String classId; + + private String className; + + /** 阅读状态:0 未读 / 1 已读 */ + private Integer isRead; + + /** 首次阅读时间 */ + private LocalDateTime firstReadAt; + + /** 最近阅读时间 */ + private LocalDateTime lastReadAt; + + /** 提交状态(PRD 五态;未交为 NOT_SUBMITTED) */ + private String submitStatus; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期:0/1 */ + private Integer isLate; + + /** 批阅状态 */ + private String reviewStatus; + + /** 评分 */ + private BigDecimal score; + + /** 最新提交附件 URL 列表(缩略图) */ + private List thumbnails; + + /** 提交内容类型摘要:image=拍照/图片、attach=上传附件、text=文字作答(未交为空列表) */ + private List contentTypes; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java new file mode 100644 index 0000000..f4d9cd1 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentResultVo.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.util.List; + +/** + * 学生查看批阅结果(PRD §18 学生页 2 result 接口)。 + */ +@Data +public class StudentResultVo { + + private Integer homeworkId; + + private String homeworkTitle; + + /** 当前版本批阅结果(未批阅为 null) */ + private SubmissionVersionVo latest; + + /** 全部版本(含各版评语,新→旧) */ + private List versions; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java new file mode 100644 index 0000000..1fa7545 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/StudentSubmitRequest.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.Size; +import java.util.List; + +/** + * 学生提交作业请求(PRD §18 学生页 3)。附件只接受 file/Upload 产物(fileId 或平台 URL),拒绝 dataURL。 + */ +@Data +public class StudentSubmitRequest { + + /** 文本内容(submitType 含 text 时有效) */ + @Size(max = 20000, message = "文本内容过长") + private String text; + + /** 附件列表(图片/文件) */ + private List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java new file mode 100644 index 0000000..6c576d9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubjectDto.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 学科 DTO(布置过的学科 / 我的任教学科共用返回结构)。 + */ +@Data +public class SubjectDto { + + /** 学科编码(homework.subject 字典值 / bschclass SubID) */ + private String subId; + + /** 学科名称(本校学科字典解析;字典缺失时回退为编码) */ + private String subName; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java new file mode 100644 index 0000000..cac00dc --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionActionRequest.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 撤回重交动作(PUT /student/homeworks/{id}/submissions/{submissionId})。 + * withdraw = 撤回(截止前,产生 NOT_SUBMITTED 新版本行);resubmit = 打回后重交(走提交语义)。 + */ +@Data +public class SubmissionActionRequest { + + @NotBlank(message = "action 不得为空") + private String action; + + /** resubmit 时的提交内容(等同 StudentSubmitRequest) */ + private String text; + + /** resubmit 时的附件 */ + private java.util.List attachments; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java new file mode 100644 index 0000000..bff2972 --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/SubmissionVersionVo.java @@ -0,0 +1,63 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 提交版本条目(历史版本/批阅弹窗/学生结果页共用)。 + */ +@Data +public class SubmissionVersionVo { + + private Integer submissionId; + + private Integer homeworkId; + + /** 学生ID(用户中心数字ID) */ + private Integer studentId; + + private String studentName; + + /** 版本号 */ + private Integer version; + + /** 是否当前版本:0/1 */ + private Integer isLatest; + + /** 提交状态 */ + private String submitStatus; + + private String submitStatusDesc; + + /** 提交时间 */ + private LocalDateTime submittedAt; + + /** 是否逾期 */ + private Integer isLate; + + /** 文本内容 */ + private String text; + + /** 附件 */ + private List attachments; + + /** 评分 */ + private BigDecimal score; + + /** 评语 */ + private String comment; + + /** 批阅状态 */ + private String reviewStatus; + + private String reviewStatusDesc; + + /** 批阅人(展示姓名快照,缺失时回退ID) */ + private String reviewedBy; + + /** 批阅时间 */ + private LocalDateTime reviewedAt; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java new file mode 100644 index 0000000..6d6991e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeacherHomeworkQuery.java @@ -0,0 +1,19 @@ +package com.yunhai.homework.model.v2; + +import com.yunhai.homework.model.BasePageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教师端作业列表查询(草稿箱/已发布列表共用)。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TeacherHomeworkQuery extends BasePageQuery { + + /** 学科筛选 */ + private String subject; + + /** 作业状态筛选(已发布列表:PUBLISHED/CLOSED/ARCHIVED) */ + private String hwStatus; +} diff --git a/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java new file mode 100644 index 0000000..d9fd64e --- /dev/null +++ b/src/main/java/com/yunhai/homework/model/v2/TeachingClassVo.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.model.v2; + +import lombok.Data; + +/** + * 任教班级 VO(按学科筛选任教班级返回结构)。 + */ +@Data +public class TeachingClassVo { + + /** 班级业务ID(bschclass sch_class.class_id) */ + private String classId; + + /** 班级名称(学校班级列表映射;缺失时回退为班级ID) */ + private String className; +} diff --git a/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java new file mode 100644 index 0000000..0581e29 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ClassStudentRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ClassStudent; +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; + +/** + * 班级学生名册仓储。 + */ +public interface ClassStudentRepository extends JpaRepository { + + /** 按学生ID(用户中心数字ID)反查学生身份(学生端接口入口) */ + List findByStudentIdAndStatus(Integer studentId, Integer status); + + /** 班级在班学生 */ + List findByClassIdAndStatus(String classId, Integer status); + + /** 指定班级集合的全部在班学生(应交名单) */ + List findByClassIdInAndStatus(List classIds, Integer status); + + /** 指定学校的全部名册(同步任务全量刷新用) */ + List findBySchoolId(Integer schoolId); + + /** 年级→班级树:按学校去重班级(class_student 快照聚合),列序:classId, className, gradeName */ + @Query("select distinct c.classId, c.className, c.gradeName from ClassStudent c " + + "where c.schoolId = :schoolId and c.status = 1") + List findDistinctClasses(@Param("schoolId") Integer schoolId); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java new file mode 100644 index 0000000..4dc9ee4 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupRepository.java @@ -0,0 +1,27 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroup; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ContactsGroupRepository extends JpaRepository { + Page findAllByOrderByCreateTimeDesc(Pageable pageable); + + /** + * 按创建人ID(用户中心数字ID)查询联系组列表。 + */ + List findByCreatorId(Integer creatorId); + + /** + * 按创建人ID查询联系组列表(分页)。 + */ + Page findByCreatorId(Integer creatorId, Pageable pageable); + + /** + * 查询全部联系组(按创建时间降序)。 + */ + List findAllByOrderByCreateTimeDesc(); +} diff --git a/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java new file mode 100644 index 0000000..df5ab28 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/ContactsGroupUserRepository.java @@ -0,0 +1,42 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.ContactsGroupUser; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ContactsGroupUserRepository extends JpaRepository { + Page findByGroupId(Integer groupId, Pageable pageable); + List findByGroupId(Integer groupId); + + /** + * 按组ID批量删除成员(JPQL bulk delete,SQL 立即执行)。 + * 注意:不能用 derived delete——其 DELETE 延迟到 flush 才执行, + * 同事务内先 insert 会触发 uk_group_user 唯一约束冲突。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId = :groupId") + void deleteByGroupIdBulk(@Param("groupId") Integer groupId); + + /** + * 按多个组ID批量删除成员。 + */ + @Modifying(clearAutomatically = true) + @Query("delete from ContactsGroupUser g where g.groupId in :groupIds") + void deleteByGroupIdInBulk(@Param("groupIds") List groupIds); + + /** + * 按多个组ID查询所有成员(用于统计每组人数、组树构建)。 + */ + List findByGroupIdIn(List groupIds); + + /** + * 按组ID和用户ID查询(判断是否已存在)。 + */ + boolean existsByGroupIdAndUserId(Integer groupId, Integer userId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java new file mode 100644 index 0000000..1e6af55 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkAttachmentRepository.java @@ -0,0 +1,16 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * 作业附件仓储。 + */ +public interface HomeworkAttachmentRepository extends JpaRepository { + + List findByHomeworkIdOrderBySortAscIdAsc(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java new file mode 100644 index 0000000..8f651f5 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkClassRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkClass; +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; + +/** + * 作业-班级关联仓储。 + */ +public interface HomeworkClassRepository extends JpaRepository { + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 指定班级的全部作业ID(学生端列表倒查用) */ + @Query("select c.homeworkId from HomeworkClass c where c.classId in :classIds") + List findHomeworkIdsByClassIds(@Param("classIds") List classIds); + + /** 批量查 N 个作业的班级关联(列表展示避免 N+1) */ + @Query("select c from HomeworkClass c where c.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java new file mode 100644 index 0000000..6c16a81 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRecordRepository.java @@ -0,0 +1,23 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReadRecord; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 学生阅读记录仓储。 + */ +public interface HomeworkReadRecordRepository extends JpaRepository { + + Optional findByHomeworkIdAndStudentId(Integer homeworkId, Integer studentId); + + List findByHomeworkId(Integer homeworkId); + + /** 批量查 N 个作业的已读记录(列表统计避免 N+1) */ + List findByHomeworkIdIn(Collection homeworkIds); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java new file mode 100644 index 0000000..20dc83c --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReadRepository.java @@ -0,0 +1,52 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkRead; +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.Optional; + +/** + * 作业阅读记录仓储(懒标记模型)。 + */ +public interface HomeworkReadRepository extends JpaRepository { + + /** + * 某用户是否已读某作业。 + */ + boolean existsByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 某作业的总已读人数。 + */ + long countByHomeworkId(Integer homeworkId); + + /** + * 删除某作业的全部阅读记录(作业删除时级联清理)。 + */ + void deleteByHomeworkId(Integer homeworkId); + + /** + * 某用户已读的作业总数(用于「我的未读」= 已发布总数 - 已读总数)。 + */ + long countByReaderId(Integer readerId); + + /** + * 查询某用户已读的作业ID列表(用于列表页批量判定未读)。 + */ + @Query("select r.homeworkId from HomeworkRead r where r.readerId = :readerId") + List findHomeworkIdsByReaderId(@Param("readerId") Integer readerId); + + /** + * 查询某用户对某作业的阅读记录(标记已读时幂等判断)。 + */ + Optional findByHomeworkIdAndReaderId(Integer homeworkId, Integer readerId); + + /** + * 查询某作业的全部阅读记录(按阅读时间倒序),用于阅读明细列表。 + */ + @Query("select r from HomeworkRead r where r.homeworkId = :homeworkId order by r.readTime desc") + List findByHomeworkIdOrderByReadTimeDesc(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java new file mode 100644 index 0000000..402d455 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkReceiverRepository.java @@ -0,0 +1,31 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkReceiver; +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; + +/** + * 作业接收人关联表仓储。 + */ +public interface HomeworkReceiverRepository extends JpaRepository { + + /** 按作业ID查询全部接收人 */ + List findByHomeworkId(Integer homeworkId); + + /** 按作业ID删除全部接收人 */ + void deleteByHomeworkId(Integer homeworkId); + + /** 按作业ID和接收类型查询接收人 */ + List findByHomeworkIdAndReceiveType(Integer homeworkId, Integer receiveType); + + /** + * 批量查 N 个作业的接收人,用于列表展示(避免 N+1)。 + * @param homeworkIds 作业ID列表 + * @return 接收人关联记录列表 + */ + @Query("select r from HomeworkReceiver r where r.homeworkId in :homeworkIds") + List findByHomeworkIds(@Param("homeworkIds") List homeworkIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java new file mode 100644 index 0000000..bf862b0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkRepository.java @@ -0,0 +1,139 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Homework; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; + +/** + * 课程作业仓储。 + */ +public interface HomeworkRepository extends JpaRepository, + JpaSpecificationExecutor { + + /** + * 作业流程状态条件更新(CAS):仅当当前状态为 from 时更新为 to。 + * 状态机单点收敛用,天然幂等、并发安全(定时任务与接口共用)。 + * clearAutomatically:批量更新绕过一级缓存,必须清空以避免后续 findById 读到旧实体 + * 再被 save() 写回旧状态(同事务内必现,集成测试发现)。 + * + * @return 影响行数(0 = 状态已变化,调用方应放弃本次迁移) + */ + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query("update Homework h set h.hwStatus = :to where h.id = :id and h.hwStatus = :from") + int casHwStatus(@Param("id") Integer id, + @Param("from") String from, + @Param("to") String to); + + /** + * 定时发布任务扫描:定时待发布且已到点。 + */ + List findByHwStatusAndTimingSendTimeLessThanEqual(String hwStatus, LocalDateTime now); + + /** + * 到点关闭任务扫描:进行中且已过截止时间。 + */ + List findByHwStatusAndDeadlineAtBefore(String hwStatus, LocalDateTime now); + + /** + * 截止提醒任务扫描:进行中且截止时间落在指定窗口内(remind_1d/remind_1h 开关在服务侧逐单过滤)。 + */ + List findByHwStatusAndDeadlineAtBetween(String hwStatus, LocalDateTime start, LocalDateTime end); + + /** + * 按作业流程状态查询(兼容层/统计用)。 + */ + List findByHwStatus(String hwStatus); + + /** + * 当前登录人发布的全部作业(课程作业口径统计:我布置的作业 / 提交与批改进度明细)。 + */ + List findByPublisherId(Integer publisherId); + + /** + * 当前登录人布置过(已发布及之后状态)作业的学科去重清单(学科筛选用,字典序;草稿不计)。 + */ + @Query("select distinct h.subject from Homework h " + + "where h.publisherId = :publisherId and h.subject is not null and h.subject <> '' " + + "and h.hwStatus in :statuses order by h.subject") + List findAssignedSubjects(@Param("publisherId") Integer publisherId, + @Param("statuses") Collection statuses); + + /** + * 按状态计数(用于统计已发布总数)。 + */ + long countByStatus(Integer status); + + /** + * 已发布且已到点(定时发送未到点不计入)的作业数,用于用户端未读数统计。 + */ + @Query("select count(n) from Homework n where n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDue(@Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定状态、指定时间范围内的作业创建时间(用于按月统计,避免加载大字段)。 + */ + @Query("select n.createTime from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定状态、指定时间范围内的发布人姓名(用于按发布人统计)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.status = :status and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesByStatusAndRange(@Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 按学校和状态计数(多租户隔离统计)。 + */ + long countBySchIdAndStatus(Integer schId, Integer status); + + /** + * 按学校计数(多租户隔离统计:全部状态)。 + */ + long countBySchId(Integer schId); + + /** + * 已发布且已到点(定时发送未到点不计入)且属于指定学校的作业数(用户端未读数统计,多租户隔离)。 + */ + @Query("select count(n) from Homework n where n.schId = :schId and n.status = :status " + + "and (n.timingSendTime is null or n.timingSendTime <= :now)") + long countPublishedDueBySchId(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("now") LocalDateTime now); + + /** + * 查询指定学校、指定状态、指定时间范围内的作业创建时间(按月统计,多租户隔离)。 + */ + @Query("select n.createTime from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findCreateTimesBySchIdStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); + + /** + * 查询指定学校、指定状态、指定时间范围内的发布人姓名(按发布人统计,多租户隔离)。 + */ + @Query("select n.publisherName from Homework n " + + "where n.schId = :schId and n.status = :status " + + "and n.createTime >= :start and n.createTime <= :end") + List findPublisherNamesBySchStatusAndRange(@Param("schId") Integer schId, + @Param("status") Integer status, + @Param("start") LocalDateTime start, + @Param("end") LocalDateTime end); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java new file mode 100644 index 0000000..547c5ea --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkSubmissionRepository.java @@ -0,0 +1,48 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkSubmission; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * 作业提交仓储(多版本)。统计一律基于 is_latest=1 的当前版本行。 + */ +public interface HomeworkSubmissionRepository extends JpaRepository { + + /** 学生在指定作业的当前版本行 */ + Optional findByHomeworkIdAndStudentIdAndIsLatest(Integer homeworkId, + Integer studentId, + Integer isLatest); + + /** 学生在指定作业的全部版本(新→旧) */ + List findByHomeworkIdAndStudentIdOrderByVersionDesc(Integer homeworkId, Integer studentId); + + /** 指定作业的当前版本行(统计/矩阵用) */ + List findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest); + + /** 批量查 N 个作业的当前版本行(列表统计避免 N+1) */ + List findByHomeworkIdInAndIsLatest(Collection homeworkIds, Integer isLatest); + + /** 学生矩阵分页 */ + Page findByHomeworkIdAndIsLatest(Integer homeworkId, Integer isLatest, Pageable pageable); + + /** 学生当前版本最大版本号 */ + @Query("select max(s.version) from HomeworkSubmission s " + + "where s.homeworkId = :homeworkId and s.studentId = :studentId") + Integer findMaxVersion(@Param("homeworkId") Integer homeworkId, @Param("studentId") Integer studentId); + + void deleteByHomeworkId(Integer homeworkId); + + /** 附件等子表清理后按作业删除历史版本行 */ + @Modifying + @Query("delete from HomeworkSubmission s where s.homeworkId = :homeworkId") + int deleteAllByHomeworkId(@Param("homeworkId") Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java new file mode 100644 index 0000000..ecc75cb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/HomeworkTemplateRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.HomeworkTemplate; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface HomeworkTemplateRepository extends JpaRepository { + Page findByTypeId(Integer typeId, Pageable pageable); + + /** + * 查询全部模板(按引用次数降序),分页。 + */ + Page findAllByOrderByRefCountDesc(Pageable pageable); + + /** + * 按类型ID查询全部模板(不分页,用于检查类型是否被引用)。 + */ + long countByTypeId(Integer typeId); +} diff --git a/src/main/java/com/yunhai/homework/repository/NotificationRepository.java b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java new file mode 100644 index 0000000..1066c10 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/NotificationRepository.java @@ -0,0 +1,49 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.Notification; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +/** + * 站内通知仓储。 + */ +public interface NotificationRepository extends JpaRepository { + + Page findByReceiverId(Integer receiverId, Pageable pageable); + + Page findByReceiverIdAndType(Integer receiverId, String type, Pageable pageable); + + Page findByReceiverIdAndIsRead(Integer receiverId, Integer isRead, Pageable pageable); + + Page findByReceiverIdAndTypeAndIsRead(Integer receiverId, String type, + Integer isRead, Pageable pageable); + + long countByReceiverIdAndIsRead(Integer receiverId, Integer isRead); + + Optional findByIdAndReceiverId(Integer id, Integer receiverId); + + /** 截止提醒去重:同作业同类型同档位已发过即跳过(related_id 存档位:1=1天档 2=1小时档) */ + boolean existsByHomeworkIdAndTypeAndRelatedId(Integer homeworkId, String type, Integer relatedId); + + /** 截止提醒去重(不分档位) */ + boolean existsByHomeworkIdAndType(Integer homeworkId, String type); + + /** 全部已读(clearAutomatically 防同事务内缓存脏读) */ + @Modifying(clearAutomatically = true) + @Query("update Notification n set n.isRead = 1, n.readAt = :now " + + "where n.receiverId = :receiverId and n.isRead = 0") + int markAllRead(@Param("receiverId") Integer receiverId, @Param("now") java.time.LocalDateTime now); + + /** 按作业删除通知(作业删除级联) */ + void deleteByHomeworkId(Integer homeworkId); + + /** 指定作业的全部通知(清理/统计用) */ + List findByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java new file mode 100644 index 0000000..8e60f47 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SchClassTeachRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SchClassTeach; +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; + +/** + * 班级任教关系快照仓储。 + */ +public interface SchClassTeachRepository extends JpaRepository { + + /** 指定学校指定教师的全部有效任教记录 */ + List findBySchoolIdAndTeaIdAndStatus(Integer schoolId, Integer teaId, Integer status); + + /** 指定学校指定教师去重学科编码(字典序由调用方保证) */ + @Query("select distinct t.subId from SchClassTeach t " + + "where t.schoolId = :schoolId and t.teaId = :teaId and t.status = 1") + List findDistinctSubIds(@Param("schoolId") Integer schoolId, @Param("teaId") Integer teaId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java new file mode 100644 index 0000000..18595bb --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsModuleRepository.java @@ -0,0 +1,13 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsModule; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsModuleRepository extends JpaRepository { + List findAllByOrderByIdAsc(); + + /** 按模块标识查询(如 module_code="homework")。 */ + SmsModule findByModuleCode(String moduleCode); +} diff --git a/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java new file mode 100644 index 0000000..0d277a0 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SmsSchoolConfigRepository.java @@ -0,0 +1,25 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SmsSchoolConfig; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface SmsSchoolConfigRepository extends JpaRepository { + Page findBySchoolNameContaining(String schoolName, Pageable pageable); + Page findAll(Pageable pageable); + List findBySchoolId(Integer schoolId); + List findBySchoolIdInAndModuleIdIn(List schoolIds, List moduleIds); + + /** + * 按学校ID和模块ID查询单条配置(判断是否已存在)。 + */ + SmsSchoolConfig findBySchoolIdAndModuleId(Integer schoolId, Integer moduleId); + + /** + * 按学校ID查询全部模块配置(用于"设置某校全部模块")。 + */ + List findBySchoolIdIn(List schoolIds); +} diff --git a/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java new file mode 100644 index 0000000..8848b14 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SubmissionAttachmentRepository.java @@ -0,0 +1,22 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SubmissionAttachment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Collection; +import java.util.List; + +/** + * 提交附件仓储。 + */ +public interface SubmissionAttachmentRepository extends JpaRepository { + + List findBySubmissionIdOrderBySortAscIdAsc(Integer submissionId); + + /** 批量查 N 个提交行的附件(矩阵/版本列表避免 N+1) */ + List findBySubmissionIdIn(Collection submissionIds); + + List findByHomeworkId(Integer homeworkId); + + void deleteByHomeworkId(Integer homeworkId); +} diff --git a/src/main/java/com/yunhai/homework/repository/SysDictRepository.java b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java new file mode 100644 index 0000000..c083840 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/SysDictRepository.java @@ -0,0 +1,15 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.SysDict; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface SysDictRepository extends JpaRepository { + List findByDictTypeOrderBySortOrderAsc(String dictType); + List findByDictTypeAndEnableFlagOrderBySortOrderAsc(String dictType, Integer enableFlag); + + /** 按类型+key 取单条字典(发文部门名称解析用) */ + Optional findByDictTypeAndDictKey(String dictType, String dictKey); +} diff --git a/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java new file mode 100644 index 0000000..a3a0f12 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/TemplateTypeRepository.java @@ -0,0 +1,20 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.TemplateType; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface TemplateTypeRepository extends JpaRepository { + List findAllByOrderBySortOrderAsc(); + + /** + * 按类型名称查询(重名校验)。 + */ + boolean existsByTypeName(String typeName); + + /** + * 按类型名称查询(重名校验,排除自身)。 + */ + boolean existsByTypeNameAndIdNot(String typeName, Integer id); +} diff --git a/src/main/java/com/yunhai/homework/repository/UpFileRepository.java b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java new file mode 100644 index 0000000..421f142 --- /dev/null +++ b/src/main/java/com/yunhai/homework/repository/UpFileRepository.java @@ -0,0 +1,26 @@ +package com.yunhai.homework.repository; + +import com.yunhai.homework.entity.UpFile; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +/** + * 附件文件仓储。 + */ +public interface UpFileRepository extends JpaRepository { + + /** + * 按主题类型和主题ID查询附件列表。 + */ + List findByTopicTypeAndTopicId(String topicType, Integer topicId); + + /** + * 按主题类型和多个主题ID批量查询附件(消除 N+1)。 + */ + List findByTopicTypeAndTopicIdIn(String topicType, List topicIds); + + /** + * 按主题类型和主题ID删除附件(作业删除时级联清理)。 + */ + void deleteByTopicTypeAndTopicId(String topicType, Integer topicId); +} diff --git a/src/main/java/com/yunhai/homework/service/IClassService.java b/src/main/java/com/yunhai/homework/service/IClassService.java new file mode 100644 index 0000000..2346cd9 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IClassService.java @@ -0,0 +1,116 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; + +import java.util.List; + +/** + * 班级管理(bschclass)开放接口客户端门面。 + * 基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。 + * 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。 + */ +public interface IClassService { + + /** + * 获取指定学校全部教师(含所属组 GroupIds)。 + * 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}} + * + * @param schId 学校ID(即用户当前身份的 schId) + */ + List getSchTeaList(int schId); + + /** + * 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。 + * 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}} + */ + SchInfoDto getSchFirst(int schId); + + /** + * 学校学段(学制)列表:小学部 / 初中部…。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=} + */ + List getSchLenList(String schSerId); + + /** + * 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=} + */ + List getSchGradeList(String schSerId); + + /** + * 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。 + * 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=} + */ + List getSchClassList(String schSerId); + + /** + * 按校全部学生(圈人:全校范围展开)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=} + */ + List getSchStuList(String schSerId); + + /** + * 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=} + */ + List getGradeStuList(String schSerId, Integer gradeId); + + /** + * 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=} + */ + List getSubStuList(String schSerId, Integer schSystemId); + + /** + * 按班级批量查学生(圈人:班级范围一次多班)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3} + */ + List getClaArrStuList(List classIds); + + /** + * 按学生ID批量查(圈人:指定学员范围)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3} + */ + List getStuIdsList(List stuIds); + + /** + * 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。 + * 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。 + * 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=} + */ + List getTeaTree(Integer schId, boolean toUser); + + List getStuTree(Integer schId,boolean toUser); + + /** + * 学校学科列表(含学科编码、名称、排序号)。 + * 对应:GET {@code {bschclass}/oapi/SubInfo/GetSchSublist?SchID=} + */ + List getSchSubList(String schSerId); + + /** + * 指定教师的全部任教(校验教师归属该校;SchID 过滤学校、UserTeaSerID 过滤教师)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=&UserTeaSerID=} + */ + List getSchTeachList(String schSerId, Integer teaId); + + /** + * 指定班级的全部任教(班级维度:该班所有教师与学科)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchClaTeachlist?ClassID=} + */ + List getSchClaTeachList(Integer classId); + + /** + * 学校全部任教(全校所有教师的班级任教关系)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=} + */ + List getSchAllTeachList(String schSerId); +} diff --git a/src/main/java/com/yunhai/homework/service/IContactsService.java b/src/main/java/com/yunhai/homework/service/IContactsService.java new file mode 100644 index 0000000..0e1fdce --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IContactsService.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 联系组业务接口。 + * 对应前端 Contacts 组接口:AddGroup/GetGroup/EditGroup/DelGroup/SetContactsTea/GetContactsTea/GetGroupTree/GetDeptRoleGroupTree/Import/Export。 + */ +public interface IContactsService { + + /** + * 添加联系组。 + * + * @param name 组名 + * @param type 类型(1 自定义组, 2 明信片) + */ + void addGroup(String name, Integer type); + + /** + * 获取联系组列表(含每组人数,分页)。 + * + * @param page 页码(从1起) + * @param count 每页条数 + * @param type 类型(1 自定义组, 2 明信片) + * @return Map: { list, total } + */ + Map getGroup(Integer page, Integer count, Integer type); + + /** + * 编辑联系组。 + * + * @param id 组ID + * @param name 组名 + * @param type 类型 + */ + void editGroup(Integer id, String name, Integer type); + + /** + * 删除联系组(支持批量,同时删除组内成员)。 + * + * @param ids 逗号分隔的组ID + */ + void delGroup(String ids); + + /** + * 设置联系组教师(全量替换:先删后增)。 + * + * @param gid 组ID + * @param ids 逗号分隔的用户ID + */ + void setContactsTea(Integer gid, String ids); + + /** + * 获取联系组教师列表(分页)。 + * + * @param gid 组ID + * @param page 页码 + * @param count 每页条数 + * @return Map: { list, total } + */ + Map getContactsTea(String gid, Integer page, Integer count); + + /** + * 获取联系组树(组节点+用户节点,递归构建)。 + * + * @param type 类型(1 自定义组, 2 明信片) + * @return 树形结构 List<Map> + */ + List> getGroupTree(Integer type); + + /** + * 获取部门角色组树。 + * C# 从外部API获取部门树+用户角色映射。Java版预留。 + * + * @return 树形结构 List<Map> + */ + List> getDeptRoleGroupTree(); +} diff --git a/src/main/java/com/yunhai/homework/service/IDictService.java b/src/main/java/com/yunhai/homework/service/IDictService.java new file mode 100644 index 0000000..31ef515 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IDictService.java @@ -0,0 +1,75 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 系统字典业务接口。 + * 对应前端 Base 组接口:GetSysDic/GetDictMangeList/AddDeptDic/UpdateDeptDic/DelDept/OrdbyDicIds/GetDeptSync/GetSchModuleIsSms。 + */ +public interface IDictService { + + /** + * 获取系统字典(下拉框用)。 + * C# GetSysDic: Type=1 部门类别, 其余类型略。 + * + * @param type 字典类型编号(1=部门类别) + * @return List<Map> 每项 {k, v} + */ + List> getSysDic(Integer type); + + /** + * 获取系统字典管理列表(含 SerID/CodeID/Name/Des/Enable)。 + * + * @param type 字典类型编号(0=部门类别) + * @return List<Map> + */ + List> getDictMangeList(String type); + + /** + * 添加发文单位。 + * + * @param name 单位名称 + */ + void addDeptDic(String name); + + /** + * 更新发文单位。 + * + * @param serID 记录ID + * @param name 名称 + * @param enable 启用状态 + */ + void updateDeptDic(Integer serID, String name, Integer enable); + + /** + * 删除发文单位(校验是否被作业引用)。 + * + * @param serID 记录ID + */ + void delDept(Integer serID); + + /** + * 发文单位排序。 + * + * @param ids 逗号分隔的 SerID 列表(按前端顺序) + */ + void ordbyDicIds(String ids); + + /** + * 本校是否开通短信模块(前端 dict/Sync,用于"本校是否开通短信"判断)。 + * + * @param schId 当前学校ID(取当前登录用户角色所属学校) + * @return 开通返回 true,未开通/未配置返回 false + */ + Boolean getSchModuleIsSms(Integer schId); + + /** + * 按发文部门字典 key(CodeID)解析部门名称;未命中返回 null。 + * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 + * + * @param deptId 作业 deptId(存的是 sys_dict.dict_key) + * @return 部门名称;无匹配时返回 null + */ + String resolveDeptName(String deptId); +} diff --git a/src/main/java/com/yunhai/homework/service/IFileService.java b/src/main/java/com/yunhai/homework/service/IFileService.java new file mode 100644 index 0000000..1b01bbf --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IFileService.java @@ -0,0 +1,94 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.UpFile; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.model.homework.UpFileResultVo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件上传/下载/删除业务接口。 + */ +public interface IFileService { + /** + * 上传单个文件。 + * + * @param file 上传的文件 + * @param appType 应用类型(如 "homework") + * @param topicId 关联主题ID(可为空,作业保存后再绑定) + * @return 文件视图对象 + */ + UpFileVo upload(MultipartFile file, String appType, Integer topicId); + + /** + * 将已上传但未绑定主题的附件绑定到指定作业。 + * + * @param fileIds 文件记录ID列表 + * @param topicId 作业ID + */ + void bindToTopic(List fileIds, Integer topicId); + + /** + * 查询某主题下的附件列表。 + * + * @param appType 应用类型 + * @param topicId 主题ID + * @return 附件列表 + */ + List listByTopic(String appType, Integer topicId); + + /** + * 下载文件。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void download(Integer fileId, HttpServletResponse response); + + /** + * 在线预览文件(Content-Disposition: inline,按扩展名输出真实 Content-Type, + * 供前端 open-file-viewer 取 Blob / video 播放)。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void preview(Integer fileId, HttpServletResponse response); + + /** + * 删除文件(同时删除物理文件和数据库记录)。 + * + * @param fileId 文件记录ID + */ + void delete(Integer fileId); + + /** + * 打包下载多个附件为 ZIP。 + * + * @param files 附件记录列表(调用方按 fileIds 查好) + * @param zipName 压缩包显示名(自动追加 .zip) + * @param response HTTP 响应 + */ + void downloadZip(List files, String zipName, HttpServletResponse response); + + /** + * 按主题类型和主题ID打包下载附件为 ZIP(Controller 便捷方法,内部自行查附件列表)。 + * + * @param topicType 主题类型(如 "homework") + * @param topicId 主题ID + * @param zipName 压缩包显示名 + * @param response HTTP 响应 + */ + void downloadZipByTopic(String topicType, Integer topicId, String zipName, HttpServletResponse response); + + /** + * 批量上传图片(循环上传,返回每个文件的视图对象列表)。 + */ + List uploadImages(MultipartFile[] files, String appType); + + /** + * 上传单个文件并返回前端兼容的结果 VO(含 fileid/name/fileurl/host/type/size)。 + */ + UpFileResultVo uploadToResult(MultipartFile file, String appType); +} diff --git a/src/main/java/com/yunhai/homework/service/IHomeworkService.java b/src/main/java/com/yunhai/homework/service/IHomeworkService.java new file mode 100644 index 0000000..ed50167 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IHomeworkService.java @@ -0,0 +1,142 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业业务接口。 + */ +public interface IHomeworkService { + + /** + * 发作业:以当前登录用户为发布人,状态置为「已发布」入库。 + * 若 request.timingSendTime 不为空且晚于当前时间,则作为定时发送, + * 未到点前用户端收件箱不可见(到点后由查询过滤自然可见)。 + * + * @return 新建作业 ID(用于后续回填附件 TopicID 等) + */ + Integer publish(HomeworkRequest request); + + /** + * 编辑作业:按 id 更新标题/内容/类型/部门/置顶,发布人与状态保持不变。 + */ + void edit(Integer id, HomeworkRequest request); + + /** + * 批量删除作业:删除主记录并级联清理阅读记录、接收人、附件关联。 + */ + void deleteBatch(List ids); + + /** + * 撤回作业:仅已发布且未撤回的作业可撤回,置 isPulled=1。 + */ + void pull(Integer id); + + /** + * 置顶/取消置顶。 + */ + void setTopmost(Integer id, Integer topmost); + + /** + * 转发作业:基于源作业创建新作业(已发布),parentId 关联源作业。 + */ + Integer forward(Integer sourceId, HomeworkRequest request); + + /** + * 用户端列表:仅展示已发布且已到点的作业,并标记当前用户是否已读,附未读总数。 + * 返回 HomeworkPageResult(扩展 unreadCount)。 + */ + HomeworkPageResult userList(HomeworkQuery query, Pageable pageable); + + /** + * 管理端列表:按条件查询全部作业(管理员查看全部)。 + */ + BasePageResult adminList(HomeworkQuery query, Pageable pageable); + + /** + * 作业详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。 + */ + HomeworkVo detail(Integer id); + + /** + * 编辑回显详情(不标记已读)。 + */ + HomeworkVo getDetail(Integer id); + + /** + * 标记已读:为当前用户写入(幂等)该作业的阅读记录。 + */ + void markRead(Integer id); + + /** + * 一键全部已读(批量化):当前用户所有未读的已发布作业批量插入阅读记录。 + * @return 已读条数 + */ + int initializeRed(); + + /** + * 追加接收人(事务化):并入接收人列表并重算 allowcount。 + */ + void receiveUsers(Integer homeworkId, String users); + + /** + * 上一篇/下一篇作业ID(SQL 相邻查询)。 + * @param serid 当前作业ID + * @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列) + * @return {prevId, nextId} + */ + Map getNextPrev(Integer serid, String type); + + /** + * 获取某作业的阅读明细列表。 + */ + List getReadDetail(Integer homeworkId); + + /** + * 阅读情况分组(已读/未读)。 + */ + List> getReadDetailGrouped(Integer serid); + + /** + * 催收未读:枚举未读接收人,后续接入消息推送。 + */ + void expedite(Integer serid); + + /** + * 作业统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。 + */ + HomeworkStatisticsVo statistics(HomeworkQuery query, LocalDateTime start, LocalDateTime end); + + /** + * 作业详情视图(含附件、接收人信息),供 Controller 返回。 + */ + HomeworkDetailVo getDetailMap(Integer serid); + + /** + * 已发列表附件批量查询(消除 N+1)。 + */ + Map> batchFileIds(List homeworkIds); + + /** + * 查看作业详情视图(含附件、接收人)。 + * type=1 时先标记已读再返回,type=0 时直接返回不标记。 + */ + HomeworkDetailVo detailMap(Integer serid, Integer type); + + /** + * 一键全部已读并返回结果(count = 已读条数)。 + */ + InitializeRedVo initializeRedResult(); + + /** + * 批量下载作业全部附件(打包 zip):查作业标题后调文件域打包下载。 + */ + void downloadAllFiles(Integer dataid, HttpServletResponse response); +} diff --git a/src/main/java/com/yunhai/homework/service/ISmsService.java b/src/main/java/com/yunhai/homework/service/ISmsService.java new file mode 100644 index 0000000..09b9b5f --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ISmsService.java @@ -0,0 +1,77 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置业务接口。 + * 对应前端 setSms 组接口:getmodule/getgroup/getschsms/getsch/setschsms/setschallsms/setschmodulesms。 + */ +public interface ISmsService { + + /** + * 获取短信模块列表。 + * C# 从 AuthConfig.AppKeyMap 读取。Java版从 sms_module 表读取。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getModuleList(); + + /** + * 获取学校父类组列表。 + * C# 从外部BLL获取学校组。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchParentGroup(); + + /** + * 分页获取学校短信开关列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param schName 学校名称(模糊搜索) + * @return Map: { list, total } + */ + Map getSchIsSmsList(Integer pageSize, Integer pageIndex, String schName); + + /** + * 获取学校列表。 + * C# 从外部BLL获取学校列表。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchList(); + + /** + * 设置单校单模块短信开关。 + * + * @param schSerID 学校ID + * @param moduleId 模块ID + * @param isSms 是否开通 + */ + void setSchSms(Integer schSerID, Integer moduleId, Boolean isSms); + + /** + * 设置某校全部模块短信开关。 + * + * @param schSerID 学校ID + * @param isSms 是否开通 + */ + void setSchSmsAll(Integer schSerID, Boolean isSms); + + /** + * 批量设置多校多模块短信开关。 + * + * @param schSerIDs 逗号分隔的学校ID + * @param modules 逗号分隔的模块ID + * @param isSms 是否开通 + */ + void setSchModuleSms(String schSerIDs, String modules, Boolean isSms); + + /** + * 从 JSON 字符串解析并设置单校单模块短信开关。 + * 解析 SchSerID/ModuID/IsSms 三个字段。 + */ + void setSchSmsFromJson(String itemsModuJson); +} diff --git a/src/main/java/com/yunhai/homework/service/ITemplateService.java b/src/main/java/com/yunhai/homework/service/ITemplateService.java new file mode 100644 index 0000000..5594401 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ITemplateService.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板业务接口。 + * 对应前端 Temp 组接口:GetTypelist/EditType/DelType/GetTemplatelist/EditTemplate/DelTemplate/GetTemplateFist。 + */ +public interface ITemplateService { + + /** + * 获取模板类型列表(首项固定为"全部")。 + * + * @return List<Map> 每项 {TypeID, TypeName} + */ + List> getTypelist(); + + /** + * 新增/编辑模板类型。 + * + * @param typeid 类型ID(0=新增) + * @param typeName 类型名称 + */ + void editType(Integer typeid, String typeName); + + /** + * 删除模板类型(校验是否有模板引用此类型)。 + * + * @param typeid 类型ID + */ + void delType(Integer typeid); + + /** + * 分页获取模板列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param typeid 类型ID(0=全部) + * @return Map: { list, total } + */ + Map getTemplatelist(Integer pageSize, Integer pageIndex, Integer typeid); + + /** + * 新增/编辑模板。 + * + * @param serID 模板ID(null=新增) + * @param title 标题 + * @param content 内容 + * @param typeId 类型ID + * @param cover 封面文件ID + */ + void editTemplate(Integer serID, String title, String content, Integer typeId, Integer cover); + + /** + * 删除模板。 + * + * @param serID 模板ID + */ + void delTemplate(Integer serID); + + /** + * 获取单个模板详情。 + * + * @param serID 模板ID + * @return Map 模板详情 + */ + Map getTemplateFist(Integer serID); +} diff --git a/src/main/java/com/yunhai/homework/service/IUserCenterService.java b/src/main/java/com/yunhai/homework/service/IUserCenterService.java new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IUserCenterService.java @@ -0,0 +1,51 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 用户中心门面服务(对齐 UserBase {@code UserCenterService} 模式)。 + * 封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。 + */ +public interface IUserCenterService { + + /** + * 获取当前用户学校组及用户(树形结构)。 + * 组装规则(从原 BaseApiController.getSchoolAll 下沉): + * + * userbase 读取当前学校整棵组树(getGroupById(schId, withSub=true)),剔除学生组(type=8)及其子组; + * bschclass 获取该校全部教师(/oapi/DbClass/GetSchteaList?Group=schId),按 GroupIds 挂到对应组下, + * 无有效组(如所属组全被剔除)的教师兜底挂学校根节点; + * 扁平节点按 pid 递归组装为树(空组剔除,同 C# FormatUserTree)。 + * + * + * @param schId 学校ID(用户当前角色所属学校) + * @return 用户树形结构(组节点 type=1 / 教师节点 type=2) + */ + List> getSchoolAllTree(Integer schId); + + /** + * 获取指定组内用户(教师 + 管理员)。 + * 对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。 + * + * @param gid 组ID + * @param role 角色(默认 2=教师;1=管理员等) + * @return 组内用户列表 + */ + List> getGroupUser(Integer gid, Integer role); + + /** + * 获取当前用户可访问的模块树(左侧菜单)。 + * 从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。 + * + * @param userId 用户ID + * @param schId 学校ID(可为 null) + * @return 模块树(顶层 List,含 children 嵌套) + */ + List> getUserModuleTree(Integer userId, Integer schId); +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/service/impl/ClassService.java b/src/main/java/com/yunhai/homework/service/impl/ClassService.java new file mode 100644 index 0000000..8be5e2a --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/impl/ClassService.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.service.impl; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.service.IClassService; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班级管理(bschclass)开放接口实现。 + * bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。 + * 推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List
将基础平台返回的扁平 {@code List} 转换为前端 layout(参考 UserBase)期望的树结构: + * {@code modId}(路由高亮匹配)/{@code page}(跳转路径)/{@code children}(子菜单)。
基址取配置 {@code yh.open.bschclass}(/oapi/** 无需应用签名,独立 RestTemplate 直连)。 + * 推送范围树、圈人、身份快照统一经本门面取数,不散落 RestTemplate 调用(改造方案 2.0)。 + */ +public interface IClassService { + + /** + * 获取指定学校全部教师(含所属组 GroupIds)。 + * 对应 bschclass 开放接口:GET {@code {bschclass}/oapi/DbClass/GetSchteaList?Group={schId}} + * + * @param schId 学校ID(即用户当前身份的 schId) + */ + List getSchTeaList(int schId); + + /** + * 用户中心学校ID → bschclass 学校信息(SchSerId / 学校名)。 + * 对应:GET {@code {bschclass}/oapi/SchInfo/GetUserSchFirst?UserSchID={schId}} + */ + SchInfoDto getSchFirst(int schId); + + /** + * 学校学段(学制)列表:小学部 / 初中部…。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenlist?SchSerID=} + */ + List getSchLenList(String schSerId); + + /** + * 学校年级列表(Id=gradeId、Name=gradeNickname、OwnerId=学段id)。 + * 对应:GET {@code {bschclass}/oapi/Schoolen/GetSchLenLevellist?SchSerID=} + */ + List getSchGradeList(String schSerId); + + /** + * 学校全部在读班级(ClassID/ClassName/Levell=gradeId),ScopeTree 一次取数客户端组装。 + * 对应:GET {@code {bschclass}/oapi/VSchClass/GetVClassList?SchSerID=} + */ + List getSchClassList(String schSerId); + + /** + * 按校全部学生(圈人:全校范围展开)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSchStulist?schserid=} + */ + List getSchStuList(String schSerId); + + /** + * 按年级直查学生(圈人:年级范围免逐班聚合;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetGradeStulist?SchSerID=&GradeId=} + */ + List getGradeStuList(String schSerId, Integer gradeId); + + /** + * 按学段直查学生(圈人:学部范围一键展开;bschclass 侧补缺接口)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetSubStulist?SchSerID=&SchSystemId=} + */ + List getSubStuList(String schSerId, Integer schSystemId); + + /** + * 按班级批量查学生(圈人:班级范围一次多班)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetClaArrStulist?ClassIDs=1,2,3} + */ + List getClaArrStuList(List classIds); + + /** + * 按学生ID批量查(圈人:指定学员范围)。 + * 对应:GET {@code {bschclass}/oapi/OAStu/GetStuIdslist?stuids=1,2,3} + */ + List getStuIdsList(List stuIds); + + /** + * 学校学生树:学校→学段→年级→班级→学生(单元素列表,根为学校节点)。 + * 节点契约:组节点 type=1、isParent=true、children 非 null;学生叶子 type=2、isParent=false、children=null。 + * 对应:GET {@code {bschclass}/oapi/SchGroup/GetStuTree?SchSerID=} + */ + List getTeaTree(Integer schId, boolean toUser); + + List getStuTree(Integer schId,boolean toUser); + + /** + * 学校学科列表(含学科编码、名称、排序号)。 + * 对应:GET {@code {bschclass}/oapi/SubInfo/GetSchSublist?SchID=} + */ + List getSchSubList(String schSerId); + + /** + * 指定教师的全部任教(校验教师归属该校;SchID 过滤学校、UserTeaSerID 过滤教师)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=&UserTeaSerID=} + */ + List getSchTeachList(String schSerId, Integer teaId); + + /** + * 指定班级的全部任教(班级维度:该班所有教师与学科)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchClaTeachlist?ClassID=} + */ + List getSchClaTeachList(Integer classId); + + /** + * 学校全部任教(全校所有教师的班级任教关系)。 + * 对应:GET {@code {bschclass}/oapi/TeachInfo/GetSchTeachlist?SchID=} + */ + List getSchAllTeachList(String schSerId); +} diff --git a/src/main/java/com/yunhai/homework/service/IContactsService.java b/src/main/java/com/yunhai/homework/service/IContactsService.java new file mode 100644 index 0000000..0e1fdce --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IContactsService.java @@ -0,0 +1,79 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 联系组业务接口。 + * 对应前端 Contacts 组接口:AddGroup/GetGroup/EditGroup/DelGroup/SetContactsTea/GetContactsTea/GetGroupTree/GetDeptRoleGroupTree/Import/Export。 + */ +public interface IContactsService { + + /** + * 添加联系组。 + * + * @param name 组名 + * @param type 类型(1 自定义组, 2 明信片) + */ + void addGroup(String name, Integer type); + + /** + * 获取联系组列表(含每组人数,分页)。 + * + * @param page 页码(从1起) + * @param count 每页条数 + * @param type 类型(1 自定义组, 2 明信片) + * @return Map: { list, total } + */ + Map getGroup(Integer page, Integer count, Integer type); + + /** + * 编辑联系组。 + * + * @param id 组ID + * @param name 组名 + * @param type 类型 + */ + void editGroup(Integer id, String name, Integer type); + + /** + * 删除联系组(支持批量,同时删除组内成员)。 + * + * @param ids 逗号分隔的组ID + */ + void delGroup(String ids); + + /** + * 设置联系组教师(全量替换:先删后增)。 + * + * @param gid 组ID + * @param ids 逗号分隔的用户ID + */ + void setContactsTea(Integer gid, String ids); + + /** + * 获取联系组教师列表(分页)。 + * + * @param gid 组ID + * @param page 页码 + * @param count 每页条数 + * @return Map: { list, total } + */ + Map getContactsTea(String gid, Integer page, Integer count); + + /** + * 获取联系组树(组节点+用户节点,递归构建)。 + * + * @param type 类型(1 自定义组, 2 明信片) + * @return 树形结构 List<Map> + */ + List> getGroupTree(Integer type); + + /** + * 获取部门角色组树。 + * C# 从外部API获取部门树+用户角色映射。Java版预留。 + * + * @return 树形结构 List<Map> + */ + List> getDeptRoleGroupTree(); +} diff --git a/src/main/java/com/yunhai/homework/service/IDictService.java b/src/main/java/com/yunhai/homework/service/IDictService.java new file mode 100644 index 0000000..31ef515 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IDictService.java @@ -0,0 +1,75 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 系统字典业务接口。 + * 对应前端 Base 组接口:GetSysDic/GetDictMangeList/AddDeptDic/UpdateDeptDic/DelDept/OrdbyDicIds/GetDeptSync/GetSchModuleIsSms。 + */ +public interface IDictService { + + /** + * 获取系统字典(下拉框用)。 + * C# GetSysDic: Type=1 部门类别, 其余类型略。 + * + * @param type 字典类型编号(1=部门类别) + * @return List<Map> 每项 {k, v} + */ + List> getSysDic(Integer type); + + /** + * 获取系统字典管理列表(含 SerID/CodeID/Name/Des/Enable)。 + * + * @param type 字典类型编号(0=部门类别) + * @return List<Map> + */ + List> getDictMangeList(String type); + + /** + * 添加发文单位。 + * + * @param name 单位名称 + */ + void addDeptDic(String name); + + /** + * 更新发文单位。 + * + * @param serID 记录ID + * @param name 名称 + * @param enable 启用状态 + */ + void updateDeptDic(Integer serID, String name, Integer enable); + + /** + * 删除发文单位(校验是否被作业引用)。 + * + * @param serID 记录ID + */ + void delDept(Integer serID); + + /** + * 发文单位排序。 + * + * @param ids 逗号分隔的 SerID 列表(按前端顺序) + */ + void ordbyDicIds(String ids); + + /** + * 本校是否开通短信模块(前端 dict/Sync,用于"本校是否开通短信"判断)。 + * + * @param schId 当前学校ID(取当前登录用户角色所属学校) + * @return 开通返回 true,未开通/未配置返回 false + */ + Boolean getSchModuleIsSms(Integer schId); + + /** + * 按发文部门字典 key(CodeID)解析部门名称;未命中返回 null。 + * 用于作业列表/详情展示 DeptName(对应 C# GetDeptName)。 + * + * @param deptId 作业 deptId(存的是 sys_dict.dict_key) + * @return 部门名称;无匹配时返回 null + */ + String resolveDeptName(String deptId); +} diff --git a/src/main/java/com/yunhai/homework/service/IFileService.java b/src/main/java/com/yunhai/homework/service/IFileService.java new file mode 100644 index 0000000..1b01bbf --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IFileService.java @@ -0,0 +1,94 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.UpFile; +import com.yunhai.homework.model.homework.UpFileVo; +import com.yunhai.homework.model.homework.UpFileResultVo; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 文件上传/下载/删除业务接口。 + */ +public interface IFileService { + /** + * 上传单个文件。 + * + * @param file 上传的文件 + * @param appType 应用类型(如 "homework") + * @param topicId 关联主题ID(可为空,作业保存后再绑定) + * @return 文件视图对象 + */ + UpFileVo upload(MultipartFile file, String appType, Integer topicId); + + /** + * 将已上传但未绑定主题的附件绑定到指定作业。 + * + * @param fileIds 文件记录ID列表 + * @param topicId 作业ID + */ + void bindToTopic(List fileIds, Integer topicId); + + /** + * 查询某主题下的附件列表。 + * + * @param appType 应用类型 + * @param topicId 主题ID + * @return 附件列表 + */ + List listByTopic(String appType, Integer topicId); + + /** + * 下载文件。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void download(Integer fileId, HttpServletResponse response); + + /** + * 在线预览文件(Content-Disposition: inline,按扩展名输出真实 Content-Type, + * 供前端 open-file-viewer 取 Blob / video 播放)。 + * + * @param fileId 文件记录ID + * @param response HTTP 响应 + */ + void preview(Integer fileId, HttpServletResponse response); + + /** + * 删除文件(同时删除物理文件和数据库记录)。 + * + * @param fileId 文件记录ID + */ + void delete(Integer fileId); + + /** + * 打包下载多个附件为 ZIP。 + * + * @param files 附件记录列表(调用方按 fileIds 查好) + * @param zipName 压缩包显示名(自动追加 .zip) + * @param response HTTP 响应 + */ + void downloadZip(List files, String zipName, HttpServletResponse response); + + /** + * 按主题类型和主题ID打包下载附件为 ZIP(Controller 便捷方法,内部自行查附件列表)。 + * + * @param topicType 主题类型(如 "homework") + * @param topicId 主题ID + * @param zipName 压缩包显示名 + * @param response HTTP 响应 + */ + void downloadZipByTopic(String topicType, Integer topicId, String zipName, HttpServletResponse response); + + /** + * 批量上传图片(循环上传,返回每个文件的视图对象列表)。 + */ + List uploadImages(MultipartFile[] files, String appType); + + /** + * 上传单个文件并返回前端兼容的结果 VO(含 fileid/name/fileurl/host/type/size)。 + */ + UpFileResultVo uploadToResult(MultipartFile file, String appType); +} diff --git a/src/main/java/com/yunhai/homework/service/IHomeworkService.java b/src/main/java/com/yunhai/homework/service/IHomeworkService.java new file mode 100644 index 0000000..ed50167 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IHomeworkService.java @@ -0,0 +1,142 @@ +package com.yunhai.homework.service; + +import com.yunhai.homework.entity.Homework; +import com.yunhai.homework.model.BasePageResult; +import com.yunhai.homework.model.homework.*; +import org.springframework.data.domain.Pageable; + +import javax.servlet.http.HttpServletResponse; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 课程作业业务接口。 + */ +public interface IHomeworkService { + + /** + * 发作业:以当前登录用户为发布人,状态置为「已发布」入库。 + * 若 request.timingSendTime 不为空且晚于当前时间,则作为定时发送, + * 未到点前用户端收件箱不可见(到点后由查询过滤自然可见)。 + * + * @return 新建作业 ID(用于后续回填附件 TopicID 等) + */ + Integer publish(HomeworkRequest request); + + /** + * 编辑作业:按 id 更新标题/内容/类型/部门/置顶,发布人与状态保持不变。 + */ + void edit(Integer id, HomeworkRequest request); + + /** + * 批量删除作业:删除主记录并级联清理阅读记录、接收人、附件关联。 + */ + void deleteBatch(List ids); + + /** + * 撤回作业:仅已发布且未撤回的作业可撤回,置 isPulled=1。 + */ + void pull(Integer id); + + /** + * 置顶/取消置顶。 + */ + void setTopmost(Integer id, Integer topmost); + + /** + * 转发作业:基于源作业创建新作业(已发布),parentId 关联源作业。 + */ + Integer forward(Integer sourceId, HomeworkRequest request); + + /** + * 用户端列表:仅展示已发布且已到点的作业,并标记当前用户是否已读,附未读总数。 + * 返回 HomeworkPageResult(扩展 unreadCount)。 + */ + HomeworkPageResult userList(HomeworkQuery query, Pageable pageable); + + /** + * 管理端列表:按条件查询全部作业(管理员查看全部)。 + */ + BasePageResult adminList(HomeworkQuery query, Pageable pageable); + + /** + * 作业详情:返回视图对象,并顺带为当前用户标记已读(打开即视为已读)。 + */ + HomeworkVo detail(Integer id); + + /** + * 编辑回显详情(不标记已读)。 + */ + HomeworkVo getDetail(Integer id); + + /** + * 标记已读:为当前用户写入(幂等)该作业的阅读记录。 + */ + void markRead(Integer id); + + /** + * 一键全部已读(批量化):当前用户所有未读的已发布作业批量插入阅读记录。 + * @return 已读条数 + */ + int initializeRed(); + + /** + * 追加接收人(事务化):并入接收人列表并重算 allowcount。 + */ + void receiveUsers(Integer homeworkId, String users); + + /** + * 上一篇/下一篇作业ID(SQL 相邻查询)。 + * @param serid 当前作业ID + * @param type 0 已发(当前用户发布的序列)/ 1 已收(阅读记录序列) + * @return {prevId, nextId} + */ + Map getNextPrev(Integer serid, String type); + + /** + * 获取某作业的阅读明细列表。 + */ + List getReadDetail(Integer homeworkId); + + /** + * 阅读情况分组(已读/未读)。 + */ + List> getReadDetailGrouped(Integer serid); + + /** + * 催收未读:枚举未读接收人,后续接入消息推送。 + */ + void expedite(Integer serid); + + /** + * 作业统计:总数/已发布、累计阅读人次、当前用户未读、按发布人、按月。 + */ + HomeworkStatisticsVo statistics(HomeworkQuery query, LocalDateTime start, LocalDateTime end); + + /** + * 作业详情视图(含附件、接收人信息),供 Controller 返回。 + */ + HomeworkDetailVo getDetailMap(Integer serid); + + /** + * 已发列表附件批量查询(消除 N+1)。 + */ + Map> batchFileIds(List homeworkIds); + + /** + * 查看作业详情视图(含附件、接收人)。 + * type=1 时先标记已读再返回,type=0 时直接返回不标记。 + */ + HomeworkDetailVo detailMap(Integer serid, Integer type); + + /** + * 一键全部已读并返回结果(count = 已读条数)。 + */ + InitializeRedVo initializeRedResult(); + + /** + * 批量下载作业全部附件(打包 zip):查作业标题后调文件域打包下载。 + */ + void downloadAllFiles(Integer dataid, HttpServletResponse response); +} diff --git a/src/main/java/com/yunhai/homework/service/ISmsService.java b/src/main/java/com/yunhai/homework/service/ISmsService.java new file mode 100644 index 0000000..09b9b5f --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ISmsService.java @@ -0,0 +1,77 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 短信设置业务接口。 + * 对应前端 setSms 组接口:getmodule/getgroup/getschsms/getsch/setschsms/setschallsms/setschmodulesms。 + */ +public interface ISmsService { + + /** + * 获取短信模块列表。 + * C# 从 AuthConfig.AppKeyMap 读取。Java版从 sms_module 表读取。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getModuleList(); + + /** + * 获取学校父类组列表。 + * C# 从外部BLL获取学校组。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchParentGroup(); + + /** + * 分页获取学校短信开关列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param schName 学校名称(模糊搜索) + * @return Map: { list, total } + */ + Map getSchIsSmsList(Integer pageSize, Integer pageIndex, String schName); + + /** + * 获取学校列表。 + * C# 从外部BLL获取学校列表。Java版预留。 + * + * @return List<Map> 每项 {ID, Name} + */ + List> getSchList(); + + /** + * 设置单校单模块短信开关。 + * + * @param schSerID 学校ID + * @param moduleId 模块ID + * @param isSms 是否开通 + */ + void setSchSms(Integer schSerID, Integer moduleId, Boolean isSms); + + /** + * 设置某校全部模块短信开关。 + * + * @param schSerID 学校ID + * @param isSms 是否开通 + */ + void setSchSmsAll(Integer schSerID, Boolean isSms); + + /** + * 批量设置多校多模块短信开关。 + * + * @param schSerIDs 逗号分隔的学校ID + * @param modules 逗号分隔的模块ID + * @param isSms 是否开通 + */ + void setSchModuleSms(String schSerIDs, String modules, Boolean isSms); + + /** + * 从 JSON 字符串解析并设置单校单模块短信开关。 + * 解析 SchSerID/ModuID/IsSms 三个字段。 + */ + void setSchSmsFromJson(String itemsModuJson); +} diff --git a/src/main/java/com/yunhai/homework/service/ITemplateService.java b/src/main/java/com/yunhai/homework/service/ITemplateService.java new file mode 100644 index 0000000..5594401 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/ITemplateService.java @@ -0,0 +1,69 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 作业模板业务接口。 + * 对应前端 Temp 组接口:GetTypelist/EditType/DelType/GetTemplatelist/EditTemplate/DelTemplate/GetTemplateFist。 + */ +public interface ITemplateService { + + /** + * 获取模板类型列表(首项固定为"全部")。 + * + * @return List<Map> 每项 {TypeID, TypeName} + */ + List> getTypelist(); + + /** + * 新增/编辑模板类型。 + * + * @param typeid 类型ID(0=新增) + * @param typeName 类型名称 + */ + void editType(Integer typeid, String typeName); + + /** + * 删除模板类型(校验是否有模板引用此类型)。 + * + * @param typeid 类型ID + */ + void delType(Integer typeid); + + /** + * 分页获取模板列表。 + * + * @param pageSize 每页条数 + * @param pageIndex 页码 + * @param typeid 类型ID(0=全部) + * @return Map: { list, total } + */ + Map getTemplatelist(Integer pageSize, Integer pageIndex, Integer typeid); + + /** + * 新增/编辑模板。 + * + * @param serID 模板ID(null=新增) + * @param title 标题 + * @param content 内容 + * @param typeId 类型ID + * @param cover 封面文件ID + */ + void editTemplate(Integer serID, String title, String content, Integer typeId, Integer cover); + + /** + * 删除模板。 + * + * @param serID 模板ID + */ + void delTemplate(Integer serID); + + /** + * 获取单个模板详情。 + * + * @param serID 模板ID + * @return Map 模板详情 + */ + Map getTemplateFist(Integer serID); +} diff --git a/src/main/java/com/yunhai/homework/service/IUserCenterService.java b/src/main/java/com/yunhai/homework/service/IUserCenterService.java new file mode 100644 index 0000000..8698338 --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/IUserCenterService.java @@ -0,0 +1,51 @@ +package com.yunhai.homework.service; + +import java.util.List; +import java.util.Map; + +/** + * 用户中心门面服务(对齐 UserBase {@code UserCenterService} 模式)。 + * 封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。 + */ +public interface IUserCenterService { + + /** + * 获取当前用户学校组及用户(树形结构)。 + * 组装规则(从原 BaseApiController.getSchoolAll 下沉): + * + * userbase 读取当前学校整棵组树(getGroupById(schId, withSub=true)),剔除学生组(type=8)及其子组; + * bschclass 获取该校全部教师(/oapi/DbClass/GetSchteaList?Group=schId),按 GroupIds 挂到对应组下, + * 无有效组(如所属组全被剔除)的教师兜底挂学校根节点; + * 扁平节点按 pid 递归组装为树(空组剔除,同 C# FormatUserTree)。 + * + * + * @param schId 学校ID(用户当前角色所属学校) + * @return 用户树形结构(组节点 type=1 / 教师节点 type=2) + */ + List> getSchoolAllTree(Integer schId); + + /** + * 获取指定组内用户(教师 + 管理员)。 + * 对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。 + * + * @param gid 组ID + * @param role 角色(默认 2=教师;1=管理员等) + * @return 组内用户列表 + */ + List> getGroupUser(Integer gid, Integer role); + + /** + * 获取当前用户可访问的模块树(左侧菜单)。 + * 从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。 + * + * @param userId 用户ID + * @param schId 学校ID(可为 null) + * @return 模块树(顶层 List,含 children 嵌套) + */ + List> getUserModuleTree(Integer userId, Integer schId); +} \ No newline at end of file diff --git a/src/main/java/com/yunhai/homework/service/impl/ClassService.java b/src/main/java/com/yunhai/homework/service/impl/ClassService.java new file mode 100644 index 0000000..8be5e2a --- /dev/null +++ b/src/main/java/com/yunhai/homework/service/impl/ClassService.java @@ -0,0 +1,217 @@ +package com.yunhai.homework.service.impl; + +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.SchClassDto; +import com.yunhai.homework.model.bsch.SchGradeDto; +import com.yunhai.homework.model.bsch.SchInfoDto; +import com.yunhai.homework.model.bsch.SchStudentDto; +import com.yunhai.homework.model.bsch.SchSystemDto; +import com.yunhai.homework.model.bsch.SchTreeNodeDto; +import com.yunhai.homework.service.IClassService; +import com.yunhai.homework.model.SchTeaList; +import com.yunhai.homework.model.bsch.*; +import com.yunhai.platform.apistd.BizException; +import com.yunhai.platform.utils.exception.ErrorCode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 班级管理(bschclass)开放接口实现。 + * bschclass 为开放接口(无需应用签名),使用独立 RestTemplate 直连。 + * 推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List
封装 {@code YschBaseOpenApi} / {@code OpenApiUserClient} 静态门面调用, + * 把原本散落在 Controller 的组树组装、教师挂组、成员解析等业务逻辑下沉到 Service 层, + * Controller 只保留"取参 → 调 Service → 返回"的薄壳。
组装规则(从原 BaseApiController.getSchoolAll 下沉):
对应前端 base/GetGroupUser。原实现为空桩返回空列表,现按 C# 语义 + * (ITeainfoBLL.GetGroupTeainfo + IAdminfoBLL.GetGroupAdminfo)接入用户中心。 + * 若用户中心不可用或未配置对应开放接口,则抛出带语义的异常(显式报错而非静默失能)。
从基础平台拉取当前用户模块列表(含学校上下文),组装为前端 layout 期望的树结构 + * ({@code modId/page/children}),对应前端 GetUserMod 接口。
推送范围树/圈人/身份快照统一经本门面(改造方案 2.0): + * 学段 GetSchLenlist、年级 GetSchLenLevellist、班级 GetVClassList、 + * 学生 GetSchStulist / GetGradeStulist(补缺) / GetSubStulist(补缺) / GetClaArrStulist / GetStuIdslist、 + * 学生树 SchGroup/GetStuTree。 + */ +@Service +public class ClassService implements IClassService { + + private static final Logger log = LoggerFactory.getLogger(ClassService.class); + + /** 班级管理服务基址,如 http://base.yunhaixingtong.com/bschclass */ + @Value("${yh.open.bschclass:}") + private String bschClassHost; + + private final RestTemplate restTemplate; + + public ClassService() { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(5000); + factory.setReadTimeout(10000); + this.restTemplate = new RestTemplate(factory); + } + + @Override + public List getSchTeaList(int schId) { + return exchangeList(bschClassHost + "/oapi/DbClass/GetSchteaList?Group=" + schId, + new ParameterizedTypeReference>() { + }, "获取教师数据", "schId=" + schId); + } + + @Override + public SchInfoDto getSchFirst(int schId) { + requireHost(); + String url = bschClassHost + "/oapi/SchInfo/GetUserSchFirst?UserSchID=" + schId; + try { + ResponseEntity resp = restTemplate.getForEntity(URI.create(url), SchInfoDto.class); + return resp.getBody(); + } catch (RestClientException e) { + log.error("[getSchFirst] 请求班级管理接口失败, url={}", url, e); + throw new BizException(ErrorCode.INTERNAL_SERVER_ERROR, "获取学校信息失败"); + } + } + + @Override + public List getSchLenList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenlist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学段列表", "schSerId=" + schSerId); + } + + @Override + public List getSchGradeList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/Schoolen/GetSchLenLevellist?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取年级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchClassList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/VSchClass/GetVClassList?SchSerID=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取班级列表", "schSerId=" + schSerId); + } + + @Override + public List getSchStuList(String schSerId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSchStulist?schserid=" + schSerId, + new ParameterizedTypeReference>() { + }, "获取学校学生", "schSerId=" + schSerId); + } + + @Override + public List getGradeStuList(String schSerId, Integer gradeId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetGradeStulist?SchSerID=" + schSerId + "&GradeId=" + gradeId, + new ParameterizedTypeReference>() { + }, "获取年级学生", "schSerId=" + schSerId + ", gradeId=" + gradeId); + } + + @Override + public List getSubStuList(String schSerId, Integer schSystemId) { + return exchangeList(bschClassHost + "/oapi/OAStu/GetSubStulist?SchSerID=" + schSerId + "&SchSystemId=" + schSystemId, + new ParameterizedTypeReference>() { + }, "获取学段学生", "schSerId=" + schSerId + ", schSystemId=" + schSystemId); + } + + @Override + public List