commit 5a74ab0b0de8d09631601118f3082b9877a7ed86 Author: liweijie Date: Tue Aug 18 09:49:02 2026 +0800 初始化提交 diff --git a/backend/DmJdbcDriver8.jar b/backend/DmJdbcDriver8.jar new file mode 100644 index 000000000..a710ec0c6 Binary files /dev/null and b/backend/DmJdbcDriver8.jar differ diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 000000000..47503b221 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,20 @@ +# zhjx 智慧教学项目 +# 项目介绍 +## 技术架构 +* jdk 21 +* maven 3.9.6 +* springboot 3.1.1 +* mysql 8.0.33 +* DM8 +* redis 7.2.13 + +### 目录介绍 +* admin 启动类 +* common 实体等 +* quartz 定时任务 +* system 系统管理 +* sql目录下的jwgl.sql文件是未来所有新增功能需要在数据库执行更新的脚本 + + +## 前端框架 +* 前端采用Vue、Element UI。 \ No newline at end of file diff --git a/backend/bin/clean.bat b/backend/bin/clean.bat new file mode 100644 index 000000000..ac3c34c09 --- /dev/null +++ b/backend/bin/clean.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [信息] 清理所有target目录文件 +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean + +pause \ No newline at end of file diff --git a/backend/bin/package.bat b/backend/bin/package.bat new file mode 100644 index 000000000..3f01f7f04 --- /dev/null +++ b/backend/bin/package.bat @@ -0,0 +1,12 @@ +@echo off +echo. +echo [信息] 打包Web项目,生成war/jar文件 +echo. + +%~d0 +cd %~dp0 + +cd .. +call mvn clean package -Dmaven.test.skip=true + +pause \ No newline at end of file diff --git a/backend/bin/run.bat b/backend/bin/run.bat new file mode 100644 index 000000000..e80a11a7b --- /dev/null +++ b/backend/bin/run.bat @@ -0,0 +1,14 @@ +@echo off +echo. +echo [信息] 使用Jar方式启动Web项目。 +echo. + +cd %~dp0 +cd ../roomroot-admin/target + +set JAVA_OPTS=-Xms256m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m + +java -jar %JAVA_OPTS% roomroot-admin.jar + +cd bin +pause \ No newline at end of file diff --git a/backend/pom.xml b/backend/pom.xml new file mode 100644 index 000000000..2097f82b0 --- /dev/null +++ b/backend/pom.xml @@ -0,0 +1,225 @@ + + + 4.0.0 + + com.roomroot + roomroot + 3.9.2 + + roomroot + 风影随行管理系统 + + + 3.9.2 + UTF-8 + UTF-8 + 17 + 4.0.6 + 3.5.7 + 1.2.28 + 8.1.1 + 2.3.3 + 2.0.62 + 7.3.0 + 2.22.0 + 4.1.2 + 2.3 + 0.9.1 + 2.3.1 + 3.0.3 + + + + + + + + + org.springframework.boot + spring-boot-dependencies + ${spring-boot.version} + pom + import + + + + + com.alibaba + druid-spring-boot-4-starter + ${druid.version} + + + + + nl.basjes.parse.useragent + yauaa + ${yauaa.version} + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + + javax.xml.bind + jaxb-api + ${jaxb-api.version} + + + + + com.github.oshi + oshi-core + ${oshi.version} + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + ${springdoc.version} + + + + + commons-io + commons-io + ${commons.io.version} + + + + + org.apache.poi + poi-ooxml + ${poi.version} + + + + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + + + + com.alibaba.fastjson2 + fastjson2 + ${fastjson.version} + + + + + io.jsonwebtoken + jjwt + ${jwt.version} + + + + + pro.fessional + kaptcha + ${kaptcha.version} + + + + + com.roomroot + roomroot-quartz + ${roomroot.version} + + + + + com.roomroot + roomroot-generator + ${roomroot.version} + + + + + com.roomroot + roomroot-framework + ${roomroot.version} + + + + + com.roomroot + roomroot-system + ${roomroot.version} + + + + + com.roomroot + roomroot-common + ${roomroot.version} + + + + + + + roomroot-admin + roomroot-framework + roomroot-system + roomroot-quartz + roomroot-generator + roomroot-common + roomroot-jwgl + + pom + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + true + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring-boot.version} + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + + + + + public + aliyun nexus + https://maven.aliyun.com/repository/public + + true + + + false + + + + + \ No newline at end of file diff --git a/backend/roomroot-admin/pom.xml b/backend/roomroot-admin/pom.xml new file mode 100644 index 000000000..dfd6bd640 --- /dev/null +++ b/backend/roomroot-admin/pom.xml @@ -0,0 +1,101 @@ + + + + roomroot + com.roomroot + 3.9.2 + + 4.0.0 + jar + roomroot-admin + + + web服务入口 + + + + + + + org.springframework.boot + spring-boot-devtools + true + + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + + + + + com.dameng + DmJdbcDriver18 + 8.1.2.192 + + + + + com.roomroot + roomroot-framework + + + + + com.roomroot + roomroot-quartz + + + + + com.roomroot + roomroot-generator + + + + com.github.codemonstur + embedded-redis + 0.12.0 + + + + com.roomroot + roomroot-jwgl + 3.9.2 + compile + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + true + + + + + repackage + + + + + + org.apache.maven.plugins + maven-war-plugin + 3.1.0 + + false + ${project.artifactId} + + + + ${project.artifactId} + + + \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/EmbeddedRedisConfig.java b/backend/roomroot-admin/src/main/java/com/roomroot/EmbeddedRedisConfig.java new file mode 100644 index 000000000..d1379f112 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/EmbeddedRedisConfig.java @@ -0,0 +1,49 @@ +package com.roomroot; + +import com.roomroot.web.core.config.EmbeddedRedisCondition; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import redis.embedded.RedisServer; +import java.io.IOException; + +@Configuration +@Conditional(EmbeddedRedisCondition.class) +public class EmbeddedRedisConfig { + + private final int redisPort; + private final String maxMemory; + private RedisServer redisServer; + + public EmbeddedRedisConfig( + @Value("${custom.embedded-redis.port}") int redisPort, + @Value("${custom.embedded-redis.max-memory}") String maxMemory + ) { + this.redisPort = redisPort; + this.maxMemory = maxMemory; + } + + @PostConstruct + public void startEmbeddedRedis() throws IOException { + try { + redisServer = new RedisServer(redisPort); + // 可变参数追加redis配置 + redisServer.start( + ); + System.out.println("===== 内嵌Redis启动成功,端口:" + redisPort + " ====="); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("内嵌Redis启动失败!端口:" + redisPort); + } + } + + @PreDestroy + public void stopEmbeddedRedis() throws IOException { + if (redisServer != null && redisServer.isActive()) { + redisServer.stop(); + System.out.println("===== 内嵌Redis已停止 ====="); + } + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootApplication.java b/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootApplication.java new file mode 100644 index 000000000..cdf9c2c61 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootApplication.java @@ -0,0 +1,24 @@ +package com.roomroot; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; + +/** + * 启动程序 + * + * @author roomroot + */ +@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class }) +@MapperScan({"com.roomroot.**.mapper"}) +public class RoomRootApplication +{ + public static void main(String[] args) + { + // System.setProperty("spring.devtools.restart.enabled", "false"); + SpringApplication.run(RoomRootApplication.class, args); + System.out.println("(♥◠‿◠)ノ゙ 风影随行启动成功 ლ(´ڡ`ლ)゙ \n"); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootServletInitializer.java b/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootServletInitializer.java new file mode 100644 index 000000000..347d3fc97 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/RoomRootServletInitializer.java @@ -0,0 +1,18 @@ +package com.roomroot; + +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; + +/** + * web容器中进行部署 + * + * @author roomroot + */ +public class RoomRootServletInitializer extends SpringBootServletInitializer +{ + @Override + protected SpringApplicationBuilder configure(SpringApplicationBuilder application) + { + return application.sources(RoomRootApplication.class); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CaptchaController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CaptchaController.java new file mode 100644 index 000000000..7e770f1d5 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CaptchaController.java @@ -0,0 +1,94 @@ +package com.roomroot.web.controller.common; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import jakarta.annotation.Resource; +import javax.imageio.ImageIO; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.FastByteArrayOutputStream; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import com.google.code.kaptcha.Producer; +import com.roomroot.common.config.RoomRootConfig; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.redis.RedisCache; +import com.roomroot.common.utils.sign.Base64; +import com.roomroot.common.utils.uuid.IdUtils; +import com.roomroot.system.service.ISysConfigService; + +/** + * 验证码操作处理 + * + * @author roomroot + */ +@RestController +public class CaptchaController +{ + @Resource(name = "captchaProducer") + private Producer captchaProducer; + + @Resource(name = "captchaProducerMath") + private Producer captchaProducerMath; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysConfigService configService; + /** + * 生成验证码 + */ + @GetMapping("/captchaImage") + public AjaxResult getCode(HttpServletResponse response) throws IOException + { + AjaxResult ajax = AjaxResult.success(); + boolean captchaEnabled = configService.selectCaptchaEnabled(); + ajax.put("captchaEnabled", captchaEnabled); + if (!captchaEnabled) + { + return ajax; + } + + // 保存验证码信息 + String uuid = IdUtils.simpleUUID(); + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + uuid; + + String capStr = null, code = null; + BufferedImage image = null; + + // 生成验证码 + String captchaType = RoomRootConfig.getCaptchaType(); + if ("math".equals(captchaType)) + { + String capText = captchaProducerMath.createText(); + capStr = capText.substring(0, capText.lastIndexOf("@")); + code = capText.substring(capText.lastIndexOf("@") + 1); + image = captchaProducerMath.createImage(capStr); + } + else if ("char".equals(captchaType)) + { + capStr = code = captchaProducer.createText(); + image = captchaProducer.createImage(capStr); + } + + redisCache.setCacheObject(verifyKey, code, Constants.CAPTCHA_EXPIRATION, TimeUnit.MINUTES); + // 转换流信息写出 + FastByteArrayOutputStream os = new FastByteArrayOutputStream(); + try + { + ImageIO.write(image, "jpg", os); + } + catch (IOException e) + { + return AjaxResult.error(e.getMessage()); + } + + ajax.put("uuid", uuid); + ajax.put("img", Base64.encode(os.toByteArray())); + return ajax; + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CommonController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CommonController.java new file mode 100644 index 000000000..ce3d1c2f7 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/common/CommonController.java @@ -0,0 +1,166 @@ +package com.roomroot.web.controller.common; + +import java.util.ArrayList; +import java.util.List; + +import com.roomroot.system.service.ISysConfigService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import com.roomroot.common.config.RoomRootConfig; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.file.FileUploadUtils; +import com.roomroot.common.utils.file.FileUtils; +import com.roomroot.framework.config.ServerConfig; + +/** + * 通用请求处理 + * + * @author roomroot + */ +@RestController +@RequestMapping("/common") +public class CommonController +{ + private static final Logger log = LoggerFactory.getLogger(CommonController.class); + + @Autowired + private ServerConfig serverConfig; + + private static final String FILE_DELIMITER = ","; + + @Autowired + private ISysConfigService configService; + + /** + * 通用下载请求 + * + * @param fileName 文件名称 + * @param delete 是否删除 + */ + @GetMapping("/download") + public void fileDownload(String fileName, Boolean delete, HttpServletResponse response, HttpServletRequest request) + { + try + { + if (!FileUtils.checkAllowDownload(fileName)) + { + throw new Exception(StringUtils.format("文件名称({})非法,不允许下载。 ", fileName)); + } + String realFileName = System.currentTimeMillis() + fileName.substring(fileName.indexOf("_") + 1); + String filePath = RoomRootConfig.getDownloadPath() + fileName; + + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, realFileName); + FileUtils.writeBytes(filePath, response.getOutputStream()); + if (delete) + { + FileUtils.deleteFile(filePath); + } + } + catch (Exception e) + { + log.error("下载文件失败", e); + } + } + + /** + * 通用上传请求(单个) + */ + @PostMapping("/upload") + public AjaxResult uploadFile(MultipartFile file) throws Exception + { + try{ + // 上传文件路径 + String filePath = configService.selectConfigByKey("sys.document.path"); + if(StringUtils.isEmpty(filePath)){ + filePath = RoomRootConfig.getUploadPath(); + } + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + AjaxResult ajax = AjaxResult.success(); + ajax.put("url", url); + ajax.put("fileName", fileName); + ajax.put("newFileName", FileUtils.getName(fileName)); + ajax.put("originalFilename", file.getOriginalFilename()); + return ajax; + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 通用上传请求(多个) + */ + @PostMapping("/uploads") + public AjaxResult uploadFiles(List files) { + try{ + // 上传文件路径 + String filePath = configService.selectConfigByKey("sys.document.path"); + if(StringUtils.isEmpty(filePath)){ + filePath = RoomRootConfig.getUploadPath(); + } + List urls = new ArrayList(); + List fileNames = new ArrayList(); + List newFileNames = new ArrayList(); + List originalFilenames = new ArrayList(); + for (MultipartFile file : files) + { + // 上传并返回新文件名称 + String fileName = FileUploadUtils.upload(filePath, file); + String url = serverConfig.getUrl() + fileName; + urls.add(url); + fileNames.add(fileName); + newFileNames.add(FileUtils.getName(fileName)); + originalFilenames.add(file.getOriginalFilename()); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("urls", StringUtils.join(urls, FILE_DELIMITER)); + ajax.put("fileNames", StringUtils.join(fileNames, FILE_DELIMITER)); + ajax.put("newFileNames", StringUtils.join(newFileNames, FILE_DELIMITER)); + ajax.put("originalFilenames", StringUtils.join(originalFilenames, FILE_DELIMITER)); + return ajax; + } catch (Exception e) { + return AjaxResult.error(e.getMessage()); + } + } + + /** + * 本地资源通用下载 + */ + @GetMapping("/download/resource") + public void resourceDownload(String resource, HttpServletRequest request, HttpServletResponse response) + throws Exception + { + try + { + if (!FileUtils.checkAllowDownload(resource)) + { + throw new Exception(StringUtils.format("资源文件({})非法,不允许下载。 ", resource)); + } + // 本地资源路径 + String localPath = RoomRootConfig.getProfile(); + // 数据库资源地址 + String downloadPath = localPath + FileUtils.stripPrefix(resource); + // 下载名称 + String downloadName = StringUtils.substringAfterLast(downloadPath, "/"); + response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE); + FileUtils.setAttachmentResponseHeader(response, downloadName); + FileUtils.writeBytes(downloadPath, response.getOutputStream()); + } + catch (Exception e) + { + log.error("下载文件失败", e); + } + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AccountManagementController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AccountManagementController.java new file mode 100644 index 000000000..a122c7ca7 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AccountManagementController.java @@ -0,0 +1,330 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.accountmanagement.AccountCreateDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountDisableDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountIdDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountLoginDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountLoginInformationQueryDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountMergeDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountPasswordChangeDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountPasswordResetDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountQueryDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountRoleConfigurationDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountRoleOptionQueryDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountSsoSessionResolveDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountUpdateDTO; +import com.roomroot.jwgl.service.AccountManagementService; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.vo.accountmanagement.AccountMergeResultVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountLoginCaptchaVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountLoginInformationVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountLoginVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountRoleOptionVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountRoleTypeVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountRoleVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountSsoSessionVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountVO; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + + +import java.util.List; + +/** + * 账户管理接口。 + */ +@RestController +@RequestMapping("/account-management") +public class AccountManagementController { + + /** + * 账户管理业务服务。 + */ + @Resource + private AccountManagementService accountManagementService; + + /** + * 查询账户列表。 + * + * @param dto 账户查询条件 + * @return 符合条件的账户分页结果 + */ + @GetMapping("/list") + public Result> list( + @Valid AccountQueryDTO dto) { + // 调用业务层完成账户条件筛选、数据库分页和当前页角色批量装配。 + return Result.success(accountManagementService.list(dto)); + } + + /** + * 查询账户详情。 + * + * @param dto 账户编号参数 + * @return 包含角色和数据范围的账户详情 + */ + @GetMapping("/get") + public Result get(@Valid AccountIdDTO dto) { + // 根据账户编号查询完整信息,并确保密码和密码提示答案不向页面返回。 + return Result.success(accountManagementService.get(dto)); + } + + /** + * 新增账户并配置初始角色。 + * + * @param dto 账户新增参数 + * @return 新增后的账户详情 + */ + @PostMapping("/add") + public Result add( + @Valid @RequestBody AccountCreateDTO dto) { + // 调用业务层完成唯一性校验、账户写入、状态初始化和初始角色配置。 + return Result.success("新增成功", accountManagementService.add(dto)); + } + + /** + * 修改账户基本信息。 + * + * @param dto 账户修改参数 + * @return 修改后的账户详情 + */ + @PostMapping("/update") + public Result update( + @Valid @RequestBody AccountUpdateDTO dto) { + // 普通修改只维护账户资料,不在当前接口隐式修改密码、状态或角色。 + return Result.success( + "修改成功", accountManagementService.update(dto)); + } + + /** + * 停用账户。 + * + * @param dto 账户停用参数 + * @return 停用后的账户详情 + */ + @PostMapping("/disable") + public Result disable( + @Valid @RequestBody AccountDisableDTO dto) { + // 使用账户状态表进行软停用,保留账户及历史业务引用。 + return Result.success( + "停用成功", accountManagementService.disable(dto)); + } + + /** + * 启用普通停用账户。 + * + * @param dto 账户编号参数 + * @return 启用后的账户详情 + */ + @PostMapping("/enable") + public Result enable( + @Valid @RequestBody AccountIdDTO dto) { + // 调用业务层清除普通停用信息,已合并的源账户不允许重新启用。 + return Result.success( + "启用成功", accountManagementService.enable(dto)); + } + + /** + * 重置账户密码。 + * + * @param dto 密码重置参数 + * @return 密码重置后的账户详情 + */ + @PostMapping("/reset-password") + public Result resetPassword( + @Valid @RequestBody AccountPasswordResetDTO dto) { + // 调用业务层重置密码,并同步清除临时登录失败次数。 + return Result.success( + "密码重置成功", + accountManagementService.resetPassword(dto)); + } + + /** + * 当前登录账户修改自己的密码。 + * + * @param dto 原密码、新密码和确认密码 + * @return 密码修改后的脱敏账户信息 + */ + @PostMapping("/change-password") + public Result changePassword( + @Valid @RequestBody AccountPasswordChangeDTO dto) { + /* + * 账户编号只从服务端登录会话读取,不接收客户端指定的目标账户, + * 防止普通账户通过修改请求参数变更其他账户的密码。 + */ + return Result.success( + "密码修改成功,请重新登录", + accountManagementService.changePassword(dto)); + } + + /** + * 查询当前登录账户的登录信息和成功登录历史。 + * + * @param dto 登录历史分页参数 + * @return 当前账户的上次、本次登录时间和成功登录历史 + */ + @GetMapping("/login-information") + public Result loginInformation( + @Valid AccountLoginInformationQueryDTO dto) { + /* + * 查询目标只从服务端当前会话取得,不接收客户端账户编号, + * 因此普通登录账户只能查看自己的登录信息和登录历史。 + */ + return Result.success( + accountManagementService.getLoginInformation(dto)); + } + + /** + * 生成本地账户登录使用的一次性图形验证码。 + * + * @param response 当前 HTTP 响应 + * @return 验证码编号、PNG 图片数据和有效秒数 + */ + @GetMapping("/login-captcha") + public Result loginCaptcha( + HttpServletResponse response) { + /* + * 第一步:禁止浏览器和中间代理缓存验证码响应, + * 防止刷新页面时继续显示已经失效的旧验证码。 + */ + response.setHeader("Cache-Control", + "no-store, no-cache, must-revalidate, max-age=0"); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0L); + + /* + * 第二步:生成验证码并保存到当前 Servlet Session, + * 页面只会收到验证码编号和图片,不会收到验证码答案。 + */ + return Result.success( + accountManagementService.generateLoginCaptcha()); + } + + /** + * 使用本地账号密码登录。 + * + * @param dto 本地账号密码登录参数 + * @return 脱敏后的本地账户信息和角色 + */ + @PostMapping("/login") + public Result login( + @Valid @RequestBody AccountLoginDTO dto) { + /* + * 本地登录接口只负责接收登录参数并返回脱敏结果; + * 验证码、密码、账户状态检查和 Servlet Session 建立全部由业务层完成。 + */ + return Result.success( + "登录成功", accountManagementService.login(dto)); + } + + /** + * 校验 SSO 会话、建立本地登录状态并解析唯一的本地账户。 + * + * @param dto SSO 会话解析参数 + * @return SSO 会话及其对应的本地账户信息;本地 Session 标识不直接返回 + */ + @PostMapping("/resolve-sso-session") + public Result resolveSsoSession( + @Valid @RequestBody AccountSsoSessionResolveDTO dto) { + /* + * 第一步:把会话标识交给业务层校验有效期。 + * 第二步:同时使用 LoginID 和 UserID 解析唯一的本地统一账号。 + * 第三步:仅返回不包含密码和 SSO 会话标识的核验结果。 + */ + return Result.success( + accountManagementService.resolveSsoSession(dto)); + } + + /** + * 退出当前登录会话。 + * + * @return 不包含业务数据的退出结果 + */ + @PostMapping("/logout") + public Result logout() { + // 调用业务层清理当前在线记录并作废本地 Servlet Session。 + accountManagementService.logout(); + + // 退出接口没有业务返回对象,因此使用 Result 返回统一成功结果。 + return Result.success("退出登录成功", null); + } + + /** + * 查询系统支持的角色类型。 + * + * @return 角色类型列表 + */ + @GetMapping("/role-types") + public Result> roleTypes() { + // 返回与角色配置、查询和合并流程一致的七类角色定义。 + return Result.success(accountManagementService.listRoleTypes()); + } + + /** + * 查询指定角色类型下的身份选项。 + * + * @param dto 角色身份查询条件 + * @return 角色身份选项分页结果 + */ + @GetMapping("/role-options") + public Result> roleOptions( + @Valid AccountRoleOptionQueryDTO dto) { + // 调用业务层完成角色类型、关键字、绑定状态筛选和数据库分页。 + return Result.success( + accountManagementService.listRoleOptions(dto)); + } + + /** + * 查询账户已有角色和数据范围。 + * + * @param dto 账户编号参数 + * @return 账户角色列表 + */ + @GetMapping("/roles") + public Result> roles( + @Valid AccountIdDTO dto) { + // 查询账户现有全部身份绑定,并补充统一角色名称。 + return Result.success(accountManagementService.listRoles(dto)); + } + + /** + * 整体配置账户角色和数据范围。 + * + * @param dto 账户角色配置参数 + * @return 配置完成后的账户详情 + */ + @PostMapping("/configure-roles") + public Result configureRoles( + @Valid @RequestBody AccountRoleConfigurationDTO dto) { + /* + * 业务层会先预校验全部身份,再在同一事务中替换角色, + * 避免部分身份无效时已经清除了账户原有角色。 + */ + return Result.success( + "角色配置成功", + accountManagementService.configureRoles(dto)); + } + + /** + * 合并重复账户。 + * + * @param dto 账户合并参数 + * @return 合并后的源账户和目标账户信息 + */ + @PostMapping("/merge") + public Result merge( + @Valid @RequestBody AccountMergeDTO dto) { + /* + * 合并只迁移角色和数据范围,不物理删除源账户, + * 从而保留其他历史业务表对源账户的原始引用。 + */ + return Result.success( + "账户合并成功", accountManagementService.merge(dto)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AffiliationSettingController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AffiliationSettingController.java new file mode 100644 index 000000000..7be874132 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/AffiliationSettingController.java @@ -0,0 +1,246 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.affiliationsetting.PersonnelAffiliationDTO; +import com.roomroot.jwgl.dto.affiliationsetting.PersonnelTransferDTO; +import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationAddDTO; +import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationIdDTO; +import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationQueryDTO; +import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeOptionQueryDTO; +import com.roomroot.jwgl.dto.affiliationsetting.TeachingClassAffiliationDTO; +import com.roomroot.jwgl.service.AffiliationSettingService; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.vo.affiliationsetting.AffiliationDepartmentVO; +import com.roomroot.jwgl.vo.affiliationsetting.PersonnelAffiliationVO; +import com.roomroot.jwgl.vo.affiliationsetting.ResearchOfficeAffiliationVO; +import com.roomroot.jwgl.vo.affiliationsetting.ResearchOfficeVO; +import com.roomroot.jwgl.vo.affiliationsetting.TeachingClassAffiliationVO; +import com.roomroot.jwgl.vo.affiliationsetting.TransferHistoryVO; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + + +import java.util.List; + +/** + * 隶属关系设置统一接口。 + * + *

该控制器统一承载教研室和教学班次两类隶属关系接口, + * 同时保留原有请求路径,避免前端调用方式发生变化。

+ */ +@RestController +public class AffiliationSettingController { + + /** + * 隶属关系设置统一业务服务。 + */ + @Resource + private AffiliationSettingService affiliationSettingService; + + /** + * 查询可供页面选择的启用部别。 + * + * @param dto 查询条件 + * @return 可选部别列表 + */ + @GetMapping("/research-office-affiliation/departments") + public Result> researchOfficeDepartments( + @Valid ResearchOfficeOptionQueryDTO dto) { + // 调用业务层查询启用部别,并返回专用于页面展示的 VO 列表。 + return Result.success( + affiliationSettingService.listResearchOfficeDepartments(dto)); + } + + /** + * 查询指定部别已经关联的教研室。 + * + * @param dto 部别编号及查询条件 + * @return 已建立的教研室隶属关系列表 + */ + @GetMapping("/research-office-affiliation/list") + public Result> researchOfficeList( + @Valid ResearchOfficeAffiliationQueryDTO dto) { + // 调用业务层查询当前部别的全部隶属关系,并返回包含教研室展示字段的 VO。 + return Result.success( + affiliationSettingService.listResearchOfficeAffiliations(dto)); + } + + /** + * 查询尚未分配且处于启用状态的教研室。 + * + * @param dto 查询条件 + * @return 可添加的教研室列表 + */ + @GetMapping("/research-office-affiliation/available") + public Result> availableResearchOffices( + @Valid ResearchOfficeOptionQueryDTO dto) { + // 调用业务层排除已经存在隶属关系的教研室,返回可添加的教研室 VO。 + return Result.success( + affiliationSettingService.listAvailableResearchOffices(dto)); + } + + /** + * 新增教研室隶属关系。 + * + * @param dto 部别编号和教研室编号 + * @return 新增后的隶属关系 + */ + @PostMapping("/research-office-affiliation/add") + public Result addResearchOfficeAffiliation( + @Valid @RequestBody ResearchOfficeAffiliationAddDTO dto) { + // 调用业务层完成校验和新增,并将持久化后的关系 VO 返回给前端。 + return Result.success( + "添加成功", + affiliationSettingService.addResearchOfficeAffiliation(dto)); + } + + /** + * 解除教研室隶属关系。 + * + * @param dto 隶属关系编号 + * @return 被解除的隶属关系 + */ + @PostMapping("/research-office-affiliation/remove") + public Result removeResearchOfficeAffiliation( + @Valid @RequestBody ResearchOfficeAffiliationIdDTO dto) { + // 按关系编号解除隶属关系,避免仅凭教研室编号误删其他业务数据。 + return Result.success( + "解除成功", + affiliationSettingService.removeResearchOfficeAffiliation(dto)); + } + + /** + * 查询可供教学班次隶属关系页面选择的启用部别。 + * + * @param dto 查询条件 + * @return 可选部别列表 + */ + @GetMapping("/teaching-class-affiliation/departments") + public Result> teachingClassDepartments( + @Valid TeachingClassAffiliationDTO dto) { + // 调用统一业务层查询启用部别,并返回与数据库实体隔离的共享部别 VO。 + return Result.success( + affiliationSettingService.listTeachingClassDepartments(dto)); + } + + /** + * 查询指定部别已经关联的教学班次。 + * + * @param dto 部别编号及查询条件 + * @return 已建立的教学班次隶属关系列表 + */ + @GetMapping("/teaching-class-affiliation/list") + public Result> teachingClassList( + @Valid TeachingClassAffiliationDTO dto) { + // 调用统一业务层查询当前部别的全部教学班次关系,并组装页面展示 VO。 + return Result.success( + affiliationSettingService.listTeachingClassAffiliations(dto)); + } + + /** + * 查询尚未分配给任何部别的教学班次。 + * + * @param dto 查询条件 + * @return 可添加的教学班次列表 + */ + @GetMapping("/teaching-class-affiliation/available") + public Result> availableTeachingClasses( + @Valid TeachingClassAffiliationDTO dto) { + // 调用统一业务层排除已经存在隶属关系的班次,并返回可添加的教学班次 VO。 + return Result.success( + affiliationSettingService.listAvailableTeachingClasses(dto)); + } + + /** + * 新增教学班次隶属关系。 + * + * @param dto 部别编号和教学班次编号 + * @return 新增后的隶属关系 + */ + @PostMapping("/teaching-class-affiliation/add") + public Result addTeachingClassAffiliation( + @Valid @RequestBody TeachingClassAffiliationDTO dto) { + // 调用统一业务层完成参数校验、冲突检查和关系新增,并返回新增后的关系 VO。 + return Result.success( + "添加成功", + affiliationSettingService.addTeachingClassAffiliation(dto)); + } + + /** + * 解除教学班次隶属关系。 + * + * @param dto 部别编号和教学班次编号 + * @return 被解除的隶属关系 + */ + @PostMapping("/teaching-class-affiliation/remove") + public Result removeTeachingClassAffiliation( + @Valid @RequestBody TeachingClassAffiliationDTO dto) { + // 按照部别编号和教学班次编号精确解除关系,避免旧页面请求误删新归属。 + return Result.success( + "解除成功", + affiliationSettingService.removeTeachingClassAffiliation(dto)); + } + + // ============ 人员隶属关系统一接口(方向1新增)============ + + /** + * 查询人员当前隶属关系。 + *

支持按人员类型+部门编号筛选,可附加关键字匹配姓名/拼音。

+ * + * @param dto 查询条件 + * @return 人员隶属关系列表 + */ + @GetMapping("/affiliation/personnel/list") + public Result> personnelList(@Valid PersonnelAffiliationDTO dto) { + return Result.success(affiliationSettingService.listPersonnelAffiliations(dto)); + } + + /** + * 分配人员到部门/组织单元(首次归属)。 + *

若人员已有归属,返回冲突错误,提示走异动接口。

+ * + * @param dto 分配参数 + * @return 分配后的人员隶属关系 + */ + @PostMapping("/affiliation/personnel/assign") + public Result assignPersonnel(@Valid @RequestBody PersonnelAffiliationDTO dto) { + return Result.success("分配成功", affiliationSettingService.assignPersonnel(dto)); + } + + /** + * 解除人员隶属关系(清空当前归属)。 + * + * @param dto 解除参数 + * @return 解除后的人员隶属关系 + */ + @PostMapping("/affiliation/personnel/unassign") + public Result unassignPersonnel(@Valid @RequestBody PersonnelAffiliationDTO dto) { + return Result.success("解除成功", affiliationSettingService.unassignPersonnel(dto)); + } + + /** + * 人员异动调整(核心接口)。 + *

记录异动历史 + 更新归属 + 校验关联表,必须填写异动原因。

+ * + * @param dto 异动参数 + * @return 异动后的人员隶属关系 + */ + @PostMapping("/affiliation/personnel/transfer") + public Result transferPersonnel(@Valid @RequestBody PersonnelTransferDTO dto) { + return Result.success("异动成功", affiliationSettingService.transferPersonnel(dto)); + } + + /** + * 查询人员异动历史。 + * + * @param dto 查询参数 + * @return 异动历史VO(含JSON原文) + */ + @GetMapping("/affiliation/personnel/history") + public Result personnelHistory(@Valid PersonnelAffiliationDTO dto) { + return Result.success(affiliationSettingService.getTransferHistory(dto)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BZLBController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BZLBController.java new file mode 100644 index 000000000..e415739ed --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BZLBController.java @@ -0,0 +1,76 @@ +package com.roomroot.web.controller.jwgl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.BZLB; +import com.roomroot.jwgl.service.BZLBService; +import com.roomroot.jwgl.unit.Result; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + + +import java.util.List; + +/** + * 保障类别 Controller + */ +@RestController +@RequestMapping("/bzlb") +public class BZLBController { + + @Resource + private BZLBService bzlbService; + + /** + * 新增保障类别 + */ + @PostMapping("/add") + public Result add(@RequestBody BZLB bzlb) { + bzlbService.add(bzlb); + return Result.success(); + } + + /** + * 删除保障类别 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + bzlbService.delete(bh); + return Result.success(); + } + + /** + * 更新保障类别 + */ + @PostMapping("/update") + public Result update(@RequestBody BZLB bzlb) { + bzlbService.update(bzlb); + return Result.success(); + } + + /** + * 根据编号查询保障类别 + */ + @GetMapping("/get") + public Result get(@RequestParam("bh") String bh) { + return Result.success(bzlbService.getByBh(bh)); + } + + /** + * 分页查询保障类别 + */ + @GetMapping("/list") + public Result> list( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "10") int pageSize, + BZLB bzlb) { + return Result.success(bzlbService.list(pageNum, pageSize, bzlb)); + } + + /** + * 查询所有启用的保障类别 + */ + @GetMapping("/all") + public Result> all() { + return Result.success(bzlbService.all()); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BuildController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BuildController.java new file mode 100644 index 000000000..4ed1955cb --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/BuildController.java @@ -0,0 +1,92 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.JXLB; +import com.roomroot.jwgl.service.BuildService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * 教学楼表控制器 + */ +@RestController +@RequestMapping("/build") +public class BuildController { + + @Resource + private BuildService buildService; + + /** + * 新增教学楼 + */ + @PostMapping("/add") + public Result add(@RequestBody JXLB jxlb) { + buildService.add(jxlb); + return Result.success(); + } + + /** + * 删除教学楼 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("id") String id) { + buildService.delete(id); + return Result.success(); + } + + /** + * 更新教学楼 + */ + @PostMapping("/update") + public Result update(@RequestBody JXLB jxlb) { + buildService.update(jxlb); + return Result.success(); + } + + /** + * 根据ID查询 + */ + @GetMapping("/get") + public Result getById(@RequestParam("id") String id) { + return Result.success(buildService.getById(id)); + } + + /** + * 分页查询教学楼列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, JXLB jxlb) { + return Result.success(buildService.pageList(query, jxlb)); + } + + /** + * 查询所有教学楼列表 + */ + @GetMapping("/all") + public Result> all(JXLB jxlb) { + return Result.success(buildService.list(jxlb)); + } + + /** + * 导入Excel文件 + */ + @PostMapping("/import") + public Result importExcel(@RequestParam("file") MultipartFile file) { + int count = buildService.importExcel(file); + return Result.success("导入成功", count); + } + + /** + * 下载模板文件 + */ + @GetMapping("/template") + public void downloadTemplate(HttpServletResponse response) { + buildService.downloadTemplate(response); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomCalendarController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomCalendarController.java new file mode 100644 index 000000000..b31295e72 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomCalendarController.java @@ -0,0 +1,89 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.JXCDL; +import com.roomroot.jwgl.service.ClassRoomCalendarService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 教学场地历控制器 + */ +@RestController +@RequestMapping("/classroom/jxcdl") +public class ClassRoomCalendarController { + + @Resource + private ClassRoomCalendarService calendarService; + + /** + * 新增教学场地历 + */ + @PostMapping("/add") + public Result add(@RequestBody JXCDL jxcdl) { + calendarService.add(jxcdl); + return Result.success(); + } + + /** + * 删除教学场地历 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("id") String id) { + calendarService.delete(id); + return Result.success(); + } + + /** + * 更新教学场地历 + */ + @PostMapping("/update") + public Result update(@RequestBody JXCDL jxcdl) { + calendarService.update(jxcdl); + return Result.success(); + } + + /** + * 根据ID查询 + */ + @GetMapping("/get") + public Result getById(@RequestParam("id") String id) { + return Result.success(calendarService.getById(id)); + } + + /** + * 分页查询教学场地历列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, JXCDL jxcdl) { + return Result.success(calendarService.pageList(query, jxcdl)); + } + + /** + * 查询所有教学场地历列表 + */ + @GetMapping("/all") + public Result> all(JXCDL jxcdl) { + return Result.success(calendarService.list(jxcdl)); + } + + /** + * 根据教室编号查询教学场地历列表 + */ + @GetMapping("/listByJsbh") + public Result> listByJsbh(@RequestParam("jsbh") String jsbh) { + return Result.success(calendarService.listByJsbh(jsbh)); + } + + /** + * 根据日期查询教学场地历列表 + */ + @GetMapping("/listByRq") + public Result> listByRq(@RequestParam("rq") String rq) { + return Result.success(calendarService.listByRq(rq)); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomController.java new file mode 100644 index 000000000..1dd781be6 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomController.java @@ -0,0 +1,81 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.JSB; +import com.roomroot.jwgl.service.ClassRoomService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 教室表控制器 + */ +@RestController +@RequestMapping("/classroom") +public class ClassRoomController { + + @Resource + private ClassRoomService jsbService; + + /** + * 新增教室 + */ + @PostMapping("/add") + public Result add(@RequestBody JSB jsb) { + jsbService.add(jsb); + return Result.success(); + } + + /** + * 删除教室 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("id") String id) { + jsbService.delete(id); + return Result.success(); + } + + /** + * 更新教室 + */ + @PostMapping("/update") + public Result update(@RequestBody JSB jsb) { + jsbService.update(jsb); + return Result.success(); + } + + /** + * 根据教室编号查询 + */ + @GetMapping("/get") + public Result getById(@RequestParam("id") String id) { + return Result.success(jsbService.getById(id)); + } + + /** + * 分页查询教室列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, JSB jsb) { + return Result.success(jsbService.pageList(query, jsb)); + } + + /** + * 查询所有教室列表 + */ + @GetMapping("/all") + public Result> all(JSB jsb) { + return Result.success(jsbService.list(jsb)); + } + + /** + * 根据教学楼代号查询教室列表 + */ + @GetMapping("/listByJxldh") + public Result> listByJxldh(@RequestParam("jxldh") String jxldh) { + return Result.success(jsbService.listByJxldh(jxldh)); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomLessonController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomLessonController.java new file mode 100644 index 000000000..83ae33b04 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassRoomLessonController.java @@ -0,0 +1,83 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.JSBKCB; +import com.roomroot.jwgl.service.ClassRoomLessonService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.*; + + +import java.util.List; + +/** + * 教室输出课程表控制器 + */ +@RestController +@RequestMapping("/classRoomLesson") +public class ClassRoomLessonController { + + @Resource + private ClassRoomLessonService lessonService; + + /** + * 新增教室输出课程表 + */ + @PostMapping("/add") + public Result add(@RequestBody JSBKCB jsbkcb) { + lessonService.add(jsbkcb); + return Result.success(); + } + + /** + * 删除教室输出课程表 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("id") String id) { + lessonService.delete(id); + return Result.success(); + } + + /** + * 更新教室输出课程表 + */ + @PostMapping("/update") + public Result update(@RequestBody JSBKCB jsbkcb) { + lessonService.update(jsbkcb); + return Result.success(); + } + + /** + * 根据ID查询 + */ + @GetMapping("/get") + public Result getById(@RequestParam("id") String id) { + return Result.success(lessonService.getById(id)); + } + + /** + * 根据教室编号和年度查询 + */ + @GetMapping("/getByJsbhAndNd") + public Result getByJsbhAndNd(@RequestParam("jsbh") String jsbh, + @RequestParam("nd") Integer nd) { + return Result.success(lessonService.getByJsbhAndNd(jsbh, nd)); + } + + /** + * 分页查询教室输出课程表列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, JSBKCB jsbkcb) { + return Result.success(lessonService.pageList(query, jsbkcb)); + } + + /** + * 查询所有教室输出课程表列表 + */ + @GetMapping("/all") + public Result> all(JSBKCB jsbkcb) { + return Result.success(lessonService.list(jsbkcb)); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassSemesterCalendarController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassSemesterCalendarController.java new file mode 100644 index 000000000..6fa62d72b --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassSemesterCalendarController.java @@ -0,0 +1,82 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.XYDRWB; +import com.roomroot.jwgl.service.ClassSemesterCalendarService; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.Map; + +/** + * 学员队任务表控制器 + */ +@RestController +@RequestMapping("/classCalendar") +public class ClassSemesterCalendarController { + + @Resource + private ClassSemesterCalendarService classSemesterCalendarService; + + /** + * 更新 + */ + @PostMapping("/update") + public Result update(@RequestBody XYDRWB xydrwb) { + classSemesterCalendarService.update(xydrwb); + return Result.success(); + } + + /** + * 根据编号查询 + */ + @GetMapping("/get") + public Result getByBh(@RequestParam("bh") String bh) { + return Result.success(classSemesterCalendarService.getByBh(bh)); + } + + /** + * 条目批次复制 + *

+ * 根据源学员队任务表编号集合,复制到多个目标学员队期表下, + * 替换学员队学期编号和学员队编号为目标学员队期表的值 + *

+ * + * @param params 包含sourceBhList(源编号列表)和targetBhList(目标学员队期表编号列表) + * @return 复制结果 + */ + @PostMapping("/batchCopy") + public Result> batchCopy(@RequestBody Map params) { + List sourceBhList = (List) params.get("sourceBhList"); + List targetBhList = (List) params.get("targetBhList"); + Map result = classSemesterCalendarService.batchCopy(sourceBhList, targetBhList); + return Result.success(result); + } + + /** + * 按学期批次复制 + *

+ * 根据源学员队期表编号查询其下所有任务条目,复制到多个目标学员队期表下, + * 替换学员队学期编号和学员队编号为目标学员队期表的值 + *

+ * + * @param params 包含sourceXydxqbh(源学员队期表编号)和targetBhList(目标学员队期表编号列表) + * @return 复制结果 + */ + @PostMapping("/batchCopyBySemester") + public Result> batchCopyBySemester(@RequestBody Map params) { + String sourceXydxqbh = (String) params.get("sourceXydxqbh"); + List targetBhList = (List) params.get("targetBhList"); + Map result = classSemesterCalendarService.batchCopyBySemester(sourceXydxqbh, targetBhList); + return Result.success(result); + } + + /** + * 查询所有有效数据(DEL_FLAG = 0) + */ + @GetMapping("/all") + public Result> all(XYDRWB xydrwb) { + return Result.success(classSemesterCalendarService.listAllValid(xydrwb)); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassSemesterController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassSemesterController.java new file mode 100644 index 000000000..6a8bde6d8 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ClassSemesterController.java @@ -0,0 +1,172 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.XYDNDXQJBXXB; +import com.roomroot.jwgl.service.ClassSemesterService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 班次学期控制器 + */ +@RestController +@RequestMapping("/classSemester") +public class ClassSemesterController { + + @Resource + private ClassSemesterService classSemesterService; + + /** + * 新增 + */ + @PostMapping("/add") + public Result add(@RequestBody XYDNDXQJBXXB xydndxqjbxxb) { + classSemesterService.add(xydndxqjbxxb); + return Result.success(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + classSemesterService.delete(bh); + return Result.success(); + } + + /** + * 批量删除 + */ + @PostMapping("/batchDelete") + public Result batchDelete(@RequestBody List bhList) { + classSemesterService.batchDelete(bhList); + return Result.success(); + } + + /** + * 批量修改日期范围 + *

+ * 更新班次学期的日期范围,并同步更新班次学期日历: + * 1. 删除日期范围之外的日历记录 + * 2. 补充新日期范围内缺少的日历记录 + *

+ * @return 更新数量 + */ + @PostMapping("/batchUpdateDateRange") + public Result batchUpdateDateRange(@RequestBody Map params) { + List bhList = (List) params.get("bhList");//编号列表 + String xqkssj = (String) params.get("xqkssj");//学期开始时间 + String xqjssj = (String) params.get("xqjssj");//学期结束时间 + int count = classSemesterService.batchUpdateDateRange(bhList, xqkssj, xqjssj); + return Result.success(count); + } + + /** + * 批量修改开放教员排课状态 + * + * @param bhList XYDNDXQJBXXB编号列表 + * @param kfjypk 开放教员排课(0-否,1-是) + * @return 更新数量 + */ + @PostMapping("/batchUpdateKfjypk") + public Result batchUpdateKfjypk(@RequestBody Map params) { + List bhList = (List) params.get("bhList"); + Integer kfjypk = (Integer) params.get("kfjypk"); + int count = classSemesterService.batchUpdateKfjypk(bhList, kfjypk); + return Result.success(count); + } + + /** + * 批量修改学期第次 + * + * @param bhList XYDNDXQJBXXB编号列表 + * @param xqdc 学期第次 + * @return 更新数量 + */ + @PostMapping("/batchUpdateXqdc") + public Result batchUpdateXqdc(@RequestBody Map params) { + List bhList = (List) params.get("bhList"); + Integer xqdc = (Integer) params.get("xqdc"); + int count = classSemesterService.batchUpdateXqdc(bhList, xqdc); + return Result.success(count); + } + + /** + * 批量修改教学任务编号 + * + * @return 更新数量 + */ + @PostMapping("/batchUpdateJxrwbh") + public Result batchUpdateJxrwbh(@RequestBody Map params) { + List bhList = (List) params.get("bhList"); + String jxrwbh = (String) params.get("jxrwbh");//教学任务编号 + int count = classSemesterService.batchUpdateJxrwbh(bhList, jxrwbh); + return Result.success(count); + } + + /** + * 更新 + */ + @PostMapping("/update") + public Result update(@RequestBody XYDNDXQJBXXB xydndxqjbxxb) { + classSemesterService.update(xydndxqjbxxb); + return Result.success(); + } + + /** + * 根据编号查询 + */ + @GetMapping("/get") + public Result getByBh(@RequestParam("bh") String bh) { + return Result.success(classSemesterService.getByBh(bh)); + } + + /** + * 分页查询列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, XYDNDXQJBXXB xydndxqjbxxb) { + return Result.success(classSemesterService.pageList(query, xydndxqjbxxb)); + } + + /** + * 查询所有有效数据(DEL_FLAG = 0) + */ + @GetMapping("/all") + public Result> all() { + return Result.success(classSemesterService.listAllValid()); + } + + /** + * 根据选修班次列表批量添加班次学期 + *

+ * 根据时间范围过滤学员队表,批量添加班次学期信息,并生成对应的班次学期日历 + * 筛选条件:入学日期 <= startTime 且 毕业日期 >= endTime + *

+ * + * @param startTime 时间范围开始 + * @param endTime 时间范围结束 + * @param nd 年度 + * @param xqdc 学期第次 + * @return 添加数量 + */ + @PostMapping("/batchAddFromElective") + public Result batchAddFromElective( + @RequestParam("startTime") String startTime, + @RequestParam("endTime") String endTime, + @RequestParam("nd") String nd, + @RequestParam("xqdc") String xqdc) { + + LocalDateTime start = LocalDateTime.parse(startTime + "T00:00:00"); + LocalDateTime end = LocalDateTime.parse(endTime + "T23:59:59"); + + int count = classSemesterService.batchAddFromElective(start, end, nd, xqdc, startTime, endTime); + return Result.success(count); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/CourseRunningController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/CourseRunningController.java new file mode 100644 index 000000000..6ff05f272 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/CourseRunningController.java @@ -0,0 +1,829 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.courserunning.ClassesImportDTO; +import com.roomroot.jwgl.dto.courserunning.ImportRemoveDTO; +import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustApplyDTO; +import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustStrategyDTO; +import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustAuditDTO; +import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustQueryDTO; +import com.roomroot.jwgl.dto.courserunning.SemesterImportDTO; +import com.roomroot.jwgl.dto.courserunning.SingleCourseImportDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingPlanBatchFillDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingPlanFillDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingPlanQueryDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingSupportBatchPlanDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingSupportConfirmDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingSupportPlanDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingSupportUpdateDTO; +import com.roomroot.jwgl.dto.courserunning.StudentLeaveApplyDTO; +import com.roomroot.jwgl.dto.courserunning.StudentLeaveAuditDTO; +import com.roomroot.jwgl.dto.courserunning.StudentLeaveQueryDTO; +import com.roomroot.jwgl.dto.courserunning.OperationAnalysisQueryDTO; +import com.roomroot.jwgl.service.CourseRunningImportService; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.utils.BasicManagementConstants; +import com.roomroot.jwgl.utils.ExcelParseUtil; +import com.roomroot.jwgl.vo.courserunning.*; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +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 org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import jakarta.annotation.Resource; +import tools.jackson.databind.ObjectMapper; + +import java.util.List; + +/** + * 课程教学运行管理控制器。 + *

+ * 提供课程教学运行管理的全部REST API接口,包括:
+ * 1. 教学运行数据导入:整学期导入、教学班次导入、单门课程导入、删除已导入课程;
+ * 2. 教学实施计划填写:课次列表查询、详情查询、单条填写、批量填写、提交;
+ * 3. 调课管理:调课申请列表查询、详情查询、教员提交申请、教研室审批、教研室查收、机关审批、机关查收、撤销申请;
+ * 4. 教学保障管理:保障资源查询、保障计划列表查询、单条登记、批量登记、修改、删除、确认到位、批量确认、取消确认。 + *

+ */ +@RestController +@RequestMapping("/course-running") +public class CourseRunningController { + + /** + * 课程教学运行管理业务服务。 + */ + @Resource + private CourseRunningImportService courseRunningImportService; + + /** + * JSON 序列化工具。 + */ + @Resource + private ObjectMapper objectMapper; + + // ==================== 教学运行数据导入 ==================== + + /** + * 按学期导入课程。 + *

+ * 支持两种导入方式:
+ * 1. JSON 方式:请求 Content-Type 为 application/json,传入 {@link SemesterImportDTO};
+ * 2. Excel 方式:请求 Content-Type 为 multipart/form-data,上传 Excel 文件。
+ * Excel 表头为:年度、学期第次。 + *

+ * + * @param request HTTP 请求对象 + * @return 导入结果 + */ + @PostMapping("/import/semester") + public Result importBySemester(HttpServletRequest request) throws Exception { + String contentType = request.getContentType(); + + if (contentType != null && contentType.toLowerCase().contains("multipart")) { + MultipartFile file = ((MultipartHttpServletRequest) request).getFile("file"); + if (file == null || file.isEmpty()) { + throw new ServiceException("导入文件不能为空", BasicManagementConstants.BAD_REQUEST); + } + List paramsList = ExcelParseUtil.parseCourseSemesterImportExcel( + file.getInputStream(), file.getOriginalFilename()); + if (paramsList.isEmpty()) { + throw new ServiceException("Excel 中无有效导入数据", BasicManagementConstants.BAD_REQUEST); + } + return Result.success("Excel导入成功", + courseRunningImportService.importBySemester(paramsList.get(0))); + } + + SemesterImportDTO dto = objectMapper.readValue(request.getInputStream(), SemesterImportDTO.class); + return Result.success("导入成功", courseRunningImportService.importBySemester(dto)); + } + + /** + * 按教学班次导入课程。 + *

+ * 支持两种导入方式:
+ * 1. JSON 方式:请求 Content-Type 为 application/json,传入 {@link ClassesImportDTO};
+ * 2. Excel 方式:请求 Content-Type 为 multipart/form-data,上传 Excel 文件。
+ * Excel 表头为:教学班次编号、年度。 + *

+ * + * @param request HTTP 请求对象 + * @return 导入结果 + */ + @PostMapping("/import/classes") + public Result importByClasses(HttpServletRequest request) throws Exception { + String contentType = request.getContentType(); + + if (contentType != null && contentType.toLowerCase().contains("multipart")) { + MultipartFile file = ((MultipartHttpServletRequest) request).getFile("file"); + if (file == null || file.isEmpty()) { + throw new ServiceException("导入文件不能为空", BasicManagementConstants.BAD_REQUEST); + } + List paramsList = ExcelParseUtil.parseCourseClassesImportExcel( + file.getInputStream(), file.getOriginalFilename()); + if (paramsList.isEmpty()) { + throw new ServiceException("Excel 中无有效导入数据", BasicManagementConstants.BAD_REQUEST); + } + return Result.success("Excel导入成功", + courseRunningImportService.importByClasses(paramsList.get(0))); + } + + ClassesImportDTO dto = objectMapper.readValue(request.getInputStream(), ClassesImportDTO.class); + return Result.success("导入成功", courseRunningImportService.importByClasses(dto)); + } + + /** + * 导入单门课程。 + *

+ * 支持两种导入方式:
+ * 1. JSON 方式:请求 Content-Type 为 application/json,传入 {@link SingleCourseImportDTO};
+ * 2. Excel 方式:请求 Content-Type 为 multipart/form-data,上传 Excel 文件。
+ * Excel 表头为:课程编号、教学班次编号、年度。 + *

+ * + * @param request HTTP 请求对象 + * @return 导入结果 + */ + @PostMapping("/import/single") + public Result importSingleCourse(HttpServletRequest request) throws Exception { + String contentType = request.getContentType(); + + if (contentType != null && contentType.toLowerCase().contains("multipart")) { + MultipartFile file = ((MultipartHttpServletRequest) request).getFile("file"); + if (file == null || file.isEmpty()) { + throw new ServiceException("导入文件不能为空", BasicManagementConstants.BAD_REQUEST); + } + List paramsList = ExcelParseUtil.parseCourseSingleImportExcel( + file.getInputStream(), file.getOriginalFilename()); + if (paramsList.isEmpty()) { + throw new ServiceException("Excel 中无有效导入数据", BasicManagementConstants.BAD_REQUEST); + } + return Result.success("Excel导入成功", + courseRunningImportService.importSingleCourse(paramsList.get(0))); + } + + SingleCourseImportDTO dto = objectMapper.readValue(request.getInputStream(), SingleCourseImportDTO.class); + return Result.success("导入成功", courseRunningImportService.importSingleCourse(dto)); + } + + /** + * 删除已导入课程。 + *

+ * 删除已导入到课程教学运行管理子系统的课次数据,支持按学期、班次或单个课次删除。 + *

+ * + * @param params 删除参数(包含删除范围、课次编号列表和年度) + * @return 操作结果 + */ + @DeleteMapping("/import/remove") + public Result removeImportedCourses(@Valid @RequestBody ImportRemoveDTO params) { + courseRunningImportService.removeImportedCourses(params); + return Result.success(); + } + + // ==================== 教学实施计划填写 ==================== + + /** + * 分页查询待填写或已填写的课次列表。 + *

+ * 根据查询条件分页查询课次列表,支持按实施课程编号、年度、教学方法、 + * 日期范围和填写状态筛选,返回课次基本信息及填写状态。 + *

+ * + * @param query 查询参数(包含分页信息和筛选条件) + * @return 分页课次列表 + */ + @GetMapping("/plan/list") + public Result> queryLessonList(TeachingPlanQueryDTO query) { + return Result.success(courseRunningImportService.queryLessonList(query)); + } + + /** + * 按当前查询条件导出课次数据为 Excel 文件。 + * + * @param query 查询参数 + * @return Excel 文件下载响应 + */ + @GetMapping("/plan/export") + public ResponseEntity exportLessonList(TeachingPlanQueryDTO query) { + byte[] excelBytes = courseRunningImportService.exportLessonListToExcel(query); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + headers.setContentDispositionFormData("attachment", "课次数据.xlsx"); + + return ResponseEntity.ok() + .headers(headers) + .body(excelBytes); + } + + /** + * 查询单个课次的教学实施计划详情。 + *

+ * 根据课次编号查询该课次的完整教学实施计划信息。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + * @return 教学实施计划详情 + */ + @GetMapping("/plan/detail") + public Result getTeachingPlanDetail(@RequestParam("sskcbbh") String sskcbbh) { + return Result.success(courseRunningImportService.getTeachingPlanDetail(sskcbbh)); + } + + /** + * 单条填写教学实施计划。 + *

+ * 对单个课次填写教学实施计划内容,保存后填写状态更新为"已填写"。 + *

+ * + * @param dto 教学实施计划填写参数 + * @return 填写结果 + */ + @PostMapping("/plan/fill") + public Result fillTeachingPlan(@Valid @RequestBody TeachingPlanFillDTO dto) { + return Result.success("填写成功", courseRunningImportService.fillTeachingPlan(dto)); + } + + /** + * 批量填写教学实施计划。 + *

+ * 一次性提交多个课次的教学实施计划填写数据。 + *

+ * + * @param dto 批量教学实施计划填写参数 + * @return 批量填写结果 + */ + @PostMapping("/plan/fill/batch") + public Result batchFillTeachingPlan(@Valid @RequestBody TeachingPlanBatchFillDTO dto) { + return Result.success("批量填写完成", courseRunningImportService.batchFillTeachingPlan(dto)); + } + + /** + * 提交教学实施计划。 + *

+ * 将已填写的教学实施计划提交审核,提交后填写状态更新为"已提交"。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + * @return 操作结果 + */ + @PostMapping("/plan/submit") + public Result submitTeachingPlan(@RequestParam("sskcbbh") String sskcbbh) { + courseRunningImportService.submitTeachingPlan(sskcbbh); + return Result.success(); + } + + // ==================== 调课管理 ==================== + + /** + * 分页查询调课申请列表。 + *

+ * 根据查询条件分页查询调课申请列表,支持按实施_课程表编号、申请教员、 + * 年度、审批状态、查收状态、删除状态、申请日期范围筛选。 + *

+ * + * @param query 查询参数(包含分页信息和筛选条件) + * @return 分页调课申请列表 + */ + @GetMapping("/adjust/list") + public Result> queryScheduleAdjustList(ScheduleAdjustQueryDTO query) { + return Result.success(courseRunningImportService.queryScheduleAdjustList(query)); + } + + /** + * 查询单个调课申请的完整详情。 + *

+ * 根据调课申请编号查询该申请的完整信息,包括申请信息、调整后的内容、 + * 教研室审批状态、机关审批记录、关联的教室、辅助教员、学员队、保障明细等。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @return 调课申请详情 + */ + @GetMapping("/adjust/detail") + public Result getScheduleAdjustDetail(@RequestParam("ssdksqbh") String ssdksqbh) { + return Result.success(courseRunningImportService.getScheduleAdjustDetail(ssdksqbh)); + } + + /** + * 提交调课申请。 + *

+ * 教员提交调课申请,包含课次编号、调课事由、新的日期节次、新的教室、 + * 辅助教员、学员队、保障明细等完整信息。 + *

+ * + * @param dto 调课申请参数 + * @return 调课申请编号 + */ + @PostMapping("/adjust/submit") + public Result submitScheduleAdjust(@Valid @RequestBody ScheduleAdjustApplyDTO dto) { + return Result.success("申请提交成功", courseRunningImportService.submitScheduleAdjust(dto)); + } + + /** + * 教研室审批调课申请。 + *

+ * 教研室审批教员提交的调课申请,包含同意、发回、拒绝三种处理方式。 + *

+ * + * @param dto 教研室审批参数(auditRole 传 "jys") + * @return 操作结果 + */ + @PostMapping("/adjust/audit/jys") + public Result auditByTeachingOffice(@Valid @RequestBody ScheduleAdjustAuditDTO dto) { + // 自动设置审批角色为教研室 + dto.setAuditRole("jys"); + courseRunningImportService.auditByTeachingOffice(dto); + return Result.success(); + } + + /** + * 教研室查收调课申请。 + *

+ * 教研室查收调课申请,标记查收状态和查收时间。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @param csrbh 查收人编号 + * @return 操作结果 + */ + @PostMapping("/adjust/receive/jys") + public Result receiveByTeachingOffice(@RequestParam("ssdksqbh") String ssdksqbh, + @RequestParam("csrbh") String csrbh) { + courseRunningImportService.receiveByTeachingOffice(ssdksqbh, csrbh); + return Result.success(); + } + + /** + * 教学系审批调课申请。 + *

+ * 教学系审批教研室已同意的调课申请,包含同意、发回、拒绝三种处理方式。 + * 构成三级审批流程的中间环节(教研室→教学系→机关)。 + *

+ * + * @param dto 教学系审批参数(auditRole 传 "jxx") + * @return 操作结果 + */ + @PostMapping("/adjust/audit/jxx") + public Result auditByTeachingDepartment(@Valid @RequestBody ScheduleAdjustAuditDTO dto) { + // 自动设置审批角色为教学系 + dto.setAuditRole("jxx"); + courseRunningImportService.auditByTeachingDepartment(dto); + return Result.success(); + } + + /** + * 机关审批调课申请。 + *

+ * 机关审批教研室已同意的调课申请,包含同意、发回、拒绝三种处理方式。 + *

+ * + * @param dto 机关审批参数(auditRole 传 "jg",需传 jgbh) + * @return 操作结果 + */ + @PostMapping("/adjust/audit/jg") + public Result auditByAuthority(@Valid @RequestBody ScheduleAdjustAuditDTO dto) { + // 自动设置审批角色为机关 + dto.setAuditRole("jg"); + courseRunningImportService.auditByAuthority(dto); + return Result.success(); + } + + /** + * 机关查收调课申请。 + *

+ * 机关查收调课申请,标记查收状态和查收时间。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @param jgbh 机关编号 + * @param csrbh 查收人编号 + * @return 操作结果 + */ + @PostMapping("/adjust/receive/jg") + public Result receiveByAuthority(@RequestParam("ssdksqbh") String ssdksqbh, + @RequestParam("jgbh") String jgbh, + @RequestParam("csrbh") String csrbh) { + courseRunningImportService.receiveByAuthority(ssdksqbh, jgbh, csrbh); + return Result.success(); + } + + /** + * 撤销调课申请。 + *

+ * 教员撤销自己提交的、未经过审批的调课申请。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @param sqjybh 申请教员编号 + * @return 操作结果 + */ + @DeleteMapping("/adjust/cancel") + public Result cancelScheduleAdjust(@RequestParam("ssdksqbh") String ssdksqbh, + @RequestParam("sqjybh") String sqjybh) { + courseRunningImportService.cancelScheduleAdjust(ssdksqbh, sqjybh); + return Result.success(); + } + + // ==================== 调课策略管理 ==================== + + /** + * 查询调课策略列表。 + *

+ * 支持按策略类型筛选,返回所有策略(含停用)。 + *

+ * + * @param cllx 策略类型:TIME_LIMIT=时间段限制,AUTO_APPROVE=免审批,为空时查询全部 + * @return 调课策略列表 + */ + @GetMapping("/adjust/strategy/list") + public Result> listScheduleAdjustStrategies( + @RequestParam(required = false) String cllx) { + return Result.success(courseRunningImportService.listScheduleAdjustStrategies(cllx)); + } + + /** + * 保存调课策略(新增或修改)。 + *

+ * 策略编号为空时执行新增,非空时执行修改。 + *

+ * + * @param dto 策略参数 + * @return 策略编号 + */ + @PostMapping("/adjust/strategy/save") + public Result saveScheduleAdjustStrategy(@Valid @RequestBody ScheduleAdjustStrategyDTO dto) { + return Result.success("保存成功", courseRunningImportService.saveScheduleAdjustStrategy(dto)); + } + + /** + * 删除调课策略。 + * + * @param clbh 策略编号 + * @return 操作结果 + */ + @DeleteMapping("/adjust/strategy/delete") + public Result deleteScheduleAdjustStrategy(@RequestParam("clbh") String clbh) { + courseRunningImportService.deleteScheduleAdjustStrategy(clbh); + return Result.success(); + } + + // ==================== 教学保障管理 ==================== + + /** + * 查询教学保障提供资源列表。 + *

+ * 从保障提供明细表中查询可供选择的保障资源,支持按保障类别编号筛选。 + *

+ * + * @param bzlbbh 保障类别编号,为空时查询全部 + * @return 保障提供资源列表 + */ + @GetMapping("/support/resource") + public Result> querySupportResourceList(@RequestParam(required = false) String bzlbbh) { + return Result.success(courseRunningImportService.querySupportResourceList(bzlbbh)); + } + + /** + * 查询课次的教学保障计划列表。 + *

+ * 根据课次编号查询该课次已登记的教学保障计划明细,包括保障名称、单位、 + * 数量、备注、确认状态及确认信息等。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + * @return 教学保障计划列表 + */ + @GetMapping("/support/plan") + public Result> queryTeachingSupportPlanList(@RequestParam("sskcbbh") String sskcbbh) { + return Result.success(courseRunningImportService.queryTeachingSupportPlanList(sskcbbh)); + } + + /** + * 登记单条教学保障计划。 + *

+ * 为指定课次登记一条教学保障需求,指定保障提供明细、数量、备注等信息。 + *

+ * + * @param dto 教学保障计划登记参数 + * @return 课程表_保障明细编号 + */ + @PostMapping("/support/plan") + public Result saveTeachingSupportPlan(@Valid @RequestBody TeachingSupportPlanDTO dto) { + return Result.success("登记成功", courseRunningImportService.saveTeachingSupportPlan(dto)); + } + + /** + * 批量登记教学保障计划。 + *

+ * 一次性为指定课次登记多条教学保障需求。 + *

+ * + * @param dto 教学保障计划批量登记参数 + * @return 操作结果 + */ + @PostMapping("/support/plan/batch") + public Result batchSaveTeachingSupportPlan(@Valid @RequestBody TeachingSupportBatchPlanDTO dto) { + courseRunningImportService.batchSaveTeachingSupportPlan(dto); + return Result.success(); + } + + /** + * 修改教学保障计划。 + *

+ * 修改已登记的教学保障计划,包括调整数量和备注信息。 + * 已确认的教学保障计划不允许修改,如需修改需先取消确认。 + *

+ * + * @param dto 教学保障计划修改参数 + * @return 操作结果 + */ + @PutMapping("/support/plan") + public Result updateTeachingSupportPlan(@Valid @RequestBody TeachingSupportUpdateDTO dto) { + courseRunningImportService.updateTeachingSupportPlan(dto); + return Result.success(); + } + + /** + * 删除教学保障计划。 + *

+ * 删除已登记的教学保障计划。 + * 已确认的教学保障计划不允许删除,如需删除需先取消确认。 + *

+ * + * @param kcbbzmxbh 课程表_保障明细编号 + * @return 操作结果 + */ + @DeleteMapping("/support/plan") + public Result deleteTeachingSupportPlan(@RequestParam("kcbbzmxbh") String kcbbzmxbh) { + courseRunningImportService.deleteTeachingSupportPlan(kcbbzmxbh); + return Result.success(); + } + + /** + * 确认教学保障到位。 + *

+ * 确认某条教学保障计划已经落实到位,记录确认人、确认时间和确认备注。 + * 已确认的保障计划不允许重复确认。 + *

+ * + * @param dto 教学保障到位确认参数 + * @return 操作结果 + */ + @PostMapping("/support/confirm") + public Result confirmTeachingSupport(@Valid @RequestBody TeachingSupportConfirmDTO dto) { + courseRunningImportService.confirmTeachingSupport(dto); + return Result.success(); + } + + /** + * 批量确认教学保障到位。 + *

+ * 一次性确认多条教学保障计划已经落实到位,逐条处理并返回整体执行结果。 + *

+ * + * @param dtoList 教学保障到位确认参数列表 + * @return 批量确认结果 + */ + @PostMapping("/support/confirm/batch") + public Result batchConfirmTeachingSupport(@Valid @RequestBody List dtoList) { + return Result.success("批量确认完成", courseRunningImportService.batchConfirmTeachingSupport(dtoList)); + } + + /** + * 取消教学保障到位确认。 + *

+ * 取消某条教学保障计划的到位确认,恢复为未确认状态。 + *

+ * + * @param kcbbzmxbh 课程表_保障明细编号 + * @return 操作结果 + */ + @DeleteMapping("/support/confirm") + public Result cancelConfirmTeachingSupport(@RequestParam("kcbbzmxbh") String kcbbzmxbh) { + courseRunningImportService.cancelConfirmTeachingSupport(kcbbzmxbh); + return Result.success(); + } + + /** + * 查询教学保障历史联想列表。 + *

+ * 根据当前课次编号,查询同一实施课程下其他历史课次已登记过的保障物资, + * 按保障提供明细编号聚合去重,统计历史使用次数、最近登记时间和平均数量, + * 供教师在登记新保障时快速联想选择,避免重复录入。 + *

+ * + * @param sskcbbh 当前课次编号(实施_课程表编号) + * @return 历史联想保障物资列表 + */ + @GetMapping("/support/history") + public Result> listTeachingSupportHistory(@RequestParam("sskcbbh") String sskcbbh) { + return Result.success(courseRunningImportService.listTeachingSupportHistory(sskcbbh)); + } + + // ==================== 学员正课请假 ==================== + + /** + * 分页查询学员请假申请列表。 + *

+ * 根据查询条件分页查询学员正课请假申请记录,支持按请假编号、学员编号、 + * 学员姓名、学员队编号、审批状态、年度、创建时间范围等多条件筛选。 + *

+ * + * @param query 查询参数(包含分页信息和筛选条件) + * @return 分页学员请假申请列表 + */ + @PostMapping("/leave/list") + public Result> queryStudentLeaveList(@Valid @RequestBody StudentLeaveQueryDTO query) { + return Result.success(courseRunningImportService.queryStudentLeaveList(query)); + } + + /** + * 查询学员请假申请详情。 + *

+ * 根据请假编号查询该申请的完整信息,包括学员信息、请假时间范围、 + * 审批状态、审批记录及关联课次列表。 + *

+ * + * @param qjbh 请假编号 + * @return 学员请假申请详情 + */ + @GetMapping("/leave/detail") + public Result getStudentLeaveDetail(@RequestParam("qjbh") String qjbh) { + return Result.success(courseRunningImportService.getStudentLeaveDetail(qjbh)); + } + + /** + * 学员提交请假申请。 + *

+ * 学员在线提交正课请假申请,包含请假原因、时间范围、请假类型、 + * 关联课次等信息。系统自动生成请假编号并记录申请时间。 + *

+ * + * @param dto 学员请假申请参数 + * @return 请假编号 + */ + @PostMapping("/leave/apply") + public Result submitStudentLeave(@Valid @RequestBody StudentLeaveApplyDTO dto) { + return Result.success("申请提交成功", courseRunningImportService.submitStudentLeave(dto)); + } + + /** + * 教师审批学员请假申请。 + *

+ * 教师审批学员提交的正课请假申请,包含同意、拒绝、发回修改三种处理方式。 + * 审批通过后联动更新考勤记录。 + *

+ * + * @param dto 教师审批参数 + * @return 操作结果 + */ + @PostMapping("/leave/audit") + public Result auditStudentLeave(@Valid @RequestBody StudentLeaveAuditDTO dto) { + courseRunningImportService.auditStudentLeave(dto); + return Result.success(); + } + + /** + * 学员撤回请假申请。 + *

+ * 学员撤回自己提交的、未经过审批的请假申请。 + *

+ * + * @param qjbh 请假编号 + * @param xybh 学员编号(用于权限校验) + * @return 操作结果 + */ + @DeleteMapping("/leave") + public Result cancelStudentLeave(@RequestParam("qjbh") String qjbh, @RequestParam("xybh") String xybh) { + courseRunningImportService.cancelStudentLeave(qjbh, xybh); + return Result.success(); + } + + /** + * 学员查询自己的请假申请列表。 + *

+ * 学员自助查询接口,只能查看自己提交的请假记录,支持分页和按审批状态筛选。 + *

+ * + * @param xybh 学员编号(必填) + * @param pageNum 页码(必填) + * @param pageSize 每页记录数(必填) + * @param spzt 审批状态(可选:0待审批/1已同意/2已拒绝/3已发回) + * @param qjlx 请假类型(可选) + * @param nd 年度(可选) + * @return 分页学员请假申请列表 + */ + @GetMapping("/leave/my-list") + public Result> queryStudentOwnLeaveList( + @RequestParam("xybh") String xybh, + @RequestParam("pageNum") Integer pageNum, + @RequestParam("pageSize") Integer pageSize, + @RequestParam(value = "spzt", required = false) Integer spzt, + @RequestParam(value = "qjlx", required = false) String qjlx, + @RequestParam(value = "nd", required = false) Integer nd) { + StudentLeaveQueryDTO query = new StudentLeaveQueryDTO(); + query.setXybh(xybh); + query.setPageNum(pageNum); + query.setPageSize(pageSize); + query.setSpzt(spzt); + query.setQjlx(qjlx); + query.setNd(nd); + return Result.success(courseRunningImportService.queryStudentOwnLeaveList(query)); + } + + // ==================== 运行分析 ==================== + + /** + * 获取教学运行概览指标。 + *

+ * 返回教学运行关键指标的汇总数据,包括整体排课完成率、学员缺勤率、 + * 场地平均使用率、教师周均课时等核心指标。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 运行概览指标 + */ + @PostMapping("/analysis/overview") + public Result getOperationOverview(@RequestBody OperationAnalysisQueryDTO query) { + return Result.success(courseRunningImportService.getOperationOverview(query)); + } + + /** + * 获取各教学系排课完成率对比数据。 + *

+ * 返回各教学系的排课完成率,用于柱状图展示。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 各教学系排课完成率列表 + */ + @PostMapping("/analysis/course-completion") + public Result> getCourseCompletionByDept(@RequestBody OperationAnalysisQueryDTO query) { + return Result.success(courseRunningImportService.getCourseCompletionByDept(query)); + } + + /** + * 获取场地使用率分布数据。 + *

+ * 返回不同场地类型的使用率分布情况,用于饼图展示。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 场地使用率分布列表 + */ + @PostMapping("/analysis/venue-usage") + public Result> getVenueUsageRate(@RequestBody OperationAnalysisQueryDTO query) { + return Result.success(courseRunningImportService.getVenueUsageRate(query)); + } + + /** + * 获取学期调课频次趋势数据。 + *

+ * 返回学期内各时间段的调课频次变化趋势,用于折线图展示。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 调课频次趋势列表 + */ + @PostMapping("/analysis/adjust-trend") + public Result> getScheduleAdjustTrend(@RequestBody OperationAnalysisQueryDTO query) { + return Result.success(courseRunningImportService.getScheduleAdjustTrend(query)); + } + + /** + * 导出运行分析报表为 Excel 文件。 + *

+ * 汇总运行概览、各教学系排课完成率、场地使用率分布、调课频次趋势四类数据, + * 分别写入同一 Excel 文件的四个工作表,便于教学管理决策存档与汇报。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return Excel 文件字节数组 + */ + @PostMapping("/analysis/export") + public ResponseEntity exportAnalysisReport(@RequestBody OperationAnalysisQueryDTO query) { + byte[] excelBytes = courseRunningImportService.exportAnalysisToExcel(query); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + headers.setContentDispositionFormData("attachment", "运行分析报表.xlsx"); + + return ResponseEntity.ok() + .headers(headers) + .body(excelBytes); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/CourseTeachingController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/CourseTeachingController.java new file mode 100644 index 000000000..1cd736ac1 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/CourseTeachingController.java @@ -0,0 +1,87 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.KCBZ; +import com.roomroot.jwgl.service.CourseTeachingService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; + +/** + * 课程教学管理控制器 + *

+ * 提供课程标准(KCBZ)的增删改查及多版本管理接口。 + * 删除逻辑:填写撤回原因(非空=已撤回),不物理删除。 + * 查询自动附带课堂列表(ktList)。 + *

+ */ +@RestController +@RequestMapping("/course-teaching") +public class CourseTeachingController { + + @Resource + private CourseTeachingService courseTeachingService; + + /** + * 新增课程标准(ktList 内嵌课堂列表) + */ + @PostMapping("/add") + public Result add(@RequestBody KCBZ kcbz) { + courseTeachingService.add(kcbz); + return Result.success(); + } + + /** + * 撤回(删除)课程标准——填写撤回原因 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh, + @RequestParam("chyy") String chyy) { + courseTeachingService.delete(bh, chyy); + return Result.success(); + } + + /** + * 更新课程标准(ktList 内嵌课堂列表,先删后插) + */ + @PostMapping("/update") + public Result update(@RequestBody KCBZ kcbz) { + courseTeachingService.update(kcbz); + return Result.success(); + } + + /** + * 查询课程标准详情(含课堂列表) + */ + @GetMapping("/get") + public Result getById(@RequestParam("bh") String bh) { + return Result.success(courseTeachingService.getById(bh)); + } + + /** + * 分页查询课程标准列表(每项含课堂列表) + */ + @GetMapping("/list") + public Result> list(PageQuery query, KCBZ kcbz) { + return Result.success(courseTeachingService.pageList(query, kcbz)); + } + + /** + * 查询某课程的所有版本 + */ + @GetMapping("/versions") + public Result> getVersions(@RequestParam("kbh") String kbh) { + return Result.success(courseTeachingService.getVersionsByCourse(kbh)); + } + + /** + * 从课程表自动生成课程标准 + */ + @PostMapping("/generate") + public Result generateFromCourse(@RequestParam("kbh") String kbh) { + String bh = courseTeachingService.generateFromCourse(kbh); + return Result.success("生成成功", bh); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DepartmentManagementController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DepartmentManagementController.java new file mode 100644 index 000000000..94c81056a --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DepartmentManagementController.java @@ -0,0 +1,130 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.department.DepartmentCreateDTO; +import com.roomroot.jwgl.dto.department.DepartmentIdDTO; +import com.roomroot.jwgl.dto.department.DepartmentQueryDTO; +import com.roomroot.jwgl.dto.department.DepartmentUpdateDTO; +import com.roomroot.jwgl.service.DepartmentManagementService; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.vo.department.DepartmentTreeVO; +import com.roomroot.jwgl.vo.department.DepartmentVO; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 部别管理接口。 + */ +@RestController +@RequestMapping("/department") +public class DepartmentManagementController { + + /** + * 部别管理业务服务。 + */ + @Resource + private DepartmentManagementService departmentManagementService; + + /** + * 新增部别。 + * + * @param dto 部别新增参数 + * @return 新增后的部别信息 + */ + @PostMapping("/add") + public Result add(@Valid @RequestBody DepartmentCreateDTO dto) { + // 调用业务层完成新增,并将新增后的 VO 返回给前端。 + return Result.success("新增成功", departmentManagementService.add(dto)); + } + + /** + * 修改部别。 + * + * @param dto 部别修改参数 + * @return 修改后的部别信息 + */ + @PostMapping("/update") + public Result update(@Valid @RequestBody DepartmentUpdateDTO dto) { + // 调用业务层完成修改,并将修改后的 VO 返回给前端。 + return Result.success("修改成功", departmentManagementService.update(dto)); + } + + /** + * 停用部别。 + * + * @param dto 部别编号参数 + * @return 停用后的部别信息 + */ + @PostMapping("/disable") + public Result disable(@Valid @RequestBody DepartmentIdDTO dto) { + // 调用业务层执行逻辑停用,不物理删除数据库记录。 + return Result.success("停用成功", departmentManagementService.disable(dto)); + } + + /** + * 启用部别。 + * + * @param dto 部别编号参数 + * @return 启用后的部别信息 + */ + @PostMapping("/enable") + public Result enable(@Valid @RequestBody DepartmentIdDTO dto) { + // 调用业务层恢复部别的启用状态。 + return Result.success("启用成功", departmentManagementService.enable(dto)); + } + + /** + * 删除部别(逻辑删除)。 + *

删除前会检查下级部别和业务引用,确认无依赖后才执行逻辑删除。

+ * + * @param dto 部别编号参数 + * @return 被删除的部别信息 + */ + @PostMapping("/delete") + public Result delete(@Valid @RequestBody DepartmentIdDTO dto) { + // 调用业务层执行逻辑删除,已删除的部别不再出现在列表和树形结构中。 + return Result.success("删除成功", departmentManagementService.delete(dto)); + } + + /** + * 根据编号查询部别详情。 + * + * @param dto 部别编号参数 + * @return 部别详情 + */ + @GetMapping("/get") + public Result get(DepartmentIdDTO dto) { + // 根据 DTO 中的编号查询并返回部别 VO。 + return Result.success(departmentManagementService.get(dto)); + } + + /** + * 查询部别平铺列表。 + * + * @param dto 查询条件 + * @return 符合条件的部别列表 + */ + @GetMapping("/list") + public Result> list(DepartmentQueryDTO dto) { + // 返回适用于表格展示的平铺数据。 + return Result.success(departmentManagementService.list(dto)); + } + + /** + * 查询部别树。 + * + * @param dto 查询条件 + * @return 符合条件并保留祖先节点的部别树 + */ + @GetMapping("/tree") + public Result> tree(DepartmentQueryDTO dto) { + // 返回适用于树形控件展示的层级数据。 + return Result.success(departmentManagementService.tree(dto)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DepartmentPersonnelController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DepartmentPersonnelController.java new file mode 100644 index 000000000..c3df8e773 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DepartmentPersonnelController.java @@ -0,0 +1,199 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelCreateDTO; +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelIdDTO; +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelImportDTO; +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelQueryDTO; +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelUpdateDTO; +import com.roomroot.jwgl.service.DepartmentPersonnelService; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.utils.BasicManagementConstants; +import com.roomroot.jwgl.vo.departmentpersonnel.DepartmentPersonnelDepartmentVO; +import com.roomroot.jwgl.vo.departmentpersonnel.DepartmentPersonnelVO; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; +import tools.jackson.databind.ObjectMapper; + + +import java.util.List; + +/** + * 部别人员管理接口。 + */ +@RestController +@RequestMapping("/department-personnel") +public class DepartmentPersonnelController { + + /** + * 部别人员管理业务服务。 + */ + @Resource + private DepartmentPersonnelService departmentPersonnelService; + + /** + * JSON 序列化工具。 + */ + @Resource + private ObjectMapper objectMapper; + + /** + * 查询可供人员管理页面选择的启用部别。 + * + * @param dto 部别查询条件 + * @return 启用部别列表 + */ + @GetMapping("/departments") + public Result> departments( + @Valid DepartmentPersonnelQueryDTO dto) { + // 调用业务层过滤停用部别,并返回与数据库实体隔离的部别 VO。 + return Result.success(departmentPersonnelService.listDepartments(dto)); + } + + /** + * 查询部别人员列表。 + * + * @param dto 人员查询条件 + * @return 符合条件的人员列表 + */ + @GetMapping("/list") + public Result> list( + @Valid DepartmentPersonnelQueryDTO dto) { + // 调用业务层应用部别、关键字、离职状态和主官状态筛选。 + return Result.success(departmentPersonnelService.list(dto)); + } + + /** + * 查询部别人员详情。 + * + * @param dto 人员编号参数 + * @return 人员详情 + */ + @GetMapping("/get") + public Result get( + @Valid DepartmentPersonnelIdDTO dto) { + // 按人员编号查询完整信息,并补充所属部别名称和简称。 + return Result.success(departmentPersonnelService.get(dto)); + } + + /** + * 新增部别人员。 + * + * @param dto 人员新增参数 + * @return 新增后的人员信息 + */ + @PostMapping("/add") + public Result add( + @Valid @RequestBody DepartmentPersonnelCreateDTO dto) { + // 调用业务层完成部别状态校验、实体转换和数据库新增。 + return Result.success("新增成功", departmentPersonnelService.add(dto)); + } + + /** + * 修改部别人员,并支持人员调整到其他部别。 + * + * @param dto 人员修改参数 + * @return 修改后的人员信息 + */ + @PostMapping("/update") + public Result update( + @Valid @RequestBody DepartmentPersonnelUpdateDTO dto) { + // 调用业务层更新人员基本信息和所属部别,不在此接口修改离职状态。 + return Result.success("修改成功", departmentPersonnelService.update(dto)); + } + + /** + * 将部别人员标记为离职。 + * + * @param dto 人员编号参数 + * @return 离职后的人员信息 + */ + @PostMapping("/leave") + public Result leave( + @Valid @RequestBody DepartmentPersonnelIdDTO dto) { + // 使用离职状态进行软管理,不物理删除被业务表引用的人员记录。 + return Result.success("离职处理成功", departmentPersonnelService.leave(dto)); + } + + /** + * 将离职人员恢复为在职。 + * + * @param dto 人员编号参数 + * @return 复职后的人员信息 + */ + @PostMapping("/restore") + public Result restore( + @Valid @RequestBody DepartmentPersonnelIdDTO dto) { + // 调用业务层校验所属部别仍然启用后恢复在职状态。 + return Result.success("复职处理成功", departmentPersonnelService.restore(dto)); + } + + /** + * 批量导入部别人员。 + *

+ * 支持两种导入方式:
+ * 1. JSON 方式:请求 Content-Type 为 application/json,传入 {@link DepartmentPersonnelImportDTO};
+ * 2. Excel 方式:请求 Content-Type 为 multipart/form-data,上传 Excel 文件。
+ * 两种方式共用同一 URL,由请求类型自动区分。 + *

+ * + * @param request HTTP 请求对象 + * @return 本次成功导入的人员列表 + */ + @PostMapping("/batch-import") + public Result> batchImport(HttpServletRequest request) throws Exception { + String contentType = request.getContentType(); + + // 判断是否为 multipart/form-data 请求,处理 Excel 文件导入 + if (contentType != null && contentType.toLowerCase().contains("multipart")) { + MultipartFile file = ((MultipartHttpServletRequest) request).getFile("file"); + if (file == null || file.isEmpty()) { + throw new ServiceException("导入文件不能为空", BasicManagementConstants.BAD_REQUEST); + } + return Result.success( + "Excel导入成功", + departmentPersonnelService.importFromExcel( + file.getInputStream(), file.getOriginalFilename())); + } + + // 否则按 JSON 方式处理 + DepartmentPersonnelImportDTO dto = objectMapper.readValue( + request.getInputStream(), DepartmentPersonnelImportDTO.class); + return Result.success( + "批量导入成功", departmentPersonnelService.batchImport(dto)); + } + + /** + * 按当前查询条件导出部别人员数据为 Excel 文件。 + * + * @param dto 导出筛选条件 + * @return Excel 文件下载响应 + */ + @GetMapping("/export") + public ResponseEntity export( + @Valid DepartmentPersonnelQueryDTO dto) { + // 复用列表的统一筛选和排序规则,保证导出与页面结果一致。 + byte[] excelBytes = departmentPersonnelService.exportToExcel(dto); + + // 构建文件下载响应头 + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + headers.setContentDispositionFormData("attachment", "部别人员数据.xlsx"); + + return ResponseEntity.ok() + .headers(headers) + .body(excelBytes); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DictDataController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DictDataController.java new file mode 100644 index 000000000..a73b0a13f --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DictDataController.java @@ -0,0 +1,154 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.dict.DictDataBatchQueryDTO; +import com.roomroot.jwgl.dto.dict.DictDataQueryDTO; +import com.roomroot.jwgl.dto.dict.DictDataSaveDTO; +import com.roomroot.jwgl.service.DictDataService; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.vo.dict.DictDataVO; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Map; + +/** + * 字典数据接口。 + *

+ * 提供字典数据的查询(单类型、分页、批量)、保存(新增/编辑)和删除功能。 + * 字典数据是字典类型下的具体选项,用于前端下拉框、单选、多选等场景。 + *

+ */ +@RestController +@RequestMapping("/dict-data") +public class DictDataController { + + /** + * 字典数据业务服务。 + */ + @Resource + private DictDataService dictDataService; + + /** + * 按类型编码查询全部启用的字典数据。 + *

+ * 返回指定字典类型下所有状态为启用的数据项,按排序号升序排列。 + * 主要用于前端业务页面的下拉框、单选、多选等场景。 + *

+ * + * @param dictTypeCode 字典类型编码 + * @return 该类型下启用的字典数据列表 + */ + @GetMapping("/list/{dictTypeCode}") + public Result> listByType( + @PathVariable String dictTypeCode) { + return Result.success(dictDataService.listByType(dictTypeCode)); + } + + /** + * 分页查询字典数据列表(管理页面用)。 + *

+ * 支持按字典类型编码、关键字(匹配标签或值)、状态进行筛选, + * 按排序号升序排列。可查看启用和停用的全部数据项。 + *

+ * + * @param dto 查询条件,包含分页参数和筛选条件 + * @return 符合条件的字典数据分页结果 + */ + @PostMapping("/page") + public Result> page( + @Valid @RequestBody DictDataQueryDTO dto) { + return Result.success(dictDataService.page(dto)); + } + + /** + * 批量查询多个类型的字典数据。 + *

+ * 一次请求查询多个字典类型的数据,返回结果按类型编码分组。 + * 用于页面初始化时需要同时加载多个下拉框数据的场景,减少前端请求次数。 + *

+ * + * @param dto 批量查询参数,包含字典类型编码列表 + * @return 按类型编码分组的数据字典 + */ + @PostMapping("/batch-list") + public Result>> batchList( + @Valid @RequestBody DictDataBatchQueryDTO dto) { + return Result.success(dictDataService.batchList(dto)); + } + + /** + * 保存字典数据(新增或编辑)。 + *

+ * 当 {@code dto.dictDataId} 为空时执行新增操作, + * 当 {@code dto.dictDataId} 有值时执行编辑操作。 + * 保存时会校验关联的字典类型是否存在, + * 若设置为默认值会自动取消该类型下其他数据的默认标记。 + *

+ * + * @param dto 字典数据保存参数 + * @return 保存后的字典数据详情 + */ + @PostMapping("/save") + public Result save( + @Valid @RequestBody DictDataSaveDTO dto) { + return Result.success("保存成功", dictDataService.save(dto)); + } + + /** + * 删除字典数据。 + *

+ * 根据字典数据ID删除单条记录,删除前会校验数据是否存在。 + *

+ * + * @param dictDataId 字典数据ID + * @return 删除成功提示 + */ + @DeleteMapping("/{dictDataId}") + public Result delete( + @PathVariable Long dictDataId) { + dictDataService.delete(dictDataId); + return Result.success("删除成功", null); + } + + /** + * 获取字典数据的历史版本列表。 + *

+ * 返回该字典数据所有的历史快照信息,用于版本回溯查看。 + *

+ * + * @param dictDataId 字典数据ID + * @return 历史版本快照列表 + */ + @GetMapping("/history/{dictDataId}") + public Result>> getHistory( + @PathVariable Long dictDataId) { + return Result.success(dictDataService.getHistory(dictDataId)); + } + + /** + * 回滚字典数据到指定版本。 + *

+ * 根据目标版本号,从历史快照中恢复数据,并创建新的版本记录。 + *

+ * + * @param dictDataId 字典数据ID + * @param targetVersion 目标版本号 + * @return 回滚后的字典数据详情 + */ + @PostMapping("/rollback") + public Result rollback( + @RequestParam Long dictDataId, + @RequestParam Integer targetVersion) { + return Result.success("回滚成功", dictDataService.rollback(dictDataId, targetVersion)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DictTypeController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DictTypeController.java new file mode 100644 index 000000000..839a779fc --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DictTypeController.java @@ -0,0 +1,121 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.dict.DictTypeQueryDTO; +import com.roomroot.jwgl.dto.dict.DictTypeSaveDTO; +import com.roomroot.jwgl.service.DictTypeService; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.vo.dict.DictTypeVO; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Map; + +/** + * 字典类型接口。 + *

+ * 提供字典类型的分页查询、保存(新增/编辑)和删除功能。 + * 字典类型用于对系统字典进行分类管理,如课程类型、职称等级等。 + *

+ */ +@RestController +@RequestMapping("/dict-type") +public class DictTypeController { + + /** + * 字典类型业务服务。 + */ + @Resource + private DictTypeService dictTypeService; + + /** + * 分页查询字典类型列表。 + *

+ * 支持按关键字(匹配类型编码或名称)、所属分组、状态进行筛选, + * 按创建时间降序排列。 + *

+ * + * @param dto 查询条件,包含分页参数和筛选条件 + * @return 符合条件的字典类型分页结果 + */ + @PostMapping("/page") + public Result> page( + @Valid @RequestBody DictTypeQueryDTO dto) { + return Result.success(dictTypeService.page(dto)); + } + + /** + * 保存字典类型(新增或编辑)。 + *

+ * 当 {@code dto.dictTypeId} 为空时执行新增操作, + * 当 {@code dto.dictTypeId} 有值时执行编辑操作。 + * 新增时校验字典类型编码全局唯一性,编辑时允许修改除编码外的其他字段。 + *

+ * + * @param dto 字典类型保存参数 + * @return 保存后的字典类型详情 + */ + @PostMapping("/save") + public Result save( + @Valid @RequestBody DictTypeSaveDTO dto) { + return Result.success("保存成功", dictTypeService.save(dto)); + } + + /** + * 删除字典类型。 + *

+ * 删除前会校验该字典类型下是否存在关联的字典数据项, + * 若存在数据项则不允许删除,防止误删导致业务数据异常。 + *

+ * + * @param dictTypeId 字典类型ID + * @return 删除成功提示 + */ + @DeleteMapping("/{dictTypeId}") + public Result delete( + @PathVariable Long dictTypeId) { + dictTypeService.delete(dictTypeId); + return Result.success("删除成功", null); + } + + /** + * 获取字典类型的历史版本列表。 + *

+ * 返回该字典类型所有的历史快照信息,用于版本回溯查看。 + *

+ * + * @param dictTypeId 字典类型ID + * @return 历史版本快照列表 + */ + @GetMapping("/history/{dictTypeId}") + public Result>> getHistory( + @PathVariable Long dictTypeId) { + return Result.success(dictTypeService.getHistory(dictTypeId)); + } + + /** + * 回滚字典类型到指定版本。 + *

+ * 根据目标版本号,从历史快照中恢复数据,并创建新的版本记录。 + *

+ * + * @param dictTypeId 字典类型ID + * @param targetVersion 目标版本号 + * @return 回滚后的字典类型详情 + */ + @PostMapping("/rollback") + public Result rollback( + @RequestParam Long dictTypeId, + @RequestParam Integer targetVersion) { + return Result.success("回滚成功", dictTypeService.rollback(dictTypeId, targetVersion)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DisciplineManagementController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DisciplineManagementController.java new file mode 100644 index 000000000..82c8adeb8 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/DisciplineManagementController.java @@ -0,0 +1,120 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.XKZYXX; +import com.roomroot.jwgl.service.DisciplineService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; + +/** + * 学科专业管理控制器 + *

+ * 提供学科、专业的增加、删除(逻辑删除)、更新、查询等功能管理接口。 + * 对应数据表:学科专业信息 + *

+ *

+ * 接口规范:仅使用 GET 和 POST 两种方法,参数通过请求体或查询参数传递,不拼接在路径中。 + *

+ * + * @author management + */ +@RestController +@RequestMapping("/discipline") +public class DisciplineManagementController { + + @Resource + private DisciplineService disciplineService; + + /** + * 新增学科专业 + * + * @param entity 学科专业实体 + * @return 操作结果 + */ + @PostMapping("/add") + public Result add(@RequestBody XKZYXX entity) { + disciplineService.add(entity); + return Result.success(); + } + + /** + * 停用(逻辑删除)学科专业 + *

将指定记录的 停用 字段置为 1(停用=0为启用,停用=1为已停用/删除)。

+ * + * @param bsh 标识号 + * @return 操作结果 + */ + @PostMapping("/disable") + public Result disable(@RequestParam("bsh") String bsh) { + disciplineService.disable(bsh); + return Result.success(); + } + + /** + * 更新学科专业 + * + * @param entity 学科专业实体(含标识号) + * @return 操作结果 + */ + @PostMapping("/update") + public Result update(@RequestBody XKZYXX entity) { + disciplineService.update(entity); + return Result.success(); + } + + /** + * 根据标识号查询学科专业详情 + * + * @param bsh 标识号 + * @return 学科专业详情 + */ + @GetMapping("/get") + public Result getById(@RequestParam("bsh") String bsh) { + XKZYXX entity = disciplineService.getById(bsh); + return Result.success(entity); + } + + /** + * 分页条件查询学科专业列表 + *

+ * 支持模糊查询参数: + *

    + *
  • zYMC — 专业名称(模糊匹配)
  • + *
  • zYFX — 专业方向(模糊匹配)
  • + *
+ *

+ * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param zymc 专业名称(可选) + * @param zyfx 专业方向(可选) + * @return 分页结果 + */ + + @GetMapping("/list") + public Result> list( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String zymc, + @RequestParam(required = false) String zyfx, + @RequestParam(required = false) String zydm, + @RequestParam(required = false) Integer ty, + @RequestParam(required = false) String pxlx2, + @RequestParam(required = false) String xnz) { + PageQuery query = new PageQuery(); + query.setPageNum(pageNum); + query.setPageSize(pageSize); + XKZYXX cond = new XKZYXX(); + cond.setZymc(zymc); + cond.setZyfx(zyfx); + cond.setZydm(zydm); + cond.setTy(ty); + cond.setPxlx2(pxlx2); + cond.setXnz(xnz); + PageResult pageResult = disciplineService.pageList(query, cond); + return Result.success(pageResult); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ElectiveController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ElectiveController.java new file mode 100644 index 000000000..d19e1386a --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ElectiveController.java @@ -0,0 +1,373 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.elective.AddClassDTO; +import com.roomroot.jwgl.entity.JSB; +import com.roomroot.jwgl.entity.JXLB; +import com.roomroot.jwgl.entity.XYDB; +import com.roomroot.jwgl.entity.XYDNDXQJBXXB; +import com.roomroot.jwgl.entity.XYDQB; +import com.roomroot.jwgl.entity.XYXX; +import com.roomroot.jwgl.service.ElectiveService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.vo.elective.*; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 教学班次管理控制器 + *

+ * 提供教学班次(学员队表)的完整管理功能,包括: + *

    + *
  • 教学班次的增加、删除(停用)、更新、查询
  • + *
  • 支持选修课临时教学班次的管理(通过学员队类型 xydlx 区分)
  • + *
  • 教学班与管理班的区别管理
  • + *
  • 学员自行注册开关设置
  • + *
  • 学号前缀和学号自动累加位数配置
  • + *
+ * 涉及数据表:学员队表(XYDB)、学员队期表(XYDQB)。 + *

+ */ +@RestController +@RequestMapping("/elective") +public class ElectiveController { + + @Resource + private ElectiveService electiveService; + + // ======================== 教学班次 CRUD ======================== + + /** + * 新增教学班次(学员队) + *

+ * 创建一个新的教学班次,支持设置名称、专业、年级、计划人数、 + * 任务类别(规划内/其他)、入学日期、毕业日期等信息。 + *

+ * @return 操作结果 + */ + @PostMapping("/add") + public Result add(@RequestBody AddClassDTO dto) { + electiveService.add(dto); + return Result.success(); + } + + /** + * 删除教学班次(物理删除) + *

+ * 直接删除指定教学班次及其关联的学员队期记录(学号规则、注册设置等)。 + *

+ * + * @param xydbh 学员队编号 + * @return 操作结果 + */ + @PostMapping("/disable") + public Result disable(@RequestParam("xydbh") String xydbh) { + electiveService.disable(xydbh); + return Result.success(); + } + + /** + * 更新教学班次信息 + *

+ * 支持修改班次名称、专业、年级、计划人数、任务类别、 + * 入学日期、毕业日期等信息。 + *

+ * + * @param entity 教学班次实体(需含学员队编号) + * @return 操作结果 + */ + @PostMapping("/update") + public Result update(@RequestBody XYDB entity) { + electiveService.update(entity); + return Result.success(); + } + + /** + * 根据编号查询教学班次详情 + * + * @param xydbh 学员队编号 + * @return 教学班次信息 + */ + @GetMapping("/get") + public Result getById(@RequestParam("xydbh") String xydbh) { + return Result.success(electiveService.getById(xydbh)); + } + + /** + * 分页条件查询教学班次列表(双表联查) + *

+ * 左连接学员队期表,展示字段包括: + *

    + *
  • 名称、专业、年级、计划人数
  • + *
  • 任务类别(规划内、其他)
  • + *
  • 入学日期、毕业日期
  • + *
  • 学员学号前缀、学员学号自动累加位数
  • + *
  • 是否允许学员自行注册开关
  • + *
+ *

+ * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param xydmc 班次名称(模糊匹配,可选) + * @param nj 年级(精确匹配,可选) + * @param zydh 专业代号(精确匹配,可选) + * @param rwlb 任务类别(规划内/其他,可选) + * @param xydlx 学员队类型(教学班/管理班/选修班,可选) + * @return 分页结果(含学号规则、注册开关) + */ + @GetMapping("/list") + public Result> list( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String xydmc, + @RequestParam(required = false) String nj, + @RequestParam(required = false) String zydh, + @RequestParam(required = false) String rwlb, + @RequestParam(required = false) String xydlx, + @RequestParam(required = false) String xq, + @RequestParam(required = false) String pxrwbsh, + @RequestParam(required = false) Integer xslx, + @RequestParam(required = false) String zxzt, + @RequestParam(required = false) String pxlx, + @RequestParam(required = false) String pxcc, + @RequestParam(required = false) String pxlx2, + @RequestParam(required = false) String rxrqStart, + @RequestParam(required = false) String rxrqEnd, + @RequestParam(required = false) String byrqStart, + @RequestParam(required = false) String byrqEnd) { + PageQuery query = new PageQuery(pageNum, pageSize); + ElectiveClassVO cond = new ElectiveClassVO(); + cond.setXydmc(xydmc); + cond.setNj(nj); + cond.setZydh(zydh); + cond.setRwlb(rwlb); + cond.setXydlx(xydlx); + cond.setXq(xq); + cond.setPxrwbsh(pxrwbsh); + cond.setXslx(xslx); + cond.setZxzt(zxzt); + cond.setPxlx(pxlx); + cond.setPxcc(pxcc); + cond.setPxlx2(pxlx2); + if (rxrqStart != null) cond.setRxrqStart(java.time.LocalDateTime.parse(rxrqStart + "T00:00:00")); + if (rxrqEnd != null) cond.setRxrqEnd(java.time.LocalDateTime.parse(rxrqEnd + "T23:59:59")); + if (byrqStart != null) cond.setByrqStart(java.time.LocalDateTime.parse(byrqStart + "T00:00:00")); + if (byrqEnd != null) cond.setByrqEnd(java.time.LocalDateTime.parse(byrqEnd + "T23:59:59")); + return Result.success(electiveService.pageListWithPeriod(query, cond)); + } + + // ======================== 学员队期/注册设置 ======================== + + /** + * 查询教学班次的学号规则与注册设置 + *

+ * 返回指定班次在学员队期表中的附加信息,包括: + * 学号前缀、学号自动累加位数、允许自行注册开关等。 + *

+ * + * @param xydbh 学员队编号 + * @return 学员队期信息(含学号规则、注册设置) + */ + @GetMapping("/period/get") + public Result getPeriod(@RequestParam("xydbh") String xydbh) { + return Result.success(electiveService.getPeriodByXydbh(xydbh)); + } + + /** + * 更新教学班次的学号规则与注册设置 + *

+ * 支持修改学号前缀(xhqz)、学号自动累加位数(xhws)、 + * 入学日期、毕业日期、允许注册开关(yxzc)等配置。 + *

+ * + * @param entity 学员队期实体(需含编号) + * @return 操作结果 + */ + @PostMapping("/period/update") + public Result updatePeriod(@RequestBody XYDQB entity) { + electiveService.updatePeriod(entity); + return Result.success(); + } + + /** + * 生成下一个学员学号 + *

+ * 格式:入学年份年月日 + 三位流水号,如 20240301001、202403010002。 + * 流水号位数通过学员队期表的学号自动累加位数(xhws)控制,默认3位。 + *

+ * + * @param xydbh 学员队编号 + * @return 生成的学号 + */ + @GetMapping("/generate-student-number") + public Result generateStudentNumber(@RequestParam("xydbh") String xydbh) { + return Result.success(electiveService.generateNextStudentNumber(xydbh)); + } + /** + * 设置是否允许学员自行注册 + *

+ * 控制该教学班次是否允许学员通过网络自行注册。 + * 0 = 关闭(不允许注册),1 = 开启(允许注册)。 + *

+ * + * @param xydbh 学员队编号 + * @param enabled 注册开关(0-关闭,1-开启) + * @return 操作结果 + */ + @PostMapping("/period/set-registration") + public Result setRegistration( + @RequestParam("xydbh") String xydbh, + @RequestParam("enabled") Integer enabled) { + electiveService.setRegistrationEnabled(xydbh, enabled); + return Result.success(); + } + + // ======================== 关联信息查询 ======================== + + /** + * 查询教学班次的课程列表 + *

通过实施_课程学员队关联查询班次的课程安排。

+ * + * @param xydbh 学员队编号 + * @param nd 年度(可选) + * @return 课程列表 + */ + @GetMapping("/courses") + public Result> getCourses( + @RequestParam("xydbh") String xydbh, + @RequestParam(required = false) Integer nd) { + return Result.success(electiveService.getClassCourses(xydbh, nd)); + } + + /** + * 查询教学班次的学期课程表 + * + * @param xydbh 学员队编号 + * @param nd 年度(可选) + * @return 课程表列表 + */ + @GetMapping("/schedule") + public Result> getSchedule( + @RequestParam("xydbh") String xydbh, + @RequestParam(required = false) Integer nd) { + return Result.success(electiveService.getClassSchedule(xydbh, nd)); + } + + /** + * 查询教学班次的学员列表 + * + * @param xydbh 学员队编号 + * @return 学员列表 + */ + @GetMapping("/students") + public Result> getStudents(@RequestParam("xydbh") String xydbh) { + return Result.success(electiveService.getClassStudents(xydbh)); + } + + /** + * 查询教学班次的学员成绩 + * + * @param xydbh 学员队编号 + * @param kmbh 科目编号(可选,按科目筛选) + * @return 成绩列表 + */ + @GetMapping("/grades") + public Result> getGrades( + @RequestParam("xydbh") String xydbh, + @RequestParam(required = false) String kmbh) { + return Result.success(electiveService.getClassGrades(xydbh, kmbh)); + } + + /** + * 查询教学班次的学员缺勤记录 + *

关联学员教学请假表和请假学员表,返回请假学员明细。

+ * + * @param xydbh 学员队编号 + * @param nd 年度(可选) + * @return 缺勤记录列表(含学员姓名学号) + */ + @GetMapping("/absences") + public Result> getAbsences( + @RequestParam("xydbh") String xydbh, + @RequestParam(required = false) Integer nd) { + return Result.success(electiveService.getClassAbsences(xydbh, nd)); + } + + // ======================== 教学班次学期信息设置 ======================== + + /** + * 查询教学楼列表(教学班次学期信息设置窗口 - 教学楼下拉框) + * + * @return 教学楼列表 + */ + @GetMapping("/semester/buildings") + public Result> semesterBuildings() { + return Result.success(electiveService.listBuildings()); + } + + /** + * 查询教室列表(教学班次学期信息设置窗口 - 专用教室选择) + *

支持按教学楼代号过滤;在【查找教室】输入框输入教室名称拼音首字母可快速查找。

+ * + * @param jxldh 教学楼代号(可选) + * @param py 教室名称拼音首字母(可选) + * @return 教室列表 + */ + @GetMapping("/semester/rooms") + public Result> semesterRooms( + @RequestParam(required = false) String jxldh, + @RequestParam(required = false) String py) { + return Result.success(electiveService.listRooms(jxldh, py)); + } + + /** + * 获取教学班次学期信息设置窗口的初始化默认值 + *

+ * 根据班次学制自动给出默认值: + *

    + *
  • 学制半年以内:学期第次固定为第一学期,开学日期=入学日期,结束日期=毕业日期
  • + *
  • 学制半年以上:按入学日期智能推断当前学期第次(可手动指定), + * 开学/结束日期默认取窗口预设日期
  • + *
  • 第二以上学期:默认预设上学期的专用教室
  • + *
+ *

+ * + * @param xydbh 班次编号(学员队编号) + * @param presetKxrq 窗口预设开学日期(yyyy-MM-dd,可选) + * @param presetJsrq 窗口预设结束日期(yyyy-MM-dd,可选) + * @return 初始化默认值 + */ + @GetMapping("/semester/init") + public Result semesterInit( + @RequestParam("xydbh") String xydbh, + @RequestParam(required = false) String presetKxrq, + @RequestParam(required = false) String presetJsrq) { + return Result.success(electiveService.getSemesterInit(xydbh, presetKxrq, presetJsrq)); + } + + /** + * 创建教学班次学期信息(教学班次学期信息设置窗口 - 确定按钮) + *

+ * 服务端自动处理: + *

    + *
  • 学制半年以内:学期第次强制为第一学期(传入其他值校验失败), + * 开学/结束日期为空时默认取班次的入学/毕业日期
  • + *
  • 学制半年以上:学期第次为空时按入学日期智能推断; + * 开学/结束日期使用窗口预设值,不允许为空
  • + *
  • 第二以上学期:专用教室为空时默认预设上学期的专用教室
  • + *
  • 同一班次+年度+学期第次已存在时拒绝重复创建
  • + *
+ *

+ * + * @param xydndxqjbxxb 班次学期信息(必填:学员队编号;可选:年度、学期第次、开学/结束日期、专用教室编号、节次类别、备注等) + * @return 操作结果 + */ + @PostMapping("/semester/add") + public Result semesterAdd(@RequestBody XYDNDXQJBXXB xydndxqjbxxb) { + electiveService.addSemester(xydndxqjbxxb); + return Result.success(); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/FacultyController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/FacultyController.java new file mode 100644 index 000000000..2a51e1763 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/FacultyController.java @@ -0,0 +1,247 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.faculty.AddTeacherDTO; +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.service.FacultyService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.vo.faculty.TeacherListVO; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; + +/** + * 教研室与教师信息管理控制器 + *

+ * 提供教研室和教师信息的增加、删除(停用/离职)、更新、查询等管理接口。 + * 覆盖教研室管理、教员档案管理、教员属性维护、年度综合评定查询、课程表查询等功能。 + *

+ *

+ * 接口规范:仅使用 GET 和 POST 两种方法,参数通过请求体或查询参数传递,不拼接在路径中。 + *

+ * + * @author management + */ +@RestController +@RequestMapping("/faculty") +public class FacultyController { + + @Resource + private FacultyService facultyService; + + // ======================== 教研室管理 ======================== + + /** + * 新增教研室 + */ + @PostMapping("/office/add") + public Result addOffice(@RequestBody JYSB entity) { + facultyService.addJYSB(entity); + return Result.success(); + } + + /** + * 停用(逻辑删除)教研室 + * + * @param jysdh 教研室代号 + */ + @PostMapping("/office/disable") + public Result disableOffice(@RequestParam("jysdh") String jysdh) { + facultyService.disableJYSB(jysdh); + return Result.success(); + } + + /** + * 更新教研室 + */ + @PostMapping("/office/update") + public Result updateOffice(@RequestBody JYSB entity) { + facultyService.updateJYSB(entity); + return Result.success(); + } + + /** + * 根据代号查询教研室详情 + * + * @param jysdh 教研室代号 + */ + @GetMapping("/office/get") + public Result getOffice(@RequestParam("jysdh") String jysdh) { + JYSB entity = facultyService.getJYSBById(jysdh); + return Result.success(entity); + } + + /** + * 分页条件查询教研室列表 + *

+ * 支持模糊查询参数: + *

    + *
  • jysmc — 教研室名称(模糊匹配)
  • + *
  • bx — 部系(模糊匹配)
  • + *
+ *

+ */ + @GetMapping("/office/list") + public Result> listOffice( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String jysdh, + @RequestParam(required = false) String jysmc, + @RequestParam(required = false) String bx) { + PageQuery query = new PageQuery(); + query.setPageNum(pageNum); + query.setPageSize(pageSize); + JYSB cond = new JYSB(); + cond.setJysdh(jysdh); + cond.setJysmc(jysmc); + cond.setBx(bx); + PageResult pageResult = facultyService.pageJYSB(query, cond); + return Result.success(pageResult); + } + + // ======================== 教员管理 ======================== + + /** + * 新增教员(同时写入教员表和教员属性表) + *

教员属性中的非空字段由前端一并传入。

+ */ + @PostMapping("/teacher/add") + public Result addTeacher(@RequestBody AddTeacherDTO dto) { + facultyService.addJYB(dto); + return Result.success(); + } + + /** + * 离职(逻辑删除)教员 + * + * @param jybh 教员编号 + */ + @PostMapping("/teacher/disable") + public Result disableTeacher(@RequestParam("jybh") String jybh) { + facultyService.disableJYB(jybh); + return Result.success(); + } + + /** + * 更新教员基本信息 + *

支持教学单位调整(修改教研室代号)、职称调整(修改职称、专业技术职务等级)。

+ */ + @PostMapping("/teacher/update") + public Result updateTeacher(@RequestBody JYB entity) { + facultyService.updateJYB(entity); + return Result.success(); + } + + /** + * 根据编号查询教员详情 + * + * @param jybh 教员编号 + */ + @GetMapping("/teacher/get") + public Result getTeacher(@RequestParam("jybh") String jybh) { + JYB entity = facultyService.getJYBById(jybh); + return Result.success(entity); + } + + /** + * 分页条件查询教员列表 + *

+ * 支持模糊查询参数: + *

    + *
  • jyxm — 教员姓名(模糊匹配)
  • + *
  • jysdh — 教研室代号(精确匹配)
  • + *
  • zc — 职称(模糊匹配)
  • + *
  • jylb — 教员类别(精确匹配)
  • + *
+ *

+ */ + @GetMapping("/teacher/list") + public Result> listTeacher( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String jyxm, + @RequestParam(required = false) String jysdh, + @RequestParam(required = false) String zc, + @RequestParam(required = false) String jylb) { + PageQuery query = new PageQuery(); + query.setPageNum(pageNum); + query.setPageSize(pageSize); + JYB cond = new JYB(); + cond.setJyxm(jyxm); + cond.setJysdh(jysdh); + cond.setZc(zc); + cond.setJylb(jylb); + PageResult pageResult = facultyService.pageJYBDetail(query, cond); + return Result.success(pageResult); + } + + // ======================== 教员属性管理 ======================== + + /** + * 查询教员属性 + * + * @param jybh 教员编号 + */ + @GetMapping("/teacher/attribute/get") + public Result getTeacherAttribute(@RequestParam("jybh") String jybh) { + JYSX entity = facultyService.getJYSXByJYBH(jybh); + return Result.success(entity); + } + + /** + * 更新教员属性 + *

支持岗位调整(修改职务、职务时间)和技术等级调整(修改技术等级、技术等级时间)。

+ */ + @PostMapping("/teacher/attribute/update") + public Result updateTeacherAttribute(@RequestBody JYSX entity) { + facultyService.updateJYSX(entity); + return Result.success(); + } + + // ======================== 年度综合评定查询 ======================== + + /** + * 分页查询年度综合评定_教员 + */ + @GetMapping("/evaluation/list") + public Result> listEvaluation( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String jybh, + @RequestParam(required = false) String ndzhpdjysbh) { + PageQuery query = new PageQuery(); + query.setPageNum(pageNum); + query.setPageSize(pageSize); + NDZHPDJY cond = new NDZHPDJY(); + cond.setJybh(jybh); + cond.setNdzhpd_jysbh(ndzhpdjysbh); + PageResult pageResult = facultyService.pageNDZHPDJY(query, cond); + return Result.success(pageResult); + } + + // ======================== 教员课程表查询 ======================== + + /** + * 分页查询教员输出课程表 + *

+ * 支持按年度和教员编号筛选。 + *

+ */ + @GetMapping("/timetable/list") + public Result> listTimetable( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) Integer nd, + @RequestParam(required = false) String jybh) { + PageQuery query = new PageQuery(); + query.setPageNum(pageNum); + query.setPageSize(pageSize); + JYSCKCB cond = new JYSCKCB(); + cond.setNd(nd); + cond.setJybh(jybh); + PageResult pageResult = facultyService.pageJYSCKCB(query, cond); + return Result.success(pageResult); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBController.java new file mode 100644 index 000000000..a15091370 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/KCBController.java @@ -0,0 +1,72 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.KCB; +import com.roomroot.jwgl.service.KCBService; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 课程表控制器 + */ +@RestController +@RequestMapping("/kcb") +public class KCBController { + + @Resource + private KCBService kcbService; + + /** + * 新增 + */ + @PostMapping("/add") + public Result add(@RequestBody KCB kcb) { + kcbService.add(kcb); + return Result.success(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("kbh") String kbh) { + kcbService.delete(kbh); + return Result.success(); + } + + /** + * 批量删除 + */ + @PostMapping("/batchDelete") + public Result batchDelete(@RequestBody List kbhList) { + kcbService.batchDelete(kbhList); + return Result.success(); + } + + /** + * 更新 + */ + @PostMapping("/update") + public Result update(@RequestBody KCB kcb) { + kcbService.update(kcb); + return Result.success(); + } + + /** + * 根据课编号查询 + */ + @GetMapping("/get") + public Result getByKbh(@RequestParam("kbh") String kbh) { + return Result.success(kcbService.getByKbh(kbh)); + } + + /** + * 查询所有数据 + */ + @GetMapping("/list") + public Result> list() { + return Result.success(kcbService.list()); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LeaveController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LeaveController.java new file mode 100644 index 000000000..d97fd1c2b --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LeaveController.java @@ -0,0 +1,134 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.LeaveDTO; +import com.roomroot.jwgl.dto.LeaveQueryDTO; +import com.roomroot.jwgl.entity.XYJXQJB; +import com.roomroot.jwgl.entity.XYQJSPB; +import com.roomroot.jwgl.service.LeaveService; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 请假控制器 + */ +@RestController +@RequestMapping("/leave") +public class LeaveController { + + @Resource + private LeaveService leaveService; + + // ===== 请假主表 ===== + + /** + * 新增请假(主表+课堂+学员) + */ + @PostMapping("/add") + public Result add(@RequestBody LeaveDTO dto) { + leaveService.add(dto); + return Result.success(); + } + + /** + * 更新请假主表 + */ + @PostMapping("/update") + public Result update(@RequestBody XYJXQJB xyjxqjb) { + if (!leaveService.update(xyjxqjb)) { + return Result.error("该请假已有审批记录,不允许修改"); + } + return Result.success(); + } + + /** + * 删除请假(级联删除课堂+学员+审批) + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + if (!leaveService.delete(bh)) { + return Result.error("该请假已有审批记录,不允许删除"); + } + return Result.success(); + } + + /** + * 根据编号查询请假 + */ + @GetMapping("/get") + public Result getByBh(@RequestParam("bh") String bh) { + return Result.success(leaveService.getByBh(bh)); + } + + /** + * 查询所有请假 + * 支持按编号、名称、状态、学员队编号查询,起始时间、结束时间、创建时间支持范围查询 + */ + @GetMapping("/list") + public Result> list(LeaveQueryDTO queryDTO) { + return Result.success(leaveService.list(queryDTO)); + } + + /** + * 根据请假编号查询课堂编号列表 + */ + @GetMapping("/classrooms") + public Result> listClassrooms(@RequestParam("qjbh") String qjbh) { + return Result.success(leaveService.listClassroomBhsByQjbh(qjbh)); + } + + /** + * 根据请假编号查询学员编号列表 + */ + @GetMapping("/students") + public Result> listStudents(@RequestParam("qjbh") String qjbh) { + return Result.success(leaveService.listStudentBhsByQjbh(qjbh)); + } + + // ===== 审批表 ===== + + /** + * 新增审批 + */ + @PostMapping("/approval/add") + public Result addApproval(@RequestBody XYQJSPB xyqjspb) { + leaveService.addApproval(xyqjspb); + return Result.success(); + } + + /** + * 删除审批 + */ + @PostMapping("/approval/delete") + public Result deleteApproval(@RequestParam("bh") String bh) { + leaveService.deleteApproval(bh); + return Result.success(); + } + + /** + * 更新审批 + */ + @PostMapping("/approval/update") + public Result updateApproval(@RequestBody XYQJSPB xyqjspb) { + leaveService.updateApproval(xyqjspb); + return Result.success(); + } + + /** + * 根据编号查询审批 + */ + @GetMapping("/approval/get") + public Result getApproval(@RequestParam("bh") String bh) { + return Result.success(leaveService.getApprovalByBh(bh)); + } + + /** + * 根据请假编号查询审批列表 + */ + @GetMapping("/approval/list") + public Result> listApprovals(@RequestParam("qjbh") String qjbh) { + return Result.success(leaveService.listApprovalsByQjbh(qjbh)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LogController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LogController.java new file mode 100644 index 000000000..102189501 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LogController.java @@ -0,0 +1,883 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.service.LogService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.vo.log.TeacherHoursVO; +import com.roomroot.jwgl.vo.log.TeachingLogListVO; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 教学日志管理控制器 + *

+ * 覆盖教学日志核心管理、课时核算标准、课时补助标准、联教联训、教师工作量等全功能。 + * 接口路径前缀:/log + *

+ */ +@RestController +@RequestMapping("/log") +public class LogController { + + @Resource private LogService logService; + + // ======================== 教学日志核心 ======================== + + /** + * 新增教学日志 + *

记录单次课堂教学情况,包含授课信息、学员出勤、教学效果等。

+ * + * @param entity 教学日志实体(班次课堂教学日志) + * @return 操作结果 + */ + @PostMapping("/teaching-log/add") + public Result addLog(@RequestBody SSKCB_RZ entity) { + logService.addLog(entity); + return Result.success(); + } + + /** + * 分页条件查询教学日志列表(待上报视图) + *

未指定状态 rzzt 时默认过滤已上报、已审核,仅展示系统创建/已退回的教学日志(含历史空状态);指定状态时按状态精确过滤。

+ */ + @GetMapping("/teaching-log/list") + public Result> listTeachingLog( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String ksrq, + @RequestParam(required = false) String jsrq, + @RequestParam(required = false) String kcmc, + @RequestParam(required = false) String jys, + @RequestParam(required = false) String jxff, + @RequestParam(required = false) String skjy, + @RequestParam(required = false) String rzzt, + @RequestParam(required = false) String pxqb, + @RequestParam(required = false) String gdqk, + @RequestParam(required = false) String gdxxqk, + @RequestParam(required = false) String rwlb) { + PageQuery query = new PageQuery(pageNum, pageSize); + TeachingLogListVO cond = buildTeachingLogCond(ksrq, jsrq, kcmc, jys, jxff, skjy, rzzt, pxqb, gdqk, gdxxqk, rwlb); + return Result.success(logService.pageTeachingLogDetail(query, cond)); + } + + /** + * 分页条件查询已上报的全部教学日志列表 + *

查询条件与 /teaching-log/list 一致,状态固定为已上报。

+ */ + @GetMapping("/teaching-log/reported/list") + public Result> listReportedTeachingLog( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String ksrq, + @RequestParam(required = false) String jsrq, + @RequestParam(required = false) String kcmc, + @RequestParam(required = false) String jys, + @RequestParam(required = false) String jxff, + @RequestParam(required = false) String skjy, + @RequestParam(required = false) String rzzt, + @RequestParam(required = false) String pxqb, + @RequestParam(required = false) String gdqk, + @RequestParam(required = false) String gdxxqk, + @RequestParam(required = false) String rwlb) { + PageQuery query = new PageQuery(pageNum, pageSize); + TeachingLogListVO cond = buildTeachingLogCond(ksrq, jsrq, kcmc, jys, jxff, skjy, rzzt, pxqb, gdqk, gdxxqk, rwlb); + cond.setZt("已上报"); + return Result.success(logService.pageTeachingLogDetail(query, cond)); + } + + /** + * 分页条件查询已审核的教学日志列表 + *

查询条件与 /teaching-log/list 一致,状态固定为已审核。

+ */ + @GetMapping("/teaching-log/audited/list") + public Result> listAuditedTeachingLog( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String ksrq, + @RequestParam(required = false) String jsrq, + @RequestParam(required = false) String kcmc, + @RequestParam(required = false) String jys, + @RequestParam(required = false) String jxff, + @RequestParam(required = false) String skjy, + @RequestParam(required = false) String rzzt, + @RequestParam(required = false) String pxqb, + @RequestParam(required = false) String gdqk, + @RequestParam(required = false) String gdxxqk, + @RequestParam(required = false) String rwlb) { + PageQuery query = new PageQuery(pageNum, pageSize); + TeachingLogListVO cond = buildTeachingLogCond(ksrq, jsrq, kcmc, jys, jxff, skjy, rzzt, pxqb, gdqk, gdxxqk, rwlb); + cond.setZt("已审核"); + return Result.success(logService.pageTeachingLogDetail(query, cond)); + } + + /** + * 批量上报教学日志 + *

根据主键编号组(bh)将状态修改为已上报,仅对待上报(系统创建/已退回)数据生效,返回实际更新条数。

+ */ + @PostMapping("/teaching-log/batch-report") + public Result batchReportLogs(@RequestBody List bhList) { + return Result.success(logService.batchReportLogs(bhList)); + } + + /** + * 批量审核教学日志 + *

根据主键编号组(bh)将状态修改为已审核,仅对已上报数据生效,返回实际更新条数。

+ */ + @PostMapping("/teaching-log/batch-audit") + public Result batchAuditLogs(@RequestBody List bhList) { + return Result.success(logService.batchAuditLogs(bhList)); + } + + /** + * 构建教学日志列表查询条件(参数与 /teaching-log/list 保持一致) + */ + private TeachingLogListVO buildTeachingLogCond(String ksrq, String jsrq, String kcmc, String jys, String jxff, String skjy, String rzzt, String pxqb, String gdqk, String gdxxqk, String rwlb) { + TeachingLogListVO cond = new TeachingLogListVO(); + cond.setKcmc(kcmc); cond.setJys(jys); cond.setJsfs(jxff); + cond.setSkjy(skjy); cond.setZt(rzzt); cond.setPxqbxx(pxqb); + cond.setGdqk(gdqk); cond.setGdxxqk(gdxxqk); cond.setRwlb(rwlb); + if (gdqk != null && !gdqk.isEmpty()) cond.setGdqkList(java.util.Arrays.asList(gdqk.split(","))); + if (gdxxqk != null && !gdxxqk.isEmpty()) cond.setGdxxqkList(java.util.Arrays.asList(gdxxqk.split(","))); + if (ksrq != null && !ksrq.isEmpty()) cond.setKsrq(java.time.LocalDate.parse(ksrq).atStartOfDay()); + if (jsrq != null && !jsrq.isEmpty()) cond.setJsrq(java.time.LocalDate.parse(jsrq).atTime(23, 59, 59)); + return cond; + } + + /** + * 退回教学日志 + */ + @PostMapping("/teaching-log/reject") + public Result rejectLog( + @RequestParam("bh") String bh, + @RequestParam("thyj") String thyj) { + logService.rejectLog(bh, thyj); + return Result.success(); + } + + /** + * 根据课表自动创建教学日志 + */ + @PostMapping("/teaching-log/auto-create") + public Result autoCreateLog(@RequestParam("tybh") String tybh) { + return Result.success(logService.autoCreateLog(tybh)); + } + + /** + * 批量导入教学日志 + */ + @PostMapping("/teaching-log/import") + public Result importLog(@RequestBody List list) { + logService.importLog(list); + return Result.success(); + } + + @PostMapping("/teaching-log/import-excel") + public Result importLogExcel(@RequestParam("file") MultipartFile file) { + logService.importLogFromExcel(file); + return Result.success(); + } + + /** + * 导出教学日志 Excel + */ + @GetMapping("/teaching-log/export") + public void exportLog(TeachingLogListVO cond, HttpServletResponse response) { + logService.exportLogExcel(cond, response); + } + + /** + * 下载导入模板 + */ + @GetMapping("/teaching-log/download-template") + public void downloadTemplate(HttpServletResponse response) { + try { + java.io.InputStream is = getClass().getClassLoader().getResourceAsStream("template/导入教学日志模板.xls"); + if (is == null) { + response.setStatus(404); + return; + } + response.setContentType("application/vnd.ms-excel"); + response.setHeader("Content-Disposition", "attachment; filename=导入教学日志模板.xls"); + org.apache.commons.io.IOUtils.copy(is, response.getOutputStream()); + response.getOutputStream().flush(); + is.close(); + } catch (Exception e) { + throw new RuntimeException("下载模板失败", e); + } + } + + // ======================== 教学日志授课教员 ======================== + + /** + * 新增教学日志授课教员 + *

为教学日志添加主讲教师或辅讲教师(通过主教员字段zjy区分)。

+ * + * @param entity 授课教员关联实体 + * @return 操作结果 + */ + @PostMapping("/teaching-log/teacher/add") + public Result addLogTeacher(@RequestBody BCKTJXRZSKJY entity) { + logService.addLogTeacher(entity); + return Result.success(); + } + + /** + * 分页查询教学日志授课教员列表 + * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param bcktjxrzbh 教学日志编号(可选) + * @param fzjybh 辅助教员编号(可选) + * @return 分页结果 + */ + @GetMapping("/teaching-log/teacher/list") + public Result> listLogTeacher( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String bcktjxrzbh, + @RequestParam(required = false) String fzjybh) { + PageQuery query = new PageQuery(pageNum, pageSize); + BCKTJXRZSKJY cond = new BCKTJXRZSKJY(); + cond.setBcktjxrzbh(bcktjxrzbh); cond.setFzjybh(fzjybh); + return Result.success(logService.pageLogTeacher(query, cond)); + } + + // ======================== 教学日志学员学习情况 ======================== + + /** + * 新增学员学习情况记录 + *

记录教学日志中每位学员的学习效果(A-优秀 B-良好 C-合格 D-不合格)、到课和请假情况。

+ * + * @param entity 学员学习情况实体 + * @return 操作结果 + */ + @PostMapping("/teaching-log/student-status/add") + public Result addStudentStatus(@RequestBody JXRZXYXXQK entity) { + logService.addStudentLearningStatus(entity); + return Result.success(); + } + + /** + * 分页查询学员学习情况列表 + * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param jxrzbh 教学日志编号(可选) + * @return 分页结果 + */ + @GetMapping("/teaching-log/student-status/list") + public Result> listStudentStatus( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String jxrzbh) { + return Result.success(logService.pageStudentLearningStatus(new PageQuery(pageNum, pageSize), jxrzbh)); + } + + // ======================== 课时核算标准 ======================== + + /** + * 新增课时核算标准 + *

定义主讲教师课时系数、辅讲教师课时系数、讲授方式系数列表等核算参数。 + * 可单独设置考试主讲系数和考试指导教师系数。

+ * + * @param entity 课时核算标准实体 + * @return 操作结果 + */ + @PostMapping("/calc-standard/add") + public Result addCalcStandard(@RequestBody KSHBZ entity) { + logService.addCalculationStandard(entity); + return Result.success(); + } + + /** + * 更新课时核算标准 + * + * @param entity 课时核算标准实体(需含编号) + * @return 操作结果 + */ + @PostMapping("/calc-standard/update") + public Result updateCalcStandard(@RequestBody KSHBZ entity) { + logService.updateCalculationStandard(entity); + return Result.success(); + } + + /** + * 根据编号查询课时核算标准详情 + * + * @param bh 编号 + * @return 课时核算标准 + */ + @GetMapping("/calc-standard/get") + public Result getCalcStandard(@RequestParam("bh") String bh) { + return Result.success(logService.getCalculationStandardById(bh)); + } + + /** + * 分页查询课时核算标准列表 + * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param mc 名称(模糊匹配,可选) + * @return 分页结果 + */ + @GetMapping("/calc-standard/list") + public Result> listCalcStandard( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String mc) { + KSHBZ cond = new KSHBZ(); cond.setMc(mc); + return Result.success(logService.pageCalculationStandard(new PageQuery(pageNum, pageSize), cond)); + } + + // ======================== 课时系数方案 ======================== + + /** + * 新增课时系数方案 + *

定义主讲/辅讲课时系数、班级系数、合班系数、各时段课时量等核算参数。

+ * + * @param entity 课时系数方案实体 + * @return 操作结果 + */ + @PostMapping("/coefficient-plan/add") + public Result addCoefficientPlan(@RequestBody KSXSFA entity) { + logService.addCoefficientPlan(entity); + return Result.success(); + } + + /** + * 更新课时系数方案 + * + * @param entity 课时系数方案实体(需含编号) + * @return 操作结果 + */ + @PostMapping("/coefficient-plan/update") + public Result updateCoefficientPlan(@RequestBody KSXSFA entity) { + logService.updateCoefficientPlan(entity); + return Result.success(); + } + + /** + * 根据编号查询课时系数方案详情 + * + * @param bh 编号 + * @return 课时系数方案 + */ + @GetMapping("/coefficient-plan/get") + public Result getCoefficientPlan(@RequestParam("bh") String bh) { + return Result.success(logService.getCoefficientPlanById(bh)); + } + + /** + * 分页查询课时系数方案列表 + * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param mc 名称(模糊匹配,可选) + * @return 分页结果 + */ + @GetMapping("/coefficient-plan/list") + public Result> listCoefficientPlan( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String mc) { + KSXSFA cond = new KSXSFA(); cond.setMc(mc); + return Result.success(logService.pageCoefficientPlan(new PageQuery(pageNum, pageSize), cond)); + } + + // ======================== 课时费标准 ======================== + + /** + * 新增课时费标准 + *

定义绩效等级对应的课时费标准,包括优秀/良好/中等/及格/不及格各档补助金额。

+ * + * @param entity 课时费标准实体 + * @return 操作结果 + */ + @PostMapping("/fee-standard/add") + public Result addFeeStandard(@RequestBody KSFBZ entity) { + logService.addFeeStandard(entity); + return Result.success(); + } + + /** + * 更新课时费标准 + * + * @param entity 课时费标准实体(需含编号) + * @return 操作结果 + */ + @PostMapping("/fee-standard/update") + public Result updateFeeStandard(@RequestBody KSFBZ entity) { + logService.updateFeeStandard(entity); + return Result.success(); + } + + /** + * 根据编号查询课时费标准详情 + * + * @param bh 编号 + * @return 课时费标准 + */ + @GetMapping("/fee-standard/get") + public Result getFeeStandard(@RequestParam("bh") String bh) { + return Result.success(logService.getFeeStandardById(bh)); + } + + /** + * 分页查询课时费标准列表 + * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param mc 名称(模糊匹配,可选) + * @return 分页结果 + */ + @GetMapping("/fee-standard/list") + public Result> listFeeStandard( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String mc) { + KSFBZ cond = new KSFBZ(); cond.setMc(mc); + return Result.success(logService.pageFeeStandard(new PageQuery(pageNum, pageSize), cond)); + } + + // ======================== 技术职务课时费标准 ======================== + + /** + * 新增技术职务课时费标准 + *

按技术职务(教授、副教授、讲师等)定义标准课时量、课时费和超量课时费。

+ * + * @param entity 技术职务课时费标准实体 + * @return 操作结果 + */ + @PostMapping("/tech-pos-fee/add") + public Result addTechPosFee(@RequestBody JSZWKSFBZ entity) { + logService.addTechPosFeeStandard(entity); + return Result.success(); + } + + /** + * 更新技术职务课时费标准 + * + * @param entity 技术职务课时费标准实体(需含编号) + * @return 操作结果 + */ + @PostMapping("/tech-pos-fee/update") + public Result updateTechPosFee(@RequestBody JSZWKSFBZ entity) { + logService.updateTechPosFeeStandard(entity); + return Result.success(); + } + + /** + * 根据编号查询技术职务课时费标准详情 + * + * @param bh 编号 + * @return 技术职务课时费标准 + */ + @GetMapping("/tech-pos-fee/get") + public Result getTechPosFee(@RequestParam("bh") String bh) { + return Result.success(logService.getTechPosFeeStandardById(bh)); + } + + /** + * 分页查询技术职务课时费标准列表 + * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param jszwbh 技术职务编号(可选) + * @return 分页结果 + */ + @GetMapping("/tech-pos-fee/list") + public Result> listTechPosFee( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String jszwbh) { + JSZWKSFBZ cond = new JSZWKSFBZ(); cond.setJszwbh(jszwbh); + return Result.success(logService.pageTechPosFeeStandard(new PageQuery(pageNum, pageSize), cond)); + } + + // ======================== 课时补助标准 ======================== + + /** + * 新增课时补助项目 + *

定义课时补助项目,支持联教联训等不同类型的补助。

+ * + * @param entity 课时补助项目实体 + * @return 操作结果 + */ + @PostMapping("/subsidy-project/add") + public Result addSubsidyProject(@RequestBody KSBZXM entity) { + logService.addSubsidyProject(entity); + return Result.success(); + } + + /** + * 更新课时补助项目 + * + * @param entity 课时补助项目实体(需含编号) + * @return 操作结果 + */ + @PostMapping("/subsidy-project/update") + public Result updateSubsidyProject(@RequestBody KSBZXM entity) { + logService.updateSubsidyProject(entity); + return Result.success(); + } + + /** + * 根据编号查询课时补助项目详情 + * + * @param bh 编号 + * @return 课时补助项目 + */ + @GetMapping("/subsidy-project/get") + public Result getSubsidyProject(@RequestParam("bh") String bh) { + return Result.success(logService.getSubsidyProjectById(bh)); + } + + /** + * 分页查询课时补助项目列表 + * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param mc 名称(模糊匹配,可选) + * @return 分页结果 + */ + @GetMapping("/subsidy-project/list") + public Result> listSubsidyProject( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String mc) { + KSBZXM cond = new KSBZXM(); cond.setMc(mc); + return Result.success(logService.pageSubsidyProject(new PageQuery(pageNum, pageSize), cond)); + } + + // ======================== 教学日志课时补助审批表(汇总) ======================== + + /** + * 新增教学日志课时补助审批表(汇总) + *

创建一个核算周期内的课时补助汇总,包含学期、任务类别、核算起止日期等。

+ * + * @param entity 汇总审批表实体 + * @return 操作结果 + */ + @PostMapping("/summary/add") + public Result addSummary(@RequestBody JXRZKSBZSPB entity) { + logService.addSubsidyApproval(entity); + return Result.success(); + } + + /** + * 更新教学日志课时补助审批表(汇总) + * + * @param entity 汇总审批表实体(需含编号) + * @return 操作结果 + */ + @PostMapping("/summary/update") + public Result updateSummary(@RequestBody JXRZKSBZSPB entity) { + logService.updateSubsidyApproval(entity); + return Result.success(); + } + + /** + * 根据编号查询教学日志课时补助审批表详情 + * + * @param bh 编号 + * @return 汇总审批表 + */ + @GetMapping("/summary/get") + public Result getSummary(@RequestParam("bh") String bh) { + return Result.success(logService.getSubsidyApprovalById(bh)); + } + + /** + * 分页查询教学日志课时补助审批表列表 + *

支持按状态和任务类别筛选。

+ * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param zt 状态(可选) + * @param rwlb 任务类别(可选) + * @return 分页结果 + */ + @GetMapping("/summary/list") + public Result> listSummary( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String zt, + @RequestParam(required = false) String rwlb) { + JXRZKSBZSPB cond = new JXRZKSBZSPB(); cond.setZt(zt); cond.setRwlb(rwlb); + return Result.success(logService.pageSubsidyApproval(new PageQuery(pageNum, pageSize), cond)); + } + + /** + * 自动核算汇总数据 + *

根据教学日志教员课时补助明细,自动计算总课时量、优质课时量、优质课时量占比、总补助金额。

+ * + * @param bh 汇总审批表编号 + * @return 操作结果 + */ + @PostMapping("/summary/calculate") + public Result calculateSummary(@RequestParam("bh") String bh) { + logService.calculateSummary(bh); + return Result.success(); + } + + // ======================== 教学日志教员课时补助明细 ======================== + + /** + * 分页查询教学日志教员课时补助明细 + *

返回每位教员的课时补助明细,包括课时量、基本标准、增发标准、补助金额等。

+ * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param jxrzksbzspbh 汇总审批表编号(可选) + * @param jybh 教员编号(可选) + * @return 分页结果 + */ + @GetMapping("/summary/detail/list") + public Result> listSummaryDetail( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String jxrzksbzspbh, + @RequestParam(required = false) String jybh) { + JXRZJYKSBZ cond = new JXRZJYKSBZ(); cond.setJxrzksbzspbh(jxrzksbzspbh); cond.setJybh(jybh); + return Result.success(logService.pageSubsidyDetail(new PageQuery(pageNum, pageSize), cond)); + } + + // ======================== 教员课时补助 ======================== + + /** + * 新增教员课时补助记录 + * + * @param entity 教员课时补助实体 + * @return 操作结果 + */ + @PostMapping("/teacher-subsidy/add") + public Result addTeacherSubsidy(@RequestBody JYKSBZ entity) { + logService.addTeacherSubsidy(entity); + return Result.success(); + } + + /** + * 分页查询教员课时补助列表 + * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param jybh 教员编号(可选) + * @return 分页结果 + */ + @GetMapping("/teacher-subsidy/list") + public Result> listTeacherSubsidy( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String jybh) { + JYKSBZ cond = new JYKSBZ(); cond.setJybh(jybh); + return Result.success(logService.pageTeacherSubsidy(new PageQuery(pageNum, pageSize), cond)); + } + + // ======================== 年终课时费 ======================== + + /** + * 新增年终课时费 + *

创建学年课时费核算主表,按学期统计总课时量。

+ * + * @param entity 年终课时费实体 + * @return 操作结果 + */ + @PostMapping("/year-end-fee/add") + public Result addYearEndFee(@RequestBody NZKSF entity) { + logService.addYearEndFee(entity); + return Result.success(); + } + + /** + * 更新年终课时费 + * + * @param entity 年终课时费实体(需含编号) + * @return 操作结果 + */ + @PostMapping("/year-end-fee/update") + public Result updateYearEndFee(@RequestBody NZKSF entity) { + logService.updateYearEndFee(entity); + return Result.success(); + } + + /** + * 根据编号查询年终课时费详情 + * + * @param bh 编号 + * @return 年终课时费 + */ + @GetMapping("/year-end-fee/get") + public Result getYearEndFee(@RequestParam("bh") String bh) { + return Result.success(logService.getYearEndFeeById(bh)); + } + + /** + * 分页查询年终课时费列表 + * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param mc 名称(模糊匹配,可选) + * @return 分页结果 + */ + @GetMapping("/year-end-fee/list") + public Result> listYearEndFee( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String mc) { + NZKSF cond = new NZKSF(); cond.setMc(mc); + return Result.success(logService.pageYearEndFee(new PageQuery(pageNum, pageSize), cond)); + } + + /** + * 分页查询教员年终课时费明细 + *

每位教员的课时费核算明细,包含学期课时量、超课时量、计算公式、课时费金额等。

+ * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param jybh 教员编号(可选) + * @param nzksfbh 年终课时费编号(可选) + * @return 分页结果 + */ + @GetMapping("/year-end-fee/teacher/list") + public Result> listTeacherYearEndFee( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String jybh, + @RequestParam(required = false) String nzksfbh) { + JYNZKSF cond = new JYNZKSF(); cond.setJybh(jybh); cond.setNzksfbh(nzksfbh); + return Result.success(logService.pageTeacherYearEndFee(new PageQuery(pageNum, pageSize), cond)); + } + + // ======================== 教师工作量 ======================== + + /** + * 新增教师工作量记录 + *

支持录入五类工作量:课程教学、科研学术、指导学员、教学建设、咨询服务。

+ * + * @param entity 教师工作量实体 + * @return 操作结果 + */ + @PostMapping("/workload/add") + public Result addWorkload(@RequestBody JZGZL entity) { + logService.addWorkload(entity); + return Result.success(); + } + + /** + * 更新教师工作量记录 + * + * @param entity 教师工作量实体(需含编号) + * @return 操作结果 + */ + @PostMapping("/workload/update") + public Result updateWorkload(@RequestBody JZGZL entity) { + logService.updateWorkload(entity); + return Result.success(); + } + + /** + * 根据编号查询教师工作量详情 + * + * @param bh 编号 + * @return 教师工作量 + */ + @GetMapping("/workload/get") + public Result getWorkload(@RequestParam("bh") String bh) { + return Result.success(logService.getWorkloadById(bh)); + } + + /** + * 分页查询教师工作量列表 + * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param jybh 教员编号(可选) + * @param nd 年度(可选) + * @param xq 学期(可选) + * @return 分页结果 + */ + @GetMapping("/workload/list") + public Result> listWorkload( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String jybh, + @RequestParam(required = false) Integer nd, + @RequestParam(required = false) String xq) { + JZGZL cond = new JZGZL(); cond.setJybh(jybh); cond.setNd(nd); cond.setXq(xq); + return Result.success(logService.pageWorkload(new PageQuery(pageNum, pageSize), cond)); + } + + /** + * 比对各类教师工作量情况 + *

按总工作量降序排列,支持按年度和学期筛选,用于分析教师工作量分布。

+ * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param nd 年度(可选) + * @param xq 学期(可选) + * @return 排序后的工作量列表 + */ + @GetMapping("/workload/compare") + public Result compareWorkload( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) Integer nd, + @RequestParam(required = false) String xq) { + return Result.success(logService.compareWorkload(new PageQuery(pageNum, pageSize), nd, xq)); + } + + // ======================== 教员课时查询管理 ======================== + + /** + * 分页查询教员课时信息 + *

+ * 数据来源:学期教员信息表 JOIN 教员表 JOIN 教研室表。 + * 列表展示:教研室名称、教员姓名、教员类别、当前职称、学期职称、 + * 当前专业技术职务等级、学期专业技术职务等级、主讲课时、辅讲课时、 + * 课程总课时、课程折算总课时、项目补助课时、总课时。 + *

+ *

查询条件(均为可选):教员姓名(模糊)、教研室(代号精确)、教研室名称(模糊)、 + * 当前职称/职务、学期职称、当前/学期专业技术职务等级、部系名称(模糊)、年度(学期); + * 各课时量支持数字范围(xx课时至xx课时):主讲课时、辅讲课时、课程总课时、 + * 课程折算总课时、项目补助课时、总课时(传 xxxMin/xxxMax 参数)。

+ * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param cond 查询条件对象(GET 参数自动绑定) + * @return 分页结果 + */ + @GetMapping("/teacher-hours/list") + public Result> listTeacherHours( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + TeacherHoursVO cond) { + return Result.success(logService.pageTeacherHours(new PageQuery(pageNum, pageSize), cond)); + } + + // ======================== 教学实施计划统计 ======================== + + /** + * 分页查询教学实施计划统计数据 + *

按天统计教学运行数据,包括教学日志填写数、审核数、听查课数、调课数、缺课人次等。

+ * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param rq 日期(可选) + * @return 分页结果 + */ + @GetMapping("/stats/list") + public Result> listStats( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String rq) { + JXSSJHTJB cond = new JXSSJHTJB(); + return Result.success(logService.pageTeachingStats(new PageQuery(pageNum, pageSize), cond)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LoginController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LoginController.java new file mode 100644 index 000000000..775cedc64 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/LoginController.java @@ -0,0 +1,132 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.accountmanagement.AccountLoginDTO; +import com.roomroot.jwgl.dto.login.LoginDTO; +import com.roomroot.jwgl.entity.SSJG; +import com.roomroot.jwgl.mapper.SSJGMapper; +import com.roomroot.jwgl.service.AccountManagementService; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.utils.AccountManagementConstants; +import com.roomroot.jwgl.vo.accountmanagement.AccountLoginVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountRoleVO; +import com.roomroot.jwgl.vo.login.LoginResultVO; +import com.roomroot.common.annotation.Anonymous; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 登录认证控制器 + *

+ * 支持行政、教员、学员、管理员四种用户类型登录。 + * 管理员登录不需要单位(orgId),其余类型需选择单位。 + * 使用数据表:登录信息表(用户凭证)、教学管理机构/实施_机关(单位信息)。 + *

+ */ +@RestController +@RequestMapping("/auth") +@Anonymous +public class LoginController { + + @Resource + private AccountManagementService accountManagementService; + + @Resource + private SSJGMapper ssjgMapper; + + /** + * 用户登录 + *

+ * 根据登录名和密码验证身份,返回用户信息、角色和单位信息。 + *

+ */ + @PostMapping("/login") + public Result login(@RequestBody LoginDTO loginDTO) { + // 1. 参数校验 + if (loginDTO.getLoginName() == null || loginDTO.getLoginName().isEmpty()) { + return Result.error(400, "登录名不能为空"); + } + if (loginDTO.getPassword() == null || loginDTO.getPassword().isEmpty()) { + return Result.error(400, "密码不能为空"); + } + if (loginDTO.getUserType() == null || loginDTO.getUserType().isEmpty()) { + return Result.error(400, "用户类型不能为空"); + } + + // 管理员登录不需要单位 + if (!"MANAGER".equals(loginDTO.getUserType()) && (loginDTO.getOrgId() == null || loginDTO.getOrgId().isEmpty())) { + return Result.error(400, "单位不能为空"); + } + + // 2. 参数转换(LoginDTO → AccountLoginDTO) + AccountLoginDTO accountLoginDTO = new AccountLoginDTO(); + accountLoginDTO.setLoginName(loginDTO.getLoginName()); + accountLoginDTO.setPassword(loginDTO.getPassword()); + + // 3. 委托 AccountManagementService 处理登录 + AccountLoginVO loginVO = accountManagementService.login(accountLoginDTO); + + // 4. 匹配用户类型 + String roleTypeCode = mapUserTypeToRoleCode(loginDTO.getUserType()); + List roles = loginVO.getRoles(); + boolean hasRole = roles.stream().anyMatch(r -> roleTypeCode.equals(r.getRoleType())); + if (!hasRole) { + return Result.error(403, "当前用户没有" + loginDTO.getUserType() + "权限"); + } + + // 5. 查询单位信息 + String orgName = null; + if (!"MANAGER".equals(loginDTO.getUserType())) { + for (AccountRoleVO role : roles) { + if (roleTypeCode.equals(role.getRoleType())) { + SSJG org = ssjgMapper.selectById(role.getTargetId()); + if (org != null) { + orgName = org.getJGMC(); + } + break; + } + } + } + + // 6. 返回值转换(AccountLoginVO → LoginResultVO) + LoginResultVO result = new LoginResultVO(); + result.setUserId(loginVO.getAccountId()); + result.setUserName(loginVO.getUserName()); + result.setLoginName(loginVO.getLoginName()); + result.setUserType(loginDTO.getUserType()); + result.setOrgId(loginDTO.getOrgId()); + result.setOrgName(orgName); + + List roleInfos = roles.stream().map(r -> { + LoginResultVO.RoleInfo ri = new LoginResultVO.RoleInfo(); + ri.setRoleType(r.getRoleType()); + ri.setRoleName(r.getRoleName()); + ri.setTargetId(r.getTargetId()); + ri.setTargetName(r.getTargetName()); + return ri; + }).collect(Collectors.toList()); + result.setRoles(roleInfos); + + return Result.success(result); + } + + /** + * 将前端用户类型映射为角色编码 + */ + private String mapUserTypeToRoleCode(String userType) { + switch (userType) { + case "ADMIN": + return AccountManagementConstants.ROLE_DEPARTMENT_PERSONNEL; + case "TEACHER": + return AccountManagementConstants.ROLE_TEACHER; + case "STUDENT": + return AccountManagementConstants.ROLE_STUDENT; + case "MANAGER": + return AccountManagementConstants.ROLE_DEPARTMENT_PERSONNEL; + default: + return userType; + } + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NoticeAnnouncementController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NoticeAnnouncementController.java new file mode 100644 index 000000000..478f0f2c9 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NoticeAnnouncementController.java @@ -0,0 +1,200 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementCreateDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementIdDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPriorityDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPublishDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementQueryDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientQueryDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementUpdateDTO; +import com.roomroot.jwgl.dto.noticeannouncement.UserNoticeAnnouncementQueryDTO; +import com.roomroot.jwgl.service.NoticeAnnouncementService; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementDetailVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementPublishResultVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementRecipientVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementStatisticsVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementVO; +import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementDetailVO; +import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementVO; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +/** + * 通知公告管理接口。 + * + *

后台公告维护、发布统计和用户阅读入口统一保留在当前控制器中, + * 不为同一通知公告功能重复创建控制器。

+ */ +@RestController +@RequestMapping("/notice-announcement") +public class NoticeAnnouncementController { + + /** + * 通知公告管理业务服务。 + */ + @Resource + private NoticeAnnouncementService noticeAnnouncementService; + + /** + * 分页查询后台通知公告。 + */ + @GetMapping("/list") + public Result> list( + @Valid NoticeAnnouncementQueryDTO dto) { + // 调用业务层完成筛选、接收阅读统计和分页结果封装。 + return Result.success( + noticeAnnouncementService.list(dto)); + } + + /** + * 查询通知公告后台详情。 + */ + @GetMapping("/get") + public Result get( + @Valid NoticeAnnouncementIdDTO dto) { + // 调用业务层查询公告内容、状态、发布范围和阅读统计。 + return Result.success( + noticeAnnouncementService.get(dto)); + } + + /** + * 新增一条拟制公告。 + */ + @PostMapping("/add") + public Result add( + @Valid @RequestBody + NoticeAnnouncementCreateDTO dto) { + // 调用业务层固定创建拟制状态公告。 + return Result.success( + "新增成功", + noticeAnnouncementService.add(dto)); + } + + /** + * 修改拟制或已下架公告。 + */ + @PostMapping("/update") + public Result update( + @Valid @RequestBody + NoticeAnnouncementUpdateDTO dto) { + // 调用业务层校验状态并修改公告可编辑内容。 + return Result.success( + "修改成功", + noticeAnnouncementService.update(dto)); + } + + /** + * 发布或重新发布公告。 + */ + @PostMapping("/publish") + public Result publish( + @Valid @RequestBody + NoticeAnnouncementPublishDTO dto) { + /* + * 业务层会解析全校、全体教员或指定部别账户, + * 在同一事务中替换接收人快照并更新公告状态。 + */ + return Result.success( + "发布成功", + noticeAnnouncementService.publish(dto)); + } + + /** + * 下架已发布公告。 + */ + @PostMapping("/down") + public Result down( + @Valid @RequestBody + NoticeAnnouncementIdDTO dto) { + // 调用业务层锁定公告并执行已发布到已下架状态流转。 + return Result.success( + "下架成功", + noticeAnnouncementService.down(dto)); + } + + /** + * 删除拟制或已下架公告。 + */ + @PostMapping("/delete") + public Result delete( + @Valid @RequestBody + NoticeAnnouncementIdDTO dto) { + // 第一步:调用业务层校验状态并删除接收人快照和公告主体。 + noticeAnnouncementService.delete(dto); + + // 第二步:删除操作没有业务返回对象,返回统一成功结果。 + return Result.success("删除成功", null); + } + + /** + * 调整公告优先级或取消置顶。 + */ + @PostMapping("/priority") + public Result priority( + @Valid @RequestBody + NoticeAnnouncementPriorityDTO dto) { + // 优先级为0表示取消置顶,大于0时参与公告列表优先排序。 + return Result.success( + "优先级调整成功", + noticeAnnouncementService + .updatePriority(dto)); + } + + /** + * 分页查询公告接收和阅读明细。 + */ + @GetMapping("/recipients") + public Result> + recipients( + @Valid NoticeAnnouncementRecipientQueryDTO dto) { + // 调用业务层按账户去重查询接收时间、阅读状态和首次阅读时间。 + return Result.success( + noticeAnnouncementService.recipients(dto)); + } + + /** + * 查询公告接收和阅读统计。 + */ + @GetMapping("/statistics") + public Result statistics( + @Valid NoticeAnnouncementIdDTO dto) { + // 调用业务层计算接收、已读、未读人数和阅读率。 + return Result.success( + noticeAnnouncementService.statistics(dto)); + } + + /** + * 分页查询当前登录账户可见的已发布公告。 + */ + @GetMapping("/user/list") + public Result> + userList( + @Valid UserNoticeAnnouncementQueryDTO dto) { + // 查询账户身份只从服务端 Session 读取,不接收客户端账户编号。 + return Result.success( + noticeAnnouncementService.userList(dto)); + } + + /** + * 当前登录账户打开并阅读公告。 + */ + @PostMapping("/user/read") + public Result read( + @Valid @RequestBody + NoticeAnnouncementIdDTO dto) { + /* + * 首次打开会记录阅读状态和首次阅读时间; + * 重复打开只增加公告累计阅读次数,不重复增加阅读人数。 + */ + return Result.success( + noticeAnnouncementService.read(dto)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NotificationLogController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NotificationLogController.java new file mode 100644 index 000000000..9684422e7 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/NotificationLogController.java @@ -0,0 +1,122 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.notificationlog.NotificationLogExportDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogIdDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogQueryDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogRecipientQueryDTO; +import com.roomroot.jwgl.service.NotificationLogService; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.utils.NotificationLogUtil; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogDetailVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogRecipientVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogStatisticsVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogVO; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import java.util.List; + +import static com.roomroot.jwgl.utils.NotificationLogUtil.writeNotificationLogExcel; + +/** + * 通知日志管理接口。 + * + *

当前控制器统一承载通知发送历史追踪接口, + * 不修改通知公告管理已有的发布和阅读入口。

+ */ +@RestController +@RequestMapping("/notification-log") +public class NotificationLogController { + + /** + * 通知日志管理业务服务。 + */ + @Resource + private NotificationLogService notificationLogService; + + /** + * 分页查询通知发送历史。 + * + * @param dto 通知日志查询条件 + * @return 通知发送历史分页结果 + */ + @GetMapping("/list") + public Result> list( + @Valid NotificationLogQueryDTO dto) { + // 调用业务层完成筛选、发送接收统计和统一分页结果封装。 + return Result.success( + notificationLogService.list(dto)); + } + + /** + * 根据通知编号查询发送日志详情。 + * + * @param dto 通知日志编号参数 + * @return 通知主体和完整发送、阅读统计 + */ + @GetMapping("/get") + public Result get( + @Valid NotificationLogIdDTO dto) { + // 调用业务层校验日志编号并查询完整发送日志详情。 + return Result.success( + notificationLogService.get(dto)); + } + + /** + * 分页查询指定通知的接收对象和阅读明细。 + * + * @param dto 通知编号、分页和接收阅读筛选条件 + * @return 按账户去重的接收与阅读明细 + */ + @GetMapping("/recipients") + public Result> + recipients( + @Valid NotificationLogRecipientQueryDTO dto) { + // 调用业务层完成日志存在性校验、账户去重和分页结果封装。 + return Result.success( + notificationLogService.recipients(dto)); + } + + /** + * 按当前筛选条件汇总通知发送和阅读情况。 + * + * @param dto 通知日志筛选条件 + * @return 通知数量、发送结果和阅读情况汇总 + */ + @GetMapping("/statistics") + public Result statistics( + @Valid NotificationLogExportDTO dto) { + // 调用业务层复用列表筛选规则并计算完整汇总结果。 + return Result.success( + notificationLogService.statistics(dto)); + } + + /** + * 按当前筛选条件导出全部通知日志。 + * + * @param dto 通知日志导出筛选条件 + * @param response 当前 HTTP 响应 + */ + @GetMapping("/export") + public void export( + @Valid NotificationLogExportDTO dto, + HttpServletResponse response) { + /* + * 第一步:调用业务层按照固定批次查询全部符合条件的通知日志, + * 并复用列表的筛选、稳定排序和统计字段补全规则。 + */ + List records = + notificationLogService.export(dto); + + /* + * 第二步:由通知日志工具类统一生成 Excel 并写入响应, + * 控制器不重复维护工作簿样式、文件名编码和安全文本规则。 + */ + NotificationLogUtil.writeNotificationLogExcel(response, records); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OfficeTaskBookController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OfficeTaskBookController.java new file mode 100644 index 000000000..baba2ed06 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OfficeTaskBookController.java @@ -0,0 +1,90 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.JYSRWS; +import com.roomroot.jwgl.service.OfficeTaskBookService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 教研室任务书控制器 + */ +@RestController +@RequestMapping("/officeTaskBook") +public class OfficeTaskBookController { + + @Resource + private OfficeTaskBookService officeTaskBookService; + + /** + * 新增 + */ + @PostMapping("/add") + public Result add(@RequestBody JYSRWS jysrws) { + officeTaskBookService.add(jysrws); + return Result.success(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + officeTaskBookService.delete(bh); + return Result.success(); + } + + /** + * 批量删除 + */ + @PostMapping("/batchDelete") + public Result batchDelete(@RequestBody List bhList) { + officeTaskBookService.batchDelete(bhList); + return Result.success(); + } + + /** + * 更新 + */ + @PostMapping("/update") + public Result update(@RequestBody JYSRWS jysrws) { + officeTaskBookService.update(jysrws); + return Result.success(); + } + + /** + * 根据编号查询 + */ + @GetMapping("/get") + public Result getByBh(@RequestParam("bh") String bh) { + return Result.success(officeTaskBookService.getByBh(bh)); + } + + /** + * 分页查询列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, JYSRWS jysrws) { + return Result.success(officeTaskBookService.pageList(query, jysrws)); + } + + /** + * 根据教学任务编号查询 + */ + @GetMapping("/listByJxrwbh") + public Result> listByJxrwbh(@RequestParam("jxrwbh") String jxrwbh) { + return Result.success(officeTaskBookService.listByJxrwbh(jxrwbh)); + } + + /** + * 根据教研室代号查询 + */ + @GetMapping("/listByJysdh") + public Result> listByJysdh(@RequestParam("jysdh") String jysdh) { + return Result.success(officeTaskBookService.listByJysdh(jysdh)); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OperationLogController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OperationLogController.java new file mode 100644 index 000000000..dc56be4b9 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/OperationLogController.java @@ -0,0 +1,99 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.operationlog.OperationLogExportDTO; +import com.roomroot.jwgl.dto.operationlog.OperationLogIdDTO; +import com.roomroot.jwgl.dto.operationlog.OperationLogQueryDTO; +import com.roomroot.jwgl.service.OperationLogService; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.utils.OperationLogUtil; +import com.roomroot.jwgl.vo.operationlog.OperationLogDetailVO; +import com.roomroot.jwgl.vo.operationlog.OperationLogModuleVO; +import com.roomroot.jwgl.vo.operationlog.OperationLogVO; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import java.util.List; + + +/** + * 操作日志管理接口。 + * + *

统一提供分页查询、详情查询和 Excel 导出接口, + * 操作日志自动采集由请求拦截器完成,不拆分重复的业务控制器。

+ */ +@RestController +@RequestMapping("/operation-log") +public class OperationLogController { + + /** + * 操作日志管理业务服务。 + */ + @Resource + private OperationLogService operationLogService; + + /** + * 分页查询操作日志。 + * + * @param dto 查询条件 + * @return 操作日志分页结果 + */ + @GetMapping("/list") + public Result> list( + @Valid OperationLogQueryDTO dto) { + // 调用业务层完成参数校验、三表联查、模块识别和分页结果封装。 + return Result.success(operationLogService.list(dto)); + } + + /** + * 查询操作日志页面可使用的模块选项。 + * + * @return 与模块筛选白名单保持一致的模块选项 + */ + @GetMapping("/modules") + public Result> modules() { + // 调用业务层取得按系统业务顺序排列的操作日志模块选项。 + return Result.success(operationLogService.modules()); + } + + /** + * 根据编号查询操作日志详情。 + * + * @param dto 操作日志编号参数 + * @return 操作日志详情 + */ + @GetMapping("/get") + public Result get( + @Valid OperationLogIdDTO dto) { + // 调用业务层校验日志编号并查询完整的操作、登录和账户信息。 + return Result.success(operationLogService.get(dto)); + } + + /** + * 按当前筛选条件导出全部操作日志数据。 + * + * @param dto 导出筛选条件 + * @param response 当前 HTTP 响应 + */ + @GetMapping("/export") + public void export( + @Valid OperationLogExportDTO dto, + HttpServletResponse response) { + /* + * 第一步:调用业务层复用列表筛选、稳定排序和模块识别规则, + * 查询全部符合当前导出条件的操作日志。 + */ + List records = + operationLogService.export(dto); + + /* + * 第二步:由操作日志工具类统一生成 Excel 文件并写入响应, + * 控制器不重复维护工作簿样式、文件名编码和安全文本处理。 + */ + OperationLogUtil.writeOperationLogExcel(response, records); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterCalendarController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterCalendarController.java new file mode 100644 index 000000000..8138230a9 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterCalendarController.java @@ -0,0 +1,74 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.XQXLB; +import com.roomroot.jwgl.service.SemesterCalendarService; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 学期校历表控制器 + */ +@RestController +@RequestMapping("/xqxlb") +public class SemesterCalendarController { + + @Resource + private SemesterCalendarService semesterCalendarService; + + /** + * 新增学期校历 + */ + @PostMapping("/add") + public Result add(@RequestParam("startDate") String startDate,@RequestParam("endDate") String endDate) { + semesterCalendarService.add(startDate,endDate); + return Result.success(); + } + + /** + * 删除学期校历 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + semesterCalendarService.delete(bh); + return Result.success(); + } + + /** + * 更新学期校历 + */ + @PostMapping("/update") + public Result update(@RequestBody XQXLB xqxlb) { + semesterCalendarService.update(xqxlb); + return Result.success(); + } + + /** + * 根据编号查询学期校历 + */ + @GetMapping("/get") + public Result getById(@RequestParam("bh") String bh) { + return Result.success(semesterCalendarService.getById(bh)); + } + + /** + * 根据年度查询学期校历列表(全量) + */ + @GetMapping("/list") + public Result> list(@RequestParam("nd") Integer nd) { + return Result.success(semesterCalendarService.listByNd(nd)); + } + + /** + * 导入Excel文件 + */ + @PostMapping("/import") + public Result importExcel(@RequestParam("file") MultipartFile file) { + int count = semesterCalendarService.importExcel(file); + return Result.success("导入成功", count); + } + +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterController.java new file mode 100644 index 000000000..304b6cc92 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SemesterController.java @@ -0,0 +1,73 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.XQ; +import com.roomroot.jwgl.service.SemesterService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 学期控制器 + */ +@RestController +@RequestMapping("/semester") +public class SemesterController { + + @Resource + private SemesterService semesterService; + + /** + * 新增学期 + */ + @PostMapping("/add") + public Result add(@RequestBody XQ xq) { + semesterService.add(xq); + return Result.success(); + } + + /** + * 删除学期 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("nd") Integer nd) { + semesterService.delete(nd); + return Result.success(); + } + + /** + * 更新学期 + */ + @PostMapping("/update") + public Result update(@RequestBody XQ xq) { + semesterService.update(xq); + return Result.success(); + } + + /** + * 根据年度查询 + */ + @GetMapping("/get") + public Result getByNd(@RequestParam("nd") Integer nd) { + return Result.success(semesterService.getByNd(nd)); + } + + /** + * 分页查询学期列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, XQ xq) { + return Result.success(semesterService.pageList(query, xq)); + } + + /** + * 查询所有学期列表 + */ + @GetMapping("/all") + public Result> all() { + return Result.success(semesterService.list()); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentRecordsController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentRecordsController.java new file mode 100644 index 000000000..80e5df2a5 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentRecordsController.java @@ -0,0 +1,114 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.XYXX; +import com.roomroot.jwgl.entity.XYKCCJ; +import com.roomroot.jwgl.entity.XYKCGCCJ; +import com.roomroot.jwgl.service.StudentRecordsService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 学员档案管理控制器 + *

+ * 提供学员信息的增删改查、学籍异动管理, + * 以及学员课程成绩查询、课程过程成绩查询、课程表查询和 Excel 导出功能。 + *

+ */ +@RestController +@RequestMapping("/student-records") +public class StudentRecordsController { + + @Resource + private StudentRecordsService studentRecordsService; + + // ======================== 学员 CRUD ======================== + + @PostMapping("/add") + public Result add(@RequestBody XYXX xyxx) { + studentRecordsService.add(xyxx); + return Result.success(); + } + + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + studentRecordsService.delete(bh); + return Result.success(); + } + + @PostMapping("/update") + public Result update(@RequestBody XYXX xyxx) { + studentRecordsService.update(xyxx); + return Result.success(); + } + + @GetMapping("/get") + public Result getById(@RequestParam("bh") String bh) { + return Result.success(studentRecordsService.getById(bh)); + } + + @GetMapping("/list") + public Result> list(PageQuery query, XYXX xyxx) { + return Result.success(studentRecordsService.pageList(query, xyxx)); + } + + // ======================== 学籍异动 ======================== + + @PostMapping("/retain-grade") + public Result retainGrade(@RequestParam("bh") String bh) { + studentRecordsService.retainGrade(bh); + return Result.success(); + } + + @PostMapping("/downgrade") + public Result downgrade(@RequestParam("bh") String bh) { + studentRecordsService.downgrade(bh); + return Result.success(); + } + + @PostMapping("/drop-out") + public Result dropOut(@RequestParam("bh") String bh) { + studentRecordsService.dropOut(bh); + return Result.success(); + } + + // ======================== 课程成绩查询 ======================== + + /** + * 查询学员的课程成绩列表JW + * + * @param xybh 学员编号 + */ + @GetMapping("/grades") + public Result> getGrades(@RequestParam("xybh") String xybh) { + return Result.success(studentRecordsService.getCourseGrades(xybh)); + } + + /** + * 查询学员的课程过程成绩列表 + * + * @param xybh 学员编号 + */ + @GetMapping("/process-grades") + public Result> getProcessGrades(@RequestParam("xybh") String xybh) { + return Result.success(studentRecordsService.getCourseProcessGrades(xybh)); + } + + // ======================== Excel 导出 ======================== + + /** + * 导出学员课程成绩 Excel + * + * @param xybh 学员编号 + * @param response HTTP 响应 + */ + @GetMapping("/export-grades") + public void exportGrades(@RequestParam("xybh") String xybh, HttpServletResponse response) { + studentRecordsService.exportGradesExcel(xybh, response); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamOutputScheduleController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamOutputScheduleController.java new file mode 100644 index 000000000..9fc89ff8c --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamOutputScheduleController.java @@ -0,0 +1,89 @@ +package com.roomroot.web.controller.jwgl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDSCKCB; +import com.roomroot.jwgl.service.StudentTeamOutputScheduleService; +import com.roomroot.jwgl.unit.Result; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 学员队输出课程表 Controller + */ +@RestController +@RequestMapping("/studentTeamOutputSchedule") +public class StudentTeamOutputScheduleController { + + @Autowired + private StudentTeamOutputScheduleService studentTeamOutputScheduleService; + + /** + * 新增 + */ + @PostMapping("/add") + public Result add(@RequestBody XYDSCKCB xydsckcb) { + studentTeamOutputScheduleService.add(xydsckcb); + return Result.success(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + studentTeamOutputScheduleService.delete(bh); + return Result.success(); + } + + /** + * 更新 + */ + @PostMapping("/update") + public Result update(@RequestBody XYDSCKCB xydsckcb) { + studentTeamOutputScheduleService.update(xydsckcb); + return Result.success(); + } + + /** + * 根据编号查询 + */ + @GetMapping("/get") + public Result get(@RequestParam("bh") String bh) { + XYDSCKCB xydsckcb = studentTeamOutputScheduleService.getByBh(bh); + return Result.success(xydsckcb); + } + + /** + * 根据年度和学员队编号查询 + */ + @GetMapping("/getByNdAndXydbh") + public Result getByNdAndXydbh( + @RequestParam("nd") Integer nd, + @RequestParam("xydbh") String xydbh) { + XYDSCKCB xydsckcb = studentTeamOutputScheduleService.getByNdAndXydbh(nd, xydbh); + return Result.success(xydsckcb); + } + + /** + * 分页查询 + */ + @GetMapping("/list") + public Result> list( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "10") int pageSize, + XYDSCKCB xydsckcb) { + Page page = studentTeamOutputScheduleService.list(pageNum, pageSize, xydsckcb); + return Result.success(page); + } + + /** + * 查询所有 + */ + @GetMapping("/all") + public Result> all() { + List list = studentTeamOutputScheduleService.all(); + return Result.success(list); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamTaskController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamTaskController.java new file mode 100644 index 000000000..7164fb83f --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/StudentTeamTaskController.java @@ -0,0 +1,162 @@ +package com.roomroot.web.controller.jwgl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDRWB; +import com.roomroot.jwgl.service.StudentTeamTaskService; +import com.roomroot.jwgl.unit.Result; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +/** + * 学员队任务表 Controller + */ +@RestController +@RequestMapping("/studentTeamTask") +public class StudentTeamTaskController { + + @Autowired + private StudentTeamTaskService studentTeamTaskService; + + /** + * 新增 + */ + @PostMapping("/add") + public Result add(@RequestBody XYDRWB xydrwb) { + studentTeamTaskService.add(xydrwb); + return Result.success(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + studentTeamTaskService.delete(bh); + return Result.success(); + } + + /** + * 更新 + */ + @PostMapping("/update") + public Result update(@RequestBody XYDRWB xydrwb) { + studentTeamTaskService.update(xydrwb); + return Result.success(); + } + + /** + * 根据编号查询 + */ + @GetMapping("/get") + public Result get(@RequestParam("bh") String bh) { + XYDRWB xydrwb = studentTeamTaskService.getByBh(bh); + return Result.success(xydrwb); + } + + /** + * 分页查询(按序号标识排序) + */ + @GetMapping("/list") + public Result> list( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "10") int pageSize, + XYDRWB xydrwb) { + Page page = studentTeamTaskService.list(pageNum, pageSize, xydrwb); + return Result.success(page); + } + + /** + * 分页查询(按计划课次序号排序) + */ + @GetMapping("/groupList") + public Result> groupList( + @RequestParam(defaultValue = "1") int pageNum, + @RequestParam(defaultValue = "10") int pageSize, + XYDRWB xydrwb) { + Page page = studentTeamTaskService.groupList(pageNum, pageSize, xydrwb); + return Result.success(page); + } + + /** + * 批量预设合班 + *

+ * 根据编号列表,按年度排序生成统一的计划课次序号 + *

+ * + * @param params 包含bhList(编号列表) + * @return 更新数量 + */ + @PostMapping("/batchPresetMerge") + public Result batchPresetMerge(@RequestBody Map params) { + List bhList = (List) params.get("bhList"); + int count = studentTeamTaskService.batchPresetMerge(bhList); + return Result.success(count); + } + + /** + * 批量预设拆班 + *

+ * 根据编号列表,清空计划课次序号 + *

+ * + * @param params 包含bhList(编号列表) + * @return 更新数量 + */ + @PostMapping("/batchPresetSplit") + public Result batchPresetSplit(@RequestBody Map params) { + List bhList = (List) params.get("bhList"); + int count = studentTeamTaskService.batchPresetSplit(bhList); + return Result.success(count); + } + + /** + * 查询所有 + */ + @GetMapping("/all") + public Result> all() { + List list = studentTeamTaskService.all(); + return Result.success(list); + } + + /** + * 自动生成必修课程 + *

+ * 根据学员队编号从学员队表获取专业代号, + * 再根据专业代号和学期第次查询专业教学计划表, + * 为当前班次自动生成必修课程,插入到学员队任务表。 + * 已存在的课程(按课编号判断)不会重复添加。 + *

+ * + * @param xydbh 学员队编号 + * @param xqdc 学期第次 + * @return 新增数量 + */ + @PostMapping("/autoGenerateRequiredCourses") + public Result autoGenerateRequiredCourses( + @RequestParam("xydbh") String xydbh, + @RequestParam("xqdc") Integer xqdc) { + int count = studentTeamTaskService.autoGenerateRequiredCourses(xydbh, xqdc); + return Result.success(count); + } + + /** + * 批量复制课程 + *

+ * 根据传入的学员队任务表编号集合,复制课程信息, + * 替换学员队编号为新的值 + *

+ * + * @param params 包含bhList(源编号集合)和xydbh(新学员队编号) + * @return 新生成的编号集合 + */ + @PostMapping("/copy") + public Result> copy(@RequestBody Map params) { + List bhList = (List) params.get("bhList"); + String newXydbh = (String) params.get("xydbh"); + List newBhList = studentTeamTaskService.copy(bhList, newXydbh); + return Result.success(newBhList); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SystemInitializationController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SystemInitializationController.java new file mode 100644 index 000000000..39615935f --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/SystemInitializationController.java @@ -0,0 +1,149 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.dto.systeminitialization.SemesterInitializationSaveDTO; +import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationConfigurationSaveDTO; +import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationExecutionDTO; +import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationHistoricalImportDTO; +import com.roomroot.jwgl.service.SystemInitializationService; +import com.roomroot.jwgl.unit.Result; +import com.roomroot.jwgl.vo.systeminitialization.SemesterInitializationVO; +import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationConfigurationVO; +import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationExecutionResultVO; +import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationHistoricalImportResultVO; +import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationPrecheckVO; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import jakarta.annotation.Resource; +import jakarta.validation.Valid; + +/** + * 系统初始化管理接口。 + * + *

系统基础参数、学期参数、历史数据导入、预检查和最终执行确认 + * 统一保留在同一个控制器中,不为同一业务菜单重复创建控制器。

+ */ +@RestController +@RequestMapping("/system-initialization") +public class SystemInitializationController { + + /** + * 系统初始化业务服务。 + */ + @Resource + private SystemInitializationService systemInitializationService; + + /** + * 查询当前系统基础参数。 + * + * @return 当前生效的系统基础参数 + */ + @GetMapping("/configuration") + public Result configuration() { + // 调用业务层查询当前生效配置,并使用统一结果对象返回给前端。 + return Result.success( + systemInitializationService.getCurrentConfiguration()); + } + + /** + * 保存新的系统基础参数版本。 + * + * @param dto 系统基础参数 + * @return 保存后立即生效的新版本 + */ + @PostMapping("/configuration/save") + public Result + saveConfiguration( + @Valid @RequestBody + SystemInitializationConfigurationSaveDTO dto) { + // 调用业务层校验期望版本并新增一条系统配置版本记录。 + return Result.success( + "系统基础参数保存成功", + systemInitializationService.saveConfiguration(dto)); + } + + /** + * 查询当前学期初始化参数。 + * + * @return 当前学期 + */ + @GetMapping("/semester") + public Result semester() { + // 调用业务层读取当前学期并补充统一展示名称。 + return Result.success( + systemInitializationService.getCurrentSemester()); + } + + /** + * 新增或修改指定年度和序号的学期参数。 + * + * @param dto 学期初始化参数 + * @return 保存后的学期 + */ + @PostMapping("/semester/save") + public Result saveSemester( + @Valid @RequestBody + SemesterInitializationSaveDTO dto) { + // 调用业务层完成日期冲突检查、当前学期切换和学期保存。 + return Result.success( + "学期参数保存成功", + systemInitializationService.saveSemester(dto)); + } + + /** + * 导入历史部别、教研室隶属关系和部别人员基础数据。 + * + * @param dto 历史数据 Excel 文件 + * @return 各类数据实际导入数量 + */ + @PostMapping( + value = "/history/import", + consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public Result + importHistoricalData( + @Valid SystemInitializationHistoricalImportDTO dto) { + /* + * 调用业务层解析并预校验整份工作簿, + * 全部数据通过后在同一事务中写入四张基础数据表。 + */ + return Result.success( + "历史基础数据导入成功", + systemInitializationService + .importHistoricalData(dto)); + } + + /** + * 执行系统初始化前的完整预检查。 + * + * @return 配置、学期、组织数据和引用完整性检查结果 + */ + @GetMapping("/precheck") + public Result precheck() { + // 调用业务层汇总所有初始化必需检查项。 + return Result.success( + systemInitializationService.precheck()); + } + + /** + * 在明确确认且预检查通过后完成系统初始化确认。 + * + * @param dto 页面预检查快照和确认标志 + * @return 初始化执行结果 + */ + @PostMapping("/execute") + public Result execute( + @Valid @RequestBody + SystemInitializationExecutionDTO dto) { + /* + * 调用业务层重新执行预检查, + * 同时校验配置版本和当前学期未在页面确认后发生变化。 + */ + return Result.success( + "系统初始化完成", + systemInitializationService.execute(dto)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingMaterialController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingMaterialController.java new file mode 100644 index 000000000..021cb3cf3 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingMaterialController.java @@ -0,0 +1,100 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.JCXX; +import com.roomroot.jwgl.service.TeachingMaterialService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 教材信息控制器 + */ +@RestController +@RequestMapping("/teachingMaterial") +public class TeachingMaterialController { + + @Resource + private TeachingMaterialService teachingMaterialService; + + /** + * 新增教材信息 + */ + @PostMapping("/add") + public Result add(@RequestBody JCXX jcxx) { + teachingMaterialService.add(jcxx); + return Result.success(); + } + + /** + * 删除教材信息 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("id") String id) { + teachingMaterialService.delete(id); + return Result.success(); + } + + /** + * 更新教材信息 + */ + @PostMapping("/update") + public Result update(@RequestBody JCXX jcxx) { + teachingMaterialService.update(jcxx); + return Result.success(); + } + + /** + * 根据编号查询 + */ + @GetMapping("/getByBh") + public Result getByBh(@RequestParam("bh") String bh) { + return Result.success(teachingMaterialService.getByBh(bh)); + } + + /** + * 根据ID查询 + */ + @GetMapping("/getById") + public Result getById(@RequestParam("id") String id) { + return Result.success(teachingMaterialService.getById(id)); + } + + /** + * 分页查询教材信息列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, JCXX jcxx) { + return Result.success(teachingMaterialService.pageList(query, jcxx)); + } + + /** + * 查询所有教材信息列表 + */ + @GetMapping("/all") + public Result> all(JCXX jcxx) { + return Result.success(teachingMaterialService.list(jcxx)); + } + + /** + * 导入Excel文件 + */ + @PostMapping("/import") + public Result importExcel(@RequestParam("file") MultipartFile file) { + int count = teachingMaterialService.importExcel(file); + return Result.success("导入成功", count); + } + + /** + * 下载模板文件 + */ + @GetMapping("/template") + public void downloadTemplate(HttpServletResponse response) { + teachingMaterialService.downloadTemplate(response); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingPlanController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingPlanController.java new file mode 100644 index 000000000..cb363ceee --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingPlanController.java @@ -0,0 +1,110 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.JXSSJH; +import com.roomroot.jwgl.service.TeachingPlanService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.List; + +/** + * 导入数据表-教学实施计划控制器 + */ +@RestController +@RequestMapping("/teachingPlan") +public class TeachingPlanController { + + @Resource + private TeachingPlanService teachingPlanService; + + /** + * 新增 + */ + @PostMapping("/add") + public Result add(@RequestBody JXSSJH jxssjh) { + teachingPlanService.add(jxssjh); + return Result.success(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bsh") String bsh) { + teachingPlanService.delete(bsh); + return Result.success(); + } + + /** + * 更新 + */ + @PostMapping("/update") + public Result update(@RequestBody JXSSJH drsjbjxssjh) { + teachingPlanService.update(drsjbjxssjh); + return Result.success(); + } + + /** + * 根据标识号查询 + */ + @GetMapping("/get") + public Result getByBsh(@RequestParam("bsh") String bsh) { + return Result.success(teachingPlanService.getByBsh(bsh)); + } + + /** + * 分页查询列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, JXSSJH jxssjh) { + return Result.success(teachingPlanService.pageList(query, jxssjh)); + } + + /** + * 查询所有列表 + */ + @GetMapping("/all") + public Result> all(JXSSJH jxssjh) { + return Result.success(teachingPlanService.list(jxssjh)); + } + + /** + * Excel导入教学实施计划。 + * 标识号和登录编号自动生成,其他字段从Excel读取。 + */ + @PostMapping("/import") + public Result importFromExcel(@RequestParam("file") MultipartFile file) throws Exception { + int count = teachingPlanService.importFromExcel(file); + return Result.success("导入成功,共" + count + "条记录", count); + } + + /** + * 下载教学实施计划导入模板 + */ + @GetMapping("/template") + public ResponseEntity downloadTemplate() throws IOException { + ClassPathResource resource = new ClassPathResource("template/教学实施计划导入模板.xlsx"); + if (!resource.exists()) { + return ResponseEntity.notFound().build(); + } + String fileName = URLEncoder.encode("教学实施计划导入模板.xlsx", "UTF-8"); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName); + + return ResponseEntity.ok() + .headers(headers) + .body(resource); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockController.java new file mode 100644 index 000000000..d329595d6 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockController.java @@ -0,0 +1,81 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.JCKCD; +import com.roomroot.jwgl.service.TeachingStockService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 教材库存单控制器 + */ +@RestController +@RequestMapping("/teachingStock") +public class TeachingStockController { + + @Resource + private TeachingStockService teachingStockService; + + /** + * 新增教材库存单 + */ + @PostMapping("/add") + public Result add(@RequestBody JCKCD jckcd) { + teachingStockService.add(jckcd); + return Result.success(); + } + + /** + * 删除教材库存单 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("id") String id) { + teachingStockService.delete(id); + return Result.success(); + } + + /** + * 更新教材库存单 + */ + @PostMapping("/update") + public Result update(@RequestBody JCKCD jckcd) { + teachingStockService.update(jckcd); + return Result.success(); + } + + /** + * 根据ID查询 + */ + @GetMapping("/get") + public Result getById(@RequestParam("id") String id) { + return Result.success(teachingStockService.getById(id)); + } + + /** + * 根据编号查询 + */ + @GetMapping("/getByBh") + public Result getByBh(@RequestParam("bh") String bh) { + return Result.success(teachingStockService.getByBh(bh)); + } + + /** + * 分页查询教材库存单列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, JCKCD jckcd) { + return Result.success(teachingStockService.pageList(query, jckcd)); + } + + /** + * 查询所有教材库存单列表 + */ + @GetMapping("/all") + public Result> all(JCKCD jckcd) { + return Result.success(teachingStockService.list(jckcd)); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockDetailController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockDetailController.java new file mode 100644 index 000000000..361207fa2 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingStockDetailController.java @@ -0,0 +1,89 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.JCKCMXX; +import com.roomroot.jwgl.service.TeachingStockDetailService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 教材库存明细项控制器 + */ +@RestController +@RequestMapping("/teachingStock/detail") +public class TeachingStockDetailController { + + @Resource + private TeachingStockDetailService teachingStockDetailService; + + /** + * 新增教材库存明细项 + */ + @PostMapping("/add") + public Result add(@RequestBody JCKCMXX jckcmxx) { + teachingStockDetailService.add(jckcmxx); + return Result.success(); + } + + /** + * 删除教材库存明细项 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("id") String id) { + teachingStockDetailService.delete(id); + return Result.success(); + } + + /** + * 更新教材库存明细项 + */ + @PostMapping("/update") + public Result update(@RequestBody JCKCMXX jckcmxx) { + teachingStockDetailService.update(jckcmxx); + return Result.success(); + } + + /** + * 根据ID查询 + */ + @GetMapping("/get") + public Result getById(@RequestParam("id") String id) { + return Result.success(teachingStockDetailService.getById(id)); + } + + /** + * 根据编号查询 + */ + @GetMapping("/getByBh") + public Result getByBh(@RequestParam("bh") String bh) { + return Result.success(teachingStockDetailService.getByBh(bh)); + } + + /** + * 根据教材库存单编号查询 + */ + @GetMapping("/getByJckcdbbh") + public Result> getByJckcdbbh(@RequestParam("jckcdbbh") String jckcdbbh) { + return Result.success(teachingStockDetailService.getByJckcdbbh(jckcdbbh)); + } + + /** + * 分页查询教材库存明细项列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, JCKCMXX jckcmxx) { + return Result.success(teachingStockDetailService.pageList(query, jckcmxx)); + } + + /** + * 查询所有教材库存明细项列表 + */ + @GetMapping("/all") + public Result> all(JCKCMXX jckcmxx) { + return Result.success(teachingStockDetailService.list(jckcmxx)); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingTaskController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingTaskController.java new file mode 100644 index 000000000..1b56835f3 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TeachingTaskController.java @@ -0,0 +1,95 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.JXRW; +import com.roomroot.jwgl.service.TeachingTaskService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 教学任务控制器 + */ +@RestController +@RequestMapping("/teachingTask") +public class TeachingTaskController { + + @Resource + private TeachingTaskService teachingTaskService; + + /** + * 新增 + */ + @PostMapping("/add") + public Result add(@RequestBody JXRW jxrw) { + teachingTaskService.add(jxrw); + return Result.success(); + } + + /** + * 删除 + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + teachingTaskService.delete(bh); + return Result.success(); + } + + /** + * 批量删除 + */ + @PostMapping("/batchDelete") + public Result batchDelete(@RequestBody List bhList) { + teachingTaskService.batchDelete(bhList); + return Result.success(); + } + + /** + * 更新 + */ + @PostMapping("/update") + public Result update(@RequestBody JXRW jxrw) { + teachingTaskService.update(jxrw); + return Result.success(); + } + + /** + * 根据编号查询 + */ + @GetMapping("/get") + public Result getByBh(@RequestParam("bh") String bh) { + return Result.success(teachingTaskService.getByBh(bh)); + } + + /** + * 分页查询列表 + */ + @GetMapping("/list") + public Result> list(PageQuery query, JXRW jxrw) { + return Result.success(teachingTaskService.pageList(query, jxrw)); + } + + /** + * 查询所有有效数据(DEL_FLAG = 0) + */ + @GetMapping("/all") + public Result> all() { + return Result.success(teachingTaskService.listAllValid()); + } + + /** + * 发布教学任务及教研室任务书 + * 根据教学任务编号修改状态为"发布",并批量更新所有关联的教研室任务书状态为"发布" + * + * @param bh 教学任务编号 + * @return 更新数量 + */ + @PostMapping("/batchPublish") + public Result batchPublish(@RequestParam("bh") String bh) { + int count = teachingTaskService.batchPublish(bh); + return Result.success(count); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TrainingProgramController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TrainingProgramController.java new file mode 100644 index 000000000..c5c6d27bd --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/TrainingProgramController.java @@ -0,0 +1,118 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.ZYB; +import com.roomroot.jwgl.service.TrainingProgramService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; + +/** + * 培养方案管理控制器 + *

+ * 提供专业/培养方案的增加、删除(逻辑删除)、更新、查询等功能管理接口。 + * 对应数据表:专业表 + * 多版本通过培训类型(培训类型)字段区分。 + *

+ *

+ * 接口规范:仅使用 GET 和 POST 两种方法,参数通过请求体或查询参数传递,不拼接在路径中。 + *

+ */ +@RestController +@RequestMapping("/training") +public class TrainingProgramController { + + @Resource + private TrainingProgramService trainingProgramService; + + /** + * 新增培养方案(专业) + * + * @param entity 专业表实体 + * @return 操作结果 + */ + @PostMapping("/add") + public Result add(@RequestBody ZYB entity) { + trainingProgramService.add(entity); + return Result.success(); + } + + /** + * 停用(逻辑删除)培养方案 + *

将指定记录的 停用 字段置为 true(停用=false为启用,停用=true为已停用/删除)。

+ * + * @param zydh 专业代号 + * @return 操作结果 + */ + @PostMapping("/disable") + public Result disable(@RequestParam("zydh") String zydh) { + trainingProgramService.disable(zydh); + return Result.success(); + } + + /** + * 更新培养方案 + * + * @param entity 专业表实体(含专业代号) + * @return 操作结果 + */ + @PostMapping("/update") + public Result update(@RequestBody ZYB entity) { + trainingProgramService.update(entity); + return Result.success(); + } + + /** + * 根据专业代号查询培养方案详情 + * + * @param zydh 专业代号 + * @return 培养方案详情 + */ + @GetMapping("/get") + public Result getById(@RequestParam("zydh") String zydh) { + ZYB entity = trainingProgramService.getById(zydh); + return Result.success(entity); + } + + /** + * 分页条件查询培养方案列表 + *

+ * 支持模糊查询参数: + *

    + *
  • zymc — 专业名称(模糊匹配)
  • + *
  • zydm — 专业代码(模糊匹配)
  • + *
  • pxlx — 培训类型(精确匹配,用于版本筛选)
  • + *
  • pxcc — 培训层次(精确匹配)
  • + *
+ *

+ * + * @param pageNum 页码(默认1) + * @param pageSize 每页条数(默认20) + * @param zymc 专业名称(可选) + * @param zydm 专业代码(可选) + * @param pxlx 培训类型(可选) + * @param pxcc 培训层次(可选) + * @return 分页结果 + */ + @GetMapping("/list") + public Result> list( + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "20") Integer pageSize, + @RequestParam(required = false) String zymc, + @RequestParam(required = false) String zydm, + @RequestParam(required = false) String pxlx, + @RequestParam(required = false) String pxcc) { + PageQuery query = new PageQuery(); + query.setPageNum(pageNum); + query.setPageSize(pageSize); + ZYB cond = new ZYB(); + cond.setZymc(zymc); + cond.setZydm(zydm); + cond.setPxlx(pxlx); + cond.setPxcc(pxcc); + PageResult pageResult = trainingProgramService.pageList(query, cond); + return Result.success(pageResult); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ZYJXJHBController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ZYJXJHBController.java new file mode 100644 index 000000000..1bfe5a55a --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/jwgl/ZYJXJHBController.java @@ -0,0 +1,84 @@ +package com.roomroot.web.controller.jwgl; + +import com.roomroot.jwgl.entity.ZYJXJHB; +import com.roomroot.jwgl.service.ZYJXJHBService; +import com.roomroot.jwgl.unit.Result; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 专业教学计划表控制器 + */ +@RestController +@RequestMapping("/zyjxjhb") +public class ZYJXJHBController { + + @Resource + private ZYJXJHBService zyjxjhbService; + + /** + * 新增 + */ + @PostMapping("/add") + public Result add(@RequestBody ZYJXJHB zyjxjhb) { + zyjxjhbService.add(zyjxjhb); + return Result.success(); + } + + /** + * 删除(停用) + */ + @PostMapping("/delete") + public Result delete(@RequestParam("bh") String bh) { + zyjxjhbService.delete(bh); + return Result.success(); + } + + /** + * 批量删除(停用) + */ + @PostMapping("/batchDelete") + public Result batchDelete(@RequestBody List bhList) { + zyjxjhbService.batchDelete(bhList); + return Result.success(); + } + + /** + * 更新 + */ + @PostMapping("/update") + public Result update(@RequestBody ZYJXJHB zyjxjhb) { + zyjxjhbService.update(zyjxjhb); + return Result.success(); + } + + /** + * 根据编号查询 + */ + @GetMapping("/get") + public Result getByBh(@RequestParam("bh") String bh) { + return Result.success(zyjxjhbService.getByBh(bh)); + } + + /** + * 查询所有数据 + */ + @GetMapping("/list") + public Result> list() { + return Result.success(zyjxjhbService.list()); + } + + /** + * 根据专业代号和停用标识查询 + * + * @param zydh 专业代号 + * @param ty 停用标识(0-正常,1-停用) + */ + @GetMapping("/listByZydhAndTy") + public Result> listByZydhAndTy(@RequestParam("zydh") String zydh, + @RequestParam("ty") Integer ty) { + return Result.success(zyjxjhbService.listByZydhAndTy(zydh, ty)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/CacheController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/CacheController.java new file mode 100644 index 000000000..9f96ad94d --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/CacheController.java @@ -0,0 +1,122 @@ +package com.roomroot.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisCallback; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.domain.SysCache; + +/** + * 缓存监控 + * + * @author roomroot + */ +@RestController +@RequestMapping("/monitor/cache") +public class CacheController +{ + @Autowired + private RedisTemplate redisTemplate; + + private final static List caches = new ArrayList(); + { + caches.add(new SysCache(CacheConstants.LOGIN_TOKEN_KEY, "用户信息")); + caches.add(new SysCache(CacheConstants.SYS_CONFIG_KEY, "配置信息")); + caches.add(new SysCache(CacheConstants.SYS_DICT_KEY, "数据字典")); + caches.add(new SysCache(CacheConstants.CAPTCHA_CODE_KEY, "验证码")); + caches.add(new SysCache(CacheConstants.REPEAT_SUBMIT_KEY, "防重提交")); + caches.add(new SysCache(CacheConstants.RATE_LIMIT_KEY, "限流处理")); + caches.add(new SysCache(CacheConstants.PWD_ERR_CNT_KEY, "密码错误次数")); + } + + @SuppressWarnings("deprecation") + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Properties info = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info()); + Properties commandStats = (Properties) redisTemplate.execute((RedisCallback) connection -> connection.info("commandstats")); + Object dbSize = redisTemplate.execute((RedisCallback) connection -> connection.dbSize()); + + Map result = new HashMap<>(3); + result.put("info", info); + result.put("dbSize", dbSize); + + List> pieList = new ArrayList<>(); + commandStats.stringPropertyNames().forEach(key -> { + Map data = new HashMap<>(2); + String property = commandStats.getProperty(key); + data.put("name", StringUtils.removeStart(key, "cmdstat_")); + data.put("value", StringUtils.substringBetween(property, "calls=", ",usec")); + pieList.add(data); + }); + result.put("commandStats", pieList); + return AjaxResult.success(result); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getNames") + public AjaxResult cache() + { + return AjaxResult.success(caches); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getKeys/{cacheName}") + public AjaxResult getCacheKeys(@PathVariable String cacheName) + { + Set cacheKeys = redisTemplate.keys(cacheName + "*"); + return AjaxResult.success(new TreeSet<>(cacheKeys)); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @GetMapping("/getValue/{cacheName}/{cacheKey}") + public AjaxResult getCacheValue(@PathVariable String cacheName, @PathVariable String cacheKey) + { + String cacheValue = redisTemplate.opsForValue().get(cacheKey); + SysCache sysCache = new SysCache(cacheName, cacheKey, cacheValue); + return AjaxResult.success(sysCache); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheName/{cacheName}") + public AjaxResult clearCacheName(@PathVariable String cacheName) + { + Collection cacheKeys = redisTemplate.keys(cacheName + "*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheKey/{cacheKey}") + public AjaxResult clearCacheKey(@PathVariable String cacheKey) + { + redisTemplate.delete(cacheKey); + return AjaxResult.success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:cache:list')") + @DeleteMapping("/clearCacheAll") + public AjaxResult clearCacheAll() + { + Collection cacheKeys = redisTemplate.keys("*"); + redisTemplate.delete(cacheKeys); + return AjaxResult.success(); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/ServerController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/ServerController.java new file mode 100644 index 000000000..52ebf1fd2 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/ServerController.java @@ -0,0 +1,27 @@ +package com.roomroot.web.controller.monitor; + +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.framework.web.domain.Server; + +/** + * 服务器监控 + * + * @author roomroot + */ +@RestController +@RequestMapping("/monitor/server") +public class ServerController +{ + @PreAuthorize("@ss.hasPermi('monitor:server:list')") + @GetMapping() + public AjaxResult getInfo() throws Exception + { + Server server = new Server(); + server.copyTo(); + return AjaxResult.success(server); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/SysLogininforController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/SysLogininforController.java new file mode 100644 index 000000000..6423b56b5 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/SysLogininforController.java @@ -0,0 +1,92 @@ +package com.roomroot.web.controller.monitor; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.annotation.Anonymous; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.poi.ExcelUtil; +import com.roomroot.framework.web.service.SysPasswordService; +import com.roomroot.system.domain.SysLogininfor; +import com.roomroot.system.service.ISysLogininforService; + +/** + * 系统访问记录 + * + * @author roomroot + */ +@RestController +@RequestMapping("/monitor/logininfor") +public class SysLogininforController extends BaseController +{ + @Autowired + private ISysLogininforService logininforService; + + @Autowired + private SysPasswordService passwordService; + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:list')") + @GetMapping("/list") + public TableDataInfo list(SysLogininfor logininfor) + { + startPage(); + List list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + + @Anonymous + @GetMapping("/homeList") + public TableDataInfo homeList(SysLogininfor logininfor) + { + startPage(); + List list = logininforService.selectLogininforList(logininfor); + return getDataTable(list); + } + + @Log(title = "登录日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('monitor:logininfor:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysLogininfor logininfor) + { + List list = logininforService.selectLogininforList(logininfor); + ExcelUtil util = new ExcelUtil(SysLogininfor.class); + util.exportExcel(response, list, "登录日志"); + } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{infoIds}") + public AjaxResult remove(@PathVariable Long[] infoIds) + { + return toAjax(logininforService.deleteLogininforByIds(infoIds)); + } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:remove')") + @Log(title = "登录日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + logininforService.cleanLogininfor(); + return success(); + } + + @PreAuthorize("@ss.hasPermi('monitor:logininfor:unlock')") + @Log(title = "账户解锁", businessType = BusinessType.OTHER) + @GetMapping("/unlock/{userName}") + public AjaxResult unlock(@PathVariable("userName") String userName) + { + passwordService.clearLoginRecordCache(userName); + return success(); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/SysOperlogController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/SysOperlogController.java new file mode 100644 index 000000000..977569ad8 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/SysOperlogController.java @@ -0,0 +1,69 @@ +package com.roomroot.web.controller.monitor; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.poi.ExcelUtil; +import com.roomroot.system.domain.SysOperLog; +import com.roomroot.system.service.ISysOperLogService; + +/** + * 操作日志记录 + * + * @author roomroot + */ +@RestController +@RequestMapping("/monitor/operlog") +public class SysOperlogController extends BaseController +{ + @Autowired + private ISysOperLogService operLogService; + + @PreAuthorize("@ss.hasPermi('monitor:operlog:list')") + @GetMapping("/list") + public TableDataInfo list(SysOperLog operLog) + { + startPage(); + List list = operLogService.selectOperLogList(operLog); + return getDataTable(list); + } + + @Log(title = "操作日志", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('monitor:operlog:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysOperLog operLog) + { + List list = operLogService.selectOperLogList(operLog); + ExcelUtil util = new ExcelUtil(SysOperLog.class); + util.exportExcel(response, list, "操作日志"); + } + + @Log(title = "操作日志", businessType = BusinessType.DELETE) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/{operIds}") + public AjaxResult remove(@PathVariable Long[] operIds) + { + return toAjax(operLogService.deleteOperLogByIds(operIds)); + } + + @Log(title = "操作日志", businessType = BusinessType.CLEAN) + @PreAuthorize("@ss.hasPermi('monitor:operlog:remove')") + @DeleteMapping("/clean") + public AjaxResult clean() + { + operLogService.cleanOperLog(); + return success(); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/SysUserOnlineController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/SysUserOnlineController.java new file mode 100644 index 000000000..2972e80d9 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/monitor/SysUserOnlineController.java @@ -0,0 +1,83 @@ +package com.roomroot.web.controller.monitor; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.core.redis.RedisCache; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.domain.SysUserOnline; +import com.roomroot.system.service.ISysUserOnlineService; + +/** + * 在线用户监控 + * + * @author roomroot + */ +@RestController +@RequestMapping("/monitor/online") +public class SysUserOnlineController extends BaseController +{ + @Autowired + private ISysUserOnlineService userOnlineService; + + @Autowired + private RedisCache redisCache; + + @PreAuthorize("@ss.hasPermi('monitor:online:list')") + @GetMapping("/list") + public TableDataInfo list(String ipaddr, String userName) + { + Collection keys = redisCache.keys(CacheConstants.LOGIN_TOKEN_KEY + "*"); + List userOnlineList = new ArrayList(); + for (String key : keys) + { + LoginUser user = redisCache.getCacheObject(key); + if (StringUtils.isNotEmpty(ipaddr) && StringUtils.isNotEmpty(userName)) + { + userOnlineList.add(userOnlineService.selectOnlineByInfo(ipaddr, userName, user)); + } + else if (StringUtils.isNotEmpty(ipaddr)) + { + userOnlineList.add(userOnlineService.selectOnlineByIpaddr(ipaddr, user)); + } + else if (StringUtils.isNotEmpty(userName) && StringUtils.isNotNull(user.getUser())) + { + userOnlineList.add(userOnlineService.selectOnlineByUserName(userName, user)); + } + else + { + userOnlineList.add(userOnlineService.loginUserToUserOnline(user)); + } + } + Collections.reverse(userOnlineList); + userOnlineList.removeAll(Collections.singleton(null)); + return getDataTable(userOnlineList); + } + + /** + * 强退用户 + */ + @PreAuthorize("@ss.hasPermi('monitor:online:forceLogout')") + @Log(title = "在线用户", businessType = BusinessType.FORCE) + @DeleteMapping("/{tokenId}") + public AjaxResult forceLogout(@PathVariable String tokenId) + { + redisCache.deleteObject(CacheConstants.LOGIN_TOKEN_KEY + tokenId); + return success(); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysConfigController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysConfigController.java new file mode 100644 index 000000000..6748219d0 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysConfigController.java @@ -0,0 +1,133 @@ +package com.roomroot.web.controller.system; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.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.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.poi.ExcelUtil; +import com.roomroot.system.domain.SysConfig; +import com.roomroot.system.service.ISysConfigService; + +/** + * 参数配置 信息操作处理 + * + * @author roomroot + */ +@RestController +@RequestMapping("/system/config") +public class SysConfigController extends BaseController +{ + @Autowired + private ISysConfigService configService; + + /** + * 获取参数配置列表 + */ + @PreAuthorize("@ss.hasPermi('system:config:list')") + @GetMapping("/list") + public TableDataInfo list(SysConfig config) + { + startPage(); + List list = configService.selectConfigList(config); + return getDataTable(list); + } + + @Log(title = "参数管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:config:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysConfig config) + { + List list = configService.selectConfigList(config); + ExcelUtil util = new ExcelUtil(SysConfig.class); + util.exportExcel(response, list, "参数数据"); + } + + /** + * 根据参数编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:config:query')") + @GetMapping(value = "/{configId}") + public AjaxResult getInfo(@PathVariable Long configId) + { + return success(configService.selectConfigById(configId)); + } + + /** + * 根据参数键名查询参数值 + */ + @GetMapping(value = "/configKey/{configKey}") + public AjaxResult getConfigKey(@PathVariable String configKey) + { + return success(configService.selectConfigByKey(configKey)); + } + + /** + * 新增参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:add')") + @Log(title = "参数管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("新增参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setCreateBy(getUsername()); + return toAjax(configService.insertConfig(config)); + } + + /** + * 修改参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:edit')") + @Log(title = "参数管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysConfig config) + { + if (!configService.checkConfigKeyUnique(config)) + { + return error("修改参数'" + config.getConfigName() + "'失败,参数键名已存在"); + } + config.setUpdateBy(getUsername()); + return toAjax(configService.updateConfig(config)); + } + + /** + * 删除参数配置 + */ + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{configIds}") + public AjaxResult remove(@PathVariable Long[] configIds) + { + configService.deleteConfigByIds(configIds); + return success(); + } + + /** + * 刷新参数缓存 + */ + @PreAuthorize("@ss.hasPermi('system:config:remove')") + @Log(title = "参数管理", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + configService.resetConfigCache(); + return success(); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysDeptController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysDeptController.java new file mode 100644 index 000000000..10920260f --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysDeptController.java @@ -0,0 +1,147 @@ +package com.roomroot.web.controller.system; + +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.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.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.entity.SysDept; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.service.ISysDeptService; + +/** + * 部门信息 + * + * @author roomroot + */ +@RestController +@RequestMapping("/system/dept") +public class SysDeptController extends BaseController +{ + @Autowired + private ISysDeptService deptService; + + /** + * 获取部门列表 + */ + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list") + public AjaxResult list(SysDept dept) + { + List depts = deptService.selectDeptList(dept); + return success(depts); + } + + /** + * 查询部门列表(排除节点) + */ + @PreAuthorize("@ss.hasPermi('system:dept:list')") + @GetMapping("/list/exclude/{deptId}") + public AjaxResult excludeChild(@PathVariable(value = "deptId", required = false) Long deptId) + { + List depts = deptService.selectDeptList(new SysDept()); + depts.removeIf(d -> d.getDeptId().intValue() == deptId || ArrayUtils.contains(StringUtils.split(d.getAncestors(), ","), deptId + "")); + return success(depts); + } + + /** + * 根据部门编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:dept:query')") + @GetMapping(value = "/{deptId}") + public AjaxResult getInfo(@PathVariable Long deptId) + { + deptService.checkDeptDataScope(deptId); + return success(deptService.selectDeptById(deptId)); + } + + /** + * 新增部门 + */ + @PreAuthorize("@ss.hasPermi('system:dept:add')") + @Log(title = "部门管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDept dept) + { + if (!deptService.checkDeptNameUnique(dept)) + { + return error("新增部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + dept.setCreateBy(getUsername()); + return toAjax(deptService.insertDept(dept)); + } + + /** + * 修改部门 + */ + @PreAuthorize("@ss.hasPermi('system:dept:edit')") + @Log(title = "部门管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDept dept) + { + Long deptId = dept.getDeptId(); + deptService.checkDeptDataScope(deptId); + if (!deptService.checkDeptNameUnique(dept)) + { + return error("修改部门'" + dept.getDeptName() + "'失败,部门名称已存在"); + } + else if (dept.getParentId().equals(deptId)) + { + return error("修改部门'" + dept.getDeptName() + "'失败,上级部门不能是自己"); + } + else if (StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()) && deptService.selectNormalChildrenDeptById(deptId) > 0) + { + return error("该部门包含未停用的子部门!"); + } + dept.setUpdateBy(getUsername()); + return toAjax(deptService.updateDept(dept)); + } + + /** + * 保存部门排序 + */ + @PreAuthorize("@ss.hasPermi('system:dept:edit')") + @Log(title = "保存部门排序", businessType = BusinessType.UPDATE) + @PutMapping("/updateSort") + public AjaxResult updateSort(@RequestBody Map params) + { + String[] deptIds = params.get("deptIds").split(","); + String[] orderNums = params.get("orderNums").split(","); + deptService.updateDeptSort(deptIds, orderNums); + return success(); + } + + /** + * 删除部门 + */ + @PreAuthorize("@ss.hasPermi('system:dept:remove')") + @Log(title = "部门管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{deptId}") + public AjaxResult remove(@PathVariable Long deptId) + { + if (deptService.hasChildByDeptId(deptId)) + { + return warn("存在下级部门,不允许删除"); + } + if (deptService.checkDeptExistUser(deptId)) + { + return warn("部门存在用户,不允许删除"); + } + deptService.checkDeptDataScope(deptId); + return toAjax(deptService.deleteDeptById(deptId)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysDictDataController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysDictDataController.java new file mode 100644 index 000000000..46ba7d3b4 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysDictDataController.java @@ -0,0 +1,121 @@ +package com.roomroot.web.controller.system; + +import java.util.ArrayList; +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.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.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.entity.SysDictData; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.poi.ExcelUtil; +import com.roomroot.system.service.ISysDictDataService; +import com.roomroot.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author roomroot + */ +@RestController +@RequestMapping("/system/dict/data") +public class SysDictDataController extends BaseController +{ + @Autowired + private ISysDictDataService dictDataService; + + @Autowired + private ISysDictTypeService dictTypeService; + + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictData dictData) + { + startPage(); + List list = dictDataService.selectDictDataList(dictData); + return getDataTable(list); + } + + @Log(title = "字典数据", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:dict:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictData dictData) + { + List list = dictDataService.selectDictDataList(dictData); + ExcelUtil util = new ExcelUtil(SysDictData.class); + util.exportExcel(response, list, "字典数据"); + } + + /** + * 查询字典数据详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictCode}") + public AjaxResult getInfo(@PathVariable Long dictCode) + { + return success(dictDataService.selectDictDataById(dictCode)); + } + + /** + * 根据字典类型查询字典数据信息 + */ + @GetMapping(value = "/type/{dictType}") + public AjaxResult dictType(@PathVariable String dictType) + { + List data = dictTypeService.selectDictDataByType(dictType); + if (StringUtils.isNull(data)) + { + data = new ArrayList(); + } + return success(data); + } + + /** + * 新增字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典数据", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictData dict) + { + dict.setCreateBy(getUsername()); + return toAjax(dictDataService.insertDictData(dict)); + } + + /** + * 修改保存字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典数据", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictData dict) + { + dict.setUpdateBy(getUsername()); + return toAjax(dictDataService.updateDictData(dict)); + } + + /** + * 删除字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictCodes}") + public AjaxResult remove(@PathVariable Long[] dictCodes) + { + dictDataService.deleteDictDataByIds(dictCodes); + return success(); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysDictTypeController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysDictTypeController.java new file mode 100644 index 000000000..709ac9b1e --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysDictTypeController.java @@ -0,0 +1,131 @@ +package com.roomroot.web.controller.system; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.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.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.entity.SysDictType; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.poi.ExcelUtil; +import com.roomroot.system.service.ISysDictTypeService; + +/** + * 数据字典信息 + * + * @author roomroot + */ +@RestController +@RequestMapping("/system/dict/type") +public class SysDictTypeController extends BaseController +{ + @Autowired + private ISysDictTypeService dictTypeService; + + @PreAuthorize("@ss.hasPermi('system:dict:list')") + @GetMapping("/list") + public TableDataInfo list(SysDictType dictType) + { + startPage(); + List list = dictTypeService.selectDictTypeList(dictType); + return getDataTable(list); + } + + @Log(title = "字典类型", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:dict:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysDictType dictType) + { + List list = dictTypeService.selectDictTypeList(dictType); + ExcelUtil util = new ExcelUtil(SysDictType.class); + util.exportExcel(response, list, "字典类型"); + } + + /** + * 查询字典类型详细 + */ + @PreAuthorize("@ss.hasPermi('system:dict:query')") + @GetMapping(value = "/{dictId}") + public AjaxResult getInfo(@PathVariable Long dictId) + { + return success(dictTypeService.selectDictTypeById(dictId)); + } + + /** + * 新增字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:add')") + @Log(title = "字典类型", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysDictType dict) + { + if (!dictTypeService.checkDictTypeUnique(dict)) + { + return error("新增字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setCreateBy(getUsername()); + return toAjax(dictTypeService.insertDictType(dict)); + } + + /** + * 修改字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:edit')") + @Log(title = "字典类型", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysDictType dict) + { + if (!dictTypeService.checkDictTypeUnique(dict)) + { + return error("修改字典'" + dict.getDictName() + "'失败,字典类型已存在"); + } + dict.setUpdateBy(getUsername()); + return toAjax(dictTypeService.updateDictType(dict)); + } + + /** + * 删除字典类型 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.DELETE) + @DeleteMapping("/{dictIds}") + public AjaxResult remove(@PathVariable Long[] dictIds) + { + dictTypeService.deleteDictTypeByIds(dictIds); + return success(); + } + + /** + * 刷新字典缓存 + */ + @PreAuthorize("@ss.hasPermi('system:dict:remove')") + @Log(title = "字典类型", businessType = BusinessType.CLEAN) + @DeleteMapping("/refreshCache") + public AjaxResult refreshCache() + { + dictTypeService.resetDictCache(); + return success(); + } + + /** + * 获取字典选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List dictTypes = dictTypeService.selectDictTypeAll(); + return success(dictTypes); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysIndexController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysIndexController.java new file mode 100644 index 000000000..475d14274 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysIndexController.java @@ -0,0 +1,64 @@ +package com.roomroot.web.controller.system; + +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.config.RoomRootConfig; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.service.ISysUserService; + +/** + * 首页 + * + * @author roomroot + */ +@RestController +public class SysIndexController +{ + /** 系统基础配置 */ + @Autowired + private RoomRootConfig RoomRootConfig; + + @Autowired + private ISysUserService userService; + + /** + * 访问首页,提示语 + */ + @RequestMapping("/") + public String index() + { + return StringUtils.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。", RoomRootConfig.getName(), RoomRootConfig.getVersion()); + } + + /** + * 解锁屏幕 + */ + @PostMapping("/unlockscreen") + public AjaxResult unlockScreen(@RequestBody Map body) + { + String password = body.get("password"); + if (StringUtils.isEmpty(password)) + { + return AjaxResult.error("密码不能为空"); + } + String username = SecurityUtils.getUsername(); + SysUser user = userService.selectUserByUserName(username); + if (user == null) + { + return AjaxResult.error("服务器超时,请重新登录"); + } + if (!SecurityUtils.matchesPassword(password, user.getPassword())) + { + return AjaxResult.error("密码错误,请重新输入"); + } + + return AjaxResult.success("解锁成功"); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysLoginController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysLoginController.java new file mode 100644 index 000000000..4de1ca3f8 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysLoginController.java @@ -0,0 +1,138 @@ +package com.roomroot.web.controller.system; + +import java.util.Date; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.entity.SysMenu; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.core.domain.model.LoginBody; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.framework.web.service.SysLoginService; +import com.roomroot.framework.web.service.SysPermissionService; +import com.roomroot.framework.web.service.TokenService; +import com.roomroot.system.service.ISysConfigService; +import com.roomroot.system.service.ISysMenuService; + +/** + * 登录验证 + * + * @author roomroot + */ +@RestController +public class SysLoginController +{ + @Autowired + private SysLoginService loginService; + + @Autowired + private ISysMenuService menuService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private TokenService tokenService; + + @Autowired + private ISysConfigService configService; + + /** + * 登录方法 + * + * @param loginBody 登录信息 + * @return 结果 + */ + @PostMapping("/login") + public AjaxResult login(@RequestBody LoginBody loginBody) + { + AjaxResult ajax = AjaxResult.success(); + // 生成令牌 + String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(), + loginBody.getUuid()); + ajax.put(Constants.TOKEN, token); + return ajax; + } + + /** + * 获取用户信息 + * + * @return 用户信息 + */ + @GetMapping("getInfo") + public AjaxResult getInfo() + { + LoginUser loginUser = SecurityUtils.getLoginUser(); + SysUser user = loginUser.getUser(); + // 角色集合 + Set roles = permissionService.getRolePermission(user); + // 权限集合 + Set permissions = permissionService.getMenuPermission(user); + if (!loginUser.getPermissions().equals(permissions)) + { + loginUser.setPermissions(permissions); + tokenService.refreshToken(loginUser); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("user", user); + ajax.put("roles", roles); + ajax.put("permissions", permissions); + ajax.put("pwdChrtype", getSysAccountChrtype()); + ajax.put("isDefaultModifyPwd", initPasswordIsModify(user.getPwdUpdateDate())); + ajax.put("isPasswordExpired", passwordIsExpiration(user.getPwdUpdateDate())); + return ajax; + } + + /** + * 获取路由信息 + * + * @return 路由信息 + */ + @GetMapping("getRouters") + public AjaxResult getRouters() + { + Long userId = SecurityUtils.getUserId(); + List menus = menuService.selectMenuTreeByUserId(userId); + return AjaxResult.success(menuService.buildMenus(menus)); + } + + // 获取用户密码自定义配置规则 + public String getSysAccountChrtype() + { + return Convert.toStr(configService.selectConfigByKey("sys.account.chrtype"), "0"); + } + + // 检查初始密码是否提醒修改 + public boolean initPasswordIsModify(Date pwdUpdateDate) + { + Integer initPasswordModify = Convert.toInt(configService.selectConfigByKey("sys.account.initPasswordModify")); + return initPasswordModify != null && initPasswordModify == 1 && pwdUpdateDate == null; + } + + // 检查密码是否过期 + public boolean passwordIsExpiration(Date pwdUpdateDate) + { + Integer passwordValidateDays = Convert.toInt(configService.selectConfigByKey("sys.account.passwordValidateDays")); + if (passwordValidateDays != null && passwordValidateDays > 0) + { + if (StringUtils.isNull(pwdUpdateDate)) + { + // 如果从未修改过初始密码,直接提醒过期 + return true; + } + Date nowDate = DateUtils.getNowDate(); + return DateUtils.differentDaysByMillisecond(nowDate, pwdUpdateDate) > passwordValidateDays; + } + return false; + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysMenuController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysMenuController.java new file mode 100644 index 000000000..f458189f3 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysMenuController.java @@ -0,0 +1,165 @@ +package com.roomroot.web.controller.system; + +import java.util.List; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.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.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.entity.SysMenu; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.service.ISysMenuService; + +/** + * 菜单信息 + * + * @author roomroot + */ +@RestController +@RequestMapping("/system/menu") +public class SysMenuController extends BaseController +{ + @Autowired + private ISysMenuService menuService; + + /** + * 获取菜单列表 + */ + @PreAuthorize("@ss.hasPermi('system:menu:list')") + @GetMapping("/list") + public AjaxResult list(SysMenu menu) + { + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menus); + } + + /** + * 根据菜单编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:menu:query')") + @GetMapping(value = "/{menuId}") + public AjaxResult getInfo(@PathVariable Long menuId) + { + return success(menuService.selectMenuById(menuId)); + } + + /** + * 获取菜单下拉树列表 + */ + @GetMapping("/treeselect") + public AjaxResult treeselect(SysMenu menu) + { + List menus = menuService.selectMenuList(menu, getUserId()); + return success(menuService.buildMenuTreeSelect(menus)); + } + + /** + * 加载对应角色菜单列表树 + */ + @GetMapping(value = "/roleMenuTreeselect/{roleId}") + public AjaxResult roleMenuTreeselect(@PathVariable("roleId") Long roleId) + { + List menus = menuService.selectMenuList(getUserId()); + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", menuService.selectMenuListByRoleId(roleId)); + ajax.put("menus", menuService.buildMenuTreeSelect(menus)); + return ajax; + } + + /** + * 新增菜单 + */ + @PreAuthorize("@ss.hasPermi('system:menu:add')") + @Log(title = "菜单管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysMenu menu) + { + if (!menuService.checkMenuNameUnique(menu)) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + else if (!menuService.checkRouteConfigUnique(menu)) + { + return error("新增菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在"); + } + menu.setCreateBy(getUsername()); + return toAjax(menuService.insertMenu(menu)); + } + + /** + * 修改菜单 + */ + @PreAuthorize("@ss.hasPermi('system:menu:edit')") + @Log(title = "菜单管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysMenu menu) + { + if (!menuService.checkMenuNameUnique(menu)) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,菜单名称已存在"); + } + else if (UserConstants.YES_FRAME.equals(menu.getIsFrame()) && !StringUtils.ishttp(menu.getPath())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,地址必须以http(s)://开头"); + } + else if (menu.getMenuId().equals(menu.getParentId())) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,上级菜单不能选择自己"); + } + else if (!menuService.checkRouteConfigUnique(menu)) + { + return error("修改菜单'" + menu.getMenuName() + "'失败,路由名称或地址已存在"); + } + menu.setUpdateBy(getUsername()); + return toAjax(menuService.updateMenu(menu)); + } + + /** + * 保存菜单排序 + */ + @PreAuthorize("@ss.hasPermi('system:menu:edit')") + @Log(title = "保存菜单排序", businessType = BusinessType.UPDATE) + @PutMapping("/updateSort") + public AjaxResult updateSort(@RequestBody Map params) + { + String[] menuIds = params.get("menuIds").split(","); + String[] orderNums = params.get("orderNums").split(","); + menuService.updateMenuSort(menuIds, orderNums); + return success(); + } + + /** + * 删除菜单 + */ + @PreAuthorize("@ss.hasPermi('system:menu:remove')") + @Log(title = "菜单管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{menuId}") + public AjaxResult remove(@PathVariable("menuId") Long menuId) + { + if (menuService.hasChildByMenuId(menuId)) + { + return warn("存在子菜单,不允许删除"); + } + if (menuService.checkMenuExistRole(menuId)) + { + return warn("菜单已分配,不允许删除"); + } + return toAjax(menuService.deleteMenuById(menuId)); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysNoticeController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysNoticeController.java new file mode 100644 index 000000000..40422f365 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysNoticeController.java @@ -0,0 +1,150 @@ +package com.roomroot.web.controller.system; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.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.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.system.domain.SysNotice; +import com.roomroot.system.service.ISysNoticeReadService; +import com.roomroot.system.service.ISysNoticeService; + +/** + * 公告 信息操作处理 + * + * @author roomroot + */ +@RestController +@RequestMapping("/system/notice") +public class SysNoticeController extends BaseController +{ + @Autowired + private ISysNoticeService noticeService; + + @Autowired + private ISysNoticeReadService noticeReadService; + + /** + * 获取通知公告列表 + */ + @PreAuthorize("@ss.hasPermi('system:notice:list')") + @GetMapping("/list") + public TableDataInfo list(SysNotice notice) + { + startPage(); + List list = noticeService.selectNoticeList(notice); + return getDataTable(list); + } + + /** + * 根据通知公告编号获取详细信息 + */ + @GetMapping(value = "/{noticeId}") + public AjaxResult getInfo(@PathVariable Long noticeId) + { + return success(noticeService.selectNoticeById(noticeId)); + } + + /** + * 新增通知公告 + */ + @PreAuthorize("@ss.hasPermi('system:notice:add')") + @Log(title = "通知公告", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysNotice notice) + { + notice.setCreateBy(getUsername()); + return toAjax(noticeService.insertNotice(notice)); + } + + /** + * 修改通知公告 + */ + @PreAuthorize("@ss.hasPermi('system:notice:edit')") + @Log(title = "通知公告", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysNotice notice) + { + notice.setUpdateBy(getUsername()); + return toAjax(noticeService.updateNotice(notice)); + } + + /** + * 首页顶部公告列表(返回全部正常公告,带当前用户已读标记,最多5条) + */ + @GetMapping("/listTop") + @ResponseBody + public AjaxResult listTop() + { + Long userId = getUserId(); + List list = noticeReadService.selectNoticeListWithReadStatus(userId, 5); + long unreadCount = list.stream().filter(n -> !n.getIsRead()).count(); + AjaxResult result = AjaxResult.success(list); + result.put("unreadCount", unreadCount); + return result; + } + + /** + * 标记公告已读 + */ + @PostMapping("/markRead") + @ResponseBody + public AjaxResult markRead(Long noticeId) + { + Long userId = getUserId(); + noticeReadService.markRead(noticeId, userId); + return success(); + } + + /** + * 批量标记已读 + */ + @PostMapping("/markReadAll") + @ResponseBody + public AjaxResult markReadAll(String ids) + { + Long userId = getUserId(); + Long[] noticeIds = Convert.toLongArray(ids); + noticeReadService.markReadBatch(userId, noticeIds); + return success(); + } + + /** + * 已读用户列表数据 + */ + @PreAuthorize("@ss.hasPermi('system:notice:list')") + @GetMapping("/readUsers/list") + @ResponseBody + public TableDataInfo readUsersList(Long noticeId, String searchValue) + { + startPage(); + List list = noticeReadService.selectReadUsersByNoticeId(noticeId, searchValue); + return getDataTable(list); + } + + /** + * 删除通知公告 + */ + @PreAuthorize("@ss.hasPermi('system:notice:remove')") + @Log(title = "通知公告", businessType = BusinessType.DELETE) + @DeleteMapping("/{noticeIds}") + public AjaxResult remove(@PathVariable Long[] noticeIds) + { + noticeReadService.deleteByNoticeIds(noticeIds); + return toAjax(noticeService.deleteNoticeByIds(noticeIds)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysPostController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysPostController.java new file mode 100644 index 000000000..925eb0ded --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysPostController.java @@ -0,0 +1,129 @@ +package com.roomroot.web.controller.system; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.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.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.poi.ExcelUtil; +import com.roomroot.system.domain.SysPost; +import com.roomroot.system.service.ISysPostService; + +/** + * 岗位信息操作处理 + * + * @author roomroot + */ +@RestController +@RequestMapping("/system/post") +public class SysPostController extends BaseController +{ + @Autowired + private ISysPostService postService; + + /** + * 获取岗位列表 + */ + @PreAuthorize("@ss.hasPermi('system:post:list')") + @GetMapping("/list") + public TableDataInfo list(SysPost post) + { + startPage(); + List list = postService.selectPostList(post); + return getDataTable(list); + } + + @Log(title = "岗位管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:post:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysPost post) + { + List list = postService.selectPostList(post); + ExcelUtil util = new ExcelUtil(SysPost.class); + util.exportExcel(response, list, "岗位数据"); + } + + /** + * 根据岗位编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:post:query')") + @GetMapping(value = "/{postId}") + public AjaxResult getInfo(@PathVariable Long postId) + { + return success(postService.selectPostById(postId)); + } + + /** + * 新增岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:add')") + @Log(title = "岗位管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysPost post) + { + if (!postService.checkPostNameUnique(post)) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (!postService.checkPostCodeUnique(post)) + { + return error("新增岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setCreateBy(getUsername()); + return toAjax(postService.insertPost(post)); + } + + /** + * 修改岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:edit')") + @Log(title = "岗位管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysPost post) + { + if (!postService.checkPostNameUnique(post)) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位名称已存在"); + } + else if (!postService.checkPostCodeUnique(post)) + { + return error("修改岗位'" + post.getPostName() + "'失败,岗位编码已存在"); + } + post.setUpdateBy(getUsername()); + return toAjax(postService.updatePost(post)); + } + + /** + * 删除岗位 + */ + @PreAuthorize("@ss.hasPermi('system:post:remove')") + @Log(title = "岗位管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{postIds}") + public AjaxResult remove(@PathVariable Long[] postIds) + { + return toAjax(postService.deletePostByIds(postIds)); + } + + /** + * 获取岗位选择框列表 + */ + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + List posts = postService.selectPostAll(); + return success(posts); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysProfileController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysProfileController.java new file mode 100644 index 000000000..52e0ac3d7 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysProfileController.java @@ -0,0 +1,156 @@ +package com.roomroot.web.controller.system; + +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +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 org.springframework.web.multipart.MultipartFile; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.config.RoomRootConfig; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.file.FileUploadUtils; +import com.roomroot.common.utils.file.FileUtils; +import com.roomroot.common.utils.file.MimeTypeUtils; +import com.roomroot.framework.web.service.TokenService; +import com.roomroot.system.service.ISysUserService; +import com.roomroot.system.service.ISysLogininforService; + +/** + * 个人信息 业务处理 + * + * @author roomroot + */ +@RestController +@RequestMapping("/system/user/profile") +public class SysProfileController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private TokenService tokenService; + + @Autowired + private ISysLogininforService logininforService; + + /** + * 个人信息 + */ + @GetMapping + public AjaxResult profile() + { + LoginUser loginUser = getLoginUser(); + SysUser user = loginUser.getUser(); + AjaxResult ajax = AjaxResult.success(user); + ajax.put("roleGroup", userService.selectUserRoleGroup(loginUser.getUsername())); + ajax.put("postGroup", userService.selectUserPostGroup(loginUser.getUsername())); + // 查询登录统计信息 + Map loginStats = logininforService.selectLoginStatsByUserName(loginUser.getUsername()); + ajax.put("loginStats", loginStats); + return ajax; + } + + /** + * 修改用户 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult updateProfile(@RequestBody SysUser user) + { + LoginUser loginUser = getLoginUser(); + SysUser currentUser = loginUser.getUser(); + currentUser.setNickName(user.getNickName()); + currentUser.setEmail(user.getEmail()); + currentUser.setPhonenumber(user.getPhonenumber()); + currentUser.setSex(user.getSex()); + if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(currentUser)) + { + return error("修改用户'" + loginUser.getUsername() + "'失败,手机号码已存在"); + } + if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(currentUser)) + { + return error("修改用户'" + loginUser.getUsername() + "'失败,邮箱账号已存在"); + } + if (userService.updateUserProfile(currentUser) > 0) + { + // 更新缓存用户信息 + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改个人信息异常,请联系管理员"); + } + + /** + * 重置密码 + */ + @Log(title = "个人信息", businessType = BusinessType.UPDATE) + @PutMapping("/updatePwd") + public AjaxResult updatePwd(@RequestBody Map params) + { + String oldPassword = params.get("oldPassword"); + String newPassword = params.get("newPassword"); + LoginUser loginUser = getLoginUser(); + Long userId = loginUser.getUserId(); + SysUser user = userService.selectUserById(userId); + String password = user.getPassword(); + if (!SecurityUtils.matchesPassword(oldPassword, password)) + { + return error("修改密码失败,旧密码错误"); + } + if (SecurityUtils.matchesPassword(newPassword, password)) + { + return error("新密码不能与旧密码相同"); + } + newPassword = SecurityUtils.encryptPassword(newPassword); + if (userService.resetUserPwd(userId, newPassword) > 0) + { + // 更新缓存用户密码&密码最后更新时间 + loginUser.getUser().setPwdUpdateDate(DateUtils.getNowDate()); + loginUser.getUser().setPassword(newPassword); + tokenService.setLoginUser(loginUser); + return success(); + } + return error("修改密码异常,请联系管理员"); + } + + /** + * 头像上传 + */ + @Log(title = "用户头像", businessType = BusinessType.UPDATE) + @PostMapping("/avatar") + public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) throws Exception + { + if (!file.isEmpty()) + { + LoginUser loginUser = getLoginUser(); + String avatar = FileUploadUtils.upload(RoomRootConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION, true); + if (userService.updateUserAvatar(loginUser.getUserId(), avatar)) + { + String oldAvatar = loginUser.getUser().getAvatar(); + if (StringUtils.isNotEmpty(oldAvatar)) + { + FileUtils.deleteFile(RoomRootConfig.getProfile() + FileUtils.stripPrefix(oldAvatar)); + } + AjaxResult ajax = AjaxResult.success(); + ajax.put("imgUrl", avatar); + // 更新缓存用户头像 + loginUser.getUser().setAvatar(avatar); + tokenService.setLoginUser(loginUser); + return ajax; + } + } + return error("上传图片异常,请联系管理员"); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysRegisterController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysRegisterController.java new file mode 100644 index 000000000..57658a9dc --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysRegisterController.java @@ -0,0 +1,38 @@ +package com.roomroot.web.controller.system; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.model.RegisterBody; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.framework.web.service.SysRegisterService; +import com.roomroot.system.service.ISysConfigService; + +/** + * 注册验证 + * + * @author roomroot + */ +@RestController +public class SysRegisterController extends BaseController +{ + @Autowired + private SysRegisterService registerService; + + @Autowired + private ISysConfigService configService; + + @PostMapping("/register") + public AjaxResult register(@RequestBody RegisterBody user) + { + if (!("true".equals(configService.selectConfigByKey("sys.account.registerUser")))) + { + return error("当前系统没有开启注册功能!"); + } + String msg = registerService.register(user); + return StringUtils.isEmpty(msg) ? success() : error(msg); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysRoleController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysRoleController.java new file mode 100644 index 000000000..c9211b77e --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysRoleController.java @@ -0,0 +1,254 @@ +package com.roomroot.web.controller.system; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.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.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.entity.SysDept; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.poi.ExcelUtil; +import com.roomroot.framework.web.service.SysPermissionService; +import com.roomroot.framework.web.service.TokenService; +import com.roomroot.system.domain.SysUserRole; +import com.roomroot.system.service.ISysDeptService; +import com.roomroot.system.service.ISysRoleService; +import com.roomroot.system.service.ISysUserService; + +/** + * 角色信息 + * + * @author roomroot + */ +@RestController +@RequestMapping("/system/role") +public class SysRoleController extends BaseController +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private TokenService tokenService; + + @Autowired + private SysPermissionService permissionService; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysDeptService deptService; + + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/list") + public TableDataInfo list(SysRole role) + { + startPage(); + List list = roleService.selectRoleList(role); + return getDataTable(list); + } + + @Log(title = "角色管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:role:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysRole role) + { + List list = roleService.selectRoleList(role); + ExcelUtil util = new ExcelUtil(SysRole.class); + util.exportExcel(response, list, "角色数据"); + } + + /** + * 根据角色编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/{roleId}") + public AjaxResult getInfo(@PathVariable Long roleId) + { + roleService.checkRoleDataScope(roleId); + return success(roleService.selectRoleById(roleId)); + } + + /** + * 新增角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:add')") + @Log(title = "角色管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysRole role) + { + if (!roleService.checkRoleNameUnique(role)) + { + return error("新增角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (!roleService.checkRoleKeyUnique(role)) + { + return error("新增角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setCreateBy(getUsername()); + return toAjax(roleService.insertRole(role)); + + } + + /** + * 修改保存角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + if (!roleService.checkRoleNameUnique(role)) + { + return error("修改角色'" + role.getRoleName() + "'失败,角色名称已存在"); + } + else if (!roleService.checkRoleKeyUnique(role)) + { + return error("修改角色'" + role.getRoleName() + "'失败,角色权限已存在"); + } + role.setUpdateBy(getUsername()); + + if (roleService.updateRole(role) > 0) + { + // 刷新所有持有该角色的在线用户权限 + tokenService.refreshPermissionByRoleId(role.getRoleId(), permissionService); + return success(); + } + return error("修改角色'" + role.getRoleName() + "'失败,请联系管理员"); + } + + /** + * 修改保存数据权限 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/dataScope") + public AjaxResult dataScope(@RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + return toAjax(roleService.authDataScope(role)); + } + + /** + * 状态修改 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysRole role) + { + roleService.checkRoleAllowed(role); + roleService.checkRoleDataScope(role.getRoleId()); + role.setUpdateBy(getUsername()); + return toAjax(roleService.updateRoleStatus(role)); + } + + /** + * 删除角色 + */ + @PreAuthorize("@ss.hasPermi('system:role:remove')") + @Log(title = "角色管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{roleIds}") + public AjaxResult remove(@PathVariable Long[] roleIds) + { + return toAjax(roleService.deleteRoleByIds(roleIds)); + } + + /** + * 获取角色选择框列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping("/optionselect") + public AjaxResult optionselect() + { + return success(roleService.selectRoleAll()); + } + + /** + * 查询已分配用户角色列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/allocatedList") + public TableDataInfo allocatedList(SysUser user) + { + startPage(); + List list = userService.selectAllocatedList(user); + return getDataTable(list); + } + + /** + * 查询未分配用户角色列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:list')") + @GetMapping("/authUser/unallocatedList") + public TableDataInfo unallocatedList(SysUser user) + { + startPage(); + List list = userService.selectUnallocatedList(user); + return getDataTable(list); + } + + /** + * 取消授权用户 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancel") + public AjaxResult cancelAuthUser(@RequestBody SysUserRole userRole) + { + return toAjax(roleService.deleteAuthUser(userRole)); + } + + /** + * 批量取消授权用户 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/cancelAll") + public AjaxResult cancelAuthUserAll(Long roleId, Long[] userIds) + { + return toAjax(roleService.deleteAuthUsers(roleId, userIds)); + } + + /** + * 批量选择用户授权 + */ + @PreAuthorize("@ss.hasPermi('system:role:edit')") + @Log(title = "角色管理", businessType = BusinessType.GRANT) + @PutMapping("/authUser/selectAll") + public AjaxResult selectAuthUserAll(Long roleId, Long[] userIds) + { + roleService.checkRoleDataScope(roleId); + return toAjax(roleService.insertAuthUsers(roleId, userIds)); + } + + /** + * 获取对应角色部门树列表 + */ + @PreAuthorize("@ss.hasPermi('system:role:query')") + @GetMapping(value = "/deptTree/{roleId}") + public AjaxResult deptTree(@PathVariable("roleId") Long roleId) + { + AjaxResult ajax = AjaxResult.success(); + ajax.put("checkedKeys", deptService.selectDeptListByRoleId(roleId)); + ajax.put("depts", deptService.selectDeptTreeList(new SysDept())); + return ajax; + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysUserController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysUserController.java new file mode 100644 index 000000000..d7c15e2a7 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/system/SysUserController.java @@ -0,0 +1,256 @@ +package com.roomroot.web.controller.system; + +import java.util.List; +import java.util.stream.Collectors; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.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.RestController; +import org.springframework.web.multipart.MultipartFile; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.entity.SysDept; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.poi.ExcelUtil; +import com.roomroot.system.service.ISysDeptService; +import com.roomroot.system.service.ISysPostService; +import com.roomroot.system.service.ISysRoleService; +import com.roomroot.system.service.ISysUserService; + +/** + * 用户信息 + * + * @author roomroot + */ +@RestController +@RequestMapping("/system/user") +public class SysUserController extends BaseController +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysDeptService deptService; + + @Autowired + private ISysPostService postService; + + /** + * 获取用户列表 + */ + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/list") + public TableDataInfo list(SysUser user) + { + startPage(); + List list = userService.selectUserList(user); + return getDataTable(list); + } + + @Log(title = "用户管理", businessType = BusinessType.EXPORT) + @PreAuthorize("@ss.hasPermi('system:user:export')") + @PostMapping("/export") + public void export(HttpServletResponse response, SysUser user) + { + List list = userService.selectUserList(user); + ExcelUtil util = new ExcelUtil(SysUser.class); + util.exportExcel(response, list, "用户数据"); + } + + @Log(title = "用户管理", businessType = BusinessType.IMPORT) + @PreAuthorize("@ss.hasPermi('system:user:import')") + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception + { + ExcelUtil util = new ExcelUtil(SysUser.class); + List userList = util.importExcel(file.getInputStream()); + String operName = getUsername(); + String message = userService.importUser(userList, updateSupport, operName); + return success(message); + } + + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) + { + ExcelUtil util = new ExcelUtil(SysUser.class); + util.importTemplateExcel(response, "用户数据"); + } + + /** + * 根据用户编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping(value = { "/", "/{userId}" }) + public AjaxResult getInfo(@PathVariable(value = "userId", required = false) Long userId) + { + AjaxResult ajax = AjaxResult.success(); + if (StringUtils.isNotNull(userId)) + { + userService.checkUserDataScope(userId); + SysUser sysUser = userService.selectUserById(userId); + ajax.put(AjaxResult.DATA_TAG, sysUser); + ajax.put("postIds", postService.selectPostListByUserId(userId)); + ajax.put("roleIds", sysUser.getRoles().stream().map(SysRole::getRoleId).collect(Collectors.toList())); + } + List roles = roleService.selectRoleAll(); + ajax.put("roles", SecurityUtils.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + ajax.put("posts", postService.selectPostAll()); + return ajax; + } + + /** + * 新增用户 + */ + @PreAuthorize("@ss.hasPermi('system:user:add')") + @Log(title = "用户管理", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody SysUser user) + { + deptService.checkDeptDataScope(user.getDeptId()); + roleService.checkRoleDataScope(user.getRoleIds()); + if (!userService.checkUserNameUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) + { + return error("新增用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setCreateBy(getUsername()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + return toAjax(userService.insertUser(user)); + } + + /** + * 修改用户 + */ + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + deptService.checkDeptDataScope(user.getDeptId()); + roleService.checkRoleDataScope(user.getRoleIds()); + if (!userService.checkUserNameUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,登录账号已存在"); + } + else if (StringUtils.isNotEmpty(user.getPhonenumber()) && !userService.checkPhoneUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); + } + else if (StringUtils.isNotEmpty(user.getEmail()) && !userService.checkEmailUnique(user)) + { + return error("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); + } + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUser(user)); + } + + /** + * 删除用户 + */ + @PreAuthorize("@ss.hasPermi('system:user:remove')") + @Log(title = "用户管理", businessType = BusinessType.DELETE) + @DeleteMapping("/{userIds}") + public AjaxResult remove(@PathVariable Long[] userIds) + { + if (ArrayUtils.contains(userIds, getUserId())) + { + return error("当前用户不能删除"); + } + return toAjax(userService.deleteUserByIds(userIds)); + } + + /** + * 重置密码 + */ + @PreAuthorize("@ss.hasPermi('system:user:resetPwd')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/resetPwd") + public AjaxResult resetPwd(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setPassword(SecurityUtils.encryptPassword(user.getPassword())); + user.setUpdateBy(getUsername()); + return toAjax(userService.resetPwd(user)); + } + + /** + * 状态修改 + */ + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysUser user) + { + userService.checkUserAllowed(user); + userService.checkUserDataScope(user.getUserId()); + user.setUpdateBy(getUsername()); + return toAjax(userService.updateUserStatus(user)); + } + + /** + * 根据用户编号获取授权角色 + */ + @PreAuthorize("@ss.hasPermi('system:user:query')") + @GetMapping("/authRole/{userId}") + public AjaxResult authRole(@PathVariable("userId") Long userId) + { + AjaxResult ajax = AjaxResult.success(); + SysUser user = userService.selectUserById(userId); + List roles = roleService.selectRolesByUserId(userId); + ajax.put("user", user); + ajax.put("roles", SecurityUtils.isAdmin(userId) ? roles : roles.stream().filter(r -> !r.isAdmin()).collect(Collectors.toList())); + return ajax; + } + + /** + * 用户授权角色 + */ + @PreAuthorize("@ss.hasPermi('system:user:edit')") + @Log(title = "用户管理", businessType = BusinessType.GRANT) + @PutMapping("/authRole") + public AjaxResult insertAuthRole(Long userId, Long[] roleIds) + { + userService.checkUserDataScope(userId); + roleService.checkRoleDataScope(roleIds); + userService.insertUserAuth(userId, roleIds); + return success(); + } + + /** + * 获取部门树列表 + */ + @PreAuthorize("@ss.hasPermi('system:user:list')") + @GetMapping("/deptTree") + public AjaxResult deptTree(SysDept dept) + { + return success(deptService.selectDeptTreeList(dept)); + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/tool/TestController.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/tool/TestController.java new file mode 100644 index 000000000..31c6cf957 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/controller/tool/TestController.java @@ -0,0 +1,175 @@ +package com.roomroot.web.controller.tool; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +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.RestController; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.R; +import com.roomroot.common.utils.StringUtils; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; + +/** + * swagger 用户测试方法 + * + * @author roomroot + */ +@Tag(name = "用户信息管理") +@RestController +@RequestMapping("/test/user") +public class TestController extends BaseController +{ + private final static Map users = new LinkedHashMap(); + { + users.put(1, new UserEntity(1, "admin", "admin123", "15888888888")); + users.put(2, new UserEntity(2, "ry", "admin123", "15666666666")); + } + + @Operation(summary = "获取用户列表") + @GetMapping("/list") + public R> userList() + { + List userList = new ArrayList(users.values()); + return R.ok(userList); + } + + @Operation(summary = "获取用户详细") + @GetMapping("/{userId}") + public R getUser(@PathVariable(name = "userId") + Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + return R.ok(users.get(userId)); + } + else + { + return R.fail("用户不存在"); + } + } + + @Operation(summary = "新增用户") + @PostMapping("/save") + public R save(UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return R.fail("用户ID不能为空"); + } + users.put(user.getUserId(), user); + return R.ok(); + } + + @Operation(summary = "更新用户") + @PutMapping("/update") + public R update(@RequestBody + UserEntity user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUserId())) + { + return R.fail("用户ID不能为空"); + } + if (users.isEmpty() || !users.containsKey(user.getUserId())) + { + return R.fail("用户不存在"); + } + users.remove(user.getUserId()); + users.put(user.getUserId(), user); + return R.ok(); + } + + @Operation(summary = "删除用户信息") + @DeleteMapping("/{userId}") + public R delete(@PathVariable(name = "userId") + Integer userId) + { + if (!users.isEmpty() && users.containsKey(userId)) + { + users.remove(userId); + return R.ok(); + } + else + { + return R.fail("用户不存在"); + } + } +} + +@Schema(description = "用户实体") +class UserEntity +{ + @Schema(title = "用户ID") + private Integer userId; + + @Schema(title = "用户名称") + private String username; + + @Schema(title = "用户密码") + private String password; + + @Schema(title = "用户手机") + private String mobile; + + public UserEntity() + { + + } + + public UserEntity(Integer userId, String username, String password, String mobile) + { + this.userId = userId; + this.username = username; + this.password = password; + this.mobile = mobile; + } + + public Integer getUserId() + { + return userId; + } + + public void setUserId(Integer userId) + { + this.userId = userId; + } + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getMobile() + { + return mobile; + } + + public void setMobile(String mobile) + { + this.mobile = mobile; + } +} diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/core/config/EmbeddedRedisCondition.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/core/config/EmbeddedRedisCondition.java new file mode 100644 index 000000000..55fba97c8 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/core/config/EmbeddedRedisCondition.java @@ -0,0 +1,14 @@ +package com.roomroot.web.core.config; + +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.core.type.AnnotatedTypeMetadata; + +public class EmbeddedRedisCondition implements Condition { + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + // 读取yml开关,true才启用内嵌Redis + String enable = context.getEnvironment().getProperty("custom.embedded-redis.enable", "false"); + return Boolean.parseBoolean(enable); + } +} \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/java/com/roomroot/web/core/config/SwaggerConfig.java b/backend/roomroot-admin/src/main/java/com/roomroot/web/core/config/SwaggerConfig.java new file mode 100644 index 000000000..e765c0ad7 --- /dev/null +++ b/backend/roomroot-admin/src/main/java/com/roomroot/web/core/config/SwaggerConfig.java @@ -0,0 +1,64 @@ +package com.roomroot.web.core.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.roomroot.common.config.RoomRootConfig; +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; + +/** + * Swagger2的接口配置 + * + * @author roomroot + */ +@Configuration +public class SwaggerConfig +{ + /** 系统基础配置 */ + @Autowired + private RoomRootConfig RoomRootConfig; + + /** + * 自定义的 OpenAPI 对象 + */ + @Bean + public OpenAPI customOpenApi() + { + return new OpenAPI().components(new Components() + // 设置认证的请求头 + .addSecuritySchemes("apikey", securityScheme())) + .addSecurityItem(new SecurityRequirement().addList("apikey")) + .info(getApiInfo()); + } + + @Bean + public SecurityScheme securityScheme() + { + return new SecurityScheme() + .type(SecurityScheme.Type.APIKEY) + .name("Authorization") + .in(SecurityScheme.In.HEADER) + .scheme("Bearer"); + } + + /** + * 添加摘要信息 + */ + public Info getApiInfo() + { + return new Info() + // 设置标题 + .title("标题:风影随行管理系统_接口文档") + // 描述 + .description("描述:用于管理集团旗下公司的人员信息,具体包括XXX,XXX模块...") + // 作者信息 + .contact(new Contact().name(RoomRootConfig.getName())) + // 版本 + .version("版本号:" + RoomRootConfig.getVersion()); + } +} diff --git a/backend/roomroot-admin/src/main/resources/META-INF/spring-devtools.properties b/backend/roomroot-admin/src/main/resources/META-INF/spring-devtools.properties new file mode 100644 index 000000000..37e7b5806 --- /dev/null +++ b/backend/roomroot-admin/src/main/resources/META-INF/spring-devtools.properties @@ -0,0 +1 @@ +restart.include.json=/com.alibaba.fastjson2.*.jar \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/resources/application-druid.yml b/backend/roomroot-admin/src/main/resources/application-druid.yml new file mode 100644 index 000000000..52ea2b73b --- /dev/null +++ b/backend/roomroot-admin/src/main/resources/application-druid.yml @@ -0,0 +1,56 @@ +# 数据源配置 +spring: + datasource: + type: com.alibaba.druid.pool.DruidDataSource + # 达梦连接串:ip:端口,schema指定默认模式 + url: jdbc:dm://192.168.1.27:5236?schema=JIAOWU&useUnicode=true&characterEncoding=utf-8 + username: JIAOWU + password: Jiaowu123 + driver-class-name: dm.jdbc.driver.DmDriver + + # Druid 基础参数 + druid: + # 初始连接数 + initialSize: 5 + # 最小连接池数量 + minIdle: 10 + # 最大连接池数量 + maxActive: 20 + # 配置获取连接等待超时的时间 + maxWait: 60000 + # 配置连接超时时间 + connectTimeout: 30000 + # 配置网络超时时间 + socketTimeout: 60000 + # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 + timeBetweenEvictionRunsMillis: 60000 + # 配置一个连接在池中最小生存的时间,单位是毫秒 + minEvictableIdleTimeMillis: 300000 + # 配置一个连接在池中最大生存的时间,单位是毫秒 + maxEvictableIdleTimeMillis: 900000 + # 配置检测连接是否有效 + validationQuery: SELECT 1 FROM DUAL + testWhileIdle: true + testOnBorrow: false + testOnReturn: false + webStatFilter: + enabled: true + statViewServlet: + enabled: true + # 设置白名单,不填则允许所有访问 + allow: + url-pattern: /druid/* + # 控制台管理用户名和密码 + login-username: roomroot + login-password: 123456 + filter: + stat: + enabled: true + # 慢SQL记录 + log-slow-sql: true + slow-sql-millis: 1000 + merge-sql: true + wall: + config: + multi-statement-allow: true + db-type: dm \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/resources/application.yml b/backend/roomroot-admin/src/main/resources/application.yml new file mode 100644 index 000000000..7f9bf86a0 --- /dev/null +++ b/backend/roomroot-admin/src/main/resources/application.yml @@ -0,0 +1,149 @@ +# 项目相关配置 +roomroot: + # 名称 + name: roomroot + # 版本 + version: 3.9.2 + # 版权年份 + copyrightYear: 2026 + # 文件路径 示例( Windows配置D:/roomroot/uploadPath,Linux配置 /home/roomroot/uploadPath) + profile: D:/roomroot/uploadPath + # 获取ip地址开关 + addressEnabled: false + # 验证码类型 math 数字计算 char 字符验证 + captchaType: math + +# 开发环境配置 +server: + # 服务器的HTTP端口,默认为8080 + port: 8080 + servlet: + # 应用的访问路径 + context-path: /api + tomcat: + # tomcat的URI编码 + uri-encoding: UTF-8 + # 连接数满后的排队数,默认为100 + accept-count: 1000 + threads: + # tomcat最大线程数,默认为200 + max: 800 + # Tomcat启动初始化的线程数,默认值10 + min-spare: 100 + +# 日志配置 +logging: + level: + com.roomroot: debug + #org.springframework: warn + +# 用户配置 +user: + password: + # 密码最大错误次数 + maxRetryCount: 5 + # 密码锁定时间(默认10分钟) + lockTime: 10 + +# Spring配置 +spring: + # 资源信息 + messages: + # 国际化资源文件路径 + basename: i18n/messages + profiles: + active: druid + # 文件上传 + servlet: + multipart: + # 单个文件大小 + max-file-size: 10MB + # 设置总上传的文件大小 + max-request-size: 20MB + jackson: + time-zone: GMT+8 + date-format: yyyy-MM-dd HH:mm:ss + # 服务模块 + devtools: + restart: + # 热部署开关 + enabled: true + data: + # redis 配置 + redis: + # 地址 + host: localhost + # 端口,默认为6379 + port: 6379 + # 数据库索引 + database: 0 + # 密码 + password: + # 连接超时时间 + timeout: 10s + lettuce: + pool: + # 连接池中的最小空闲连接 + min-idle: 0 + # 连接池中的最大空闲连接 + max-idle: 8 + # 连接池的最大数据库连接数 + max-active: 8 + # #连接池最大阻塞等待时间(使用负值表示没有限制) + max-wait: -1ms + +# token配置 +token: + # 令牌自定义标识 + header: Authorization + # 令牌密钥 + secret: abcdefghijklmnopqrstuvwxyz + # 令牌有效期(默认30分钟) + expireTime: 30 + +# MyBatis-Plus配置 +mybatis-plus: + # 搜索指定包别名 + typeAliasesPackage: com.roomroot.**.domain + # 配置mapper的扫描,找到所有的mapper.xml映射文件 + mapperLocations: classpath*:mapper/**/*Mapper.xml + # 加载全局的配置文件 + configLocation: classpath:mybatis/mybatis-config.xml + +# Springdoc配置 +springdoc: + api-docs: + path: /v3/api-docs + swagger-ui: + enabled: true + path: /swagger-ui.html + tags-sorter: alpha + group-configs: + - group: 'default' + display-name: '测试模块' + paths-to-match: '/**' + packages-to-scan: com.roomroot.web.controller.tool + +# 防盗链配置 +referer: + # 防盗链开关 + enabled: false + # 允许的域名列表 + allowed-domains: localhost,127.0.0.1,roomroot.vip,www.roomroot.vip + +# 防止XSS攻击 +xss: + # 过滤开关 + enabled: true + # 排除链接(多个用逗号分隔) + excludes: /system/notice + # 匹配链接 + urlPatterns: /system/*,/monitor/*,/tool/* + +# 自定义内嵌Redis开关 +custom: + embedded-redis: + # dev环境true自动启动内嵌Redis;prod改为false使用外部Redis + enable: false + port: 6379 + max-memory: 128M \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/resources/banner.txt b/backend/roomroot-admin/src/main/resources/banner.txt new file mode 100644 index 000000000..b662e817a --- /dev/null +++ b/backend/roomroot-admin/src/main/resources/banner.txt @@ -0,0 +1,5 @@ +Application Version: ${roomroot.version} +Spring Boot Version: ${spring-boot.version} +//////////////////////////////////////////////////////////////////// +// 启动开始 永不宕机 永无BUG // +//////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/resources/i18n/messages.properties b/backend/roomroot-admin/src/main/resources/i18n/messages.properties new file mode 100644 index 000000000..93de0055b --- /dev/null +++ b/backend/roomroot-admin/src/main/resources/i18n/messages.properties @@ -0,0 +1,38 @@ +#错误消息 +not.null=* 必须填写 +user.jcaptcha.error=验证码错误 +user.jcaptcha.expire=验证码已失效 +user.not.exists=用户不存在/密码错误 +user.password.not.match=用户不存在/密码错误 +user.password.retry.limit.count=密码输入错误{0}次 +user.password.retry.limit.exceed=密码输入错误{0}次,帐户锁定{1}分钟 +user.password.delete=对不起,您的账号已被删除 +user.blocked=用户已封禁,请联系管理员 +role.blocked=角色已封禁,请联系管理员 +login.blocked=很遗憾,访问IP已被列入系统黑名单 +user.logout.success=退出成功 + +length.not.valid=长度必须在{min}到{max}个字符之间 + +user.username.not.valid=* 2到20个汉字、字母、数字或下划线组成,且必须以非数字开头 +user.password.not.valid=* 5-50个字符 + +user.email.not.valid=邮箱格式错误 +user.mobile.phone.number.not.valid=手机号格式错误 +user.login.success=登录成功 +user.register.success=注册成功 +user.notfound=请重新登录 +user.forcelogout=管理员强制退出,请重新登录 +user.unknown.error=未知错误,请重新登录 + +##文件上传消息 +upload.exceed.maxSize=上传的文件大小超出限制的文件大小!
允许的文件最大大小是:{0}MB! +upload.filename.exceed.length=上传的文件名最长{0}个字符 + +##权限 +no.permission=您没有数据的权限,请联系管理员添加权限 [{0}] +no.create.permission=您没有创建数据的权限,请联系管理员添加权限 [{0}] +no.update.permission=您没有修改数据的权限,请联系管理员添加权限 [{0}] +no.delete.permission=您没有删除数据的权限,请联系管理员添加权限 [{0}] +no.export.permission=您没有导出数据的权限,请联系管理员添加权限 [{0}] +no.view.permission=您没有查看数据的权限,请联系管理员添加权限 [{0}] diff --git a/backend/roomroot-admin/src/main/resources/logback.xml b/backend/roomroot-admin/src/main/resources/logback.xml new file mode 100644 index 000000000..9303f27bd --- /dev/null +++ b/backend/roomroot-admin/src/main/resources/logback.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + ${log.pattern} + + + + + + ${log.path}/sys-info.log + + + + ${log.path}/sys-info.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + INFO + + ACCEPT + + DENY + + + + + ${log.path}/sys-error.log + + + + ${log.path}/sys-error.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + ERROR + + ACCEPT + + DENY + + + + + + ${log.path}/sys-user.log + + + ${log.path}/sys-user.%d{yyyy-MM-dd}.log + + 60 + + + ${log.pattern} + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-admin/src/main/resources/mybatis/mybatis-config.xml b/backend/roomroot-admin/src/main/resources/mybatis/mybatis-config.xml new file mode 100644 index 000000000..ac47c038e --- /dev/null +++ b/backend/roomroot-admin/src/main/resources/mybatis/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-common/pom.xml b/backend/roomroot-common/pom.xml new file mode 100644 index 000000000..13782df41 --- /dev/null +++ b/backend/roomroot-common/pom.xml @@ -0,0 +1,129 @@ + + + + roomroot + com.roomroot + 3.9.2 + + 4.0.0 + + roomroot-common + + + common通用工具 + + + + + + + org.springframework + spring-context-support + + + + + org.springframework + spring-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + + + org.springframework.boot + spring-boot-starter-validation + + + + + org.apache.commons + commons-lang3 + + + + + tools.jackson.core + jackson-databind + + + + + com.alibaba.fastjson2 + fastjson2 + + + + + commons-io + commons-io + + + + + org.apache.poi + poi-ooxml + + + + + io.jsonwebtoken + jjwt + + + + + javax.xml.bind + jaxb-api + + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.springframework.boot + spring-boot-starter-cache + + + + + org.apache.commons + commons-pool2 + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + + + nl.basjes.parse.useragent + yauaa + + + + + jakarta.servlet + jakarta.servlet-api + + + + + \ No newline at end of file diff --git a/backend/roomroot-common/roomroot-common.iml b/backend/roomroot-common/roomroot-common.iml new file mode 100644 index 000000000..abe781f78 --- /dev/null +++ b/backend/roomroot-common/roomroot-common.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Anonymous.java b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Anonymous.java new file mode 100644 index 000000000..a067ee6b0 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Anonymous.java @@ -0,0 +1,19 @@ +package com.roomroot.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 匿名访问不鉴权注解 + * + * @author roomroot + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Anonymous +{ +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/DataScope.java b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/DataScope.java new file mode 100644 index 000000000..143d7478d --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/DataScope.java @@ -0,0 +1,43 @@ +package com.roomroot.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 数据权限过滤注解 + * + * @author roomroot + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataScope +{ + /** + * 用户表的别名 + */ + public String userAlias() default ""; + + /** + * 部门表的别名 + */ + public String deptAlias() default ""; + + /** + * 用户字段名 + */ + public String userField() default "user_id"; + + /** + * 部门字段名 + */ + public String deptField() default "dept_id"; + + /** + * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来 + */ + public String permission() default ""; +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/DataSource.java b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/DataSource.java new file mode 100644 index 000000000..c1043b89a --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/DataSource.java @@ -0,0 +1,28 @@ +package com.roomroot.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.roomroot.common.enums.DataSourceType; + +/** + * 自定义多数据源切换注解 + * + * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准 + * + * @author roomroot + */ +@Target({ ElementType.METHOD, ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +public @interface DataSource +{ + /** + * 切换数据源名称 + */ + public DataSourceType value() default DataSourceType.MASTER; +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Excel.java b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Excel.java new file mode 100644 index 000000000..3c0a80287 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Excel.java @@ -0,0 +1,198 @@ +package com.roomroot.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.math.BigDecimal; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import com.roomroot.common.utils.poi.ExcelHandlerAdapter; + +/** + * 自定义导出Excel数据注解 + * + * @author roomroot + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Excel +{ + /** + * 导出时在excel中排序 + */ + public int sort() default Integer.MAX_VALUE; + + /** + * 导出到Excel中的名字. + */ + public String name() default ""; + + /** + * 日期格式, 如: yyyy-MM-dd + */ + public String dateFormat() default ""; + + /** + * 如果是字典类型,请设置字典的type值 (如: sys_user_sex) + */ + public String dictType() default ""; + + /** + * 读取内容转表达式 (如: 0=男,1=女,2=未知) + */ + public String readConverterExp() default ""; + + /** + * 分隔符,读取字符串组内容 + */ + public String separator() default ","; + + /** + * BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) + */ + public int scale() default -1; + + /** + * BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN + */ + @SuppressWarnings("deprecation") + public int roundingMode() default BigDecimal.ROUND_HALF_EVEN; + + /** + * 导出时在excel中每个列的高度 + */ + public double height() default 14; + + /** + * 导出时在excel中每个列的宽度 + */ + public double width() default 16; + + /** + * 文字后缀,如% 90 变成90% + */ + public String suffix() default ""; + + /** + * 当值为空时,字段的默认值 + */ + public String defaultValue() default ""; + + /** + * 提示信息 + */ + public String prompt() default ""; + + /** + * 是否允许内容换行 + */ + public boolean wrapText() default false; + + /** + * 设置只能选择不能输入的列内容. + */ + public String[] combo() default {}; + + /** + * 是否从字典读数据到combo,默认不读取,如读取需要设置dictType注解. + */ + public boolean comboReadDict() default false; + + /** + * 是否需要纵向合并单元格,应对需求:含有list集合单元格) + */ + public boolean needMerge() default false; + + /** + * 是否导出数据,应对需求:有时我们需要导出一份模板,这是标题需要但内容需要用户手工填写. + */ + public boolean isExport() default true; + + /** + * 另一个类中的属性名称,支持多级获取,以小数点隔开 + */ + public String targetAttr() default ""; + + /** + * 是否自动统计数据,在最后追加一行统计数据总和 + */ + public boolean isStatistics() default false; + + /** + * 导出类型(0数字 1字符串 2图片) + */ + public ColumnType cellType() default ColumnType.STRING; + + /** + * 导出列头背景颜色 + */ + public IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; + + /** + * 导出列头字体颜色 + */ + public IndexedColors headerColor() default IndexedColors.WHITE; + + /** + * 导出单元格背景颜色 + */ + public IndexedColors backgroundColor() default IndexedColors.WHITE; + + /** + * 导出单元格字体颜色 + */ + public IndexedColors color() default IndexedColors.BLACK; + + /** + * 导出字段对齐方式 + */ + public HorizontalAlignment align() default HorizontalAlignment.CENTER; + + /** + * 自定义数据处理器 + */ + public Class handler() default ExcelHandlerAdapter.class; + + /** + * 自定义数据处理器参数 + */ + public String[] args() default {}; + + /** + * 字段类型(0:导出导入;1:仅导出;2:仅导入) + */ + Type type() default Type.ALL; + + public enum Type + { + ALL(0), EXPORT(1), IMPORT(2); + private final int value; + + Type(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } + + public enum ColumnType + { + NUMERIC(0), STRING(1), IMAGE(2), TEXT(3); + private final int value; + + ColumnType(int value) + { + this.value = value; + } + + public int value() + { + return this.value; + } + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Excels.java b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Excels.java new file mode 100644 index 000000000..bdf0aef3b --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Excels.java @@ -0,0 +1,18 @@ +package com.roomroot.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Excel注解集 + * + * @author roomroot + */ +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface Excels +{ + public Excel[] value(); +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Log.java b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Log.java new file mode 100644 index 000000000..d226604fc --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Log.java @@ -0,0 +1,51 @@ +package com.roomroot.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.enums.OperatorType; + +/** + * 自定义操作日志记录注解 + * + * @author roomroot + * + */ +@Target({ ElementType.PARAMETER, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface Log +{ + /** + * 模块 + */ + public String title() default ""; + + /** + * 功能 + */ + public BusinessType businessType() default BusinessType.OTHER; + + /** + * 操作人类别 + */ + public OperatorType operatorType() default OperatorType.MANAGE; + + /** + * 是否保存请求的参数 + */ + public boolean isSaveRequestData() default true; + + /** + * 是否保存响应的参数 + */ + public boolean isSaveResponseData() default true; + + /** + * 排除指定的请求参数 + */ + public String[] excludeParamNames() default {}; +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/RateLimiter.java b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/RateLimiter.java new file mode 100644 index 000000000..9ed5799b3 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/RateLimiter.java @@ -0,0 +1,40 @@ +package com.roomroot.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.enums.LimitType; + +/** + * 限流注解 + * + * @author roomroot + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RateLimiter +{ + /** + * 限流key + */ + public String key() default CacheConstants.RATE_LIMIT_KEY; + + /** + * 限流时间,单位秒 + */ + public int time() default 60; + + /** + * 限流次数 + */ + public int count() default 100; + + /** + * 限流类型 + */ + public LimitType limitType() default LimitType.DEFAULT; +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/RepeatSubmit.java b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/RepeatSubmit.java new file mode 100644 index 000000000..52967e924 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/RepeatSubmit.java @@ -0,0 +1,31 @@ +package com.roomroot.common.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义注解防止表单重复提交 + * + * @author roomroot + * + */ +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RepeatSubmit +{ + /** + * 间隔时间(ms),小于此时间视为重复提交 + */ + public int interval() default 5000; + + /** + * 提示消息 + */ + public String message() default "不允许重复提交,请稍候再试"; +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Sensitive.java b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Sensitive.java new file mode 100644 index 000000000..14622805b --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/annotation/Sensitive.java @@ -0,0 +1,24 @@ +package com.roomroot.common.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import tools.jackson.databind.annotation.JsonSerialize; +import com.roomroot.common.config.serializer.SensitiveJsonSerializer; +import com.roomroot.common.enums.DesensitizedType; + +/** + * 数据脱敏注解 + * + * @author roomroot + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.FIELD, ElementType.METHOD }) +@JacksonAnnotationsInside +@JsonSerialize(using = SensitiveJsonSerializer.class) +public @interface Sensitive +{ + DesensitizedType desensitizedType(); +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/config/RoomRootConfig.java b/backend/roomroot-common/src/main/java/com/roomroot/common/config/RoomRootConfig.java new file mode 100644 index 000000000..015c56769 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/config/RoomRootConfig.java @@ -0,0 +1,122 @@ +package com.roomroot.common.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * 读取项目相关配置 + * + * @author roomroot + */ +@Component +@ConfigurationProperties(prefix = "roomroot") +public class RoomRootConfig +{ + /** 项目名称 */ + private String name; + + /** 版本 */ + private String version; + + /** 版权年份 */ + private String copyrightYear; + + /** 上传路径 */ + private static String profile; + + /** 获取地址开关 */ + private static boolean addressEnabled; + + /** 验证码类型 */ + private static String captchaType; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getCopyrightYear() + { + return copyrightYear; + } + + public void setCopyrightYear(String copyrightYear) + { + this.copyrightYear = copyrightYear; + } + + public static String getProfile() + { + return profile; + } + + public void setProfile(String profile) + { + RoomRootConfig.profile = profile; + } + + public static boolean isAddressEnabled() + { + return addressEnabled; + } + + public void setAddressEnabled(boolean addressEnabled) + { + RoomRootConfig.addressEnabled = addressEnabled; + } + + public static String getCaptchaType() { + return captchaType; + } + + public void setCaptchaType(String captchaType) { + RoomRootConfig.captchaType = captchaType; + } + + /** + * 获取导入上传路径 + */ + public static String getImportPath() + { + return getProfile() + "/import"; + } + + /** + * 获取头像上传路径 + */ + public static String getAvatarPath() + { + return getProfile() + "/avatar"; + } + + /** + * 获取下载路径 + */ + public static String getDownloadPath() + { + return getProfile() + "/download/"; + } + + /** + * 获取上传路径 + */ + public static String getUploadPath() + { + return getProfile() + "/upload"; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/config/serializer/SensitiveJsonSerializer.java b/backend/roomroot-common/src/main/java/com/roomroot/common/config/serializer/SensitiveJsonSerializer.java new file mode 100644 index 000000000..1196f75c1 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/config/serializer/SensitiveJsonSerializer.java @@ -0,0 +1,77 @@ +package com.roomroot.common.config.serializer; + +import java.util.Objects; +import tools.jackson.core.JacksonException; +import tools.jackson.core.JsonGenerator; +import tools.jackson.databind.BeanProperty; +import tools.jackson.databind.DatabindException; +import tools.jackson.databind.SerializationContext; +import tools.jackson.databind.ValueSerializer; +import tools.jackson.databind.ser.std.StdSerializer; +import com.roomroot.common.annotation.Sensitive; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.enums.DesensitizedType; +import com.roomroot.common.utils.SecurityUtils; + +/** + * 数据脱敏序列化过滤 + * + * @author roomroot + */ +public class SensitiveJsonSerializer extends StdSerializer +{ + private final DesensitizedType desensitizedType; + + public SensitiveJsonSerializer() + { + super(String.class); + this.desensitizedType = null; + } + + public SensitiveJsonSerializer(DesensitizedType desensitizedType) + { + super(String.class); + this.desensitizedType = desensitizedType; + } + + @Override + public void serialize(String value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException + { + if (desensitizedType != null && desensitization()) + { + gen.writeString(desensitizedType.desensitizer().apply(value)); + } + else + { + gen.writeString(value); + } + } + + @Override + public ValueSerializer createContextual(SerializationContext ctxt, BeanProperty property) throws DatabindException + { + Sensitive annotation = property.getAnnotation(Sensitive.class); + if (Objects.nonNull(annotation) && Objects.equals(String.class, property.getType().getRawClass())) + { + return new SensitiveJsonSerializer(annotation.desensitizedType()); + } + return ctxt.findValueSerializer(property.getType()); + } + + /** + * 是否需要脱敏处理 + */ + private boolean desensitization() + { + try + { + LoginUser securityUser = SecurityUtils.getLoginUser(); + // 管理员不脱敏 + return !securityUser.getUser().isAdmin(); + } + catch (Exception e) + { + return true; + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/constant/CacheConstants.java b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/CacheConstants.java new file mode 100644 index 000000000..0d2a9538b --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/CacheConstants.java @@ -0,0 +1,44 @@ +package com.roomroot.common.constant; + +/** + * 缓存的key 常量 + * + * @author roomroot + */ +public class CacheConstants +{ + /** + * 登录用户 redis key + */ + public static final String LOGIN_TOKEN_KEY = "login_tokens:"; + + /** + * 验证码 redis key + */ + public static final String CAPTCHA_CODE_KEY = "captcha_codes:"; + + /** + * 参数管理 cache key + */ + public static final String SYS_CONFIG_KEY = "sys_config:"; + + /** + * 字典管理 cache key + */ + public static final String SYS_DICT_KEY = "sys_dict:"; + + /** + * 防重提交 redis key + */ + public static final String REPEAT_SUBMIT_KEY = "repeat_submit:"; + + /** + * 限流 redis key + */ + public static final String RATE_LIMIT_KEY = "rate_limit:"; + + /** + * 登录账户密码错误次数 redis key + */ + public static final String PWD_ERR_CNT_KEY = "pwd_err_cnt:"; +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/constant/Constants.java b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/Constants.java new file mode 100644 index 000000000..b26aa253a --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/Constants.java @@ -0,0 +1,204 @@ +package com.roomroot.common.constant; + +import java.util.Locale; +import io.jsonwebtoken.Claims; + +/** + * 通用常量信息 + * + * @author roomroot + */ +public class Constants +{ + /** + * UTF-8 字符集 + */ + public static final String UTF8 = "UTF-8"; + + /** + * GBK 字符集 + */ + public static final String GBK = "GBK"; + + /** + * 系统语言 + */ + public static final Locale DEFAULT_LOCALE = Locale.SIMPLIFIED_CHINESE; + + /** + * www主域 + */ + public static final String WWW = "www."; + + /** + * http请求 + */ + public static final String HTTP = "http://"; + + /** + * https请求 + */ + public static final String HTTPS = "https://"; + + /** + * 通用成功标识 + */ + public static final String SUCCESS = "0"; + + /** + * 通用失败标识 + */ + public static final String FAIL = "1"; + + /** + * 登录成功 + */ + public static final String LOGIN_SUCCESS = "Success"; + + /** + * 注销 + */ + public static final String LOGOUT = "Logout"; + + /** + * 注册 + */ + public static final String REGISTER = "Register"; + + /** + * 登录失败 + */ + public static final String LOGIN_FAIL = "Error"; + + /** + * 所有权限标识 + */ + public static final String ALL_PERMISSION = "*:*:*"; + + /** + * 管理员角色权限标识 + */ + public static final String SUPER_ADMIN = "admin"; + + /** + * 角色权限分隔符 + */ + public static final String ROLE_DELIMITER = ","; + + /** + * 权限标识分隔符 + */ + public static final String PERMISSION_DELIMITER = ","; + + /** + * 验证码有效期(分钟) + */ + public static final Integer CAPTCHA_EXPIRATION = 2; + + /** + * 令牌 + */ + public static final String TOKEN = "token"; + + /** + * 令牌前缀 + */ + public static final String TOKEN_PREFIX = "Bearer "; + + /** + * 令牌前缀 + */ + public static final String LOGIN_USER_KEY = "login_user_key"; + + /** + * 用户ID + */ + public static final String JWT_USERID = "userid"; + + /** + * 用户名称 + */ + public static final String JWT_USERNAME = Claims.SUBJECT; + + /** + * 用户头像 + */ + public static final String JWT_AVATAR = "avatar"; + + /** + * 创建时间 + */ + public static final String JWT_CREATED = "created"; + + /** + * 用户权限 + */ + public static final String JWT_AUTHORITIES = "authorities"; + + /** + * 资源映射路径 前缀 + */ + public static final String RESOURCE_PREFIX = "/profile"; + + /** + * RMI 远程方法调用 + */ + public static final String LOOKUP_RMI = "rmi:"; + + /** + * LDAP 远程方法调用 + */ + public static final String LOOKUP_LDAP = "ldap:"; + + /** + * LDAPS 远程方法调用 + */ + public static final String LOOKUP_LDAPS = "ldaps:"; + + /** + * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全) + */ + public static final String[] JSON_WHITELIST_STR = { "com.roomroot" }; + + /** + * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加) + */ + public static final String[] JOB_WHITELIST_STR = { "com.roomroot.quartz.task" }; + + /** + * 定时任务违规的字符 + */ + public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml", + "org.springframework", "org.apache", "com.roomroot.common.utils.file", "com.roomroot.common.config", "com.roomroot.generator" }; + + /** + * 部门相关常量 + */ + public static class Dept + { + /** + * 全部数据权限 + */ + public static final String DATA_SCOPE_ALL = "1"; + + /** + * 自定数据权限 + */ + public static final String DATA_SCOPE_CUSTOM = "2"; + + /** + * 部门数据权限 + */ + public static final String DATA_SCOPE_DEPT = "3"; + + /** + * 部门及以下数据权限 + */ + public static final String DATA_SCOPE_DEPT_AND_CHILD = "4"; + + /** + * 仅本人数据权限 + */ + public static final String DATA_SCOPE_SELF = "5"; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/constant/GenConstants.java b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/GenConstants.java new file mode 100644 index 000000000..194ddc440 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/GenConstants.java @@ -0,0 +1,120 @@ +package com.roomroot.common.constant; + +/** + * 代码生成通用常量 + * + * @author roomroot + */ +public class GenConstants +{ + /** 单表(增删改查) */ + public static final String TPL_CRUD = "crud"; + + /** 树表(增删改查) */ + public static final String TPL_TREE = "tree"; + + /** 主子表(增删改查) */ + public static final String TPL_SUB = "sub"; + + /** 树编码字段 */ + public static final String TREE_CODE = "treeCode"; + + /** 树父编码字段 */ + public static final String TREE_PARENT_CODE = "treeParentCode"; + + /** 树名称字段 */ + public static final String TREE_NAME = "treeName"; + + /** 上级菜单ID字段 */ + public static final String PARENT_MENU_ID = "parentMenuId"; + + /** 上级菜单名称字段 */ + public static final String PARENT_MENU_NAME = "parentMenuName"; + + /** 生成详情页开关 */ + public static final String GEN_VIEW = "genView"; + + /** 数据库字符串类型 */ + public static final String[] COLUMNTYPE_STR = { "char", "varchar", "nvarchar", "varchar2" }; + + /** 数据库文本类型 */ + public static final String[] COLUMNTYPE_TEXT = { "tinytext", "text", "mediumtext", "longtext" }; + + /** 数据库时间类型 */ + public static final String[] COLUMNTYPE_TIME = { "datetime", "time", "date", "timestamp" }; + + /** 数据库数字类型 */ + public static final String[] COLUMNTYPE_NUMBER = { "tinyint", "smallint", "mediumint", "int", "number", "integer", + "bit", "bigint", "float", "double", "decimal" }; + + /** 页面不需要编辑字段 */ + public static final String[] COLUMNNAME_NOT_EDIT = { "id", "create_by", "create_time", "del_flag" }; + + /** 页面不需要显示的列表字段 */ + public static final String[] COLUMNNAME_NOT_LIST = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time" }; + + /** 页面不需要查询字段 */ + public static final String[] COLUMNNAME_NOT_QUERY = { "id", "create_by", "create_time", "del_flag", "update_by", + "update_time", "remark" }; + + /** Entity基类字段 */ + public static final String[] BASE_ENTITY = { "createBy", "createTime", "updateBy", "updateTime", "remark" }; + + /** Tree基类字段 */ + public static final String[] TREE_ENTITY = { "parentName", "parentId", "orderNum", "ancestors", "children" }; + + /** 文本框 */ + public static final String HTML_INPUT = "input"; + + /** 文本域 */ + public static final String HTML_TEXTAREA = "textarea"; + + /** 下拉框 */ + public static final String HTML_SELECT = "select"; + + /** 单选框 */ + public static final String HTML_RADIO = "radio"; + + /** 复选框 */ + public static final String HTML_CHECKBOX = "checkbox"; + + /** 日期控件 */ + public static final String HTML_DATETIME = "datetime"; + + /** 图片上传控件 */ + public static final String HTML_IMAGE_UPLOAD = "imageUpload"; + + /** 文件上传控件 */ + public static final String HTML_FILE_UPLOAD = "fileUpload"; + + /** 富文本控件 */ + public static final String HTML_EDITOR = "editor"; + + /** 字符串类型 */ + public static final String TYPE_STRING = "String"; + + /** 整型 */ + public static final String TYPE_INTEGER = "Integer"; + + /** 长整型 */ + public static final String TYPE_LONG = "Long"; + + /** 浮点型 */ + public static final String TYPE_DOUBLE = "Double"; + + /** 高精度计算类型 */ + public static final String TYPE_BIGDECIMAL = "BigDecimal"; + + /** 时间类型 */ + public static final String TYPE_DATE = "Date"; + + /** 模糊查询 */ + public static final String QUERY_LIKE = "LIKE"; + + /** 相等查询 */ + public static final String QUERY_EQ = "EQ"; + + /** 需要 */ + public static final String REQUIRE = "1"; +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/constant/HttpStatus.java b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/HttpStatus.java new file mode 100644 index 000000000..42bb75549 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/HttpStatus.java @@ -0,0 +1,94 @@ +package com.roomroot.common.constant; + +/** + * 返回状态码 + * + * @author roomroot + */ +public class HttpStatus +{ + /** + * 操作成功 + */ + public static final int SUCCESS = 200; + + /** + * 对象创建成功 + */ + public static final int CREATED = 201; + + /** + * 请求已经被接受 + */ + public static final int ACCEPTED = 202; + + /** + * 操作已经执行成功,但是没有返回数据 + */ + public static final int NO_CONTENT = 204; + + /** + * 资源已被移除 + */ + public static final int MOVED_PERM = 301; + + /** + * 重定向 + */ + public static final int SEE_OTHER = 303; + + /** + * 资源没有被修改 + */ + public static final int NOT_MODIFIED = 304; + + /** + * 参数列表错误(缺少,格式不匹配) + */ + public static final int BAD_REQUEST = 400; + + /** + * 未授权 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 访问受限,授权过期 + */ + public static final int FORBIDDEN = 403; + + /** + * 资源,服务未找到 + */ + public static final int NOT_FOUND = 404; + + /** + * 不允许的http方法 + */ + public static final int BAD_METHOD = 405; + + /** + * 资源冲突,或者资源被锁 + */ + public static final int CONFLICT = 409; + + /** + * 不支持的数据,媒体类型 + */ + public static final int UNSUPPORTED_TYPE = 415; + + /** + * 系统内部错误 + */ + public static final int ERROR = 500; + + /** + * 接口未实现 + */ + public static final int NOT_IMPLEMENTED = 501; + + /** + * 系统警告消息 + */ + public static final int WARN = 601; +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/constant/ScheduleConstants.java b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/ScheduleConstants.java new file mode 100644 index 000000000..712c88663 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/ScheduleConstants.java @@ -0,0 +1,50 @@ +package com.roomroot.common.constant; + +/** + * 任务调度通用常量 + * + * @author roomroot + */ +public class ScheduleConstants +{ + public static final String TASK_CLASS_NAME = "TASK_CLASS_NAME"; + + /** 执行目标key */ + public static final String TASK_PROPERTIES = "TASK_PROPERTIES"; + + /** 默认 */ + public static final String MISFIRE_DEFAULT = "0"; + + /** 立即触发执行 */ + public static final String MISFIRE_IGNORE_MISFIRES = "1"; + + /** 触发一次执行 */ + public static final String MISFIRE_FIRE_AND_PROCEED = "2"; + + /** 不触发立即执行 */ + public static final String MISFIRE_DO_NOTHING = "3"; + + public enum Status + { + /** + * 正常 + */ + NORMAL("0"), + /** + * 暂停 + */ + PAUSE("1"); + + private String value; + + private Status(String value) + { + this.value = value; + } + + public String getValue() + { + return value; + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/constant/UserConstants.java b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/UserConstants.java new file mode 100644 index 000000000..80af6f20a --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/constant/UserConstants.java @@ -0,0 +1,81 @@ +package com.roomroot.common.constant; + +/** + * 用户常量信息 + * + * @author roomroot + */ +public class UserConstants +{ + /** + * 平台内系统用户的唯一标志 + */ + public static final String SYS_USER = "SYS_USER"; + + /** 正常状态 */ + public static final String NORMAL = "0"; + + /** 异常状态 */ + public static final String EXCEPTION = "1"; + + /** 用户封禁状态 */ + public static final String USER_DISABLE = "1"; + + /** 角色正常状态 */ + public static final String ROLE_NORMAL = "0"; + + /** 角色封禁状态 */ + public static final String ROLE_DISABLE = "1"; + + /** 部门正常状态 */ + public static final String DEPT_NORMAL = "0"; + + /** 部门停用状态 */ + public static final String DEPT_DISABLE = "1"; + + /** 字典正常状态 */ + public static final String DICT_NORMAL = "0"; + + /** 是否为系统默认(是) */ + public static final String YES = "Y"; + + /** 是否菜单外链(是) */ + public static final String YES_FRAME = "0"; + + /** 是否菜单外链(否) */ + public static final String NO_FRAME = "1"; + + /** 菜单类型(目录) */ + public static final String TYPE_DIR = "M"; + + /** 菜单类型(菜单) */ + public static final String TYPE_MENU = "C"; + + /** 菜单类型(按钮) */ + public static final String TYPE_BUTTON = "F"; + + /** Layout组件标识 */ + public final static String LAYOUT = "Layout"; + + /** ParentView组件标识 */ + public final static String PARENT_VIEW = "ParentView"; + + /** InnerLink组件标识 */ + public final static String INNER_LINK = "InnerLink"; + + /** 校验是否唯一的返回标识 */ + public final static boolean UNIQUE = true; + public final static boolean NOT_UNIQUE = false; + + /** + * 用户名长度限制 + */ + public static final int USERNAME_MIN_LENGTH = 2; + public static final int USERNAME_MAX_LENGTH = 20; + + /** + * 密码长度限制 + */ + public static final int PASSWORD_MIN_LENGTH = 5; + public static final int PASSWORD_MAX_LENGTH = 20; +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/controller/BaseController.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/controller/BaseController.java new file mode 100644 index 000000000..fae7f84c0 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/controller/BaseController.java @@ -0,0 +1,216 @@ +package com.roomroot.common.core.controller; + +import java.beans.PropertyEditorSupport; +import java.util.Date; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.bind.annotation.InitBinder; +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.common.constant.HttpStatus; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.core.page.PageDomain; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.core.page.TableSupport; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.PageUtils; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.sql.SqlUtil; + +/** + * web层通用数据处理 + * + * @author roomroot + */ +public class BaseController +{ + protected final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * 将前台传递过来的日期格式的字符串,自动转化为Date类型 + */ + @InitBinder + public void initBinder(WebDataBinder binder) + { + // Date 类型转换 + binder.registerCustomEditor(Date.class, new PropertyEditorSupport() + { + @Override + public void setAsText(String text) + { + setValue(DateUtils.parseDate(text)); + } + }); + } + + /** + * 设置请求分页数据 + */ + protected void startPage() + { + PageUtils.startPage(); + } + + /** + * 设置请求排序数据 + */ + protected void startOrderBy() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + if (StringUtils.isNotEmpty(pageDomain.getOrderBy())) + { + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + Page page = PageUtils.getPage(); + if (page != null) + { + String[] orderParts = orderBy.split("\\s+"); + if (orderParts.length > 0) + { + String column = orderParts[0]; + boolean isAsc = orderParts.length <= 1 || !"desc".equalsIgnoreCase(orderParts[1]); + OrderItem orderItem = new OrderItem(); + orderItem.setColumn(column); + orderItem.setAsc(isAsc); + page.addOrder(orderItem); + } + } + } + } + + /** + * 清理分页的线程变量 + */ + protected void clearPage() + { + PageUtils.clearPage(); + } + + /** + * 响应请求分页数据 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected TableDataInfo getDataTable(List list) + { + TableDataInfo rspData = new TableDataInfo(); + rspData.setCode(HttpStatus.SUCCESS); + rspData.setMsg("查询成功"); + rspData.setRows(list); + Page page = PageUtils.getPage(); + rspData.setTotal(page != null ? page.getTotal() : list.size()); + return rspData; + } + + /** + * 返回成功 + */ + public AjaxResult success() + { + return AjaxResult.success(); + } + + /** + * 返回失败消息 + */ + public AjaxResult error() + { + return AjaxResult.error(); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(String message) + { + return AjaxResult.success(message); + } + + /** + * 返回成功消息 + */ + public AjaxResult success(Object data) + { + return AjaxResult.success(data); + } + + /** + * 返回失败消息 + */ + public AjaxResult error(String message) + { + return AjaxResult.error(message); + } + + /** + * 返回警告消息 + */ + public AjaxResult warn(String message) + { + return AjaxResult.warn(message); + } + + /** + * 响应返回结果 + * + * @param rows 影响行数 + * @return 操作结果 + */ + protected AjaxResult toAjax(int rows) + { + return rows > 0 ? AjaxResult.success() : AjaxResult.error(); + } + + /** + * 响应返回结果 + * + * @param result 结果 + * @return 操作结果 + */ + protected AjaxResult toAjax(boolean result) + { + return result ? success() : error(); + } + + /** + * 页面跳转 + */ + public String redirect(String url) + { + return StringUtils.format("redirect:{}", url); + } + + /** + * 获取用户缓存信息 + */ + public LoginUser getLoginUser() + { + return SecurityUtils.getLoginUser(); + } + + /** + * 获取登录用户id + */ + public Long getUserId() + { + return getLoginUser().getUserId(); + } + + /** + * 获取登录部门id + */ + public Long getDeptId() + { + return getLoginUser().getDeptId(); + } + + /** + * 获取登录用户名 + */ + public String getUsername() + { + return getLoginUser().getUsername(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/AjaxResult.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/AjaxResult.java new file mode 100644 index 000000000..8a70057be --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/AjaxResult.java @@ -0,0 +1,216 @@ +package com.roomroot.common.core.domain; + +import java.util.HashMap; +import java.util.Objects; +import com.roomroot.common.constant.HttpStatus; +import com.roomroot.common.utils.StringUtils; + +/** + * 操作消息提醒 + * + * @author roomroot + */ +public class AjaxResult extends HashMap +{ + private static final long serialVersionUID = 1L; + + /** 状态码 */ + public static final String CODE_TAG = "code"; + + /** 返回内容 */ + public static final String MSG_TAG = "msg"; + + /** 数据对象 */ + public static final String DATA_TAG = "data"; + + /** + * 初始化一个新创建的 AjaxResult 对象,使其表示一个空消息。 + */ + public AjaxResult() + { + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + */ + public AjaxResult(int code, String msg) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + } + + /** + * 初始化一个新创建的 AjaxResult 对象 + * + * @param code 状态码 + * @param msg 返回内容 + * @param data 数据对象 + */ + public AjaxResult(int code, String msg, Object data) + { + super.put(CODE_TAG, code); + super.put(MSG_TAG, msg); + if (StringUtils.isNotNull(data)) + { + super.put(DATA_TAG, data); + } + } + + /** + * 返回成功消息 + * + * @return 成功消息 + */ + public static AjaxResult success() + { + return AjaxResult.success("操作成功"); + } + + /** + * 返回成功数据 + * + * @return 成功消息 + */ + public static AjaxResult success(Object data) + { + return AjaxResult.success("操作成功", data); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @return 成功消息 + */ + public static AjaxResult success(String msg) + { + return AjaxResult.success(msg, null); + } + + /** + * 返回成功消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 成功消息 + */ + public static AjaxResult success(String msg, Object data) + { + return new AjaxResult(HttpStatus.SUCCESS, msg, data); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @return 警告消息 + */ + public static AjaxResult warn(String msg) + { + return AjaxResult.warn(msg, null); + } + + /** + * 返回警告消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 警告消息 + */ + public static AjaxResult warn(String msg, Object data) + { + return new AjaxResult(HttpStatus.WARN, msg, data); + } + + /** + * 返回错误消息 + * + * @return 错误消息 + */ + public static AjaxResult error() + { + return AjaxResult.error("操作失败"); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(String msg) + { + return AjaxResult.error(msg, null); + } + + /** + * 返回错误消息 + * + * @param msg 返回内容 + * @param data 数据对象 + * @return 错误消息 + */ + public static AjaxResult error(String msg, Object data) + { + return new AjaxResult(HttpStatus.ERROR, msg, data); + } + + /** + * 返回错误消息 + * + * @param code 状态码 + * @param msg 返回内容 + * @return 错误消息 + */ + public static AjaxResult error(int code, String msg) + { + return new AjaxResult(code, msg, null); + } + + /** + * 是否为成功消息 + * + * @return 结果 + */ + public boolean isSuccess() + { + return Objects.equals(HttpStatus.SUCCESS, this.get(CODE_TAG)); + } + + /** + * 是否为警告消息 + * + * @return 结果 + */ + public boolean isWarn() + { + return Objects.equals(HttpStatus.WARN, this.get(CODE_TAG)); + } + + /** + * 是否为错误消息 + * + * @return 结果 + */ + public boolean isError() + { + return Objects.equals(HttpStatus.ERROR, this.get(CODE_TAG)); + } + + /** + * 方便链式调用 + * + * @param key 键 + * @param value 值 + * @return 数据对象 + */ + @Override + public AjaxResult put(String key, Object value) + { + super.put(key, value); + return this; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/BaseEntity.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/BaseEntity.java new file mode 100644 index 000000000..50647d120 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/BaseEntity.java @@ -0,0 +1,118 @@ +package com.roomroot.common.core.domain; + +import java.io.Serializable; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; + +/** + * Entity基类 + * + * @author roomroot + */ +public class BaseEntity implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 搜索值 */ + @JsonIgnore + private String searchValue; + + /** 创建者 */ + private String createBy; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新者 */ + private String updateBy; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 备注 */ + private String remark; + + /** 请求参数 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map params; + + public String getSearchValue() + { + return searchValue; + } + + public void setSearchValue(String searchValue) + { + this.searchValue = searchValue; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public Map getParams() + { + if (params == null) + { + params = new HashMap<>(); + } + return params; + } + + public void setParams(Map params) + { + this.params = params; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/R.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/R.java new file mode 100644 index 000000000..278f44738 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/R.java @@ -0,0 +1,115 @@ +package com.roomroot.common.core.domain; + +import java.io.Serializable; +import com.roomroot.common.constant.HttpStatus; + +/** + * 响应信息主体 + * + * @author roomroot + */ +public class R implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 成功 */ + public static final int SUCCESS = HttpStatus.SUCCESS; + + /** 失败 */ + public static final int FAIL = HttpStatus.ERROR; + + private int code; + + private String msg; + + private T data; + + public static R ok() + { + return restResult(null, SUCCESS, "操作成功"); + } + + public static R ok(T data) + { + return restResult(data, SUCCESS, "操作成功"); + } + + public static R ok(T data, String msg) + { + return restResult(data, SUCCESS, msg); + } + + public static R fail() + { + return restResult(null, FAIL, "操作失败"); + } + + public static R fail(String msg) + { + return restResult(null, FAIL, msg); + } + + public static R fail(T data) + { + return restResult(data, FAIL, "操作失败"); + } + + public static R fail(T data, String msg) + { + return restResult(data, FAIL, msg); + } + + public static R fail(int code, String msg) + { + return restResult(null, code, msg); + } + + private static R restResult(T data, int code, String msg) + { + R apiResult = new R<>(); + apiResult.setCode(code); + apiResult.setData(data); + apiResult.setMsg(msg); + return apiResult; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public T getData() + { + return data; + } + + public void setData(T data) + { + this.data = data; + } + + public static Boolean isError(R ret) + { + return !isSuccess(ret); + } + + public static Boolean isSuccess(R ret) + { + return R.SUCCESS == ret.getCode(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/TreeEntity.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/TreeEntity.java new file mode 100644 index 000000000..1596c90b6 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/TreeEntity.java @@ -0,0 +1,79 @@ +package com.roomroot.common.core.domain; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tree基类 + * + * @author roomroot + */ +public class TreeEntity extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 父菜单名称 */ + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private Integer orderNum; + + /** 祖级列表 */ + private String ancestors; + + /** 子部门 */ + private List children = new ArrayList<>(); + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/TreeSelect.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/TreeSelect.java new file mode 100644 index 000000000..95fddea38 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/TreeSelect.java @@ -0,0 +1,93 @@ +package com.roomroot.common.core.domain; + +import java.io.Serializable; +import java.util.List; +import java.util.stream.Collectors; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.entity.SysDept; +import com.roomroot.common.core.domain.entity.SysMenu; +import com.roomroot.common.utils.StringUtils; + +/** + * Treeselect树结构实体类 + * + * @author roomroot + */ +public class TreeSelect implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 节点ID */ + private Long id; + + /** 节点名称 */ + private String label; + + /** 节点禁用 */ + private boolean disabled = false; + + /** 子节点 */ + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List children; + + public TreeSelect() + { + + } + + public TreeSelect(SysDept dept) + { + this.id = dept.getDeptId(); + this.label = dept.getDeptName(); + this.disabled = StringUtils.equals(UserConstants.DEPT_DISABLE, dept.getStatus()); + this.children = dept.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public TreeSelect(SysMenu menu) + { + this.id = menu.getMenuId(); + this.label = menu.getMenuName(); + this.children = menu.getChildren().stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getLabel() + { + return label; + } + + public void setLabel(String label) + { + this.label = label; + } + + public boolean isDisabled() + { + return disabled; + } + + public void setDisabled(boolean disabled) + { + this.disabled = disabled; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysDept.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysDept.java new file mode 100644 index 000000000..f44efc311 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysDept.java @@ -0,0 +1,205 @@ +package com.roomroot.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * 部门表 sys_dept + * + * @author roomroot + */ +@TableName("sys_dept") +public class SysDept extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 部门ID */ + private Long deptId; + + /** 父部门ID */ + private Long parentId; + + /** 祖级列表 */ + private String ancestors; + + /** 部门名称 */ + private String deptName; + + /** 显示顺序 */ + private Integer orderNum; + + /** 负责人 */ + private String leader; + + /** 联系电话 */ + private String phone; + + /** 邮箱 */ + private String email; + + /** 部门状态:0正常,1停用 */ + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 父部门名称 */ + private String parentName; + + /** 子部门 */ + private List children = new ArrayList(); + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + public String getAncestors() + { + return ancestors; + } + + public void setAncestors(String ancestors) + { + this.ancestors = ancestors; + } + + @NotBlank(message = "部门名称不能为空") + @Size(min = 0, max = 30, message = "部门名称长度不能超过30个字符") + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + public String getLeader() + { + return leader; + } + + public void setLeader(String leader) + { + this.leader = leader; + } + + @Size(min = 0, max = 11, message = "联系电话长度不能超过11个字符") + public String getPhone() + { + return phone; + } + + public void setPhone(String phone) + { + this.phone = phone; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("deptId", getDeptId()) + .append("parentId", getParentId()) + .append("ancestors", getAncestors()) + .append("deptName", getDeptName()) + .append("orderNum", getOrderNum()) + .append("leader", getLeader()) + .append("phone", getPhone()) + .append("email", getEmail()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysDictData.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysDictData.java new file mode 100644 index 000000000..9bc151802 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysDictData.java @@ -0,0 +1,178 @@ +package com.roomroot.common.core.domain.entity; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.annotation.Excel.ColumnType; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * 字典数据表 sys_dict_data + * + * @author roomroot + */ +@TableName("sys_dict_data") +public class SysDictData extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典编码 */ + @Excel(name = "字典编码", cellType = ColumnType.NUMERIC) + private Long dictCode; + + /** 字典排序 */ + @Excel(name = "字典排序", cellType = ColumnType.NUMERIC) + private Long dictSort; + + /** 字典标签 */ + @Excel(name = "字典标签") + private String dictLabel; + + /** 字典键值 */ + @Excel(name = "字典键值") + private String dictValue; + + /** 字典类型 */ + @Excel(name = "字典类型") + private String dictType; + + /** 样式属性(其他样式扩展) */ + private String cssClass; + + /** 表格字典样式 */ + private String listClass; + + /** 是否默认(Y是 N否) */ + @Excel(name = "是否默认", readConverterExp = "Y=是,N=否") + private String isDefault; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + public Long getDictCode() + { + return dictCode; + } + + public void setDictCode(Long dictCode) + { + this.dictCode = dictCode; + } + + public Long getDictSort() + { + return dictSort; + } + + public void setDictSort(Long dictSort) + { + this.dictSort = dictSort; + } + + @NotBlank(message = "字典标签不能为空") + @Size(min = 0, max = 100, message = "字典标签长度不能超过100个字符") + public String getDictLabel() + { + return dictLabel; + } + + public void setDictLabel(String dictLabel) + { + this.dictLabel = dictLabel; + } + + @NotBlank(message = "字典键值不能为空") + @Size(min = 0, max = 100, message = "字典键值长度不能超过100个字符") + public String getDictValue() + { + return dictValue; + } + + public void setDictValue(String dictValue) + { + this.dictValue = dictValue; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型长度不能超过100个字符") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + @Size(min = 0, max = 100, message = "样式属性长度不能超过100个字符") + public String getCssClass() + { + return cssClass; + } + + public void setCssClass(String cssClass) + { + this.cssClass = cssClass; + } + + public String getListClass() + { + return listClass; + } + + public void setListClass(String listClass) + { + this.listClass = listClass; + } + + public boolean getDefault() + { + return UserConstants.YES.equals(this.isDefault); + } + + public String getIsDefault() + { + return isDefault; + } + + public void setIsDefault(String isDefault) + { + this.isDefault = isDefault; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictCode", getDictCode()) + .append("dictSort", getDictSort()) + .append("dictLabel", getDictLabel()) + .append("dictValue", getDictValue()) + .append("dictType", getDictType()) + .append("cssClass", getCssClass()) + .append("listClass", getListClass()) + .append("isDefault", getIsDefault()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysDictType.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysDictType.java new file mode 100644 index 000000000..2b0b82486 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysDictType.java @@ -0,0 +1,98 @@ +package com.roomroot.common.core.domain.entity; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.annotation.Excel.ColumnType; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * 字典类型表 sys_dict_type + * + * @author roomroot + */ +@TableName("sys_dict_type") +public class SysDictType extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 字典主键 */ + @Excel(name = "字典主键", cellType = ColumnType.NUMERIC) + private Long dictId; + + /** 字典名称 */ + @Excel(name = "字典名称") + private String dictName; + + /** 字典类型 */ + @Excel(name = "字典类型") + private String dictType; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + public Long getDictId() + { + return dictId; + } + + public void setDictId(Long dictId) + { + this.dictId = dictId; + } + + @NotBlank(message = "字典名称不能为空") + @Size(min = 0, max = 100, message = "字典类型名称长度不能超过100个字符") + public String getDictName() + { + return dictName; + } + + public void setDictName(String dictName) + { + this.dictName = dictName; + } + + @NotBlank(message = "字典类型不能为空") + @Size(min = 0, max = 100, message = "字典类型类型长度不能超过100个字符") + @Pattern(regexp = "^[a-z][a-z0-9_]*$", message = "字典类型必须以字母开头,且只能为(小写字母,数字,下滑线)") + public String getDictType() + { + return dictType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("dictId", getDictId()) + .append("dictName", getDictName()) + .append("dictType", getDictType()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysMenu.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysMenu.java new file mode 100644 index 000000000..94b2bbbcd --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysMenu.java @@ -0,0 +1,276 @@ +package com.roomroot.common.core.domain.entity; + +import java.util.ArrayList; +import java.util.List; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * 菜单权限表 sys_menu + * + * @author roomroot + */ +@TableName("sys_menu") +public class SysMenu extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 菜单ID */ + private Long menuId; + + /** 菜单名称 */ + private String menuName; + + /** 父菜单名称 */ + private String parentName; + + /** 父菜单ID */ + private Long parentId; + + /** 显示顺序 */ + private Integer orderNum; + + /** 路由地址 */ + private String path; + + /** 组件路径 */ + private String component; + + /** 路由参数 */ + private String query; + + /** 路由名称,默认和路由地址相同的驼峰格式(注意:因为vue3版本的router会删除名称相同路由,为避免名字的冲突,特殊情况可以自定义) */ + private String routeName; + + /** 是否为外链(0是 1否) */ + private String isFrame; + + /** 是否缓存(0缓存 1不缓存) */ + private String isCache; + + /** 类型(M目录 C菜单 F按钮) */ + private String menuType; + + /** 显示状态(0显示 1隐藏) */ + private String visible; + + /** 菜单状态(0正常 1停用) */ + private String status; + + /** 权限字符串 */ + private String perms; + + /** 菜单图标 */ + private String icon; + + /** 子菜单 */ + private List children = new ArrayList(); + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @NotBlank(message = "菜单名称不能为空") + @Size(min = 0, max = 50, message = "菜单名称长度不能超过50个字符") + public String getMenuName() + { + return menuName; + } + + public void setMenuName(String menuName) + { + this.menuName = menuName; + } + + public String getParentName() + { + return parentName; + } + + public void setParentName(String parentName) + { + this.parentName = parentName; + } + + public Long getParentId() + { + return parentId; + } + + public void setParentId(Long parentId) + { + this.parentId = parentId; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getOrderNum() + { + return orderNum; + } + + public void setOrderNum(Integer orderNum) + { + this.orderNum = orderNum; + } + + @Size(min = 0, max = 200, message = "路由地址不能超过200个字符") + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + @Size(min = 0, max = 200, message = "组件路径不能超过255个字符") + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public String getRouteName() + { + return routeName; + } + + public void setRouteName(String routeName) + { + this.routeName = routeName; + } + + public String getIsFrame() + { + return isFrame; + } + + public void setIsFrame(String isFrame) + { + this.isFrame = isFrame; + } + + public String getIsCache() + { + return isCache; + } + + public void setIsCache(String isCache) + { + this.isCache = isCache; + } + + @NotBlank(message = "菜单类型不能为空") + public String getMenuType() + { + return menuType; + } + + public void setMenuType(String menuType) + { + this.menuType = menuType; + } + + public String getVisible() + { + return visible; + } + + public void setVisible(String visible) + { + this.visible = visible; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Size(min = 0, max = 100, message = "权限标识长度不能超过100个字符") + public String getPerms() + { + return perms; + } + + public void setPerms(String perms) + { + this.perms = perms; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("menuId", getMenuId()) + .append("menuName", getMenuName()) + .append("parentId", getParentId()) + .append("orderNum", getOrderNum()) + .append("path", getPath()) + .append("component", getComponent()) + .append("query", getQuery()) + .append("routeName", getRouteName()) + .append("isFrame", getIsFrame()) + .append("IsCache", getIsCache()) + .append("menuType", getMenuType()) + .append("visible", getVisible()) + .append("status ", getStatus()) + .append("perms", getPerms()) + .append("icon", getIcon()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysRole.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysRole.java new file mode 100644 index 000000000..0c1f41f00 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysRole.java @@ -0,0 +1,243 @@ +package com.roomroot.common.core.domain.entity; + +import java.util.Set; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.annotation.Excel.ColumnType; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * 角色表 sys_role + * + * @author roomroot + */ +@TableName("sys_role") +public class SysRole extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 角色ID */ + @Excel(name = "角色序号", cellType = ColumnType.NUMERIC) + private Long roleId; + + /** 角色名称 */ + @Excel(name = "角色名称") + private String roleName; + + /** 角色权限 */ + @Excel(name = "角色权限") + private String roleKey; + + /** 角色排序 */ + @Excel(name = "角色排序") + private Integer roleSort; + + /** 数据范围(1:所有数据权限;2:自定义数据权限;3:本部门数据权限;4:本部门及以下数据权限;5:仅本人数据权限) */ + @Excel(name = "数据范围", readConverterExp = "1=所有数据权限,2=自定义数据权限,3=本部门数据权限,4=本部门及以下数据权限,5=仅本人数据权限") + private String dataScope; + + /** 菜单树选择项是否关联显示( 0:父子不互相关联显示 1:父子互相关联显示) */ + private boolean menuCheckStrictly; + + /** 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示 ) */ + private boolean deptCheckStrictly; + + /** 角色状态(0正常 1停用) */ + @Excel(name = "角色状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 用户是否存在此角色标识 默认不存在 */ + private boolean flag = false; + + /** 菜单组 */ + private Long[] menuIds; + + /** 部门组(数据权限) */ + private Long[] deptIds; + + /** 角色菜单权限 */ + private Set permissions; + + public SysRole() + { + + } + + public SysRole(Long roleId) + { + this.roleId = roleId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public boolean isAdmin() + { + return isAdmin(this.roleId); + } + + public static boolean isAdmin(Long roleId) + { + return roleId != null && 1L == roleId; + } + + @NotBlank(message = "角色名称不能为空") + @Size(min = 0, max = 30, message = "角色名称长度不能超过30个字符") + public String getRoleName() + { + return roleName; + } + + public void setRoleName(String roleName) + { + this.roleName = roleName; + } + + @NotBlank(message = "权限字符不能为空") + @Size(min = 0, max = 100, message = "权限字符长度不能超过100个字符") + public String getRoleKey() + { + return roleKey; + } + + public void setRoleKey(String roleKey) + { + this.roleKey = roleKey; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getRoleSort() + { + return roleSort; + } + + public void setRoleSort(Integer roleSort) + { + this.roleSort = roleSort; + } + + public String getDataScope() + { + return dataScope; + } + + public void setDataScope(String dataScope) + { + this.dataScope = dataScope; + } + + public boolean isMenuCheckStrictly() + { + return menuCheckStrictly; + } + + public void setMenuCheckStrictly(boolean menuCheckStrictly) + { + this.menuCheckStrictly = menuCheckStrictly; + } + + public boolean isDeptCheckStrictly() + { + return deptCheckStrictly; + } + + public void setDeptCheckStrictly(boolean deptCheckStrictly) + { + this.deptCheckStrictly = deptCheckStrictly; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + public Long[] getMenuIds() + { + return menuIds; + } + + public void setMenuIds(Long[] menuIds) + { + this.menuIds = menuIds; + } + + public Long[] getDeptIds() + { + return deptIds; + } + + public void setDeptIds(Long[] deptIds) + { + this.deptIds = deptIds; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("roleName", getRoleName()) + .append("roleKey", getRoleKey()) + .append("roleSort", getRoleSort()) + .append("dataScope", getDataScope()) + .append("menuCheckStrictly", isMenuCheckStrictly()) + .append("deptCheckStrictly", isDeptCheckStrictly()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysUser.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysUser.java new file mode 100644 index 000000000..d9380310f --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/SysUser.java @@ -0,0 +1,340 @@ +package com.roomroot.common.core.domain.entity; + +import java.util.Date; +import java.util.List; +import jakarta.validation.constraints.*; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.annotation.Excel.ColumnType; +import com.roomroot.common.annotation.Excel.Type; +import com.roomroot.common.annotation.Excels; +import com.roomroot.common.core.domain.BaseEntity; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.xss.Xss; + +/** + * 用户对象 sys_user + * + * @author roomroot + */ +@TableName("sys_user") +public class SysUser extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 用户ID */ + @Excel(name = "用户序号", type = Type.EXPORT, cellType = ColumnType.NUMERIC, prompt = "用户编号") + private Long userId; + + /** 部门ID */ + @Excel(name = "部门编号", type = Type.IMPORT) + private Long deptId; + + /** 用户账号 */ + @Excel(name = "登录名称") + private String userName; + + /** 用户昵称 */ + @Excel(name = "用户名称") + private String nickName; + + /** 用户邮箱 */ + @Excel(name = "用户邮箱") + private String email; + + /** 手机号码 */ + @Excel(name = "手机号码", cellType = ColumnType.TEXT) + private String phonenumber; + + /** 用户性别 */ + @Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知") + private String sex; + + /** 用户头像 */ + private String avatar; + + /** 密码 */ + private String password; + + /** 账号状态(0正常 1停用) */ + @Excel(name = "账号状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; + + /** 最后登录IP */ + @Excel(name = "最后登录IP", type = Type.EXPORT) + private String loginIp; + + /** 最后登录时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "最后登录时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss", type = Type.EXPORT) + private Date loginDate; + + /** 密码最后更新时间 */ + private Date pwdUpdateDate; + + /** 部门对象 */ + @Excels({ + @Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT), + @Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT) + }) + private SysDept dept; + + /** 角色对象 */ + private List roles; + + /** 角色组 */ + private Long[] roleIds; + + /** 岗位组 */ + private Long[] postIds; + + /** 角色ID */ + private Long roleId; + + public SysUser() + { + + } + + public SysUser(Long userId) + { + this.userId = userId; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public boolean isAdmin() + { + return SecurityUtils.isAdmin(this.userId); + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + @Xss(message = "用户昵称不能包含脚本字符") + @Size(min = 0, max = 30, message = "用户昵称长度不能超过30个字符") + public String getNickName() + { + return nickName; + } + + public void setNickName(String nickName) + { + this.nickName = nickName; + } + + @Xss(message = "用户账号不能包含脚本字符") + @NotBlank(message = "用户账号不能为空") + @Size(min = 0, max = 30, message = "用户账号长度不能超过30个字符") + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + @Email(message = "邮箱格式不正确") + @Size(min = 0, max = 50, message = "邮箱长度不能超过50个字符") + public String getEmail() + { + return email; + } + + public void setEmail(String email) + { + this.email = email; + } + + @Size(min = 0, max = 11, message = "手机号码长度不能超过11个字符") + public String getPhonenumber() + { + return phonenumber; + } + + public void setPhonenumber(String phonenumber) + { + this.phonenumber = phonenumber; + } + + public String getSex() + { + return sex; + } + + public void setSex(String sex) + { + this.sex = sex; + } + + public String getAvatar() + { + return avatar; + } + + public void setAvatar(String avatar) + { + this.avatar = avatar; + } + + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getDelFlag() + { + return delFlag; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getLoginIp() + { + return loginIp; + } + + public void setLoginIp(String loginIp) + { + this.loginIp = loginIp; + } + + public Date getLoginDate() + { + return loginDate; + } + + public void setLoginDate(Date loginDate) + { + this.loginDate = loginDate; + } + + public Date getPwdUpdateDate() + { + return pwdUpdateDate; + } + + public void setPwdUpdateDate(Date pwdUpdateDate) + { + this.pwdUpdateDate = pwdUpdateDate; + } + + public SysDept getDept() + { + return dept; + } + + public void setDept(SysDept dept) + { + this.dept = dept; + } + + public List getRoles() + { + return roles; + } + + public void setRoles(List roles) + { + this.roles = roles; + } + + public Long[] getRoleIds() + { + return roleIds; + } + + public void setRoleIds(Long[] roleIds) + { + this.roleIds = roleIds; + } + + public Long[] getPostIds() + { + return postIds; + } + + public void setPostIds(Long[] postIds) + { + this.postIds = postIds; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("deptId", getDeptId()) + .append("userName", getUserName()) + .append("nickName", getNickName()) + .append("email", getEmail()) + .append("phonenumber", getPhonenumber()) + .append("sex", getSex()) + .append("avatar", getAvatar()) + .append("password", getPassword()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("loginIp", getLoginIp()) + .append("loginDate", getLoginDate()) + .append("pwdUpdateDate", getPwdUpdateDate()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("dept", getDept()) + .toString(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxClass.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxClass.java new file mode 100644 index 000000000..eb247fb8b --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxClass.java @@ -0,0 +1,156 @@ +package com.roomroot.common.core.domain.entity; + +import java.io.Serializable; +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.common.annotation.Excel; +import tools.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.ser.std.ToStringSerializer; + +@TableName("t_zhjx_kcgl_class") +public class ZhjxClass implements Serializable +{ + private static final long serialVersionUID = 1L; + + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + @Excel(name = "班级名称") + private String className; + + @Excel(name = "任课教师") + private String teacherName; + + @Excel(name = "学生人数") + private Integer studentCount; + + private String status; + + private String isDelete; + + private String createBy; + + private Date createTime; + + private String updateBy; + + private Date updateTime; + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getClassName() + { + return className; + } + + public void setClassName(String className) + { + this.className = className; + } + + public String getTeacherName() + { + return teacherName; + } + + public void setTeacherName(String teacherName) + { + this.teacherName = teacherName; + } + + public Integer getStudentCount() + { + return studentCount; + } + + public void setStudentCount(Integer studentCount) + { + this.studentCount = studentCount; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getIsDelete() + { + return isDelete; + } + + public void setIsDelete(String isDelete) + { + this.isDelete = isDelete; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("className", getClassName()) + .append("teacherName", getTeacherName()) + .append("studentCount", getStudentCount()) + .append("status", getStatus()) + .append("isDelete", getIsDelete()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxClassReport.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxClassReport.java new file mode 100644 index 000000000..13d6c86a3 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxClassReport.java @@ -0,0 +1,204 @@ +package com.roomroot.common.core.domain.entity; + +import java.io.Serializable; +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.roomroot.common.annotation.Excel; +import tools.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.ser.std.ToStringSerializer; + +/** + * 课堂报告实体类 t_zhjx_class_report + * + * @author roomroot + */ +@TableName("t_zhjx_class_report") +public class ZhjxClassReport implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + /** 报告名称 */ + @Excel(name = "报告名称") + private String reportName; + + /** 班级名称 */ + @Excel(name = "班级名称") + private String className; + + /** 报告日期 */ + @Excel(name = "报告日期", dateFormat = "yyyy-MM-dd") + @JsonFormat(pattern = "yyyy-MM-dd") + private Date reportDate; + + /** 报告类型 */ + @Excel(name = "报告类型") + private String reportType; + + /** 报告内容 */ + private String reportContent; + + /** 创建人 */ + private String createBy; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新人 */ + private String updateBy; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 备注 */ + private String remark; + + /** 是否删除:0未删除,1已删除 */ + private String isDelete; + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getReportName() + { + return reportName; + } + + public void setReportName(String reportName) + { + this.reportName = reportName; + } + + public String getClassName() + { + return className; + } + + public void setClassName(String className) + { + this.className = className; + } + + public Date getReportDate() + { + return reportDate; + } + + public void setReportDate(Date reportDate) + { + this.reportDate = reportDate; + } + + public String getReportType() + { + return reportType; + } + + public void setReportType(String reportType) + { + this.reportType = reportType; + } + + public String getReportContent() + { + return reportContent; + } + + public void setReportContent(String reportContent) + { + this.reportContent = reportContent; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public String getIsDelete() + { + return isDelete; + } + + public void setIsDelete(String isDelete) + { + this.isDelete = isDelete; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("reportName", getReportName()) + .append("className", getClassName()) + .append("reportDate", getReportDate()) + .append("reportType", getReportType()) + .append("reportContent", getReportContent()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("isDelete", getIsDelete()) + .toString(); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxCourse.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxCourse.java new file mode 100644 index 000000000..0b8c3cb05 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxCourse.java @@ -0,0 +1,150 @@ +package com.roomroot.common.core.domain.entity; + +import java.io.Serializable; +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; + +@TableName("t_zhjx_jxkc_course") +public class ZhjxCourse implements Serializable +{ + private static final long serialVersionUID = 1L; + + private Long id; + + private String courseName; + + private String teacherName; + + private String courseTime; + + /** 学期:0上学期,1下学期 */ + private String semester; + + private String isDelete; + + private String createBy; + + private Date createTime; + + private String updateBy; + + private Date updateTime; + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getCourseName() + { + return courseName; + } + + public void setCourseName(String courseName) + { + this.courseName = courseName; + } + + public String getTeacherName() + { + return teacherName; + } + + public void setTeacherName(String teacherName) + { + this.teacherName = teacherName; + } + + public String getCourseTime() + { + return courseTime; + } + + public void setCourseTime(String courseTime) + { + this.courseTime = courseTime; + } + + public String getSemester() + { + return semester; + } + + public void setSemester(String semester) + { + this.semester = semester; + } + + public String getIsDelete() + { + return isDelete; + } + + public void setIsDelete(String isDelete) + { + this.isDelete = isDelete; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("courseName", getCourseName()) + .append("teacherName", getTeacherName()) + .append("courseTime", getCourseTime()) + .append("semester", getSemester()) + .append("isDelete", getIsDelete()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxJspxCourse.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxJspxCourse.java new file mode 100644 index 000000000..deb476e12 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxJspxCourse.java @@ -0,0 +1,123 @@ +package com.roomroot.common.core.domain.entity; + +import java.io.Serializable; +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; + +@TableName("t_zhjx_jspx_course") +public class ZhjxJspxCourse implements Serializable +{ + private static final long serialVersionUID = 1L; + + private Long id; + + private String courseName; + + private String trainingTime; + + private String speaker; + + private String createBy; + + private Date createTime; + + private String updateBy; + + private Date updateTime; + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getCourseName() + { + return courseName; + } + + public void setCourseName(String courseName) + { + this.courseName = courseName; + } + + public String getTrainingTime() + { + return trainingTime; + } + + public void setTrainingTime(String trainingTime) + { + this.trainingTime = trainingTime; + } + + public String getSpeaker() + { + return speaker; + } + + public void setSpeaker(String speaker) + { + this.speaker = speaker; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("courseName", getCourseName()) + .append("trainingTime", getTrainingTime()) + .append("speaker", getSpeaker()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxJxkcDocument.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxJxkcDocument.java new file mode 100644 index 000000000..b867711d7 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxJxkcDocument.java @@ -0,0 +1,189 @@ +package com.roomroot.common.core.domain.entity; + +import java.io.Serializable; +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.roomroot.common.annotation.Excel; +import tools.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.ser.std.ToStringSerializer; + +/** + * 课程附件实体类 t_zhjx_jxkc_document + * + * @author roomroot + */ +@TableName("t_zhjx_jxkc_document") +public class ZhjxJxkcDocument implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + /** 文件名 */ + @Excel(name = "文件名") + private String fileName; + + /** 文件路径 */ + private String filePath; + + /** 文件大小(KB) */ + private Long fileSize; + + /** 文件类型 */ + @Excel(name = "文件类型") + private String fileType; + + /** 所属课程ID */ + @JsonSerialize(using = ToStringSerializer.class) + private Long courseId; + + /** 上传时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date uploadTime; + + /** 上传人 */ + private String uploadBy; + + /** 删除标识:0未删除,1已删除 */ + private String isDelete; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getFileName() + { + return fileName; + } + + public void setFileName(String fileName) + { + this.fileName = fileName; + } + + public String getFilePath() + { + return filePath; + } + + public void setFilePath(String filePath) + { + this.filePath = filePath; + } + + public Long getFileSize() + { + return fileSize; + } + + public void setFileSize(Long fileSize) + { + this.fileSize = fileSize; + } + + public String getFileType() + { + return fileType; + } + + public void setFileType(String fileType) + { + this.fileType = fileType; + } + + public Long getCourseId() + { + return courseId; + } + + public void setCourseId(Long courseId) + { + this.courseId = courseId; + } + + public Date getUploadTime() + { + return uploadTime; + } + + public void setUploadTime(Date uploadTime) + { + this.uploadTime = uploadTime; + } + + public String getUploadBy() + { + return uploadBy; + } + + public void setUploadBy(String uploadBy) + { + this.uploadBy = uploadBy; + } + + public String getIsDelete() + { + return isDelete; + } + + public void setIsDelete(String isDelete) + { + this.isDelete = isDelete; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("fileName", getFileName()) + .append("filePath", getFilePath()) + .append("fileSize", getFileSize()) + .append("fileType", getFileType()) + .append("courseId", getCourseId()) + .append("uploadTime", getUploadTime()) + .append("uploadBy", getUploadBy()) + .append("isDelete", getIsDelete()) + .append("createTime", getCreateTime()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxMyCourse.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxMyCourse.java new file mode 100644 index 000000000..34a69d527 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxMyCourse.java @@ -0,0 +1,153 @@ +package com.roomroot.common.core.domain.entity; + +import java.io.Serializable; +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; + +@TableName("t_zhjx_my_course") +public class ZhjxMyCourse implements Serializable +{ + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long id; + + private String courseName; + + private String teacherName; + + private String courseTime; + + private String isTop; + + private Integer sortOrder; + + private String createBy; + + private Date createTime; + + private String updateBy; + + private Date updateTime; + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getCourseName() + { + return courseName; + } + + public void setCourseName(String courseName) + { + this.courseName = courseName; + } + + public String getTeacherName() + { + return teacherName; + } + + public void setTeacherName(String teacherName) + { + this.teacherName = teacherName; + } + + public String getCourseTime() + { + return courseTime; + } + + public void setCourseTime(String courseTime) + { + this.courseTime = courseTime; + } + + public String getIsTop() + { + return isTop; + } + + public void setIsTop(String isTop) + { + this.isTop = isTop; + } + + public Integer getSortOrder() + { + return sortOrder; + } + + public void setSortOrder(Integer sortOrder) + { + this.sortOrder = sortOrder; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("courseName", getCourseName()) + .append("teacherName", getTeacherName()) + .append("courseTime", getCourseTime()) + .append("isTop", getIsTop()) + .append("sortOrder", getSortOrder()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxSchool.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxSchool.java new file mode 100644 index 000000000..1597928d3 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxSchool.java @@ -0,0 +1,110 @@ +package com.roomroot.common.core.domain.entity; + +import java.io.Serializable; +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; + +@TableName("t_zhjx_school") +public class ZhjxSchool implements Serializable +{ + private static final long serialVersionUID = 1L; + + private Long id; + + private String schoolName; + + private String schoolNo; + + private String createBy; + + private Date createTime; + + private String updateBy; + + private Date updateTime; + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getSchoolName() + { + return schoolName; + } + + public void setSchoolName(String schoolName) + { + this.schoolName = schoolName; + } + + public String getSchoolNo() + { + return schoolNo; + } + + public void setSchoolNo(String schoolNo) + { + this.schoolNo = schoolNo; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("schoolName", getSchoolName()) + .append("schoolNo", getSchoolNo()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxTszlFile.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxTszlFile.java new file mode 100644 index 000000000..355cfe47b --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxTszlFile.java @@ -0,0 +1,216 @@ +package com.roomroot.common.core.domain.entity; + +import java.io.Serializable; +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.roomroot.common.annotation.Excel; +import tools.jackson.databind.annotation.JsonSerialize; +import tools.jackson.databind.ser.std.ToStringSerializer; + +/** + * 资料推送实体类 t_zhjx_tszl_file + * + * @author roomroot + */ +@TableName("t_zhjx_tszl_file") +public class ZhjxTszlFile implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 主键ID */ + @JsonSerialize(using = ToStringSerializer.class) + private Long id; + + /** 资料名称 */ + @Excel(name = "资料名称") + private String fileName; + + /** 文件路径 */ + private String filePath; + + /** 文件类型 */ + @Excel(name = "文件类型") + private String fileType; + + /** 文件大小 */ + private Long fileSize; + + /** 状态:0未发布,1已发布,2已撤回 */ + private String status; + + /** 发布时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date publishTime; + + /** 创建人 */ + private String createBy; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新人 */ + private String updateBy; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** 备注 */ + private String remark; + + /** 是否删除:0未删除,1已删除 */ + private String isDelete; + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public String getFileName() + { + return fileName; + } + + public void setFileName(String fileName) + { + this.fileName = fileName; + } + + public String getFilePath() + { + return filePath; + } + + public void setFilePath(String filePath) + { + this.filePath = filePath; + } + + public String getFileType() + { + return fileType; + } + + public void setFileType(String fileType) + { + this.fileType = fileType; + } + + public Long getFileSize() + { + return fileSize; + } + + public void setFileSize(Long fileSize) + { + this.fileSize = fileSize; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public Date getPublishTime() + { + return publishTime; + } + + public void setPublishTime(Date publishTime) + { + this.publishTime = publishTime; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public String getIsDelete() + { + return isDelete; + } + + public void setIsDelete(String isDelete) + { + this.isDelete = isDelete; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("fileName", getFileName()) + .append("filePath", getFilePath()) + .append("fileType", getFileType()) + .append("fileSize", getFileSize()) + .append("status", getStatus()) + .append("publishTime", getPublishTime()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .append("isDelete", getIsDelete()) + .toString(); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxYxpxCourse.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxYxpxCourse.java new file mode 100644 index 000000000..a3f310c54 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/entity/ZhjxYxpxCourse.java @@ -0,0 +1,147 @@ +package com.roomroot.common.core.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +@TableName("t_zhjx_yxpx_course") +public class ZhjxYxpxCourse +{ + private static final long serialVersionUID = 1L; + + @TableId(type = IdType.AUTO) + private Long id; + + @TableField("source_id") + private Long sourceId; + + @TableField("course_name") + private String courseName; + + @TableField("training_time") + private String trainingTime; + + @TableField("speaker") + private String speaker; + + @TableField("create_by") + private String createBy; + + @TableField("create_time") + private Date createTime; + + @TableField("update_by") + private String updateBy; + + @TableField("update_time") + private Date updateTime; + + public void setId(Long id) + { + this.id = id; + } + + public Long getId() + { + return id; + } + + public void setSourceId(Long sourceId) + { + this.sourceId = sourceId; + } + + public Long getSourceId() + { + return sourceId; + } + + public void setCourseName(String courseName) + { + this.courseName = courseName; + } + + public String getCourseName() + { + return courseName; + } + + public void setTrainingTime(String trainingTime) + { + this.trainingTime = trainingTime; + } + + public String getTrainingTime() + { + return trainingTime; + } + + public void setSpeaker(String speaker) + { + this.speaker = speaker; + } + + public String getSpeaker() + { + return speaker; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + public Date getUpdateTime() + { + return updateTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("id", getId()) + .append("sourceId", getSourceId()) + .append("courseName", getCourseName()) + .append("trainingTime", getTrainingTime()) + .append("speaker", getSpeaker()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .toString(); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/model/LoginBody.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/model/LoginBody.java new file mode 100644 index 000000000..2e2a552ef --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/model/LoginBody.java @@ -0,0 +1,69 @@ +package com.roomroot.common.core.domain.model; + +/** + * 用户登录对象 + * + * @author roomroot + */ +public class LoginBody +{ + /** + * 用户名 + */ + private String username; + + /** + * 用户密码 + */ + private String password; + + /** + * 验证码 + */ + private String code; + + /** + * 唯一标识 + */ + private String uuid; + + public String getUsername() + { + return username; + } + + public void setUsername(String username) + { + this.username = username; + } + + public String getPassword() + { + return password; + } + + public void setPassword(String password) + { + this.password = password; + } + + public String getCode() + { + return code; + } + + public void setCode(String code) + { + this.code = code; + } + + public String getUuid() + { + return uuid; + } + + public void setUuid(String uuid) + { + this.uuid = uuid; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/model/LoginUser.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/model/LoginUser.java new file mode 100644 index 000000000..c0cdc92ca --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/model/LoginUser.java @@ -0,0 +1,278 @@ +package com.roomroot.common.core.domain.model; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import com.alibaba.fastjson2.annotation.JSONField; +import com.roomroot.common.core.domain.entity.SysUser; + +/** + * 登录用户身份权限 + * + * @author roomroot + */ +public class LoginUser implements UserDetails +{ + private static final long serialVersionUID = 1L; + + /** + * 用户ID + */ + private Long userId; + + /** + * 部门ID + */ + private Long deptId; + + /** + * 用户唯一标识 + */ + private String token; + + /** + * 登录时间 + */ + private Long loginTime; + + /** + * 过期时间 + */ + private Long expireTime; + + /** + * 登录IP地址 + */ + private String ipaddr; + + /** + * 登录地点 + */ + private String loginLocation; + + /** + * 浏览器类型 + */ + private String browser; + + /** + * 操作系统 + */ + private String os; + + /** + * 权限列表 + */ + private Set permissions; + + /** + * 用户信息 + */ + private SysUser user; + + public LoginUser() + { + } + + public LoginUser(SysUser user, Set permissions) + { + this.user = user; + this.permissions = permissions; + } + + public LoginUser(Long userId, Long deptId, SysUser user, Set permissions) + { + this.userId = userId; + this.deptId = deptId; + this.user = user; + this.permissions = permissions; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + public String getToken() + { + return token; + } + + public void setToken(String token) + { + this.token = token; + } + + @JSONField(serialize = false) + @Override + public String getPassword() + { + return user.getPassword(); + } + + @Override + public String getUsername() + { + return user.getUserName(); + } + + /** + * 账户是否未过期,过期无法验证 + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonExpired() + { + return true; + } + + /** + * 指定用户是否解锁,锁定的用户无法进行身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isAccountNonLocked() + { + return true; + } + + /** + * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isCredentialsNonExpired() + { + return true; + } + + /** + * 是否可用 ,禁用的用户不能身份验证 + * + * @return + */ + @JSONField(serialize = false) + @Override + public boolean isEnabled() + { + return true; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Long getExpireTime() + { + return expireTime; + } + + public void setExpireTime(Long expireTime) + { + this.expireTime = expireTime; + } + + public Set getPermissions() + { + return permissions; + } + + public void setPermissions(Set permissions) + { + this.permissions = permissions; + } + + public SysUser getUser() + { + return user; + } + + public void setUser(SysUser user) + { + this.user = user; + } + + @Override + @JSONField(serialize = false) + public Collection getAuthorities() + { + if (permissions == null || permissions.isEmpty()) + { + return Collections.emptyList(); + } + return permissions.stream() + .filter(Objects::nonNull) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/model/RegisterBody.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/model/RegisterBody.java new file mode 100644 index 000000000..6a77eddef --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/domain/model/RegisterBody.java @@ -0,0 +1,11 @@ +package com.roomroot.common.core.domain.model; + +/** + * 用户注册对象 + * + * @author roomroot + */ +public class RegisterBody extends LoginBody +{ + +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/page/PageDomain.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/page/PageDomain.java new file mode 100644 index 000000000..d0fe626c3 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/page/PageDomain.java @@ -0,0 +1,101 @@ +package com.roomroot.common.core.page; + +import com.roomroot.common.utils.StringUtils; + +/** + * 分页数据 + * + * @author roomroot + */ +public class PageDomain +{ + /** 当前记录起始索引 */ + private Integer pageNum; + + /** 每页显示记录数 */ + private Integer pageSize; + + /** 排序列 */ + private String orderByColumn; + + /** 排序的方向desc或者asc */ + private String isAsc = "asc"; + + /** 分页参数合理化 */ + private Boolean reasonable = true; + + public String getOrderBy() + { + if (StringUtils.isEmpty(orderByColumn)) + { + return ""; + } + return StringUtils.toUnderScoreCase(orderByColumn) + " " + isAsc; + } + + public Integer getPageNum() + { + return pageNum; + } + + public void setPageNum(Integer pageNum) + { + this.pageNum = pageNum; + } + + public Integer getPageSize() + { + return pageSize; + } + + public void setPageSize(Integer pageSize) + { + this.pageSize = pageSize; + } + + public String getOrderByColumn() + { + return orderByColumn; + } + + public void setOrderByColumn(String orderByColumn) + { + this.orderByColumn = orderByColumn; + } + + public String getIsAsc() + { + return isAsc; + } + + public void setIsAsc(String isAsc) + { + if (StringUtils.isNotEmpty(isAsc)) + { + // 兼容前端排序类型 + if ("ascending".equals(isAsc)) + { + isAsc = "asc"; + } + else if ("descending".equals(isAsc)) + { + isAsc = "desc"; + } + this.isAsc = isAsc; + } + } + + public Boolean getReasonable() + { + if (StringUtils.isNull(reasonable)) + { + return Boolean.TRUE; + } + return reasonable; + } + + public void setReasonable(Boolean reasonable) + { + this.reasonable = reasonable; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/page/TableDataInfo.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/page/TableDataInfo.java new file mode 100644 index 000000000..2edef438c --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/page/TableDataInfo.java @@ -0,0 +1,85 @@ +package com.roomroot.common.core.page; + +import java.io.Serializable; +import java.util.List; + +/** + * 表格分页数据对象 + * + * @author roomroot + */ +public class TableDataInfo implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 总记录数 */ + private long total; + + /** 列表数据 */ + private List rows; + + /** 消息状态码 */ + private int code; + + /** 消息内容 */ + private String msg; + + /** + * 表格数据对象 + */ + public TableDataInfo() + { + } + + /** + * 分页 + * + * @param list 列表数据 + * @param total 总记录数 + */ + public TableDataInfo(List list, long total) + { + this.rows = list; + this.total = total; + } + + public long getTotal() + { + return total; + } + + public void setTotal(long total) + { + this.total = total; + } + + public List getRows() + { + return rows; + } + + public void setRows(List rows) + { + this.rows = rows; + } + + public int getCode() + { + return code; + } + + public void setCode(int code) + { + this.code = code; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/page/TableSupport.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/page/TableSupport.java new file mode 100644 index 000000000..b5b76936e --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/page/TableSupport.java @@ -0,0 +1,56 @@ +package com.roomroot.common.core.page; + +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.utils.ServletUtils; + +/** + * 表格数据处理 + * + * @author roomroot + */ +public class TableSupport +{ + /** + * 当前记录起始索引 + */ + public static final String PAGE_NUM = "pageNum"; + + /** + * 每页显示记录数 + */ + public static final String PAGE_SIZE = "pageSize"; + + /** + * 排序列 + */ + public static final String ORDER_BY_COLUMN = "orderByColumn"; + + /** + * 排序的方向 "desc" 或者 "asc". + */ + public static final String IS_ASC = "isAsc"; + + /** + * 分页参数合理化 + */ + public static final String REASONABLE = "reasonable"; + + /** + * 封装分页对象 + */ + public static PageDomain getPageDomain() + { + PageDomain pageDomain = new PageDomain(); + pageDomain.setPageNum(Convert.toInt(ServletUtils.getParameter(PAGE_NUM), 1)); + pageDomain.setPageSize(Convert.toInt(ServletUtils.getParameter(PAGE_SIZE), 10)); + pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN)); + pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC)); + pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE)); + return pageDomain; + } + + public static PageDomain buildPageRequest() + { + return getPageDomain(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/redis/RedisCache.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/redis/RedisCache.java new file mode 100644 index 000000000..e670d0b03 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/redis/RedisCache.java @@ -0,0 +1,268 @@ +package com.roomroot.common.core.redis; + +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.BoundSetOperations; +import org.springframework.data.redis.core.HashOperations; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +/** + * spring redis 工具类 + * + * @author roomroot + **/ +@SuppressWarnings(value = { "unchecked", "rawtypes" }) +@Component +public class RedisCache +{ + @Autowired + public RedisTemplate redisTemplate; + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + */ + public void setCacheObject(final String key, final T value) + { + redisTemplate.opsForValue().set(key, value); + } + + /** + * 缓存基本的对象,Integer、String、实体类等 + * + * @param key 缓存的键值 + * @param value 缓存的值 + * @param timeout 时间 + * @param timeUnit 时间颗粒度 + */ + public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) + { + redisTemplate.opsForValue().set(key, value, timeout, timeUnit); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout) + { + return expire(key, timeout, TimeUnit.SECONDS); + } + + /** + * 设置有效时间 + * + * @param key Redis键 + * @param timeout 超时时间 + * @param unit 时间单位 + * @return true=设置成功;false=设置失败 + */ + public boolean expire(final String key, final long timeout, final TimeUnit unit) + { + return redisTemplate.expire(key, timeout, unit); + } + + /** + * 获取有效时间 + * + * @param key Redis键 + * @return 有效时间 + */ + public long getExpire(final String key) + { + return redisTemplate.getExpire(key); + } + + /** + * 判断 key是否存在 + * + * @param key 键 + * @return true 存在 false不存在 + */ + public Boolean hasKey(String key) + { + return redisTemplate.hasKey(key); + } + + /** + * 获得缓存的基本对象。 + * + * @param key 缓存键值 + * @return 缓存键值对应的数据 + */ + public T getCacheObject(final String key) + { + ValueOperations operation = redisTemplate.opsForValue(); + return operation.get(key); + } + + /** + * 删除单个对象 + * + * @param key + */ + public boolean deleteObject(final String key) + { + return redisTemplate.delete(key); + } + + /** + * 删除集合对象 + * + * @param collection 多个对象 + * @return + */ + public boolean deleteObject(final Collection collection) + { + return redisTemplate.delete(collection) > 0; + } + + /** + * 缓存List数据 + * + * @param key 缓存的键值 + * @param dataList 待缓存的List数据 + * @return 缓存的对象 + */ + public long setCacheList(final String key, final List dataList) + { + Long count = redisTemplate.opsForList().rightPushAll(key, dataList); + return count == null ? 0 : count; + } + + /** + * 获得缓存的list对象 + * + * @param key 缓存的键值 + * @return 缓存键值对应的数据 + */ + public List getCacheList(final String key) + { + return redisTemplate.opsForList().range(key, 0, -1); + } + + /** + * 缓存Set + * + * @param key 缓存键值 + * @param dataSet 缓存的数据 + * @return 缓存数据的对象 + */ + public BoundSetOperations setCacheSet(final String key, final Set dataSet) + { + BoundSetOperations setOperation = redisTemplate.boundSetOps(key); + Iterator it = dataSet.iterator(); + while (it.hasNext()) + { + setOperation.add(it.next()); + } + return setOperation; + } + + /** + * 获得缓存的set + * + * @param key + * @return + */ + public Set getCacheSet(final String key) + { + return redisTemplate.opsForSet().members(key); + } + + /** + * 缓存Map + * + * @param key + * @param dataMap + */ + public void setCacheMap(final String key, final Map dataMap) + { + if (dataMap != null) { + redisTemplate.opsForHash().putAll(key, dataMap); + } + } + + /** + * 获得缓存的Map + * + * @param key + * @return + */ + public Map getCacheMap(final String key) + { + return redisTemplate.opsForHash().entries(key); + } + + /** + * 往Hash中存入数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @param value 值 + */ + public void setCacheMapValue(final String key, final String hKey, final T value) + { + redisTemplate.opsForHash().put(key, hKey, value); + } + + /** + * 获取Hash中的数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return Hash中的对象 + */ + public T getCacheMapValue(final String key, final String hKey) + { + HashOperations opsForHash = redisTemplate.opsForHash(); + return opsForHash.get(key, hKey); + } + + /** + * 获取多个Hash中的数据 + * + * @param key Redis键 + * @param hKeys Hash键集合 + * @return Hash对象集合 + */ + public List getMultiCacheMapValue(final String key, final Collection hKeys) + { + return redisTemplate.opsForHash().multiGet(key, hKeys); + } + + /** + * 删除Hash中的某条数据 + * + * @param key Redis键 + * @param hKey Hash键 + * @return 是否成功 + */ + public boolean deleteCacheMapValue(final String key, final String hKey) + { + return redisTemplate.opsForHash().delete(key, hKey) > 0; + } + + /** + * 获得缓存的基本对象列表 + * + * @param pattern 字符串前缀 + * @return 对象列表 + */ + public Collection keys(final String pattern) + { + return redisTemplate.keys(pattern); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/text/CharsetKit.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/text/CharsetKit.java new file mode 100644 index 000000000..645f42d79 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/text/CharsetKit.java @@ -0,0 +1,86 @@ +package com.roomroot.common.core.text; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import com.roomroot.common.utils.StringUtils; + +/** + * 字符集工具类 + * + * @author roomroot + */ +public class CharsetKit +{ + /** ISO-8859-1 */ + public static final String ISO_8859_1 = "ISO-8859-1"; + /** UTF-8 */ + public static final String UTF_8 = "UTF-8"; + /** GBK */ + public static final String GBK = "GBK"; + + /** ISO-8859-1 */ + public static final Charset CHARSET_ISO_8859_1 = Charset.forName(ISO_8859_1); + /** UTF-8 */ + public static final Charset CHARSET_UTF_8 = Charset.forName(UTF_8); + /** GBK */ + public static final Charset CHARSET_GBK = Charset.forName(GBK); + + /** + * 转换为Charset对象 + * + * @param charset 字符集,为空则返回默认字符集 + * @return Charset + */ + public static Charset charset(String charset) + { + return StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, String srcCharset, String destCharset) + { + return convert(source, Charset.forName(srcCharset), Charset.forName(destCharset)); + } + + /** + * 转换字符串的字符集编码 + * + * @param source 字符串 + * @param srcCharset 源字符集,默认ISO-8859-1 + * @param destCharset 目标字符集,默认UTF-8 + * @return 转换后的字符集 + */ + public static String convert(String source, Charset srcCharset, Charset destCharset) + { + if (null == srcCharset) + { + srcCharset = StandardCharsets.ISO_8859_1; + } + + if (null == destCharset) + { + destCharset = StandardCharsets.UTF_8; + } + + if (StringUtils.isEmpty(source) || srcCharset.equals(destCharset)) + { + return source; + } + return new String(source.getBytes(srcCharset), destCharset); + } + + /** + * @return 系统字符集编码 + */ + public static String systemCharset() + { + return Charset.defaultCharset().name(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/text/Convert.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/text/Convert.java new file mode 100644 index 000000000..28d57b610 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/text/Convert.java @@ -0,0 +1,1018 @@ +package com.roomroot.common.core.text; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.text.NumberFormat; +import java.util.Set; +import com.roomroot.common.utils.StringUtils; + +/** + * 类型转换器 + * + * @author roomroot + */ +public class Convert +{ + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static String toStr(Object value, String defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof String) + { + return (String) value; + } + return value.toString(); + } + + /** + * 转换为字符串
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static String toStr(Object value) + { + return toStr(value, null); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Character toChar(Object value, Character defaultValue) + { + if (null == value) + { + return defaultValue; + } + if (value instanceof Character) + { + return (Character) value; + } + + final String valueStr = toStr(value, null); + return StringUtils.isEmpty(valueStr) ? defaultValue : valueStr.charAt(0); + } + + /** + * 转换为字符
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Character toChar(Object value) + { + return toChar(value, null); + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Byte toByte(Object value, Byte defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Byte) + { + return (Byte) value; + } + if (value instanceof Number) + { + return ((Number) value).byteValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Byte.parseByte(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为byte
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Byte toByte(Object value) + { + return toByte(value, null); + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Short toShort(Object value, Short defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Short) + { + return (Short) value; + } + if (value instanceof Number) + { + return ((Number) value).shortValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Short.parseShort(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Short
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Short toShort(Object value) + { + return toShort(value, null); + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Number toNumber(Object value, Number defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Number) + { + return (Number) value; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return NumberFormat.getInstance().parse(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Number
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Number toNumber(Object value) + { + return toNumber(value, null); + } + + /** + * 转换为int
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Integer toInt(Object value, Integer defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Integer) + { + return (Integer) value; + } + if (value instanceof Number) + { + return ((Number) value).intValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Integer.parseInt(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为int
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Integer toInt(Object value) + { + return toInt(value, null); + } + + /** + * 转换为Integer数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String str) + { + return toIntArray(",", str); + } + + /** + * 转换为Long数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String str) + { + return toLongArray(",", str); + } + + /** + * 转换为Integer数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static Integer[] toIntArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Integer[] {}; + } + String[] arr = str.split(split); + final Integer[] ints = new Integer[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Integer v = toInt(arr[i], 0); + ints[i] = v; + } + return ints; + } + + /** + * 转换为Long数组
+ * + * @param split 分隔符 + * @param str 被转换的值 + * @return 结果 + */ + public static Long[] toLongArray(String split, String str) + { + if (StringUtils.isEmpty(str)) + { + return new Long[] {}; + } + String[] arr = str.split(split); + final Long[] longs = new Long[arr.length]; + for (int i = 0; i < arr.length; i++) + { + final Long v = toLong(arr[i], null); + longs[i] = v; + } + return longs; + } + + /** + * 转换为String数组
+ * + * @param str 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String str) + { + if (StringUtils.isEmpty(str)) + { + return new String[] {}; + } + return toStrArray(",", str); + } + + /** + * 转换为String数组
+ * + * @param split 分隔符 + * @param split 被转换的值 + * @return 结果 + */ + public static String[] toStrArray(String split, String str) + { + return str.split(split); + } + + /** + * 转换为long
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Long toLong(Object value, Long defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Long) + { + return (Long) value; + } + if (value instanceof Number) + { + return ((Number) value).longValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).longValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为long
+ * 如果给定的值为null,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Long toLong(Object value) + { + return toLong(value, null); + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Double toDouble(Object value, Double defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Double) + { + return (Double) value; + } + if (value instanceof Number) + { + return ((Number) value).doubleValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + // 支持科学计数法 + return new BigDecimal(valueStr.trim()).doubleValue(); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为double
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Double toDouble(Object value) + { + return toDouble(value, null); + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Float toFloat(Object value, Float defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Float) + { + return (Float) value; + } + if (value instanceof Number) + { + return ((Number) value).floatValue(); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Float.parseFloat(valueStr.trim()); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Float
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Float toFloat(Object value) + { + return toFloat(value, null); + } + + /** + * 转换为boolean
+ * String支持的值为:true、false、yes、ok、no、1、0、是、否, 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static Boolean toBool(Object value, Boolean defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof Boolean) + { + return (Boolean) value; + } + String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + valueStr = valueStr.trim().toLowerCase(); + switch (valueStr) + { + case "true": + case "yes": + case "ok": + case "1": + case "是": + return true; + case "false": + case "no": + case "0": + case "否": + return false; + default: + return defaultValue; + } + } + + /** + * 转换为boolean
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static Boolean toBool(Object value) + { + return toBool(value, null); + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * + * @param clazz Enum的Class + * @param value 值 + * @param defaultValue 默认值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value, E defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (clazz.isAssignableFrom(value.getClass())) + { + @SuppressWarnings("unchecked") + E myE = (E) value; + return myE; + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return Enum.valueOf(clazz, valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为Enum对象
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * + * @param clazz Enum的Class + * @param value 值 + * @return Enum + */ + public static > E toEnum(Class clazz, Object value) + { + return toEnum(clazz, value, null); + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value, BigInteger defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigInteger) + { + return (BigInteger) value; + } + if (value instanceof Long) + { + return BigInteger.valueOf((Long) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigInteger(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigInteger
+ * 如果给定的值为空,或者转换失败,返回默认值null
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigInteger toBigInteger(Object value) + { + return toBigInteger(value, null); + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @param defaultValue 转换错误时的默认值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value, BigDecimal defaultValue) + { + if (value == null) + { + return defaultValue; + } + if (value instanceof BigDecimal) + { + return (BigDecimal) value; + } + if (value instanceof Long) + { + return new BigDecimal((Long) value); + } + if (value instanceof Double) + { + return BigDecimal.valueOf((Double) value); + } + if (value instanceof Integer) + { + return new BigDecimal((Integer) value); + } + final String valueStr = toStr(value, null); + if (StringUtils.isEmpty(valueStr)) + { + return defaultValue; + } + try + { + return new BigDecimal(valueStr); + } + catch (Exception e) + { + return defaultValue; + } + } + + /** + * 转换为BigDecimal
+ * 如果给定的值为空,或者转换失败,返回默认值
+ * 转换失败不会报错 + * + * @param value 被转换的值 + * @return 结果 + */ + public static BigDecimal toBigDecimal(Object value) + { + return toBigDecimal(value, null); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @return 字符串 + */ + public static String utf8Str(Object obj) + { + return str(obj, CharsetKit.CHARSET_UTF_8); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charsetName 字符集 + * @return 字符串 + */ + public static String str(Object obj, String charsetName) + { + return str(obj, Charset.forName(charsetName)); + } + + /** + * 将对象转为字符串
+ * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法 + * + * @param obj 对象 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(Object obj, Charset charset) + { + if (null == obj) + { + return null; + } + + if (obj instanceof String) + { + return (String) obj; + } + else if (obj instanceof byte[] || obj instanceof Byte[]) + { + if (obj instanceof byte[]) + { + return str((byte[]) obj, charset); + } + else + { + Byte[] bytes = (Byte[]) obj; + int length = bytes.length; + byte[] dest = new byte[length]; + for (int i = 0; i < length; i++) + { + dest[i] = bytes[i]; + } + return str(dest, charset); + } + } + else if (obj instanceof ByteBuffer) + { + return str((ByteBuffer) obj, charset); + } + return obj.toString(); + } + + /** + * 将byte数组转为字符串 + * + * @param bytes byte数组 + * @param charset 字符集 + * @return 字符串 + */ + public static String str(byte[] bytes, String charset) + { + return str(bytes, StringUtils.isEmpty(charset) ? Charset.defaultCharset() : Charset.forName(charset)); + } + + /** + * 解码字节码 + * + * @param data 字符串 + * @param charset 字符集,如果此字段为空,则解码的结果取决于平台 + * @return 解码后的字符串 + */ + public static String str(byte[] data, Charset charset) + { + if (data == null) + { + return null; + } + + if (null == charset) + { + return new String(data); + } + return new String(data, charset); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, String charset) + { + if (data == null) + { + return null; + } + + return str(data, Charset.forName(charset)); + } + + /** + * 将编码的byteBuffer数据转换为字符串 + * + * @param data 数据 + * @param charset 字符集,如果为空使用当前系统字符集 + * @return 字符串 + */ + public static String str(ByteBuffer data, Charset charset) + { + if (null == charset) + { + charset = Charset.defaultCharset(); + } + return charset.decode(data).toString(); + } + + // ----------------------------------------------------------------------- 全角半角转换 + /** + * 半角转全角 + * + * @param input String. + * @return 全角字符串. + */ + public static String toSBC(String input) + { + return toSBC(input, null); + } + + /** + * 半角转全角 + * + * @param input String + * @param notConvertSet 不替换的字符集合 + * @return 全角字符串. + */ + public static String toSBC(String input, Set notConvertSet) + { + char[] c = input.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == ' ') + { + c[i] = '\u3000'; + } + else if (c[i] < '\177') + { + c[i] = (char) (c[i] + 65248); + + } + } + return new String(c); + } + + /** + * 全角转半角 + * + * @param input String. + * @return 半角字符串 + */ + public static String toDBC(String input) + { + return toDBC(input, null); + } + + /** + * 替换全角为半角 + * + * @param text 文本 + * @param notConvertSet 不替换的字符集合 + * @return 替换后的字符 + */ + public static String toDBC(String text, Set notConvertSet) + { + char[] c = text.toCharArray(); + for (int i = 0; i < c.length; i++) + { + if (null != notConvertSet && notConvertSet.contains(c[i])) + { + // 跳过不替换的字符 + continue; + } + + if (c[i] == '\u3000') + { + c[i] = ' '; + } + else if (c[i] > '\uFF00' && c[i] < '\uFF5F') + { + c[i] = (char) (c[i] - 65248); + } + } + return new String(c); + } + + /** + * 数字金额大写转换 先写个完整的然后将如零拾替换成零 + * + * @param n 数字 + * @return 中文大写数字 + */ + public static String digitUppercase(double n) + { + String[] fraction = { "角", "分" }; + String[] digit = { "零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖" }; + String[][] unit = { { "元", "万", "亿" }, { "", "拾", "佰", "仟" } }; + + String head = n < 0 ? "负" : ""; + n = Math.abs(n); + + String s = ""; + for (int i = 0; i < fraction.length; i++) + { + // 优化double计算精度丢失问题 + BigDecimal nNum = new BigDecimal(n); + BigDecimal decimal = new BigDecimal(10); + BigDecimal scale = nNum.multiply(decimal).setScale(2, RoundingMode.HALF_EVEN); + double d = scale.doubleValue(); + s += (digit[(int) (Math.floor(d * Math.pow(10, i)) % 10)] + fraction[i]).replaceAll("(零.)+", ""); + } + if (s.length() < 1) + { + s = "整"; + } + int integerPart = (int) Math.floor(n); + + for (int i = 0; i < unit[0].length && integerPart > 0; i++) + { + String p = ""; + for (int j = 0; j < unit[1].length && n > 0; j++) + { + p = digit[integerPart % 10] + unit[1][j] + p; + integerPart = integerPart / 10; + } + s = p.replaceAll("(零.)*零$", "").replaceAll("^$", "零") + unit[0][i] + s; + } + return head + s.replaceAll("(零.)*零元", "元").replaceFirst("(零.)+", "").replaceAll("(零.)+", "零").replaceAll("^整$", "零元整"); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/core/text/StrFormatter.java b/backend/roomroot-common/src/main/java/com/roomroot/common/core/text/StrFormatter.java new file mode 100644 index 000000000..3eb576b38 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/core/text/StrFormatter.java @@ -0,0 +1,92 @@ +package com.roomroot.common.core.text; + +import com.roomroot.common.utils.StringUtils; + +/** + * 字符串格式化 + * + * @author roomroot + */ +public class StrFormatter +{ + public static final String EMPTY_JSON = "{}"; + public static final char C_BACKSLASH = '\\'; + public static final char C_DELIM_START = '{'; + public static final char C_DELIM_END = '}'; + + /** + * 格式化字符串
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param strPattern 字符串模板 + * @param argArray 参数列表 + * @return 结果 + */ + public static String format(final String strPattern, final Object... argArray) + { + if (StringUtils.isEmpty(strPattern) || StringUtils.isEmpty(argArray)) + { + return strPattern; + } + final int strPatternLength = strPattern.length(); + + // 初始化定义好的长度以获得更好的性能 + StringBuilder sbuf = new StringBuilder(strPatternLength + 50); + + int handledPosition = 0; + int delimIndex;// 占位符所在位置 + for (int argIndex = 0; argIndex < argArray.length; argIndex++) + { + delimIndex = strPattern.indexOf(EMPTY_JSON, handledPosition); + if (delimIndex == -1) + { + if (handledPosition == 0) + { + return strPattern; + } + else + { // 字符串模板剩余部分不再包含占位符,加入剩余部分后返回结果 + sbuf.append(strPattern, handledPosition, strPatternLength); + return sbuf.toString(); + } + } + else + { + if (delimIndex > 0 && strPattern.charAt(delimIndex - 1) == C_BACKSLASH) + { + if (delimIndex > 1 && strPattern.charAt(delimIndex - 2) == C_BACKSLASH) + { + // 转义符之前还有一个转义符,占位符依旧有效 + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + else + { + // 占位符被转义 + argIndex--; + sbuf.append(strPattern, handledPosition, delimIndex - 1); + sbuf.append(C_DELIM_START); + handledPosition = delimIndex + 1; + } + } + else + { + // 正常占位符 + sbuf.append(strPattern, handledPosition, delimIndex); + sbuf.append(Convert.utf8Str(argArray[argIndex])); + handledPosition = delimIndex + 2; + } + } + } + // 加入最后一个占位符后所有的字符 + sbuf.append(strPattern, handledPosition, strPattern.length()); + + return sbuf.toString(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/enums/BusinessStatus.java b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/BusinessStatus.java new file mode 100644 index 000000000..ae746d8c7 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/BusinessStatus.java @@ -0,0 +1,20 @@ +package com.roomroot.common.enums; + +/** + * 操作状态 + * + * @author roomroot + * + */ +public enum BusinessStatus +{ + /** + * 成功 + */ + SUCCESS, + + /** + * 失败 + */ + FAIL, +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/enums/BusinessType.java b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/BusinessType.java new file mode 100644 index 000000000..7ef22b875 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/BusinessType.java @@ -0,0 +1,59 @@ +package com.roomroot.common.enums; + +/** + * 业务操作类型 + * + * @author roomroot + */ +public enum BusinessType +{ + /** + * 其它 + */ + OTHER, + + /** + * 新增 + */ + INSERT, + + /** + * 修改 + */ + UPDATE, + + /** + * 删除 + */ + DELETE, + + /** + * 授权 + */ + GRANT, + + /** + * 导出 + */ + EXPORT, + + /** + * 导入 + */ + IMPORT, + + /** + * 强退 + */ + FORCE, + + /** + * 生成代码 + */ + GENCODE, + + /** + * 清空数据 + */ + CLEAN, +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/enums/DataSourceType.java b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/DataSourceType.java new file mode 100644 index 000000000..230c90be5 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/DataSourceType.java @@ -0,0 +1,19 @@ +package com.roomroot.common.enums; + +/** + * 数据源 + * + * @author roomroot + */ +public enum DataSourceType +{ + /** + * 主库 + */ + MASTER, + + /** + * 从库 + */ + SLAVE +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/enums/DesensitizedType.java b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/DesensitizedType.java new file mode 100644 index 000000000..bc4a19bf2 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/DesensitizedType.java @@ -0,0 +1,59 @@ +package com.roomroot.common.enums; + +import java.util.function.Function; +import com.roomroot.common.utils.DesensitizedUtil; + +/** + * 脱敏类型 + * + * @author roomroot + */ +public enum DesensitizedType +{ + /** + * 姓名,第2位星号替换 + */ + USERNAME(s -> s.replaceAll("(\\S)\\S(\\S*)", "$1*$2")), + + /** + * 密码,全部字符都用*代替 + */ + PASSWORD(DesensitizedUtil::password), + + /** + * 身份证,中间10位星号替换 + */ + ID_CARD(s -> s.replaceAll("(\\d{4})\\d{10}(\\d{3}[Xx]|\\d{4})", "$1** **** ****$2")), + + /** + * 手机号,中间4位星号替换 + */ + PHONE(s -> s.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2")), + + /** + * 电子邮箱,仅显示第一个字母和@后面的地址显示,其他星号替换 + */ + EMAIL(s -> s.replaceAll("(^.)[^@]*(@.*$)", "$1****$2")), + + /** + * 银行卡号,保留最后4位,其他星号替换 + */ + BANK_CARD(s -> s.replaceAll("\\d{15}(\\d{3})", "**** **** **** **** $1")), + + /** + * 车牌号码,包含普通车辆、新能源车辆 + */ + CAR_LICENSE(DesensitizedUtil::carLicense); + + private final Function desensitizer; + + DesensitizedType(Function desensitizer) + { + this.desensitizer = desensitizer; + } + + public Function desensitizer() + { + return desensitizer; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/enums/HttpMethod.java b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/HttpMethod.java new file mode 100644 index 000000000..b963fc895 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/HttpMethod.java @@ -0,0 +1,36 @@ +package com.roomroot.common.enums; + +import java.util.HashMap; +import java.util.Map; +import org.jspecify.annotations.Nullable; + +/** + * 请求方式 + * + * @author roomroot + */ +public enum HttpMethod +{ + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE; + + private static final Map mappings = new HashMap<>(16); + + static + { + for (HttpMethod httpMethod : values()) + { + mappings.put(httpMethod.name(), httpMethod); + } + } + + @Nullable + public static HttpMethod resolve(@Nullable String method) + { + return (method != null ? mappings.get(method) : null); + } + + public boolean matches(String method) + { + return (this == resolve(method)); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/enums/LimitType.java b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/LimitType.java new file mode 100644 index 000000000..507f5a453 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/LimitType.java @@ -0,0 +1,20 @@ +package com.roomroot.common.enums; + +/** + * 限流类型 + * + * @author roomroot + */ + +public enum LimitType +{ + /** + * 默认策略全局限流 + */ + DEFAULT, + + /** + * 根据请求者IP进行限流 + */ + IP +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/enums/OperatorType.java b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/OperatorType.java new file mode 100644 index 000000000..12b0f73b7 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/OperatorType.java @@ -0,0 +1,24 @@ +package com.roomroot.common.enums; + +/** + * 操作人类别 + * + * @author roomroot + */ +public enum OperatorType +{ + /** + * 其它 + */ + OTHER, + + /** + * 后台用户 + */ + MANAGE, + + /** + * 手机端用户 + */ + MOBILE +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/enums/UserStatus.java b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/UserStatus.java new file mode 100644 index 000000000..bf2aee42b --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/enums/UserStatus.java @@ -0,0 +1,30 @@ +package com.roomroot.common.enums; + +/** + * 用户状态 + * + * @author roomroot + */ +public enum UserStatus +{ + OK("0", "正常"), DISABLE("1", "停用"), DELETED("2", "删除"); + + private final String code; + private final String info; + + UserStatus(String code, String info) + { + this.code = code; + this.info = info; + } + + public String getCode() + { + return code; + } + + public String getInfo() + { + return info; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/DemoModeException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/DemoModeException.java new file mode 100644 index 000000000..f79fd897e --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/DemoModeException.java @@ -0,0 +1,15 @@ +package com.roomroot.common.exception; + +/** + * 演示模式异常 + * + * @author roomroot + */ +public class DemoModeException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + public DemoModeException() + { + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/GlobalException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/GlobalException.java new file mode 100644 index 000000000..7d8651671 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/GlobalException.java @@ -0,0 +1,58 @@ +package com.roomroot.common.exception; + +/** + * 全局异常 + * + * @author roomroot + */ +public class GlobalException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public GlobalException() + { + } + + public GlobalException(String message) + { + this.message = message; + } + + public String getDetailMessage() + { + return detailMessage; + } + + public GlobalException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } + + @Override + public String getMessage() + { + return message; + } + + public GlobalException setMessage(String message) + { + this.message = message; + return this; + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/ServiceException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/ServiceException.java new file mode 100644 index 000000000..003d871b8 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/ServiceException.java @@ -0,0 +1,74 @@ +package com.roomroot.common.exception; + +/** + * 业务异常 + * + * @author roomroot + */ +public final class ServiceException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 错误码 + */ + private Integer code; + + /** + * 错误提示 + */ + private String message; + + /** + * 错误明细,内部调试错误 + * + * 和 {@link CommonResult#getDetailMessage()} 一致的设计 + */ + private String detailMessage; + + /** + * 空构造方法,避免反序列化问题 + */ + public ServiceException() + { + } + + public ServiceException(String message) + { + this.message = message; + } + + public ServiceException(String message, Integer code) + { + this.message = message; + this.code = code; + } + + public String getDetailMessage() + { + return detailMessage; + } + + @Override + public String getMessage() + { + return message; + } + + public Integer getCode() + { + return code; + } + + public ServiceException setMessage(String message) + { + this.message = message; + return this; + } + + public ServiceException setDetailMessage(String detailMessage) + { + this.detailMessage = detailMessage; + return this; + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/UtilException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/UtilException.java new file mode 100644 index 000000000..f24c35234 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/UtilException.java @@ -0,0 +1,26 @@ +package com.roomroot.common.exception; + +/** + * 工具类异常 + * + * @author roomroot + */ +public class UtilException extends RuntimeException +{ + private static final long serialVersionUID = 8247610319171014183L; + + public UtilException(Throwable e) + { + super(e.getMessage(), e); + } + + public UtilException(String message) + { + super(message); + } + + public UtilException(String message, Throwable throwable) + { + super(message, throwable); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/base/BaseException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/base/BaseException.java new file mode 100644 index 000000000..828f1dce5 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/base/BaseException.java @@ -0,0 +1,97 @@ +package com.roomroot.common.exception.base; + +import com.roomroot.common.utils.MessageUtils; +import com.roomroot.common.utils.StringUtils; + +/** + * 基础异常 + * + * @author roomroot + */ +public class BaseException extends RuntimeException +{ + private static final long serialVersionUID = 1L; + + /** + * 所属模块 + */ + private String module; + + /** + * 错误码 + */ + private String code; + + /** + * 错误码对应的参数 + */ + private Object[] args; + + /** + * 错误消息 + */ + private String defaultMessage; + + public BaseException(String module, String code, Object[] args, String defaultMessage) + { + this.module = module; + this.code = code; + this.args = args; + this.defaultMessage = defaultMessage; + } + + public BaseException(String module, String code, Object[] args) + { + this(module, code, args, null); + } + + public BaseException(String module, String defaultMessage) + { + this(module, null, null, defaultMessage); + } + + public BaseException(String code, Object[] args) + { + this(null, code, args, null); + } + + public BaseException(String defaultMessage) + { + this(null, null, null, defaultMessage); + } + + @Override + public String getMessage() + { + String message = null; + if (!StringUtils.isEmpty(code)) + { + message = MessageUtils.message(code, args); + } + if (message == null) + { + message = defaultMessage; + } + return message; + } + + public String getModule() + { + return module; + } + + public String getCode() + { + return code; + } + + public Object[] getArgs() + { + return args; + } + + public String getDefaultMessage() + { + return defaultMessage; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileException.java new file mode 100644 index 000000000..c6a0e7de2 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileException.java @@ -0,0 +1,19 @@ +package com.roomroot.common.exception.file; + +import com.roomroot.common.exception.base.BaseException; + +/** + * 文件信息异常类 + * + * @author roomroot + */ +public class FileException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public FileException(String code, Object[] args) + { + super("file", code, args, null); + } + +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileNameLengthLimitExceededException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileNameLengthLimitExceededException.java new file mode 100644 index 000000000..aa07c1f9e --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileNameLengthLimitExceededException.java @@ -0,0 +1,16 @@ +package com.roomroot.common.exception.file; + +/** + * 文件名称超长限制异常类 + * + * @author roomroot + */ +public class FileNameLengthLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileNameLengthLimitExceededException(int defaultFileNameLength) + { + super("upload.filename.exceed.length", new Object[] { defaultFileNameLength }); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileSizeLimitExceededException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileSizeLimitExceededException.java new file mode 100644 index 000000000..9dbf84b19 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileSizeLimitExceededException.java @@ -0,0 +1,16 @@ +package com.roomroot.common.exception.file; + +/** + * 文件名大小限制异常类 + * + * @author roomroot + */ +public class FileSizeLimitExceededException extends FileException +{ + private static final long serialVersionUID = 1L; + + public FileSizeLimitExceededException(long defaultMaxSize) + { + super("upload.exceed.maxSize", new Object[] { defaultMaxSize }); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileUploadException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileUploadException.java new file mode 100644 index 000000000..8030cf732 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/FileUploadException.java @@ -0,0 +1,61 @@ +package com.roomroot.common.exception.file; + +import java.io.PrintStream; +import java.io.PrintWriter; + +/** + * 文件上传异常类 + * + * @author roomroot + */ +public class FileUploadException extends Exception +{ + + private static final long serialVersionUID = 1L; + + private final Throwable cause; + + public FileUploadException() + { + this(null, null); + } + + public FileUploadException(final String msg) + { + this(msg, null); + } + + public FileUploadException(String msg, Throwable cause) + { + super(msg); + this.cause = cause; + } + + @Override + public void printStackTrace(PrintStream stream) + { + super.printStackTrace(stream); + if (cause != null) + { + stream.println("Caused by:"); + cause.printStackTrace(stream); + } + } + + @Override + public void printStackTrace(PrintWriter writer) + { + super.printStackTrace(writer); + if (cause != null) + { + writer.println("Caused by:"); + cause.printStackTrace(writer); + } + } + + @Override + public Throwable getCause() + { + return cause; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/InvalidExtensionException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/InvalidExtensionException.java new file mode 100644 index 000000000..351034e2d --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/file/InvalidExtensionException.java @@ -0,0 +1,80 @@ +package com.roomroot.common.exception.file; + +import java.util.Arrays; + +/** + * 文件上传无效扩展名异常类 + * + * @author roomroot + */ +public class InvalidExtensionException extends FileUploadException +{ + private static final long serialVersionUID = 1L; + + private String[] allowedExtension; + private String extension; + private String filename; + + public InvalidExtensionException(String[] allowedExtension, String extension, String filename) + { + super("文件[" + filename + "]后缀[" + extension + "]不正确,请上传" + Arrays.toString(allowedExtension) + "格式"); + this.allowedExtension = allowedExtension; + this.extension = extension; + this.filename = filename; + } + + public String[] getAllowedExtension() + { + return allowedExtension; + } + + public String getExtension() + { + return extension; + } + + public String getFilename() + { + return filename; + } + + public static class InvalidImageExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidImageExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidFlashExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidFlashExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidMediaExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidMediaExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } + + public static class InvalidVideoExtensionException extends InvalidExtensionException + { + private static final long serialVersionUID = 1L; + + public InvalidVideoExtensionException(String[] allowedExtension, String extension, String filename) + { + super(allowedExtension, extension, filename); + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/job/TaskException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/job/TaskException.java new file mode 100644 index 000000000..6b61ce2d2 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/job/TaskException.java @@ -0,0 +1,34 @@ +package com.roomroot.common.exception.job; + +/** + * 计划策略异常 + * + * @author roomroot + */ +public class TaskException extends Exception +{ + private static final long serialVersionUID = 1L; + + private Code code; + + public TaskException(String msg, Code code) + { + this(msg, code, null); + } + + public TaskException(String msg, Code code, Exception nestedEx) + { + super(msg, nestedEx); + this.code = code; + } + + public Code getCode() + { + return code; + } + + public enum Code + { + TASK_EXISTS, NO_TASK_EXISTS, TASK_ALREADY_STARTED, UNKNOWN, CONFIG_ERROR, TASK_NODE_NOT_AVAILABLE + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/BlackListException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/BlackListException.java new file mode 100644 index 000000000..c3c4668fc --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/BlackListException.java @@ -0,0 +1,16 @@ +package com.roomroot.common.exception.user; + +/** + * 黑名单IP异常类 + * + * @author roomroot + */ +public class BlackListException extends UserException +{ + private static final long serialVersionUID = 1L; + + public BlackListException() + { + super("login.blocked", null); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/CaptchaException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/CaptchaException.java new file mode 100644 index 000000000..531965fb0 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/CaptchaException.java @@ -0,0 +1,16 @@ +package com.roomroot.common.exception.user; + +/** + * 验证码错误异常类 + * + * @author roomroot + */ +public class CaptchaException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaException() + { + super("user.jcaptcha.error", null); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/CaptchaExpireException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/CaptchaExpireException.java new file mode 100644 index 000000000..591629fe9 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/CaptchaExpireException.java @@ -0,0 +1,16 @@ +package com.roomroot.common.exception.user; + +/** + * 验证码失效异常类 + * + * @author roomroot + */ +public class CaptchaExpireException extends UserException +{ + private static final long serialVersionUID = 1L; + + public CaptchaExpireException() + { + super("user.jcaptcha.expire", null); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserException.java new file mode 100644 index 000000000..ce7ff5940 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserException.java @@ -0,0 +1,18 @@ +package com.roomroot.common.exception.user; + +import com.roomroot.common.exception.base.BaseException; + +/** + * 用户信息异常类 + * + * @author roomroot + */ +public class UserException extends BaseException +{ + private static final long serialVersionUID = 1L; + + public UserException(String code, Object[] args) + { + super("user", code, args, null); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserNotExistsException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserNotExistsException.java new file mode 100644 index 000000000..c2c6e0668 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserNotExistsException.java @@ -0,0 +1,16 @@ +package com.roomroot.common.exception.user; + +/** + * 用户不存在异常类 + * + * @author roomroot + */ +public class UserNotExistsException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserNotExistsException() + { + super("user.not.exists", null); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserPasswordNotMatchException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserPasswordNotMatchException.java new file mode 100644 index 000000000..ed29add83 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserPasswordNotMatchException.java @@ -0,0 +1,16 @@ +package com.roomroot.common.exception.user; + +/** + * 用户密码不正确或不符合规范异常类 + * + * @author roomroot + */ +public class UserPasswordNotMatchException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordNotMatchException() + { + super("user.password.not.match", null); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserPasswordRetryLimitExceedException.java b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserPasswordRetryLimitExceedException.java new file mode 100644 index 000000000..2fc1982c3 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/exception/user/UserPasswordRetryLimitExceedException.java @@ -0,0 +1,16 @@ +package com.roomroot.common.exception.user; + +/** + * 用户错误最大次数异常类 + * + * @author roomroot + */ +public class UserPasswordRetryLimitExceedException extends UserException +{ + private static final long serialVersionUID = 1L; + + public UserPasswordRetryLimitExceedException(int retryLimitCount, int lockTime) + { + super("user.password.retry.limit.exceed", new Object[] { retryLimitCount, lockTime }); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/filter/PropertyPreExcludeFilter.java b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/PropertyPreExcludeFilter.java new file mode 100644 index 000000000..cfcfe8bea --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/PropertyPreExcludeFilter.java @@ -0,0 +1,24 @@ +package com.roomroot.common.filter; + +import com.alibaba.fastjson2.filter.SimplePropertyPreFilter; + +/** + * 排除JSON敏感属性 + * + * @author roomroot + */ +public class PropertyPreExcludeFilter extends SimplePropertyPreFilter +{ + public PropertyPreExcludeFilter() + { + } + + public PropertyPreExcludeFilter addExcludes(String... filters) + { + for (int i = 0; i < filters.length; i++) + { + this.getExcludes().add(filters[i]); + } + return this; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/filter/RefererFilter.java b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/RefererFilter.java new file mode 100644 index 000000000..4999198f4 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/RefererFilter.java @@ -0,0 +1,77 @@ +package com.roomroot.common.filter; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +/** + * 防盗链过滤器 + * + * @author roomroot + */ +public class RefererFilter implements Filter +{ + /** + * 允许的域名列表 + */ + public List allowedDomains; + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String domains = filterConfig.getInitParameter("allowedDomains"); + this.allowedDomains = Arrays.asList(domains.split(",")); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + + String referer = req.getHeader("Referer"); + + // 如果Referer为空,拒绝访问 + if (referer == null || referer.isEmpty()) + { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied: Referer header is required"); + return; + } + + // 检查Referer是否在允许的域名列表中 + boolean allowed = false; + for (String domain : allowedDomains) + { + if (referer.contains(domain)) + { + allowed = true; + break; + } + } + + // 根据检查结果决定是否放行 + if (allowed) + { + chain.doFilter(request, response); + } + else + { + resp.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied: Referer '" + referer + "' is not allowed"); + } + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/filter/RepeatableFilter.java b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/RepeatableFilter.java new file mode 100644 index 000000000..3436eec77 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/RepeatableFilter.java @@ -0,0 +1,52 @@ +package com.roomroot.common.filter; + +import java.io.IOException; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.MediaType; +import com.roomroot.common.utils.StringUtils; + +/** + * Repeatable 过滤器 + * + * @author roomroot + */ +public class RepeatableFilter implements Filter +{ + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + ServletRequest requestWrapper = null; + if (request instanceof HttpServletRequest + && StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE)) + { + requestWrapper = new RepeatedlyRequestWrapper((HttpServletRequest) request, response); + } + if (null == requestWrapper) + { + chain.doFilter(request, response); + } + else + { + chain.doFilter(requestWrapper, response); + } + } + + @Override + public void destroy() + { + + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/filter/RepeatedlyRequestWrapper.java b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/RepeatedlyRequestWrapper.java new file mode 100644 index 000000000..ce2c6eb94 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/RepeatedlyRequestWrapper.java @@ -0,0 +1,76 @@ +package com.roomroot.common.filter; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import com.roomroot.common.utils.http.HttpHelper; +import com.roomroot.common.constant.Constants; + +/** + * 构建可重复读取inputStream的request + * + * @author roomroot + */ +public class RepeatedlyRequestWrapper extends HttpServletRequestWrapper +{ + private final byte[] body; + + public RepeatedlyRequestWrapper(HttpServletRequest request, ServletResponse response) throws IOException + { + super(request); + request.setCharacterEncoding(Constants.UTF8); + response.setCharacterEncoding(Constants.UTF8); + + body = HttpHelper.getBodyString(request).getBytes(Constants.UTF8); + } + + @Override + public BufferedReader getReader() throws IOException + { + return new BufferedReader(new InputStreamReader(getInputStream())); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + final ByteArrayInputStream bais = new ByteArrayInputStream(body); + return new ServletInputStream() + { + @Override + public int read() throws IOException + { + return bais.read(); + } + + @Override + public int available() throws IOException + { + return body.length; + } + + @Override + public boolean isFinished() + { + return false; + } + + @Override + public boolean isReady() + { + return false; + } + + @Override + public void setReadListener(ReadListener readListener) + { + + } + }; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/filter/XssFilter.java b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/XssFilter.java new file mode 100644 index 000000000..2554dfa92 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/XssFilter.java @@ -0,0 +1,75 @@ +package com.roomroot.common.filter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.enums.HttpMethod; + +/** + * 防止XSS攻击的过滤器 + * + * @author roomroot + */ +public class XssFilter implements Filter +{ + /** + * 排除链接 + */ + public List excludes = new ArrayList<>(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException + { + String tempExcludes = filterConfig.getInitParameter("excludes"); + if (StringUtils.isNotEmpty(tempExcludes)) + { + String[] urls = tempExcludes.split(","); + for (String url : urls) + { + excludes.add(url); + } + } + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + if (handleExcludeURL(req, resp)) + { + chain.doFilter(request, response); + return; + } + XssHttpServletRequestWrapper xssRequest = new XssHttpServletRequestWrapper((HttpServletRequest) request); + chain.doFilter(xssRequest, response); + } + + private boolean handleExcludeURL(HttpServletRequest request, HttpServletResponse response) + { + String url = request.getServletPath(); + String method = request.getMethod(); + // GET DELETE 不过滤 + if (method == null || HttpMethod.GET.matches(method) || HttpMethod.DELETE.matches(method)) + { + return true; + } + return StringUtils.matches(url, excludes); + } + + @Override + public void destroy() + { + + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/filter/XssHttpServletRequestWrapper.java b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/XssHttpServletRequestWrapper.java new file mode 100644 index 000000000..e86897dd1 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/filter/XssHttpServletRequestWrapper.java @@ -0,0 +1,111 @@ +package com.roomroot.common.filter; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequestWrapper; +import org.apache.commons.io.IOUtils; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.html.EscapeUtil; + +/** + * XSS过滤处理 + * + * @author roomroot + */ +public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper +{ + /** + * @param request + */ + public XssHttpServletRequestWrapper(HttpServletRequest request) + { + super(request); + } + + @Override + public String[] getParameterValues(String name) + { + String[] values = super.getParameterValues(name); + if (values != null) + { + int length = values.length; + String[] escapesValues = new String[length]; + for (int i = 0; i < length; i++) + { + // 防xss攻击和过滤前后空格 + escapesValues[i] = EscapeUtil.clean(values[i]).trim(); + } + return escapesValues; + } + return super.getParameterValues(name); + } + + @Override + public ServletInputStream getInputStream() throws IOException + { + // 非json类型,直接返回 + if (!isJsonRequest()) + { + return super.getInputStream(); + } + + // 为空,直接返回 + String json = IOUtils.toString(super.getInputStream(), "utf-8"); + if (StringUtils.isEmpty(json)) + { + return super.getInputStream(); + } + + // xss过滤 + json = EscapeUtil.clean(json).trim(); + byte[] jsonBytes = json.getBytes("utf-8"); + final ByteArrayInputStream bis = new ByteArrayInputStream(jsonBytes); + return new ServletInputStream() + { + @Override + public boolean isFinished() + { + return true; + } + + @Override + public boolean isReady() + { + return true; + } + + @Override + public int available() throws IOException + { + return jsonBytes.length; + } + + @Override + public void setReadListener(ReadListener readListener) + { + } + + @Override + public int read() throws IOException + { + return bis.read(); + } + }; + } + + /** + * 是否是Json请求 + * + * @param request + */ + public boolean isJsonRequest() + { + String header = super.getHeader(HttpHeaders.CONTENT_TYPE); + return StringUtils.startsWithIgnoreCase(header, MediaType.APPLICATION_JSON_VALUE); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/Arith.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/Arith.java new file mode 100644 index 000000000..a2c0f7ba6 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/Arith.java @@ -0,0 +1,113 @@ +package com.roomroot.common.utils; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 精确的浮点数运算 + * + * @author roomroot + */ +public class Arith +{ + + /** 默认除法运算精度 */ + private static final int DEF_DIV_SCALE = 10; + + /** 这个类不能实例化 */ + private Arith() + { + } + + /** + * 提供精确的加法运算。 + * @param v1 被加数 + * @param v2 加数 + * @return 两个参数的和 + */ + public static double add(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.add(b2).doubleValue(); + } + + /** + * 提供精确的减法运算。 + * @param v1 被减数 + * @param v2 减数 + * @return 两个参数的差 + */ + public static double sub(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.subtract(b2).doubleValue(); + } + + /** + * 提供精确的乘法运算。 + * @param v1 被乘数 + * @param v2 乘数 + * @return 两个参数的积 + */ + public static double mul(double v1, double v2) + { + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + return b1.multiply(b2).doubleValue(); + } + + /** + * 提供(相对)精确的除法运算,当发生除不尽的情况时,精确到 + * 小数点以后10位,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @return 两个参数的商 + */ + public static double div(double v1, double v2) + { + return div(v1, v2, DEF_DIV_SCALE); + } + + /** + * 提供(相对)精确的除法运算。当发生除不尽的情况时,由scale参数指 + * 定精度,以后的数字四舍五入。 + * @param v1 被除数 + * @param v2 除数 + * @param scale 表示表示需要精确到小数点以后几位。 + * @return 两个参数的商 + */ + public static double div(double v1, double v2, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b1 = new BigDecimal(Double.toString(v1)); + BigDecimal b2 = new BigDecimal(Double.toString(v2)); + if (b1.compareTo(BigDecimal.ZERO) == 0) + { + return BigDecimal.ZERO.doubleValue(); + } + return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 提供精确的小数位四舍五入处理。 + * @param v 需要四舍五入的数字 + * @param scale 小数点后保留几位 + * @return 四舍五入后的结果 + */ + public static double round(double v, int scale) + { + if (scale < 0) + { + throw new IllegalArgumentException( + "The scale must be a positive integer or zero"); + } + BigDecimal b = new BigDecimal(Double.toString(v)); + return b.divide(BigDecimal.ONE, scale, RoundingMode.HALF_UP).doubleValue(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DateUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DateUtils.java new file mode 100644 index 000000000..15f7c1ce5 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DateUtils.java @@ -0,0 +1,192 @@ +package com.roomroot.common.utils; + +import java.lang.management.ManagementFactory; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.Date; +import org.apache.commons.lang3.time.DateFormatUtils; + +/** + * 时间工具类 + * + * @author roomroot + */ +@SuppressWarnings("deprecation") +public class DateUtils extends org.apache.commons.lang3.time.DateUtils +{ + public static String YYYY = "yyyy"; + + public static String YYYY_MM = "yyyy-MM"; + + public static String YYYY_MM_DD = "yyyy-MM-dd"; + + public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss"; + + public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + + private static String[] parsePatterns = { + "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM", + "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM", + "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"}; + + /** + * 获取当前Date型日期 + * + * @return Date() 当前日期 + */ + public static Date getNowDate() + { + return new Date(); + } + + /** + * 获取当前日期, 默认格式为yyyy-MM-dd + * + * @return String + */ + public static String getDate() + { + return dateTimeNow(YYYY_MM_DD); + } + + public static final String getTime() + { + return dateTimeNow(YYYY_MM_DD_HH_MM_SS); + } + + public static final String dateTimeNow() + { + return dateTimeNow(YYYYMMDDHHMMSS); + } + + public static final String dateTimeNow(final String format) + { + return parseDateToStr(format, new Date()); + } + + public static final String dateTime(final Date date) + { + return parseDateToStr(YYYY_MM_DD, date); + } + + public static final String parseDateToStr(final String format, final Date date) + { + return new SimpleDateFormat(format).format(date); + } + + public static final Date dateTime(final String format, final String ts) + { + try + { + return new SimpleDateFormat(format).parse(ts); + } + catch (ParseException e) + { + throw new RuntimeException(e); + } + } + + /** + * 日期路径 即年/月/日 如2018/08/08 + */ + public static final String datePath() + { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyy/MM/dd"); + } + + /** + * 日期路径 即年/月/日 如20180808 + */ + public static final String dateTime() + { + Date now = new Date(); + return DateFormatUtils.format(now, "yyyyMMdd"); + } + + /** + * 日期型字符串转化为日期 格式 + */ + public static Date parseDate(Object str) + { + if (str == null) + { + return null; + } + try + { + return parseDate(str.toString(), parsePatterns); + } + catch (ParseException e) + { + return null; + } + } + + /** + * 获取服务器启动时间 + */ + public static Date getServerStartDate() + { + long time = ManagementFactory.getRuntimeMXBean().getStartTime(); + return new Date(time); + } + + /** + * 计算相差天数 + */ + public static int differentDaysByMillisecond(Date date1, Date date2) + { + return Math.abs((int) ((date2.getTime() - date1.getTime()) / (1000 * 3600 * 24))); + } + + /** + * 计算时间差 + * + * @param endDate 最后时间 + * @param startTime 开始时间 + * @return 时间差(天/小时/分钟) + */ + public static String timeDistance(Date endDate, Date startTime) + { + long nd = 1000 * 24 * 60 * 60; + long nh = 1000 * 60 * 60; + long nm = 1000 * 60; + // long ns = 1000; + // 获得两个时间的毫秒时间差异 + long diff = endDate.getTime() - startTime.getTime(); + // 计算差多少天 + long day = diff / nd; + // 计算差多少小时 + long hour = diff % nd / nh; + // 计算差多少分钟 + long min = diff % nd % nh / nm; + // 计算差多少秒//输出结果 + // long sec = diff % nd % nh % nm / ns; + return day + "天" + hour + "小时" + min + "分钟"; + } + + /** + * 增加 LocalDateTime ==> Date + */ + public static Date toDate(LocalDateTime temporalAccessor) + { + ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } + + /** + * 增加 LocalDate ==> Date + */ + public static Date toDate(LocalDate temporalAccessor) + { + LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0)); + ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault()); + return Date.from(zdt.toInstant()); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DbTypeUtil.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DbTypeUtil.java new file mode 100644 index 000000000..8839bc40a --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DbTypeUtil.java @@ -0,0 +1,25 @@ +package com.roomroot.common.utils; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.SQLException; + +@Component +public class DbTypeUtil { + @Autowired + private DataSource dataSource; + + public String getDbType() { + try (Connection conn = dataSource.getConnection()) { + String url = conn.getMetaData().getURL(); + if (url.contains(":dm:")) return "dm"; + if (url.contains(":mysql:")) return "mysql"; + return "mysql"; + } catch (SQLException e) { + return "mysql"; + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DesensitizedUtil.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DesensitizedUtil.java new file mode 100644 index 000000000..4c8315951 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DesensitizedUtil.java @@ -0,0 +1,49 @@ +package com.roomroot.common.utils; + +/** + * 脱敏工具类 + * + * @author roomroot + */ +public class DesensitizedUtil +{ + /** + * 密码的全部字符都用*代替,比如:****** + * + * @param password 密码 + * @return 脱敏后的密码 + */ + public static String password(String password) + { + if (StringUtils.isBlank(password)) + { + return StringUtils.EMPTY; + } + return StringUtils.repeat('*', password.length()); + } + + /** + * 车牌中间用*代替,如果是错误的车牌,不处理 + * + * @param carLicense 完整的车牌号 + * @return 脱敏后的车牌 + */ + public static String carLicense(String carLicense) + { + if (StringUtils.isBlank(carLicense)) + { + return StringUtils.EMPTY; + } + // 普通车牌 + if (carLicense.length() == 7) + { + carLicense = StringUtils.hide(carLicense, 3, 6); + } + else if (carLicense.length() == 8) + { + // 新能源车牌 + carLicense = StringUtils.hide(carLicense, 3, 7); + } + return carLicense; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DictUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DictUtils.java new file mode 100644 index 000000000..f622766a7 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/DictUtils.java @@ -0,0 +1,217 @@ +package com.roomroot.common.utils; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import com.alibaba.fastjson2.JSONArray; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.core.domain.entity.SysDictData; +import com.roomroot.common.core.redis.RedisCache; +import com.roomroot.common.utils.spring.SpringUtils; + +/** + * 字典工具类 + * + * @author roomroot + */ +public class DictUtils +{ + /** + * 分隔符 + */ + public static final String SEPARATOR = ","; + + /** + * 设置字典缓存 + * + * @param key 参数键 + * @param dictDatas 字典数据列表 + */ + public static void setDictCache(String key, List dictDatas) + { + SpringUtils.getBean(RedisCache.class).setCacheObject(getCacheKey(key), dictDatas); + } + + /** + * 获取字典缓存 + * + * @param key 参数键 + * @return dictDatas 字典数据列表 + */ + public static List getDictCache(String key) + { + JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key)); + if (StringUtils.isNotNull(arrayCache)) + { + return arrayCache.toList(SysDictData.class); + } + return null; + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue) + { + if (StringUtils.isEmpty(dictValue)) + { + return StringUtils.EMPTY; + } + return getDictLabel(dictType, dictValue, SEPARATOR); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel) + { + if (StringUtils.isEmpty(dictLabel)) + { + return StringUtils.EMPTY; + } + return getDictValue(dictType, dictLabel, SEPARATOR); + } + + /** + * 根据字典类型和字典值获取字典标签 + * + * @param dictType 字典类型 + * @param dictValue 字典值 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String getDictLabel(String dictType, String dictValue, String separator) + { + List datas = getDictCache(dictType); + if (StringUtils.isNull(datas) || StringUtils.isEmpty(dictValue)) + { + return StringUtils.EMPTY; + } + Map dictMap = datas.stream().collect(HashMap::new, (map, dict) -> map.put(dict.getDictValue(), dict.getDictLabel()), Map::putAll); + if (!StringUtils.contains(dictValue, separator)) + { + return dictMap.getOrDefault(dictValue, StringUtils.EMPTY); + } + StringBuilder labelBuilder = new StringBuilder(); + for (String seperatedValue : dictValue.split(separator)) + { + if (dictMap.containsKey(seperatedValue)) + { + labelBuilder.append(dictMap.get(seperatedValue)).append(separator); + } + } + return StringUtils.removeEnd(labelBuilder.toString(), separator); + } + + /** + * 根据字典类型和字典标签获取字典值 + * + * @param dictType 字典类型 + * @param dictLabel 字典标签 + * @param separator 分隔符 + * @return 字典值 + */ + public static String getDictValue(String dictType, String dictLabel, String separator) + { + List datas = getDictCache(dictType); + if (StringUtils.isNull(datas) || StringUtils.isEmpty(dictLabel)) + { + return StringUtils.EMPTY; + } + Map dictMap = datas.stream().collect(HashMap::new, (map, dict) -> map.put(dict.getDictLabel(), dict.getDictValue()), Map::putAll); + if (!StringUtils.contains(dictLabel, separator)) + { + return dictMap.getOrDefault(dictLabel, StringUtils.EMPTY); + } + StringBuilder valueBuilder = new StringBuilder(); + for (String seperatedValue : dictLabel.split(separator)) + { + if (dictMap.containsKey(seperatedValue)) + { + valueBuilder.append(dictMap.get(seperatedValue)).append(separator); + } + } + return StringUtils.removeEnd(valueBuilder.toString(), separator); + } + + /** + * 根据字典类型获取字典所有值 + * + * @param dictType 字典类型 + * @return 字典值 + */ + public static String getDictValues(String dictType) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.isNull(datas)) + { + return StringUtils.EMPTY; + } + for (SysDictData dict : datas) + { + propertyString.append(dict.getDictValue()).append(SEPARATOR); + } + return StringUtils.stripEnd(propertyString.toString(), SEPARATOR); + } + + /** + * 根据字典类型获取字典所有标签 + * + * @param dictType 字典类型 + * @return 字典值 + */ + public static String getDictLabels(String dictType) + { + StringBuilder propertyString = new StringBuilder(); + List datas = getDictCache(dictType); + if (StringUtils.isNull(datas)) + { + return StringUtils.EMPTY; + } + for (SysDictData dict : datas) + { + propertyString.append(dict.getDictLabel()).append(SEPARATOR); + } + return StringUtils.stripEnd(propertyString.toString(), SEPARATOR); + } + + /** + * 删除指定字典缓存 + * + * @param key 字典键 + */ + public static void removeDictCache(String key) + { + SpringUtils.getBean(RedisCache.class).deleteObject(getCacheKey(key)); + } + + /** + * 清空字典缓存 + */ + public static void clearDictCache() + { + Collection keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.SYS_DICT_KEY + "*"); + SpringUtils.getBean(RedisCache.class).deleteObject(keys); + } + + /** + * 设置cache key + * + * @param configKey 参数键 + * @return 缓存键key + */ + public static String getCacheKey(String configKey) + { + return CacheConstants.SYS_DICT_KEY + configKey; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ExceptionUtil.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ExceptionUtil.java new file mode 100644 index 000000000..a54f5cdda --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ExceptionUtil.java @@ -0,0 +1,39 @@ +package com.roomroot.common.utils; + +import java.io.PrintWriter; +import java.io.StringWriter; +import org.apache.commons.lang3.exception.ExceptionUtils; + +/** + * 错误信息处理类。 + * + * @author roomroot + */ +public class ExceptionUtil +{ + /** + * 获取exception的详细错误信息。 + */ + public static String getExceptionMessage(Throwable e) + { + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw, true)); + return sw.toString(); + } + + public static String getRootErrorMessage(Exception e) + { + Throwable root = ExceptionUtils.getRootCause(e); + root = (root == null ? e : root); + if (root == null) + { + return ""; + } + String msg = root.getMessage(); + if (msg == null) + { + return "null"; + } + return StringUtils.defaultString(msg); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/LogUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/LogUtils.java new file mode 100644 index 000000000..5b0410f45 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/LogUtils.java @@ -0,0 +1,18 @@ +package com.roomroot.common.utils; + +/** + * 处理并记录日志文件 + * + * @author roomroot + */ +public class LogUtils +{ + public static String getBlock(Object msg) + { + if (msg == null) + { + msg = ""; + } + return "[" + msg.toString() + "]"; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/MessageUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/MessageUtils.java new file mode 100644 index 000000000..cfe943701 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/MessageUtils.java @@ -0,0 +1,26 @@ +package com.roomroot.common.utils; + +import org.springframework.context.MessageSource; +import org.springframework.context.i18n.LocaleContextHolder; +import com.roomroot.common.utils.spring.SpringUtils; + +/** + * 获取i18n资源文件 + * + * @author roomroot + */ +public class MessageUtils +{ + /** + * 根据消息键和参数 获取消息 委托给spring messageSource + * + * @param code 消息键 + * @param args 参数 + * @return 获取国际化翻译值 + */ + public static String message(String code, Object... args) + { + MessageSource messageSource = SpringUtils.getBean(MessageSource.class); + return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/PageUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/PageUtils.java new file mode 100644 index 000000000..cfac15885 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/PageUtils.java @@ -0,0 +1,60 @@ +package com.roomroot.common.utils; + +import com.baomidou.mybatisplus.core.metadata.OrderItem; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.common.core.page.PageDomain; +import com.roomroot.common.core.page.TableSupport; +import com.roomroot.common.utils.sql.SqlUtil; + +/** + * 分页工具类 + * + * @author roomroot + */ +public class PageUtils +{ + private static final ThreadLocal> PAGE_HOLDER = new ThreadLocal<>(); + + /** + * 设置请求分页数据 + */ + public static void startPage() + { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageNum = pageDomain.getPageNum(); + Integer pageSize = pageDomain.getPageSize(); + String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy()); + Boolean reasonable = pageDomain.getReasonable(); + Page page = new Page<>(pageNum, pageSize); + if (StringUtils.isNotEmpty(orderBy)) + { + String[] orderParts = orderBy.split("\\s+"); + if (orderParts.length > 0) + { + String column = orderParts[0]; + boolean isAsc = orderParts.length <= 1 || !"desc".equalsIgnoreCase(orderParts[1]); + OrderItem orderItem = new OrderItem(); + orderItem.setColumn(column); + orderItem.setAsc(isAsc); + page.addOrder(orderItem); + } + } + PAGE_HOLDER.set(page); + } + + /** + * 获取当前分页对象 + */ + public static Page getPage() + { + return PAGE_HOLDER.get(); + } + + /** + * 清理分页的线程变量 + */ + public static void clearPage() + { + PAGE_HOLDER.remove(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/SecurityUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/SecurityUtils.java new file mode 100644 index 000000000..d2d5287ea --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/SecurityUtils.java @@ -0,0 +1,188 @@ +package com.roomroot.common.utils; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.util.PatternMatchUtils; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.constant.HttpStatus; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.exception.ServiceException; + +/** + * 安全服务工具类 + * + * @author roomroot + */ +public class SecurityUtils +{ + + /** + * 用户ID + **/ + public static Long getUserId() + { + try + { + return getLoginUser().getUserId(); + } + catch (Exception e) + { + throw new ServiceException("获取用户ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取部门ID + **/ + public static Long getDeptId() + { + try + { + return getLoginUser().getDeptId(); + } + catch (Exception e) + { + throw new ServiceException("获取部门ID异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户账户 + **/ + public static String getUsername() + { + try + { + return getLoginUser().getUsername(); + } + catch (Exception e) + { + throw new ServiceException("获取用户账户异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取用户 + **/ + public static LoginUser getLoginUser() + { + try + { + return (LoginUser) getAuthentication().getPrincipal(); + } + catch (Exception e) + { + throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED); + } + } + + /** + * 获取Authentication + */ + public static Authentication getAuthentication() + { + return SecurityContextHolder.getContext().getAuthentication(); + } + + /** + * 生成BCryptPasswordEncoder密码 + * + * @param password 密码 + * @return 加密字符串 + */ + public static String encryptPassword(String password) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.encode(password); + } + + /** + * 判断密码是否相同 + * + * @param rawPassword 真实密码 + * @param encodedPassword 加密后字符 + * @return 结果 + */ + public static boolean matchesPassword(String rawPassword, String encodedPassword) + { + BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); + return passwordEncoder.matches(rawPassword, encodedPassword); + } + + /** + * 是否为管理员 + * + * @return 结果 + */ + public static boolean isAdmin() + { + return isAdmin(getUserId()); + } + + /** + * 是否为管理员 + * + * @param userId 用户ID + * @return 结果 + */ + public static boolean isAdmin(Long userId) + { + return userId != null && 1L == userId; + } + + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public static boolean hasPermi(String permission) + { + return hasPermi(getLoginUser().getPermissions(), permission); + } + + /** + * 判断是否包含权限 + * + * @param authorities 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public static boolean hasPermi(Collection authorities, String permission) + { + return authorities.stream().filter(StringUtils::hasText) + .anyMatch(x -> Constants.ALL_PERMISSION.equals(x) || PatternMatchUtils.simpleMatch(x, permission)); + } + + /** + * 验证用户是否拥有某个角色 + * + * @param role 角色标识 + * @return 用户是否具备某角色 + */ + public static boolean hasRole(String role) + { + List roleList = getLoginUser().getUser().getRoles(); + Collection roles = roleList.stream().map(SysRole::getRoleKey).collect(Collectors.toSet()); + return hasRole(roles, role); + } + + /** + * 判断是否包含角色 + * + * @param roles 角色列表 + * @param role 角色 + * @return 用户是否具备某角色权限 + */ + public static boolean hasRole(Collection roles, String role) + { + return roles.stream().filter(StringUtils::hasText) + .anyMatch(x -> Constants.SUPER_ADMIN.equals(x) || PatternMatchUtils.simpleMatch(x, role)); + } + +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ServletUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ServletUtils.java new file mode 100644 index 000000000..50e383eb7 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ServletUtils.java @@ -0,0 +1,218 @@ +package com.roomroot.common.utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.core.text.Convert; + +/** + * 客户端工具类 + * + * @author roomroot + */ +public class ServletUtils +{ + /** + * 获取String参数 + */ + public static String getParameter(String name) + { + return getRequest().getParameter(name); + } + + /** + * 获取String参数 + */ + public static String getParameter(String name, String defaultValue) + { + return Convert.toStr(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name) + { + return Convert.toInt(getRequest().getParameter(name)); + } + + /** + * 获取Integer参数 + */ + public static Integer getParameterToInt(String name, Integer defaultValue) + { + return Convert.toInt(getRequest().getParameter(name), defaultValue); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name) + { + return Convert.toBool(getRequest().getParameter(name)); + } + + /** + * 获取Boolean参数 + */ + public static Boolean getParameterToBool(String name, Boolean defaultValue) + { + return Convert.toBool(getRequest().getParameter(name), defaultValue); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParams(ServletRequest request) + { + final Map map = request.getParameterMap(); + return Collections.unmodifiableMap(map); + } + + /** + * 获得所有请求参数 + * + * @param request 请求对象{@link ServletRequest} + * @return Map + */ + public static Map getParamMap(ServletRequest request) + { + Map params = new HashMap<>(); + for (Map.Entry entry : getParams(request).entrySet()) + { + params.put(entry.getKey(), StringUtils.join(entry.getValue(), ",")); + } + return params; + } + + /** + * 获取request + */ + public static HttpServletRequest getRequest() + { + return getRequestAttributes().getRequest(); + } + + /** + * 获取response + */ + public static HttpServletResponse getResponse() + { + return getRequestAttributes().getResponse(); + } + + /** + * 获取session + */ + public static HttpSession getSession() + { + return getRequest().getSession(); + } + + public static ServletRequestAttributes getRequestAttributes() + { + RequestAttributes attributes = RequestContextHolder.getRequestAttributes(); + return (ServletRequestAttributes) attributes; + } + + /** + * 将字符串渲染到客户端 + * + * @param response 渲染对象 + * @param string 待渲染的字符串 + */ + public static void renderString(HttpServletResponse response, String string) + { + try + { + response.setStatus(200); + response.setContentType("application/json"); + response.setCharacterEncoding("utf-8"); + response.getWriter().print(string); + } + catch (IOException e) + { + e.printStackTrace(); + } + } + + /** + * 是否是Ajax异步请求 + * + * @param request + */ + public static boolean isAjaxRequest(HttpServletRequest request) + { + String accept = request.getHeader("accept"); + if (accept != null && accept.contains("application/json")) + { + return true; + } + + String xRequestedWith = request.getHeader("X-Requested-With"); + if (xRequestedWith != null && xRequestedWith.contains("XMLHttpRequest")) + { + return true; + } + + String uri = request.getRequestURI(); + if (StringUtils.inStringIgnoreCase(uri, ".json", ".xml")) + { + return true; + } + + String ajax = request.getParameter("__ajax"); + return StringUtils.inStringIgnoreCase(ajax, "json", "xml"); + } + + /** + * 内容编码 + * + * @param str 内容 + * @return 编码后的内容 + */ + public static String urlEncode(String str) + { + try + { + return URLEncoder.encode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } + + /** + * 内容解码 + * + * @param str 内容 + * @return 解码后的内容 + */ + public static String urlDecode(String str) + { + try + { + return URLDecoder.decode(str, Constants.UTF8); + } + catch (UnsupportedEncodingException e) + { + return StringUtils.EMPTY; + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/StringUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/StringUtils.java new file mode 100644 index 000000000..0bbd37b37 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/StringUtils.java @@ -0,0 +1,921 @@ +package com.roomroot.common.utils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.commons.lang3.Strings; +import org.springframework.util.AntPathMatcher; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.core.text.StrFormatter; + +/** + * 字符串工具类 + * + * @author roomroot + */ +@SuppressWarnings("deprecation") +public class StringUtils extends org.apache.commons.lang3.StringUtils +{ + /** 空字符串 */ + private static final String NULLSTR = ""; + + /** 下划线 */ + private static final char SEPARATOR = '_'; + + /** 星号 */ + private static final char ASTERISK = '*'; + + /** + * 获取参数不为空值 + * + * @param value defaultValue 要判断的value + * @return value 返回值 + */ + public static T nvl(T value, T defaultValue) + { + return value != null ? value : defaultValue; + } + + /** + * * 判断一个Collection是否为空, 包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Collection coll) + { + return isNull(coll) || coll.isEmpty(); + } + + /** + * * 判断一个Collection是否非空,包含List,Set,Queue + * + * @param coll 要判断的Collection + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Collection coll) + { + return !isEmpty(coll); + } + + /** + * * 判断一个对象数组是否为空 + * + * @param objects 要判断的对象数组 + ** @return true:为空 false:非空 + */ + public static boolean isEmpty(Object[] objects) + { + return isNull(objects) || (objects.length == 0); + } + + /** + * * 判断一个对象数组是否非空 + * + * @param objects 要判断的对象数组 + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Object[] objects) + { + return !isEmpty(objects); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:为空 false:非空 + */ + public static boolean isEmpty(Map map) + { + return isNull(map) || map.isEmpty(); + } + + /** + * * 判断一个Map是否为空 + * + * @param map 要判断的Map + * @return true:非空 false:空 + */ + public static boolean isNotEmpty(Map map) + { + return !isEmpty(map); + } + + /** + * * 判断一个字符串是否为空串 + * + * @param str String + * @return true:为空 false:非空 + */ + public static boolean isEmpty(String str) + { + return isNull(str) || NULLSTR.equals(str.trim()); + } + + /** + * * 判断一个字符串是否为非空串 + * + * @param str String + * @return true:非空串 false:空串 + */ + public static boolean isNotEmpty(String str) + { + return !isEmpty(str); + } + + /** + * * 判断一个对象是否为空 + * + * @param object Object + * @return true:为空 false:非空 + */ + public static boolean isNull(Object object) + { + return object == null; + } + + /** + * * 判断一个对象是否非空 + * + * @param object Object + * @return true:非空 false:空 + */ + public static boolean isNotNull(Object object) + { + return !isNull(object); + } + + /** + * * 判断一个对象是否是数组类型(Java基本型别的数组) + * + * @param object 对象 + * @return true:是数组 false:不是数组 + */ + public static boolean isArray(Object object) + { + return isNotNull(object) && object.getClass().isArray(); + } + + /** + * 去空格 + */ + public static String trim(String str) + { + return (str == null ? "" : str.trim()); + } + + /** + * 替换指定字符串的指定区间内字符为"*" + * + * @param str 字符串 + * @param startInclude 开始位置(包含) + * @param endExclude 结束位置(不包含) + * @return 替换后的字符串 + */ + public static String hide(CharSequence str, int startInclude, int endExclude) + { + if (isEmpty(str)) + { + return NULLSTR; + } + final int strLength = str.length(); + if (startInclude > strLength) + { + return NULLSTR; + } + if (endExclude > strLength) + { + endExclude = strLength; + } + if (startInclude > endExclude) + { + // 如果起始位置大于结束位置,不替换 + return NULLSTR; + } + final char[] chars = new char[strLength]; + for (int i = 0; i < strLength; i++) + { + if (i >= startInclude && i < endExclude) + { + chars[i] = ASTERISK; + } + else + { + chars[i] = str.charAt(i); + } + } + return new String(chars); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @return 结果 + */ + public static String substring(final String str, int start) + { + if (str == null) + { + return NULLSTR; + } + + if (start < 0) + { + start = str.length() + start; + } + + if (start < 0) + { + start = 0; + } + if (start > str.length()) + { + return NULLSTR; + } + + return str.substring(start); + } + + /** + * 截取字符串 + * + * @param str 字符串 + * @param start 开始 + * @param end 结束 + * @return 结果 + */ + public static String substring(final String str, int start, int end) + { + if (str == null) + { + return NULLSTR; + } + + if (end < 0) + { + end = str.length() + end; + } + if (start < 0) + { + start = str.length() + start; + } + + if (end > str.length()) + { + end = str.length(); + } + + if (start > end) + { + return NULLSTR; + } + + if (start < 0) + { + start = 0; + } + if (end < 0) + { + end = 0; + } + + return str.substring(start, end); + } + + /** + * 在字符串中查找第一个出现的 `open` 和最后一个出现的 `close` 之间的子字符串 + * + * @param str 要截取的字符串 + * @param open 起始字符串 + * @param close 结束字符串 + * @return 截取结果 + */ + public static String substringBetweenLast(final String str, final String open, final String close) + { + if (isEmpty(str) || isEmpty(open) || isEmpty(close)) + { + return NULLSTR; + } + final int start = str.indexOf(open); + if (start != INDEX_NOT_FOUND) + { + final int end = str.lastIndexOf(close); + if (end != INDEX_NOT_FOUND) + { + return str.substring(start + open.length(), end); + } + } + return NULLSTR; + } + + /** + * 判断是否为空,并且不是空白字符 + * + * @param str 要判断的value + * @return 结果 + */ + public static boolean hasText(String str) + { + return (str != null && !str.isEmpty() && containsText(str)); + } + + private static boolean containsText(CharSequence str) + { + int strLen = str.length(); + for (int i = 0; i < strLen; i++) + { + if (!Character.isWhitespace(str.charAt(i))) + { + return true; + } + } + return false; + } + + /** + * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用:format("this is {} for {}", "a", "b") -> this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") -> this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") -> this is \a for b
+ * + * @param template 文本模板,被替换的部分用 {} 表示 + * @param params 参数值 + * @return 格式化后的文本 + */ + public static String format(String template, Object... params) + { + if (isEmpty(params) || isEmpty(template)) + { + return template; + } + return StrFormatter.format(template, params); + } + + /** + * 是否为http(s)://开头 + * + * @param link 链接 + * @return 结果 + */ + public static boolean ishttp(String link) + { + return StringUtils.startsWithAny(link, Constants.HTTP, Constants.HTTPS); + } + + /** + * 字符串转set + * + * @param str 字符串 + * @param sep 分隔符 + * @return set集合 + */ + public static final Set str2Set(String str, String sep) + { + return new HashSet(str2List(str, sep, true, false)); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @return list集合 + */ + public static final List str2List(String str, String sep) + { + return str2List(str, sep, true, false); + } + + /** + * 字符串转list + * + * @param str 字符串 + * @param sep 分隔符 + * @param filterBlank 过滤纯空白 + * @param trim 去掉首尾空白 + * @return list集合 + */ + public static final List str2List(String str, String sep, boolean filterBlank, boolean trim) + { + List list = new ArrayList(); + if (StringUtils.isEmpty(str)) + { + return list; + } + + // 过滤空白字符串 + if (filterBlank && StringUtils.isBlank(str)) + { + return list; + } + String[] split = str.split(sep); + for (String string : split) + { + if (filterBlank && StringUtils.isBlank(string)) + { + continue; + } + if (trim) + { + string = string.trim(); + } + list.add(string); + } + + return list; + } + + /** + * 检查子字符串是否存在 + * + * @param seq 检查的字符串 + * @param searchSeq 查找的字符串 + * @return 结果 + */ + public static boolean contains(final CharSequence seq, final CharSequence searchSeq) + { + return Strings.CS.contains(seq, searchSeq); + } + + /** + * 判断给定的collection列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value + * + * @param collection 给定的集合 + * @param array 给定的数组 + * @return 结果 + */ + public static boolean containsAny(Collection collection, String... array) + { + if (isEmpty(collection) || isEmpty(array)) + { + return false; + } + else + { + for (String str : array) + { + if (collection.contains(str)) + { + return true; + } + } + return false; + } + } + + /** + * 判断是否包含给定数组中的任意一个。 + * + * @param cs 要判断的字符串 + * @param searchCharSequences 要判断的数组 + * @return 结果 + */ + public static boolean containsAny(final CharSequence cs, final CharSequence... searchCharSequences) + { + return Strings.CS.containsAny(cs, searchCharSequences); + } + + /** + * 查找指定字符串是否包含指定字符串列表中的任意一个字符串同时串忽略大小写 + * + * @param cs 指定字符串 + * @param searchCharSequences 需要检查的字符串数组 + * @return 是否包含任意一个字符串 + */ + public static boolean containsAnyIgnoreCase(CharSequence cs, CharSequence... searchCharSequences) + { + if (isEmpty(cs) || isEmpty(searchCharSequences)) + { + return false; + } + for (CharSequence testStr : searchCharSequences) + { + if (containsIgnoreCase(cs, testStr)) + { + return true; + } + } + return false; + } + + /** + * 检查是否包含要搜索的字符串,忽略大小写 + * + * @param str 要检查的字符串 + * @param searchStr 要查找的字符串 + * @return 如果包含要搜索的字符串(忽略大小写)则返回true,如果不包含或返回false + */ + public static boolean containsIgnoreCase(final CharSequence str, final CharSequence searchStr) + { + return Strings.CI.contains(str, searchStr); + } + + /** + * 检查字符串是否以任意前缀开始 + * + * @param sequence 要检查的字符串 + * @param searchStrings 区分大小写的字符串前缀数组 + * @return 结果 + */ + public static boolean startsWithAny(final CharSequence sequence, final CharSequence... searchStrings) + { + return Strings.CS.startsWithAny(sequence, searchStrings); + } + + /** + * 不区分大小写地检查一个字符串是否以指定前缀开头。 + * + * @param str 待检查的字符串 + * @param 要查找的前缀 + * @return 结果 + */ + public static boolean startsWithIgnoreCase(final CharSequence str, final CharSequence prefix) + { + return Strings.CI.startsWith(str, prefix); + } + + /** + * 比较两个字符串是否相同 + * + * @param cs1 第一个字符串 + * @param cs2 第二个字符串 + * @return 如果给定对象与字符串相等,则返回 true;否则返回 false + */ + public static boolean equals(final CharSequence cs1, final CharSequence cs2) + { + return Strings.CS.equals(cs1, cs2); + } + + /** + * 替换字符串中所有匹配的字符 + * + * @param text 要搜索和替换的文本 + * @param searchString 要搜索的字符串 + * @param replacement 用于替换的字符串 + * @return 处理完所有替换后的文本 + */ + public static String replace(final String text, final String searchString, final String replacement) + { + return Strings.CS.replace(text, searchString, replacement); + } + + /** + * 仅当子字符串位于源字符串末尾时才将其移除,否则返回源字符串。 + * @param str 要搜索的源字符串 + * @param remove 要搜索并移除的字符串 + * @return 如果找到并移除了字符串,则返回移除后的子字符串 + */ + public static String removeEnd(final String str, final String remove) + { + return Strings.CS.removeEnd(str, remove); + } + + /** + * 查找字符串首次出现位置的索引 + * + * @param seq 要检查的字符串 + * @param searchSeq 要查找的字符串 + * @return 返回指定字符在字符串中第一次出现处的索引,如果此字符串中没有这样的字符,则返回 -1 + */ + public static int indexOf(final CharSequence seq, final CharSequence searchSeq) + { + return Strings.CS.indexOf(seq, searchSeq); + } + + /** + * 检查字符串是否以指定的后缀结尾 + * + * @param str 要检查的字符 + * @param suffix 要检查的后缀 + * @return 若参数与该字符串末尾相符 true;否则 false + */ + public static boolean endsWith(final CharSequence str, final CharSequence suffix) + { + return Strings.CS.endsWith(str, suffix); + } + + /** + * 将给定的字符串与数组进行比较 + * + * @param string 要比较的字符串 + * @param searchStrings 字符串数组 + * @return 如果字符串等于(区分大小写){@code searchStrings}中的任意其他元素,则返回true;如果{@code searchStrings}为null或不包含匹配项,则返回false + */ + public static boolean equalsAny(final CharSequence string, final CharSequence... searchStrings) + { + return Strings.CS.equalsAny(string, searchStrings); + } + + /** + * 检查一个字符串是否以任意提供的区分大小写的后缀结尾。 + * + * @param sequence 要检查的字符串 + * @param searchStrings 要查找的区分大小写的字符串数组 + * @return 如果输入参数{@code sequence}为null且未提供任何{@code searchStrings},或者输入{@code sequence}以任意提供的区分大小写的{@code searchStrings}结尾,则返回{@code true}。 + */ + public static boolean endsWithAny(final CharSequence sequence, final CharSequence... searchStrings) + { + return Strings.CS.endsWithAny(sequence, searchStrings); + } + + /** + * 不区分大小写地检查字符序列是否以指定的后缀结尾 + * + * @param str 要检查的字符序列 + * @param suffix 要查找的后缀 + * @return 如果字符序列以该后缀结尾(不区分大小写),或两者均为{@code null},则返回{@code true} + */ + public static boolean endsWithIgnoreCase(final CharSequence str, final CharSequence suffix) + { + return Strings.CI.endsWith(str, suffix); + } + + /** + * 指定范围内查找字符串,忽略大小写 + * + * @param str 要检查的字符串 + * @param searchStr 要查找的字符串 + * @return 搜索字符串的第一个索引,如果未找到匹配项则返回 -1 + */ + public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) + { + return Strings.CI.indexOf(str, searchStr); + } + + /** + * Compares given {@code string} to a CharSequences vararg of {@code searchStrings}, + * returning {@code true} if the {@code string} is equal to any of the {@code searchStrings}, ignoring case. + * + * @param string to compare, may be {@code null}. + * @param searchStrings a vararg of strings, may be {@code null}. + * @return {@code true} if the string is equal (case-insensitive) to any other element of {@code searchStrings}; + */ + public static boolean equalsAnyIgnoreCase(final CharSequence string, final CharSequence... searchStrings) + { + return Strings.CI.equalsAny(string, searchStrings); + } + + /** + * 驼峰转下划线命名 + */ + public static String toUnderScoreCase(String str) + { + if (str == null) + { + return null; + } + StringBuilder sb = new StringBuilder(); + // 前置字符是否大写 + boolean preCharIsUpperCase = true; + // 当前字符是否大写 + boolean curreCharIsUpperCase = true; + // 下一字符是否大写 + boolean nexteCharIsUpperCase = true; + for (int i = 0; i < str.length(); i++) + { + char c = str.charAt(i); + if (i > 0) + { + preCharIsUpperCase = Character.isUpperCase(str.charAt(i - 1)); + } + else + { + preCharIsUpperCase = false; + } + + curreCharIsUpperCase = Character.isUpperCase(c); + + if (i < (str.length() - 1)) + { + nexteCharIsUpperCase = Character.isUpperCase(str.charAt(i + 1)); + } + + if (preCharIsUpperCase && curreCharIsUpperCase && !nexteCharIsUpperCase) + { + sb.append(SEPARATOR); + } + else if ((i != 0 && !preCharIsUpperCase) && curreCharIsUpperCase) + { + sb.append(SEPARATOR); + } + sb.append(Character.toLowerCase(c)); + } + + return sb.toString(); + } + + /** + * 是否包含字符串 + * + * @param str 验证字符串 + * @param strs 字符串组 + * @return 包含返回true + */ + public static boolean inStringIgnoreCase(String str, String... strs) + { + if (str != null && strs != null) + { + for (String s : strs) + { + if (str.equalsIgnoreCase(trim(s))) + { + return true; + } + } + } + return false; + } + + /** + * 删除最后一个字符串 + * + * @param str 输入字符串 + * @param spit 以什么类型结尾的 + * @return 截取后的字符串 + */ + public static String lastStringDel(String str, String spit) + { + if (!StringUtils.isEmpty(str) && str.endsWith(spit)) + { + return str.subSequence(0, str.length() - 1).toString(); + } + return str; + } + + /** + * 将下划线大写方式命名的字符串转换为驼峰式。如果转换前的下划线大写方式命名的字符串为空,则返回空字符串。 例如:HELLO_WORLD->HelloWorld + * + * @param name 转换前的下划线大写方式命名的字符串 + * @return 转换后的驼峰式命名的字符串 + */ + public static String convertToCamelCase(String name) + { + StringBuilder result = new StringBuilder(); + // 快速检查 + if (name == null || name.isEmpty()) + { + // 没必要转换 + return ""; + } + else if (!name.contains("_")) + { + // 不含下划线,仅将首字母大写 + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + // 用下划线将原始字符串分割 + String[] camels = name.split("_"); + for (String camel : camels) + { + // 跳过原始字符串中开头、结尾的下换线或双重下划线 + if (camel.isEmpty()) + { + continue; + } + // 首字母大写 + result.append(camel.substring(0, 1).toUpperCase()); + result.append(camel.substring(1).toLowerCase()); + } + return result.toString(); + } + + /** + * 驼峰式命名法 + * 例如:user_name->userName + */ + public static String toCamelCase(String s) + { + if (s == null) + { + return null; + } + if (s.indexOf(SEPARATOR) == -1) + { + return s; + } + s = s.toLowerCase(); + StringBuilder sb = new StringBuilder(s.length()); + boolean upperCase = false; + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + + if (c == SEPARATOR) + { + upperCase = true; + } + else if (upperCase) + { + sb.append(Character.toUpperCase(c)); + upperCase = false; + } + else + { + sb.append(c); + } + } + return sb.toString(); + } + + /** + * 查找指定字符串是否匹配指定字符串列表中的任意一个字符串 + * + * @param str 指定字符串 + * @param strs 需要检查的字符串数组 + * @return 是否匹配 + */ + public static boolean matches(String str, List strs) + { + if (isEmpty(str) || isEmpty(strs)) + { + return false; + } + for (String pattern : strs) + { + if (isMatch(pattern, str)) + { + return true; + } + } + return false; + } + + /** + * 判断url是否与规则配置: + * ? 表示单个字符; + * * 表示一层路径内的任意字符串,不可跨层级; + * ** 表示任意层路径; + * + * @param pattern 匹配规则 + * @param url 需要匹配的url + * @return + */ + public static boolean isMatch(String pattern, String url) + { + AntPathMatcher matcher = new AntPathMatcher(); + return matcher.match(pattern, url); + } + + @SuppressWarnings("unchecked") + public static T cast(Object obj) + { + return (T) obj; + } + + /** + * 数字左边补齐0,使之达到指定长度。注意,如果数字转换为字符串后,长度大于size,则只保留 最后size个字符。 + * + * @param num 数字对象 + * @param size 字符串指定长度 + * @return 返回数字的字符串格式,该字符串为指定长度。 + */ + public static final String padl(final Number num, final int size) + { + return padl(num.toString(), size, '0'); + } + + /** + * 字符串左补齐。如果原始字符串s长度大于size,则只保留最后size个字符。 + * + * @param s 原始字符串 + * @param size 字符串指定长度 + * @param c 用于补齐的字符 + * @return 返回指定长度的字符串,由原字符串左补齐或截取得到。 + */ + public static final String padl(final String s, final int size, final char c) + { + final StringBuilder sb = new StringBuilder(size); + if (s != null) + { + final int len = s.length(); + if (s.length() <= size) + { + for (int i = size - len; i > 0; i--) + { + sb.append(c); + } + sb.append(s); + } + else + { + return s.substring(len - size, len); + } + } + else + { + for (int i = size; i > 0; i--) + { + sb.append(c); + } + } + return sb.toString(); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/Threads.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/Threads.java new file mode 100644 index 000000000..8b6ab6774 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/Threads.java @@ -0,0 +1,99 @@ +package com.roomroot.common.utils; + +import java.util.concurrent.CancellationException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 线程相关工具类. + * + * @author roomroot + */ +public class Threads +{ + private static final Logger logger = LoggerFactory.getLogger(Threads.class); + + /** + * sleep等待,单位为毫秒 + */ + public static void sleep(long milliseconds) + { + try + { + Thread.sleep(milliseconds); + } + catch (InterruptedException e) + { + return; + } + } + + /** + * 停止线程池 + * 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务. + * 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数. + * 如果仍然超時,則強制退出. + * 另对在shutdown时线程本身被调用中断做了处理. + */ + public static void shutdownAndAwaitTermination(ExecutorService pool) + { + if (pool != null && !pool.isShutdown()) + { + pool.shutdown(); + try + { + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + pool.shutdownNow(); + if (!pool.awaitTermination(120, TimeUnit.SECONDS)) + { + logger.info("Pool did not terminate"); + } + } + } + catch (InterruptedException ie) + { + pool.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + /** + * 打印线程异常信息 + */ + public static void printException(Runnable r, Throwable t) + { + if (t == null && r instanceof Future) + { + try + { + Future future = (Future) r; + if (future.isDone()) + { + future.get(); + } + } + catch (CancellationException ce) + { + t = ce; + } + catch (ExecutionException ee) + { + t = ee.getCause(); + } + catch (InterruptedException ie) + { + Thread.currentThread().interrupt(); + } + } + if (t != null) + { + logger.error(t.getMessage(), t); + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/bean/BeanUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/bean/BeanUtils.java new file mode 100644 index 000000000..8b3bb0e53 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/bean/BeanUtils.java @@ -0,0 +1,110 @@ +package com.roomroot.common.utils.bean; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Bean 工具类 + * + * @author roomroot + */ +public class BeanUtils extends org.springframework.beans.BeanUtils +{ + /** Bean方法名中属性名开始的下标 */ + private static final int BEAN_METHOD_PROP_INDEX = 3; + + /** * 匹配getter方法的正则表达式 */ + private static final Pattern GET_PATTERN = Pattern.compile("get(\\p{javaUpperCase}\\w*)"); + + /** * 匹配setter方法的正则表达式 */ + private static final Pattern SET_PATTERN = Pattern.compile("set(\\p{javaUpperCase}\\w*)"); + + /** + * Bean属性复制工具方法。 + * + * @param dest 目标对象 + * @param src 源对象 + */ + public static void copyBeanProp(Object dest, Object src) + { + try + { + copyProperties(src, dest); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + /** + * 获取对象的setter方法。 + * + * @param obj 对象 + * @return 对象的setter方法列表 + */ + public static List getSetterMethods(Object obj) + { + // setter方法列表 + List setterMethods = new ArrayList(); + + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + + // 查找setter方法 + + for (Method method : methods) + { + Matcher m = SET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 1)) + { + setterMethods.add(method); + } + } + // 返回setter方法列表 + return setterMethods; + } + + /** + * 获取对象的getter方法。 + * + * @param obj 对象 + * @return 对象的getter方法列表 + */ + + public static List getGetterMethods(Object obj) + { + // getter方法列表 + List getterMethods = new ArrayList(); + // 获取所有方法 + Method[] methods = obj.getClass().getMethods(); + // 查找getter方法 + for (Method method : methods) + { + Matcher m = GET_PATTERN.matcher(method.getName()); + if (m.matches() && (method.getParameterTypes().length == 0)) + { + getterMethods.add(method); + } + } + // 返回getter方法列表 + return getterMethods; + } + + /** + * 检查Bean方法名中的属性名是否相等。
+ * 如getName()和setName()属性名一样,getName()和setAge()属性名不一样。 + * + * @param m1 方法名1 + * @param m2 方法名2 + * @return 属性名一样返回true,否则返回false + */ + + public static boolean isMethodPropEquals(String m1, String m2) + { + return m1.substring(BEAN_METHOD_PROP_INDEX).equals(m2.substring(BEAN_METHOD_PROP_INDEX)); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/bean/BeanValidators.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/bean/BeanValidators.java new file mode 100644 index 000000000..0a037265f --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/bean/BeanValidators.java @@ -0,0 +1,24 @@ +package com.roomroot.common.utils.bean; + +import java.util.Set; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Validator; + +/** + * bean对象属性验证 + * + * @author roomroot + */ +public class BeanValidators +{ + public static void validateWithException(Validator validator, Object object, Class... groups) + throws ConstraintViolationException + { + Set> constraintViolations = validator.validate(object, groups); + if (!constraintViolations.isEmpty()) + { + throw new ConstraintViolationException(constraintViolations); + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/FileTypeUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/FileTypeUtils.java new file mode 100644 index 000000000..7e48d26ef --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/FileTypeUtils.java @@ -0,0 +1,76 @@ +package com.roomroot.common.utils.file; + +import java.io.File; +import org.apache.commons.lang3.StringUtils; + +/** + * 文件类型工具类 + * + * @author roomroot + */ +public class FileTypeUtils +{ + /** + * 获取文件类型 + *

+ * 例如: roomroot.txt, 返回: txt + * + * @param file 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(File file) + { + if (null == file) + { + return StringUtils.EMPTY; + } + return getFileType(file.getName()); + } + + /** + * 获取文件类型 + *

+ * 例如: roomroot.txt, 返回: txt + * + * @param fileName 文件名 + * @return 后缀(不含".") + */ + public static String getFileType(String fileName) + { + int separatorIndex = fileName.lastIndexOf("."); + if (separatorIndex < 0) + { + return ""; + } + return fileName.substring(separatorIndex + 1).toLowerCase(); + } + + /** + * 获取文件类型 + * + * @param photoByte 文件字节码 + * @return 后缀(不含".") + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "JPG"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "GIF"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "JPG"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "BMP"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "PNG"; + } + return strFileExtendName; + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/FileUploadUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/FileUploadUtils.java new file mode 100644 index 000000000..babbbf1e4 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/FileUploadUtils.java @@ -0,0 +1,260 @@ +package com.roomroot.common.utils.file; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Objects; +import org.apache.commons.io.FilenameUtils; +import org.springframework.web.multipart.MultipartFile; +import com.roomroot.common.config.RoomRootConfig; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.exception.file.FileNameLengthLimitExceededException; +import com.roomroot.common.exception.file.FileSizeLimitExceededException; +import com.roomroot.common.exception.file.InvalidExtensionException; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.uuid.IdUtils; +import com.roomroot.common.utils.uuid.Seq; + +/** + * 文件上传工具类 + * + * @author roomroot + */ +public class FileUploadUtils +{ + /** + * 默认大小 50M + */ + public static final long DEFAULT_MAX_SIZE = 50 * 1024 * 1024L; + + /** + * 默认的文件名最大长度 100 + */ + public static final int DEFAULT_FILE_NAME_LENGTH = 100; + + /** + * 默认上传的地址 + */ + private static String defaultBaseDir = RoomRootConfig.getProfile(); + + public static void setDefaultBaseDir(String defaultBaseDir) + { + FileUploadUtils.defaultBaseDir = defaultBaseDir; + } + + public static String getDefaultBaseDir() + { + return defaultBaseDir; + } + + /** + * 以默认配置进行文件上传 + * + * @param file 上传的文件 + * @return 文件名称 + * @throws Exception + */ + public static final String upload(MultipartFile file) throws IOException + { + try + { + return upload(getDefaultBaseDir(), file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + catch (Exception e) + { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 根据文件路径上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @return 文件名称 + * @throws IOException + */ + public static final String upload(String baseDir, MultipartFile file) throws IOException + { + try + { + return upload(baseDir, file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION); + } + catch (Exception e) + { + throw new IOException(e.getMessage(), e); + } + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param allowedExtension 上传文件类型 + * @return 返回上传成功的文件名 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileNameLengthLimitExceededException 文件名太长 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + InvalidExtensionException + { + return upload(baseDir, file, allowedExtension, false); + } + + /** + * 文件上传 + * + * @param baseDir 相对应用的基目录 + * @param file 上传的文件 + * @param useCustomNaming 系统自定义文件名 + * @param allowedExtension 上传文件类型 + * @return 返回上传成功的文件名 + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws FileNameLengthLimitExceededException 文件名太长 + * @throws IOException 比如读写文件出错时 + * @throws InvalidExtensionException 文件校验异常 + */ + public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension, boolean useCustomNaming) + throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException, + InvalidExtensionException + { + int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length(); + if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH) + { + throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH); + } + + assertAllowed(file, allowedExtension); + + String fileName = useCustomNaming ? uuidFilename(file) : extractFilename(file); + + String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath(); + file.transferTo(Paths.get(absPath)); + return getPathFileName(baseDir, fileName); + } + + /** + * 编码文件名(日期格式目录 + 原文件名 + 序列值 + 后缀) + */ + public static final String extractFilename(MultipartFile file) + { + return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file)); + } + + /** + * 编编码文件名(日期格式目录 + UUID + 后缀) + */ + public static final String uuidFilename(MultipartFile file) + { + return StringUtils.format("{}/{}.{}", DateUtils.datePath(), IdUtils.fastSimpleUUID(), getExtension(file)); + } + + public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException + { + File desc = new File(uploadDir + File.separator + fileName); + + if (!desc.exists()) + { + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + } + return desc; + } + + public static final String getPathFileName(String uploadDir, String fileName) throws IOException + { + int dirLastIndex = RoomRootConfig.getProfile().length() + 1; + String currentDir = StringUtils.substring(uploadDir, dirLastIndex); + return Constants.RESOURCE_PREFIX + "/" + currentDir + "/" + fileName; + } + + /** + * 文件大小校验 + * + * @param file 上传的文件 + * @return + * @throws FileSizeLimitExceededException 如果超出最大大小 + * @throws InvalidExtensionException + */ + public static final void assertAllowed(MultipartFile file, String[] allowedExtension) + throws FileSizeLimitExceededException, InvalidExtensionException + { + long size = file.getSize(); + if (size > DEFAULT_MAX_SIZE) + { + throw new FileSizeLimitExceededException(DEFAULT_MAX_SIZE / 1024 / 1024); + } + + String fileName = file.getOriginalFilename(); + String extension = getExtension(file); + if (allowedExtension != null && !isAllowedExtension(extension, allowedExtension)) + { + if (allowedExtension == MimeTypeUtils.IMAGE_EXTENSION) + { + throw new InvalidExtensionException.InvalidImageExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.FLASH_EXTENSION) + { + throw new InvalidExtensionException.InvalidFlashExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.MEDIA_EXTENSION) + { + throw new InvalidExtensionException.InvalidMediaExtensionException(allowedExtension, extension, + fileName); + } + else if (allowedExtension == MimeTypeUtils.VIDEO_EXTENSION) + { + throw new InvalidExtensionException.InvalidVideoExtensionException(allowedExtension, extension, + fileName); + } + else + { + throw new InvalidExtensionException(allowedExtension, extension, fileName); + } + } + } + + /** + * 判断MIME类型是否是允许的MIME类型 + * + * @param extension + * @param allowedExtension + * @return + */ + public static final boolean isAllowedExtension(String extension, String[] allowedExtension) + { + for (String str : allowedExtension) + { + if (str.equalsIgnoreCase(extension)) + { + return true; + } + } + return false; + } + + /** + * 获取文件名的后缀 + * + * @param file 表单文件 + * @return 后缀名 + */ + public static final String getExtension(MultipartFile file) + { + String extension = FilenameUtils.getExtension(file.getOriginalFilename()); + if (StringUtils.isEmpty(extension)) + { + extension = MimeTypeUtils.getExtension(Objects.requireNonNull(file.getContentType())); + } + return extension; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/FileUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/FileUtils.java new file mode 100644 index 000000000..d29de556c --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/FileUtils.java @@ -0,0 +1,303 @@ +package com.roomroot.common.utils.file; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import com.roomroot.common.config.RoomRootConfig; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.uuid.IdUtils; + +/** + * 文件处理工具类 + * + * @author roomroot + */ +public class FileUtils +{ + public static String FILENAME_PATTERN = "[a-zA-Z0-9_\\-\\|\\.\\u4e00-\\u9fa5]+"; + + /** + * 输出指定文件的byte数组 + * + * @param filePath 文件路径 + * @param os 输出流 + * @return + */ + public static void writeBytes(String filePath, OutputStream os) throws IOException + { + FileInputStream fis = null; + try + { + File file = new File(filePath); + if (!file.exists()) + { + throw new FileNotFoundException(filePath); + } + fis = new FileInputStream(file); + byte[] b = new byte[1024]; + int length; + while ((length = fis.read(b)) > 0) + { + os.write(b, 0, length); + } + } + catch (IOException e) + { + throw e; + } + finally + { + IOUtils.close(os); + IOUtils.close(fis); + } + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeImportBytes(byte[] data) throws IOException + { + return writeBytes(data, RoomRootConfig.getImportPath()); + } + + /** + * 写数据到文件中 + * + * @param data 数据 + * @param uploadDir 目标文件 + * @return 目标文件 + * @throws IOException IO异常 + */ + public static String writeBytes(byte[] data, String uploadDir) throws IOException + { + FileOutputStream fos = null; + String pathName = ""; + try + { + String extension = getFileExtendName(data); + pathName = DateUtils.datePath() + "/" + IdUtils.fastUUID() + "." + extension; + File file = FileUploadUtils.getAbsoluteFile(uploadDir, pathName); + fos = new FileOutputStream(file); + fos.write(data); + } + finally + { + IOUtils.close(fos); + } + return FileUploadUtils.getPathFileName(uploadDir, pathName); + } + + /** + * 移除路径中的请求前缀片段 + * + * @param filePath 文件路径 + * @return 移除后的文件路径 + */ + public static String stripPrefix(String filePath) + { + return StringUtils.substringAfter(filePath, Constants.RESOURCE_PREFIX); + } + + /** + * 删除文件 + * + * @param filePath 文件 + * @return + */ + public static boolean deleteFile(String filePath) + { + boolean flag = false; + File file = new File(filePath); + // 路径为文件且不为空则进行删除 + if (file.isFile() && file.exists()) + { + flag = file.delete(); + } + return flag; + } + + /** + * 文件名称验证 + * + * @param filename 文件名称 + * @return true 正常 false 非法 + */ + public static boolean isValidFilename(String filename) + { + return filename.matches(FILENAME_PATTERN); + } + + /** + * 检查文件是否可下载 + * + * @param resource 需要下载的文件 + * @return true 正常 false 非法 + */ + public static boolean checkAllowDownload(String resource) + { + // 禁止目录上跳级别 + if (StringUtils.contains(resource, "..")) + { + return false; + } + + // 检查允许下载的文件规则 + if (ArrayUtils.contains(MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION, FileTypeUtils.getFileType(resource))) + { + return true; + } + + // 不在允许下载的文件规则 + return false; + } + + /** + * 下载文件名重新编码 + * + * @param request 请求对象 + * @param fileName 文件名 + * @return 编码后的文件名 + */ + public static String setFileDownloadHeader(HttpServletRequest request, String fileName) throws UnsupportedEncodingException + { + final String agent = request.getHeader("USER-AGENT"); + String filename = fileName; + if (agent.contains("MSIE")) + { + // IE浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + filename = filename.replace("+", " "); + } + else if (agent.contains("Firefox")) + { + // 火狐浏览器 + filename = new String(fileName.getBytes(), "ISO8859-1"); + } + else if (agent.contains("Chrome")) + { + // google浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + else + { + // 其它浏览器 + filename = URLEncoder.encode(filename, "utf-8"); + } + return filename; + } + + /** + * 下载文件名重新编码 + * + * @param response 响应对象 + * @param realFileName 真实文件名 + */ + public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException + { + String percentEncodedFileName = percentEncode(realFileName); + + StringBuilder contentDispositionValue = new StringBuilder(); + contentDispositionValue.append("attachment; filename=") + .append(percentEncodedFileName) + .append(";") + .append("filename*=") + .append("utf-8''") + .append(percentEncodedFileName); + + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename"); + response.setHeader("Content-disposition", contentDispositionValue.toString()); + response.setHeader("download-filename", percentEncodedFileName); + } + + /** + * 百分号编码工具方法 + * + * @param s 需要百分号编码的字符串 + * @return 百分号编码后的字符串 + */ + public static String percentEncode(String s) throws UnsupportedEncodingException + { + String encode = URLEncoder.encode(s, StandardCharsets.UTF_8.toString()); + return encode.replaceAll("\\+", "%20"); + } + + /** + * 获取图像后缀 + * + * @param photoByte 图像数据 + * @return 后缀名 + */ + public static String getFileExtendName(byte[] photoByte) + { + String strFileExtendName = "jpg"; + if ((photoByte[0] == 71) && (photoByte[1] == 73) && (photoByte[2] == 70) && (photoByte[3] == 56) + && ((photoByte[4] == 55) || (photoByte[4] == 57)) && (photoByte[5] == 97)) + { + strFileExtendName = "gif"; + } + else if ((photoByte[6] == 74) && (photoByte[7] == 70) && (photoByte[8] == 73) && (photoByte[9] == 70)) + { + strFileExtendName = "jpg"; + } + else if ((photoByte[0] == 66) && (photoByte[1] == 77)) + { + strFileExtendName = "bmp"; + } + else if ((photoByte[1] == 80) && (photoByte[2] == 78) && (photoByte[3] == 71)) + { + strFileExtendName = "png"; + } + return strFileExtendName; + } + + /** + * 获取文件名称 /profile/upload/2022/04/16/roomroot.png -- roomroot.png + * + * @param fileName 路径名称 + * @return 没有文件路径的名称 + */ + public static String getName(String fileName) + { + if (fileName == null) + { + return null; + } + int lastUnixPos = fileName.lastIndexOf('/'); + int lastWindowsPos = fileName.lastIndexOf('\\'); + int index = Math.max(lastUnixPos, lastWindowsPos); + return fileName.substring(index + 1); + } + + /** + * 获取不带后缀文件名称 /profile/upload/2022/04/16/roomroot.png -- roomroot + * + * @param fileName 路径名称 + * @return 没有文件路径和后缀的名称 + */ + public static String getNameNotSuffix(String fileName) + { + if (fileName == null) + { + return null; + } + String baseName = FilenameUtils.getBaseName(fileName); + return baseName; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/ImageUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/ImageUtils.java new file mode 100644 index 000000000..64fc93b9a --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/ImageUtils.java @@ -0,0 +1,98 @@ +package com.roomroot.common.utils.file; + +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.util.Arrays; +import org.apache.poi.util.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.roomroot.common.config.RoomRootConfig; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.utils.StringUtils; + +/** + * 图片处理工具类 + * + * @author roomroot + */ +public class ImageUtils +{ + private static final Logger log = LoggerFactory.getLogger(ImageUtils.class); + + public static byte[] getImage(String imagePath) + { + InputStream is = getFile(imagePath); + try + { + return IOUtils.toByteArray(is); + } + catch (Exception e) + { + log.error("图片加载异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(is); + } + } + + public static InputStream getFile(String imagePath) + { + try + { + byte[] result = readFile(imagePath); + result = Arrays.copyOf(result, result.length); + return new ByteArrayInputStream(result); + } + catch (Exception e) + { + log.error("获取图片异常 {}", e); + } + return null; + } + + /** + * 读取文件为字节数据 + * + * @param url 地址 + * @return 字节数据 + */ + public static byte[] readFile(String url) + { + InputStream in = null; + try + { + if (url.startsWith("http")) + { + // 网络地址 + URL urlObj = new URL(url); + URLConnection urlConnection = urlObj.openConnection(); + urlConnection.setConnectTimeout(30 * 1000); + urlConnection.setReadTimeout(60 * 1000); + urlConnection.setDoInput(true); + in = urlConnection.getInputStream(); + } + else + { + // 本机地址 + String localPath = RoomRootConfig.getProfile(); + String downloadPath = localPath + StringUtils.substringAfter(url, Constants.RESOURCE_PREFIX); + in = new FileInputStream(downloadPath); + } + return IOUtils.toByteArray(in); + } + catch (Exception e) + { + log.error("获取文件路径异常 {}", e); + return null; + } + finally + { + IOUtils.closeQuietly(in); + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/MimeTypeUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/MimeTypeUtils.java new file mode 100644 index 000000000..24f8c1fe2 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/file/MimeTypeUtils.java @@ -0,0 +1,59 @@ +package com.roomroot.common.utils.file; + +/** + * 媒体类型工具类 + * + * @author roomroot + */ +public class MimeTypeUtils +{ + public static final String IMAGE_PNG = "image/png"; + + public static final String IMAGE_JPG = "image/jpg"; + + public static final String IMAGE_JPEG = "image/jpeg"; + + public static final String IMAGE_BMP = "image/bmp"; + + public static final String IMAGE_GIF = "image/gif"; + + public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" }; + + public static final String[] FLASH_EXTENSION = { "swf", "flv" }; + + public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg", + "asf", "rm", "rmvb" }; + + public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" }; + + public static final String[] DEFAULT_ALLOWED_EXTENSION = { + // 图片 + "bmp", "gif", "jpg", "jpeg", "png", + // word excel powerpoint + "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", + // 压缩文件 + "rar", "zip", "gz", "bz2", + // 视频格式 + "mp4", "avi", "rmvb", + // pdf + "pdf" }; + + public static String getExtension(String prefix) + { + switch (prefix) + { + case IMAGE_PNG: + return "png"; + case IMAGE_JPG: + return "jpg"; + case IMAGE_JPEG: + return "jpeg"; + case IMAGE_BMP: + return "bmp"; + case IMAGE_GIF: + return "gif"; + default: + return ""; + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/html/EscapeUtil.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/html/EscapeUtil.java new file mode 100644 index 000000000..c5a828efe --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/html/EscapeUtil.java @@ -0,0 +1,167 @@ +package com.roomroot.common.utils.html; + +import com.roomroot.common.utils.StringUtils; + +/** + * 转义和反转义工具类 + * + * @author roomroot + */ +public class EscapeUtil +{ + public static final String RE_HTML_MARK = "(<[^<]*?>)|(<[\\s]*?/[^<]*?>)|(<[^<]*?/[\\s]*?>)"; + + private static final char[][] TEXT = new char[64][]; + + static + { + for (int i = 0; i < 64; i++) + { + TEXT[i] = new char[] { (char) i }; + } + + // special HTML characters + TEXT['\''] = "'".toCharArray(); // 单引号 + TEXT['"'] = """.toCharArray(); // 双引号 + TEXT['&'] = "&".toCharArray(); // &符 + TEXT['<'] = "<".toCharArray(); // 小于号 + TEXT['>'] = ">".toCharArray(); // 大于号 + } + + /** + * 转义文本中的HTML字符为安全的字符 + * + * @param text 被转义的文本 + * @return 转义后的文本 + */ + public static String escape(String text) + { + return encode(text); + } + + /** + * 还原被转义的HTML特殊字符 + * + * @param content 包含转义符的HTML内容 + * @return 转换后的字符串 + */ + public static String unescape(String content) + { + return decode(content); + } + + /** + * 清除所有HTML标签,但是不删除标签内的内容 + * + * @param content 文本 + * @return 清除标签后的文本 + */ + public static String clean(String content) + { + return new HTMLFilter().filter(content); + } + + /** + * Escape编码 + * + * @param text 被编码的文本 + * @return 编码后的字符 + */ + private static String encode(String text) + { + if (StringUtils.isEmpty(text)) + { + return StringUtils.EMPTY; + } + + final StringBuilder tmp = new StringBuilder(text.length() * 6); + char c; + for (int i = 0; i < text.length(); i++) + { + c = text.charAt(i); + if (c < 256) + { + tmp.append("%"); + if (c < 16) + { + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + else + { + tmp.append("%u"); + if (c <= 0xfff) + { + // issue#I49JU8@Gitee + tmp.append("0"); + } + tmp.append(Integer.toString(c, 16)); + } + } + return tmp.toString(); + } + + /** + * Escape解码 + * + * @param content 被转义的内容 + * @return 解码后的字符串 + */ + public static String decode(String content) + { + if (StringUtils.isEmpty(content)) + { + return content; + } + + StringBuilder tmp = new StringBuilder(content.length()); + int lastPos = 0, pos = 0; + char ch; + while (lastPos < content.length()) + { + pos = content.indexOf("%", lastPos); + if (pos == lastPos) + { + if (content.charAt(pos + 1) == 'u') + { + ch = (char) Integer.parseInt(content.substring(pos + 2, pos + 6), 16); + tmp.append(ch); + lastPos = pos + 6; + } + else + { + ch = (char) Integer.parseInt(content.substring(pos + 1, pos + 3), 16); + tmp.append(ch); + lastPos = pos + 3; + } + } + else + { + if (pos == -1) + { + tmp.append(content.substring(lastPos)); + lastPos = content.length(); + } + else + { + tmp.append(content.substring(lastPos, pos)); + lastPos = pos; + } + } + } + return tmp.toString(); + } + + public static void main(String[] args) + { + String html = ""; + String escape = EscapeUtil.escape(html); + // String html = "ipt>alert(\"XSS\")ipt>"; + // String html = "<123"; + // String html = "123>"; + System.out.println("clean: " + EscapeUtil.clean(html)); + System.out.println("escape: " + escape); + System.out.println("unescape: " + EscapeUtil.unescape(escape)); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/html/HTMLFilter.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/html/HTMLFilter.java new file mode 100644 index 000000000..ff737ec6c --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/html/HTMLFilter.java @@ -0,0 +1,570 @@ +package com.roomroot.common.utils.html; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * HTML过滤器,用于去除XSS漏洞隐患。 + * + * @author roomroot + */ +public final class HTMLFilter +{ + /** + * regex flag union representing /si modifiers in php + **/ + private static final int REGEX_FLAGS_SI = Pattern.CASE_INSENSITIVE | Pattern.DOTALL; + private static final Pattern P_COMMENTS = Pattern.compile("", Pattern.DOTALL); + private static final Pattern P_COMMENT = Pattern.compile("^!--(.*)--$", REGEX_FLAGS_SI); + private static final Pattern P_TAGS = Pattern.compile("<(.*?)>", Pattern.DOTALL); + private static final Pattern P_END_TAG = Pattern.compile("^/([a-z0-9]+)", REGEX_FLAGS_SI); + private static final Pattern P_START_TAG = Pattern.compile("^([a-z0-9]+)(.*?)(/?)$", REGEX_FLAGS_SI); + private static final Pattern P_QUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)=([\"'])(.*?)\\2", REGEX_FLAGS_SI); + private static final Pattern P_UNQUOTED_ATTRIBUTES = Pattern.compile("([a-z0-9]+)(=)([^\"\\s']+)", REGEX_FLAGS_SI); + private static final Pattern P_PROTOCOL = Pattern.compile("^([^:]+):", REGEX_FLAGS_SI); + private static final Pattern P_ENTITY = Pattern.compile("&#(\\d+);?"); + private static final Pattern P_ENTITY_UNICODE = Pattern.compile("&#x([0-9a-f]+);?"); + private static final Pattern P_ENCODE = Pattern.compile("%([0-9a-f]{2});?"); + private static final Pattern P_VALID_ENTITIES = Pattern.compile("&([^&;]*)(?=(;|&|$))"); + private static final Pattern P_VALID_QUOTES = Pattern.compile("(>|^)([^<]+?)(<|$)", Pattern.DOTALL); + private static final Pattern P_END_ARROW = Pattern.compile("^>"); + private static final Pattern P_BODY_TO_END = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_XML_CONTENT = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_STRAY_LEFT_ARROW = Pattern.compile("<([^>]*?)(?=<|$)"); + private static final Pattern P_STRAY_RIGHT_ARROW = Pattern.compile("(^|>)([^<]*?)(?=>)"); + private static final Pattern P_AMP = Pattern.compile("&"); + private static final Pattern P_QUOTE = Pattern.compile("\""); + private static final Pattern P_LEFT_ARROW = Pattern.compile("<"); + private static final Pattern P_RIGHT_ARROW = Pattern.compile(">"); + private static final Pattern P_BOTH_ARROWS = Pattern.compile("<>"); + + // @xxx could grow large... maybe use sesat's ReferenceMap + private static final ConcurrentMap P_REMOVE_PAIR_BLANKS = new ConcurrentHashMap<>(); + private static final ConcurrentMap P_REMOVE_SELF_BLANKS = new ConcurrentHashMap<>(); + + /** + * set of allowed html elements, along with allowed attributes for each element + **/ + private final Map> vAllowed; + /** + * counts of open tags for each (allowable) html element + **/ + private final Map vTagCounts = new HashMap<>(); + + /** + * html elements which must always be self-closing (e.g. "") + **/ + private final String[] vSelfClosingTags; + /** + * html elements which must always have separate opening and closing tags (e.g. "") + **/ + private final String[] vNeedClosingTags; + /** + * set of disallowed html elements + **/ + private final String[] vDisallowed; + /** + * attributes which should be checked for valid protocols + **/ + private final String[] vProtocolAtts; + /** + * allowed protocols + **/ + private final String[] vAllowedProtocols; + /** + * tags which should be removed if they contain no content (e.g. "" or "") + **/ + private final String[] vRemoveBlanks; + /** + * entities allowed within html markup + **/ + private final String[] vAllowedEntities; + /** + * flag determining whether comments are allowed in input String. + */ + private final boolean stripComment; + private final boolean encodeQuotes; + /** + * flag determining whether to try to make tags when presented with "unbalanced" angle brackets (e.g. "" + * becomes " text "). If set to false, unbalanced angle brackets will be html escaped. + */ + private final boolean alwaysMakeTags; + + /** + * Default constructor. + */ + public HTMLFilter() + { + vAllowed = new HashMap<>(); + + final ArrayList a_atts = new ArrayList<>(); + a_atts.add("href"); + a_atts.add("target"); + vAllowed.put("a", a_atts); + + final ArrayList img_atts = new ArrayList<>(); + img_atts.add("src"); + img_atts.add("width"); + img_atts.add("height"); + img_atts.add("alt"); + vAllowed.put("img", img_atts); + + final ArrayList no_atts = new ArrayList<>(); + vAllowed.put("b", no_atts); + vAllowed.put("strong", no_atts); + vAllowed.put("i", no_atts); + vAllowed.put("em", no_atts); + + vSelfClosingTags = new String[] { "img" }; + vNeedClosingTags = new String[] { "a", "b", "strong", "i", "em" }; + vDisallowed = new String[] {}; + vAllowedProtocols = new String[] { "http", "mailto", "https" }; // no ftp. + vProtocolAtts = new String[] { "src", "href" }; + vRemoveBlanks = new String[] { "a", "b", "strong", "i", "em" }; + vAllowedEntities = new String[] { "amp", "gt", "lt", "quot" }; + stripComment = true; + encodeQuotes = true; + alwaysMakeTags = false; + } + + /** + * Map-parameter configurable constructor. + * + * @param conf map containing configuration. keys match field names. + */ + @SuppressWarnings("unchecked") + public HTMLFilter(final Map conf) + { + + assert conf.containsKey("vAllowed") : "configuration requires vAllowed"; + assert conf.containsKey("vSelfClosingTags") : "configuration requires vSelfClosingTags"; + assert conf.containsKey("vNeedClosingTags") : "configuration requires vNeedClosingTags"; + assert conf.containsKey("vDisallowed") : "configuration requires vDisallowed"; + assert conf.containsKey("vAllowedProtocols") : "configuration requires vAllowedProtocols"; + assert conf.containsKey("vProtocolAtts") : "configuration requires vProtocolAtts"; + assert conf.containsKey("vRemoveBlanks") : "configuration requires vRemoveBlanks"; + assert conf.containsKey("vAllowedEntities") : "configuration requires vAllowedEntities"; + + vAllowed = Collections.unmodifiableMap((HashMap>) conf.get("vAllowed")); + vSelfClosingTags = (String[]) conf.get("vSelfClosingTags"); + vNeedClosingTags = (String[]) conf.get("vNeedClosingTags"); + vDisallowed = (String[]) conf.get("vDisallowed"); + vAllowedProtocols = (String[]) conf.get("vAllowedProtocols"); + vProtocolAtts = (String[]) conf.get("vProtocolAtts"); + vRemoveBlanks = (String[]) conf.get("vRemoveBlanks"); + vAllowedEntities = (String[]) conf.get("vAllowedEntities"); + stripComment = conf.containsKey("stripComment") ? (Boolean) conf.get("stripComment") : true; + encodeQuotes = conf.containsKey("encodeQuotes") ? (Boolean) conf.get("encodeQuotes") : true; + alwaysMakeTags = conf.containsKey("alwaysMakeTags") ? (Boolean) conf.get("alwaysMakeTags") : true; + } + + private void reset() + { + vTagCounts.clear(); + } + + // --------------------------------------------------------------- + // my versions of some PHP library functions + public static String chr(final int decimal) + { + return String.valueOf((char) decimal); + } + + public static String htmlSpecialChars(final String s) + { + String result = s; + result = regexReplace(P_AMP, "&", result); + result = regexReplace(P_QUOTE, """, result); + result = regexReplace(P_LEFT_ARROW, "<", result); + result = regexReplace(P_RIGHT_ARROW, ">", result); + return result; + } + + // --------------------------------------------------------------- + + /** + * given a user submitted input String, filter out any invalid or restricted html. + * + * @param input text (i.e. submitted by a user) than may contain html + * @return "clean" version of input, with only valid, whitelisted html elements allowed + */ + public String filter(final String input) + { + reset(); + String s = input; + + s = escapeComments(s); + + s = balanceHTML(s); + + s = checkTags(s); + + s = processRemoveBlanks(s); + + // s = validateEntities(s); + + return s; + } + + public boolean isAlwaysMakeTags() + { + return alwaysMakeTags; + } + + public boolean isStripComments() + { + return stripComment; + } + + private String escapeComments(final String s) + { + final Matcher m = P_COMMENTS.matcher(s); + final StringBuffer buf = new StringBuffer(); + if (m.find()) + { + final String match = m.group(1); // (.*?) + m.appendReplacement(buf, Matcher.quoteReplacement("")); + } + m.appendTail(buf); + + return buf.toString(); + } + + private String balanceHTML(String s) + { + if (alwaysMakeTags) + { + // + // try and form html + // + s = regexReplace(P_END_ARROW, "", s); + // 不追加结束标签 + s = regexReplace(P_BODY_TO_END, "<$1>", s); + s = regexReplace(P_XML_CONTENT, "$1<$2", s); + + } + else + { + // + // escape stray brackets + // + s = regexReplace(P_STRAY_LEFT_ARROW, "<$1", s); + s = regexReplace(P_STRAY_RIGHT_ARROW, "$1$2><", s); + + // + // the last regexp causes '<>' entities to appear + // (we need to do a lookahead assertion so that the last bracket can + // be used in the next pass of the regexp) + // + s = regexReplace(P_BOTH_ARROWS, "", s); + } + + return s; + } + + private String checkTags(String s) + { + Matcher m = P_TAGS.matcher(s); + + final StringBuffer buf = new StringBuffer(); + while (m.find()) + { + String replaceStr = m.group(1); + replaceStr = processTag(replaceStr); + m.appendReplacement(buf, Matcher.quoteReplacement(replaceStr)); + } + m.appendTail(buf); + + // these get tallied in processTag + // (remember to reset before subsequent calls to filter method) + final StringBuilder sBuilder = new StringBuilder(buf.toString()); + for (String key : vTagCounts.keySet()) + { + for (int ii = 0; ii < vTagCounts.get(key); ii++) + { + sBuilder.append(""); + } + } + s = sBuilder.toString(); + + return s; + } + + private String processRemoveBlanks(final String s) + { + String result = s; + for (String tag : vRemoveBlanks) + { + if (!P_REMOVE_PAIR_BLANKS.containsKey(tag)) + { + P_REMOVE_PAIR_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?>")); + } + result = regexReplace(P_REMOVE_PAIR_BLANKS.get(tag), "", result); + if (!P_REMOVE_SELF_BLANKS.containsKey(tag)) + { + P_REMOVE_SELF_BLANKS.putIfAbsent(tag, Pattern.compile("<" + tag + "(\\s[^>]*)?/>")); + } + result = regexReplace(P_REMOVE_SELF_BLANKS.get(tag), "", result); + } + + return result; + } + + private static String regexReplace(final Pattern regex_pattern, final String replacement, final String s) + { + Matcher m = regex_pattern.matcher(s); + return m.replaceAll(replacement); + } + + private String processTag(final String s) + { + // ending tags + Matcher m = P_END_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + if (allowed(name)) + { + if (!inArray(name, vSelfClosingTags)) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) - 1); + return ""; + } + } + } + } + + // starting tags + m = P_START_TAG.matcher(s); + if (m.find()) + { + final String name = m.group(1).toLowerCase(); + final String body = m.group(2); + String ending = m.group(3); + + // debug( "in a starting tag, name='" + name + "'; body='" + body + "'; ending='" + ending + "'" ); + if (allowed(name)) + { + final StringBuilder params = new StringBuilder(); + + final Matcher m2 = P_QUOTED_ATTRIBUTES.matcher(body); + final Matcher m3 = P_UNQUOTED_ATTRIBUTES.matcher(body); + final List paramNames = new ArrayList<>(); + final List paramValues = new ArrayList<>(); + while (m2.find()) + { + paramNames.add(m2.group(1)); // ([a-z0-9]+) + paramValues.add(m2.group(3)); // (.*?) + } + while (m3.find()) + { + paramNames.add(m3.group(1)); // ([a-z0-9]+) + paramValues.add(m3.group(3)); // ([^\"\\s']+) + } + + String paramName, paramValue; + for (int ii = 0; ii < paramNames.size(); ii++) + { + paramName = paramNames.get(ii).toLowerCase(); + paramValue = paramValues.get(ii); + + // debug( "paramName='" + paramName + "'" ); + // debug( "paramValue='" + paramValue + "'" ); + // debug( "allowed? " + vAllowed.get( name ).contains( paramName ) ); + + if (allowedAttribute(name, paramName)) + { + if (inArray(paramName, vProtocolAtts)) + { + paramValue = processParamProtocol(paramValue); + } + params.append(' ').append(paramName).append("=\\\"").append(paramValue).append("\\\""); + } + } + + if (inArray(name, vSelfClosingTags)) + { + ending = " /"; + } + + if (inArray(name, vNeedClosingTags)) + { + ending = ""; + } + + if (ending == null || ending.length() < 1) + { + if (vTagCounts.containsKey(name)) + { + vTagCounts.put(name, vTagCounts.get(name) + 1); + } + else + { + vTagCounts.put(name, 1); + } + } + else + { + ending = " /"; + } + return "<" + name + params + ending + ">"; + } + else + { + return ""; + } + } + + // comments + m = P_COMMENT.matcher(s); + if (!stripComment && m.find()) + { + return "<" + m.group() + ">"; + } + + return ""; + } + + private String processParamProtocol(String s) + { + s = decodeEntities(s); + final Matcher m = P_PROTOCOL.matcher(s); + if (m.find()) + { + final String protocol = m.group(1); + if (!inArray(protocol, vAllowedProtocols)) + { + // bad protocol, turn into local anchor link instead + s = "#" + s.substring(protocol.length() + 1); + if (s.startsWith("#//")) + { + s = "#" + s.substring(3); + } + } + } + + return s; + } + + private String decodeEntities(String s) + { + StringBuffer buf = new StringBuffer(); + + Matcher m = P_ENTITY.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.decode(match).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENTITY_UNICODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + buf = new StringBuffer(); + m = P_ENCODE.matcher(s); + while (m.find()) + { + final String match = m.group(1); + final int decimal = Integer.valueOf(match, 16).intValue(); + m.appendReplacement(buf, Matcher.quoteReplacement(chr(decimal))); + } + m.appendTail(buf); + s = buf.toString(); + + s = validateEntities(s); + return s; + } + + private String validateEntities(final String s) + { + StringBuffer buf = new StringBuffer(); + + // validate entities throughout the string + Matcher m = P_VALID_ENTITIES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // ([^&;]*) + final String two = m.group(2); // (?=(;|&|$)) + m.appendReplacement(buf, Matcher.quoteReplacement(checkEntity(one, two))); + } + m.appendTail(buf); + + return encodeQuotes(buf.toString()); + } + + private String encodeQuotes(final String s) + { + if (encodeQuotes) + { + StringBuffer buf = new StringBuffer(); + Matcher m = P_VALID_QUOTES.matcher(s); + while (m.find()) + { + final String one = m.group(1); // (>|^) + final String two = m.group(2); // ([^<]+?) + final String three = m.group(3); // (<|$) + // 不替换双引号为",防止json格式无效 regexReplace(P_QUOTE, """, two) + m.appendReplacement(buf, Matcher.quoteReplacement(one + two + three)); + } + m.appendTail(buf); + return buf.toString(); + } + else + { + return s; + } + } + + private String checkEntity(final String preamble, final String term) + { + + return ";".equals(term) && isValidEntity(preamble) ? '&' + preamble : "&" + preamble; + } + + private boolean isValidEntity(final String entity) + { + return inArray(entity, vAllowedEntities); + } + + private static boolean inArray(final String s, final String[] array) + { + for (String item : array) + { + if (item != null && item.equals(s)) + { + return true; + } + } + return false; + } + + private boolean allowed(final String name) + { + return (vAllowed.isEmpty() || vAllowed.containsKey(name)) && !inArray(name, vDisallowed); + } + + private boolean allowedAttribute(final String name, final String paramName) + { + return allowed(name) && (vAllowed.isEmpty() || vAllowed.get(name).contains(paramName)); + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/http/HttpHelper.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/http/HttpHelper.java new file mode 100644 index 000000000..abe1f1cf7 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/http/HttpHelper.java @@ -0,0 +1,55 @@ +package com.roomroot.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import jakarta.servlet.ServletRequest; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 通用http工具封装 + * + * @author roomroot + */ +public class HttpHelper +{ + private static final Logger LOGGER = LoggerFactory.getLogger(HttpHelper.class); + + public static String getBodyString(ServletRequest request) + { + StringBuilder sb = new StringBuilder(); + BufferedReader reader = null; + try (InputStream inputStream = request.getInputStream()) + { + reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + String line = ""; + while ((line = reader.readLine()) != null) + { + sb.append(line); + } + } + catch (IOException e) + { + LOGGER.warn("getBodyString出现问题!"); + } + finally + { + if (reader != null) + { + try + { + reader.close(); + } + catch (IOException e) + { + LOGGER.error(ExceptionUtils.getMessage(e)); + } + } + } + return sb.toString(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/http/HttpUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/http/HttpUtils.java new file mode 100644 index 000000000..7323b5e0a --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/http/HttpUtils.java @@ -0,0 +1,293 @@ +package com.roomroot.common.utils.http; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.URL; +import java.net.URLConnection; +import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.utils.StringUtils; +import org.springframework.http.MediaType; + +/** + * 通用http发送方法 + * + * @author roomroot + */ +public class HttpUtils +{ + private static final Logger log = LoggerFactory.getLogger(HttpUtils.class); + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url) + { + return sendGet(url, StringUtils.EMPTY); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param) + { + return sendGet(url, param, Constants.UTF8); + } + + /** + * 向指定 URL 发送GET方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @param contentType 编码类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendGet(String url, String param, String contentType) + { + StringBuilder result = new StringBuilder(); + BufferedReader in = null; + try + { + String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url; + log.info("sendGet - {}", urlNameString); + URL realUrl = new URL(urlNameString); + URLConnection connection = realUrl.openConnection(); + connection.setRequestProperty("accept", "*/*"); + connection.setRequestProperty("connection", "Keep-Alive"); + connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + connection.connect(); + in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e); + } + finally + { + try + { + if (in != null) + { + in.close(); + } + } + catch (Exception ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param) + { + return sendPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + } + + /** + * 向指定 URL 发送POST方法的请求 + * + * @param url 发送请求的 URL + * @param param 请求参数 + * @param contentType 内容类型 + * @return 所代表远程资源的响应结果 + */ + public static String sendPost(String url, String param, String contentType) + { + PrintWriter out = null; + BufferedReader in = null; + StringBuilder result = new StringBuilder(); + try + { + log.info("sendPost - {}", url); + URL realUrl = new URL(url); + URLConnection conn = realUrl.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("Content-Type", contentType); + conn.setDoOutput(true); + conn.setDoInput(true); + out = new PrintWriter(conn.getOutputStream()); + out.print(param); + out.flush(); + in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8)); + String line; + while ((line = in.readLine()) != null) + { + result.append(line); + } + log.info("recv - {}", result); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e); + } + finally + { + try + { + if (out != null) + { + out.close(); + } + if (in != null) + { + in.close(); + } + } + catch (IOException ex) + { + log.error("调用in.close Exception, url=" + url + ",param=" + param, ex); + } + } + return result.toString(); + } + + public static String sendSSLPost(String url, String param) + { + return sendSSLPost(url, param, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + } + + public static String sendSSLPost(String url, String param, String contentType) + { + StringBuilder result = new StringBuilder(); + String urlNameString = url + "?" + param; + try + { + log.info("sendSSLPost - {}", urlNameString); + SSLContext sc = SSLContext.getInstance("SSL"); + sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom()); + URL console = new URL(urlNameString); + HttpsURLConnection conn = (HttpsURLConnection) console.openConnection(); + conn.setRequestProperty("accept", "*/*"); + conn.setRequestProperty("connection", "Keep-Alive"); + conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"); + conn.setRequestProperty("Accept-Charset", "utf-8"); + conn.setRequestProperty("Content-Type", contentType); + conn.setDoOutput(true); + conn.setDoInput(true); + + conn.setSSLSocketFactory(sc.getSocketFactory()); + conn.setHostnameVerifier(new TrustAnyHostnameVerifier()); + conn.connect(); + InputStream is = conn.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String ret = ""; + while ((ret = br.readLine()) != null) + { + if (ret != null && !"".equals(ret.trim())) + { + result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)); + } + } + log.info("recv - {}", result); + conn.disconnect(); + br.close(); + } + catch (ConnectException e) + { + log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e); + } + catch (SocketTimeoutException e) + { + log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e); + } + catch (IOException e) + { + log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e); + } + catch (Exception e) + { + log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e); + } + return result.toString(); + } + + private static class TrustAnyTrustManager implements X509TrustManager + { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) + { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + { + } + + @Override + public X509Certificate[] getAcceptedIssuers() + { + return new X509Certificate[] {}; + } + } + + private static class TrustAnyHostnameVerifier implements HostnameVerifier + { + @Override + public boolean verify(String hostname, SSLSession session) + { + return true; + } + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/http/UserAgentUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/http/UserAgentUtils.java new file mode 100644 index 000000000..70b82277e --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/http/UserAgentUtils.java @@ -0,0 +1,254 @@ +package com.roomroot.common.utils.http; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.roomroot.common.utils.StringUtils; +import nl.basjes.parse.useragent.UserAgent; +import nl.basjes.parse.useragent.UserAgentAnalyzer; + +/** + * UserAgent解析工具类 + * + * @author roomroot + */ +public class UserAgentUtils +{ + public static final String UNKNOWN = ""; + + // 浏览器正则表达式模式 + private static final Pattern CHROME_PATTERN = Pattern.compile("Chrome/(\\d+)(?:\\.\\d+)*"); + private static final Pattern FIREFOX_PATTERN = Pattern.compile("Firefox/(\\d+)(?:\\.\\d+)*"); + private static final Pattern EDGE_PATTERN = Pattern.compile("Edg(?:e)?/(\\d+)(?:\\.\\d+)*"); + private static final Pattern SAFARI_PATTERN = Pattern.compile("Version/(\\d+)(?:\\.\\d+)*"); + private static final Pattern OPERA_PATTERN = Pattern.compile("Opera/(\\d+)(?:\\.\\d+)*"); + private static final Pattern IE_PATTERN = Pattern.compile("(?:MSIE |Trident/.*rv:)(\\d+)(?:\\.\\d+)*"); + private static final Pattern SAMSUNG_PATTERN = Pattern.compile("SamsungBrowser/(\\d+)(?:\\.\\d+)*"); + private static final Pattern UC_PATTERN = Pattern.compile("UCBrowser/(\\d+)(?:\\.\\d+)*"); + private static final Pattern QQ_PATTERN = Pattern.compile("QQBrowser/(\\d+)(?:\\.\\d+)*"); + private static final Pattern WECHAT_PATTERN = Pattern.compile("MicroMessenger/(\\d+)(?:\\.\\d+)*"); + private static final Pattern BAIDU_PATTERN = Pattern.compile("baidubrowser/(\\d+)(?:\\.\\d+)*"); + + // 操作系统正则表达式模式 + private static final Pattern WINDOWS_PATTERN = Pattern.compile("Windows NT (\\d+\\.\\d+)"); + private static final Pattern MACOS_PATTERN = Pattern.compile("Mac OS X (\\d+[_\\d]*)"); + private static final Pattern ANDROID_PATTERN = Pattern.compile("Android (\\d+)(?:\\.\\d+)*"); + private static final Pattern IOS_PATTERN = Pattern.compile("OS[\\s_](\\d+)(?:_\\d+)*"); + private static final Pattern LINUX_PATTERN = Pattern.compile("Linux"); + private static final Pattern CHROMEOS_PATTERN = Pattern.compile("CrOS"); + + private static final UserAgentAnalyzer userAgentAnalyzer = UserAgentAnalyzer + .newBuilder().hideMatcherLoadStats() + .withCache(5000) + .showMinimalVersion() + .withField(UserAgent.AGENT_NAME_VERSION) + .withField(UserAgent.OPERATING_SYSTEM_NAME_VERSION) + .build(); + + /** + * 获取客户端浏览器 + */ + public static String getBrowser(String userAgent) + { + UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent); + String agentNameVersion = iua.get(UserAgent.AGENT_NAME_VERSION).getValue(); + if (StringUtils.isBlank(agentNameVersion) || agentNameVersion.contains("??")) + { + return formatBrowser(userAgent); + } + return agentNameVersion; + } + + /** + * 获取客户端操作系统 + */ + public static String getOperatingSystem(String userAgent) + { + UserAgent.ImmutableUserAgent iua = userAgentAnalyzer.parse(userAgent); + String operatingSystemNameVersion = iua.get(UserAgent.OPERATING_SYSTEM_NAME_VERSION).getValue(); + if (StringUtils.isBlank(operatingSystemNameVersion) || operatingSystemNameVersion.contains("??")) + { + return formatOperatingSystem(userAgent); + } + return operatingSystemNameVersion; + } + + /** + * 全面浏览器检测 + */ + private static String formatBrowser(String browser) + { + // Chrome系列浏览器 + Matcher chromeMatcher = CHROME_PATTERN.matcher(browser); + if (chromeMatcher.find() && (browser.contains("Chrome") || browser.contains("CriOS"))) + { + return "Chrome" + chromeMatcher.group(1); + } + // Firefox + Matcher firefoxMatcher = FIREFOX_PATTERN.matcher(browser); + if (firefoxMatcher.find()) + { + return "Firefox" + firefoxMatcher.group(1); + } + // Edge浏览器 + Matcher edgeMatcher = EDGE_PATTERN.matcher(browser); + if (edgeMatcher.find()) + { + return "Edge" + edgeMatcher.group(1); + } + // Safari浏览器(需排除Chrome) + Matcher safariMatcher = SAFARI_PATTERN.matcher(browser); + if (safariMatcher.find() && !browser.contains("Chrome")) + { + return "Safari" + safariMatcher.group(1); + } + // 微信内置浏览器 + Matcher wechatMatcher = WECHAT_PATTERN.matcher(browser); + if (wechatMatcher.find()) + { + return "WeChat" + wechatMatcher.group(1); + } + // UC浏览器 + Matcher ucMatcher = UC_PATTERN.matcher(browser); + if (ucMatcher.find()) + { + return "UC Browser" + ucMatcher.group(1); + } + // QQ浏览器 + Matcher qqMatcher = QQ_PATTERN.matcher(browser); + if (qqMatcher.find()) + { + return "QQ Browser" + qqMatcher.group(1); + } + // 百度浏览器 + Matcher baiduMatcher = BAIDU_PATTERN.matcher(browser); + if (baiduMatcher.find()) + { + return "Baidu Browser" + baiduMatcher.group(1); + } + // Samsung浏览器 + Matcher samsungMatcher = SAMSUNG_PATTERN.matcher(browser); + if (samsungMatcher.find()) + { + return "Samsung Browser" + samsungMatcher.group(1); + } + // Opera浏览器 + Matcher operaMatcher = OPERA_PATTERN.matcher(browser); + if (operaMatcher.find()) + { + return "Opera" + operaMatcher.group(1); + } + // IE浏览器 + Matcher ieMatcher = IE_PATTERN.matcher(browser); + if (ieMatcher.find()) + { + return "Internet Explorer" + ieMatcher.group(1); + } + return UNKNOWN; + } + + /** + * 检测操作系统 + */ + private static String formatOperatingSystem(String operatingSystem) + { + // Windows系统 + Matcher windowsMatcher = WINDOWS_PATTERN.matcher(operatingSystem); + if (windowsMatcher.find()) + { + return "Windows" + getWindowsVersionDisplay(windowsMatcher.group(1)); + } + // macOS系统 + Matcher macMatcher = MACOS_PATTERN.matcher(operatingSystem); + if (macMatcher.find()) + { + String version = macMatcher.group(1).replace("_", "."); + return "macOS" + extractMajorVersion(version); + } + // Android系统 + Matcher androidMatcher = ANDROID_PATTERN.matcher(operatingSystem); + if (androidMatcher.find()) + { + return "Android" + extractMajorVersion(androidMatcher.group(1)); + } + // iOS系统 + Matcher iosMatcher = IOS_PATTERN.matcher(operatingSystem); + if (iosMatcher.find() && (operatingSystem.contains("iPhone") || operatingSystem.contains("iPad"))) + { + return "iOS" + extractMajorVersion(iosMatcher.group(1)); + } + // Linux系统 + if (LINUX_PATTERN.matcher(operatingSystem).find() && !operatingSystem.contains("Android")) + { + return "Linux"; + } + // Chrome OS + if (CHROMEOS_PATTERN.matcher(operatingSystem).find()) + { + return "Chrome OS"; + } + return UNKNOWN; + } + + /** + * 提取优化的主版本号 + */ + private static String extractMajorVersion(String fullVersion) + { + if (StringUtils.isEmpty(fullVersion)) + { + return StringUtils.EMPTY; + } + try + { + // 清理版本号中的非数字字符 + String cleanVersion = fullVersion.replaceAll("[^0-9.]", ""); + String[] parts = cleanVersion.split("\\."); + if (parts.length > 0) + { + String firstPart = parts[0]; + if (firstPart.matches("\\d+")) + { + int version = Integer.parseInt(firstPart); + + // 处理三位数版本号(如142 -> 14) + if (version >= 100) + { + return String.valueOf(version / 10); + } + return firstPart; + } + } + } + catch (NumberFormatException e) + { + // 版本号解析失败,返回原始值 + } + return fullVersion; + } + + /** + * Windows版本号显示优化 + */ + private static String getWindowsVersionDisplay(String version) + { + switch (version) + { + case "10.0": + return "10"; + case "6.3": + return "8.1"; + case "6.2": + return "8"; + case "6.1": + return "7"; + case "6.0": + return "Vista"; + case "5.1": + return "XP"; + case "5.0": + return "2000"; + default: + return extractMajorVersion(version); + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ip/AddressUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ip/AddressUtils.java new file mode 100644 index 000000000..f462e8c5a --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ip/AddressUtils.java @@ -0,0 +1,56 @@ +package com.roomroot.common.utils.ip; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.roomroot.common.config.RoomRootConfig; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.http.HttpUtils; + +/** + * 获取地址类 + * + * @author roomroot + */ +public class AddressUtils +{ + private static final Logger log = LoggerFactory.getLogger(AddressUtils.class); + + // IP地址查询 + public static final String IP_URL = "https://whois.pconline.com.cn/ipJson.jsp"; + + // 未知地址 + public static final String UNKNOWN = "XX XX"; + + public static String getRealAddressByIP(String ip) + { + // 内网不查询 + if (IpUtils.internalIp(ip)) + { + return "内网IP"; + } + if (RoomRootConfig.isAddressEnabled()) + { + try + { + String rspStr = HttpUtils.sendGet(IP_URL, "ip=" + ip + "&json=true", Constants.GBK); + if (StringUtils.isEmpty(rspStr)) + { + log.error("获取地理位置异常 {}", ip); + return UNKNOWN; + } + JSONObject obj = JSON.parseObject(rspStr); + String region = obj.getString("pro"); + String city = obj.getString("city"); + return String.format("%s %s", region, city); + } + catch (Exception e) + { + log.error("获取地理位置异常 {}", ip); + } + } + return UNKNOWN; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ip/IpUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ip/IpUtils.java new file mode 100644 index 000000000..0a0aab955 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/ip/IpUtils.java @@ -0,0 +1,382 @@ +package com.roomroot.common.utils.ip; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import jakarta.servlet.http.HttpServletRequest; +import com.roomroot.common.utils.ServletUtils; +import com.roomroot.common.utils.StringUtils; + +/** + * 获取IP方法 + * + * @author roomroot + */ +public class IpUtils +{ + public final static String REGX_0_255 = "(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)"; + // 匹配 ip + public final static String REGX_IP = "((" + REGX_0_255 + "\\.){3}" + REGX_0_255 + ")"; + public final static String REGX_IP_WILDCARD = "(((\\*\\.){3}\\*)|(" + REGX_0_255 + "(\\.\\*){3})|(" + REGX_0_255 + "\\." + REGX_0_255 + ")(\\.\\*){2}" + "|((" + REGX_0_255 + "\\.){3}\\*))"; + // 匹配网段 + public final static String REGX_IP_SEG = "(" + REGX_IP + "\\-" + REGX_IP + ")"; + + /** + * 获取客户端IP + * + * @return IP地址 + */ + public static String getIpAddr() + { + return getIpAddr(ServletUtils.getRequest()); + } + + /** + * 获取客户端IP + * + * @param request 请求对象 + * @return IP地址 + */ + public static String getIpAddr(HttpServletRequest request) + { + if (request == null) + { + return "unknown"; + } + String ip = request.getHeader("x-forwarded-for"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Forwarded-For"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getHeader("X-Real-IP"); + } + + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) + { + ip = request.getRemoteAddr(); + } + + return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param ip IP地址 + * @return 结果 + */ + public static boolean internalIp(String ip) + { + byte[] addr = textToNumericFormatV4(ip); + return internalIp(addr) || "127.0.0.1".equals(ip); + } + + /** + * 检查是否为内部IP地址 + * + * @param addr byte地址 + * @return 结果 + */ + private static boolean internalIp(byte[] addr) + { + if (StringUtils.isNull(addr) || addr.length < 2) + { + return true; + } + final byte b0 = addr[0]; + final byte b1 = addr[1]; + // 10.x.x.x/8 + final byte SECTION_1 = 0x0A; + // 172.16.x.x/12 + final byte SECTION_2 = (byte) 0xAC; + final byte SECTION_3 = (byte) 0x10; + final byte SECTION_4 = (byte) 0x1F; + // 192.168.x.x/16 + final byte SECTION_5 = (byte) 0xC0; + final byte SECTION_6 = (byte) 0xA8; + switch (b0) + { + case SECTION_1: + return true; + case SECTION_2: + if (b1 >= SECTION_3 && b1 <= SECTION_4) + { + return true; + } + case SECTION_5: + switch (b1) + { + case SECTION_6: + return true; + } + default: + return false; + } + } + + /** + * 将IPv4地址转换成字节 + * + * @param text IPv4地址 + * @return byte 字节 + */ + public static byte[] textToNumericFormatV4(String text) + { + if (text.length() == 0) + { + return null; + } + + byte[] bytes = new byte[4]; + String[] elements = text.split("\\.", -1); + try + { + long l; + int i; + switch (elements.length) + { + case 1: + l = Long.parseLong(elements[0]); + if ((l < 0L) || (l > 4294967295L)) + { + return null; + } + bytes[0] = (byte) (int) (l >> 24 & 0xFF); + bytes[1] = (byte) (int) ((l & 0xFFFFFF) >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 2: + l = Integer.parseInt(elements[0]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[0] = (byte) (int) (l & 0xFF); + l = Integer.parseInt(elements[1]); + if ((l < 0L) || (l > 16777215L)) + { + return null; + } + bytes[1] = (byte) (int) (l >> 16 & 0xFF); + bytes[2] = (byte) (int) ((l & 0xFFFF) >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 3: + for (i = 0; i < 2; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + l = Integer.parseInt(elements[2]); + if ((l < 0L) || (l > 65535L)) + { + return null; + } + bytes[2] = (byte) (int) (l >> 8 & 0xFF); + bytes[3] = (byte) (int) (l & 0xFF); + break; + case 4: + for (i = 0; i < 4; ++i) + { + l = Integer.parseInt(elements[i]); + if ((l < 0L) || (l > 255L)) + { + return null; + } + bytes[i] = (byte) (int) (l & 0xFF); + } + break; + default: + return null; + } + } + catch (NumberFormatException e) + { + return null; + } + return bytes; + } + + /** + * 获取IP地址 + * + * @return 本地IP地址 + */ + public static String getHostIp() + { + try + { + return InetAddress.getLocalHost().getHostAddress(); + } + catch (UnknownHostException e) + { + } + return "127.0.0.1"; + } + + /** + * 获取主机名 + * + * @return 本地主机名 + */ + public static String getHostName() + { + try + { + return InetAddress.getLocalHost().getHostName(); + } + catch (UnknownHostException e) + { + } + return "未知"; + } + + /** + * 从多级反向代理中获得第一个非unknown IP地址 + * + * @param ip 获得的IP地址 + * @return 第一个非unknown IP地址 + */ + public static String getMultistageReverseProxyIp(String ip) + { + // 多级反向代理检测 + if (ip != null && ip.indexOf(",") > 0) + { + final String[] ips = ip.trim().split(","); + for (String subIp : ips) + { + if (false == isUnknown(subIp)) + { + ip = subIp; + break; + } + } + } + return StringUtils.substring(ip, 0, 255); + } + + /** + * 检测给定字符串是否为未知,多用于检测HTTP请求相关 + * + * @param checkString 被检测的字符串 + * @return 是否未知 + */ + public static boolean isUnknown(String checkString) + { + return StringUtils.isBlank(checkString) || "unknown".equalsIgnoreCase(checkString); + } + + /** + * 是否为IP + */ + public static boolean isIP(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP); + } + + /** + * 是否为IP,或 *为间隔的通配符地址 + */ + public static boolean isIpWildCard(String ip) + { + return StringUtils.isNotBlank(ip) && ip.matches(REGX_IP_WILDCARD); + } + + /** + * 检测参数是否在ip通配符里 + */ + public static boolean ipIsInWildCardNoCheck(String ipWildCard, String ip) + { + String[] s1 = ipWildCard.split("\\."); + String[] s2 = ip.split("\\."); + boolean isMatchedSeg = true; + for (int i = 0; i < s1.length && !s1[i].equals("*"); i++) + { + if (!s1[i].equals(s2[i])) + { + isMatchedSeg = false; + break; + } + } + return isMatchedSeg; + } + + /** + * 是否为特定格式如:“10.10.10.1-10.10.10.99”的ip段字符串 + */ + public static boolean isIPSegment(String ipSeg) + { + return StringUtils.isNotBlank(ipSeg) && ipSeg.matches(REGX_IP_SEG); + } + + /** + * 判断ip是否在指定网段中 + */ + public static boolean ipIsInNetNoCheck(String iparea, String ip) + { + int idx = iparea.indexOf('-'); + String[] sips = iparea.substring(0, idx).split("\\."); + String[] sipe = iparea.substring(idx + 1).split("\\."); + String[] sipt = ip.split("\\."); + long ips = 0L, ipe = 0L, ipt = 0L; + for (int i = 0; i < 4; ++i) + { + ips = ips << 8 | Integer.parseInt(sips[i]); + ipe = ipe << 8 | Integer.parseInt(sipe[i]); + ipt = ipt << 8 | Integer.parseInt(sipt[i]); + } + if (ips > ipe) + { + long t = ips; + ips = ipe; + ipe = t; + } + return ips <= ipt && ipt <= ipe; + } + + /** + * 校验ip是否符合过滤串规则 + * + * @param filter 过滤IP列表,支持后缀'*'通配,支持网段如:`10.10.10.1-10.10.10.99` + * @param ip 校验IP地址 + * @return boolean 结果 + */ + public static boolean isMatchedIp(String filter, String ip) + { + if (StringUtils.isEmpty(filter) || StringUtils.isEmpty(ip)) + { + return false; + } + String[] ips = filter.split(";"); + for (String iStr : ips) + { + if (isIP(iStr) && iStr.equals(ip)) + { + return true; + } + else if (isIpWildCard(iStr) && ipIsInWildCardNoCheck(iStr, ip)) + { + return true; + } + else if (isIPSegment(iStr) && ipIsInNetNoCheck(iStr, ip)) + { + return true; + } + } + return false; + } +} \ No newline at end of file diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/poi/ExcelHandlerAdapter.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/poi/ExcelHandlerAdapter.java new file mode 100644 index 000000000..4ad72e723 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/poi/ExcelHandlerAdapter.java @@ -0,0 +1,24 @@ +package com.roomroot.common.utils.poi; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Workbook; + +/** + * Excel数据格式处理适配器 + * + * @author roomroot + */ +public interface ExcelHandlerAdapter +{ + /** + * 格式化 + * + * @param value 单元格数据值 + * @param args excel注解args参数组 + * @param cell 单元格对象 + * @param wb 工作簿对象 + * + * @return 处理后的值 + */ + Object format(Object value, String[] args, Cell cell, Workbook wb); +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/poi/ExcelSheet.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/poi/ExcelSheet.java new file mode 100644 index 000000000..dd7406843 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/poi/ExcelSheet.java @@ -0,0 +1,85 @@ +package com.roomroot.common.utils.poi; + +import java.util.ArrayList; +import java.util.List; + +/** + * 多 Sheet 导出时的数据信息 + * + * 使用示例: + *

+ *   List> sheets = new ArrayList<>();
+ *   sheets.add(new ExcelSheet<>("参数数据", configList, Config.class, "参数信息"));
+ *   sheets.add(new ExcelSheet<>("岗位数据", postList, Post.class, "岗位信息"));
+ *   return ExcelUtil.exportMultiSheet(sheets);
+ * 
+ * + * @author roomroot + */ +public class ExcelSheet +{ + /** Sheet 名称 */ + private String sheetName; + + /** 导出数据集合 */ + private List list; + + /** 数据对应的实体 Class */ + private Class clazz; + + /** Sheet 顶部大标题(可为空) */ + private String title; + + public ExcelSheet(String sheetName, List list, Class clazz) + { + this(sheetName, list, clazz, ""); + } + + public ExcelSheet(String sheetName, List list, Class clazz, String title) + { + this.sheetName = sheetName; + this.list = list != null ? list : new ArrayList<>(); + this.clazz = clazz; + this.title = title != null ? title : ""; + } + + public String getSheetName() + { + return sheetName; + } + + public List getList() + { + return list; + } + + public Class getClazz() + { + return clazz; + } + + public String getTitle() + { + return title; + } + + public void setSheetName(String sheetName) + { + this.sheetName = sheetName; + } + + public void setList(List list) + { + this.list = list; + } + + public void setClazz(Class clazz) + { + this.clazz = clazz; + } + + public void setTitle(String title) + { + this.title = title; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/poi/ExcelUtil.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/poi/ExcelUtil.java new file mode 100644 index 000000000..0ba07a38c --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/poi/ExcelUtil.java @@ -0,0 +1,2067 @@ +package com.roomroot.common.utils.poi; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.RegExUtils; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.poi.hssf.usermodel.HSSFClientAnchor; +import org.apache.poi.hssf.usermodel.HSSFPicture; +import org.apache.poi.hssf.usermodel.HSSFPictureData; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ooxml.POIXMLDocumentPart; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.ClientAnchor; +import org.apache.poi.ss.usermodel.DataFormat; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.util.IOUtils; +import org.apache.poi.xssf.streaming.SXSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFClientAnchor; +import org.apache.poi.xssf.usermodel.XSSFDataValidation; +import org.apache.poi.xssf.usermodel.XSSFDrawing; +import org.apache.poi.xssf.usermodel.XSSFPicture; +import org.apache.poi.xssf.usermodel.XSSFShape; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.annotation.Excel.ColumnType; +import com.roomroot.common.annotation.Excel.Type; +import com.roomroot.common.annotation.Excels; +import com.roomroot.common.config.RoomRootConfig; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.exception.UtilException; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.DictUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.file.FileTypeUtils; +import com.roomroot.common.utils.file.FileUtils; +import com.roomroot.common.utils.file.ImageUtils; +import com.roomroot.common.utils.reflect.ReflectUtils; + +/** + * Excel相关处理 + * + * @author roomroot + */ +public class ExcelUtil +{ + private static final Logger log = LoggerFactory.getLogger(ExcelUtil.class); + + public static final String SEPARATOR = ","; + + public static final String FORMULA_REGEX_STR = "=|-|\\+|@"; + + public static final String[] FORMULA_STR = { "=", "-", "+", "@" }; + + /** + * 用于dictType属性数据存储,避免重复查缓存 + */ + public Map sysDictMap = new HashMap(); + + /** + * 单元格样式缓存 + */ + private Map cellStyleCache = new HashMap(); + + /** + * Excel sheet最大行数,默认65536 + */ + public static final int sheetSize = 65536; + + /** + * 工作表名称 + */ + private String sheetName; + + /** + * 导出类型(EXPORT:导出数据;IMPORT:导入模板) + */ + private Type type; + + /** + * 工作薄对象 + */ + private Workbook wb; + + /** + * 工作表对象 + */ + private Sheet sheet; + + /** + * 样式列表 + */ + private Map styles; + + /** + * 导入导出数据列表 + */ + private List list; + + /** + * 注解列表 + */ + private List fields; + + /** + * 当前行号 + */ + private int rownum; + + /** + * 标题 + */ + private String title; + + /** + * 最大高度 + */ + private short maxHeight; + + /** + * 合并后最后行数 + */ + private int subMergedLastRowNum = 0; + + /** + * 合并后开始行数 + */ + private int subMergedFirstRowNum = 1; + + /** + * 对象的子列表方法 + */ + private Map subMethods; + + /** + * 对象的子列表属性 + */ + private Map> subFieldsMap; + + /** + * 统计列表 + */ + private Map statistics = new HashMap(); + + /** + * 实体对象 + */ + public Class clazz; + + /** + * 需要显示列属性 + */ + public String[] includeFields; + + /** + * 需要排除列属性 + */ + public String[] excludeFields; + + public ExcelUtil(Class clazz) + { + this.clazz = clazz; + } + + /** + * 仅在Excel中显示列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + */ + public void showColumn(String... fields) + { + this.includeFields = fields; + } + + /** + * 隐藏Excel中列属性 + * + * @param fields 列属性名 示例[单个"name"/多个"id","name"] + */ + public void hideColumn(String... fields) + { + this.excludeFields = fields; + } + + public void init(List list, String sheetName, String title, Type type) + { + if (list == null) + { + list = new ArrayList(); + } + this.list = list; + this.sheetName = sheetName; + this.type = type; + this.title = title; + createExcelField(); + createWorkbook(); + createTitle(); + createSubHead(); + } + + /** + * 创建excel第一行标题 + */ + public void createTitle() + { + if (StringUtils.isNotEmpty(title)) + { + int titleLastCol = this.fields.size() - 1; + if (isSubList()) + { + for (List currentSubFields : subFieldsMap.values()) + { + titleLastCol = titleLastCol + currentSubFields.size() - 1; + } + } + Row titleRow = sheet.createRow(rownum == 0 ? rownum++ : 0); + titleRow.setHeightInPoints(30); + Cell titleCell = titleRow.createCell(0); + titleCell.setCellStyle(styles.get("title")); + titleCell.setCellValue(title); + sheet.addMergedRegion(new CellRangeAddress(titleRow.getRowNum(), titleRow.getRowNum(), 0, titleLastCol)); + } + } + + /** + * 创建对象的子列表名称 + */ + public void createSubHead() + { + if (isSubList()) + { + Row subRow = sheet.createRow(rownum); + int column = 0; + for (Object[] objects : fields) + { + Field field = (Field) objects[0]; + Excel attr = (Excel) objects[1]; + CellStyle cellStyle = styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor())); + if (Collection.class.isAssignableFrom(field.getType())) + { + Cell cell = subRow.createCell(column); + cell.setCellValue(attr.name()); + cell.setCellStyle(cellStyle); + int subFieldSize = subFieldsMap != null ? subFieldsMap.get(field.getName()).size() : 0; + if (subFieldSize > 1) + { + CellRangeAddress cellAddress = new CellRangeAddress(rownum, rownum, column, column + subFieldSize - 1); + sheet.addMergedRegion(cellAddress); + } + column += subFieldSize; + } + else + { + Cell cell = subRow.createCell(column++); + cell.setCellValue(attr.name()); + cell.setCellStyle(cellStyle); + } + } + rownum++; + } + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(InputStream is) + { + return importExcel(is, 0); + } + + /** + * 对excel表单默认第一个索引名转换成list + * + * @param is 输入流 + * @param titleNum 标题占用行数 + * @return 转换后集合 + */ + public List importExcel(InputStream is, int titleNum) + { + List list = null; + try + { + list = importExcel(StringUtils.EMPTY, is, titleNum); + } + catch (Exception e) + { + log.error("导入Excel异常{}", e.getMessage()); + throw new UtilException(e.getMessage()); + } + finally + { + IOUtils.closeQuietly(is); + } + return list; + } + + /** + * 对excel表单指定表格索引名转换成list + * + * @param sheetName 表格索引名 + * @param titleNum 标题占用行数 + * @param is 输入流 + * @return 转换后集合 + */ + public List importExcel(String sheetName, InputStream is, int titleNum) throws Exception + { + this.type = Type.IMPORT; + this.wb = WorkbookFactory.create(is); + List list = new ArrayList(); + // 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet + Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0); + if (sheet == null) + { + throw new IOException("文件sheet不存在"); + } + boolean isXSSFWorkbook = !(wb instanceof HSSFWorkbook); + Map> pictures = null; + if (isXSSFWorkbook) + { + pictures = getSheetPictures07((XSSFSheet) sheet, (XSSFWorkbook) wb); + } + else + { + pictures = getSheetPictures03((HSSFSheet) sheet, (HSSFWorkbook) wb); + } + // 获取最后一个非空行的行下标,比如总行数为n,则返回的为n-1 + int rows = sheet.getLastRowNum(); + if (rows > 0) + { + // 定义一个map用于存放excel列的序号和field. + Map cellMap = new HashMap(); + // 获取表头 + Row heard = sheet.getRow(titleNum); + if (heard == null) + { + throw new UtilException("文件标题行为空,请检查Excel文件格式"); + } + for (int i = 0; i < heard.getLastCellNum(); i++) + { + Cell cell = heard.getCell(i); + if (StringUtils.isNotNull(cell)) + { + String value = this.getCellValue(heard, i).toString(); + cellMap.put(value, i); + } + } + // 有数据时才处理 得到类的所有field. + List fields = this.getFields(); + Map fieldsMap = new HashMap(); + for (Object[] objects : fields) + { + Excel attr = (Excel) objects[1]; + Integer column = cellMap.get(attr.name()); + if (column != null) + { + fieldsMap.put(column, objects); + } + } + for (int i = titleNum + 1; i <= rows; i++) + { + // 从第2行开始取数据,默认第一行是表头. + Row row = sheet.getRow(i); + // 判断当前行是否是空行 + if (isRowEmpty(row)) + { + continue; + } + T entity = null; + for (Map.Entry entry : fieldsMap.entrySet()) + { + Object val = this.getCellValue(row, entry.getKey()); + + // 如果不存在实例则新建. + entity = (entity == null ? clazz.getDeclaredConstructor().newInstance() : entity); + // 从map中得到对应列的field. + Field field = (Field) entry.getValue()[0]; + Excel attr = (Excel) entry.getValue()[1]; + // 取得类型,并根据对象类型设置值. + Class fieldType = field.getType(); + if (String.class == fieldType) + { + String s = Convert.toStr(val); + if (s.matches("^\\d+\\.0$")) + { + val = StringUtils.substringBefore(s, ".0"); + } + else + { + String dateFormat = field.getAnnotation(Excel.class).dateFormat(); + if (StringUtils.isNotEmpty(dateFormat)) + { + val = parseDateToStr(dateFormat, val); + } + else + { + val = Convert.toStr(val); + } + } + } + else if ((Integer.TYPE == fieldType || Integer.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toInt(val); + } + else if ((Long.TYPE == fieldType || Long.class == fieldType) && StringUtils.isNumeric(Convert.toStr(val))) + { + val = Convert.toLong(val); + } + else if (Double.TYPE == fieldType || Double.class == fieldType) + { + val = Convert.toDouble(val); + } + else if (Float.TYPE == fieldType || Float.class == fieldType) + { + val = Convert.toFloat(val); + } + else if (BigDecimal.class == fieldType) + { + val = Convert.toBigDecimal(val); + } + else if (Date.class == fieldType) + { + if (val instanceof String) + { + val = DateUtils.parseDate(val); + } + else if (val instanceof Double) + { + val = DateUtil.getJavaDate((Double) val); + } + } + else if (Boolean.TYPE == fieldType || Boolean.class == fieldType) + { + val = Convert.toBool(val, false); + } + if (StringUtils.isNotNull(fieldType)) + { + String propertyName = field.getName(); + if (StringUtils.isNotEmpty(attr.targetAttr())) + { + propertyName = field.getName() + "." + attr.targetAttr(); + } + if (StringUtils.isNotEmpty(attr.readConverterExp())) + { + val = reverseByExp(Convert.toStr(val), attr.readConverterExp(), attr.separator()); + } + else if (StringUtils.isNotEmpty(attr.dictType())) + { + if (!sysDictMap.containsKey(attr.dictType() + val)) + { + String dictValue = reverseDictByExp(Convert.toStr(val), attr.dictType(), attr.separator()); + sysDictMap.put(attr.dictType() + val, dictValue); + } + val = sysDictMap.get(attr.dictType() + val); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + val = dataFormatHandlerAdapter(val, attr, null); + } + else if (ColumnType.IMAGE == attr.cellType() && StringUtils.isNotEmpty(pictures)) + { + StringBuilder propertyString = new StringBuilder(); + List images = pictures.get(row.getRowNum() + "_" + entry.getKey()); + for (PictureData picture : images) + { + byte[] data = picture.getData(); + String fileName = FileUtils.writeImportBytes(data); + propertyString.append(fileName).append(SEPARATOR); + } + val = StringUtils.stripEnd(propertyString.toString(), SEPARATOR); + } + ReflectUtils.invokeSetter(entity, propertyName, val); + } + } + list.add(entity); + } + } + return list; + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName) + { + return exportExcel(list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult exportExcel(List list, String sheetName, String title) + { + this.init(list, sheetName, title, Type.EXPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName) + { + exportExcel(response, list, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param response 返回数据 + * @param list 导出数据集合 + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void exportExcel(HttpServletResponse response, List list, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(list, sheetName, title, Type.EXPORT); + exportExcel(response); + } + + /** + * 多 Sheet 导出 —— 将多个不同类型的数据集合写入同一 Excel,直接输出到 HttpServletResponse + * + * @param response HTTP 响应 + * @param sheets Sheet 描述列表 + */ + public static void exportMultiSheet(HttpServletResponse response, List> sheets) + { + if (sheets == null || sheets.isEmpty()) + { + return; + } + SXSSFWorkbook wb = buildWorkbook(sheets); + try + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + wb.write(response.getOutputStream()); + } + catch (Exception e) + { + log.error("多Sheet导出Excel异常{}", e.getMessage()); + } + finally + { + IOUtils.closeQuietly(wb); + } + } + + /** + * 多 Sheet 导出 —— 将多个不同类型的数据集合写入同一 Excel,生成文件并返回下载地址 + * + * @param sheets Sheet 描述列表 + * @return AjaxResult(含文件下载地址) + */ + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static AjaxResult exportMultiSheet(List> sheets) + { + if (sheets == null || sheets.isEmpty()) + { + return AjaxResult.error("导出数据不能为空"); + } + SXSSFWorkbook wb = buildWorkbook(sheets); + OutputStream out = null; + try + { + ExcelUtil firstUtil = new ExcelUtil(sheets.get(0).getClazz()); + String filename = firstUtil.encodingFilename(sheets.get(0).getSheetName()); + out = new FileOutputStream(firstUtil.getAbsoluteFile(filename)); + wb.write(out); + return AjaxResult.success(filename); + } + catch (Exception e) + { + log.error("多Sheet导出Excel异常{}", e.getMessage()); + throw new UtilException("导出Excel失败,请联系网站管理员!"); + } + finally + { + IOUtils.closeQuietly(wb); + IOUtils.closeQuietly(out); + } + } + + /** + * 构建多 Sheet Workbook —— 创建 SXSSFWorkbook 并将所有 Sheet 数据写入 + * + * @param sheets Sheet 描述列表 + * @return 已写入所有 Sheet 数据的 SXSSFWorkbook + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static SXSSFWorkbook buildWorkbook(List> sheets) + { + SXSSFWorkbook wb = new SXSSFWorkbook(500); + for (ExcelSheet excelSheet : sheets) + { + ExcelUtil util = new ExcelUtil(excelSheet.getClazz()); + util.initWithWorkbook(wb, excelSheet.getList(), excelSheet.getSheetName(), excelSheet.getTitle()); + util.writeSheet(); + } + return wb; + } + + /** + * 使用外部传入的 Workbook 初始化(多 Sheet 导出专用) + * 与 init() 的区别:不新建 Workbook,而是在已有 wb 上追加新 Sheet + * + * @param wb 已有工作簿 + * @param list 数据集合 + * @param sheetName Sheet 名称 + * @param title 大标题(可为空) + */ + public void initWithWorkbook(SXSSFWorkbook wb, List list, String sheetName, String title) + { + if (list == null) + { + list = new ArrayList(); + } + this.list = list; + this.sheetName = sheetName; + this.title = title != null ? title : ""; + this.type = Type.EXPORT; + this.rownum = 0; + this.wb = wb; + this.sheet = wb.createSheet(sheetName); + createExcelField(); + this.styles = createStyles(wb); + createTitle(); + createSubHead(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName) + { + return importTemplateExcel(sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public AjaxResult importTemplateExcel(String sheetName, String title) + { + this.init(null, sheetName, title, Type.IMPORT); + return exportExcel(); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName) + { + importTemplateExcel(response, sheetName, StringUtils.EMPTY); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @param sheetName 工作表的名称 + * @param title 标题 + * @return 结果 + */ + public void importTemplateExcel(HttpServletResponse response, String sheetName, String title) + { + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setCharacterEncoding("utf-8"); + this.init(null, sheetName, title, Type.IMPORT); + exportExcel(response); + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public void exportExcel(HttpServletResponse response) + { + try + { + writeSheet(); + wb.write(response.getOutputStream()); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + } + finally + { + IOUtils.closeQuietly(wb); + } + } + + /** + * 对list数据源将其里面的数据导入到excel表单 + * + * @return 结果 + */ + public AjaxResult exportExcel() + { + OutputStream out = null; + try + { + writeSheet(); + String filename = encodingFilename(sheetName); + out = new FileOutputStream(getAbsoluteFile(filename)); + wb.write(out); + return AjaxResult.success(filename); + } + catch (Exception e) + { + log.error("导出Excel异常{}", e.getMessage()); + throw new UtilException("导出Excel失败,请联系网站管理员!"); + } + finally + { + IOUtils.closeQuietly(wb); + IOUtils.closeQuietly(out); + } + } + + /** + * 创建写入数据到Sheet + */ + public void writeSheet() + { + // 取出一共有多少个sheet. + int sheetNo = Math.max(1, (int) Math.ceil(list.size() * 1.0 / sheetSize)); + for (int index = 0; index < sheetNo; index++) + { + createSheet(sheetNo, index); + + // 产生一行 + Row row = sheet.createRow(rownum); + int column = 0; + // 写入各个字段的列头名称 + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + List currentSubFields = subFieldsMap.get(field.getName()); + for (Field subField : currentSubFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + this.createHeadCell(subExcel, row, column++); + } + } + else + { + this.createHeadCell(excel, row, column++); + } + } + if (Type.EXPORT.equals(type)) + { + fillExcelData(index); + addStatisticsRow(); + } + } + } + + /** + * 填充excel数据 + * + * @param index 序号 + */ + @SuppressWarnings("unchecked") + public void fillExcelData(int index) + { + int startNo = index * sheetSize; + int endNo = Math.min(startNo + sheetSize, list.size()); + int currentRowNum = rownum + 1; // 从标题行后开始 + + for (int i = startNo; i < endNo; i++) + { + Row row = sheet.createRow(currentRowNum); + T vo = (T) list.get(i); + int column = 0; + int maxSubListSize = getCurrentMaxSubListSize(vo); + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + try + { + Collection subList = (Collection) getTargetValue(vo, field, excel); + List currentSubFields = subFieldsMap.get(field.getName()); + if (subList != null && !subList.isEmpty()) + { + int subIndex = 0; + for (Object subVo : subList) + { + Row subRow = sheet.getRow(currentRowNum + subIndex); + if (subRow == null) + { + subRow = sheet.createRow(currentRowNum + subIndex); + } + + int subColumn = column; + for (Field subField : currentSubFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + addCell(subExcel, subRow, (T) subVo, subField, subColumn++); + } + subIndex++; + } + } + column += currentSubFields.size(); + } + catch (Exception e) + { + log.error("填充集合数据失败", e); + } + } + else + { + // 创建单元格并设置值 + addCell(excel, row, vo, field, column); + if (maxSubListSize > 1 && excel.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(currentRowNum, currentRowNum + maxSubListSize - 1, column, column)); + } + column++; + } + } + currentRowNum += maxSubListSize; + } + } + + /** + * 获取子列表最大数 + */ + private int getCurrentMaxSubListSize(T vo) + { + int maxSubListSize = 1; + for (Object[] os : fields) + { + Field field = (Field) os[0]; + if (Collection.class.isAssignableFrom(field.getType())) + { + try + { + Collection subList = (Collection) getTargetValue(vo, field, (Excel) os[1]); + if (subList != null && !subList.isEmpty()) + { + maxSubListSize = Math.max(maxSubListSize, subList.size()); + } + } + catch (Exception e) + { + log.error("获取集合大小失败", e); + } + } + } + return maxSubListSize; + } + + /** + * 创建表格样式 + * + * @param wb 工作薄对象 + * @return 样式列表 + */ + private Map createStyles(Workbook wb) + { + // 写入各条记录,每条记录对应excel表中的一行 + Map styles = new HashMap(); + CellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + Font titleFont = wb.createFont(); + titleFont.setFontName("Arial"); + titleFont.setFontHeightInPoints((short) 16); + titleFont.setBold(true); + style.setFont(titleFont); + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + styles.put("title", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + style.setFont(dataFont); + styles.put("data", style); + + style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setDataFormat(dataFormat.getFormat("######0.00")); + Font totalFont = wb.createFont(); + totalFont.setFontName("Arial"); + totalFont.setFontHeightInPoints((short) 10); + style.setFont(totalFont); + styles.put("total", style); + + styles.putAll(annotationHeaderStyles(wb, styles)); + + styles.putAll(annotationDataStyles(wb)); + + return styles; + } + + /** + * 根据Excel注解创建表格头样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationHeaderStyles(Workbook wb, Map styles) + { + Map headerStyles = new HashMap(); + for (Object[] os : fields) + { + Excel excel = (Excel) os[1]; + String key = StringUtils.format("header_{}_{}", excel.headerColor(), excel.headerBackgroundColor()); + if (!headerStyles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.cloneStyleFrom(styles.get("data")); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor(excel.headerBackgroundColor().index); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + Font headerFont = wb.createFont(); + headerFont.setFontName("Arial"); + headerFont.setFontHeightInPoints((short) 10); + headerFont.setBold(true); + headerFont.setColor(excel.headerColor().index); + style.setFont(headerFont); + // 设置表格头单元格文本形式 + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + headerStyles.put(key, style); + } + } + return headerStyles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param wb 工作薄对象 + * @return 自定义样式列表 + */ + private Map annotationDataStyles(Workbook wb) + { + Map styles = new HashMap(); + for (Object[] os : fields) + { + Field field = (Field) os[0]; + Excel excel = (Excel) os[1]; + if (Collection.class.isAssignableFrom(field.getType())) + { + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + List subFields = FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class); + for (Field subField : subFields) + { + Excel subExcel = subField.getAnnotation(Excel.class); + annotationDataStyles(styles, subField, subExcel); + } + } + else + { + annotationDataStyles(styles, field, excel); + } + } + return styles; + } + + /** + * 根据Excel注解创建表格列样式 + * + * @param styles 自定义样式列表 + * @param field 属性列信息 + * @param excel 注解信息 + */ + public void annotationDataStyles(Map styles, Field field, Excel excel) + { + String key = StringUtils.format("data_{}_{}_{}_{}_{}", excel.align(), excel.color(), excel.backgroundColor(), excel.cellType(), excel.wrapText()); + if (!styles.containsKey(key)) + { + CellStyle style = wb.createCellStyle(); + style.setAlignment(excel.align()); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderRight(BorderStyle.THIN); + style.setRightBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderLeft(BorderStyle.THIN); + style.setLeftBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderTop(BorderStyle.THIN); + style.setTopBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setBorderBottom(BorderStyle.THIN); + style.setBottomBorderColor(IndexedColors.GREY_50_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setFillForegroundColor(excel.backgroundColor().getIndex()); + style.setWrapText(excel.wrapText()); + Font dataFont = wb.createFont(); + dataFont.setFontName("Arial"); + dataFont.setFontHeightInPoints((short) 10); + dataFont.setColor(excel.color().index); + style.setFont(dataFont); + if (ColumnType.TEXT == excel.cellType()) + { + DataFormat dataFormat = wb.createDataFormat(); + style.setDataFormat(dataFormat.getFormat("@")); + } + styles.put(key, style); + } + } + + /** + * 创建单元格 + */ + public Cell createHeadCell(Excel attr, Row row, int column) + { + // 创建列 + Cell cell = row.createCell(column); + // 写入列信息 + cell.setCellValue(attr.name()); + setDataValidation(attr, row, column); + cell.setCellStyle(styles.get(StringUtils.format("header_{}_{}", attr.headerColor(), attr.headerBackgroundColor()))); + if (isSubList()) + { + // 填充默认样式,防止合并单元格样式失效 + sheet.setDefaultColumnStyle(column, styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText()))); + if (attr.needMerge()) + { + sheet.addMergedRegion(new CellRangeAddress(rownum - 1, rownum, column, column)); + } + } + return cell; + } + + /** + * 设置单元格信息 + * + * @param value 单元格值 + * @param attr 注解相关 + * @param cell 单元格信息 + */ + public void setCellVo(Object value, Excel attr, Cell cell) + { + if (ColumnType.STRING == attr.cellType() || ColumnType.TEXT == attr.cellType()) + { + String cellValue = Convert.toStr(value); + // 对于任何以表达式触发字符 =-+@开头的单元格,直接使用tab字符作为前缀,防止CSV注入。 + if (StringUtils.startsWithAny(cellValue, FORMULA_STR)) + { + cellValue = RegExUtils.replaceFirst(cellValue, FORMULA_REGEX_STR, "\t$0"); + } + if (value instanceof Collection && StringUtils.equals("[]", cellValue)) + { + cellValue = StringUtils.EMPTY; + } + cell.setCellValue(StringUtils.isNull(cellValue) ? attr.defaultValue() : cellValue + attr.suffix()); + } + else if (ColumnType.NUMERIC == attr.cellType()) + { + if (StringUtils.isNotNull(value)) + { + cell.setCellValue(StringUtils.contains(Convert.toStr(value), ".") ? Convert.toDouble(value) : Convert.toInt(value)); + } + } + else if (ColumnType.IMAGE == attr.cellType()) + { + ClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, (short) cell.getColumnIndex(), cell.getRow().getRowNum(), (short) (cell.getColumnIndex() + 1), cell.getRow().getRowNum() + 1); + String propertyValue = Convert.toStr(value); + if (StringUtils.isNotEmpty(propertyValue)) + { + List imagePaths = StringUtils.str2List(propertyValue, SEPARATOR); + for (String imagePath : imagePaths) + { + byte[] data = ImageUtils.getImage(imagePath); + getDrawingPatriarch(cell.getSheet()).createPicture(anchor, cell.getSheet().getWorkbook().addPicture(data, getImageType(data))); + } + } + } + } + + /** + * 获取画布 + */ + public static Drawing getDrawingPatriarch(Sheet sheet) + { + if (sheet.getDrawingPatriarch() == null) + { + sheet.createDrawingPatriarch(); + } + return sheet.getDrawingPatriarch(); + } + + /** + * 获取图片类型,设置图片插入类型 + */ + public int getImageType(byte[] value) + { + String type = FileTypeUtils.getFileExtendName(value); + if ("JPG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_JPEG; + } + else if ("PNG".equalsIgnoreCase(type)) + { + return Workbook.PICTURE_TYPE_PNG; + } + return Workbook.PICTURE_TYPE_JPEG; + } + + /** + * 创建表格样式 + */ + public void setDataValidation(Excel attr, Row row, int column) + { + if (attr.name().indexOf("注:") >= 0) + { + sheet.setColumnWidth(column, 6000); + } + else + { + // 设置列宽 + sheet.setColumnWidth(column, (int) ((attr.width() + 0.72) * 256)); + } + if (StringUtils.isNotEmpty(attr.prompt()) || attr.combo().length > 0 || attr.comboReadDict()) + { + String[] comboArray = attr.combo(); + if (attr.comboReadDict()) + { + if (!sysDictMap.containsKey("combo_" + attr.dictType())) + { + String labels = DictUtils.getDictLabels(attr.dictType()); + sysDictMap.put("combo_" + attr.dictType(), labels); + } + String val = sysDictMap.get("combo_" + attr.dictType()); + comboArray = StringUtils.split(val, DictUtils.SEPARATOR); + } + if (comboArray.length > 15 || StringUtils.join(comboArray).length() > 255) + { + // 如果下拉数大于15或字符串长度大于255,则使用一个新sheet存储,避免生成的模板下拉值获取不到 + setXSSFValidationWithHidden(sheet, comboArray, attr.prompt(), 1, 100, column, column); + } + else + { + // 提示信息或只能选择不能输入的列内容. + setPromptOrValidation(sheet, comboArray, attr.prompt(), 1, 100, column, column); + } + } + } + + /** + * 添加单元格 + */ + @SuppressWarnings("deprecation") + public Cell addCell(Excel attr, Row row, T vo, Field field, int column) + { + Cell cell = null; + try + { + // 设置行高 + row.setHeight(maxHeight); + // 根据Excel中设置情况决定是否导出,有些情况需要保持为空,希望用户填写这一列. + if (attr.isExport()) + { + // 创建cell + cell = row.createCell(column); + if (isSubListValue(vo) && getListCellValue(vo) > 1 && attr.needMerge()) + { + if (subMergedLastRowNum >= subMergedFirstRowNum) + { + sheet.addMergedRegion(new CellRangeAddress(subMergedFirstRowNum, subMergedLastRowNum, column, column)); + } + } + cell.setCellStyle(styles.get(StringUtils.format("data_{}_{}_{}_{}_{}", attr.align(), attr.color(), attr.backgroundColor(), attr.cellType(), attr.wrapText()))); + + // 用于读取对象中的属性 + Object value = getTargetValue(vo, field, attr); + String dateFormat = attr.dateFormat(); + String readConverterExp = attr.readConverterExp(); + String separator = attr.separator(); + String dictType = attr.dictType(); + if (StringUtils.isNotEmpty(dateFormat) && StringUtils.isNotNull(value)) + { + cell.setCellStyle(createCellStyle(cell.getCellStyle(), dateFormat)); + cell.setCellValue(parseDateToStr(dateFormat, value)); + } + else if (StringUtils.isNotEmpty(readConverterExp) && StringUtils.isNotNull(value)) + { + cell.setCellValue(convertByExp(Convert.toStr(value), readConverterExp, separator)); + } + else if (StringUtils.isNotEmpty(dictType) && StringUtils.isNotNull(value)) + { + if (!sysDictMap.containsKey(dictType + value)) + { + String lable = convertDictByExp(Convert.toStr(value), dictType, separator); + sysDictMap.put(dictType + value, lable); + } + cell.setCellValue(sysDictMap.get(dictType + value)); + } + else if (value instanceof BigDecimal && -1 != attr.scale()) + { + cell.setCellValue((((BigDecimal) value).setScale(attr.scale(), attr.roundingMode())).doubleValue()); + } + else if (!attr.handler().equals(ExcelHandlerAdapter.class)) + { + cell.setCellValue(dataFormatHandlerAdapter(value, attr, cell)); + } + else + { + // 设置列类型 + setCellVo(value, attr, cell); + } + addStatisticsData(column, Convert.toStr(value), attr); + } + } + catch (Exception e) + { + log.error("导出Excel失败{}", e); + } + return cell; + } + + /** + * 使用自定义格式,同时避免样式污染 + * + * @param cellStyle 从此样式复制 + * @param format 格式匹配的字符串 + * @return 格式化后CellStyle对象 + */ + private CellStyle createCellStyle(CellStyle cellStyle, String format) + { + String key = cellStyle.getIndex() + "|" + format; + CellStyle cached = cellStyleCache.get(key); + if (cached != null) + { + return cached; + } + CellStyle style = wb.createCellStyle(); + style.cloneStyleFrom(cellStyle); + style.setDataFormat(wb.getCreationHelper().createDataFormat().getFormat(format)); + cellStyleCache.put(key, style); + return style; + } + + /** + * 设置 POI XSSFSheet 单元格提示或选择框 + * + * @param sheet 表单 + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setPromptOrValidation(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, + int firstCol, int endCol) + { + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = textlist.length > 0 ? helper.createExplicitListConstraint(textlist) : helper.createCustomConstraint("DD1"); + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + sheet.addValidationData(dataValidation); + } + + /** + * 设置某些列的值只能输入预制的数据,显示下拉框(兼容超出一定数量的下拉框). + * + * @param sheet 要设置的sheet. + * @param textlist 下拉框显示的内容 + * @param promptContent 提示内容 + * @param firstRow 开始行 + * @param endRow 结束行 + * @param firstCol 开始列 + * @param endCol 结束列 + */ + public void setXSSFValidationWithHidden(Sheet sheet, String[] textlist, String promptContent, int firstRow, int endRow, int firstCol, int endCol) + { + String hideSheetName = "combo_" + firstCol + "_" + endCol; + Sheet hideSheet = null; + String hideSheetDataName = hideSheetName + "_data"; + Name name = wb.getName(hideSheetDataName); + if (name != null) + { + // 名称已存在,尝试从名称的引用中找到sheet名称 + String refersToFormula = name.getRefersToFormula(); + if (StringUtils.isNotEmpty(refersToFormula) && refersToFormula.contains("!")) + { + String sheetNameFromFormula = refersToFormula.substring(0, refersToFormula.indexOf("!")); + hideSheet = wb.getSheet(sheetNameFromFormula); + } + } + + if (hideSheet == null) + { + hideSheet = wb.createSheet(hideSheetName); // 用于存储 下拉菜单数据 + for (int i = 0; i < textlist.length; i++) + { + hideSheet.createRow(i).createCell(0).setCellValue(textlist[i]); + } + // 创建名称,可被其他单元格引用 + name = wb.createName(); + name.setNameName(hideSheetDataName); + name.setRefersToFormula(hideSheetName + "!$A$1:$A$" + textlist.length); + } + + DataValidationHelper helper = sheet.getDataValidationHelper(); + // 加载下拉列表内容 + DataValidationConstraint constraint = helper.createFormulaListConstraint(hideSheetDataName); + // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 + CellRangeAddressList regions = new CellRangeAddressList(firstRow, endRow, firstCol, endCol); + // 数据有效性对象 + DataValidation dataValidation = helper.createValidation(constraint, regions); + if (StringUtils.isNotEmpty(promptContent)) + { + // 如果设置了提示信息则鼠标放上去提示 + dataValidation.createPromptBox("", promptContent); + dataValidation.setShowPromptBox(true); + } + // 处理Excel兼容性问题 + if (dataValidation instanceof XSSFDataValidation) + { + dataValidation.setSuppressDropDownArrow(true); + dataValidation.setShowErrorBox(true); + } + else + { + dataValidation.setSuppressDropDownArrow(false); + } + + sheet.addValidationData(dataValidation); + // 设置hiddenSheet隐藏 + wb.setSheetHidden(wb.getSheetIndex(hideSheet), true); + } + + /** + * 解析导出值 0=男,1=女,2=未知 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String convertByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(SEPARATOR); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[0].equals(value)) + { + propertyString.append(itemArray[1] + separator); + break; + } + } + } + else + { + if (itemArray[0].equals(propertyValue)) + { + return itemArray[1]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 反向解析值 男=0,女=1,未知=2 + * + * @param propertyValue 参数值 + * @param converterExp 翻译注解 + * @param separator 分隔符 + * @return 解析后值 + */ + public static String reverseByExp(String propertyValue, String converterExp, String separator) + { + StringBuilder propertyString = new StringBuilder(); + String[] convertSource = converterExp.split(SEPARATOR); + for (String item : convertSource) + { + String[] itemArray = item.split("="); + if (StringUtils.containsAny(propertyValue, separator)) + { + for (String value : propertyValue.split(separator)) + { + if (itemArray[1].equals(value)) + { + propertyString.append(itemArray[0] + separator); + break; + } + } + } + else + { + if (itemArray[1].equals(propertyValue)) + { + return itemArray[0]; + } + } + } + return StringUtils.stripEnd(propertyString.toString(), separator); + } + + /** + * 解析字典值 + * + * @param dictValue 字典值 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典标签 + */ + public static String convertDictByExp(String dictValue, String dictType, String separator) + { + return DictUtils.getDictLabel(dictType, dictValue, separator); + } + + /** + * 反向解析值字典值 + * + * @param dictLabel 字典标签 + * @param dictType 字典类型 + * @param separator 分隔符 + * @return 字典值 + */ + public static String reverseDictByExp(String dictLabel, String dictType, String separator) + { + return DictUtils.getDictValue(dictType, dictLabel, separator); + } + + /** + * 数据处理器 + * + * @param value 数据值 + * @param excel 数据注解 + * @return + */ + public String dataFormatHandlerAdapter(Object value, Excel excel, Cell cell) + { + try + { + Object instance = excel.handler().getDeclaredConstructor().newInstance(); + Method formatMethod = excel.handler().getMethod("format", new Class[] { Object.class, String[].class, Cell.class, Workbook.class }); + value = formatMethod.invoke(instance, value, excel.args(), cell, this.wb); + } + catch (Exception e) + { + log.error("不能格式化数据 " + excel.handler(), e.getMessage()); + } + return Convert.toStr(value); + } + + /** + * 合计统计信息 + */ + private void addStatisticsData(Integer index, String text, Excel entity) + { + if (entity != null && entity.isStatistics()) + { + Double temp = 0D; + if (!statistics.containsKey(index)) + { + statistics.put(index, temp); + } + try + { + temp = Double.valueOf(text); + } + catch (NumberFormatException e) + { + } + statistics.put(index, statistics.get(index) + temp); + } + } + + /** + * 创建统计行 + */ + public void addStatisticsRow() + { + if (statistics.size() > 0) + { + Row row = sheet.createRow(sheet.getLastRowNum() + 1); + Set keys = statistics.keySet(); + Cell cell = row.createCell(0); + cell.setCellStyle(styles.get("total")); + cell.setCellValue("合计"); + + for (Integer key : keys) + { + cell = row.createCell(key); + cell.setCellStyle(styles.get("total")); + cell.setCellValue(statistics.get(key)); + } + statistics.clear(); + } + } + + /** + * 编码文件名 + */ + public String encodingFilename(String filename) + { + return UUID.randomUUID() + "_" + filename + ".xlsx"; + } + + /** + * 获取下载路径 + * + * @param filename 文件名称 + */ + public String getAbsoluteFile(String filename) + { + String downloadPath = RoomRootConfig.getDownloadPath() + filename; + File desc = new File(downloadPath); + if (!desc.getParentFile().exists()) + { + desc.getParentFile().mkdirs(); + } + return downloadPath; + } + + /** + * 获取bean中的属性值 + * + * @param vo 实体对象 + * @param field 字段 + * @param excel 注解 + * @return 最终的属性值 + * @throws Exception + */ + private Object getTargetValue(T vo, Field field, Excel excel) throws Exception + { + field.setAccessible(true); + Object o = field.get(vo); + if (StringUtils.isNotEmpty(excel.targetAttr())) + { + String target = excel.targetAttr(); + if (target.contains(".")) + { + String[] targets = target.split("[.]"); + for (String name : targets) + { + o = getValue(o, name); + } + } + else + { + o = getValue(o, target); + } + } + return o; + } + + /** + * 以类的属性的get方法方法形式获取值 + * + * @param o + * @param name + * @return value + * @throws Exception + */ + private Object getValue(Object o, String name) throws Exception + { + if (StringUtils.isNotNull(o) && StringUtils.isNotEmpty(name)) + { + Class clazz = o.getClass(); + Field field = clazz.getDeclaredField(name); + field.setAccessible(true); + o = field.get(o); + } + return o; + } + + /** + * 得到所有定义字段 + */ + private void createExcelField() + { + this.fields = getFields(); + this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList()); + this.maxHeight = getRowHeight(); + } + + /** + * 获取字段注解信息 + */ + public List getFields() + { + List fields = new ArrayList(); + List tempFields = new ArrayList<>(); + subFieldsMap = new HashMap<>(); + subMethods = new HashMap<>(); + tempFields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); + tempFields.addAll(Arrays.asList(clazz.getDeclaredFields())); + if (StringUtils.isNotEmpty(includeFields)) + { + for (Field field : tempFields) + { + if (ArrayUtils.contains(this.includeFields, field.getName()) || field.isAnnotationPresent(Excels.class)) + { + addField(fields, field); + } + } + } + else if (StringUtils.isNotEmpty(excludeFields)) + { + for (Field field : tempFields) + { + if (!ArrayUtils.contains(this.excludeFields, field.getName())) + { + addField(fields, field); + } + } + } + else + { + for (Field field : tempFields) + { + addField(fields, field); + } + } + return fields; + } + + /** + * 添加字段信息 + */ + public void addField(List fields, Field field) + { + // 单注解 + if (field.isAnnotationPresent(Excel.class)) + { + Excel attr = field.getAnnotation(Excel.class); + if (attr != null && (attr.type() == Type.ALL || attr.type() == type)) + { + fields.add(new Object[] { field, attr }); + } + if (Collection.class.isAssignableFrom(field.getType())) + { + String fieldName = field.getName(); + subMethods.put(fieldName, getSubMethod(fieldName, clazz)); + ParameterizedType pt = (ParameterizedType) field.getGenericType(); + Class subClass = (Class) pt.getActualTypeArguments()[0]; + subFieldsMap.put(fieldName, FieldUtils.getFieldsListWithAnnotation(subClass, Excel.class)); + } + } + + // 多注解 + if (field.isAnnotationPresent(Excels.class)) + { + Excels attrs = field.getAnnotation(Excels.class); + Excel[] excels = attrs.value(); + for (Excel attr : excels) + { + if (StringUtils.isNotEmpty(includeFields)) + { + if (ArrayUtils.contains(this.includeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + { + fields.add(new Object[] { field, attr }); + } + } + else + { + if (!ArrayUtils.contains(this.excludeFields, field.getName() + "." + attr.targetAttr()) + && (attr != null && (attr.type() == Type.ALL || attr.type() == type))) + { + fields.add(new Object[] { field, attr }); + } + } + } + } + } + + /** + * 根据注解获取最大行高 + */ + public short getRowHeight() + { + double maxHeight = 0; + for (Object[] os : this.fields) + { + Excel excel = (Excel) os[1]; + maxHeight = Math.max(maxHeight, excel.height()); + } + return (short) (maxHeight * 20); + } + + /** + * 创建一个工作簿 + */ + public void createWorkbook() + { + this.wb = new SXSSFWorkbook(500); + this.sheet = wb.createSheet(); + wb.setSheetName(0, sheetName); + this.styles = createStyles(wb); + } + + /** + * 创建工作表 + * + * @param sheetNo sheet数量 + * @param index 序号 + */ + public void createSheet(int sheetNo, int index) + { + // 设置工作表的名称. + if (sheetNo > 1 && index > 0) + { + this.sheet = wb.createSheet(); + this.createTitle(); + int actualIndex = wb.getSheetIndex(this.sheet); + wb.setSheetName(actualIndex, sheetName + index); + } + } + + /** + * 获取单元格值 + * + * @param row 获取的行 + * @param column 获取单元格列号 + * @return 单元格值 + */ + public Object getCellValue(Row row, int column) + { + if (row == null) + { + return row; + } + Object val = ""; + try + { + Cell cell = row.getCell(column); + if (StringUtils.isNotNull(cell)) + { + if (cell.getCellType() == CellType.NUMERIC || cell.getCellType() == CellType.FORMULA) + { + val = cell.getNumericCellValue(); + if (DateUtil.isCellDateFormatted(cell)) + { + val = DateUtil.getJavaDate((Double) val); // POI Excel 日期格式转换 + } + else + { + if ((Double) val % 1 != 0) + { + val = new BigDecimal(val.toString()); + } + else + { + val = new DecimalFormat("0").format(val); + } + } + } + else if (cell.getCellType() == CellType.STRING) + { + val = cell.getStringCellValue(); + } + else if (cell.getCellType() == CellType.BOOLEAN) + { + val = cell.getBooleanCellValue(); + } + else if (cell.getCellType() == CellType.ERROR) + { + val = cell.getErrorCellValue(); + } + + } + } + catch (Exception e) + { + return val; + } + return val; + } + + /** + * 判断是否是空行 + * + * @param row 判断的行 + * @return + */ + private boolean isRowEmpty(Row row) + { + if (row == null) + { + return true; + } + for (int i = row.getFirstCellNum(); i < row.getLastCellNum(); i++) + { + Cell cell = row.getCell(i); + if (cell != null && cell.getCellType() != CellType.BLANK) + { + return false; + } + } + return true; + } + + /** + * 获取Excel2003图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map> getSheetPictures03(HSSFSheet sheet, HSSFWorkbook workbook) + { + Map> sheetIndexPicMap = new HashMap<>(); + List pictures = workbook.getAllPictures(); + if (!pictures.isEmpty() && sheet.getDrawingPatriarch() != null) + { + for (HSSFShape shape : sheet.getDrawingPatriarch().getChildren()) + { + if (shape instanceof HSSFPicture) + { + HSSFPicture pic = (HSSFPicture) shape; + HSSFClientAnchor anchor = (HSSFClientAnchor) pic.getAnchor(); + String picIndex = anchor.getRow1() + "_" + anchor.getCol1(); + sheetIndexPicMap.computeIfAbsent(picIndex, k -> new ArrayList<>()).add(pic.getPictureData()); + } + } + } + return sheetIndexPicMap; + } + + /** + * 获取Excel2007图片 + * + * @param sheet 当前sheet对象 + * @param workbook 工作簿对象 + * @return Map key:图片单元格索引(1_1)String,value:图片流PictureData + */ + public static Map> getSheetPictures07(XSSFSheet sheet, XSSFWorkbook workbook) + { + Map> sheetIndexPicMap = new HashMap<>(); + for (POIXMLDocumentPart dr : sheet.getRelations()) + { + if (dr instanceof XSSFDrawing) + { + XSSFDrawing drawing = (XSSFDrawing) dr; + for (XSSFShape shape : drawing.getShapes()) + { + if (shape instanceof XSSFPicture) + { + XSSFPicture pic = (XSSFPicture) shape; + XSSFClientAnchor anchor = pic.getPreferredSize(); + CTMarker ctMarker = anchor.getFrom(); + String picIndex = ctMarker.getRow() + "_" + ctMarker.getCol(); + sheetIndexPicMap.computeIfAbsent(picIndex, k -> new ArrayList<>()).add(pic.getPictureData()); + } + } + } + } + return sheetIndexPicMap; + } + + /** + * 格式化不同类型的日期对象 + * + * @param dateFormat 日期格式 + * @param val 被格式化的日期对象 + * @return 格式化后的日期字符 + */ + public String parseDateToStr(String dateFormat, Object val) + { + if (val == null) + { + return ""; + } + String str; + if (val instanceof Date) + { + str = DateUtils.parseDateToStr(dateFormat, (Date) val); + } + else if (val instanceof LocalDateTime) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDateTime) val)); + } + else if (val instanceof LocalDate) + { + str = DateUtils.parseDateToStr(dateFormat, DateUtils.toDate((LocalDate) val)); + } + else + { + str = val.toString(); + } + return str; + } + + /** + * 是否有对象的子列表 + */ + public boolean isSubList() + { + return !StringUtils.isEmpty(subFieldsMap); + } + + /** + * 是否有对象的子列表,集合不为空 + */ + public boolean isSubListValue(T vo) + { + return !StringUtils.isEmpty(subFieldsMap) && getListCellValue(vo) > 0; + } + + /** + * 获取集合的值 + */ + public int getListCellValue(Object obj) + { + Collection value; + int max = 0; + try + { + for (String s : subMethods.keySet()) + { + value = (Collection) subMethods.get(s).invoke(obj); + if (value.size() > max) + { + max = value.size(); + } + } + } + catch (Exception e) + { + return 0; + } + return max; + } + + /** + * 获取对象的子列表方法 + * + * @param name 名称 + * @param pojoClass 类对象 + * @return 子列表方法 + */ + public Method getSubMethod(String name, Class pojoClass) + { + StringBuffer getMethodName = new StringBuffer("get"); + getMethodName.append(name.substring(0, 1).toUpperCase()); + getMethodName.append(name.substring(1)); + Method method = null; + try + { + method = pojoClass.getMethod(getMethodName.toString(), new Class[] {}); + } + catch (Exception e) + { + log.error("获取对象异常{}", e.getMessage()); + } + return method; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/reflect/ReflectUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/reflect/ReflectUtils.java new file mode 100644 index 000000000..bbd6b2926 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/reflect/ReflectUtils.java @@ -0,0 +1,412 @@ +package com.roomroot.common.utils.reflect; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Date; +import org.apache.commons.lang3.Validate; +import org.apache.poi.ss.usermodel.DateUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.StringUtils; + +/** + * 反射工具类. 提供调用getter/setter方法, 访问私有变量, 调用私有方法, 获取泛型类型Class, 被AOP过的真实类等工具函数. + * + * @author roomroot + */ +@SuppressWarnings("rawtypes") +public class ReflectUtils +{ + private static final String SETTER_PREFIX = "set"; + + private static final String GETTER_PREFIX = "get"; + + private static final String CGLIB_CLASS_SEPARATOR = "$$"; + + private static Logger logger = LoggerFactory.getLogger(ReflectUtils.class); + + /** + * 调用Getter方法. + * 支持多级,如:对象名.对象名.方法 + */ + @SuppressWarnings("unchecked") + public static E invokeGetter(Object obj, String propertyName) + { + Object object = obj; + for (String name : StringUtils.split(propertyName, ".")) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(name); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + return (E) object; + } + + /** + * 调用Setter方法, 仅匹配方法名。 + * 支持多级,如:对象名.对象名.方法 + */ + public static void invokeSetter(Object obj, String propertyName, E value) + { + Object object = obj; + String[] names = StringUtils.split(propertyName, "."); + for (int i = 0; i < names.length; i++) + { + if (i < names.length - 1) + { + String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(names[i]); + object = invokeMethod(object, getterMethodName, new Class[] {}, new Object[] {}); + } + else + { + String setterMethodName = SETTER_PREFIX + StringUtils.capitalize(names[i]); + invokeMethodByName(object, setterMethodName, new Object[] { value }); + } + } + } + + /** + * 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数. + */ + @SuppressWarnings("unchecked") + public static E getFieldValue(final Object obj, final String fieldName) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return null; + } + E result = null; + try + { + result = (E) field.get(obj); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常{}", e.getMessage()); + } + return result; + } + + /** + * 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数. + */ + public static void setFieldValue(final Object obj, final String fieldName, final E value) + { + Field field = getAccessibleField(obj, fieldName); + if (field == null) + { + // throw new IllegalArgumentException("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + fieldName + "] 字段 "); + return; + } + try + { + field.set(obj, value); + } + catch (IllegalAccessException e) + { + logger.error("不可能抛出的异常: {}", e.getMessage()); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符. + * 用于一次性调用的情况,否则应使用getAccessibleMethod()函数获得Method后反复调用. + * 同时匹配方法名+参数类型, + */ + @SuppressWarnings("unchecked") + public static E invokeMethod(final Object obj, final String methodName, final Class[] parameterTypes, + final Object[] args) + { + if (obj == null || methodName == null) + { + return null; + } + Method method = getAccessibleMethod(obj, methodName, parameterTypes); + if (method == null) + { + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 直接调用对象方法, 无视private/protected修饰符, + * 用于一次性调用的情况,否则应使用getAccessibleMethodByName()函数获得Method后反复调用. + * 只匹配函数名,如果有多个同名函数调用第一个。 + */ + @SuppressWarnings("unchecked") + public static E invokeMethodByName(final Object obj, final String methodName, final Object[] args) + { + Method method = getAccessibleMethodByName(obj, methodName, args.length); + if (method == null) + { + // 如果为空不报错,直接返回空。 + logger.debug("在 [" + obj.getClass() + "] 中,没有找到 [" + methodName + "] 方法 "); + return null; + } + try + { + // 类型转换(将参数数据类型转换为目标方法参数类型) + Class[] cs = method.getParameterTypes(); + for (int i = 0; i < cs.length; i++) + { + if (args[i] != null && !args[i].getClass().equals(cs[i])) + { + if (cs[i] == String.class) + { + args[i] = Convert.toStr(args[i]); + if (StringUtils.endsWith((String) args[i], ".0")) + { + args[i] = StringUtils.substringBefore((String) args[i], ".0"); + } + } + else if (cs[i] == Integer.class) + { + args[i] = Convert.toInt(args[i]); + } + else if (cs[i] == Long.class) + { + args[i] = Convert.toLong(args[i]); + } + else if (cs[i] == Double.class) + { + args[i] = Convert.toDouble(args[i]); + } + else if (cs[i] == Float.class) + { + args[i] = Convert.toFloat(args[i]); + } + else if (cs[i] == Date.class) + { + if (args[i] instanceof String) + { + args[i] = DateUtils.parseDate(args[i]); + } + else + { + args[i] = DateUtil.getJavaDate((Double) args[i]); + } + } + else if (cs[i] == boolean.class || cs[i] == Boolean.class) + { + args[i] = Convert.toBool(args[i]); + } + } + } + return (E) method.invoke(obj, args); + } + catch (Exception e) + { + String msg = "method: " + method + ", obj: " + obj + ", args: " + args + ""; + throw convertReflectionExceptionToUnchecked(msg, e); + } + } + + /** + * 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + */ + public static Field getAccessibleField(final Object obj, final String fieldName) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(fieldName, "fieldName can't be blank"); + for (Class superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) + { + try + { + Field field = superClass.getDeclaredField(fieldName); + makeAccessible(field); + return field; + } + catch (NoSuchFieldException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 匹配函数名+参数类型。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethod(final Object obj, final String methodName, + final Class... parameterTypes) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + try + { + Method method = searchType.getDeclaredMethod(methodName, parameterTypes); + makeAccessible(method); + return method; + } + catch (NoSuchMethodException e) + { + continue; + } + } + return null; + } + + /** + * 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问. + * 如向上转型到Object仍无法找到, 返回null. + * 只匹配函数名。 + * 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args) + */ + public static Method getAccessibleMethodByName(final Object obj, final String methodName, int argsNum) + { + // 为空不报错。直接返回 null + if (obj == null) + { + return null; + } + Validate.notBlank(methodName, "methodName can't be blank"); + for (Class searchType = obj.getClass(); searchType != Object.class; searchType = searchType.getSuperclass()) + { + Method[] methods = searchType.getDeclaredMethods(); + for (Method method : methods) + { + if (method.getName().equals(methodName) && method.getParameterTypes().length == argsNum) + { + makeAccessible(method); + return method; + } + } + } + return null; + } + + /** + * 改变private/protected的方法为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + @SuppressWarnings("deprecation") + public static void makeAccessible(Method method) + { + if ((!Modifier.isPublic(method.getModifiers()) || !Modifier.isPublic(method.getDeclaringClass().getModifiers())) + && !method.isAccessible()) + { + method.setAccessible(true); + } + } + + /** + * 改变private/protected的成员变量为public,尽量不调用实际改动的语句,避免JDK的SecurityManager抱怨。 + */ + @SuppressWarnings("deprecation") + public static void makeAccessible(Field field) + { + if ((!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers()) + || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) + { + field.setAccessible(true); + } + } + + /** + * 通过反射, 获得Class定义中声明的泛型参数的类型, 注意泛型必须定义在父类处 + * 如无法找到, 返回Object.class. + */ + @SuppressWarnings("unchecked") + public static Class getClassGenricType(final Class clazz) + { + return getClassGenricType(clazz, 0); + } + + /** + * 通过反射, 获得Class定义中声明的父类的泛型参数的类型. + * 如无法找到, 返回Object.class. + */ + public static Class getClassGenricType(final Class clazz, final int index) + { + Type genType = clazz.getGenericSuperclass(); + + if (!(genType instanceof ParameterizedType)) + { + logger.debug(clazz.getSimpleName() + "'s superclass not ParameterizedType"); + return Object.class; + } + + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + + if (index >= params.length || index < 0) + { + logger.debug("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: " + + params.length); + return Object.class; + } + if (!(params[index] instanceof Class)) + { + logger.debug(clazz.getSimpleName() + " not set the actual class on superclass generic parameter"); + return Object.class; + } + + return (Class) params[index]; + } + + public static Class getUserClass(Object instance) + { + if (instance == null) + { + throw new RuntimeException("Instance must not be null"); + } + Class clazz = instance.getClass(); + if (clazz != null && clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) + { + Class superClass = clazz.getSuperclass(); + if (superClass != null && !Object.class.equals(superClass)) + { + return superClass; + } + } + return clazz; + + } + + /** + * 将反射时的checked exception转换为unchecked exception. + */ + public static RuntimeException convertReflectionExceptionToUnchecked(String msg, Exception e) + { + if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException + || e instanceof NoSuchMethodException) + { + return new IllegalArgumentException(msg, e); + } + else if (e instanceof InvocationTargetException) + { + return new RuntimeException(msg, ((InvocationTargetException) e).getTargetException()); + } + return new RuntimeException(msg, e); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/sign/Base64.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/sign/Base64.java new file mode 100644 index 000000000..8789d4db0 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/sign/Base64.java @@ -0,0 +1,291 @@ +package com.roomroot.common.utils.sign; + +/** + * Base64工具类 + * + * @author roomroot + */ +public final class Base64 +{ + static private final int BASELENGTH = 128; + static private final int LOOKUPLENGTH = 64; + static private final int TWENTYFOURBITGROUP = 24; + static private final int EIGHTBIT = 8; + static private final int SIXTEENBIT = 16; + static private final int FOURBYTE = 4; + static private final int SIGN = -128; + static private final char PAD = '='; + static final private byte[] base64Alphabet = new byte[BASELENGTH]; + static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH]; + + static + { + for (int i = 0; i < BASELENGTH; ++i) + { + base64Alphabet[i] = -1; + } + for (int i = 'Z'; i >= 'A'; i--) + { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) + { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + + for (int i = '9'; i >= '0'; i--) + { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) + { + lookUpBase64Alphabet[i] = (char) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) + { + lookUpBase64Alphabet[i] = (char) ('0' + j); + } + lookUpBase64Alphabet[62] = (char) '+'; + lookUpBase64Alphabet[63] = (char) '/'; + } + + private static boolean isWhiteSpace(char octect) + { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isPad(char octect) + { + return (octect == PAD); + } + + private static boolean isData(char octect) + { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + /** + * Encodes hex octects into Base64 + * + * @param binaryData Array containing binaryData + * @return Encoded Base64 array + */ + public static String encode(byte[] binaryData) + { + if (binaryData == null) + { + return null; + } + + int lengthDataBits = binaryData.length * EIGHTBIT; + if (lengthDataBits == 0) + { + return ""; + } + + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets; + char encodedData[] = null; + + encodedData = new char[numberQuartet * 4]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + + for (int i = 0; i < numberTriplets; i++) + { + b1 = binaryData[dataIndex++]; + b2 = binaryData[dataIndex++]; + b3 = binaryData[dataIndex++]; + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f]; + } + + // form integral number of 6-bit groups + if (fewerThan24bits == EIGHTBIT) + { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex++] = PAD; + encodedData[encodedIndex++] = PAD; + } + else if (fewerThan24bits == SIXTEENBIT) + { + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex++] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex++] = PAD; + } + return new String(encodedData); + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode(String encoded) + { + if (encoded == null) + { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) + { + return null;// should be divisible by four + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) + { + return new byte[0]; + } + + byte decodedData[] = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) + { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++]))) + { + return null; + } // if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) + { + return null;// if found "no data" just return null + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + if (!isData((d3)) || !isData((d4))) + {// Check if they are PAD characters + if (isPad(d3) && isPad(d4)) + { + if ((b2 & 0xf) != 0)// last 4 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } + else if (!isPad(d3) && isPad(d4)) + { + b3 = base64Alphabet[d3]; + if ((b3 & 0x3) != 0)// last 2 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } + else + { + return null; + } + } + else + { // No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + return decodedData; + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) + { + if (data == null) + { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) + { + if (!isWhiteSpace(data[i])) + { + data[newSize++] = data[i]; + } + } + return newSize; + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/sign/Md5Utils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/sign/Md5Utils.java new file mode 100644 index 000000000..a4e783777 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/sign/Md5Utils.java @@ -0,0 +1,67 @@ +package com.roomroot.common.utils.sign; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Md5加密方法 + * + * @author roomroot + */ +public class Md5Utils +{ + private static final Logger log = LoggerFactory.getLogger(Md5Utils.class); + + private static byte[] md5(String s) + { + MessageDigest algorithm; + try + { + algorithm = MessageDigest.getInstance("MD5"); + algorithm.reset(); + algorithm.update(s.getBytes("UTF-8")); + byte[] messageDigest = algorithm.digest(); + return messageDigest; + } + catch (Exception e) + { + log.error("MD5 Error...", e); + } + return null; + } + + private static final String toHex(byte hash[]) + { + if (hash == null) + { + return null; + } + StringBuffer buf = new StringBuffer(hash.length * 2); + int i; + + for (i = 0; i < hash.length; i++) + { + if ((hash[i] & 0xff) < 0x10) + { + buf.append("0"); + } + buf.append(Long.toString(hash[i] & 0xff, 16)); + } + return buf.toString(); + } + + public static String hash(String s) + { + try + { + return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + } + catch (Exception e) + { + log.error("not supported charset...{}", e); + return s; + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/spring/SpringUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/spring/SpringUtils.java new file mode 100644 index 000000000..95eef725a --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/spring/SpringUtils.java @@ -0,0 +1,164 @@ +package com.roomroot.common.utils.spring; + +import org.springframework.aop.framework.Advised; +import org.springframework.aop.framework.AopContext; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.stereotype.Component; +import com.roomroot.common.utils.StringUtils; + +/** + * spring工具类 方便在非spring管理环境中获取bean + * + * @author roomroot + */ +@Component +public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware +{ + /** Spring应用上下文环境 */ + private static ConfigurableListableBeanFactory beanFactory; + + private static ApplicationContext applicationContext; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException + { + SpringUtils.beanFactory = beanFactory; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException + { + SpringUtils.applicationContext = applicationContext; + } + + /** + * 获取对象 + * + * @param name + * @return Object 一个以所给名字注册的bean的实例 + * @throws org.springframework.beans.BeansException + * + */ + @SuppressWarnings("unchecked") + public static T getBean(String name) throws BeansException + { + return (T) beanFactory.getBean(name); + } + + /** + * 获取类型为requiredType的对象 + * + * @param clz + * @return + * @throws org.springframework.beans.BeansException + * + */ + public static T getBean(Class clz) throws BeansException + { + T result = (T) beanFactory.getBean(clz); + return result; + } + + /** + * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true + * + * @param name + * @return boolean + */ + public static boolean containsBean(String name) + { + return beanFactory.containsBean(name); + } + + /** + * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) + * + * @param name + * @return boolean + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.isSingleton(name); + } + + /** + * @param name + * @return Class 注册对象的类型 + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static Class getType(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getType(name); + } + + /** + * 如果给定的bean名字在bean定义中有别名,则返回这些别名 + * + * @param name + * @return + * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException + * + */ + public static String[] getAliases(String name) throws NoSuchBeanDefinitionException + { + return beanFactory.getAliases(name); + } + + /** + * 获取aop代理对象 + * + * @param invoker + * @return + */ + @SuppressWarnings("unchecked") + public static T getAopProxy(T invoker) + { + Object proxy = AopContext.currentProxy(); + if (((Advised) proxy).getTargetSource().getTargetClass() == invoker.getClass()) + { + return (T) proxy; + } + return invoker; + } + + /** + * 获取当前的环境配置,无配置返回null + * + * @return 当前的环境配置 + */ + public static String[] getActiveProfiles() + { + return applicationContext.getEnvironment().getActiveProfiles(); + } + + /** + * 获取当前的环境配置,当有多个环境配置时,只获取第一个 + * + * @return 当前的环境配置 + */ + public static String getActiveProfile() + { + final String[] activeProfiles = getActiveProfiles(); + return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null; + } + + /** + * 获取配置文件中的值 + * + * @param key 配置文件的key + * @return 当前的配置文件的值 + * + */ + public static String getRequiredProperty(String key) + { + return applicationContext.getEnvironment().getRequiredProperty(key); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/sql/SqlUtil.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/sql/SqlUtil.java new file mode 100644 index 000000000..9a509b7ef --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/sql/SqlUtil.java @@ -0,0 +1,71 @@ +package com.roomroot.common.utils.sql; + +import com.roomroot.common.exception.UtilException; +import com.roomroot.common.utils.StringUtils; + +/** + * sql操作工具类 + * + * @author roomroot + */ +public class SqlUtil +{ + /** + * 定义常用的 sql关键字 + */ + public static String SQL_REGEX = "\u000B|%0A|and |extractvalue|updatexml|sleep|information_schema|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; + + /** + * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) + */ + public static String SQL_PATTERN = "[a-zA-Z0-9_\\ \\,\\.]+"; + + /** + * 限制orderBy最大长度 + */ + private static final int ORDER_BY_MAX_LENGTH = 500; + + /** + * 检查字符,防止注入绕过 + */ + public static String escapeOrderBySql(String value) + { + if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value)) + { + throw new UtilException("参数不符合规范,不能进行查询"); + } + if (StringUtils.length(value) > ORDER_BY_MAX_LENGTH) + { + throw new UtilException("参数已超过最大限制,不能进行查询"); + } + return value; + } + + /** + * 验证 order by 语法是否符合规范 + */ + public static boolean isValidOrderBySql(String value) + { + return value.matches(SQL_PATTERN); + } + + /** + * SQL关键字检查 + */ + public static void filterKeyword(String value) + { + if (StringUtils.isEmpty(value)) + { + return; + } + String normalizedValue = value.replaceAll("\\p{Z}|\\s", ""); + String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); + for (String sqlKeyword : sqlKeywords) + { + if (StringUtils.indexOfIgnoreCase(normalizedValue, sqlKeyword) > -1) + { + throw new UtilException("请求参数包含敏感关键词'" + sqlKeyword + "',可能存在安全风险"); + } + } + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/uuid/IdUtils.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/uuid/IdUtils.java new file mode 100644 index 000000000..320f60ec5 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/uuid/IdUtils.java @@ -0,0 +1,49 @@ +package com.roomroot.common.utils.uuid; + +/** + * ID生成器工具类 + * + * @author roomroot + */ +public class IdUtils +{ + /** + * 获取随机UUID + * + * @return 随机UUID + */ + public static String randomUUID() + { + return UUID.randomUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线 + * + * @return 简化的UUID,去掉了横线 + */ + public static String simpleUUID() + { + return UUID.randomUUID().toString(true); + } + + /** + * 获取随机UUID,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 随机UUID + */ + public static String fastUUID() + { + return UUID.fastUUID().toString(); + } + + /** + * 简化的UUID,去掉了横线,使用性能更好的ThreadLocalRandom生成UUID + * + * @return 简化的UUID,去掉了横线 + */ + public static String fastSimpleUUID() + { + return UUID.fastUUID().toString(true); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/uuid/Seq.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/uuid/Seq.java new file mode 100644 index 000000000..461c7bec4 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/uuid/Seq.java @@ -0,0 +1,86 @@ +package com.roomroot.common.utils.uuid; + +import java.util.concurrent.atomic.AtomicInteger; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.StringUtils; + +/** + * @author roomroot 序列生成类 + */ +public class Seq +{ + // 通用序列类型 + public static final String commSeqType = "COMMON"; + + // 上传序列类型 + public static final String uploadSeqType = "UPLOAD"; + + // 通用接口序列数 + private static AtomicInteger commSeq = new AtomicInteger(1); + + // 上传接口序列数 + private static AtomicInteger uploadSeq = new AtomicInteger(1); + + // 机器标识 + private static final String machineCode = "A"; + + /** + * 获取通用序列号 + * + * @return 序列值 + */ + public static String getId() + { + return getId(commSeqType); + } + + /** + * 默认16位序列号 yyMMddHHmmss + 一位机器标识 + 3长度循环递增字符串 + * + * @return 序列值 + */ + public static String getId(String type) + { + AtomicInteger atomicInt = commSeq; + if (uploadSeqType.equals(type)) + { + atomicInt = uploadSeq; + } + return getId(atomicInt, 3); + } + + /** + * 通用接口序列号 yyMMddHHmmss + 一位机器标识 + length长度循环递增字符串 + * + * @param atomicInt 序列数 + * @param length 数值长度 + * @return 序列值 + */ + public static String getId(AtomicInteger atomicInt, int length) + { + String result = DateUtils.dateTimeNow(); + result += machineCode; + result += getSeq(atomicInt, length); + return result; + } + + /** + * 序列循环递增字符串[1, 10 的 (length)幂次方), 用0左补齐length位数 + * + * @return 序列值 + */ + private synchronized static String getSeq(AtomicInteger atomicInt, int length) + { + // 先取值再+1 + int value = atomicInt.getAndIncrement(); + + // 如果更新后值>=10 的 (length)幂次方则重置为1 + int maxSeq = (int) Math.pow(10, length); + if (atomicInt.get() >= maxSeq) + { + atomicInt.set(1); + } + // 转字符串,用0左补齐 + return StringUtils.padl(value, length); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/utils/uuid/UUID.java b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/uuid/UUID.java new file mode 100644 index 000000000..5b701d8e9 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/utils/uuid/UUID.java @@ -0,0 +1,484 @@ +package com.roomroot.common.utils.uuid; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; +import com.roomroot.common.exception.UtilException; + +/** + * 提供通用唯一识别码(universally unique identifier)(UUID)实现 + * + * @author roomroot + */ +public final class UUID implements java.io.Serializable, Comparable +{ + private static final long serialVersionUID = -1185015143654744140L; + + /** + * SecureRandom 的单例 + * + */ + private static class Holder + { + static final SecureRandom numberGenerator = getSecureRandom(); + } + + /** 此UUID的最高64有效位 */ + private final long mostSigBits; + + /** 此UUID的最低64有效位 */ + private final long leastSigBits; + + /** + * 私有构造 + * + * @param data 数据 + */ + private UUID(byte[] data) + { + long msb = 0; + long lsb = 0; + assert data.length == 16 : "data must be 16 bytes in length"; + for (int i = 0; i < 8; i++) + { + msb = (msb << 8) | (data[i] & 0xff); + } + for (int i = 8; i < 16; i++) + { + lsb = (lsb << 8) | (data[i] & 0xff); + } + this.mostSigBits = msb; + this.leastSigBits = lsb; + } + + /** + * 使用指定的数据构造新的 UUID。 + * + * @param mostSigBits 用于 {@code UUID} 的最高有效 64 位 + * @param leastSigBits 用于 {@code UUID} 的最低有效 64 位 + */ + public UUID(long mostSigBits, long leastSigBits) + { + this.mostSigBits = mostSigBits; + this.leastSigBits = leastSigBits; + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID fastUUID() + { + return randomUUID(false); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID() + { + return randomUUID(true); + } + + /** + * 获取类型 4(伪随机生成的)UUID 的静态工厂。 使用加密的强伪随机数生成器生成该 UUID。 + * + * @param isSecure 是否使用{@link SecureRandom}如果是可以获得更安全的随机码,否则可以得到更好的性能 + * @return 随机生成的 {@code UUID} + */ + public static UUID randomUUID(boolean isSecure) + { + final Random ng = isSecure ? Holder.numberGenerator : getRandom(); + + byte[] randomBytes = new byte[16]; + ng.nextBytes(randomBytes); + randomBytes[6] &= 0x0f; /* clear version */ + randomBytes[6] |= 0x40; /* set to version 4 */ + randomBytes[8] &= 0x3f; /* clear variant */ + randomBytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(randomBytes); + } + + /** + * 根据指定的字节数组获取类型 3(基于名称的)UUID 的静态工厂。 + * + * @param name 用于构造 UUID 的字节数组。 + * + * @return 根据指定数组生成的 {@code UUID} + */ + public static UUID nameUUIDFromBytes(byte[] name) + { + MessageDigest md; + try + { + md = MessageDigest.getInstance("MD5"); + } + catch (NoSuchAlgorithmException nsae) + { + throw new InternalError("MD5 not supported"); + } + byte[] md5Bytes = md.digest(name); + md5Bytes[6] &= 0x0f; /* clear version */ + md5Bytes[6] |= 0x30; /* set to version 3 */ + md5Bytes[8] &= 0x3f; /* clear variant */ + md5Bytes[8] |= 0x80; /* set to IETF variant */ + return new UUID(md5Bytes); + } + + /** + * 根据 {@link #toString()} 方法中描述的字符串标准表示形式创建{@code UUID}。 + * + * @param name 指定 {@code UUID} 字符串 + * @return 具有指定值的 {@code UUID} + * @throws IllegalArgumentException 如果 name 与 {@link #toString} 中描述的字符串表示形式不符抛出此异常 + * + */ + public static UUID fromString(String name) + { + String[] components = name.split("-"); + if (components.length != 5) + { + throw new IllegalArgumentException("Invalid UUID string: " + name); + } + for (int i = 0; i < 5; i++) + { + components[i] = "0x" + components[i]; + } + + long mostSigBits = Long.decode(components[0]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[1]).longValue(); + mostSigBits <<= 16; + mostSigBits |= Long.decode(components[2]).longValue(); + + long leastSigBits = Long.decode(components[3]).longValue(); + leastSigBits <<= 48; + leastSigBits |= Long.decode(components[4]).longValue(); + + return new UUID(mostSigBits, leastSigBits); + } + + /** + * 返回此 UUID 的 128 位值中的最低有效 64 位。 + * + * @return 此 UUID 的 128 位值中的最低有效 64 位。 + */ + public long getLeastSignificantBits() + { + return leastSigBits; + } + + /** + * 返回此 UUID 的 128 位值中的最高有效 64 位。 + * + * @return 此 UUID 的 128 位值中最高有效 64 位。 + */ + public long getMostSignificantBits() + { + return mostSigBits; + } + + /** + * 与此 {@code UUID} 相关联的版本号. 版本号描述此 {@code UUID} 是如何生成的。 + *

+ * 版本号具有以下含意: + *

    + *
  • 1 基于时间的 UUID + *
  • 2 DCE 安全 UUID + *
  • 3 基于名称的 UUID + *
  • 4 随机生成的 UUID + *
+ * + * @return 此 {@code UUID} 的版本号 + */ + public int version() + { + // Version is bits masked by 0x000000000000F000 in MS long + return (int) ((mostSigBits >> 12) & 0x0f); + } + + /** + * 与此 {@code UUID} 相关联的变体号。变体号描述 {@code UUID} 的布局。 + *

+ * 变体号具有以下含意: + *

    + *
  • 0 为 NCS 向后兼容保留 + *
  • 2 IETF RFC 4122(Leach-Salz), 用于此类 + *
  • 6 保留,微软向后兼容 + *
  • 7 保留供以后定义使用 + *
+ * + * @return 此 {@code UUID} 相关联的变体号 + */ + public int variant() + { + // This field is composed of a varying number of bits. + // 0 - - Reserved for NCS backward compatibility + // 1 0 - The IETF aka Leach-Salz variant (used by this class) + // 1 1 0 Reserved, Microsoft backward compatibility + // 1 1 1 Reserved for future definition. + return (int) ((leastSigBits >>> (64 - (leastSigBits >>> 62))) & (leastSigBits >> 63)); + } + + /** + * 与此 UUID 相关联的时间戳值。 + * + *

+ * 60 位的时间戳值根据此 {@code UUID} 的 time_low、time_mid 和 time_hi 字段构造。
+ * 所得到的时间戳以 100 毫微秒为单位,从 UTC(通用协调时间) 1582 年 10 月 15 日零时开始。 + * + *

+ * 时间戳值仅在在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 {@code UUID} 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @throws UnsupportedOperationException 如果此 {@code UUID} 不是 version 为 1 的 UUID。 + */ + public long timestamp() throws UnsupportedOperationException + { + checkTimeBase(); + return (mostSigBits & 0x0FFFL) << 48// + | ((mostSigBits >> 16) & 0x0FFFFL) << 32// + | mostSigBits >>> 32; + } + + /** + * 与此 UUID 相关联的时钟序列值。 + * + *

+ * 14 位的时钟序列值根据此 UUID 的 clock_seq 字段构造。clock_seq 字段用于保证在基于时间的 UUID 中的时间唯一性。 + *

+ * {@code clockSequence} 值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。 如果此 UUID 不是基于时间的 UUID,则此方法抛出 + * UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的时钟序列 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public int clockSequence() throws UnsupportedOperationException + { + checkTimeBase(); + return (int) ((leastSigBits & 0x3FFF000000000000L) >>> 48); + } + + /** + * 与此 UUID 相关的节点值。 + * + *

+ * 48 位的节点值根据此 UUID 的 node 字段构造。此字段旨在用于保存机器的 IEEE 802 地址,该地址用于生成此 UUID 以保证空间唯一性。 + *

+ * 节点值仅在基于时间的 UUID(其 version 类型为 1)中才有意义。
+ * 如果此 UUID 不是基于时间的 UUID,则此方法抛出 UnsupportedOperationException。 + * + * @return 此 {@code UUID} 的节点值 + * + * @throws UnsupportedOperationException 如果此 UUID 的 version 不为 1 + */ + public long node() throws UnsupportedOperationException + { + checkTimeBase(); + return leastSigBits & 0x0000FFFFFFFFFFFFL; + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @return 此{@code UUID} 的字符串表现形式 + * @see #toString(boolean) + */ + @Override + public String toString() + { + return toString(false); + } + + /** + * 返回此{@code UUID} 的字符串表现形式。 + * + *

+ * UUID 的字符串表示形式由此 BNF 描述: + * + *

+     * {@code
+     * UUID                   = ----
+     * time_low               = 4*
+     * time_mid               = 2*
+     * time_high_and_version  = 2*
+     * variant_and_sequence   = 2*
+     * node                   = 6*
+     * hexOctet               = 
+     * hexDigit               = [0-9a-fA-F]
+     * }
+     * 
+ * + * + * + * @param isSimple 是否简单模式,简单模式为不带'-'的UUID字符串 + * @return 此{@code UUID} 的字符串表现形式 + */ + public String toString(boolean isSimple) + { + final StringBuilder builder = new StringBuilder(isSimple ? 32 : 36); + // time_low + builder.append(digits(mostSigBits >> 32, 8)); + if (!isSimple) + { + builder.append('-'); + } + // time_mid + builder.append(digits(mostSigBits >> 16, 4)); + if (!isSimple) + { + builder.append('-'); + } + // time_high_and_version + builder.append(digits(mostSigBits, 4)); + if (!isSimple) + { + builder.append('-'); + } + // variant_and_sequence + builder.append(digits(leastSigBits >> 48, 4)); + if (!isSimple) + { + builder.append('-'); + } + // node + builder.append(digits(leastSigBits, 12)); + + return builder.toString(); + } + + /** + * 返回此 UUID 的哈希码。 + * + * @return UUID 的哈希码值。 + */ + @Override + public int hashCode() + { + long hilo = mostSigBits ^ leastSigBits; + return ((int) (hilo >> 32)) ^ (int) hilo; + } + + /** + * 将此对象与指定对象比较。 + *

+ * 当且仅当参数不为 {@code null}、而是一个 UUID 对象、具有与此 UUID 相同的 varriant、包含相同的值(每一位均相同)时,结果才为 {@code true}。 + * + * @param obj 要与之比较的对象 + * + * @return 如果对象相同,则返回 {@code true};否则返回 {@code false} + */ + @Override + public boolean equals(Object obj) + { + if ((null == obj) || (obj.getClass() != UUID.class)) + { + return false; + } + UUID id = (UUID) obj; + return (mostSigBits == id.mostSigBits && leastSigBits == id.leastSigBits); + } + + // Comparison Operations + + /** + * 将此 UUID 与指定的 UUID 比较。 + * + *

+ * 如果两个 UUID 不同,且第一个 UUID 的最高有效字段大于第二个 UUID 的对应字段,则第一个 UUID 大于第二个 UUID。 + * + * @param val 与此 UUID 比较的 UUID + * + * @return 在此 UUID 小于、等于或大于 val 时,分别返回 -1、0 或 1。 + * + */ + @Override + public int compareTo(UUID val) + { + // The ordering is intentionally set up so that the UUIDs + // can simply be numerically compared as two numbers + return (this.mostSigBits < val.mostSigBits ? -1 : // + (this.mostSigBits > val.mostSigBits ? 1 : // + (this.leastSigBits < val.leastSigBits ? -1 : // + (this.leastSigBits > val.leastSigBits ? 1 : // + 0)))); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Private method start + /** + * 返回指定数字对应的hex值 + * + * @param val 值 + * @param digits 位 + * @return 值 + */ + private static String digits(long val, int digits) + { + long hi = 1L << (digits * 4); + return Long.toHexString(hi | (val & (hi - 1))).substring(1); + } + + /** + * 检查是否为time-based版本UUID + */ + private void checkTimeBase() + { + if (version() != 1) + { + throw new UnsupportedOperationException("Not a time-based UUID"); + } + } + + /** + * 获取{@link SecureRandom},类提供加密的强随机数生成器 (RNG) + * + * @return {@link SecureRandom} + */ + public static SecureRandom getSecureRandom() + { + try + { + return SecureRandom.getInstance("SHA1PRNG"); + } + catch (NoSuchAlgorithmException e) + { + throw new UtilException(e); + } + } + + /** + * 获取随机数生成器对象
+ * ThreadLocalRandom是JDK 7之后提供并发产生随机数,能够解决多个线程发生的竞争争夺。 + * + * @return {@link ThreadLocalRandom} + */ + public static ThreadLocalRandom getRandom() + { + return ThreadLocalRandom.current(); + } +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/xss/Xss.java b/backend/roomroot-common/src/main/java/com/roomroot/common/xss/Xss.java new file mode 100644 index 000000000..2f8d55926 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/xss/Xss.java @@ -0,0 +1,27 @@ +package com.roomroot.common.xss; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 自定义xss校验注解 + * + * @author roomroot + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value = { ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.PARAMETER }) +@Constraint(validatedBy = { XssValidator.class }) +public @interface Xss +{ + String message() + + default "不允许任何脚本运行"; + + Class[] groups() default {}; + + Class[] payload() default {}; +} diff --git a/backend/roomroot-common/src/main/java/com/roomroot/common/xss/XssValidator.java b/backend/roomroot-common/src/main/java/com/roomroot/common/xss/XssValidator.java new file mode 100644 index 000000000..662a39762 --- /dev/null +++ b/backend/roomroot-common/src/main/java/com/roomroot/common/xss/XssValidator.java @@ -0,0 +1,39 @@ +package com.roomroot.common.xss; + +import com.roomroot.common.utils.StringUtils; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * 自定义xss校验注解实现 + * + * @author roomroot + */ +public class XssValidator implements ConstraintValidator +{ + private static final String HTML_PATTERN = "<(\\S*?)[^>]*>.*?|<.*? />"; + + @Override + public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) + { + if (StringUtils.isBlank(value)) + { + return true; + } + return !containsHtml(value); + } + + public static boolean containsHtml(String value) + { + StringBuilder sHtml = new StringBuilder(); + Pattern pattern = Pattern.compile(HTML_PATTERN); + Matcher matcher = pattern.matcher(value); + while (matcher.find()) + { + sHtml.append(matcher.group()); + } + return pattern.matcher(sHtml).matches(); + } +} \ No newline at end of file diff --git a/backend/roomroot-framework/pom.xml b/backend/roomroot-framework/pom.xml new file mode 100644 index 000000000..c23ad10cb --- /dev/null +++ b/backend/roomroot-framework/pom.xml @@ -0,0 +1,64 @@ + + + + roomroot + com.roomroot + 3.9.2 + + 4.0.0 + + roomroot-framework + + + framework框架核心 + + + + + + + org.springframework.boot + spring-boot-starter-webmvc + + + + + org.springframework.boot + spring-boot-starter-aspectj + + + + + com.alibaba + druid-spring-boot-4-starter + + + + + pro.fessional + kaptcha + + + servlet-api + javax.servlet + + + + + + + com.github.oshi + oshi-core + + + + + com.roomroot + roomroot-system + + + + + \ No newline at end of file diff --git a/backend/roomroot-framework/roomroot-framework.iml b/backend/roomroot-framework/roomroot-framework.iml new file mode 100644 index 000000000..abe781f78 --- /dev/null +++ b/backend/roomroot-framework/roomroot-framework.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/DataScopeAspect.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/DataScopeAspect.java new file mode 100644 index 000000000..d215fdeb2 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/DataScopeAspect.java @@ -0,0 +1,160 @@ +package com.roomroot.framework.aspectj; + +import java.util.ArrayList; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.springframework.stereotype.Component; +import com.roomroot.common.annotation.DataScope; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.BaseEntity; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.framework.security.context.PermissionContextHolder; + +/** + * 数据过滤处理 + * + * @author roomroot + */ +@Aspect +@Component +public class DataScopeAspect +{ + /** + * 数据权限过滤关键字 + */ + public static final String DATA_SCOPE = "dataScope"; + + @Before("@annotation(controllerDataScope)") + public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable + { + clearDataScope(point); + handleDataScope(point, controllerDataScope); + } + + protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) + { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNotNull(loginUser)) + { + SysUser currentUser = loginUser.getUser(); + // 如果是超级管理员,则不过滤数据 + if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) + { + String permission = StringUtils.defaultIfEmpty(controllerDataScope.permission(), PermissionContextHolder.getContext()); + dataScopeFilter(joinPoint, currentUser, controllerDataScope.userAlias(), controllerDataScope.deptAlias(), controllerDataScope.userField(), controllerDataScope.deptField(), permission); + } + } + } + + /** + * 数据范围过滤 + * + * @param joinPoint 切点 + * @param user 用户 + * @param deptAlias 部门别名 + * @param userAlias 用户别名 + * @param permission 权限字符 + */ + public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String userAlias, String deptAlias, String userField, String deptField, String permission) + { + StringBuilder sqlString = new StringBuilder(); + List conditions = new ArrayList(); + List scopeCustomIds = new ArrayList(); + user.getRoles().forEach(role -> { + if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(role.getDataScope()) && StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && (StringUtils.isEmpty(permission) || StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission)))) + { + scopeCustomIds.add(Convert.toStr(role.getRoleId())); + } + }); + + for (SysRole role : user.getRoles()) + { + String dataScope = role.getDataScope(); + if (conditions.contains(dataScope) || StringUtils.equals(role.getStatus(), UserConstants.ROLE_DISABLE)) + { + continue; + } + if (StringUtils.isNotEmpty(permission) && !StringUtils.containsAny(role.getPermissions(), Convert.toStrArray(permission))) + { + continue; + } + if (Constants.Dept.DATA_SCOPE_ALL.equals(dataScope)) + { + sqlString = new StringBuilder(); + conditions.add(dataScope); + break; + } + else if (Constants.Dept.DATA_SCOPE_CUSTOM.equals(dataScope)) + { + if (scopeCustomIds.size() > 1) + { + // 多个自定数据权限使用in查询,避免多次拼接。 + sqlString.append(StringUtils.format(" OR {}.{} IN ( SELECT dept_id FROM sys_role_dept WHERE role_id in ({}) ) ", deptAlias, deptField, String.join(",", scopeCustomIds))); + } + else + { + sqlString.append(StringUtils.format(" OR {}.{} IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias, deptField, role.getRoleId())); + } + } + else if (Constants.Dept.DATA_SCOPE_DEPT.equals(dataScope)) + { + sqlString.append(StringUtils.format(" OR {}.{} = {} ", deptAlias, deptField, user.getDeptId())); + } + else if (Constants.Dept.DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope)) + { + sqlString.append(StringUtils.format(" OR {}.{} IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )", deptAlias, deptField, user.getDeptId(), user.getDeptId())); + } + else if (Constants.Dept.DATA_SCOPE_SELF.equals(dataScope)) + { + if (StringUtils.isNotBlank(userAlias)) + { + sqlString.append(StringUtils.format(" OR {}.{} = {} ", userAlias, userField, user.getUserId())); + } + else + { + // 数据权限为仅本人且没有userAlias别名不查询任何数据 + sqlString.append(StringUtils.format(" OR {}.{} = 0 ", deptAlias, deptField)); + } + } + conditions.add(dataScope); + } + + // 角色都不包含传递过来的权限字符,这个时候sqlString也会为空,所以要限制一下,不查询任何数据 + if (StringUtils.isEmpty(conditions)) + { + sqlString.append(StringUtils.format(" OR {}.{} = 0 ", deptAlias, deptField)); + } + + if (StringUtils.isNotBlank(sqlString.toString())) + { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")"); + } + } + } + + /** + * 拼接权限sql前先清空params.dataScope参数防止注入 + */ + private void clearDataScope(final JoinPoint joinPoint) + { + Object params = joinPoint.getArgs()[0]; + if (StringUtils.isNotNull(params) && params instanceof BaseEntity) + { + BaseEntity baseEntity = (BaseEntity) params; + baseEntity.getParams().put(DATA_SCOPE, ""); + } + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/DataSourceAspect.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/DataSourceAspect.java new file mode 100644 index 000000000..32bdcf343 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/DataSourceAspect.java @@ -0,0 +1,72 @@ +package com.roomroot.framework.aspectj; + +import java.util.Objects; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import com.roomroot.common.annotation.DataSource; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.framework.datasource.DynamicDataSourceContextHolder; + +/** + * 多数据源处理 + * + * @author roomroot + */ +@Aspect +@Order(1) +@Component +public class DataSourceAspect +{ + protected Logger logger = LoggerFactory.getLogger(getClass()); + + @Pointcut("@annotation(com.roomroot.common.annotation.DataSource)" + + "|| @within(com.roomroot.common.annotation.DataSource)") + public void dsPointCut() + { + + } + + @Around("dsPointCut()") + public Object around(ProceedingJoinPoint point) throws Throwable + { + DataSource dataSource = getDataSource(point); + + if (StringUtils.isNotNull(dataSource)) + { + DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name()); + } + + try + { + return point.proceed(); + } + finally + { + // 销毁数据源 在执行方法之后 + DynamicDataSourceContextHolder.clearDataSourceType(); + } + } + + /** + * 获取需要切换的数据源 + */ + public DataSource getDataSource(ProceedingJoinPoint point) + { + MethodSignature signature = (MethodSignature) point.getSignature(); + DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class); + if (Objects.nonNull(dataSource)) + { + return dataSource; + } + + return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/LogAspect.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/LogAspect.java new file mode 100644 index 000000000..5920beaaa --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/LogAspect.java @@ -0,0 +1,264 @@ +package com.roomroot.framework.aspectj; + +import java.util.Collection; +import java.util.Map; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.ArrayUtils; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.AfterReturning; +import org.aspectj.lang.annotation.AfterThrowing; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.NamedThreadLocal; +import org.springframework.stereotype.Component; +import org.springframework.validation.BindingResult; +import org.springframework.web.multipart.MultipartFile; +import com.alibaba.fastjson2.JSON; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.enums.BusinessStatus; +import com.roomroot.common.enums.HttpMethod; +import com.roomroot.common.filter.PropertyPreExcludeFilter; +import com.roomroot.common.utils.ExceptionUtil; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.ServletUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.ip.IpUtils; +import com.roomroot.framework.manager.AsyncManager; +import com.roomroot.framework.manager.factory.AsyncFactory; +import com.roomroot.system.domain.SysOperLog; + +/** + * 操作日志记录处理 + * + * @author roomroot + */ +@Aspect +@Component +public class LogAspect +{ + private static final Logger log = LoggerFactory.getLogger(LogAspect.class); + + /** 排除敏感属性字段 */ + public static final String[] EXCLUDE_PROPERTIES = { "password", "oldPassword", "newPassword", "confirmPassword" }; + + /** 计算操作消耗时间 */ + private static final ThreadLocal TIME_THREADLOCAL = new NamedThreadLocal("Cost Time"); + + /** 参数最大长度限制 */ + private static final int PARAM_MAX_LENGTH = 2000; + + /** + * 处理请求前执行 + */ + @Before(value = "@annotation(controllerLog)") + public void doBefore(JoinPoint joinPoint, Log controllerLog) + { + TIME_THREADLOCAL.set(System.currentTimeMillis()); + } + + /** + * 处理完请求后执行 + * + * @param joinPoint 切点 + */ + @AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult") + public void doAfterReturning(JoinPoint joinPoint, Log controllerLog, Object jsonResult) + { + handleLog(joinPoint, controllerLog, null, jsonResult); + } + + /** + * 拦截异常操作 + * + * @param joinPoint 切点 + * @param e 异常 + */ + @AfterThrowing(value = "@annotation(controllerLog)", throwing = "e") + public void doAfterThrowing(JoinPoint joinPoint, Log controllerLog, Exception e) + { + handleLog(joinPoint, controllerLog, e, null); + } + + protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) + { + try + { + // 获取当前的用户 + LoginUser loginUser = SecurityUtils.getLoginUser(); + + // *========数据库日志=========*// + SysOperLog operLog = new SysOperLog(); + operLog.setStatus(BusinessStatus.SUCCESS.ordinal()); + // 请求的地址 + String ip = IpUtils.getIpAddr(); + operLog.setOperIp(ip); + operLog.setOperUrl(StringUtils.substring(ServletUtils.getRequest().getRequestURI(), 0, 255)); + if (loginUser != null) + { + operLog.setOperName(loginUser.getUsername()); + SysUser currentUser = loginUser.getUser(); + if (StringUtils.isNotNull(currentUser) && StringUtils.isNotNull(currentUser.getDept())) + { + operLog.setDeptName(currentUser.getDept().getDeptName()); + } + } + + if (e != null) + { + operLog.setStatus(BusinessStatus.FAIL.ordinal()); + operLog.setErrorMsg(StringUtils.substring(Convert.toStr(e.getMessage(), ExceptionUtil.getExceptionMessage(e)), 0, 2000)); + } + // 设置方法名称 + String className = joinPoint.getTarget().getClass().getName(); + String methodName = joinPoint.getSignature().getName(); + operLog.setMethod(className + "." + methodName + "()"); + // 设置请求方式 + operLog.setRequestMethod(ServletUtils.getRequest().getMethod()); + // 处理设置注解上的参数 + getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult); + // 设置消耗时间 + operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get()); + // 保存数据库 + AsyncManager.me().execute(AsyncFactory.recordOper(operLog)); + } + catch (Exception exp) + { + // 记录本地异常日志 + log.error("异常信息:{}", exp.getMessage()); + exp.printStackTrace(); + } + finally + { + TIME_THREADLOCAL.remove(); + } + } + + /** + * 获取注解中对方法的描述信息 用于Controller层注解 + * + * @param log 日志 + * @param operLog 操作日志 + * @throws Exception + */ + public void getControllerMethodDescription(JoinPoint joinPoint, Log log, SysOperLog operLog, Object jsonResult) throws Exception + { + // 设置action动作 + operLog.setBusinessType(log.businessType().ordinal()); + // 设置标题 + operLog.setTitle(log.title()); + // 设置操作人类别 + operLog.setOperatorType(log.operatorType().ordinal()); + // 是否需要保存request,参数和值 + if (log.isSaveRequestData()) + { + // 获取参数的信息,传入到数据库中。 + setRequestValue(joinPoint, operLog, log.excludeParamNames()); + } + // 是否需要保存response,参数和值 + if (log.isSaveResponseData() && StringUtils.isNotNull(jsonResult)) + { + operLog.setJsonResult(StringUtils.substring(JSON.toJSONString(jsonResult), 0, 2000)); + } + } + + /** + * 获取请求的参数,放到log中 + * + * @param operLog 操作日志 + * @throws Exception 异常 + */ + private void setRequestValue(JoinPoint joinPoint, SysOperLog operLog, String[] excludeParamNames) throws Exception + { + String requestMethod = operLog.getRequestMethod(); + Map paramsMap = ServletUtils.getParamMap(ServletUtils.getRequest()); + if (StringUtils.isEmpty(paramsMap) && StringUtils.equalsAny(requestMethod, HttpMethod.PUT.name(), HttpMethod.POST.name(), HttpMethod.DELETE.name())) + { + String params = argsArrayToString(joinPoint.getArgs(), excludeParamNames); + operLog.setOperParam(params); + } + else + { + operLog.setOperParam(StringUtils.substring(JSON.toJSONString(paramsMap, excludePropertyPreFilter(excludeParamNames)), 0, PARAM_MAX_LENGTH)); + } + } + + /** + * 参数拼装 + */ + private String argsArrayToString(Object[] paramsArray, String[] excludeParamNames) + { + StringBuilder params = new StringBuilder(); + if (paramsArray != null && paramsArray.length > 0) + { + for (Object o : paramsArray) + { + if (StringUtils.isNotNull(o) && !isFilterObject(o)) + { + try + { + String jsonObj = JSON.toJSONString(o, excludePropertyPreFilter(excludeParamNames)); + params.append(jsonObj).append(" "); + if (params.length() >= PARAM_MAX_LENGTH) + { + return StringUtils.substring(params.toString(), 0, PARAM_MAX_LENGTH); + } + } + catch (Exception e) + { + log.error("请求参数拼装异常 msg:{}, 参数:{}", e.getMessage(), paramsArray, e); + } + } + } + } + return params.toString(); + } + + /** + * 忽略敏感属性 + */ + public PropertyPreExcludeFilter excludePropertyPreFilter(String[] excludeParamNames) + { + return new PropertyPreExcludeFilter().addExcludes(ArrayUtils.addAll(EXCLUDE_PROPERTIES, excludeParamNames)); + } + + /** + * 判断是否需要过滤的对象。 + * + * @param o 对象信息。 + * @return 如果是需要过滤的对象,则返回true;否则返回false。 + */ + @SuppressWarnings("rawtypes") + public boolean isFilterObject(final Object o) + { + Class clazz = o.getClass(); + if (clazz.isArray()) + { + return clazz.getComponentType().isAssignableFrom(MultipartFile.class); + } + else if (Collection.class.isAssignableFrom(clazz)) + { + Collection collection = (Collection) o; + for (Object value : collection) + { + return value instanceof MultipartFile; + } + } + else if (Map.class.isAssignableFrom(clazz)) + { + Map map = (Map) o; + for (Object value : map.entrySet()) + { + Map.Entry entry = (Map.Entry) value; + return entry.getValue() instanceof MultipartFile; + } + } + return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse + || o instanceof BindingResult; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/RateLimiterAspect.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/RateLimiterAspect.java new file mode 100644 index 000000000..cfb972a95 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/aspectj/RateLimiterAspect.java @@ -0,0 +1,89 @@ +package com.roomroot.framework.aspectj; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; +import org.aspectj.lang.JoinPoint; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Before; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.RedisScript; +import org.springframework.stereotype.Component; +import com.roomroot.common.annotation.RateLimiter; +import com.roomroot.common.enums.LimitType; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.ip.IpUtils; + +/** + * 限流处理 + * + * @author roomroot + */ +@Aspect +@Component +public class RateLimiterAspect +{ + private static final Logger log = LoggerFactory.getLogger(RateLimiterAspect.class); + + private RedisTemplate redisTemplate; + + private RedisScript limitScript; + + @Autowired + public void setRedisTemplate1(RedisTemplate redisTemplate) + { + this.redisTemplate = redisTemplate; + } + + @Autowired + public void setLimitScript(RedisScript limitScript) + { + this.limitScript = limitScript; + } + + @Before("@annotation(rateLimiter)") + public void doBefore(JoinPoint point, RateLimiter rateLimiter) throws Throwable + { + int time = rateLimiter.time(); + int count = rateLimiter.count(); + + String combineKey = getCombineKey(rateLimiter, point); + List keys = Collections.singletonList(combineKey); + try + { + Long number = redisTemplate.execute(limitScript, keys, count, time); + if (StringUtils.isNull(number) || number.intValue() > count) + { + throw new ServiceException("访问过于频繁,请稍候再试"); + } + log.info("限制请求'{}',当前请求'{}',缓存key'{}'", count, number.intValue(), combineKey); + } + catch (ServiceException e) + { + throw e; + } + catch (Exception e) + { + throw new RuntimeException("服务器限流异常,请稍候再试"); + } + } + + public String getCombineKey(RateLimiter rateLimiter, JoinPoint point) + { + StringBuffer stringBuffer = new StringBuffer(rateLimiter.key()); + if (rateLimiter.limitType() == LimitType.IP) + { + stringBuffer.append(IpUtils.getIpAddr()).append("-"); + } + MethodSignature signature = (MethodSignature) point.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + stringBuffer.append(targetClass.getName()).append("-").append(method.getName()); + return stringBuffer.toString(); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ApplicationConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ApplicationConfig.java new file mode 100644 index 000000000..a07419b18 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ApplicationConfig.java @@ -0,0 +1,19 @@ +package com.roomroot.framework.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +/** + * 程序注解配置 + * + * @author roomroot + */ +@Configuration +// 表示通过aop框架暴露该代理对象,AopContext能够访问 +@EnableAspectJAutoProxy(exposeProxy = true) +// 指定要扫描的Mapper类的包的路径 +@MapperScan("com.roomroot.**.mapper") +public class ApplicationConfig +{ +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/CaptchaConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/CaptchaConfig.java new file mode 100644 index 000000000..07ff18415 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/CaptchaConfig.java @@ -0,0 +1,83 @@ +package com.roomroot.framework.config; + +import java.util.Properties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.google.code.kaptcha.impl.DefaultKaptcha; +import com.google.code.kaptcha.util.Config; +import static com.google.code.kaptcha.Constants.*; + +/** + * 验证码配置 + * + * @author roomroot + */ +@Configuration +public class CaptchaConfig +{ + @Bean(name = "captchaProducer") + public DefaultKaptcha getKaptchaBean() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "black"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "38"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCode"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "4"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } + + @Bean(name = "captchaProducerMath") + public DefaultKaptcha getKaptchaBeanMath() + { + DefaultKaptcha defaultKaptcha = new DefaultKaptcha(); + Properties properties = new Properties(); + // 是否有边框 默认为true 我们可以自己设置yes,no + properties.setProperty(KAPTCHA_BORDER, "yes"); + // 边框颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_BORDER_COLOR, "105,179,90"); + // 验证码文本字符颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_COLOR, "blue"); + // 验证码图片宽度 默认为200 + properties.setProperty(KAPTCHA_IMAGE_WIDTH, "160"); + // 验证码图片高度 默认为50 + properties.setProperty(KAPTCHA_IMAGE_HEIGHT, "60"); + // 验证码文本字符大小 默认为40 + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_SIZE, "35"); + // KAPTCHA_SESSION_KEY + properties.setProperty(KAPTCHA_SESSION_CONFIG_KEY, "kaptchaCodeMath"); + // 验证码文本生成器 + properties.setProperty(KAPTCHA_TEXTPRODUCER_IMPL, "com.roomroot.framework.config.KaptchaTextCreator"); + // 验证码文本字符间距 默认为2 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_SPACE, "3"); + // 验证码文本字符长度 默认为5 + properties.setProperty(KAPTCHA_TEXTPRODUCER_CHAR_LENGTH, "6"); + // 验证码文本字体样式 默认为new Font("Arial", 1, fontSize), new Font("Courier", 1, fontSize) + properties.setProperty(KAPTCHA_TEXTPRODUCER_FONT_NAMES, "Arial,Courier"); + // 验证码噪点颜色 默认为Color.BLACK + properties.setProperty(KAPTCHA_NOISE_COLOR, "white"); + // 干扰实现类 + properties.setProperty(KAPTCHA_NOISE_IMPL, "com.google.code.kaptcha.impl.NoNoise"); + // 图片样式 水纹com.google.code.kaptcha.impl.WaterRipple 鱼眼com.google.code.kaptcha.impl.FishEyeGimpy 阴影com.google.code.kaptcha.impl.ShadowGimpy + properties.setProperty(KAPTCHA_OBSCURIFICATOR_IMPL, "com.google.code.kaptcha.impl.ShadowGimpy"); + Config config = new Config(properties); + defaultKaptcha.setConfig(config); + return defaultKaptcha; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/DruidConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/DruidConfig.java new file mode 100644 index 000000000..aba67b778 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/DruidConfig.java @@ -0,0 +1,126 @@ +package com.roomroot.framework.config; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import com.alibaba.druid.pool.DruidDataSource; +import com.alibaba.druid.spring.boot4.autoconfigure.DruidDataSourceBuilder; +import com.alibaba.druid.spring.boot4.autoconfigure.properties.DruidStatProperties; +import com.alibaba.druid.util.Utils; +import com.roomroot.common.enums.DataSourceType; +import com.roomroot.common.utils.spring.SpringUtils; +import com.roomroot.framework.config.properties.DruidProperties; +import com.roomroot.framework.datasource.DynamicDataSource; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +/** + * druid 配置多数据源 + * + * @author roomroot + */ +@Configuration +public class DruidConfig +{ + @Bean + @ConfigurationProperties("spring.datasource.druid.master") + public DataSource masterDataSource(DruidProperties druidProperties) + { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + @Bean + @ConfigurationProperties("spring.datasource.druid.slave") + @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true") + public DataSource slaveDataSource(DruidProperties druidProperties) + { + DruidDataSource dataSource = DruidDataSourceBuilder.create().build(); + return druidProperties.dataSource(dataSource); + } + + @Bean(name = "dynamicDataSource") + @Primary + public DynamicDataSource dataSource(DataSource masterDataSource) + { + Map targetDataSources = new HashMap<>(); + targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); + setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource"); + return new DynamicDataSource(masterDataSource, targetDataSources); + } + + /** + * 设置数据源 + * + * @param targetDataSources 备选数据源集合 + * @param sourceName 数据源名称 + * @param beanName bean名称 + */ + public void setDataSource(Map targetDataSources, String sourceName, String beanName) + { + try + { + DataSource dataSource = SpringUtils.getBean(beanName); + targetDataSources.put(sourceName, dataSource); + } + catch (Exception e) + { + } + } + + /** + * 去除监控页面底部的广告 + */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true") + public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties) + { + // 获取web监控页面的参数 + DruidStatProperties.StatViewServlet config = properties.getStatViewServlet(); + // 提取common.js的配置路径 + String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*"; + String commonJsPattern = pattern.replaceAll("\\*", "js/common.js"); + final String filePath = "support/http/resources/js/common.js"; + // 创建filter进行过滤 + Filter filter = new Filter() + { + @Override + public void init(jakarta.servlet.FilterConfig filterConfig) throws ServletException + { + } + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException + { + chain.doFilter(request, response); + // 重置缓冲区,响应头不会被重置 + response.resetBuffer(); + // 获取common.js + String text = Utils.readFromResource(filePath); + // 正则替换banner, 除去底部的广告信息 + text = text.replaceAll("
", ""); + text = text.replaceAll("powered.*?shrek.wang", ""); + response.getWriter().write(text); + } + @Override + public void destroy() + { + } + }; + FilterRegistrationBean registrationBean = new FilterRegistrationBean(); + registrationBean.setFilter(filter); + registrationBean.addUrlPatterns(commonJsPattern); + return registrationBean; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/FastJson2JsonRedisSerializer.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/FastJson2JsonRedisSerializer.java new file mode 100644 index 000000000..3195f7925 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/FastJson2JsonRedisSerializer.java @@ -0,0 +1,52 @@ +package com.roomroot.framework.config; + +import java.nio.charset.Charset; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONReader; +import com.alibaba.fastjson2.JSONWriter; +import com.alibaba.fastjson2.filter.Filter; +import com.roomroot.common.constant.Constants; + +/** + * Redis使用FastJson序列化 + * + * @author roomroot + */ +public class FastJson2JsonRedisSerializer implements RedisSerializer +{ + public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + static final Filter AUTO_TYPE_FILTER = JSONReader.autoTypeFilter(Constants.JSON_WHITELIST_STR); + + private Class clazz; + + public FastJson2JsonRedisSerializer(Class clazz) + { + super(); + this.clazz = clazz; + } + + @Override + public byte[] serialize(T t) throws SerializationException + { + if (t == null) + { + return new byte[0]; + } + return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET); + } + + @Override + public T deserialize(byte[] bytes) throws SerializationException + { + if (bytes == null || bytes.length <= 0) + { + return null; + } + String str = new String(bytes, DEFAULT_CHARSET); + + return JSON.parseObject(str, clazz, AUTO_TYPE_FILTER); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/FilterConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/FilterConfig.java new file mode 100644 index 000000000..873c32349 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/FilterConfig.java @@ -0,0 +1,80 @@ +package com.roomroot.framework.config; + +import java.util.HashMap; +import java.util.Map; +import jakarta.servlet.DispatcherType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.filter.RefererFilter; +import com.roomroot.common.filter.RepeatableFilter; +import com.roomroot.common.filter.XssFilter; +import com.roomroot.common.utils.StringUtils; + +/** + * Filter配置 + * + * @author roomroot + */ +@Configuration +public class FilterConfig +{ + @Value("${xss.excludes}") + private String excludes; + + @Value("${xss.urlPatterns}") + private String urlPatterns; + + @Value("${referer.allowed-domains}") + private String allowedDomains; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(value = "xss.enabled", havingValue = "true") + public FilterRegistrationBean xssFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new XssFilter()); + registration.addUrlPatterns(StringUtils.split(urlPatterns, ",")); + registration.setName("xssFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap(); + initParameters.put("excludes", excludes); + registration.setInitParameters(initParameters); + return registration; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + @ConditionalOnProperty(value = "referer.enabled", havingValue = "true") + public FilterRegistrationBean refererFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setDispatcherTypes(DispatcherType.REQUEST); + registration.setFilter(new RefererFilter()); + registration.addUrlPatterns(Constants.RESOURCE_PREFIX + "/*"); + registration.setName("refererFilter"); + registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); + Map initParameters = new HashMap(); + initParameters.put("allowedDomains", allowedDomains); + registration.setInitParameters(initParameters); + return registration; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Bean + public FilterRegistrationBean someFilterRegistration() + { + FilterRegistrationBean registration = new FilterRegistrationBean(); + registration.setFilter(new RepeatableFilter()); + registration.addUrlPatterns("/*"); + registration.setName("repeatableFilter"); + registration.setOrder(FilterRegistrationBean.LOWEST_PRECEDENCE); + return registration; + } + +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/I18nConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/I18nConfig.java new file mode 100644 index 000000000..a0b9a2796 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/I18nConfig.java @@ -0,0 +1,43 @@ +package com.roomroot.framework.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.LocaleResolver; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import org.springframework.web.servlet.i18n.SessionLocaleResolver; +import com.roomroot.common.constant.Constants; + +/** + * 资源文件配置加载 + * + * @author roomroot + */ +@Configuration +public class I18nConfig implements WebMvcConfigurer +{ + @Bean + public LocaleResolver localeResolver() + { + SessionLocaleResolver slr = new SessionLocaleResolver(); + // 默认语言 + slr.setDefaultLocale(Constants.DEFAULT_LOCALE); + return slr; + } + + @Bean + public LocaleChangeInterceptor localeChangeInterceptor() + { + LocaleChangeInterceptor lci = new LocaleChangeInterceptor(); + // 参数名 + lci.setParamName("lang"); + return lci; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(localeChangeInterceptor()); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/KaptchaTextCreator.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/KaptchaTextCreator.java new file mode 100644 index 000000000..fdfc63202 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/KaptchaTextCreator.java @@ -0,0 +1,68 @@ +package com.roomroot.framework.config; + +import java.util.Random; +import com.google.code.kaptcha.text.impl.DefaultTextCreator; + +/** + * 验证码文本生成器 + * + * @author roomroot + */ +public class KaptchaTextCreator extends DefaultTextCreator +{ + private static final String[] CNUMBERS = "0,1,2,3,4,5,6,7,8,9,10".split(","); + + @Override + public String getText() + { + Integer result = 0; + Random random = new Random(); + int x = random.nextInt(10); + int y = random.nextInt(10); + StringBuilder suChinese = new StringBuilder(); + int randomoperands = random.nextInt(3); + if (randomoperands == 0) + { + result = x * y; + suChinese.append(CNUMBERS[x]); + suChinese.append("*"); + suChinese.append(CNUMBERS[y]); + } + else if (randomoperands == 1) + { + if ((x != 0) && y % x == 0) + { + result = y / x; + suChinese.append(CNUMBERS[y]); + suChinese.append("/"); + suChinese.append(CNUMBERS[x]); + } + else + { + result = x + y; + suChinese.append(CNUMBERS[x]); + suChinese.append("+"); + suChinese.append(CNUMBERS[y]); + } + } + else + { + if (x >= y) + { + result = x - y; + suChinese.append(CNUMBERS[x]); + suChinese.append("-"); + suChinese.append(CNUMBERS[y]); + } + else + { + result = y - x; + suChinese.append(CNUMBERS[y]); + suChinese.append("-"); + suChinese.append(CNUMBERS[x]); + } + } + suChinese.append("=?@" + result); + return suChinese.toString(); + } +} \ No newline at end of file diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/MyBatisConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/MyBatisConfig.java new file mode 100644 index 000000000..0943e72d4 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/MyBatisConfig.java @@ -0,0 +1,136 @@ +package com.roomroot.framework.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import javax.sql.DataSource; +import org.apache.ibatis.session.SqlSessionFactory; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.core.io.support.PathMatchingResourcePatternResolver; +import org.springframework.core.io.support.ResourcePatternResolver; +import org.springframework.core.type.classreading.CachingMetadataReaderFactory; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.util.ClassUtils; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import com.roomroot.common.utils.StringUtils; + +/** + * MyBatis-Plus支持*匹配扫描包 + * + * @author roomroot + */ +@Configuration +public class MyBatisConfig +{ + @Autowired + private Environment env; + + static final String DEFAULT_RESOURCE_PATTERN = "**/*.class"; + + public static String setTypeAliasesPackage(String typeAliasesPackage) + { + ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver(); + MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver); + List allResult = new ArrayList(); + try + { + for (String aliasesPackage : typeAliasesPackage.split(",")) + { + List result = new ArrayList(); + aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + + ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN; + Resource[] resources = resolver.getResources(aliasesPackage); + if (resources != null && resources.length > 0) + { + MetadataReader metadataReader = null; + for (Resource resource : resources) + { + if (resource.isReadable()) + { + metadataReader = metadataReaderFactory.getMetadataReader(resource); + try + { + result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName()); + } + catch (ClassNotFoundException e) + { + e.printStackTrace(); + } + } + } + } + if (result.size() > 0) + { + HashSet hashResult = new HashSet(result); + allResult.addAll(hashResult); + } + } + if (allResult.size() > 0) + { + typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0])); + } + else + { + throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包"); + } + } + catch (IOException e) + { + e.printStackTrace(); + } + return typeAliasesPackage; + } + + public Resource[] resolveMapperLocations(String[] mapperLocations) + { + ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); + List resources = new ArrayList(); + if (mapperLocations != null) + { + for (String mapperLocation : mapperLocations) + { + try + { + Resource[] mappers = resourceResolver.getResources(mapperLocation); + resources.addAll(Arrays.asList(mappers)); + } + catch (IOException e) + { + // ignore + } + } + } + return resources.toArray(new Resource[resources.size()]); + } + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception + { + String typeAliasesPackage = env.getProperty("mybatis-plus.typeAliasesPackage"); + String mapperLocations = env.getProperty("mybatis-plus.mapperLocations"); + String configLocation = env.getProperty("mybatis-plus.configLocation"); + typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage); + + final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean(); + sessionFactory.setDataSource(dataSource); + sessionFactory.setTypeAliasesPackage(typeAliasesPackage); + sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ","))); + sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation)); + + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); + sessionFactory.setPlugins(interceptor); + + return sessionFactory.getObject(); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/RedisConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/RedisConfig.java new file mode 100644 index 000000000..1bf93b191 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/RedisConfig.java @@ -0,0 +1,70 @@ +package com.roomroot.framework.config; + +import org.springframework.cache.annotation.CachingConfigurerSupport; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * redis配置 + * + * @author roomroot + */ +@SuppressWarnings("deprecation") +@Configuration +@EnableCaching +public class RedisConfig extends CachingConfigurerSupport +{ + @Bean + @SuppressWarnings(value = { "unchecked", "rawtypes" }) + public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) + { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(connectionFactory); + + FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class); + + // 使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + template.setValueSerializer(serializer); + + // Hash的key也采用StringRedisSerializer的序列化方式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(serializer); + + template.afterPropertiesSet(); + return template; + } + + @Bean + public DefaultRedisScript limitScript() + { + DefaultRedisScript redisScript = new DefaultRedisScript<>(); + redisScript.setScriptText(limitScriptText()); + redisScript.setResultType(Long.class); + return redisScript; + } + + /** + * 限流脚本 + */ + private String limitScriptText() + { + return "local key = KEYS[1]\n" + + "local count = tonumber(ARGV[1])\n" + + "local time = tonumber(ARGV[2])\n" + + "local current = redis.call('get', key);\n" + + "if current and tonumber(current) > count then\n" + + " return tonumber(current);\n" + + "end\n" + + "current = redis.call('incr', key)\n" + + "if tonumber(current) == 1 then\n" + + " redis.call('expire', key, time)\n" + + "end\n" + + "return tonumber(current);"; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ResourcesConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ResourcesConfig.java new file mode 100644 index 000000000..093429474 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ResourcesConfig.java @@ -0,0 +1,72 @@ +package com.roomroot.framework.config; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import com.roomroot.common.config.RoomRootConfig; +import com.roomroot.common.constant.Constants; +import com.roomroot.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 通用配置 + * + * @author roomroot + */ +@Configuration +public class ResourcesConfig implements WebMvcConfigurer +{ + @Autowired + private RepeatSubmitInterceptor repeatSubmitInterceptor; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) + { + /** 本地文件上传路径 */ + registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**") + .addResourceLocations("file:" + RoomRootConfig.getProfile() + "/"); + + /** swagger配置 */ + registry.addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/") + .setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic()); + } + + /** + * 自定义拦截规则 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) + { + registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); + } + + /** + * 跨域配置 + */ + @Bean + public CorsFilter corsFilter() + { + CorsConfiguration config = new CorsConfiguration(); + // 设置访问源地址 + config.addAllowedOriginPattern("*"); + // 设置访问源请求头 + config.addAllowedHeader("*"); + // 设置访问源请求方法 + config.addAllowedMethod("*"); + // 有效期 1800秒 + config.setMaxAge(1800L); + // 添加映射路径,拦截一切请求 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + // 返回新的CorsFilter + return new CorsFilter(source); + } +} \ No newline at end of file diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/SecurityConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/SecurityConfig.java new file mode 100644 index 000000000..80ca0daad --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/SecurityConfig.java @@ -0,0 +1,128 @@ +package com.roomroot.framework.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.logout.LogoutFilter; +import org.springframework.web.filter.CorsFilter; +import com.roomroot.framework.config.properties.PermitAllUrlProperties; +import com.roomroot.framework.security.filter.JwtAuthenticationTokenFilter; +import com.roomroot.framework.security.handle.AuthenticationEntryPointImpl; +import com.roomroot.framework.security.handle.LogoutSuccessHandlerImpl; + +/** + * spring security配置 + * + * @author roomroot + */ +@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true) +@Configuration +public class SecurityConfig +{ + /** + * 认证失败处理类 + */ + @Autowired + private AuthenticationEntryPointImpl unauthorizedHandler; + + /** + * 退出处理类 + */ + @Autowired + private LogoutSuccessHandlerImpl logoutSuccessHandler; + + /** + * token认证过滤器 + */ + @Autowired + private JwtAuthenticationTokenFilter authenticationTokenFilter; + + /** + * 跨域过滤器 + */ + @Autowired + private CorsFilter corsFilter; + + /** + * 允许匿名访问的地址 + */ + @Autowired + private PermitAllUrlProperties permitAllUrl; + + /** + * 身份验证实现 + */ + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception + { + return authenticationConfiguration.getAuthenticationManager(); + } + + /** + * anyRequest | 匹配所有请求路径 + * access | SpringEl表达式结果为true时可以访问 + * anonymous | 匿名可以访问 + * denyAll | 用户不能访问 + * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录) + * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问 + * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问 + * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问 + * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问 + * hasRole | 如果有参数,参数表示角色,则其角色可以访问 + * permitAll | 用户可以任意访问 + * rememberMe | 允许通过remember-me登录的用户访问 + * authenticated | 用户登录后可访问 + */ + @Bean + protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception + { + return httpSecurity + // CSRF禁用,因为不使用session + .csrf(csrf -> csrf.disable()) + // 禁用HTTP响应标头 + .headers((headersCustomizer) -> { + headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin()); + }) + // 认证失败处理类 + .exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler)) + // 基于token,所以不需要session + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + // 注解标记允许匿名访问的url + .authorizeHttpRequests((requests) -> { + permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll()); + // 对于登录login 注册register 验证码captchaImage 允许匿名访问 + requests.requestMatchers("/login", "/register", "/captchaImage").permitAll() + // 静态资源,可匿名访问 + .requestMatchers(HttpMethod.GET, "/", "/*.html", "/**.html", "/**.css", "/**.js", "/profile/**").permitAll() + .requestMatchers("/swagger-ui.html", "/v3/api-docs/**", "/swagger-ui/**", "/druid/**").permitAll() + // 除上面外的所有请求全部需要鉴权认证 + .anyRequest().authenticated(); + }) + // 添加Logout filter + .logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler)) + // 添加JWT filter + .addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class) + // 添加CORS filter + .addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class) + .addFilterBefore(corsFilter, LogoutFilter.class) + .build(); + } + + /** + * 强散列哈希加密实现 + */ + @Bean + public BCryptPasswordEncoder bCryptPasswordEncoder() + { + return new BCryptPasswordEncoder(); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ServerConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ServerConfig.java new file mode 100644 index 000000000..7a3fc41bb --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ServerConfig.java @@ -0,0 +1,32 @@ +package com.roomroot.framework.config; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; +import com.roomroot.common.utils.ServletUtils; + +/** + * 服务相关配置 + * + * @author roomroot + */ +@Component +public class ServerConfig +{ + /** + * 获取完整的请求路径,包括:域名,端口,上下文访问路径 + * + * @return 服务地址 + */ + public String getUrl() + { + HttpServletRequest request = ServletUtils.getRequest(); + return getDomain(request); + } + + public static String getDomain(HttpServletRequest request) + { + StringBuffer url = request.getRequestURL(); + String contextPath = request.getServletContext().getContextPath(); + return url.delete(url.length() - request.getRequestURI().length(), url.length()).append(contextPath).toString(); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ThreadPoolConfig.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ThreadPoolConfig.java new file mode 100644 index 000000000..cc6557810 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/ThreadPoolConfig.java @@ -0,0 +1,63 @@ +package com.roomroot.framework.config; + +import com.roomroot.common.utils.Threads; +import org.apache.commons.lang3.concurrent.BasicThreadFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadPoolExecutor; + +/** + * 线程池配置 + * + * @author roomroot + **/ +@Configuration +public class ThreadPoolConfig +{ + // 核心线程池大小 + private int corePoolSize = 50; + + // 最大可创建的线程数 + private int maxPoolSize = 200; + + // 队列最大长度 + private int queueCapacity = 1000; + + // 线程池维护线程所允许的空闲时间 + private int keepAliveSeconds = 300; + + @Bean(name = "threadPoolTaskExecutor") + public ThreadPoolTaskExecutor threadPoolTaskExecutor() + { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setMaxPoolSize(maxPoolSize); + executor.setCorePoolSize(corePoolSize); + executor.setQueueCapacity(queueCapacity); + executor.setKeepAliveSeconds(keepAliveSeconds); + // 线程池对拒绝任务(无线程可用)的处理策略 + executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); + return executor; + } + + /** + * 执行周期性或定时任务 + */ + @Bean(name = "scheduledExecutorService") + protected ScheduledExecutorService scheduledExecutorService() + { + return new ScheduledThreadPoolExecutor(corePoolSize, + BasicThreadFactory.builder().namingPattern("schedule-pool-%d").daemon(true).build(), + new ThreadPoolExecutor.CallerRunsPolicy()) + { + @Override + protected void afterExecute(Runnable r, Throwable t) + { + super.afterExecute(r, t); + Threads.printException(r, t); + } + }; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/properties/DruidProperties.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/properties/DruidProperties.java new file mode 100644 index 000000000..fee32d50d --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/properties/DruidProperties.java @@ -0,0 +1,89 @@ +package com.roomroot.framework.config.properties; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import com.alibaba.druid.pool.DruidDataSource; + +/** + * druid 配置属性 + * + * @author roomroot + */ +@Configuration +public class DruidProperties +{ + @Value("${spring.datasource.druid.initialSize}") + private int initialSize; + + @Value("${spring.datasource.druid.minIdle}") + private int minIdle; + + @Value("${spring.datasource.druid.maxActive}") + private int maxActive; + + @Value("${spring.datasource.druid.maxWait}") + private int maxWait; + + @Value("${spring.datasource.druid.connectTimeout}") + private int connectTimeout; + + @Value("${spring.datasource.druid.socketTimeout}") + private int socketTimeout; + + @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}") + private int timeBetweenEvictionRunsMillis; + + @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}") + private int minEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}") + private int maxEvictableIdleTimeMillis; + + @Value("${spring.datasource.druid.validationQuery}") + private String validationQuery; + + @Value("${spring.datasource.druid.testWhileIdle}") + private boolean testWhileIdle; + + @Value("${spring.datasource.druid.testOnBorrow}") + private boolean testOnBorrow; + + @Value("${spring.datasource.druid.testOnReturn}") + private boolean testOnReturn; + + public DruidDataSource dataSource(DruidDataSource datasource) + { + /** 配置初始化大小、最小、最大 */ + datasource.setInitialSize(initialSize); + datasource.setMaxActive(maxActive); + datasource.setMinIdle(minIdle); + + /** 配置获取连接等待超时的时间 */ + datasource.setMaxWait(maxWait); + + /** 配置驱动连接超时时间,检测数据库建立连接的超时时间,单位是毫秒 */ + datasource.setConnectTimeout(connectTimeout); + + /** 配置网络超时时间,等待数据库操作完成的网络超时时间,单位是毫秒 */ + datasource.setSocketTimeout(socketTimeout); + + /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */ + datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); + + /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */ + datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); + datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis); + + /** + * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。 + */ + datasource.setValidationQuery(validationQuery); + /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */ + datasource.setTestWhileIdle(testWhileIdle); + /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnBorrow(testOnBorrow); + /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */ + datasource.setTestOnReturn(testOnReturn); + return datasource; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/properties/PermitAllUrlProperties.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/properties/PermitAllUrlProperties.java new file mode 100644 index 000000000..1c418e9d9 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/config/properties/PermitAllUrlProperties.java @@ -0,0 +1,74 @@ +package com.roomroot.framework.config.properties; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Pattern; +import org.apache.commons.lang3.RegExUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.mvc.method.RequestMappingInfo; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; +import com.roomroot.common.annotation.Anonymous; + +/** + * 设置Anonymous注解允许匿名访问的url + * + * @author roomroot + */ +@Configuration +public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware +{ + private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}"); + + private ApplicationContext applicationContext; + + private List urls = new ArrayList<>(); + + public String ASTERISK = "*"; + + @SuppressWarnings("deprecation") + @Override + public void afterPropertiesSet() + { + RequestMappingHandlerMapping mapping = applicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class); + Map map = mapping.getHandlerMethods(); + + map.keySet().forEach(info -> { + HandlerMethod handlerMethod = map.get(info); + + // 获取方法上边的注解 替代path variable 为 * + Anonymous method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Anonymous.class); + Optional.ofNullable(method).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues()) + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + + // 获取类上边的注解, 替代path variable 为 * + Anonymous controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Anonymous.class); + Optional.ofNullable(controller).ifPresent(anonymous -> Objects.requireNonNull(info.getPathPatternsCondition().getPatternValues()) + .forEach(url -> urls.add(RegExUtils.replaceAll(url, PATTERN, ASTERISK)))); + }); + } + + @Override + public void setApplicationContext(ApplicationContext context) throws BeansException + { + this.applicationContext = context; + } + + public List getUrls() + { + return urls; + } + + public void setUrls(List urls) + { + this.urls = urls; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/datasource/DynamicDataSource.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/datasource/DynamicDataSource.java new file mode 100644 index 000000000..86131d4a2 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/datasource/DynamicDataSource.java @@ -0,0 +1,26 @@ +package com.roomroot.framework.datasource; + +import java.util.Map; +import javax.sql.DataSource; +import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; + +/** + * 动态数据源 + * + * @author roomroot + */ +public class DynamicDataSource extends AbstractRoutingDataSource +{ + public DynamicDataSource(DataSource defaultTargetDataSource, Map targetDataSources) + { + super.setDefaultTargetDataSource(defaultTargetDataSource); + super.setTargetDataSources(targetDataSources); + super.afterPropertiesSet(); + } + + @Override + protected Object determineCurrentLookupKey() + { + return DynamicDataSourceContextHolder.getDataSourceType(); + } +} \ No newline at end of file diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/datasource/DynamicDataSourceContextHolder.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/datasource/DynamicDataSourceContextHolder.java new file mode 100644 index 000000000..c6639018c --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/datasource/DynamicDataSourceContextHolder.java @@ -0,0 +1,45 @@ +package com.roomroot.framework.datasource; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * 数据源切换处理 + * + * @author roomroot + */ +public class DynamicDataSourceContextHolder +{ + public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class); + + /** + * 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本, + * 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 + */ + private static final ThreadLocal CONTEXT_HOLDER = new ThreadLocal<>(); + + /** + * 设置数据源的变量 + */ + public static void setDataSourceType(String dsType) + { + log.info("切换到{}数据源", dsType); + CONTEXT_HOLDER.set(dsType); + } + + /** + * 获得数据源的变量 + */ + public static String getDataSourceType() + { + return CONTEXT_HOLDER.get(); + } + + /** + * 清空数据源变量 + */ + public static void clearDataSourceType() + { + CONTEXT_HOLDER.remove(); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/interceptor/RepeatSubmitInterceptor.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/interceptor/RepeatSubmitInterceptor.java new file mode 100644 index 000000000..7ef49e436 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/interceptor/RepeatSubmitInterceptor.java @@ -0,0 +1,56 @@ +package com.roomroot.framework.interceptor; + +import java.lang.reflect.Method; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import com.alibaba.fastjson2.JSON; +import com.roomroot.common.annotation.RepeatSubmit; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.utils.ServletUtils; + +/** + * 防止重复提交拦截器 + * + * @author roomroot + */ +@Component +public abstract class RepeatSubmitInterceptor implements HandlerInterceptor +{ + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception + { + if (handler instanceof HandlerMethod) + { + HandlerMethod handlerMethod = (HandlerMethod) handler; + Method method = handlerMethod.getMethod(); + RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class); + if (annotation != null) + { + if (this.isRepeatSubmit(request, annotation)) + { + AjaxResult ajaxResult = AjaxResult.error(annotation.message()); + ServletUtils.renderString(response, JSON.toJSONString(ajaxResult)); + return false; + } + } + return true; + } + else + { + return true; + } + } + + /** + * 验证是否重复提交由子类实现具体的防重复提交的规则 + * + * @param request 请求信息 + * @param annotation 防重复注解参数 + * @return 结果 + * @throws Exception + */ + public abstract boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation); +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/interceptor/impl/SameUrlDataInterceptor.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/interceptor/impl/SameUrlDataInterceptor.java new file mode 100644 index 000000000..4233aa99f --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/interceptor/impl/SameUrlDataInterceptor.java @@ -0,0 +1,110 @@ +package com.roomroot.framework.interceptor.impl; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.roomroot.common.annotation.RepeatSubmit; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.core.redis.RedisCache; +import com.roomroot.common.filter.RepeatedlyRequestWrapper; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.http.HttpHelper; +import com.roomroot.framework.interceptor.RepeatSubmitInterceptor; + +/** + * 判断请求url和数据是否和上一次相同, + * 如果和上次相同,则是重复提交表单。 有效时间为10秒内。 + * + * @author roomroot + */ +@Component +public class SameUrlDataInterceptor extends RepeatSubmitInterceptor +{ + public final String REPEAT_PARAMS = "repeatParams"; + + public final String REPEAT_TIME = "repeatTime"; + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + @Autowired + private RedisCache redisCache; + + @SuppressWarnings("unchecked") + @Override + public boolean isRepeatSubmit(HttpServletRequest request, RepeatSubmit annotation) + { + String nowParams = ""; + if (request instanceof RepeatedlyRequestWrapper) + { + RepeatedlyRequestWrapper repeatedlyRequest = (RepeatedlyRequestWrapper) request; + nowParams = HttpHelper.getBodyString(repeatedlyRequest); + } + + // body参数为空,获取Parameter的数据 + if (StringUtils.isEmpty(nowParams)) + { + nowParams = JSON.toJSONString(request.getParameterMap()); + } + Map nowDataMap = new HashMap(); + nowDataMap.put(REPEAT_PARAMS, nowParams); + nowDataMap.put(REPEAT_TIME, System.currentTimeMillis()); + + // 请求地址(作为存放cache的key值) + String url = request.getRequestURI(); + + // 唯一值(没有消息头则使用请求地址) + String submitKey = StringUtils.trimToEmpty(request.getHeader(header)); + + // 唯一标识(指定key + url + 消息头) + String cacheRepeatKey = CacheConstants.REPEAT_SUBMIT_KEY + url + submitKey; + + Object sessionObj = redisCache.getCacheObject(cacheRepeatKey); + if (sessionObj != null) + { + Map sessionMap = (Map) sessionObj; + if (sessionMap.containsKey(url)) + { + Map preDataMap = (Map) sessionMap.get(url); + if (compareParams(nowDataMap, preDataMap) && compareTime(nowDataMap, preDataMap, annotation.interval())) + { + return true; + } + } + } + Map cacheMap = new HashMap(); + cacheMap.put(url, nowDataMap); + redisCache.setCacheObject(cacheRepeatKey, cacheMap, annotation.interval(), TimeUnit.MILLISECONDS); + return false; + } + + /** + * 判断参数是否相同 + */ + private boolean compareParams(Map nowMap, Map preMap) + { + String nowParams = (String) nowMap.get(REPEAT_PARAMS); + String preParams = (String) preMap.get(REPEAT_PARAMS); + return nowParams.equals(preParams); + } + + /** + * 判断两次间隔时间 + */ + private boolean compareTime(Map nowMap, Map preMap, int interval) + { + long time1 = (Long) nowMap.get(REPEAT_TIME); + long time2 = (Long) preMap.get(REPEAT_TIME); + if ((time1 - time2) < interval) + { + return true; + } + return false; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/manager/AsyncManager.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/manager/AsyncManager.java new file mode 100644 index 000000000..92e9f81c5 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/manager/AsyncManager.java @@ -0,0 +1,55 @@ +package com.roomroot.framework.manager; + +import java.util.TimerTask; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import com.roomroot.common.utils.Threads; +import com.roomroot.common.utils.spring.SpringUtils; + +/** + * 异步任务管理器 + * + * @author roomroot + */ +public class AsyncManager +{ + /** + * 操作延迟10毫秒 + */ + private final int OPERATE_DELAY_TIME = 10; + + /** + * 异步操作任务调度线程池 + */ + private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService"); + + /** + * 单例模式 + */ + private AsyncManager(){} + + private static AsyncManager me = new AsyncManager(); + + public static AsyncManager me() + { + return me; + } + + /** + * 执行任务 + * + * @param task 任务 + */ + public void execute(TimerTask task) + { + executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS); + } + + /** + * 停止任务线程池 + */ + public void shutdown() + { + Threads.shutdownAndAwaitTermination(executor); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/manager/ShutdownManager.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/manager/ShutdownManager.java new file mode 100644 index 000000000..29a933751 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/manager/ShutdownManager.java @@ -0,0 +1,39 @@ +package com.roomroot.framework.manager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; +import jakarta.annotation.PreDestroy; + +/** + * 确保应用退出时能关闭后台线程 + * + * @author roomroot + */ +@Component +public class ShutdownManager +{ + private static final Logger logger = LoggerFactory.getLogger("sys-user"); + + @PreDestroy + public void destroy() + { + shutdownAsyncManager(); + } + + /** + * 停止异步执行任务 + */ + private void shutdownAsyncManager() + { + try + { + logger.info("====关闭后台任务任务线程池===="); + AsyncManager.me().shutdown(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + } + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/manager/factory/AsyncFactory.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/manager/factory/AsyncFactory.java new file mode 100644 index 000000000..4a0fe4f1a --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/manager/factory/AsyncFactory.java @@ -0,0 +1,102 @@ +package com.roomroot.framework.manager.factory; + +import java.util.TimerTask; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.utils.LogUtils; +import com.roomroot.common.utils.ServletUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.http.UserAgentUtils; +import com.roomroot.common.utils.ip.AddressUtils; +import com.roomroot.common.utils.ip.IpUtils; +import com.roomroot.common.utils.spring.SpringUtils; +import com.roomroot.system.domain.SysLogininfor; +import com.roomroot.system.domain.SysOperLog; +import com.roomroot.system.service.ISysLogininforService; +import com.roomroot.system.service.ISysOperLogService; + +/** + * 异步工厂(产生任务用) + * + * @author roomroot + */ +public class AsyncFactory +{ + private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user"); + + /** + * 记录登录信息 + * + * @param username 用户名 + * @param status 状态 + * @param message 消息 + * @param args 列表 + * @return 任务task + */ + public static TimerTask recordLogininfor(final String username, final String status, final String message, + final Object... args) + { + final String userAgent = ServletUtils.getRequest().getHeader("User-Agent"); + final String ip = IpUtils.getIpAddr(); + return new TimerTask() + { + @Override + public void run() + { + String address = AddressUtils.getRealAddressByIP(ip); + StringBuilder s = new StringBuilder(); + s.append(LogUtils.getBlock(ip)); + s.append(address); + s.append(LogUtils.getBlock(username)); + s.append(LogUtils.getBlock(status)); + s.append(LogUtils.getBlock(message)); + // 打印信息到日志 + sys_user_logger.info(s.toString(), args); + // 获取客户端操作系统 + String os = UserAgentUtils.getOperatingSystem(userAgent); + // 获取客户端浏览器 + String browser = UserAgentUtils.getBrowser(userAgent); + // 封装对象 + SysLogininfor logininfor = new SysLogininfor(); + logininfor.setUserName(username); + logininfor.setIpaddr(ip); + logininfor.setLoginLocation(address); + logininfor.setBrowser(browser); + logininfor.setOs(os); + logininfor.setMsg(message); + // 日志状态 + if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER)) + { + logininfor.setStatus(Constants.SUCCESS); + } + else if (Constants.LOGIN_FAIL.equals(status)) + { + logininfor.setStatus(Constants.FAIL); + } + // 插入数据 + SpringUtils.getBean(ISysLogininforService.class).insertLogininfor(logininfor); + } + }; + } + + /** + * 操作日志记录 + * + * @param operLog 操作日志信息 + * @return 任务task + */ + public static TimerTask recordOper(final SysOperLog operLog) + { + return new TimerTask() + { + @Override + public void run() + { + // 远程查询操作地点 + operLog.setOperLocation(AddressUtils.getRealAddressByIP(operLog.getOperIp())); + SpringUtils.getBean(ISysOperLogService.class).insertOperlog(operLog); + } + }; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/context/AuthenticationContextHolder.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/context/AuthenticationContextHolder.java new file mode 100644 index 000000000..ba95a29b3 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/context/AuthenticationContextHolder.java @@ -0,0 +1,28 @@ +package com.roomroot.framework.security.context; + +import org.springframework.security.core.Authentication; + +/** + * 身份验证信息 + * + * @author roomroot + */ +public class AuthenticationContextHolder +{ + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + public static Authentication getContext() + { + return contextHolder.get(); + } + + public static void setContext(Authentication context) + { + contextHolder.set(context); + } + + public static void clearContext() + { + contextHolder.remove(); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/context/PermissionContextHolder.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/context/PermissionContextHolder.java new file mode 100644 index 000000000..77c99701e --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/context/PermissionContextHolder.java @@ -0,0 +1,27 @@ +package com.roomroot.framework.security.context; + +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; +import com.roomroot.common.core.text.Convert; + +/** + * 权限信息 + * + * @author roomroot + */ +public class PermissionContextHolder +{ + private static final String PERMISSION_CONTEXT_ATTRIBUTES = "PERMISSION_CONTEXT"; + + public static void setContext(String permission) + { + RequestContextHolder.currentRequestAttributes().setAttribute(PERMISSION_CONTEXT_ATTRIBUTES, permission, + RequestAttributes.SCOPE_REQUEST); + } + + public static String getContext() + { + return Convert.toStr(RequestContextHolder.currentRequestAttributes().getAttribute(PERMISSION_CONTEXT_ATTRIBUTES, + RequestAttributes.SCOPE_REQUEST)); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/filter/JwtAuthenticationTokenFilter.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/filter/JwtAuthenticationTokenFilter.java new file mode 100644 index 000000000..1ad0df1ce --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/filter/JwtAuthenticationTokenFilter.java @@ -0,0 +1,44 @@ +package com.roomroot.framework.security.filter; + +import java.io.IOException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.framework.web.service.TokenService; + +/** + * token过滤器 验证token有效性 + * + * @author roomroot + */ +@Component +public class JwtAuthenticationTokenFilter extends OncePerRequestFilter +{ + @Autowired + private TokenService tokenService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) + throws ServletException, IOException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())) + { + tokenService.verifyToken(loginUser); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + chain.doFilter(request, response); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/handle/AuthenticationEntryPointImpl.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/handle/AuthenticationEntryPointImpl.java new file mode 100644 index 000000000..0dffca73a --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/handle/AuthenticationEntryPointImpl.java @@ -0,0 +1,34 @@ +package com.roomroot.framework.security.handle; + +import java.io.IOException; +import java.io.Serializable; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; +import com.alibaba.fastjson2.JSON; +import com.roomroot.common.constant.HttpStatus; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.utils.ServletUtils; +import com.roomroot.common.utils.StringUtils; + +/** + * 认证失败处理类 返回未授权 + * + * @author roomroot + */ +@Component +public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable +{ + private static final long serialVersionUID = -8970718410437077606L; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) + throws IOException + { + int code = HttpStatus.UNAUTHORIZED; + String msg = StringUtils.format("请求访问:{},认证失败,无法访问系统资源", request.getRequestURI()); + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.error(code, msg))); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/handle/LogoutSuccessHandlerImpl.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/handle/LogoutSuccessHandlerImpl.java new file mode 100644 index 000000000..b627718bc --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/security/handle/LogoutSuccessHandlerImpl.java @@ -0,0 +1,53 @@ +package com.roomroot.framework.security.handle; + +import java.io.IOException; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import com.alibaba.fastjson2.JSON; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.utils.MessageUtils; +import com.roomroot.common.utils.ServletUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.framework.manager.AsyncManager; +import com.roomroot.framework.manager.factory.AsyncFactory; +import com.roomroot.framework.web.service.TokenService; + +/** + * 自定义退出处理类 返回成功 + * + * @author roomroot + */ +@Configuration +public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler +{ + @Autowired + private TokenService tokenService; + + /** + * 退出处理 + * + * @return + */ + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) + throws IOException, ServletException + { + LoginUser loginUser = tokenService.getLoginUser(request); + if (StringUtils.isNotNull(loginUser)) + { + String userName = loginUser.getUsername(); + // 删除用户缓存记录 + tokenService.delLoginUser(loginUser.getToken()); + // 记录用户退出日志 + AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGOUT, MessageUtils.message("user.logout.success"))); + } + ServletUtils.renderString(response, JSON.toJSONString(AjaxResult.success(MessageUtils.message("user.logout.success")))); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/Server.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/Server.java new file mode 100644 index 000000000..48595f564 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/Server.java @@ -0,0 +1,240 @@ +package com.roomroot.framework.web.domain; + +import java.net.UnknownHostException; +import java.util.LinkedList; +import java.util.List; +import java.util.Properties; +import com.roomroot.common.utils.Arith; +import com.roomroot.common.utils.ip.IpUtils; +import com.roomroot.framework.web.domain.server.Cpu; +import com.roomroot.framework.web.domain.server.Jvm; +import com.roomroot.framework.web.domain.server.Mem; +import com.roomroot.framework.web.domain.server.Sys; +import com.roomroot.framework.web.domain.server.SysFile; +import oshi.SystemInfo; +import oshi.hardware.CentralProcessor; +import oshi.hardware.CentralProcessor.TickType; +import oshi.hardware.GlobalMemory; +import oshi.hardware.HardwareAbstractionLayer; +import oshi.software.os.FileSystem; +import oshi.software.os.OSFileStore; +import oshi.software.os.OperatingSystem; +import oshi.util.Util; + +/** + * 服务器相关信息 + * + * @author roomroot + */ +public class Server +{ + private static final int OSHI_WAIT_SECOND = 1000; + + /** + * CPU相关信息 + */ + private Cpu cpu = new Cpu(); + + /** + * 內存相关信息 + */ + private Mem mem = new Mem(); + + /** + * JVM相关信息 + */ + private Jvm jvm = new Jvm(); + + /** + * 服务器相关信息 + */ + private Sys sys = new Sys(); + + /** + * 磁盘相关信息 + */ + private List sysFiles = new LinkedList(); + + public Cpu getCpu() + { + return cpu; + } + + public void setCpu(Cpu cpu) + { + this.cpu = cpu; + } + + public Mem getMem() + { + return mem; + } + + public void setMem(Mem mem) + { + this.mem = mem; + } + + public Jvm getJvm() + { + return jvm; + } + + public void setJvm(Jvm jvm) + { + this.jvm = jvm; + } + + public Sys getSys() + { + return sys; + } + + public void setSys(Sys sys) + { + this.sys = sys; + } + + public List getSysFiles() + { + return sysFiles; + } + + public void setSysFiles(List sysFiles) + { + this.sysFiles = sysFiles; + } + + public void copyTo() throws Exception + { + SystemInfo si = new SystemInfo(); + HardwareAbstractionLayer hal = si.getHardware(); + + setCpuInfo(hal.getProcessor()); + + setMemInfo(hal.getMemory()); + + setSysInfo(); + + setJvmInfo(); + + setSysFiles(si.getOperatingSystem()); + } + + /** + * 设置CPU信息 + */ + private void setCpuInfo(CentralProcessor processor) + { + // CPU信息 + long[] prevTicks = processor.getSystemCpuLoadTicks(); + Util.sleep(OSHI_WAIT_SECOND); + long[] ticks = processor.getSystemCpuLoadTicks(); + long nice = ticks[TickType.NICE.getIndex()] - prevTicks[TickType.NICE.getIndex()]; + long irq = ticks[TickType.IRQ.getIndex()] - prevTicks[TickType.IRQ.getIndex()]; + long softirq = ticks[TickType.SOFTIRQ.getIndex()] - prevTicks[TickType.SOFTIRQ.getIndex()]; + long steal = ticks[TickType.STEAL.getIndex()] - prevTicks[TickType.STEAL.getIndex()]; + long cSys = ticks[TickType.SYSTEM.getIndex()] - prevTicks[TickType.SYSTEM.getIndex()]; + long user = ticks[TickType.USER.getIndex()] - prevTicks[TickType.USER.getIndex()]; + long iowait = ticks[TickType.IOWAIT.getIndex()] - prevTicks[TickType.IOWAIT.getIndex()]; + long idle = ticks[TickType.IDLE.getIndex()] - prevTicks[TickType.IDLE.getIndex()]; + long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal; + cpu.setCpuNum(processor.getLogicalProcessorCount()); + cpu.setTotal(totalCpu); + cpu.setSys(cSys); + cpu.setUsed(user); + cpu.setWait(iowait); + cpu.setFree(idle); + } + + /** + * 设置内存信息 + */ + private void setMemInfo(GlobalMemory memory) + { + mem.setTotal(memory.getTotal()); + mem.setUsed(memory.getTotal() - memory.getAvailable()); + mem.setFree(memory.getAvailable()); + } + + /** + * 设置服务器信息 + */ + private void setSysInfo() + { + Properties props = System.getProperties(); + sys.setComputerName(IpUtils.getHostName()); + sys.setComputerIp(IpUtils.getHostIp()); + sys.setOsName(props.getProperty("os.name")); + sys.setOsArch(props.getProperty("os.arch")); + sys.setUserDir(props.getProperty("user.dir")); + } + + /** + * 设置Java虚拟机 + */ + private void setJvmInfo() throws UnknownHostException + { + Properties props = System.getProperties(); + jvm.setTotal(Runtime.getRuntime().totalMemory()); + jvm.setMax(Runtime.getRuntime().maxMemory()); + jvm.setFree(Runtime.getRuntime().freeMemory()); + jvm.setVersion(props.getProperty("java.version")); + jvm.setHome(props.getProperty("java.home")); + } + + /** + * 设置磁盘信息 + */ + private void setSysFiles(OperatingSystem os) + { + FileSystem fileSystem = os.getFileSystem(); + List fsArray = fileSystem.getFileStores(); + for (OSFileStore fs : fsArray) + { + long free = fs.getUsableSpace(); + long total = fs.getTotalSpace(); + long used = total - free; + SysFile sysFile = new SysFile(); + sysFile.setDirName(fs.getMount()); + sysFile.setSysTypeName(fs.getType()); + sysFile.setTypeName(fs.getName()); + sysFile.setTotal(convertFileSize(total)); + sysFile.setFree(convertFileSize(free)); + sysFile.setUsed(convertFileSize(used)); + sysFile.setUsage(Arith.mul(Arith.div(used, total, 4), 100)); + sysFiles.add(sysFile); + } + } + + /** + * 字节转换 + * + * @param size 字节大小 + * @return 转换后值 + */ + public String convertFileSize(long size) + { + long kb = 1024; + long mb = kb * 1024; + long gb = mb * 1024; + if (size >= gb) + { + return String.format("%.1f GB", (float) size / gb); + } + else if (size >= mb) + { + float f = (float) size / mb; + return String.format(f > 100 ? "%.0f MB" : "%.1f MB", f); + } + else if (size >= kb) + { + float f = (float) size / kb; + return String.format(f > 100 ? "%.0f KB" : "%.1f KB", f); + } + else + { + return String.format("%d B", size); + } + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Cpu.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Cpu.java new file mode 100644 index 000000000..91e85d2b8 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Cpu.java @@ -0,0 +1,101 @@ +package com.roomroot.framework.web.domain.server; + +import com.roomroot.common.utils.Arith; + +/** + * CPU相关信息 + * + * @author roomroot + */ +public class Cpu +{ + /** + * 核心数 + */ + private int cpuNum; + + /** + * CPU总的使用率 + */ + private double total; + + /** + * CPU系统使用率 + */ + private double sys; + + /** + * CPU用户使用率 + */ + private double used; + + /** + * CPU当前等待率 + */ + private double wait; + + /** + * CPU当前空闲率 + */ + private double free; + + public int getCpuNum() + { + return cpuNum; + } + + public void setCpuNum(int cpuNum) + { + this.cpuNum = cpuNum; + } + + public double getTotal() + { + return Arith.round(Arith.mul(total, 100), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getSys() + { + return Arith.round(Arith.mul(sys / total, 100), 2); + } + + public void setSys(double sys) + { + this.sys = sys; + } + + public double getUsed() + { + return Arith.round(Arith.mul(used / total, 100), 2); + } + + public void setUsed(double used) + { + this.used = used; + } + + public double getWait() + { + return Arith.round(Arith.mul(wait / total, 100), 2); + } + + public void setWait(double wait) + { + this.wait = wait; + } + + public double getFree() + { + return Arith.round(Arith.mul(free / total, 100), 2); + } + + public void setFree(double free) + { + this.free = free; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Jvm.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Jvm.java new file mode 100644 index 000000000..aa82ac751 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Jvm.java @@ -0,0 +1,130 @@ +package com.roomroot.framework.web.domain.server; + +import java.lang.management.ManagementFactory; +import com.roomroot.common.utils.Arith; +import com.roomroot.common.utils.DateUtils; + +/** + * JVM相关信息 + * + * @author roomroot + */ +public class Jvm +{ + /** + * 当前JVM占用的内存总数(M) + */ + private double total; + + /** + * JVM最大可用内存总数(M) + */ + private double max; + + /** + * JVM空闲内存(M) + */ + private double free; + + /** + * JDK版本 + */ + private String version; + + /** + * JDK路径 + */ + private String home; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024), 2); + } + + public void setTotal(double total) + { + this.total = total; + } + + public double getMax() + { + return Arith.div(max, (1024 * 1024), 2); + } + + public void setMax(double max) + { + this.max = max; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024), 2); + } + + public void setFree(double free) + { + this.free = free; + } + + public double getUsed() + { + return Arith.div(total - free, (1024 * 1024), 2); + } + + public double getUsage() + { + return Arith.mul(Arith.div(total - free, total, 4), 100); + } + + /** + * 获取JDK名称 + */ + public String getName() + { + return ManagementFactory.getRuntimeMXBean().getVmName(); + } + + public String getVersion() + { + return version; + } + + public void setVersion(String version) + { + this.version = version; + } + + public String getHome() + { + return home; + } + + public void setHome(String home) + { + this.home = home; + } + + /** + * JDK启动时间 + */ + public String getStartTime() + { + return DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD_HH_MM_SS, DateUtils.getServerStartDate()); + } + + /** + * JDK运行时间 + */ + public String getRunTime() + { + return DateUtils.timeDistance(DateUtils.getNowDate(), DateUtils.getServerStartDate()); + } + + /** + * 运行参数 + */ + public String getInputArgs() + { + return ManagementFactory.getRuntimeMXBean().getInputArguments().toString(); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Mem.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Mem.java new file mode 100644 index 000000000..7667fe7a0 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Mem.java @@ -0,0 +1,61 @@ +package com.roomroot.framework.web.domain.server; + +import com.roomroot.common.utils.Arith; + +/** + * 內存相关信息 + * + * @author roomroot + */ +public class Mem +{ + /** + * 内存总量 + */ + private double total; + + /** + * 已用内存 + */ + private double used; + + /** + * 剩余内存 + */ + private double free; + + public double getTotal() + { + return Arith.div(total, (1024 * 1024 * 1024), 2); + } + + public void setTotal(long total) + { + this.total = total; + } + + public double getUsed() + { + return Arith.div(used, (1024 * 1024 * 1024), 2); + } + + public void setUsed(long used) + { + this.used = used; + } + + public double getFree() + { + return Arith.div(free, (1024 * 1024 * 1024), 2); + } + + public void setFree(long free) + { + this.free = free; + } + + public double getUsage() + { + return Arith.mul(Arith.div(used, total, 4), 100); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Sys.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Sys.java new file mode 100644 index 000000000..3b7265d85 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/Sys.java @@ -0,0 +1,84 @@ +package com.roomroot.framework.web.domain.server; + +/** + * 系统相关信息 + * + * @author roomroot + */ +public class Sys +{ + /** + * 服务器名称 + */ + private String computerName; + + /** + * 服务器Ip + */ + private String computerIp; + + /** + * 项目路径 + */ + private String userDir; + + /** + * 操作系统 + */ + private String osName; + + /** + * 系统架构 + */ + private String osArch; + + public String getComputerName() + { + return computerName; + } + + public void setComputerName(String computerName) + { + this.computerName = computerName; + } + + public String getComputerIp() + { + return computerIp; + } + + public void setComputerIp(String computerIp) + { + this.computerIp = computerIp; + } + + public String getUserDir() + { + return userDir; + } + + public void setUserDir(String userDir) + { + this.userDir = userDir; + } + + public String getOsName() + { + return osName; + } + + public void setOsName(String osName) + { + this.osName = osName; + } + + public String getOsArch() + { + return osArch; + } + + public void setOsArch(String osArch) + { + this.osArch = osArch; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/SysFile.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/SysFile.java new file mode 100644 index 000000000..927872601 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/domain/server/SysFile.java @@ -0,0 +1,114 @@ +package com.roomroot.framework.web.domain.server; + +/** + * 系统文件相关信息 + * + * @author roomroot + */ +public class SysFile +{ + /** + * 盘符路径 + */ + private String dirName; + + /** + * 盘符类型 + */ + private String sysTypeName; + + /** + * 文件类型 + */ + private String typeName; + + /** + * 总大小 + */ + private String total; + + /** + * 剩余大小 + */ + private String free; + + /** + * 已经使用量 + */ + private String used; + + /** + * 资源的使用率 + */ + private double usage; + + public String getDirName() + { + return dirName; + } + + public void setDirName(String dirName) + { + this.dirName = dirName; + } + + public String getSysTypeName() + { + return sysTypeName; + } + + public void setSysTypeName(String sysTypeName) + { + this.sysTypeName = sysTypeName; + } + + public String getTypeName() + { + return typeName; + } + + public void setTypeName(String typeName) + { + this.typeName = typeName; + } + + public String getTotal() + { + return total; + } + + public void setTotal(String total) + { + this.total = total; + } + + public String getFree() + { + return free; + } + + public void setFree(String free) + { + this.free = free; + } + + public String getUsed() + { + return used; + } + + public void setUsed(String used) + { + this.used = used; + } + + public double getUsage() + { + return usage; + } + + public void setUsage(double usage) + { + this.usage = usage; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/exception/GlobalExceptionHandler.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/exception/GlobalExceptionHandler.java new file mode 100644 index 000000000..dd96b6895 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/exception/GlobalExceptionHandler.java @@ -0,0 +1,145 @@ +package com.roomroot.framework.web.exception; + +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.validation.BindException; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingPathVariableException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import com.roomroot.common.constant.HttpStatus; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.exception.DemoModeException; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.html.EscapeUtil; + +/** + * 全局异常处理器 + * + * @author roomroot + */ +@RestControllerAdvice +public class GlobalExceptionHandler +{ + private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); + + /** + * 权限校验异常 + */ + @ExceptionHandler(AccessDeniedException.class) + public AjaxResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage()); + return AjaxResult.error(HttpStatus.FORBIDDEN, "没有权限,请联系管理员授权"); + } + + /** + * 请求方式不支持 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public AjaxResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e, + HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod()); + return AjaxResult.error(e.getMessage()); + } + + /** + * 业务异常 + */ + @ExceptionHandler(ServiceException.class) + public AjaxResult handleServiceException(ServiceException e, HttpServletRequest request) + { + log.error(e.getMessage(), e); + Integer code = e.getCode(); + return StringUtils.isNotNull(code) ? AjaxResult.error(code, e.getMessage()) : AjaxResult.error(e.getMessage()); + } + + /** + * 请求路径中缺少必需的路径变量 + */ + @ExceptionHandler(MissingPathVariableException.class) + public AjaxResult handleMissingPathVariableException(MissingPathVariableException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求路径中缺少必需的路径变量'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求路径中缺少必需的路径变量[%s]", e.getVariableName())); + } + + /** + * 请求参数类型不匹配 + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public AjaxResult handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + String value = Convert.toStr(e.getValue()); + if (StringUtils.isNotEmpty(value)) + { + value = EscapeUtil.clean(value); + } + log.error("请求参数类型不匹配'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(String.format("请求参数类型不匹配,参数[%s]要求类型为:'%s',但输入值为:'%s'", e.getName(), e.getRequiredType().getName(), value)); + } + + /** + * 拦截未知的运行时异常 + */ + @ExceptionHandler(RuntimeException.class) + public AjaxResult handleRuntimeException(RuntimeException e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生未知异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 系统异常 + */ + @ExceptionHandler(Exception.class) + public AjaxResult handleException(Exception e, HttpServletRequest request) + { + String requestURI = request.getRequestURI(); + log.error("请求地址'{}',发生系统异常.", requestURI, e); + return AjaxResult.error(e.getMessage()); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(BindException.class) + public AjaxResult handleBindException(BindException e) + { + log.error(e.getMessage(), e); + String message = e.getAllErrors().get(0).getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 自定义验证异常 + */ + @ExceptionHandler(MethodArgumentNotValidException.class) + public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) + { + log.error(e.getMessage(), e); + String message = e.getBindingResult().getFieldError().getDefaultMessage(); + return AjaxResult.error(message); + } + + /** + * 演示模式异常 + */ + @ExceptionHandler(DemoModeException.class) + public AjaxResult handleDemoModeException(DemoModeException e) + { + return AjaxResult.error("演示模式,不允许操作"); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/PermissionService.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/PermissionService.java new file mode 100644 index 000000000..e37a78928 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/PermissionService.java @@ -0,0 +1,159 @@ +package com.roomroot.framework.web.service; + +import java.util.Set; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.framework.security.context.PermissionContextHolder; + +/** + * roomroot首创 自定义权限实现,ss取自SpringSecurity首字母 + * + * @author roomroot + */ +@Service("ss") +public class PermissionService +{ + /** + * 验证用户是否具备某权限 + * + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + public boolean hasPermi(String permission) + { + if (StringUtils.isEmpty(permission)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + PermissionContextHolder.setContext(permission); + return hasPermissions(loginUser.getPermissions(), permission); + } + + /** + * 验证用户是否不具备某权限,与 hasPermi逻辑相反 + * + * @param permission 权限字符串 + * @return 用户是否不具备某权限 + */ + public boolean lacksPermi(String permission) + { + return hasPermi(permission) != true; + } + + /** + * 验证用户是否具有以下任意一个权限 + * + * @param permissions 以 PERMISSION_DELIMITER 为分隔符的权限列表 + * @return 用户是否具有以下任意一个权限 + */ + public boolean hasAnyPermi(String permissions) + { + if (StringUtils.isEmpty(permissions)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) + { + return false; + } + PermissionContextHolder.setContext(permissions); + Set authorities = loginUser.getPermissions(); + for (String permission : permissions.split(Constants.PERMISSION_DELIMITER)) + { + if (permission != null && hasPermissions(authorities, permission)) + { + return true; + } + } + return false; + } + + /** + * 判断用户是否拥有某个角色 + * + * @param role 角色字符串 + * @return 用户是否具备某角色 + */ + public boolean hasRole(String role) + { + if (StringUtils.isEmpty(role)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + for (SysRole sysRole : loginUser.getUser().getRoles()) + { + String roleKey = sysRole.getRoleKey(); + if (Constants.SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) + { + return true; + } + } + return false; + } + + /** + * 验证用户是否不具备某角色,与 isRole逻辑相反。 + * + * @param role 角色名称 + * @return 用户是否不具备某角色 + */ + public boolean lacksRole(String role) + { + return hasRole(role) != true; + } + + /** + * 验证用户是否具有以下任意一个角色 + * + * @param roles 以 ROLE_DELIMITER 为分隔符的角色列表 + * @return 用户是否具有以下任意一个角色 + */ + public boolean hasAnyRoles(String roles) + { + if (StringUtils.isEmpty(roles)) + { + return false; + } + LoginUser loginUser = SecurityUtils.getLoginUser(); + if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) + { + return false; + } + for (String role : roles.split(Constants.ROLE_DELIMITER)) + { + if (hasRole(role)) + { + return true; + } + } + return false; + } + + /** + * 判断是否包含权限 + * + * @param permissions 权限列表 + * @param permission 权限字符串 + * @return 用户是否具备某权限 + */ + private boolean hasPermissions(Set permissions, String permission) + { + return permissions.contains(Constants.ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission)); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysLoginService.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysLoginService.java new file mode 100644 index 000000000..21e59d9d1 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysLoginService.java @@ -0,0 +1,176 @@ +package com.roomroot.framework.web.service; + +import jakarta.annotation.Resource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.core.redis.RedisCache; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.exception.user.BlackListException; +import com.roomroot.common.exception.user.CaptchaException; +import com.roomroot.common.exception.user.CaptchaExpireException; +import com.roomroot.common.exception.user.UserNotExistsException; +import com.roomroot.common.exception.user.UserPasswordNotMatchException; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.MessageUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.ip.IpUtils; +import com.roomroot.framework.manager.AsyncManager; +import com.roomroot.framework.manager.factory.AsyncFactory; +import com.roomroot.framework.security.context.AuthenticationContextHolder; +import com.roomroot.system.service.ISysConfigService; +import com.roomroot.system.service.ISysUserService; + +/** + * 登录校验方法 + * + * @author roomroot + */ +@Component +public class SysLoginService +{ + @Autowired + private TokenService tokenService; + + @Resource + private AuthenticationManager authenticationManager; + + @Autowired + private RedisCache redisCache; + + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + /** + * 登录验证 + * + * @param username 用户名 + * @param password 密码 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public String login(String username, String password, String code, String uuid) + { + // 验证码校验 + validateCaptcha(username, code, uuid); + // 登录前置校验 + loginPreCheck(username, password); + // 用户验证 + Authentication authentication = null; + try + { + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password); + AuthenticationContextHolder.setContext(authenticationToken); + // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername + authentication = authenticationManager.authenticate(authenticationToken); + } + catch (Exception e) + { + if (e instanceof BadCredentialsException) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage())); + throw new ServiceException(e.getMessage()); + } + } + finally + { + AuthenticationContextHolder.clearContext(); + } + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success"))); + LoginUser loginUser = (LoginUser) authentication.getPrincipal(); + recordLoginInfo(loginUser.getUserId()); + // 生成token + return tokenService.createToken(loginUser); + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + if (captcha == null) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"))); + throw new CaptchaExpireException(); + } + redisCache.deleteObject(verifyKey); + if (!code.equalsIgnoreCase(captcha)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"))); + throw new CaptchaException(); + } + } + } + + /** + * 登录前置校验 + * @param username 用户名 + * @param password 用户密码 + */ + public void loginPreCheck(String username, String password) + { + // 用户名或密码为空 错误 + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null"))); + throw new UserNotExistsException(); + } + // 密码如果不在指定范围内 错误 + if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + // 用户名不在指定范围内 错误 + if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match"))); + throw new UserPasswordNotMatchException(); + } + // IP黑名单校验 + String blackStr = configService.selectConfigByKey("sys.login.blackIPList"); + if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked"))); + throw new BlackListException(); + } + } + + /** + * 记录登录信息 + * + * @param userId 用户ID + */ + public void recordLoginInfo(Long userId) + { + userService.updateLoginInfo(userId, IpUtils.getIpAddr(), DateUtils.getNowDate()); + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysPasswordService.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysPasswordService.java new file mode 100644 index 000000000..93fab1b40 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysPasswordService.java @@ -0,0 +1,86 @@ +package com.roomroot.framework.web.service; + +import java.util.concurrent.TimeUnit; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.core.redis.RedisCache; +import com.roomroot.common.exception.user.UserPasswordNotMatchException; +import com.roomroot.common.exception.user.UserPasswordRetryLimitExceedException; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.framework.security.context.AuthenticationContextHolder; + +/** + * 登录密码方法 + * + * @author roomroot + */ +@Component +public class SysPasswordService +{ + @Autowired + private RedisCache redisCache; + + @Value(value = "${user.password.maxRetryCount}") + private int maxRetryCount; + + @Value(value = "${user.password.lockTime}") + private int lockTime; + + /** + * 登录账户密码错误次数缓存键名 + * + * @param username 用户名 + * @return 缓存键key + */ + private String getCacheKey(String username) + { + return CacheConstants.PWD_ERR_CNT_KEY + username; + } + + public void validate(SysUser user) + { + Authentication usernamePasswordAuthenticationToken = AuthenticationContextHolder.getContext(); + String username = usernamePasswordAuthenticationToken.getName(); + String password = usernamePasswordAuthenticationToken.getCredentials().toString(); + + Integer retryCount = redisCache.getCacheObject(getCacheKey(username)); + + if (retryCount == null) + { + retryCount = 0; + } + + if (retryCount >= Integer.valueOf(maxRetryCount).intValue()) + { + throw new UserPasswordRetryLimitExceedException(maxRetryCount, lockTime); + } + + if (!matches(user, password)) + { + retryCount = retryCount + 1; + redisCache.setCacheObject(getCacheKey(username), retryCount, lockTime, TimeUnit.MINUTES); + throw new UserPasswordNotMatchException(); + } + else + { + clearLoginRecordCache(username); + } + } + + public boolean matches(SysUser user, String rawPassword) + { + return SecurityUtils.matchesPassword(rawPassword, user.getPassword()); + } + + public void clearLoginRecordCache(String loginName) + { + if (redisCache.hasKey(getCacheKey(loginName))) + { + redisCache.deleteObject(getCacheKey(loginName)); + } + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysPermissionService.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysPermissionService.java new file mode 100644 index 000000000..3dc5983bc --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysPermissionService.java @@ -0,0 +1,89 @@ +package com.roomroot.framework.web.service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.service.ISysMenuService; +import com.roomroot.system.service.ISysRoleService; + +/** + * 用户权限处理 + * + * @author roomroot + */ +@Component +public class SysPermissionService +{ + @Autowired + private ISysRoleService roleService; + + @Autowired + private ISysMenuService menuService; + + /** + * 获取角色数据权限 + * + * @param user 用户信息 + * @return 角色权限信息 + */ + public Set getRolePermission(SysUser user) + { + Set roles = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + roles.add(Constants.SUPER_ADMIN); + } + else + { + roles.addAll(roleService.selectRolePermissionByUserId(user.getUserId())); + } + return roles; + } + + /** + * 获取菜单数据权限 + * + * @param user 用户信息 + * @return 菜单权限信息 + */ + public Set getMenuPermission(SysUser user) + { + Set perms = new HashSet(); + // 管理员拥有所有权限 + if (user.isAdmin()) + { + perms.add(Constants.ALL_PERMISSION); + } + else + { + List roles = user.getRoles(); + if (!CollectionUtils.isEmpty(roles)) + { + // 多角色设置permissions属性,以便数据权限匹配权限 + for (SysRole role : roles) + { + if (StringUtils.equals(role.getStatus(), UserConstants.ROLE_NORMAL) && !role.isAdmin()) + { + Set rolePerms = menuService.selectMenuPermsByRoleId(role.getRoleId()); + role.setPermissions(rolePerms); + perms.addAll(rolePerms); + } + } + } + else + { + perms.addAll(menuService.selectMenuPermsByUserId(user.getUserId())); + } + } + return perms; + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysRegisterService.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysRegisterService.java new file mode 100644 index 000000000..390e3de02 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/SysRegisterService.java @@ -0,0 +1,117 @@ +package com.roomroot.framework.web.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.core.domain.model.RegisterBody; +import com.roomroot.common.core.redis.RedisCache; +import com.roomroot.common.exception.user.CaptchaException; +import com.roomroot.common.exception.user.CaptchaExpireException; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.MessageUtils; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.framework.manager.AsyncManager; +import com.roomroot.framework.manager.factory.AsyncFactory; +import com.roomroot.system.service.ISysConfigService; +import com.roomroot.system.service.ISysUserService; + +/** + * 注册校验方法 + * + * @author roomroot + */ +@Component +public class SysRegisterService +{ + @Autowired + private ISysUserService userService; + + @Autowired + private ISysConfigService configService; + + @Autowired + private RedisCache redisCache; + + /** + * 注册 + */ + public String register(RegisterBody registerBody) + { + String msg = "", username = registerBody.getUsername(), password = registerBody.getPassword(); + SysUser sysUser = new SysUser(); + sysUser.setUserName(username); + + // 验证码开关 + boolean captchaEnabled = configService.selectCaptchaEnabled(); + if (captchaEnabled) + { + validateCaptcha(username, registerBody.getCode(), registerBody.getUuid()); + } + + if (StringUtils.isEmpty(username)) + { + msg = "用户名不能为空"; + } + else if (StringUtils.isEmpty(password)) + { + msg = "用户密码不能为空"; + } + else if (username.length() < UserConstants.USERNAME_MIN_LENGTH + || username.length() > UserConstants.USERNAME_MAX_LENGTH) + { + msg = "账户长度必须在2到20个字符之间"; + } + else if (password.length() < UserConstants.PASSWORD_MIN_LENGTH + || password.length() > UserConstants.PASSWORD_MAX_LENGTH) + { + msg = "密码长度必须在5到20个字符之间"; + } + else if (!userService.checkUserNameUnique(sysUser)) + { + msg = "保存用户'" + username + "'失败,注册账号已存在"; + } + else + { + sysUser.setNickName(username); + sysUser.setPwdUpdateDate(DateUtils.getNowDate()); + sysUser.setPassword(SecurityUtils.encryptPassword(password)); + boolean regFlag = userService.registerUser(sysUser); + if (!regFlag) + { + msg = "注册失败,请联系系统管理人员"; + } + else + { + AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.REGISTER, MessageUtils.message("user.register.success"))); + } + } + return msg; + } + + /** + * 校验验证码 + * + * @param username 用户名 + * @param code 验证码 + * @param uuid 唯一标识 + * @return 结果 + */ + public void validateCaptcha(String username, String code, String uuid) + { + String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, ""); + String captcha = redisCache.getCacheObject(verifyKey); + redisCache.deleteObject(verifyKey); + if (captcha == null) + { + throw new CaptchaExpireException(); + } + if (!code.equalsIgnoreCase(captcha)) + { + throw new CaptchaException(); + } + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/TokenService.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/TokenService.java new file mode 100644 index 000000000..107a5d955 --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/TokenService.java @@ -0,0 +1,270 @@ +package com.roomroot.framework.web.service; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.core.redis.RedisCache; +import com.roomroot.common.utils.ServletUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.http.UserAgentUtils; +import com.roomroot.common.utils.ip.AddressUtils; +import com.roomroot.common.utils.ip.IpUtils; +import com.roomroot.common.utils.uuid.IdUtils; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import jakarta.servlet.http.HttpServletRequest; + +/** + * token验证处理 + * + * @author roomroot + */ +@Component +public class TokenService +{ + private static final Logger log = LoggerFactory.getLogger(TokenService.class); + + // 令牌自定义标识 + @Value("${token.header}") + private String header; + + // 令牌秘钥 + @Value("${token.secret}") + private String secret; + + // 令牌有效期(默认30分钟) + @Value("${token.expireTime}") + private int expireTime; + + protected static final long MILLIS_SECOND = 1000; + + protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND; + + private static final Long MILLIS_MINUTE_TWENTY = 20 * 60 * 1000L; + + @Autowired + private RedisCache redisCache; + + /** + * 获取用户身份信息 + * + * @return 用户信息 + */ + public LoginUser getLoginUser(HttpServletRequest request) + { + // 获取请求携带的令牌 + String token = getToken(request); + if (StringUtils.isNotEmpty(token)) + { + try + { + Claims claims = parseToken(token); + // 解析对应的权限以及用户信息 + String uuid = (String) claims.get(Constants.LOGIN_USER_KEY); + String userKey = getTokenKey(uuid); + LoginUser user = redisCache.getCacheObject(userKey); + return user; + } + catch (Exception e) + { + log.error("获取用户信息异常'{}'", e.getMessage()); + } + } + return null; + } + + /** + * 设置用户身份信息 + */ + public void setLoginUser(LoginUser loginUser) + { + if (StringUtils.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) + { + refreshToken(loginUser); + } + } + + /** + * 删除用户身份信息 + */ + public void delLoginUser(String token) + { + if (StringUtils.isNotEmpty(token)) + { + String userKey = getTokenKey(token); + redisCache.deleteObject(userKey); + } + } + + /** + * 创建令牌 + * + * @param loginUser 用户信息 + * @return 令牌 + */ + public String createToken(LoginUser loginUser) + { + String token = IdUtils.fastUUID(); + loginUser.setToken(token); + setUserAgent(loginUser); + refreshToken(loginUser); + + Map claims = new HashMap<>(); + claims.put(Constants.LOGIN_USER_KEY, token); + claims.put(Constants.JWT_USERNAME, loginUser.getUsername()); + return createToken(claims); + } + + /** + * 验证令牌有效期,相差不足20分钟,自动刷新缓存 + * + * @param loginUser 登录信息 + * @return 令牌 + */ + public void verifyToken(LoginUser loginUser) + { + long expireTime = loginUser.getExpireTime(); + long currentTime = System.currentTimeMillis(); + if (expireTime - currentTime <= MILLIS_MINUTE_TWENTY) + { + refreshToken(loginUser); + } + } + + /** + * 刷新令牌有效期 + * + * @param loginUser 登录信息 + */ + public void refreshToken(LoginUser loginUser) + { + loginUser.setLoginTime(System.currentTimeMillis()); + loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE); + // 根据uuid将loginUser缓存 + String userKey = getTokenKey(loginUser.getToken()); + redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES); + } + + /** + * 设置用户代理信息 + * + * @param loginUser 登录信息 + */ + public void setUserAgent(LoginUser loginUser) + { + String userAgent = ServletUtils.getRequest().getHeader("User-Agent"); + String ip = IpUtils.getIpAddr(); + loginUser.setIpaddr(ip); + loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip)); + loginUser.setBrowser(UserAgentUtils.getBrowser(userAgent)); + loginUser.setOs(UserAgentUtils.getOperatingSystem(userAgent)); + } + + /** + * 从数据声明生成令牌 + * + * @param claims 数据声明 + * @return 令牌 + */ + private String createToken(Map claims) + { + String token = Jwts.builder() + .setClaims(claims) + .signWith(SignatureAlgorithm.HS512, secret).compact(); + return token; + } + + /** + * 从令牌中获取数据声明 + * + * @param token 令牌 + * @return 数据声明 + */ + private Claims parseToken(String token) + { + return Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody(); + } + + /** + * 从令牌中获取用户名 + * + * @param token 令牌 + * @return 用户名 + */ + public String getUsernameFromToken(String token) + { + Claims claims = parseToken(token); + return claims.getSubject(); + } + + /** + * 获取请求token + * + * @param request + * @return token + */ + private String getToken(HttpServletRequest request) + { + String token = request.getHeader(header); + if (StringUtils.isNotEmpty(token) && token.startsWith(Constants.TOKEN_PREFIX)) + { + token = token.replace(Constants.TOKEN_PREFIX, ""); + } + return token; + } + + private String getTokenKey(String uuid) + { + return CacheConstants.LOGIN_TOKEN_KEY + uuid; + } + + /** + * 角色权限变更后,刷新所有持有该角色的在线用户权限 + * + * @param roleId 变更的角色ID + * @param permissionService 权限服务 + */ + public void refreshPermissionByRoleId(Long roleId, SysPermissionService permissionService) + { + // 扫描所有在线 token + String pattern = CacheConstants.LOGIN_TOKEN_KEY + "*"; + Collection keys = redisCache.keys(pattern); + if (keys == null || keys.isEmpty()) + { + return; + } + for (String key : keys) + { + LoginUser loginUser = redisCache.getCacheObject(key); + if (loginUser == null || loginUser.getUser() == null || loginUser.getUser().isAdmin()) + { + // 管理员拥有所有权限,跳过 + continue; + } + // 判断该用户是否拥有此角色 + boolean hasRole = loginUser.getUser().getRoles() != null + && loginUser.getUser().getRoles().stream().anyMatch(r -> roleId.equals(r.getRoleId())); + if (!hasRole) + { + continue; + } + // 刷新权限缓存 + loginUser.setPermissions(permissionService.getMenuPermission(loginUser.getUser())); + refreshToken(loginUser); + log.info("角色[{}]权限变更,已刷新在线用户[{}]的权限缓存", roleId, loginUser.getUsername()); + } + } +} diff --git a/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/UserDetailsServiceImpl.java b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/UserDetailsServiceImpl.java new file mode 100644 index 000000000..a9343989b --- /dev/null +++ b/backend/roomroot-framework/src/main/java/com/roomroot/framework/web/service/UserDetailsServiceImpl.java @@ -0,0 +1,66 @@ +package com.roomroot.framework.web.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.enums.UserStatus; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.MessageUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.service.ISysUserService; + +/** + * 用户验证处理 + * + * @author roomroot + */ +@Service +public class UserDetailsServiceImpl implements UserDetailsService +{ + private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class); + + @Autowired + private ISysUserService userService; + + @Autowired + private SysPasswordService passwordService; + + @Autowired + private SysPermissionService permissionService; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException + { + SysUser user = userService.selectUserByUserName(username); + if (StringUtils.isNull(user)) + { + log.info("登录用户:{} 不存在.", username); + throw new ServiceException(MessageUtils.message("user.not.exists")); + } + else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) + { + log.info("登录用户:{} 已被删除.", username); + throw new ServiceException(MessageUtils.message("user.password.delete")); + } + else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) + { + log.info("登录用户:{} 已被停用.", username); + throw new ServiceException(MessageUtils.message("user.blocked")); + } + + passwordService.validate(user); + + return createLoginUser(user); + } + + public UserDetails createLoginUser(SysUser user) + { + return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user)); + } +} diff --git a/backend/roomroot-generator/pom.xml b/backend/roomroot-generator/pom.xml new file mode 100644 index 000000000..35cb24ae8 --- /dev/null +++ b/backend/roomroot-generator/pom.xml @@ -0,0 +1,40 @@ + + + + roomroot + com.roomroot + 3.9.2 + + 4.0.0 + + roomroot-generator + + + generator代码生成 + + + + + + + org.apache.velocity + velocity-engine-core + + + + + com.roomroot + roomroot-common + + + + + com.alibaba + druid-spring-boot-4-starter + + + + + \ No newline at end of file diff --git a/backend/roomroot-generator/roomroot-generator.iml b/backend/roomroot-generator/roomroot-generator.iml new file mode 100644 index 000000000..abe781f78 --- /dev/null +++ b/backend/roomroot-generator/roomroot-generator.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/config/GenConfig.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/config/GenConfig.java new file mode 100644 index 000000000..476fb3839 --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/config/GenConfig.java @@ -0,0 +1,87 @@ +package com.roomroot.generator.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.PropertySource; +import org.springframework.stereotype.Component; + +/** + * 读取代码生成相关配置 + * + * @author roomroot + */ +@Component +@ConfigurationProperties(prefix = "gen") +@PropertySource(value = { "classpath:generator.yml" }) +public class GenConfig +{ + /** 作者 */ + public static String author; + + /** 生成包路径 */ + public static String packageName; + + /** 自动去除表前缀 */ + public static boolean autoRemovePre; + + /** 表前缀 */ + public static String tablePrefix; + + /** 是否允许生成文件覆盖到本地(自定义路径) */ + public static boolean allowOverwrite; + + public static String getAuthor() + { + return author; + } + + @Value("${author}") + public void setAuthor(String author) + { + GenConfig.author = author; + } + + public static String getPackageName() + { + return packageName; + } + + @Value("${packageName}") + public void setPackageName(String packageName) + { + GenConfig.packageName = packageName; + } + + public static boolean getAutoRemovePre() + { + return autoRemovePre; + } + + @Value("${autoRemovePre}") + public void setAutoRemovePre(boolean autoRemovePre) + { + GenConfig.autoRemovePre = autoRemovePre; + } + + public static String getTablePrefix() + { + return tablePrefix; + } + + @Value("${tablePrefix}") + public void setTablePrefix(String tablePrefix) + { + GenConfig.tablePrefix = tablePrefix; + } + + public static boolean isAllowOverwrite() + { + return allowOverwrite; + } + + @Value("${allowOverwrite}") + public void setAllowOverwrite(boolean allowOverwrite) + { + GenConfig.allowOverwrite = allowOverwrite; + } +} diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/controller/GenController.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/controller/GenController.java new file mode 100644 index 000000000..ee9e81e10 --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/controller/GenController.java @@ -0,0 +1,284 @@ +package com.roomroot.generator.controller; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.druid.sql.ast.statement.SQLCreateTableStatement; +import com.baomidou.mybatisplus.extension.toolkit.Db; +import com.roomroot.common.utils.DbTypeUtil; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.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 com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.sql.SqlUtil; +import com.roomroot.generator.config.GenConfig; +import com.roomroot.generator.domain.GenTable; +import com.roomroot.generator.domain.GenTableColumn; +import com.roomroot.generator.service.IGenTableColumnService; +import com.roomroot.generator.service.IGenTableService; + +/** + * 代码生成 操作处理 + * + * @author roomroot + */ +@RestController +@RequestMapping("/tool/gen") +public class GenController extends BaseController +{ + @Autowired + private IGenTableService genTableService; + + @Autowired + private IGenTableColumnService genTableColumnService; + @Autowired + private DbTypeUtil dbTypeUtil; + + /** + * 查询代码生成列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/list") + public TableDataInfo genList(GenTable genTable) + { + startPage(); + List list = genTableService.selectGenTableList(genTable); + return getDataTable(list); + } + + /** + * 获取代码生成信息 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:query')") + @GetMapping(value = "/{tableId}") + public AjaxResult getInfo(@PathVariable Long tableId) + { + GenTable table = genTableService.selectGenTableById(tableId); + List tables = genTableService.selectGenTableAll(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + Map map = new HashMap(); + map.put("info", table); + map.put("rows", list); + map.put("tables", tables); + return success(map); + } + + /** + * 查询数据库列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping("/db/list") + public TableDataInfo dataList(GenTable genTable) + { + startPage(); + List list = genTableService.selectDbTableList(genTable); + return getDataTable(list); + } + + /** + * 查询数据表字段列表 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:list')") + @GetMapping(value = "/column/{tableId}") + public TableDataInfo columnList(Long tableId) + { + TableDataInfo dataInfo = new TableDataInfo(); + List list = genTableColumnService.selectGenTableColumnListByTableId(tableId); + dataInfo.setRows(list); + dataInfo.setTotal(list.size()); + return dataInfo; + } + + /** + * 导入表结构(保存) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:import')") + @Log(title = "代码生成", businessType = BusinessType.IMPORT) + @PostMapping("/importTable") + public AjaxResult importTableSave(@RequestParam("tables") String tables, @RequestParam("tplWebType") String tplWebType) + { + String[] tableNames = Convert.toStrArray(tables); + // 查询表信息 + List tableList = genTableService.selectDbTableListByNames(tableNames); + genTableService.importGenTable(tableList, tplWebType, SecurityUtils.getUsername()); + return success(); + } + + /** + * 创建表结构(保存) + */ + @PreAuthorize("@ss.hasRole('admin')") + @Log(title = "创建表", businessType = BusinessType.OTHER) + @PostMapping("/createTable") + public AjaxResult createTableSave(@RequestParam("sql") String sql, @RequestParam("tplWebType") String tplWebType) + { + try + { + SqlUtil.filterKeyword(sql); + String dbtypeStr = dbTypeUtil.getDbType(); + DbType dbType = DbType.mysql; + if("dm".equals(dbtypeStr)){ + dbType = DbType.dm; + } + List sqlStatements = SQLUtils.parseStatements(sql, dbType); + List tableNames = new ArrayList<>(); + for (SQLStatement sqlStatement : sqlStatements) + { + // 统一判断通用建表语句,不再限定 MySql 子类 + if (sqlStatement instanceof SQLCreateTableStatement createTableStmt) { + // 获取原始完整建表SQL + String createSql = createTableStmt.toString(); + boolean createSuccess = genTableService.createTable(createSql); + if (createSuccess) { + String rawTableName = createTableStmt.getTableName(); + String realTableName; + if ("dm".equalsIgnoreCase(dbtypeStr)) { + // 达梦去除前后双引号 + realTableName = rawTableName.replaceAll("\"", ""); + } else { + // MySQL 去除前后反引号 + realTableName = rawTableName.replaceAll("`", ""); + } + tableNames.add(realTableName); + } + } + } + List tableList = genTableService.selectDbTableListByNames(tableNames.toArray(new String[tableNames.size()])); + String operName = SecurityUtils.getUsername(); + genTableService.importGenTable(tableList, tplWebType, operName); + return AjaxResult.success(); + } + catch (Exception e) + { + logger.error(e.getMessage(), e); + return AjaxResult.error("创建表结构异常"); + } + } + + /** + * 修改保存代码生成业务 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult editSave(@Validated @RequestBody GenTable genTable) + { + genTableService.validateEdit(genTable); + genTableService.updateGenTable(genTable); + return success(); + } + + /** + * 删除代码生成 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:remove')") + @Log(title = "代码生成", businessType = BusinessType.DELETE) + @DeleteMapping("/{tableIds}") + public AjaxResult remove(@PathVariable Long[] tableIds) + { + genTableService.deleteGenTableByIds(tableIds); + return success(); + } + + /** + * 预览代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:preview')") + @GetMapping("/preview/{tableId}") + public AjaxResult preview(@PathVariable("tableId") Long tableId) throws IOException + { + Map dataMap = genTableService.previewCode(tableId); + return success(dataMap); + } + + /** + * 生成代码(下载方式) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/download/{tableName}") + public void download(HttpServletResponse response, @PathVariable("tableName") String tableName) throws IOException + { + byte[] data = genTableService.downloadCode(tableName); + genCode(response, data); + } + + /** + * 生成代码(自定义路径) + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/genCode/{tableName}") + public AjaxResult genCode(@PathVariable("tableName") String tableName) + { + if (!GenConfig.isAllowOverwrite()) + { + return AjaxResult.error("【系统预设】不允许生成文件覆盖到本地"); + } + genTableService.generatorCode(tableName); + return success(); + } + + /** + * 同步数据库 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:edit')") + @Log(title = "代码生成", businessType = BusinessType.UPDATE) + @GetMapping("/synchDb/{tableName}") + public AjaxResult synchDb(@PathVariable("tableName") String tableName) + { + genTableService.synchDb(tableName); + return success(); + } + + /** + * 批量生成代码 + */ + @PreAuthorize("@ss.hasPermi('tool:gen:code')") + @Log(title = "代码生成", businessType = BusinessType.GENCODE) + @GetMapping("/batchGenCode") + public void batchGenCode(HttpServletResponse response, String tables) throws IOException + { + String[] tableNames = Convert.toStrArray(tables); + byte[] data = genTableService.downloadCode(tableNames); + genCode(response, data); + } + + /** + * 生成zip文件 + */ + private void genCode(HttpServletResponse response, byte[] data) throws IOException + { + response.reset(); + response.addHeader("Access-Control-Allow-Origin", "*"); + response.addHeader("Access-Control-Expose-Headers", "Content-Disposition"); + response.setHeader("Content-Disposition", "attachment; filename=\"roomroot.zip\""); + response.addHeader("Content-Length", "" + data.length); + response.setContentType("application/octet-stream; charset=UTF-8"); + IOUtils.write(data, response.getOutputStream()); + } +} \ No newline at end of file diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/domain/GenTable.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/domain/GenTable.java new file mode 100644 index 000000000..98df4364c --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/domain/GenTable.java @@ -0,0 +1,413 @@ +package com.roomroot.generator.domain; + +import java.util.List; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import org.apache.commons.lang3.ArrayUtils; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.common.constant.GenConstants; +import com.roomroot.common.core.domain.BaseEntity; +import com.roomroot.common.utils.StringUtils; + +/** + * 业务表 gen_table + * + * @author roomroot + */ +@TableName("gen_table") +public class GenTable extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long tableId; + + /** 表名称 */ + @NotBlank(message = "表名称不能为空") + private String tableName; + + /** 表描述 */ + @NotBlank(message = "表描述不能为空") + private String tableComment; + + /** 关联父表的表名 */ + private String subTableName; + + /** 本表关联父表的外键名 */ + private String subTableFkName; + + /** 实体类名称(首字母大写) */ + @NotBlank(message = "实体类名称不能为空") + private String className; + + /** 使用的模板(crud单表操作 tree树表操作 sub主子表操作) */ + private String tplCategory; + + /** 前端类型(element-ui模版 element-plus模版 element-plus-typescript模版) */ + private String tplWebType; + + /** 生成包路径 */ + @NotBlank(message = "生成包路径不能为空") + private String packageName; + + /** 生成模块名 */ + @NotBlank(message = "生成模块名不能为空") + private String moduleName; + + /** 生成业务名 */ + @NotBlank(message = "生成业务名不能为空") + private String businessName; + + /** 生成功能名 */ + @NotBlank(message = "生成功能名不能为空") + private String functionName; + + /** 生成作者 */ + @NotBlank(message = "作者不能为空") + private String functionAuthor; + + /** 表单布局(单列 双列 三列) */ + private Integer formColNum; + + /** 生成代码方式(0zip压缩包 1自定义路径) */ + private String genType; + + /** 生成路径(不填默认项目路径) */ + private String genPath; + + /** 主键信息 */ + private GenTableColumn pkColumn; + + /** 子表信息 */ + private GenTable subTable; + + /** 表列信息 */ + @Valid + private List columns; + + /** 其它生成选项 */ + private String options; + + /** 树编码字段 */ + private String treeCode; + + /** 树父编码字段 */ + private String treeParentCode; + + /** 树名称字段 */ + private String treeName; + + /** 上级菜单ID字段 */ + private Long parentMenuId; + + /** 上级菜单名称字段 */ + private String parentMenuName; + + /** 是否生成详情页 */ + private boolean isView; + + public Long getTableId() + { + return tableId; + } + + public void setTableId(Long tableId) + { + this.tableId = tableId; + } + + public String getTableName() + { + return tableName; + } + + public void setTableName(String tableName) + { + this.tableName = tableName; + } + + public String getTableComment() + { + return tableComment; + } + + public void setTableComment(String tableComment) + { + this.tableComment = tableComment; + } + + public String getSubTableName() + { + return subTableName; + } + + public void setSubTableName(String subTableName) + { + this.subTableName = subTableName; + } + + public String getSubTableFkName() + { + return subTableFkName; + } + + public void setSubTableFkName(String subTableFkName) + { + this.subTableFkName = subTableFkName; + } + + public String getClassName() + { + return className; + } + + public void setClassName(String className) + { + this.className = className; + } + + public String getTplCategory() + { + return tplCategory; + } + + public void setTplCategory(String tplCategory) + { + this.tplCategory = tplCategory; + } + + public String getTplWebType() + { + return tplWebType; + } + + public void setTplWebType(String tplWebType) + { + this.tplWebType = tplWebType; + } + + public String getPackageName() + { + return packageName; + } + + public void setPackageName(String packageName) + { + this.packageName = packageName; + } + + public String getModuleName() + { + return moduleName; + } + + public void setModuleName(String moduleName) + { + this.moduleName = moduleName; + } + + public String getBusinessName() + { + return businessName; + } + + public void setBusinessName(String businessName) + { + this.businessName = businessName; + } + + public String getFunctionName() + { + return functionName; + } + + public void setFunctionName(String functionName) + { + this.functionName = functionName; + } + + public String getFunctionAuthor() + { + return functionAuthor; + } + + public void setFunctionAuthor(String functionAuthor) + { + this.functionAuthor = functionAuthor; + } + + public Integer getFormColNum() + { + return formColNum; + } + + public void setFormColNum(Integer formColNum) + { + this.formColNum = formColNum; + } + + public String getGenType() + { + return genType; + } + + public void setGenType(String genType) + { + this.genType = genType; + } + + public String getGenPath() + { + return genPath; + } + + public void setGenPath(String genPath) + { + this.genPath = genPath; + } + + public GenTableColumn getPkColumn() + { + return pkColumn; + } + + public void setPkColumn(GenTableColumn pkColumn) + { + this.pkColumn = pkColumn; + } + + public GenTable getSubTable() + { + return subTable; + } + + public void setSubTable(GenTable subTable) + { + this.subTable = subTable; + } + + public List getColumns() + { + return columns; + } + + public void setColumns(List columns) + { + this.columns = columns; + } + + public String getOptions() + { + return options; + } + + public void setOptions(String options) + { + this.options = options; + } + + public String getTreeCode() + { + return treeCode; + } + + public void setTreeCode(String treeCode) + { + this.treeCode = treeCode; + } + + public String getTreeParentCode() + { + return treeParentCode; + } + + public void setTreeParentCode(String treeParentCode) + { + this.treeParentCode = treeParentCode; + } + + public String getTreeName() + { + return treeName; + } + + public void setTreeName(String treeName) + { + this.treeName = treeName; + } + + public Long getParentMenuId() + { + return parentMenuId; + } + + public void setParentMenuId(Long parentMenuId) + { + this.parentMenuId = parentMenuId; + } + + public String getParentMenuName() + { + return parentMenuName; + } + + public void setParentMenuName(String parentMenuName) + { + this.parentMenuName = parentMenuName; + } + + public boolean isView() + { + return isView; + } + + public void setView(boolean isView) + { + this.isView = isView; + } + + public boolean isSub() + { + return isSub(this.tplCategory); + } + + public static boolean isSub(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_SUB, tplCategory); + } + + public boolean isTree() + { + return isTree(this.tplCategory); + } + + public static boolean isTree(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory); + } + + public boolean isCrud() + { + return isCrud(this.tplCategory); + } + + public static boolean isCrud(String tplCategory) + { + return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory); + } + + public boolean isSuperColumn(String javaField) + { + return isSuperColumn(this.tplCategory, javaField); + } + + public static boolean isSuperColumn(String tplCategory, String javaField) + { + if (isTree(tplCategory)) + { + return StringUtils.equalsAnyIgnoreCase(javaField, + ArrayUtils.addAll(GenConstants.TREE_ENTITY, GenConstants.BASE_ENTITY)); + } + return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY); + } +} \ No newline at end of file diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/domain/GenTableColumn.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/domain/GenTableColumn.java new file mode 100644 index 000000000..a8bacad24 --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/domain/GenTableColumn.java @@ -0,0 +1,375 @@ +package com.roomroot.generator.domain; + +import jakarta.validation.constraints.NotBlank; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.common.core.domain.BaseEntity; +import com.roomroot.common.utils.StringUtils; + +/** + * 代码生成业务字段表 gen_table_column + * + * @author roomroot + */ +@TableName("gen_table_column") +public class GenTableColumn extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 编号 */ + private Long columnId; + + /** 归属表编号 */ + private Long tableId; + + /** 列名称 */ + private String columnName; + + /** 列描述 */ + private String columnComment; + + /** 列类型 */ + private String columnType; + + /** JAVA类型 */ + private String javaType; + + /** JAVA字段名 */ + @NotBlank(message = "Java属性不能为空") + private String javaField; + + /** 是否主键(1是) */ + private String isPk; + + /** 是否自增(1是) */ + private String isIncrement; + + /** 是否必填(1是) */ + private String isRequired; + + /** 是否为插入字段(1是) */ + private String isInsert; + + /** 是否编辑字段(1是) */ + private String isEdit; + + /** 是否列表字段(1是) */ + private String isList; + + /** 是否查询字段(1是) */ + private String isQuery; + + /** 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围) */ + private String queryType; + + /** 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件) */ + private String htmlType; + + /** 字典类型 */ + private String dictType; + + /** 排序 */ + private Integer sort; + + public void setColumnId(Long columnId) + { + this.columnId = columnId; + } + + public Long getColumnId() + { + return columnId; + } + + public void setTableId(Long tableId) + { + this.tableId = tableId; + } + + public Long getTableId() + { + return tableId; + } + + public void setColumnName(String columnName) + { + this.columnName = columnName; + } + + public String getColumnName() + { + return columnName; + } + + public void setColumnComment(String columnComment) + { + this.columnComment = columnComment; + } + + public String getColumnComment() + { + return columnComment; + } + + public void setColumnType(String columnType) + { + this.columnType = columnType; + } + + public String getColumnType() + { + return columnType; + } + + public void setJavaType(String javaType) + { + this.javaType = javaType; + } + + public String getJavaType() + { + return javaType; + } + + public void setJavaField(String javaField) + { + this.javaField = javaField; + } + + public String getJavaField() + { + return javaField; + } + + public String getCapJavaField() + { + return StringUtils.capitalize(javaField); + } + + public void setIsPk(String isPk) + { + this.isPk = isPk; + } + + public String getIsPk() + { + return isPk; + } + + public boolean isPk() + { + return isPk(this.isPk); + } + + public boolean isPk(String isPk) + { + return isPk != null && StringUtils.equals("1", isPk); + } + + public String getIsIncrement() + { + return isIncrement; + } + + public void setIsIncrement(String isIncrement) + { + this.isIncrement = isIncrement; + } + + public boolean isIncrement() + { + return isIncrement(this.isIncrement); + } + + public boolean isIncrement(String isIncrement) + { + return isIncrement != null && StringUtils.equals("1", isIncrement); + } + + public void setIsRequired(String isRequired) + { + this.isRequired = isRequired; + } + + public String getIsRequired() + { + return isRequired; + } + + public boolean isRequired() + { + return isRequired(this.isRequired); + } + + public boolean isRequired(String isRequired) + { + return isRequired != null && StringUtils.equals("1", isRequired); + } + + public void setIsInsert(String isInsert) + { + this.isInsert = isInsert; + } + + public String getIsInsert() + { + return isInsert; + } + + public boolean isInsert() + { + return isInsert(this.isInsert); + } + + public boolean isInsert(String isInsert) + { + return isInsert != null && StringUtils.equals("1", isInsert); + } + + public void setIsEdit(String isEdit) + { + this.isEdit = isEdit; + } + + public String getIsEdit() + { + return isEdit; + } + + public boolean isEdit() + { + return isInsert(this.isEdit); + } + + public boolean isEdit(String isEdit) + { + return isEdit != null && StringUtils.equals("1", isEdit); + } + + public void setIsList(String isList) + { + this.isList = isList; + } + + public String getIsList() + { + return isList; + } + + public boolean isList() + { + return isList(this.isList); + } + + public boolean isList(String isList) + { + return isList != null && StringUtils.equals("1", isList); + } + + public void setIsQuery(String isQuery) + { + this.isQuery = isQuery; + } + + public String getIsQuery() + { + return isQuery; + } + + public boolean isQuery() + { + return isQuery(this.isQuery); + } + + public boolean isQuery(String isQuery) + { + return isQuery != null && StringUtils.equals("1", isQuery); + } + + public void setQueryType(String queryType) + { + this.queryType = queryType; + } + + public String getQueryType() + { + return queryType; + } + + public String getHtmlType() + { + return htmlType; + } + + public void setHtmlType(String htmlType) + { + this.htmlType = htmlType; + } + + public void setDictType(String dictType) + { + this.dictType = dictType; + } + + public String getDictType() + { + return dictType; + } + + public void setSort(Integer sort) + { + this.sort = sort; + } + + public Integer getSort() + { + return sort; + } + + public boolean isSuperColumn() + { + return isSuperColumn(this.javaField); + } + + public static boolean isSuperColumn(String javaField) + { + return StringUtils.equalsAnyIgnoreCase(javaField, + // BaseEntity + "createBy", "createTime", "updateBy", "updateTime", "remark", + // TreeEntity + "parentName", "parentId", "orderNum", "ancestors"); + } + + public boolean isUsableColumn() + { + return isUsableColumn(javaField); + } + + public static boolean isUsableColumn(String javaField) + { + // isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单 + return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark"); + } + + public String readConverterExp() + { + String remarks = StringUtils.substringBetween(this.columnComment, "(", ")"); + StringBuffer sb = new StringBuffer(); + if (StringUtils.isNotEmpty(remarks)) + { + for (String value : remarks.split(" ")) + { + if (StringUtils.isNotEmpty(value)) + { + Object startStr = value.subSequence(0, 1); + String endStr = value.substring(1); + sb.append("").append(startStr).append("=").append(endStr).append(","); + } + } + return sb.deleteCharAt(sb.length() - 1).toString(); + } + else + { + return this.columnComment; + } + } +} diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/mapper/GenTableColumnMapper.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/mapper/GenTableColumnMapper.java new file mode 100644 index 000000000..35d934218 --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/mapper/GenTableColumnMapper.java @@ -0,0 +1,62 @@ +package com.roomroot.generator.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.generator.domain.GenTableColumn; + +/** + * 业务字段 数据层 + * + * @author roomroot + */ +public interface GenTableColumnMapper extends BaseMapper +{ + /** + * 根据表名称查询列信息 + * + * @param tableName 表名称 + * @return 列信息 + */ + public List selectMysqlTableColumnsByName(String tableName); + public List selectDMTableColumnsByName(String tableName); + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段 + * + * @param genTableColumns 列数据 + * @return 结果 + */ + public int deleteGenTableColumns(List genTableColumns); + + /** + * 批量删除业务字段 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(Long[] ids); +} diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/mapper/GenTableMapper.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/mapper/GenTableMapper.java new file mode 100644 index 000000000..7e1ad141c --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/mapper/GenTableMapper.java @@ -0,0 +1,94 @@ +package com.roomroot.generator.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.generator.domain.GenTable; +import org.apache.ibatis.annotations.Param; + +/** + * 业务 数据层 + * + * @author roomroot + */ +public interface GenTableMapper extends BaseMapper +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectMysqlTableListByNames(@Param("tableNames")String[] tableNames); + public List selectDMTableListByNames(@Param("tableNames")String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询表ID业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 查询表名称业务信息 + * + * @param tableName 表名称 + * @return 业务信息 + */ + public GenTable selectGenTableByName(String tableName); + + /** + * 新增业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int insertGenTable(GenTable genTable); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public int updateGenTable(GenTable genTable); + + /** + * 批量删除业务 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableByIds(Long[] ids); + + /** + * 创建表 + * + * @param sql 表结构 + * @return 结果 + */ + public int createTable(String sql); +} diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/GenTableColumnServiceImpl.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/GenTableColumnServiceImpl.java new file mode 100644 index 000000000..6d0ab4910 --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/GenTableColumnServiceImpl.java @@ -0,0 +1,68 @@ +package com.roomroot.generator.service; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.roomroot.common.core.text.Convert; +import com.roomroot.generator.domain.GenTableColumn; +import com.roomroot.generator.mapper.GenTableColumnMapper; + +/** + * 业务字段 服务层实现 + * + * @author roomroot + */ +@Service +public class GenTableColumnServiceImpl implements IGenTableColumnService +{ + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + @Override + public List selectGenTableColumnListByTableId(Long tableId) + { + return genTableColumnMapper.selectGenTableColumnListByTableId(tableId); + } + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int insertGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.insertGenTableColumn(genTableColumn); + } + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + @Override + public int updateGenTableColumn(GenTableColumn genTableColumn) + { + return genTableColumnMapper.updateGenTableColumn(genTableColumn); + } + + /** + * 删除业务字段对象 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteGenTableColumnByIds(String ids) + { + return genTableColumnMapper.deleteGenTableColumnByIds(Convert.toLongArray(ids)); + } +} diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/GenTableServiceImpl.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/GenTableServiceImpl.java new file mode 100644 index 000000000..10f17b548 --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/GenTableServiceImpl.java @@ -0,0 +1,585 @@ +package com.roomroot.generator.service; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import com.roomroot.common.utils.DbTypeUtil; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.velocity.Template; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.constant.GenConstants; +import com.roomroot.common.core.text.CharsetKit; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.generator.domain.GenTable; +import com.roomroot.generator.domain.GenTableColumn; +import com.roomroot.generator.mapper.GenTableColumnMapper; +import com.roomroot.generator.mapper.GenTableMapper; +import com.roomroot.generator.util.GenUtils; +import com.roomroot.generator.util.VelocityInitializer; +import com.roomroot.generator.util.VelocityUtils; + +/** + * 业务 服务层实现 + * + * @author roomroot + */ +@Service +public class GenTableServiceImpl implements IGenTableService +{ + private static final Logger log = LoggerFactory.getLogger(GenTableServiceImpl.class); + + @Autowired + private GenTableMapper genTableMapper; + + @Autowired + private GenTableColumnMapper genTableColumnMapper; + + @Autowired + DbTypeUtil dbTypeUtil; + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + @Override + public GenTable selectGenTableById(Long id) + { + GenTable genTable = genTableMapper.selectGenTableById(id); + setTableFromOptions(genTable); + return genTable; + } + + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + @Override + public List selectGenTableList(GenTable genTable) + { + return genTableMapper.selectGenTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + @Override + public List selectDbTableList(GenTable genTable) + { + return genTableMapper.selectDbTableList(genTable); + } + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + @Override + public List selectDbTableListByNames(String[] tableNames) + { + String dbType = dbTypeUtil.getDbType(); + if("dm".equals(dbType)){ + return genTableMapper.selectDMTableListByNames(tableNames); + }else{ + return genTableMapper.selectMysqlTableListByNames(tableNames); + } + } + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + @Override + public List selectGenTableAll() + { + return genTableMapper.selectGenTableAll(); + } + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + @Override + @Transactional + public void updateGenTable(GenTable genTable) + { + String options = JSON.toJSONString(genTable.getParams()); + genTable.setOptions(options); + int row = genTableMapper.updateGenTable(genTable); + if (row > 0) + { + for (GenTableColumn genTableColumn : genTable.getColumns()) + { + genTableColumnMapper.updateGenTableColumn(genTableColumn); + } + } + } + + /** + * 删除业务对象 + * + * @param tableIds 需要删除的数据ID + * @return 结果 + */ + @Override + @Transactional + public void deleteGenTableByIds(Long[] tableIds) + { + genTableMapper.deleteGenTableByIds(tableIds); + genTableColumnMapper.deleteGenTableColumnByIds(tableIds); + } + + /** + * 创建表 + * + * @param sql 创建表语句 + * @return 结果 + */ + @Override + public boolean createTable(String sql) + { + return genTableMapper.createTable(sql) == 0; + } + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + */ + @Override + @Transactional + public void importGenTable(List tableList, String tplWebType, String operName) + { + try + { + for (GenTable table : tableList) + { + String tableName = table.getTableName(); + table.setTplWebType(tplWebType); + GenUtils.initTable(table, operName); + int row = genTableMapper.insertGenTable(table); + if (row > 0) + { + // 保存列信息 + List genTableColumns = new ArrayList<>(); + String dbType = dbTypeUtil.getDbType(); + if("dm".equals(dbType)){ + genTableColumns = genTableColumnMapper.selectDMTableColumnsByName(tableName); + }else{ + genTableColumns = genTableColumnMapper.selectMysqlTableColumnsByName(tableName); + } + + for (GenTableColumn column : genTableColumns) + { + GenUtils.initColumnField(column, table); + int i = genTableColumnMapper.insertGenTableColumn(column); + System.out.println(i); + } + } + } + } + catch (Exception e) + { + throw new ServiceException("导入失败:" + e.getMessage()); + } + } + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + @Override + public Map previewCode(Long tableId) + { + Map dataMap = new LinkedHashMap<>(); + // 查询表信息 + GenTable table = genTableMapper.selectGenTableById(tableId); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table); + for (String template : templates) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + dataMap.put(template, sw.toString()); + } + return dataMap; + } + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + @Override + public byte[] downloadCode(String tableName) + { + return downloadCode(new String[] { tableName }); + } + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + */ + @Override + public void generatorCode(String tableName) + { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table); + for (String template : templates) + { + if (!StringUtils.containsAny(template, "sql.vm", "api.js.vm", "api.ts.vm", "type.ts.vm", "index.ts.vm", "index.vue.vm", "index-tree.vue.vm", "view.vue.vm")) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + try + { + String path = getGenPath(table, template); + FileUtils.writeStringToFile(new File(path), sw.toString(), CharsetKit.UTF_8); + } + catch (IOException e) + { + throw new ServiceException("渲染模板失败,表名:" + table.getTableName()); + } + } + } + } + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + @Override + @Transactional + public void synchDb(String tableName) + { + GenTable table = genTableMapper.selectGenTableByName(tableName); + List tableColumns = table.getColumns(); + Map tableColumnMap = tableColumns.stream().collect(Collectors.toMap(GenTableColumn::getColumnName, Function.identity())); + + List dbTableColumns = new ArrayList<>(); + String dbType = dbTypeUtil.getDbType(); + if("dm".equals(dbType)){ + dbTableColumns = genTableColumnMapper.selectDMTableColumnsByName(tableName); + }else{ + dbTableColumns = genTableColumnMapper.selectMysqlTableColumnsByName(tableName); + } + + if (StringUtils.isEmpty(dbTableColumns)) + { + throw new ServiceException("同步数据失败,原表结构不存在"); + } + List dbTableColumnNames = dbTableColumns.stream().map(GenTableColumn::getColumnName).collect(Collectors.toList()); + + dbTableColumns.forEach(column -> { + GenUtils.initColumnField(column, table); + if (tableColumnMap.containsKey(column.getColumnName())) + { + GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName()); + column.setColumnId(prevColumn.getColumnId()); + if (column.isList()) + { + // 如果是列表,继续保留查询方式/字典类型选项 + column.setDictType(prevColumn.getDictType()); + column.setQueryType(prevColumn.getQueryType()); + } + if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk() + && (column.isInsert() || column.isEdit()) + && ((column.isUsableColumn()) || (!column.isSuperColumn()))) + { + // 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项 + column.setIsRequired(prevColumn.getIsRequired()); + column.setHtmlType(prevColumn.getHtmlType()); + } + genTableColumnMapper.updateGenTableColumn(column); + } + else + { + genTableColumnMapper.insertGenTableColumn(column); + } + }); + + List delColumns = tableColumns.stream().filter(column -> !dbTableColumnNames.contains(column.getColumnName())).collect(Collectors.toList()); + if (StringUtils.isNotEmpty(delColumns)) + { + genTableColumnMapper.deleteGenTableColumns(delColumns); + } + } + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + @Override + public byte[] downloadCode(String[] tableNames) + { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ZipOutputStream zip = new ZipOutputStream(outputStream); + Map typeFiles = new HashMap<>(); + for (String tableName : tableNames) + { + generatorCode(tableName, zip, typeFiles); + } + for (Map.Entry entry : typeFiles.entrySet()) + { + writeToZip(zip, entry.getKey(), entry.getValue().toString()); + } + IOUtils.closeQuietly(zip); + return outputStream.toByteArray(); + } + + /** + * 查询表信息并生成代码 + */ + private void generatorCode(String tableName, ZipOutputStream zip, Map typeFiles) + { + // 查询表信息 + GenTable table = genTableMapper.selectGenTableByName(tableName); + // 设置主子表信息 + setSubTable(table); + // 设置主键列信息 + setPkColumn(table); + + VelocityInitializer.initVelocity(); + + VelocityContext context = VelocityUtils.prepareContext(table); + + // 获取模板列表 + List templates = VelocityUtils.getTemplateList(table); + for (String template : templates) + { + // 渲染模板 + StringWriter sw = new StringWriter(); + Template tpl = Velocity.getTemplate(template, Constants.UTF8); + tpl.merge(context, sw); + String fileName = VelocityUtils.getFileName(template, table); + // index-bak.ts 模版,追加内容 + if (fileName.contains("index-bak.ts")) + { + if (!typeFiles.containsKey(fileName)) + { + typeFiles.put(fileName, new StringBuffer(sw.toString())); + } + else + { + Arrays.stream(sw.toString().split("\n")).filter(line -> line.startsWith("export * from")).forEach(line -> typeFiles.get(fileName).append("\n").append(line)); + } + } + else + { + // 其他文件正常添加 + writeToZip(zip, fileName, sw.toString()); + } + } + } + + /** + * 将字符串内容写入ZIP输出流 + * + * @param zip ZIP输出流 + * @param fileName ZIP条目名称(即文件名) + * @param content 要写入的内容 + */ + private void writeToZip(ZipOutputStream zip, String fileName, String content) + { + try + { + zip.putNextEntry(new ZipEntry(fileName)); + IOUtils.write(content, zip, Constants.UTF8); + zip.flush(); + zip.closeEntry(); + } + catch (IOException e) + { + log.error("写入ZIP文件失败,文件名: " + fileName, e); + } + } + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + @Override + public void validateEdit(GenTable genTable) + { + if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) + { + String options = JSON.toJSONString(genTable.getParams()); + JSONObject paramsObj = JSON.parseObject(options); + if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_CODE))) + { + throw new ServiceException("树编码字段不能为空"); + } + else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_PARENT_CODE))) + { + throw new ServiceException("树父编码字段不能为空"); + } + else if (StringUtils.isEmpty(paramsObj.getString(GenConstants.TREE_NAME))) + { + throw new ServiceException("树名称字段不能为空"); + } + } + else if (GenConstants.TPL_SUB.equals(genTable.getTplCategory())) + { + if (StringUtils.isEmpty(genTable.getSubTableName())) + { + throw new ServiceException("关联子表的表名不能为空"); + } + else if (StringUtils.isEmpty(genTable.getSubTableFkName())) + { + throw new ServiceException("子表关联的外键名不能为空"); + } + } + } + + /** + * 设置主键列信息 + * + * @param table 业务表信息 + */ + public void setPkColumn(GenTable table) + { + for (GenTableColumn column : table.getColumns()) + { + if (column.isPk()) + { + table.setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getPkColumn())) + { + table.setPkColumn(table.getColumns().get(0)); + } + if (GenConstants.TPL_SUB.equals(table.getTplCategory())) + { + for (GenTableColumn column : table.getSubTable().getColumns()) + { + if (column.isPk()) + { + table.getSubTable().setPkColumn(column); + break; + } + } + if (StringUtils.isNull(table.getSubTable().getPkColumn())) + { + table.getSubTable().setPkColumn(table.getSubTable().getColumns().get(0)); + } + } + } + + /** + * 设置主子表信息 + * + * @param table 业务表信息 + */ + public void setSubTable(GenTable table) + { + String subTableName = table.getSubTableName(); + if (StringUtils.isNotEmpty(subTableName)) + { + table.setSubTable(genTableMapper.selectGenTableByName(subTableName)); + } + } + + /** + * 设置代码生成其他选项值 + * + * @param genTable 设置后的生成对象 + */ + public void setTableFromOptions(GenTable genTable) + { + JSONObject paramsObj = JSON.parseObject(genTable.getOptions()); + if (StringUtils.isNotNull(paramsObj)) + { + String treeCode = paramsObj.getString(GenConstants.TREE_CODE); + String treeParentCode = paramsObj.getString(GenConstants.TREE_PARENT_CODE); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + Long parentMenuId = paramsObj.getLongValue(GenConstants.PARENT_MENU_ID); + String parentMenuName = paramsObj.getString(GenConstants.PARENT_MENU_NAME); + boolean isView = paramsObj.getBooleanValue(GenConstants.GEN_VIEW); + + genTable.setTreeCode(treeCode); + genTable.setTreeParentCode(treeParentCode); + genTable.setTreeName(treeName); + genTable.setParentMenuId(parentMenuId); + genTable.setParentMenuName(parentMenuName); + genTable.setView(isView); + } + } + + /** + * 获取代码生成地址 + * + * @param table 业务表信息 + * @param template 模板文件路径 + * @return 生成地址 + */ + public static String getGenPath(GenTable table, String template) + { + String genPath = table.getGenPath(); + if (StringUtils.equals(genPath, "/")) + { + return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table); + } + return genPath + File.separator + VelocityUtils.getFileName(template, table); + } +} \ No newline at end of file diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/IGenTableColumnService.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/IGenTableColumnService.java new file mode 100644 index 000000000..c6cd44cfe --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/IGenTableColumnService.java @@ -0,0 +1,44 @@ +package com.roomroot.generator.service; + +import java.util.List; +import com.roomroot.generator.domain.GenTableColumn; + +/** + * 业务字段 服务层 + * + * @author roomroot + */ +public interface IGenTableColumnService +{ + /** + * 查询业务字段列表 + * + * @param tableId 业务字段编号 + * @return 业务字段集合 + */ + public List selectGenTableColumnListByTableId(Long tableId); + + /** + * 新增业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int insertGenTableColumn(GenTableColumn genTableColumn); + + /** + * 修改业务字段 + * + * @param genTableColumn 业务字段信息 + * @return 结果 + */ + public int updateGenTableColumn(GenTableColumn genTableColumn); + + /** + * 删除业务字段信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteGenTableColumnByIds(String ids); +} diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/IGenTableService.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/IGenTableService.java new file mode 100644 index 000000000..01f5a9a3c --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/service/IGenTableService.java @@ -0,0 +1,131 @@ +package com.roomroot.generator.service; + +import java.util.List; +import java.util.Map; +import com.roomroot.generator.domain.GenTable; + +/** + * 业务 服务层 + * + * @author roomroot + */ +public interface IGenTableService +{ + /** + * 查询业务列表 + * + * @param genTable 业务信息 + * @return 业务集合 + */ + public List selectGenTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param genTable 业务信息 + * @return 数据库表集合 + */ + public List selectDbTableList(GenTable genTable); + + /** + * 查询据库列表 + * + * @param tableNames 表名称组 + * @return 数据库表集合 + */ + public List selectDbTableListByNames(String[] tableNames); + + /** + * 查询所有表信息 + * + * @return 表信息集合 + */ + public List selectGenTableAll(); + + /** + * 查询业务信息 + * + * @param id 业务ID + * @return 业务信息 + */ + public GenTable selectGenTableById(Long id); + + /** + * 修改业务 + * + * @param genTable 业务信息 + * @return 结果 + */ + public void updateGenTable(GenTable genTable); + + /** + * 删除业务信息 + * + * @param tableIds 需要删除的表数据ID + * @return 结果 + */ + public void deleteGenTableByIds(Long[] tableIds); + + /** + * 创建表 + * + * @param sql 创建表语句 + * @return 结果 + */ + public boolean createTable(String sql); + + /** + * 导入表结构 + * + * @param tableList 导入表列表 + * @param tplWebType 前端类型 + * @param operName 操作人员 + */ + public void importGenTable(List tableList, String tplWebType, String operName); + + /** + * 预览代码 + * + * @param tableId 表编号 + * @return 预览数据列表 + */ + public Map previewCode(Long tableId); + + /** + * 生成代码(下载方式) + * + * @param tableName 表名称 + * @return 数据 + */ + public byte[] downloadCode(String tableName); + + /** + * 生成代码(自定义路径) + * + * @param tableName 表名称 + * @return 数据 + */ + public void generatorCode(String tableName); + + /** + * 同步数据库 + * + * @param tableName 表名称 + */ + public void synchDb(String tableName); + + /** + * 批量生成代码(下载方式) + * + * @param tableNames 表数组 + * @return 数据 + */ + public byte[] downloadCode(String[] tableNames); + + /** + * 修改保存参数校验 + * + * @param genTable 业务信息 + */ + public void validateEdit(GenTable genTable); +} diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/util/GenUtils.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/util/GenUtils.java new file mode 100644 index 000000000..fe8344814 --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/util/GenUtils.java @@ -0,0 +1,257 @@ +package com.roomroot.generator.util; + +import java.util.Arrays; +import org.apache.commons.lang3.RegExUtils; +import com.roomroot.common.constant.GenConstants; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.generator.config.GenConfig; +import com.roomroot.generator.domain.GenTable; +import com.roomroot.generator.domain.GenTableColumn; + +/** + * 代码生成器 工具类 + * + * @author roomroot + */ +public class GenUtils +{ + /** + * 初始化表信息 + */ + public static void initTable(GenTable genTable, String operName) + { + genTable.setClassName(convertClassName(genTable.getTableName())); + genTable.setPackageName(GenConfig.getPackageName()); + genTable.setModuleName(getModuleName(GenConfig.getPackageName())); + genTable.setBusinessName(getBusinessName(genTable.getTableName())); + genTable.setFunctionName(replaceText(genTable.getTableComment())); + genTable.setFunctionAuthor(GenConfig.getAuthor()); + genTable.setCreateBy(operName); + } + + /** + * 初始化列属性字段 + */ + public static void initColumnField(GenTableColumn column, GenTable table) + { + String dataType = getDbType(column.getColumnType()); + String columnName = column.getColumnName(); + column.setTableId(table.getTableId()); + column.setCreateBy(table.getCreateBy()); + // 设置java字段名 + column.setJavaField(StringUtils.toCamelCase(columnName)); + // 设置默认类型 + column.setJavaType(GenConstants.TYPE_STRING); + column.setQueryType(GenConstants.QUERY_EQ); + + if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) + { + // 字符串长度超过500设置为文本域 + Integer columnLength = getColumnLength(column.getColumnType()); + String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT; + column.setHtmlType(htmlType); + } + else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) + { + column.setJavaType(GenConstants.TYPE_DATE); + column.setHtmlType(GenConstants.HTML_DATETIME); + } + else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) + { + column.setHtmlType(GenConstants.HTML_INPUT); + + // 如果是浮点型 统一用BigDecimal + String[] str = StringUtils.split(StringUtils.substringBetween(column.getColumnType(), "(", ")"), ","); + if (str != null && str.length == 2 && Integer.parseInt(str[1]) > 0) + { + column.setJavaType(GenConstants.TYPE_BIGDECIMAL); + } + // 如果是整形 + else if (str != null && str.length == 1 && Integer.parseInt(str[0]) <= 10) + { + column.setJavaType(GenConstants.TYPE_INTEGER); + } + // 长整形 + else + { + column.setJavaType(GenConstants.TYPE_LONG); + } + } + + // 插入字段(默认所有字段都需要插入) + column.setIsInsert(GenConstants.REQUIRE); + + // 编辑字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName) && !column.isPk()) + { + column.setIsEdit(GenConstants.REQUIRE); + } + // 列表字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName) && !column.isPk()) + { + column.setIsList(GenConstants.REQUIRE); + } + // 查询字段 + if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) + { + column.setIsQuery(GenConstants.REQUIRE); + } + + // 查询字段类型 + if (StringUtils.endsWithIgnoreCase(columnName, "name")) + { + column.setQueryType(GenConstants.QUERY_LIKE); + } + // 状态字段设置单选框 + if (StringUtils.endsWithIgnoreCase(columnName, "status")) + { + column.setHtmlType(GenConstants.HTML_RADIO); + } + // 类型&性别字段设置下拉框 + else if (StringUtils.endsWithIgnoreCase(columnName, "type") + || StringUtils.endsWithIgnoreCase(columnName, "sex")) + { + column.setHtmlType(GenConstants.HTML_SELECT); + } + // 图片字段设置图片上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "image")) + { + column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD); + } + // 文件字段设置文件上传控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "file")) + { + column.setHtmlType(GenConstants.HTML_FILE_UPLOAD); + } + // 内容字段设置富文本控件 + else if (StringUtils.endsWithIgnoreCase(columnName, "content")) + { + column.setHtmlType(GenConstants.HTML_EDITOR); + } + } + + /** + * 校验数组是否包含指定值 + * + * @param arr 数组 + * @param targetValue 值 + * @return 是否包含 + */ + public static boolean arraysContains(String[] arr, String targetValue) + { + return Arrays.asList(arr).contains(targetValue); + } + + /** + * 获取模块名 + * + * @param packageName 包名 + * @return 模块名 + */ + public static String getModuleName(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + int nameLength = packageName.length(); + return StringUtils.substring(packageName, lastIndex + 1, nameLength); + } + + /** + * 获取业务名 + * + * @param tableName 表名 + * @return 业务名 + */ + public static String getBusinessName(String tableName) + { + int lastIndex = tableName.lastIndexOf("_"); + int nameLength = tableName.length(); + return StringUtils.substring(tableName, lastIndex + 1, nameLength); + } + + /** + * 表名转换成Java类名 + * + * @param tableName 表名称 + * @return 类名 + */ + public static String convertClassName(String tableName) + { + boolean autoRemovePre = GenConfig.getAutoRemovePre(); + String tablePrefix = GenConfig.getTablePrefix(); + if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) + { + String[] searchList = StringUtils.split(tablePrefix, ","); + tableName = replaceFirst(tableName, searchList); + } + return StringUtils.convertToCamelCase(tableName); + } + + /** + * 批量替换前缀 + * + * @param replacementm 替换值 + * @param searchList 替换列表 + * @return + */ + public static String replaceFirst(String replacementm, String[] searchList) + { + String text = replacementm; + for (String searchString : searchList) + { + if (replacementm.startsWith(searchString)) + { + text = replacementm.replaceFirst(searchString, ""); + break; + } + } + return text; + } + + /** + * 关键字替换 + * + * @param text 需要被替换的名字 + * @return 替换后的名字 + */ + public static String replaceText(String text) + { + return RegExUtils.replaceAll(text, "(?:表|风影随行)", ""); + } + + /** + * 获取数据库类型字段 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static String getDbType(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + return StringUtils.substringBefore(columnType, "("); + } + else + { + return columnType; + } + } + + /** + * 获取字段长度 + * + * @param columnType 列类型 + * @return 截取后的列类型 + */ + public static Integer getColumnLength(String columnType) + { + if (StringUtils.indexOf(columnType, "(") > 0) + { + String length = StringUtils.substringBetween(columnType, "(", ")"); + return Integer.valueOf(length); + } + else + { + return 0; + } + } +} diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/util/VelocityInitializer.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/util/VelocityInitializer.java new file mode 100644 index 000000000..2d58875a7 --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/util/VelocityInitializer.java @@ -0,0 +1,34 @@ +package com.roomroot.generator.util; + +import java.util.Properties; +import org.apache.velocity.app.Velocity; +import com.roomroot.common.constant.Constants; + +/** + * VelocityEngine工厂 + * + * @author roomroot + */ +public class VelocityInitializer +{ + /** + * 初始化vm方法 + */ + public static void initVelocity() + { + Properties p = new Properties(); + try + { + // 加载classpath目录下的vm文件 + p.setProperty("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); + // 定义字符集 + p.setProperty(Velocity.INPUT_ENCODING, Constants.UTF8); + // 初始化Velocity引擎,指定配置Properties + Velocity.init(p); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } +} diff --git a/backend/roomroot-generator/src/main/java/com/roomroot/generator/util/VelocityUtils.java b/backend/roomroot-generator/src/main/java/com/roomroot/generator/util/VelocityUtils.java new file mode 100644 index 000000000..9c6d15c3c --- /dev/null +++ b/backend/roomroot-generator/src/main/java/com/roomroot/generator/util/VelocityUtils.java @@ -0,0 +1,492 @@ +package com.roomroot.generator.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.velocity.VelocityContext; +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.roomroot.common.constant.GenConstants; +import com.roomroot.common.utils.DateUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.generator.domain.GenTable; +import com.roomroot.generator.domain.GenTableColumn; + +/** + * 模板处理工具类 + * + * @author roomroot + */ +public class VelocityUtils +{ + /** 项目空间路径 */ + private static final String PROJECT_PATH = "main/java"; + + /** mybatis空间路径 */ + private static final String MYBATIS_PATH = "main/resources/mapper"; + + /** 默认上级菜单,系统工具 */ + private static final String DEFAULT_PARENT_MENU_ID = "3"; + + /** Vue3 Element Plus 模版 */ + private static final String ELEMENT_PLUS = "element-plus"; + + /** Vue3 Element Plus TypeScript 模版 */ + private static final String ELEMENT_PLUS_TYPESSRIPT = "element-plus-typescript"; + + /** + * 设置模板变量信息 + * + * @return 模板列表 + */ + public static VelocityContext prepareContext(GenTable genTable) + { + String moduleName = genTable.getModuleName(); + String businessName = genTable.getBusinessName(); + String packageName = genTable.getPackageName(); + String tplCategory = genTable.getTplCategory(); + String functionName = genTable.getFunctionName(); + + VelocityContext velocityContext = new VelocityContext(); + velocityContext.put("tplCategory", genTable.getTplCategory()); + velocityContext.put("tableName", genTable.getTableName()); + velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】"); + velocityContext.put("ClassName", genTable.getClassName()); + velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName())); + velocityContext.put("moduleName", genTable.getModuleName()); + velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName())); + velocityContext.put("businessName", genTable.getBusinessName()); + velocityContext.put("basePackage", getPackagePrefix(packageName)); + velocityContext.put("packageName", packageName); + velocityContext.put("author", genTable.getFunctionAuthor()); + velocityContext.put("colSpan", getColSpan(genTable.getFormColNum())); + velocityContext.put("datetime", DateUtils.getDate()); + velocityContext.put("pkColumn", genTable.getPkColumn()); + velocityContext.put("importList", getImportList(genTable)); + velocityContext.put("permissionPrefix", getPermissionPrefix(moduleName, businessName)); + velocityContext.put("columns", genTable.getColumns()); + velocityContext.put("table", genTable); + velocityContext.put("dicts", getDicts(genTable)); + setExtensionsContext(velocityContext, genTable.getOptions()); + setMenuVelocityContext(velocityContext, genTable); + if (GenConstants.TPL_TREE.equals(tplCategory)) + { + setTreeVelocityContext(velocityContext, genTable); + } + if (GenConstants.TPL_SUB.equals(tplCategory)) + { + setSubVelocityContext(velocityContext, genTable); + } + return velocityContext; + } + + public static void setExtensionsContext(VelocityContext context, String options) + { + JSONObject paramsObj = JSONObject.parseObject(options); + boolean genView = genView(paramsObj); + context.put("genView", genView); + } + + public static void setMenuVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String parentMenuId = getParentMenuId(paramsObj); + context.put("parentMenuId", parentMenuId); + } + + public static void setTreeVelocityContext(VelocityContext context, GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeCode = getTreecode(paramsObj); + String treeParentCode = getTreeParentCode(paramsObj); + String treeName = getTreeName(paramsObj); + + context.put("treeCode", treeCode); + context.put("treeParentCode", treeParentCode); + context.put("treeName", treeName); + context.put("expandColumn", getExpandColumn(genTable)); + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + context.put("tree_parent_code", paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + context.put("tree_name", paramsObj.getString(GenConstants.TREE_NAME)); + } + } + + public static void setSubVelocityContext(VelocityContext context, GenTable genTable) + { + GenTable subTable = genTable.getSubTable(); + String subTableName = genTable.getSubTableName(); + String subTableFkName = genTable.getSubTableFkName(); + String subClassName = genTable.getSubTable().getClassName(); + String subTableFkClassName = StringUtils.convertToCamelCase(subTableFkName); + + context.put("subTable", subTable); + context.put("subTableName", subTableName); + context.put("subTableFkName", subTableFkName); + context.put("subTableFkClassName", subTableFkClassName); + context.put("subTableFkclassName", StringUtils.uncapitalize(subTableFkClassName)); + context.put("subClassName", subClassName); + context.put("subclassName", StringUtils.uncapitalize(subClassName)); + context.put("subImportList", getImportList(genTable.getSubTable())); + } + + /** + * 获取模板信息 + * @param tplCategory 生成的模板 + * @param tplWebType 前端类型 + * @return 模板列表 + */ + public static List getTemplateList(GenTable table) + { + String tplWebType = table.getTplWebType(); + String tplCategory = table.getTplCategory(); + JSONObject paramsObj = JSONObject.parseObject(table.getOptions()); + boolean isView = genView(paramsObj); + String useWebType = "vm/vue"; + String apiTemplate = "vm/js/api.js.vm"; + if (StringUtils.equals(ELEMENT_PLUS, tplWebType)) + { + useWebType = "vm/vue/v3"; + } + else if (StringUtils.equals(ELEMENT_PLUS_TYPESSRIPT, tplWebType)) + { + useWebType = "vm/vue/v3ts"; + apiTemplate = "vm/ts/api.ts.vm"; + } + List templates = new ArrayList(); + templates.add("vm/java/domain.java.vm"); + templates.add("vm/java/mapper.java.vm"); + templates.add("vm/java/service.java.vm"); + templates.add("vm/java/serviceImpl.java.vm"); + templates.add("vm/java/controller.java.vm"); + templates.add("vm/xml/mapper.xml.vm"); + templates.add("vm/sql/sql.vm"); + templates.add(apiTemplate); + if (StringUtils.equals(ELEMENT_PLUS_TYPESSRIPT, tplWebType)) + { + templates.add("vm/ts/type.ts.vm"); + templates.add("vm/ts/index.ts.vm"); + } + if (GenConstants.TPL_CRUD.equals(tplCategory)) + { + templates.add(useWebType + "/index.vue.vm"); + } + else if (GenConstants.TPL_TREE.equals(tplCategory)) + { + templates.add(useWebType + "/index-tree.vue.vm"); + } + else if (GenConstants.TPL_SUB.equals(tplCategory)) + { + templates.add(useWebType + "/index.vue.vm"); + templates.add("vm/java/sub-domain.java.vm"); + } + if (isView) + { + templates.add(useWebType + "/view.vue.vm"); + } + return templates; + } + + /** + * 获取文件名 + */ + public static String getFileName(String template, GenTable genTable) + { + // 文件名称 + String fileName = ""; + // 包路径 + String packageName = genTable.getPackageName(); + // 模块名 + String moduleName = genTable.getModuleName(); + // 大写类名 + String className = genTable.getClassName(); + // 业务名称 + String businessName = genTable.getBusinessName(); + + String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/"); + String mybatisPath = MYBATIS_PATH + "/" + moduleName; + String vuePath = "vue"; + + if (template.contains("domain.java.vm")) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, className); + } + if (template.contains("sub-domain.java.vm") && StringUtils.equals(GenConstants.TPL_SUB, genTable.getTplCategory())) + { + fileName = StringUtils.format("{}/domain/{}.java", javaPath, genTable.getSubTable().getClassName()); + } + else if (template.contains("mapper.java.vm")) + { + fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className); + } + else if (template.contains("service.java.vm")) + { + fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className); + } + else if (template.contains("serviceImpl.java.vm")) + { + fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className); + } + else if (template.contains("controller.java.vm")) + { + fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className); + } + else if (template.contains("mapper.xml.vm")) + { + fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className); + } + else if (template.contains("sql.vm")) + { + fileName = businessName + "Menu.sql"; + } + else if (template.contains("api.js.vm")) + { + fileName = StringUtils.format("{}/api/{}/{}.js", vuePath, moduleName, businessName); + } + else if (template.contains("api.ts.vm")) + { + fileName = StringUtils.format("{}/api/{}/{}.ts", vuePath, moduleName, businessName); + } + else if (template.contains("type.ts.vm")) + { + fileName = StringUtils.format("{}/types/api/{}/{}.ts", vuePath, moduleName, businessName); + } + else if (template.contains("index.ts.vm")) + { + fileName = StringUtils.format("{}/types/api/index-bak.ts", vuePath); + } + else if (template.contains("index.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + else if (template.contains("index-tree.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/index.vue", vuePath, moduleName, businessName); + } + else if (template.contains("view.vue.vm")) + { + fileName = StringUtils.format("{}/views/{}/{}/view.vue", vuePath, moduleName, businessName); + } + return fileName; + } + + /** + * 获取包前缀 + * + * @param packageName 包名称 + * @return 包前缀名称 + */ + public static String getPackagePrefix(String packageName) + { + int lastIndex = packageName.lastIndexOf("."); + return StringUtils.substring(packageName, 0, lastIndex); + } + + /** + * 根据列类型获取导入包 + * + * @param genTable 业务表对象 + * @return 返回需要导入的包列表 + */ + public static HashSet getImportList(GenTable genTable) + { + List columns = genTable.getColumns(); + GenTable subGenTable = genTable.getSubTable(); + HashSet importList = new HashSet(); + if (StringUtils.isNotNull(subGenTable)) + { + importList.add("java.util.List"); + } + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && GenConstants.TYPE_DATE.equals(column.getJavaType())) + { + importList.add("java.util.Date"); + importList.add("com.fasterxml.jackson.annotation.JsonFormat"); + } + else if (!column.isSuperColumn() && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) + { + importList.add("java.math.BigDecimal"); + } + } + return importList; + } + + /** + * 根据列类型获取字典组 + * + * @param genTable 业务表对象 + * @return 返回字典组 + */ + public static String getDicts(GenTable genTable) + { + List columns = genTable.getColumns(); + Set dicts = new HashSet(); + addDicts(dicts, columns); + if (StringUtils.isNotNull(genTable.getSubTable())) + { + List subColumns = genTable.getSubTable().getColumns(); + addDicts(dicts, subColumns); + } + return StringUtils.join(dicts, ", "); + } + + /** + * 添加字典列表 + * + * @param dicts 字典列表 + * @param columns 列集合 + */ + public static void addDicts(Set dicts, List columns) + { + for (GenTableColumn column : columns) + { + if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny( + column.getHtmlType(), + new String[] { GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX })) + { + dicts.add("'" + column.getDictType() + "'"); + } + } + } + + /** + * 获取权限前缀 + * + * @param moduleName 模块名称 + * @param businessName 业务名称 + * @return 返回权限前缀 + */ + public static String getPermissionPrefix(String moduleName, String businessName) + { + return StringUtils.format("{}:{}", moduleName, businessName); + } + + /** + * 获取上级菜单ID字段 + * + * @param paramsObj 生成其他选项 + * @return 上级菜单ID字段 + */ + public static String getParentMenuId(JSONObject paramsObj) + { + if (StringUtils.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID) + && StringUtils.isNotEmpty(paramsObj.getString(GenConstants.PARENT_MENU_ID))) + { + return paramsObj.getString(GenConstants.PARENT_MENU_ID); + } + return DEFAULT_PARENT_MENU_ID; + } + + /** + * 获取树编码 + * + * @param paramsObj 生成其他选项 + * @return 树编码 + */ + public static String getTreecode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 获取树父编码 + * + * @param paramsObj 生成其他选项 + * @return 树父编码 + */ + public static String getTreeParentCode(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_PARENT_CODE)); + } + return StringUtils.EMPTY; + } + + /** + * 扩展功能/生成详情页 + * + * @param paramsObj 生成其他选项 + * @return 是否生成详细页 + */ + public static boolean genView(JSONObject paramsObj) + { + if (StringUtils.isNotNull(paramsObj) && paramsObj.containsKey(GenConstants.GEN_VIEW)) + { + return paramsObj.getBoolean(GenConstants.GEN_VIEW); + } + return false; + } + + /** + * 获取树名称 + * + * @param paramsObj 生成其他选项 + * @return 树名称 + */ + public static String getTreeName(JSONObject paramsObj) + { + if (paramsObj.containsKey(GenConstants.TREE_NAME)) + { + return StringUtils.toCamelCase(paramsObj.getString(GenConstants.TREE_NAME)); + } + return StringUtils.EMPTY; + } + + /** + * 获取需要在哪一列上面显示展开按钮 + * + * @param genTable 业务表对象 + * @return 展开按钮列序号 + */ + public static int getExpandColumn(GenTable genTable) + { + String options = genTable.getOptions(); + JSONObject paramsObj = JSON.parseObject(options); + String treeName = paramsObj.getString(GenConstants.TREE_NAME); + int num = 0; + for (GenTableColumn column : genTable.getColumns()) + { + if (column.isList()) + { + num++; + String columnName = column.getColumnName(); + if (columnName.equals(treeName)) + { + break; + } + } + } + return num; + } + + /** + * 获取表单 el-col span + * + * @param formColNum 表单布局方式(1单列 2双列 3三列) + * @return span 数值字符串 + */ + public static String getColSpan(int formColNum) + { + if (formColNum == 2) + { + return "12"; + } + else if (formColNum == 3) + { + return "8"; + } + return "24"; + } +} diff --git a/backend/roomroot-generator/src/main/resources/generator.yml b/backend/roomroot-generator/src/main/resources/generator.yml new file mode 100644 index 000000000..4630ae7a9 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/generator.yml @@ -0,0 +1,12 @@ +# 代码生成 +gen: + # 作者 + author: roomroot + # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool + packageName: com.roomroot.system + # 自动去除表前缀,默认是false + autoRemovePre: false + # 表前缀(生成类名不会包含表前缀,多个用逗号分隔) + tablePrefix: sys_ + # 是否允许生成文件覆盖到本地(自定义路径),默认不允许 + allowOverwrite: false \ No newline at end of file diff --git a/backend/roomroot-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml b/backend/roomroot-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml new file mode 100644 index 000000000..d231013a0 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select column_id, table_id, column_name, column_comment, column_type, java_type, java_field, is_pk, is_increment, is_required, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, create_by, create_time, update_by, update_time from gen_table_column + + + + + + + + + + insert into GEN_TABLE_COLUMN ( + table_id, + column_name, + column_comment, + column_type, + java_type, + java_field, + is_pk, + is_increment, + is_required, + is_insert, + is_edit, + is_list, + is_query, + query_type, + html_type, + dict_type, + sort, + create_by, + create_time + )values( + #{tableId}, + #{columnName}, + #{columnComment}, + #{columnType}, + #{javaType}, + #{javaField}, + #{isPk}, + #{isIncrement}, + #{isRequired}, + #{isInsert}, + #{isEdit}, + #{isList}, + #{isQuery}, + #{queryType}, + #{htmlType}, + #{dictType}, + #{sort}, + #{createBy}, + sysdate() + ) + + + + update gen_table_column + + column_comment = #{columnComment}, + java_type = #{javaType}, + column_type = #{columnType}, + java_field = #{javaField}, + is_insert = #{isInsert}, + is_edit = #{isEdit}, + is_list = #{isList}, + is_query = #{isQuery}, + is_required = #{isRequired}, + query_type = #{queryType}, + html_type = #{htmlType}, + dict_type = #{dictType}, + sort = #{sort}, + update_by = #{updateBy}, + update_time = sysdate() + + where column_id = #{columnId} + + + + delete from gen_table_column where table_id in + + #{tableId} + + + + + delete from gen_table_column where column_id in + + #{item.columnId} + + + + \ No newline at end of file diff --git a/backend/roomroot-generator/src/main/resources/mapper/generator/GenTableMapper.xml b/backend/roomroot-generator/src/main/resources/mapper/generator/GenTableMapper.xml new file mode 100644 index 000000000..9deadefaa --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/mapper/generator/GenTableMapper.xml @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select table_id, table_name, table_comment, sub_table_name, sub_table_fk_name, class_name, tpl_category, tpl_web_type, package_name, module_name, business_name, function_name, function_author, form_col_num, gen_type, gen_path, options, create_by, create_time, update_by, update_time, remark from gen_table + + + + + + + + + + + + + + + + + + + + + + insert into gen_table ( + table_name, + table_comment, + class_name, + tpl_category, + tpl_web_type, + package_name, + module_name, + business_name, + function_name, + function_author, + form_col_num, + gen_type, + gen_path, + remark, + create_by, + create_time + )values( + #{tableName}, + #{tableComment}, + #{className}, + #{tplCategory}, + #{tplWebType}, + #{packageName}, + #{moduleName}, + #{businessName}, + #{functionName}, + #{functionAuthor}, + #{formColNum}, + #{genType}, + #{genPath}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + ${sql} + + + + update gen_table + + table_name = #{tableName}, + table_comment = #{tableComment}, + sub_table_name = #{subTableName}, + sub_table_fk_name = #{subTableFkName}, + class_name = #{className}, + function_author = #{functionAuthor}, + form_col_num = #{formColNum}, + gen_type = #{genType}, + gen_path = #{genPath}, + tpl_category = #{tplCategory}, + tpl_web_type = #{tplWebType}, + package_name = #{packageName}, + module_name = #{moduleName}, + business_name = #{businessName}, + function_name = #{functionName}, + options = #{options}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where table_id = #{tableId} + + + + delete from gen_table where table_id in + + #{tableId} + + + + \ No newline at end of file diff --git a/backend/roomroot-generator/src/main/resources/vm/java/controller.java.vm b/backend/roomroot-generator/src/main/resources/vm/java/controller.java.vm new file mode 100644 index 000000000..127966da8 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/java/controller.java.vm @@ -0,0 +1,115 @@ +package ${packageName}.controller; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.enums.BusinessType; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; +import com.roomroot.common.utils.poi.ExcelUtil; +#if($table.crud || $table.sub) +import com.roomroot.common.core.page.TableDataInfo; +#elseif($table.tree) +#end + +/** + * ${functionName}Controller + * + * @author ${author} + * @date ${datetime} + */ +@RestController +@RequestMapping("/${moduleName}/${businessName}") +public class ${ClassName}Controller extends BaseController +{ + @Autowired + private I${ClassName}Service ${className}Service; + + /** + * 查询${functionName}列表 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:list')") + @GetMapping("/list") +#if($table.crud || $table.sub) + public TableDataInfo list(${ClassName} ${className}) + { + startPage(); + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return getDataTable(list); + } +#elseif($table.tree) + public AjaxResult list(${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + return success(list); + } +#end + + /** + * 导出${functionName}列表 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:export')") + @Log(title = "${functionName}", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, ${ClassName} ${className}) + { + List<${ClassName}> list = ${className}Service.select${ClassName}List(${className}); + ExcelUtil<${ClassName}> util = new ExcelUtil<${ClassName}>(${ClassName}.class); + util.exportExcel(response, list, "${functionName}数据"); + } + + /** + * 获取${functionName}详细信息 + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:query')") + @GetMapping(value = "/{${pkColumn.javaField}}") + public AjaxResult getInfo(@PathVariable("${pkColumn.javaField}") ${pkColumn.javaType} ${pkColumn.javaField}) + { + return success(${className}Service.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField})); + } + + /** + * 新增${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:add')") + @Log(title = "${functionName}", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.insert${ClassName}(${className})); + } + + /** + * 修改${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:edit')") + @Log(title = "${functionName}", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody ${ClassName} ${className}) + { + return toAjax(${className}Service.update${ClassName}(${className})); + } + + /** + * 删除${functionName} + */ + @PreAuthorize("@ss.hasPermi('${permissionPrefix}:remove')") + @Log(title = "${functionName}", businessType = BusinessType.DELETE) + @DeleteMapping("/{${pkColumn.javaField}s}") + public AjaxResult remove(@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) + { + return toAjax(${className}Service.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s)); + } +} diff --git a/backend/roomroot-generator/src/main/resources/vm/java/domain.java.vm b/backend/roomroot-generator/src/main/resources/vm/java/domain.java.vm new file mode 100644 index 000000000..50a659e8d --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/java/domain.java.vm @@ -0,0 +1,105 @@ +package ${packageName}.domain; + +#foreach ($import in $importList) +import ${import}; +#end +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.roomroot.common.annotation.Excel; +#if($table.crud || $table.sub) +import com.roomroot.common.core.domain.BaseEntity; +#elseif($table.tree) +import com.roomroot.common.core.domain.TreeEntity; +#end + +/** + * ${functionName}对象 ${tableName} + * + * @author ${author} + * @date ${datetime} + */ +#if($table.crud || $table.sub) +#set($Entity="BaseEntity") +#elseif($table.tree) +#set($Entity="TreeEntity") +#end +public class ${ClassName} extends ${Entity} +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#if($table.sub) + /** $table.subTable.functionName信息 */ + private List<${subClassName}> ${subclassName}List; + +#end +#foreach ($column in $columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } + +#end +#end +#if($table.sub) + public List<${subClassName}> get${subClassName}List() + { + return ${subclassName}List; + } + + public void set${subClassName}List(List<${subClassName}> ${subclassName}List) + { + this.${subclassName}List = ${subclassName}List; + } + +#end + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end +#if($table.sub) + .append("${subclassName}List", get${subClassName}List()) +#end + .toString(); + } +} diff --git a/backend/roomroot-generator/src/main/resources/vm/java/mapper.java.vm b/backend/roomroot-generator/src/main/resources/vm/java/mapper.java.vm new file mode 100644 index 000000000..7e7d7c26f --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/java/mapper.java.vm @@ -0,0 +1,91 @@ +package ${packageName}.mapper; + +import java.util.List; +import ${packageName}.domain.${ClassName}; +#if($table.sub) +import ${packageName}.domain.${subClassName}; +#end + +/** + * ${functionName}Mapper接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface ${ClassName}Mapper +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 删除${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); +#if($table.sub) + + /** + * 批量删除${subTable.functionName} + * + * @param ${pkColumn.javaField}s 需要删除的数据主键集合 + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 批量新增${subTable.functionName} + * + * @param ${subclassName}List ${subTable.functionName}列表 + * @return 结果 + */ + public int batch${subClassName}(List<${subClassName}> ${subclassName}List); + + + /** + * 通过${functionName}主键删除${subTable.functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}ID + * @return 结果 + */ + public int delete${subClassName}By${subTableFkClassName}(${pkColumn.javaType} ${pkColumn.javaField}); +#end +} diff --git a/backend/roomroot-generator/src/main/resources/vm/java/service.java.vm b/backend/roomroot-generator/src/main/resources/vm/java/service.java.vm new file mode 100644 index 000000000..264882b27 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/java/service.java.vm @@ -0,0 +1,61 @@ +package ${packageName}.service; + +import java.util.List; +import ${packageName}.domain.${ClassName}; + +/** + * ${functionName}Service接口 + * + * @author ${author} + * @date ${datetime} + */ +public interface I${ClassName}Service +{ + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName}集合 + */ + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}); + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int insert${ClassName}(${ClassName} ${className}); + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ + public int update${ClassName}(${ClassName} ${className}); + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键集合 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s); + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}); +} diff --git a/backend/roomroot-generator/src/main/resources/vm/java/serviceImpl.java.vm b/backend/roomroot-generator/src/main/resources/vm/java/serviceImpl.java.vm new file mode 100644 index 000000000..133887501 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/java/serviceImpl.java.vm @@ -0,0 +1,169 @@ +package ${packageName}.service.impl; + +import java.util.List; +#foreach ($column in $columns) +#if($column.javaField == 'createTime' || $column.javaField == 'updateTime') +import com.roomroot.common.utils.DateUtils; +#break +#end +#end +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +#if($table.sub) +import java.util.ArrayList; +import com.roomroot.common.utils.StringUtils; +import org.springframework.transaction.annotation.Transactional; +import ${packageName}.domain.${subClassName}; +#end +import ${packageName}.mapper.${ClassName}Mapper; +import ${packageName}.domain.${ClassName}; +import ${packageName}.service.I${ClassName}Service; + +/** + * ${functionName}Service业务层处理 + * + * @author ${author} + * @date ${datetime} + */ +@Service +public class ${ClassName}ServiceImpl implements I${ClassName}Service +{ + @Autowired + private ${ClassName}Mapper ${className}Mapper; + + /** + * 查询${functionName} + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return ${functionName} + */ + @Override + public ${ClassName} select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { + return ${className}Mapper.select${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } + + /** + * 查询${functionName}列表 + * + * @param ${className} ${functionName} + * @return ${functionName} + */ + @Override + public List<${ClassName}> select${ClassName}List(${ClassName} ${className}) + { + return ${className}Mapper.select${ClassName}List(${className}); + } + + /** + * 新增${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int insert${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'createTime') + ${className}.setCreateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + int rows = ${className}Mapper.insert${ClassName}(${className}); + insert${subClassName}(${className}); + return rows; +#else + return ${className}Mapper.insert${ClassName}(${className}); +#end + } + + /** + * 修改${functionName} + * + * @param ${className} ${functionName} + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int update${ClassName}(${ClassName} ${className}) + { +#foreach ($column in $columns) +#if($column.javaField == 'updateTime') + ${className}.setUpdateTime(DateUtils.getNowDate()); +#end +#end +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${className}.get${pkColumn.capJavaField}()); + insert${subClassName}(${className}); +#end + return ${className}Mapper.update${ClassName}(${className}); + } + + /** + * 批量删除${functionName} + * + * @param ${pkColumn.javaField}s 需要删除的${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaType}[] ${pkColumn.javaField}s) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}s(${pkColumn.javaField}s); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}s(${pkColumn.javaField}s); + } + + /** + * 删除${functionName}信息 + * + * @param ${pkColumn.javaField} ${functionName}主键 + * @return 结果 + */ +#if($table.sub) + @Transactional +#end + @Override + public int delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaType} ${pkColumn.javaField}) + { +#if($table.sub) + ${className}Mapper.delete${subClassName}By${subTableFkClassName}(${pkColumn.javaField}); +#end + return ${className}Mapper.delete${ClassName}By${pkColumn.capJavaField}(${pkColumn.javaField}); + } +#if($table.sub) + + /** + * 新增${subTable.functionName}信息 + * + * @param ${className} ${functionName}对象 + */ + public void insert${subClassName}(${ClassName} ${className}) + { + List<${subClassName}> ${subclassName}List = ${className}.get${subClassName}List(); + ${pkColumn.javaType} ${pkColumn.javaField} = ${className}.get${pkColumn.capJavaField}(); + if (StringUtils.isNotNull(${subclassName}List)) + { + List<${subClassName}> list = new ArrayList<${subClassName}>(); + for (${subClassName} ${subclassName} : ${subclassName}List) + { + ${subclassName}.set${subTableFkClassName}(${pkColumn.javaField}); + list.add(${subclassName}); + } + if (list.size() > 0) + { + ${className}Mapper.batch${subClassName}(list); + } + } + } +#end +} diff --git a/backend/roomroot-generator/src/main/resources/vm/java/sub-domain.java.vm b/backend/roomroot-generator/src/main/resources/vm/java/sub-domain.java.vm new file mode 100644 index 000000000..d270f1f78 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/java/sub-domain.java.vm @@ -0,0 +1,76 @@ +package ${packageName}.domain; + +#foreach ($import in $subImportList) +import ${import}; +#end +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * ${subTable.functionName}对象 ${subTableName} + * + * @author ${author} + * @date ${datetime} + */ +public class ${subClassName} extends BaseEntity +{ + private static final long serialVersionUID = 1L; + +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) + /** $column.columnComment */ +#if($column.list) +#set($parentheseIndex=$column.columnComment.indexOf("(")) +#if($parentheseIndex != -1) +#set($comment=$column.columnComment.substring(0, $parentheseIndex)) +#else +#set($comment=$column.columnComment) +#end +#if($parentheseIndex != -1) + @Excel(name = "${comment}", readConverterExp = "$column.readConverterExp()") +#elseif($column.javaType == 'Date') + @JsonFormat(pattern = "yyyy-MM-dd") + @Excel(name = "${comment}", width = 30, dateFormat = "yyyy-MM-dd") +#else + @Excel(name = "${comment}") +#end +#end + private $column.javaType $column.javaField; + +#end +#end +#foreach ($column in $subTable.columns) +#if(!$table.isSuperColumn($column.javaField)) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + public void set${AttrName}($column.javaType $column.javaField) + { + this.$column.javaField = $column.javaField; + } + + public $column.javaType get${AttrName}() + { + return $column.javaField; + } +#end +#end + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) +#foreach ($column in $subTable.columns) +#if($column.javaField.length() > 2 && $column.javaField.substring(1,2).matches("[A-Z]")) +#set($AttrName=$column.javaField) +#else +#set($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)}) +#end + .append("${column.javaField}", get${AttrName}()) +#end + .toString(); + } +} diff --git a/backend/roomroot-generator/src/main/resources/vm/js/api.js.vm b/backend/roomroot-generator/src/main/resources/vm/js/api.js.vm new file mode 100644 index 000000000..9295524a4 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/js/api.js.vm @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询${functionName}列表 +export function list${BusinessName}(query) { + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data) { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}) { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} diff --git a/backend/roomroot-generator/src/main/resources/vm/sql/sql.vm b/backend/roomroot-generator/src/main/resources/vm/sql/sql.vm new file mode 100644 index 000000000..057558350 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/sql/sql.vm @@ -0,0 +1,22 @@ +-- 菜单 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}', '${parentMenuId}', '1', '${businessName}', '${moduleName}/${businessName}/index', 1, 0, 'C', '0', '0', '${permissionPrefix}:list', '#', 'admin', sysdate(), '', null, '${functionName}菜单'); + +-- 按钮父菜单ID +SELECT @parentId := LAST_INSERT_ID(); + +-- 按钮 SQL +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}查询', @parentId, '1', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:query', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}新增', @parentId, '2', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:add', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}修改', @parentId, '3', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:edit', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}删除', @parentId, '4', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:remove', '#', 'admin', sysdate(), '', null, ''); + +insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) +values('${functionName}导出', @parentId, '5', '#', '', 1, 0, 'F', '0', '0', '${permissionPrefix}:export', '#', 'admin', sysdate(), '', null, ''); \ No newline at end of file diff --git a/backend/roomroot-generator/src/main/resources/vm/ts/api.ts.vm b/backend/roomroot-generator/src/main/resources/vm/ts/api.ts.vm new file mode 100644 index 000000000..8ed5122bb --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/ts/api.ts.vm @@ -0,0 +1,51 @@ +import request from '@/utils/request' +import type { AjaxResult, TableDataInfo, ${BusinessName}QueryParams, ${ClassName} } from '@/types' + +// 查询${functionName}列表 +#if($table.tree) +export function list${BusinessName}(query?: ${BusinessName}QueryParams): Promise> { +#else +export function list${BusinessName}(query: ${BusinessName}QueryParams): Promise> { +#end + return request({ + url: '/${moduleName}/${businessName}/list', + method: 'get', + params: query + }) +} + +// 查询${functionName}详细 +export function get${BusinessName}(${pkColumn.javaField}: number): Promise> { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'get' + }) +} + +// 新增${functionName} +export function add${BusinessName}(data: ${ClassName}): Promise { + return request({ + url: '/${moduleName}/${businessName}', + method: 'post', + data: data + }) +} + +// 修改${functionName} +export function update${BusinessName}(data: ${ClassName}): Promise { + return request({ + url: '/${moduleName}/${businessName}', + method: 'put', + data: data + }) +} + +// 删除${functionName} +export function del${BusinessName}(${pkColumn.javaField}: number | number[]): Promise { + return request({ + url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField}, + method: 'delete' + }) +} + + diff --git a/backend/roomroot-generator/src/main/resources/vm/ts/index.ts.vm b/backend/roomroot-generator/src/main/resources/vm/ts/index.ts.vm new file mode 100644 index 000000000..e51ef151e --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/ts/index.ts.vm @@ -0,0 +1,9 @@ +/** + * API 类型统一导出 + */ +.... + +// 防止覆盖,需手动追加下面代码到index.ts文件中,追加好后此文件可删除 + +// ${moduleName} 模块 +export * from "./${moduleName}/${businessName}"; \ No newline at end of file diff --git a/backend/roomroot-generator/src/main/resources/vm/ts/type.ts.vm b/backend/roomroot-generator/src/main/resources/vm/ts/type.ts.vm new file mode 100644 index 000000000..fff1d4cab --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/ts/type.ts.vm @@ -0,0 +1,51 @@ +import type { PageDomain, BaseEntity } from "../common"; + +/** ${functionName}配置分页查询参数 */ +export interface ${BusinessName}QueryParams extends PageDomain { +#foreach($column in $columns) +#if($column.query) +#set($type = "string") +#if($column.javaType == "Long" || $column.javaType == "Integer") + #set($type = "number") +#elseif($column.javaType == "Boolean") + #set($type = "boolean") +#end + /** ${column.columnComment} */ + ${column.javaField}?: ${type}; +#end +#end +} + +/** ${functionName}配置信息 */ +export interface ${ClassName} extends BaseEntity { +#foreach($column in $columns) +#set($type = "string") +#if($column.javaType == "Long" || $column.javaType == "Integer") + #set($type = "number") +#elseif($column.javaType == "Boolean") + #set($type = "boolean") +#end + /** ${column.columnComment} */ + ${column.javaField}?: ${type}; +#end +#if($table.sub) + /** $table.subTable.functionName信息 */ + ${subclassName}List?: ${subClassName}[]; +#end +} +#if($table.sub) + +/** ${subTable.functionName}配置信息 */ +export interface ${subClassName} extends BaseEntity { +#foreach ($column in $subTable.columns) +#set($type = "string") +#if($column.javaType == "Long" || $column.javaType == "Integer") + #set($type = "number") +#elseif($column.javaType == "Boolean") + #set($type = "boolean") +#end + /** ${column.columnComment} */ + ${column.javaField}?: ${type}; +#end +} +#end diff --git a/backend/roomroot-generator/src/main/resources/vm/vue/index-tree.vue.vm b/backend/roomroot-generator/src/main/resources/vm/vue/index-tree.vue.vm new file mode 100644 index 000000000..7a2d0e918 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/vue/index-tree.vue.vm @@ -0,0 +1,565 @@ + + + diff --git a/backend/roomroot-generator/src/main/resources/vm/vue/index.vue.vm b/backend/roomroot-generator/src/main/resources/vm/vue/index.vue.vm new file mode 100644 index 000000000..876dae330 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/vue/index.vue.vm @@ -0,0 +1,660 @@ + + + diff --git a/backend/roomroot-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm b/backend/roomroot-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm new file mode 100644 index 000000000..51e2673ef --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm @@ -0,0 +1,526 @@ + + + diff --git a/backend/roomroot-generator/src/main/resources/vm/vue/v3/index.vue.vm b/backend/roomroot-generator/src/main/resources/vm/vue/v3/index.vue.vm new file mode 100644 index 000000000..66807757a --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/vue/v3/index.vue.vm @@ -0,0 +1,639 @@ + + + diff --git a/backend/roomroot-generator/src/main/resources/vm/vue/v3/view.vue.vm b/backend/roomroot-generator/src/main/resources/vm/vue/v3/view.vue.vm new file mode 100644 index 000000000..e486c8863 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/vue/v3/view.vue.vm @@ -0,0 +1,83 @@ + + + diff --git a/backend/roomroot-generator/src/main/resources/vm/vue/v3ts/index-tree.vue.vm b/backend/roomroot-generator/src/main/resources/vm/vue/v3ts/index-tree.vue.vm new file mode 100644 index 000000000..038165ea3 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/vue/v3ts/index-tree.vue.vm @@ -0,0 +1,528 @@ + + + diff --git a/backend/roomroot-generator/src/main/resources/vm/vue/v3ts/index.vue.vm b/backend/roomroot-generator/src/main/resources/vm/vue/v3ts/index.vue.vm new file mode 100644 index 000000000..6d0e0d0e6 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/vue/v3ts/index.vue.vm @@ -0,0 +1,644 @@ + + + diff --git a/backend/roomroot-generator/src/main/resources/vm/vue/v3ts/view.vue.vm b/backend/roomroot-generator/src/main/resources/vm/vue/v3ts/view.vue.vm new file mode 100644 index 000000000..25bd7f47a --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/vue/v3ts/view.vue.vm @@ -0,0 +1,84 @@ + + + diff --git a/backend/roomroot-generator/src/main/resources/vm/vue/view.vue.vm b/backend/roomroot-generator/src/main/resources/vm/vue/view.vue.vm new file mode 100644 index 000000000..9911bc856 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/vue/view.vue.vm @@ -0,0 +1,86 @@ + + + diff --git a/backend/roomroot-generator/src/main/resources/vm/xml/mapper.xml.vm b/backend/roomroot-generator/src/main/resources/vm/xml/mapper.xml.vm new file mode 100644 index 000000000..456755b52 --- /dev/null +++ b/backend/roomroot-generator/src/main/resources/vm/xml/mapper.xml.vm @@ -0,0 +1,140 @@ + + + + + +#foreach ($column in $columns) + +#end + +#if($table.sub) + + + + + + +#foreach ($column in $subTable.columns) + +#end + +#end + + + select#foreach($column in $columns) $column.columnName#if($foreach.count != $columns.size()),#end#end from ${tableName} + + + + + +#if($table.sub) + + +#end + + + insert into ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + $column.columnName, +#end +#end + + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName || !$pkColumn.increment) + #{$column.javaField}, +#end +#end + + + + + update ${tableName} + +#foreach($column in $columns) +#if($column.columnName != $pkColumn.columnName) + $column.columnName = #{$column.javaField}, +#end +#end + + where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} = #{${pkColumn.javaField}} + + + + delete from ${tableName} where ${pkColumn.columnName} in + + #{${pkColumn.javaField}} + + +#if($table.sub) + + + delete from ${subTableName} where ${subTableFkName} in + + #{${subTableFkclassName}} + + + + + delete from ${subTableName} where ${subTableFkName} = #{${subTableFkclassName}} + + + + insert into ${subTableName}(#foreach($column in $subTable.columns) $column.columnName#if($foreach.count != $subTable.columns.size()),#end#end) values + + (#foreach($column in $subTable.columns) #{item.$column.javaField}#if($foreach.count != $subTable.columns.size()),#end#end) + + +#end + \ No newline at end of file diff --git a/backend/roomroot-jwgl/DmJdbcDriver8.jar b/backend/roomroot-jwgl/DmJdbcDriver8.jar new file mode 100644 index 000000000..a710ec0c6 Binary files /dev/null and b/backend/roomroot-jwgl/DmJdbcDriver8.jar differ diff --git a/backend/roomroot-jwgl/pom.xml b/backend/roomroot-jwgl/pom.xml new file mode 100644 index 000000000..3607a8850 --- /dev/null +++ b/backend/roomroot-jwgl/pom.xml @@ -0,0 +1,38 @@ + + + 4.0.0 + + com.roomroot + roomroot + 3.9.2 + + + roomroot-jwgl + + + UTF-8 + + + + + com.roomroot + roomroot-framework + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/config/AccountManagementWebMvcConfig.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/config/AccountManagementWebMvcConfig.java new file mode 100644 index 000000000..f9321a5f0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/config/AccountManagementWebMvcConfig.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.config; + +import com.roomroot.jwgl.interceptor.AccountManagementAuthorizationInterceptor; +import com.roomroot.jwgl.interceptor.OperationLogInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * 在线账户状态和账户管理权限 Web MVC 配置。 + * + *

该配置对已有在线会话执行账户状态校验,同时保持普通匿名请求的原有访问规则; + * 账户管理路径继续执行超级管理员权限校验,并在校验通过后采集操作日志上下文。

+ */ +@Configuration +public class AccountManagementWebMvcConfig implements WebMvcConfigurer { + + /** + * 账户管理身份和权限拦截器。 + */ + private final AccountManagementAuthorizationInterceptor + authorizationInterceptor; + + /** + * 操作日志统一请求采集拦截器。 + */ + private final OperationLogInterceptor operationLogInterceptor; + + /** + * 构造账户管理和操作日志统一 Web MVC 配置。 + * + * @param authorizationInterceptor 账户管理身份和权限拦截器 + * @param operationLogInterceptor 操作日志统一请求采集拦截器 + */ + public AccountManagementWebMvcConfig( + AccountManagementAuthorizationInterceptor + authorizationInterceptor, + OperationLogInterceptor operationLogInterceptor) { + // 第一步:保存账户管理专用拦截器,供 MVC 注册阶段使用。 + this.authorizationInterceptor = authorizationInterceptor; + + // 第二步:保存操作日志采集拦截器,供 MVC 注册阶段使用。 + this.operationLogInterceptor = operationLogInterceptor; + } + + /** + * 注册账户状态校验、账户管理权限和操作日志采集规则。 + * + * @param registry Spring MVC 拦截器注册器 + */ + @Override + public void addInterceptors(InterceptorRegistry registry) { + /* + * 第一步:注册账户管理鉴权拦截器。 + * 第二步:覆盖全部 Controller 请求,使停用或已合并账户不能绕过 + * 账户管理路径继续访问其他业务接口。 + * 第三步:拦截器内部会放行没有 Servlet 会话的普通匿名请求, + * 因此不会改变项目现有公开接口的访问方式。 + */ + // registry.addInterceptor(authorizationInterceptor) + // .addPathPatterns("/**"); + + /* + * 第四步:在账户状态和账户管理权限校验通过后采集日志上下文。 + * 第五步:覆盖全部 Controller 请求,由采集拦截器自行跳过 + * 非 Controller 处理器和没有有效登录记录的请求。 + */ + registry.addInterceptor(operationLogInterceptor) + .addPathPatterns("/**"); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/LeaveDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/LeaveDTO.java new file mode 100644 index 000000000..c679fbcf0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/LeaveDTO.java @@ -0,0 +1,31 @@ +package com.roomroot.jwgl.dto; + +import com.roomroot.jwgl.entity.XYJXQJB; +import lombok.Data; + +import java.util.List; + +/** + * 请假DTO + *

+ * 包含学员教学请假表实体、实施_课堂编号列表、学员编号 + *

+ */ +@Data +public class LeaveDTO { + + /** + * 学员教学请假表实体 + */ + private XYJXQJB leave; + + /** + * 实施_课堂编号列表 + */ + private List ssktBhList; + + /** + * 学员编号 + */ + private String xybh; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/LeaveQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/LeaveQueryDTO.java new file mode 100644 index 000000000..7dbae78dc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/LeaveQueryDTO.java @@ -0,0 +1,79 @@ +package com.roomroot.jwgl.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +/** + * 请假查询DTO + *

+ * 支持按XYJXQJB字段查询,起始时间、结束时间、创建时间支持范围查询 + *

+ */ +@Data +public class LeaveQueryDTO { + + /** + * 编号 + */ + private String bh; + + /** + * 名称 + */ + private String mc; + + /** + * 状态 + */ + private String zt; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 起始时间-开始 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime qssjStart; + + /** + * 起始时间-结束 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime qssjEnd; + + /** + * 结束时间-开始 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime jssjStart; + + /** + * 结束时间-结束 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime jssjEnd; + + /** + * 创建时间-开始 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime cjsjStart; + + /** + * 创建时间-结束 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime cjsjEnd; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountCreateDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountCreateDTO.java new file mode 100644 index 000000000..79ec7b6df --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountCreateDTO.java @@ -0,0 +1,85 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +import static com.roomroot.jwgl.utils.AccountManagementConstants.PASSWORD_COMPLEXITY_PATTERN; + +/** + * 账户新增参数。 + */ +@Data +public class AccountCreateDTO { + + /** + * 登录账号。 + */ + @NotBlank(message = "登录账号不能为空") + @Size(max = 50, message = "登录账号不能超过50个字符") + private String loginName; + + /** + * 用户姓名。 + */ + @NotBlank(message = "用户姓名不能为空") + @Size(max = 50, message = "用户姓名不能超过50个字符") + private String userName; + + /** + * 初始密码。 + */ + @NotBlank(message = "初始密码不能为空") + @Size(min = 8, max = 50, message = "初始密码长度必须在8到50个字符之间") + @Pattern( + regexp = PASSWORD_COMPLEXITY_PATTERN, + message = "初始密码必须包含大写字母、小写字母、数字和特殊字符,且不能包含空白字符") + private String password; + + /** + * 统一身份账号。 + */ + @Size(max = 50, message = "统一账号不能超过50个字符") + private String unifiedAccount; + + /** + * 联系电话。 + */ + @Size(max = 20, message = "联系电话不能超过20个字符") + private String phone; + + /** + * 身份证号码。 + */ + @Size(max = 50, message = "身份证号码不能超过50个字符") + private String idCard; + + /** + * 密码提示问题。 + */ + @Size(max = 50, message = "密码提示问题不能超过50个字符") + private String passwordQuestion; + + /** + * 密码提示答案。 + */ + @Size(max = 50, message = "密码提示答案不能超过50个字符") + private String passwordAnswer; + + /** + * 自我介绍。 + */ + @Size(max = 2000, message = "自我介绍不能超过2000个字符") + private String introduction; + + /** + * 新账户需要绑定的角色和数据范围;允许为空。 + */ + @Valid + private List roles = new ArrayList<>(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountDisableDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountDisableDTO.java new file mode 100644 index 000000000..23eb11bc3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountDisableDTO.java @@ -0,0 +1,26 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +/** + * 账户停用参数。 + */ +@Data +public class AccountDisableDTO { + + /** + * 登录信息编号。 + */ + @NotBlank(message = "账户编号不能为空") + @Size(max = 36, message = "账户编号不能超过36个字符") + private String id; + + /** + * 停用原因。 + */ + @Size(max = 200, message = "停用原因不能超过200个字符") + private String reason; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountIdDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountIdDTO.java new file mode 100644 index 000000000..bd241c696 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountIdDTO.java @@ -0,0 +1,20 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +/** + * 账户唯一编号参数。 + */ +@Data +public class AccountIdDTO { + + /** + * 登录信息编号。 + */ + @NotBlank(message = "账户编号不能为空") + @Size(max = 36, message = "账户编号不能超过36个字符") + private String id; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountLoginDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountLoginDTO.java new file mode 100644 index 000000000..992e6d9a3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountLoginDTO.java @@ -0,0 +1,44 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +/** + * 本地账户密码登录参数。 + */ +@Data +public class AccountLoginDTO { + + /** + * 登录账号。 + */ + @NotBlank(message = "登录账号不能为空") + @Size(max = 50, message = "登录账号不能超过50个字符") + private String loginName; + + /** + * 原始登录密码。 + */ + @NotBlank(message = "登录密码不能为空") + @Size(min = 8, max = 50, message = "登录密码长度必须在8到50个字符之间") + private String password; + + /** + * 当前登录验证码编号。 + * + *

该编号由验证码获取接口生成,用于确认提交的验证码 + * 与当前 Servlet Session 中最近一次生成的验证码一致。

+ *

可选字段,为空时跳过验证码校验。

+ */ + @Size(max = 36, message = "验证码编号不能超过36个字符") + private String captchaId; + + /** + * 用户输入的登录验证码。 + *

可选字段,为空时跳过验证码校验。

+ */ + @Size(min = 4, max = 4, message = "验证码必须为4个字符") + private String captchaCode; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountLoginInformationQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountLoginInformationQueryDTO.java new file mode 100644 index 000000000..76f2c3726 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountLoginInformationQueryDTO.java @@ -0,0 +1,32 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + + +/** + * 当前登录账户的登录信息和登录历史查询参数。 + * + *

账户编号不由客户端提交,业务层会根据当前 Servlet Session + * 对应的在线记录取得真实账户编号。

+ */ +@Data +public class AccountLoginInformationQueryDTO { + + /** + * 当前页码;未填写时默认查询第一页。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数;未填写时默认20条,单页最多100条。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max(value = 100, message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountMergeDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountMergeDTO.java new file mode 100644 index 000000000..3894e5624 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountMergeDTO.java @@ -0,0 +1,33 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +/** + * 账户合并参数。 + */ +@Data +public class AccountMergeDTO { + + /** + * 被合并并最终停用的源账户编号。 + */ + @NotBlank(message = "源账户编号不能为空") + @Size(max = 36, message = "源账户编号不能超过36个字符") + private String sourceAccountId; + + /** + * 接收角色和数据范围的目标账户编号。 + */ + @NotBlank(message = "目标账户编号不能为空") + @Size(max = 36, message = "目标账户编号不能超过36个字符") + private String targetAccountId; + + /** + * 合并原因。 + */ + @Size(max = 200, message = "合并原因不能超过200个字符") + private String reason; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountPasswordChangeDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountPasswordChangeDTO.java new file mode 100644 index 000000000..bdad58f66 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountPasswordChangeDTO.java @@ -0,0 +1,43 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +import static com.roomroot.jwgl.utils.AccountManagementConstants.PASSWORD_COMPLEXITY_PATTERN; + +/** + * 当前登录账户修改密码参数。 + */ +@Data +public class AccountPasswordChangeDTO { + + /** + * 当前正在使用的原密码。 + */ + @NotBlank(message = "原密码不能为空") + @Size(min = 8, max = 50, message = "原密码长度必须在8到50个字符之间") + private String currentPassword; + + /** + * 修改后的新密码。 + */ + @NotBlank(message = "新密码不能为空") + @Size(min = 8, max = 50, message = "新密码长度必须在8到50个字符之间") + @Pattern( + regexp = PASSWORD_COMPLEXITY_PATTERN, + message = "新密码必须包含大写字母、小写字母、数字和特殊字符,且不能包含空白字符") + private String newPassword; + + /** + * 页面再次输入的新密码。 + */ + @NotBlank(message = "确认密码不能为空") + @Size(min = 8, max = 50, message = "确认密码长度必须在8到50个字符之间") + @Pattern( + regexp = PASSWORD_COMPLEXITY_PATTERN, + message = "确认密码必须包含大写字母、小写字母、数字和特殊字符,且不能包含空白字符") + private String confirmPassword; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountPasswordResetDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountPasswordResetDTO.java new file mode 100644 index 000000000..e508f16b9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountPasswordResetDTO.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + + + +import static com.roomroot.jwgl.utils.AccountManagementConstants.PASSWORD_COMPLEXITY_PATTERN; + +/** + * 账户密码重置参数。 + */ +@Data +public class AccountPasswordResetDTO { + + /** + * 登录信息编号。 + */ + @NotBlank(message = "账户编号不能为空") + @Size(max = 36, message = "账户编号不能超过36个字符") + private String id; + + /** + * 重置后的密码。 + */ + @NotBlank(message = "重置密码不能为空") + @Size(min = 8, max = 50, message = "重置密码长度必须在8到50个字符之间") + @Pattern( + regexp = PASSWORD_COMPLEXITY_PATTERN, + message = "重置密码必须包含大写字母、小写字母、数字和特殊字符,且不能包含空白字符") + private String newPassword; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountQueryDTO.java new file mode 100644 index 000000000..e44a65959 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountQueryDTO.java @@ -0,0 +1,53 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +/** + * 账户列表查询参数。 + */ +@Data +public class AccountQueryDTO { + + /** + * 当前页码;未填写时默认查询第一页。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数;未填写时默认20条,单页最多100条。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max(value = 100, message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); + + /** + * 查询关键字,可匹配账号、姓名、统一账号、电话、身份证及角色身份。 + */ + @Size(max = 50, message = "查询关键字不能超过50个字符") + private String keyword; + + /** + * 启用状态;不填写时同时查询启用和停用账户。 + */ + private Boolean enabled; + + /** + * 角色类型;不填写时不限制账户角色。 + */ + @Size(max = 40, message = "角色类型不能超过40个字符") + private String roleType; + + /** + * 角色身份编号;填写后必须同时填写角色类型。 + */ + @Size(max = 50, message = "角色身份编号不能超过50个字符") + private String targetId; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountRoleAssignmentDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountRoleAssignmentDTO.java new file mode 100644 index 000000000..bb57ea237 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountRoleAssignmentDTO.java @@ -0,0 +1,27 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +/** + * 账户角色和数据范围绑定参数。 + */ +@Data +public class AccountRoleAssignmentDTO { + + /** + * 角色类型编码。 + */ + @NotBlank(message = "角色类型不能为空") + @Size(max = 40, message = "角色类型不能超过40个字符") + private String roleType; + + /** + * 角色对应的人员、组织或班次身份编号。 + */ + @NotBlank(message = "角色身份编号不能为空") + @Size(max = 50, message = "角色身份编号不能超过50个字符") + private String targetId; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountRoleConfigurationDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountRoleConfigurationDTO.java new file mode 100644 index 000000000..1e5e897b6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountRoleConfigurationDTO.java @@ -0,0 +1,32 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +import java.util.List; + +/** + * 账户角色和数据范围整体配置参数。 + */ +@Data +public class AccountRoleConfigurationDTO { + + /** + * 登录信息编号。 + */ + @NotBlank(message = "账户编号不能为空") + @Size(max = 36, message = "账户编号不能超过36个字符") + private String accountId; + + /** + * 账户最终应保留的全部角色绑定;空集合表示清空全部角色。 + */ + @Valid + @NotNull(message = "角色配置列表不能为空") + @Size(max = 500, message = "单个账户最多配置500个角色身份") + private List roles; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountRoleOptionQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountRoleOptionQueryDTO.java new file mode 100644 index 000000000..c614a3dcd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountRoleOptionQueryDTO.java @@ -0,0 +1,51 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.*; +import lombok.Data; + + +/** + * 账户角色可选身份查询参数。 + */ +@Data +public class AccountRoleOptionQueryDTO { + + /** + * 当前页码;未填写时默认查询第一页。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数;未填写时默认20条,单页最多100条。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max(value = 100, message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); + + /** + * 需要查询的角色类型编码。 + */ + @NotBlank(message = "角色类型不能为空") + @Size(max = 40, message = "角色类型不能超过40个字符") + private String roleType; + + /** + * 查询关键字,可匹配身份编号、名称及数据范围。 + */ + @Size(max = 50, message = "查询关键字不能超过50个字符") + private String keyword; + + /** + * 是否只查询尚未绑定账户的身份。 + */ + private Boolean unboundOnly; + + /** + * 当前正在配置的账户编号,用于在只查未绑定身份时保留本账户已有身份。 + */ + @Size(max = 36, message = "当前账户编号不能超过36个字符") + private String accountId; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountSsoSessionResolveDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountSsoSessionResolveDTO.java new file mode 100644 index 000000000..42cc0bfac --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountSsoSessionResolveDTO.java @@ -0,0 +1,20 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 统一身份认证会话解析参数。 + */ +@Data +public class AccountSsoSessionResolveDTO { + + /** + * 校园统一身份认证生成的会话标识。 + */ + @NotBlank(message = "SSO会话标识不能为空") + @Size(max = 50, message = "SSO会话标识不能超过50个字符") + private String sessionKey; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountUpdateDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountUpdateDTO.java new file mode 100644 index 000000000..0a880d343 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/accountmanagement/AccountUpdateDTO.java @@ -0,0 +1,70 @@ +package com.roomroot.jwgl.dto.accountmanagement; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +/** + * 账户基本信息修改参数。 + */ +@Data +public class AccountUpdateDTO { + + /** + * 登录信息编号。 + */ + @NotBlank(message = "账户编号不能为空") + @Size(max = 36, message = "账户编号不能超过36个字符") + private String id; + + /** + * 登录账号。 + */ + @NotBlank(message = "登录账号不能为空") + @Size(max = 50, message = "登录账号不能超过50个字符") + private String loginName; + + /** + * 用户姓名。 + */ + @NotBlank(message = "用户姓名不能为空") + @Size(max = 50, message = "用户姓名不能超过50个字符") + private String userName; + + /** + * 统一身份账号。 + */ + @Size(max = 50, message = "统一账号不能超过50个字符") + private String unifiedAccount; + + /** + * 联系电话。 + */ + @Size(max = 20, message = "联系电话不能超过20个字符") + private String phone; + + /** + * 身份证号码。 + */ + @Size(max = 50, message = "身份证号码不能超过50个字符") + private String idCard; + + /** + * 密码提示问题。 + */ + @Size(max = 50, message = "密码提示问题不能超过50个字符") + private String passwordQuestion; + + /** + * 密码提示答案。 + */ + @Size(max = 50, message = "密码提示答案不能超过50个字符") + private String passwordAnswer; + + /** + * 自我介绍。 + */ + @Size(max = 2000, message = "自我介绍不能超过2000个字符") + private String introduction; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/PersonnelAffiliationDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/PersonnelAffiliationDTO.java new file mode 100644 index 000000000..8c2a573b5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/PersonnelAffiliationDTO.java @@ -0,0 +1,37 @@ +package com.roomroot.jwgl.dto.affiliationsetting; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 人员隶属关系统一操作DTO。 + * + *

通过 personnelType 区分教员/学员/机关参谋三类人员, + * 避免为每类人员单独设置接口,降低前端对接成本。

+ * + *

各接口必填字段由业务层按操作类型分别校验: + * list 必填 personnelType;assign 必填 personnelType+departmentId+personnelId; + * unassign 必填 personnelType+personnelId;history 必填 personnelType+personnelId。

+ */ +@Data +public class PersonnelAffiliationDTO { + + /** 人员类型:TEACHER=教员 / STUDENT=学员 / DEPARTMENT_PERSONNEL=机关参谋 */ + @NotBlank(message = "人员类型不能为空") + @Size(max = 50, message = "人员类型不能超过50个字符") + private String personnelType; + + /** 部门/组织单元编号(教研室代号 / 学员队期编号 / 实施_机关编号) */ + @Size(max = 64, message = "部门编号不能超过64个字符") + private String departmentId; + + /** 人员编号(教员编号 / 学员编号 / 机关参谋编号) */ + @Size(max = 64, message = "人员编号不能超过64个字符") + private String personnelId; + + /** 关键字筛选(姓名/拼音) */ + @Size(max = 100, message = "查询关键字不能超过100个字符") + private String keyword; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/PersonnelTransferDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/PersonnelTransferDTO.java new file mode 100644 index 000000000..231a17e51 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/PersonnelTransferDTO.java @@ -0,0 +1,36 @@ +package com.roomroot.jwgl.dto.affiliationsetting; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 人员异动调整专用DTO。 + * + *

异动调整区别于普通分配:必须记录异动原因, + * 且会在人员表的"异动历史"字段追加一条历史记录。

+ */ +@Data +public class PersonnelTransferDTO { + + /** 人员类型:TEACHER / STUDENT / DEPARTMENT_PERSONNEL */ + @NotBlank(message = "人员类型不能为空") + @Size(max = 50, message = "人员类型不能超过50个字符") + private String personnelType; + + /** 人员编号 */ + @NotBlank(message = "人员编号不能为空") + @Size(max = 64, message = "人员编号不能超过64个字符") + private String personnelId; + + /** 异动后归属编号(教研室代号 / 学员队期编号 / 实施_机关编号) */ + @NotBlank(message = "异动目标部门编号不能为空") + @Size(max = 64, message = "目标部门编号不能超过64个字符") + private String toDepartmentId; + + /** 异动原因(强制填写,写入历史记录) */ + @NotBlank(message = "异动原因不能为空") + @Size(max = 500, message = "异动原因不能超过500个字符") + private String reason; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeAffiliationAddDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeAffiliationAddDTO.java new file mode 100644 index 000000000..148999b9d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeAffiliationAddDTO.java @@ -0,0 +1,26 @@ +package com.roomroot.jwgl.dto.affiliationsetting; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 新增教研室隶属关系参数。 + */ +@Data +public class ResearchOfficeAffiliationAddDTO { + + /** + * 待关联的部别编号,对应“实施_机关.编号”。 + */ + @NotBlank(message = "部别编号不能为空") + @Size(max = 36, message = "部别编号不能超过36个字符") + private String departmentId; + + /** + * 待关联的教研室编号,对应“教研室表.教研室代号”。 + */ + @NotBlank(message = "教研室编号不能为空") + @Size(max = 50, message = "教研室编号不能超过50个字符") + private String researchOfficeId; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeAffiliationIdDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeAffiliationIdDTO.java new file mode 100644 index 000000000..1d0bf36f4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeAffiliationIdDTO.java @@ -0,0 +1,20 @@ +package com.roomroot.jwgl.dto.affiliationsetting; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 教研室隶属关系编号参数。 + */ +@Data +public class ResearchOfficeAffiliationIdDTO { + + /** + * 隶属关系唯一编号,对应“实施_机关_教研室.编号”。 + */ + @NotBlank(message = "隶属关系编号不能为空") + @Size(max = 36, message = "隶属关系编号不能超过36个字符") + private String id; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeAffiliationQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeAffiliationQueryDTO.java new file mode 100644 index 000000000..d52a3197f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeAffiliationQueryDTO.java @@ -0,0 +1,26 @@ +package com.roomroot.jwgl.dto.affiliationsetting; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 已隶属教研室查询参数。 + */ +@Data +public class ResearchOfficeAffiliationQueryDTO { + + /** + * 当前选择的部别编号。 + */ + @NotBlank(message = "部别编号不能为空") + @Size(max = 36, message = "部别编号不能超过36个字符") + private String departmentId; + + /** + * 教研室查询关键字,可匹配编号、名称、简称和系别。 + */ + @Size(max = 100, message = "查询关键字不能超过100个字符") + private String keyword; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeOptionQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeOptionQueryDTO.java new file mode 100644 index 000000000..9db104a09 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/ResearchOfficeOptionQueryDTO.java @@ -0,0 +1,18 @@ +package com.roomroot.jwgl.dto.affiliationsetting; + +import jakarta.validation.constraints.Size; +import lombok.Data; + + +/** + * 教研室隶属关系页面选项查询参数。 + */ +@Data +public class ResearchOfficeOptionQueryDTO { + + /** + * 可选部别或未隶属教研室的查询关键字。 + */ + @Size(max = 100, message = "查询关键字不能超过100个字符") + private String keyword; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/TeachingClassAffiliationDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/TeachingClassAffiliationDTO.java new file mode 100644 index 000000000..332547634 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/affiliationsetting/TeachingClassAffiliationDTO.java @@ -0,0 +1,40 @@ +package com.roomroot.jwgl.dto.affiliationsetting; + +import jakarta.validation.constraints.Size; +import lombok.Data; + + + +/** + * 教学班次隶属关系管理统一请求参数。 + * + *

同一功能的查询、新增和解除操作共用该 DTO, + * 各接口需要的必填字段由业务层按照具体操作分别校验。

+ */ +@Data +public class TeachingClassAffiliationDTO { + + /** + * 部别编号,对应“实施_机关.编号”。 + */ + @Size(max = 36, message = "部别编号不能超过36个字符") + private String departmentId; + + /** + * 教学班次编号,对应“学员队表.学员队编号”。 + */ + @Size(max = 50, message = "教学班次编号不能超过50个字符") + private String teachingClassId; + + /** + * 查询关键字,可匹配教学班次或部别的主要展示字段。 + */ + @Size(max = 100, message = "查询关键字不能超过100个字符") + private String keyword; + + /** + * 在校状态筛选条件,对应“学员队表.在校状态”。 + */ + @Size(max = 50, message = "在校状态不能超过50个字符") + private String status; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustRoomDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustRoomDTO.java new file mode 100644 index 000000000..48651f414 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustRoomDTO.java @@ -0,0 +1,29 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 调课申请-新教室参数。 + *

+ * 用于在调课申请中指定调整后的教室信息。 + *

+ */ +@Data +public class AdjustRoomDTO { + + /** + * 教室编号,必填,标识调整后使用的教室。 + */ + @NotBlank(message = "教室编号不能为空") + @Size(max = 50, message = "教室编号不能超过50个字符") + private String jsbh; + + /** + * 备注,教室使用相关补充说明。 + */ + @Size(max = 200, message = "备注不能超过200个字符") + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustSupportDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustSupportDTO.java new file mode 100644 index 000000000..0435f3ce5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustSupportDTO.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 调课申请-新保障明细参数。 + *

+ * 用于在调课申请中指定调整后的教学保障提供明细。 + *

+ */ +@Data +public class AdjustSupportDTO { + + /** + * 保障提供明细编号,必填,关联保障提供主表。 + */ + @NotBlank(message = "保障提供明细编号不能为空") + @Size(max = 50, message = "保障提供明细编号不能超过50个字符") + private String bztgmxbh; + + /** + * 数量,保障提供的数量。 + */ + private Integer sl; + + /** + * 备注,保障相关补充说明。 + */ + @Size(max = 200, message = "备注不能超过200个字符") + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustTeacherDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustTeacherDTO.java new file mode 100644 index 000000000..4713b1326 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustTeacherDTO.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 调课申请-新辅助教员参数。 + *

+ * 用于在调课申请中指定调整后的辅助教员信息。 + *

+ */ +@Data +public class AdjustTeacherDTO { + + /** + * 辅助教员编号,必填,标识调整后使用的辅助教员。 + */ + @NotBlank(message = "辅助教员编号不能为空") + @Size(max = 50, message = "辅助教员编号不能超过50个字符") + private String fzjybh; + + /** + * 是否主教员,1-是,0-否;用于标识该教员在调整后是否为主讲。 + */ + private Integer zjy; + + /** + * 备注,教员相关补充说明。 + */ + @Size(max = 200, message = "备注不能超过200个字符") + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustTeamDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustTeamDTO.java new file mode 100644 index 000000000..52c5141d0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/AdjustTeamDTO.java @@ -0,0 +1,23 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 调课申请-新学员队参数。 + *

+ * 用于在调课申请中指定调整后的学员队信息。 + *

+ */ +@Data +public class AdjustTeamDTO { + + /** + * 学员队编号,必填,标识调整后涉及的学员队。 + */ + @NotBlank(message = "学员队编号不能为空") + @Size(max = 50, message = "学员队编号不能超过50个字符") + private String xydbh; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ClassesImportDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ClassesImportDTO.java new file mode 100644 index 000000000..a5cb98202 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ClassesImportDTO.java @@ -0,0 +1,30 @@ +package com.roomroot.jwgl.dto.courserunning; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 教学班次课程导入参数。 + *

+ * 用于从课程教学计划编制子系统导入指定教学班次的课程安排数据。 + *

+ */ +@Data +public class ClassesImportDTO { + + /** + * 教学班次编号,对应实施_课程学员队表中的学员队编号。 + */ + @NotBlank(message = "教学班次编号不能为空") + @Size(max = 50, message = "教学班次编号不能超过50个字符") + private String classBh; + + /** + * 年度,对应实施_课程表中的年度字段。 + */ + @NotNull(message = "年度不能为空") + private Integer nd; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ImportRemoveDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ImportRemoveDTO.java new file mode 100644 index 000000000..3ecc0d665 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ImportRemoveDTO.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.*; +import java.util.List; + +/** + * 删除已导入课程参数。 + *

+ * 用于删除已导入到课程教学运行管理子系统的课次数据,支持按学期、班次或单个课次删除。 + *

+ */ +@Data +public class ImportRemoveDTO { + + /** + * 删除范围,取值为:semester(按学期删除)、class(按班次删除)、single(按单个课次删除)。 + */ + @NotBlank(message = "删除范围不能为空") + @Size(max = 20, message = "删除范围不能超过20个字符") + private String scope; + + /** + * 课次编号列表,当删除范围为single时必填。 + */ + private List idList; + + /** + * 年度,用于按学期或班次删除时限定年度范围。 + */ + @NotNull(message = "年度不能为空") + private Integer nd; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/OperationAnalysisQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/OperationAnalysisQueryDTO.java new file mode 100644 index 000000000..5d03accc5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/OperationAnalysisQueryDTO.java @@ -0,0 +1,46 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + + +import java.time.LocalDateTime; + +/** + * 运行分析查询参数。 + *

+ * 用于查询教学运行数据统计分析,支持按年度、学期、教学系等条件筛选。 + *

+ */ +@Data +public class OperationAnalysisQueryDTO { + + /** + * 年度,如2024。 + */ + private String nd; + + /** + * 学期第次,如"1"表示第一学期。 + */ + private String xqd; + + /** + * 教学系编号。 + */ + private String jxxbh; + + /** + * 起始日期。 + */ + private LocalDateTime startDate; + + /** + * 结束日期。 + */ + private LocalDateTime endDate; + + /** + * 统计维度:week(按周)、month(按月)、department(按教学系)。 + */ + private String dimension; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustApplyDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustApplyDTO.java new file mode 100644 index 000000000..5695af78c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustApplyDTO.java @@ -0,0 +1,113 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.*; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 调课申请参数。 + *

+ * 用于提交调课申请,包含课次编号、调课事由、新的日期节次、新的教室、新的辅助教员、 + * 新的学员队、教学内容变更、新的保障明细等完整信息。 + *

+ */ +@Data +public class ScheduleAdjustApplyDTO { + + /** + * 实施_课程表编号,必填,标识需要调整的课次。 + */ + @NotBlank(message = "课次编号不能为空") + @Size(max = 50, message = "课次编号不能超过50个字符") + private String sskcbbh; + + /** + * 调课事由,必填,描述本次调课的原因和依据。 + */ + @NotBlank(message = "调课事由不能为空") + @Size(max = 500, message = "调课事由不能超过500个字符") + private String sy; + + /** + * 调课后新日期,课次调整后的上课日期时间。 + */ + @NotNull(message = "调课后新日期不能为空") + private LocalDateTime rq; + + /** + * 调课后新节次,课次调整后的节次序号。 + */ + @NotNull(message = "调课后新节次不能为空") + private Integer jc; + + /** + * 年度,对应课次所属的教学年度。 + */ + @NotNull(message = "年度不能为空") + private Integer nd; + + /** + * 申请教员编号,必填,标识提交申请的教员。 + */ + @NotBlank(message = "申请教员编号不能为空") + @Size(max = 50, message = "申请教员编号不能超过50个字符") + private String sqjybh; + + /** + * 调课后教学内容,调整后的本节课教学主题或知识点。 + */ + @Size(max = 2000, message = "教学内容不能超过2000个字符") + private String jxnr; + + /** + * 调课后教学要点,调整后的本节课重点和难点。 + */ + @Size(max = 1000, message = "教学要点不能超过1000个字符") + private String jxyd; + + /** + * 调课后教学方法,调整后的教学方法。 + */ + @Size(max = 200, message = "教学方法不能超过200个字符") + private String jxff; + + /** + * 教学保障备注,调整后需要的其他教学保障需求说明。 + */ + @Size(max = 1000, message = "教学保障备注不能超过1000个字符") + private String jxbzbz; + + /** + * 用车信息,调整后需要的车辆保障需求。 + */ + @Size(max = 500, message = "用车信息不能超过500个字符") + private String ycxx; + + /** + * 备注,补充说明信息。 + */ + @Size(max = 500, message = "备注不能超过500个字符") + private String bz; + + /** + * 新教室列表,调课后使用的教室信息。 + */ + private List roomList; + + /** + * 新辅助教员列表,调课后使用的辅助教员信息。 + */ + private List teacherList; + + /** + * 新学员队列表,调课后涉及的学员队信息。 + */ + private List teamList; + + /** + * 新保障明细列表,调课后需要的保障提供明细。 + */ + private List supportList; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustAuditDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustAuditDTO.java new file mode 100644 index 000000000..d8ffbbf38 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustAuditDTO.java @@ -0,0 +1,55 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.*; +import jakarta.validation.constraints.Size; + +/** + * 调课申请审批参数。 + *

+ * 用于教研室或机关审批调课申请,包含审批结果、审批意见和审批人编号。 + *

+ */ +@Data +public class ScheduleAdjustAuditDTO { + + /** + * 调课申请编号,必填,标识需要审批的调课申请。 + */ + @NotBlank(message = "调课申请编号不能为空") + @Size(max = 50, message = "调课申请编号不能超过50个字符") + private String ssdksqbh; + + /** + * 审批角色,必填:jys(教研室审批)、jg(机关审批)。 + */ + @NotBlank(message = "审批角色不能为空") + @Size(max = 20, message = "审批角色不能超过20个字符") + private String auditRole; + + /** + * 审批结果,必填:1-同意,2-发回修改,3-拒绝。 + */ + @NotNull(message = "审批结果不能为空") + private Integer spzt; + + /** + * 审批意见,可选,记录审批的具体意见或发回原因。 + */ + @Size(max = 500, message = "审批意见不能超过500个字符") + private String fhyj; + + /** + * 审批人编号,必填,标识审批人身份。 + */ + @NotBlank(message = "审批人编号不能为空") + @Size(max = 50, message = "审批人编号不能超过50个字符") + private String sprbh; + + /** + * 机关编号,机关审批时必填,标识审批机关。 + */ + @Size(max = 50, message = "机关编号不能超过50个字符") + private String jgbh; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustQueryDTO.java new file mode 100644 index 000000000..44b6ce5dd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustQueryDTO.java @@ -0,0 +1,64 @@ +package com.roomroot.jwgl.dto.courserunning; + +import com.roomroot.jwgl.unit.PageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 调课申请查询参数。 + *

+ * 用于分页查询调课申请列表,支持按课次编号、申请教员、年度、审批状态、 + * 教研室查收状态、日期范围等条件进行筛选。 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +public class ScheduleAdjustQueryDTO extends PageQuery { + + /** + * 实施_课程表编号,筛选指定课次的调课申请。 + */ + private String sskcbbh; + + /** + * 申请教员编号,筛选指定教员提交的调课申请。 + */ + private String sqjybh; + + /** + * 年度。 + */ + private Integer nd; + + /** + * 教研室审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝。 + */ + private Integer jyspzzt; + + /** + * 教研室查收状态:0-未查收,1-已查收。 + */ + private Integer jyscszt; + + /** + * 机关审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝。 + */ + private Integer jgspzt; + + /** + * 删除状态:0-正常,1-已删除。 + */ + private Integer sczt; + + /** + * 申请日期范围开始。 + */ + private LocalDateTime cjsjStart; + + /** + * 申请日期范围结束。 + */ + private LocalDateTime cjsjEnd; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustStrategyDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustStrategyDTO.java new file mode 100644 index 000000000..7ae2004e3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/ScheduleAdjustStrategyDTO.java @@ -0,0 +1,52 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; + +/** + * 调课策略新增/修改参数。 + *

+ * 统一处理新增和修改:策略编号为空时执行新增,非空时执行修改。 + * 策略类型为 TIME_LIMIT 时需填写截止星期和截止节次; + * 策略类型为 AUTO_APPROVE 时截止星期和截止节次不生效。 + *

+ */ +@Data +public class ScheduleAdjustStrategyDTO { + + /** 策略编号(修改时必填,新增时为空) */ + @Size(max = 50, message = "策略编号不能超过50个字符") + private String clbh; + + /** 策略名称 */ + @NotBlank(message = "策略名称不能为空") + @Size(max = 100, message = "策略名称不能超过100个字符") + private String clmc; + + /** 策略类型:TIME_LIMIT=时间段限制,AUTO_APPROVE=免审批 */ + @NotBlank(message = "策略类型不能为空") + @Size(max = 20, message = "策略类型不能超过20个字符") + private String cllx; + + /** 是否启用:0=停用,1=启用(为空时默认1) */ + private Integer sfqy; + + /** 截止星期:0-6(仅 TIME_LIMIT 类型使用) */ + private Integer jzxq; + + /** 截止节次(仅 TIME_LIMIT 类型使用) */ + private Integer jzjc; + + /** 生效开始时间 */ + private LocalDateTime sxkssj; + + /** 生效结束时间 */ + private LocalDateTime sxjssj; + + /** 备注 */ + @Size(max = 500, message = "备注不能超过500个字符") + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/SemesterImportDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/SemesterImportDTO.java new file mode 100644 index 000000000..5013bb737 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/SemesterImportDTO.java @@ -0,0 +1,27 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 整学期课程导入参数。 + *

+ * 用于从课程教学计划编制子系统导入指定年度学期的所有课程安排数据。 + *

+ */ +@Data +public class SemesterImportDTO { + + /** + * 年度,对应实施_课程表中的年度字段。 + */ + @NotNull(message = "年度不能为空") + private Integer nd; + + /** + * 学期第次,对应实施_课程表中的学期第次字段。 + */ + @NotNull(message = "学期第次不能为空") + private Integer xqdc; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/SingleCourseImportDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/SingleCourseImportDTO.java new file mode 100644 index 000000000..cebb10f08 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/SingleCourseImportDTO.java @@ -0,0 +1,35 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 单门课程导入参数。 + *

+ * 用于从课程教学计划编制子系统导入指定教学班次的单门课程安排数据。 + *

+ */ +@Data +public class SingleCourseImportDTO { + + /** + * 课程编号,对应实施_课程表中的课编号字段。 + */ + @NotBlank(message = "课程编号不能为空") + @Size(max = 50, message = "课程编号不能超过50个字符") + private String courseBh; + + /** + * 教学班次编号,对应实施_课程学员队表中的学员队编号。 + */ + @NotBlank(message = "教学班次编号不能为空") + @Size(max = 50, message = "教学班次编号不能超过50个字符") + private String classBh; + + /** + * 年度,对应实施_课程表中的年度字段。 + */ + @NotNull(message = "年度不能为空") + private Integer nd; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/StudentLeaveApplyDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/StudentLeaveApplyDTO.java new file mode 100644 index 000000000..3202fc09e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/StudentLeaveApplyDTO.java @@ -0,0 +1,79 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.*; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学员正课请假申请参数。 + *

+ * 用于学员提交正课请假申请,包含请假原因、时间范围、关联课次等信息。 + *

+ */ +@Data +public class StudentLeaveApplyDTO { + + /** + * 学员编号,必填,标识提交请假申请的学员。 + */ + @NotBlank(message = "学员编号不能为空") + @Size(max = 50, message = "学员编号不能超过50个字符") + private String xybh; + + /** + * 学员姓名,可选,用于展示。 + */ + @Size(max = 50, message = "学员姓名不能超过50个字符") + private String xyxm; + + /** + * 学员队编号,必填,标识学员所属学员队。 + */ + @NotBlank(message = "学员队编号不能为空") + @Size(max = 50, message = "学员队编号不能超过50个字符") + private String xydbh; + + /** + * 请假原因,必填,描述请假的具体原因。 + */ + @NotBlank(message = "请假原因不能为空") + @Size(max = 500, message = "请假原因不能超过500个字符") + private String yy; + + /** + * 开始时间,必填,请假开始时间。 + */ + @NotNull(message = "开始时间不能为空") + private LocalDateTime qssj; + + /** + * 结束时间,必填,请假结束时间。 + */ + @NotNull(message = "结束时间不能为空") + private LocalDateTime jssj; + + /** + * 请假类型,如病假、事假、公假等。 + */ + @Size(max = 20, message = "请假类型不能超过20个字符") + private String qjlx; + + /** + * 目的地,外出请假时填写。 + */ + @Size(max = 100, message = "目的地不能超过100个字符") + private String mdd; + + /** + * 备注,补充说明信息。 + */ + @Size(max = 500, message = "备注不能超过500个字符") + private String bz; + + /** + * 关联课次编号列表,标识请假涉及的课次。 + */ + private List sskcbbhList; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/StudentLeaveAuditDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/StudentLeaveAuditDTO.java new file mode 100644 index 000000000..75caf4fcc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/StudentLeaveAuditDTO.java @@ -0,0 +1,41 @@ +package com.roomroot.jwgl.dto.courserunning; + +import jakarta.validation.constraints.*; +import lombok.Data; + + +/** + * 学员请假审批参数。 + *

+ * 用于教师审批学员提交的正课请假申请。 + *

+ */ +@Data +public class StudentLeaveAuditDTO { + + /** + * 请假编号,必填,标识需要审批的请假申请。 + */ + @NotBlank(message = "请假编号不能为空") + @Size(max = 50, message = "请假编号不能超过50个字符") + private String qjbh; + + /** + * 审批结果,1-同意,2-拒绝,3-发回修改。 + */ + @NotNull(message = "审批结果不能为空") + private Integer spzt; + + /** + * 审批人编号,必填,标识进行审批的教师。 + */ + @NotBlank(message = "审批人编号不能为空") + @Size(max = 50, message = "审批人编号不能超过50个字符") + private String sprbh; + + /** + * 审批意见,可选,记录审批意见或发回原因。 + */ + @Size(max = 500, message = "审批意见不能超过500个字符") + private String spyj; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/StudentLeaveQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/StudentLeaveQueryDTO.java new file mode 100644 index 000000000..960493cb2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/StudentLeaveQueryDTO.java @@ -0,0 +1,79 @@ +package com.roomroot.jwgl.dto.courserunning; + +import jakarta.validation.constraints.*; +import lombok.Data; + + +import java.time.LocalDateTime; + +/** + * 学员请假查询参数。 + *

+ * 用于分页查询学员正课请假记录,支持多条件筛选。 + *

+ */ +@Data +public class StudentLeaveQueryDTO { + + /** + * 页码,从1开始。 + */ + @NotNull(message = "页码不能为空") + private Integer pageNum; + + /** + * 每页记录数。 + */ + @NotNull(message = "每页记录数不能为空") + private Integer pageSize; + + /** + * 请假编号,精确匹配。 + */ + @Size(max = 50, message = "请假编号不能超过50个字符") + private String qjbh; + + /** + * 学员编号,精确匹配。 + */ + @Size(max = 50, message = "学员编号不能超过50个字符") + private String xybh; + + /** + * 学员姓名,模糊匹配。 + */ + @Size(max = 50, message = "学员姓名不能超过50个字符") + private String xyxm; + + /** + * 学员队编号,精确匹配。 + */ + @Size(max = 50, message = "学员队编号不能超过50个字符") + private String xydbh; + + /** + * 请假类型,精确匹配。 + */ + @Size(max = 50, message = "请假类型不能超过50个字符") + private String qjlx; + + /** + * 审批状态:0-待审批,1-已同意,2-已拒绝,3-已发回。 + */ + private Integer spzt; + + /** + * 年度。 + */ + private Integer nd; + + /** + * 创建时间范围起始。 + */ + private LocalDateTime cjsjStart; + + /** + * 创建时间范围结束。 + */ + private LocalDateTime cjsjEnd; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingPlanBatchFillDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingPlanBatchFillDTO.java new file mode 100644 index 000000000..6aa65c62d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingPlanBatchFillDTO.java @@ -0,0 +1,25 @@ +package com.roomroot.jwgl.dto.courserunning; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +/** + * 教学实施计划批量填写参数。 + *

+ * 用于一次性提交多个课次的教学实施计划填写数据, + * 提高教员批量填写实施计划的效率。 + *

+ */ +@Data +public class TeachingPlanBatchFillDTO { + + /** + * 教学实施计划填写列表,至少包含一条记录。 + */ + @NotEmpty(message = "填写列表不能为空") + @Valid + private List planList; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingPlanFillDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingPlanFillDTO.java new file mode 100644 index 000000000..fe623e535 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingPlanFillDTO.java @@ -0,0 +1,96 @@ +package com.roomroot.jwgl.dto.courserunning; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +import java.time.LocalDateTime; + +/** + * 教学实施计划单条填写参数。 + *

+ * 用于对单个课次填写教学实施计划内容,包括教学内容、教学要点、教学方法、 + * 上课日期节次、班组模式、用车信息及教学保障备注等。 + *

+ */ +@Data +public class TeachingPlanFillDTO { + + /** + * 实施_课程表编号,必填,标识需要填写实施计划的具体课次。 + */ + @NotBlank(message = "课次编号不能为空") + @Size(max = 50, message = "课次编号不能超过50个字符") + private String sskcbbh; + + /** + * 教学目的,描述本节课的教学目标和预期效果。 + */ + @Size(max = 1000, message = "教学目的不能超过1000个字符") + private String jxmd; + + /** + * 教学内容,描述本节课的具体教学主题或知识点。 + */ + @Size(max = 2000, message = "教学内容不能超过2000个字符") + private String jxnr; + + /** + * 教学要点,描述本节课的重点和难点。 + */ + @Size(max = 1000, message = "教学要点不能超过1000个字符") + private String jxyd; + + /** + * 课后作业,描述本节课布置的课后作业内容。 + */ + @Size(max = 1000, message = "课后作业不能超过1000个字符") + private String khzy; + + /** + * 教学方法,如讲授、讨论、实操、案例教学等。 + */ + @Size(max = 200, message = "教学方法不能超过200个字符") + private String jxff; + + /** + * 上课日期,本节课的计划上课日期时间。 + */ + private LocalDateTime rq; + + /** + * 节次,本节课在当天的节次序号。 + */ + private Integer jc; + + /** + * 班组模式,描述本节课的班组组织形式。 + */ + @Size(max = 100, message = "班组模式不能超过100个字符") + private String bzms; + + /** + * 班组划分信息,详细描述各班组的人员构成或分组情况。 + */ + @Size(max = 1000, message = "班组划分信息不能超过1000个字符") + private String bzhfxx; + + /** + * 用车信息,描述本节课需要的车辆保障需求。 + */ + @Size(max = 500, message = "用车信息不能超过500个字符") + private String ycxx; + + /** + * 教学保障备注,描述本节课需要的其他教学保障需求。 + */ + @Size(max = 1000, message = "教学保障备注不能超过1000个字符") + private String jxbzbz; + + /** + * 教员备注,授课教员对本节课的补充说明。 + */ + @Size(max = 1000, message = "教员备注不能超过1000个字符") + private String jybz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingPlanQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingPlanQueryDTO.java new file mode 100644 index 000000000..55e0fa398 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingPlanQueryDTO.java @@ -0,0 +1,49 @@ +package com.roomroot.jwgl.dto.courserunning; + +import com.roomroot.jwgl.unit.PageQuery; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教学实施计划课次查询参数。 + *

+ * 用于分页查询待填写或已填写的课次列表,支持按实施课程编号、年度、 + * 教学方法、日期范围等条件进行筛选。 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +public class TeachingPlanQueryDTO extends PageQuery { + + /** + * 实施_课程编号,关联课程教学计划编制子系统的课程数据。 + */ + private String sskcbh; + + /** + * 年度,对应课次所属的教学年度。 + */ + private Integer nd; + + /** + * 教学方法,筛选使用特定教学方法的课次。 + */ + private String jxff; + + /** + * 日期范围开始,筛选上课日期大于等于此值的课次。 + */ + private LocalDateTime rqStart; + + /** + * 日期范围结束,筛选上课日期小于等于此值的课次。 + */ + private LocalDateTime rqEnd; + + /** + * 填写状态:0-未填写,1-已填写,2-已提交;为空时查询全部。 + */ + private Integer txzt; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportBatchPlanDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportBatchPlanDTO.java new file mode 100644 index 000000000..8cad98391 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportBatchPlanDTO.java @@ -0,0 +1,31 @@ +package com.roomroot.jwgl.dto.courserunning; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +/** + * 教学保障计划批量登记参数。 + *

+ * 用于一次性为单个课次登记多条教学保障需求,提高保障登记效率。 + *

+ */ +@Data +public class TeachingSupportBatchPlanDTO { + + /** + * 实施_课程表编号(课次编号),必填,标识需要登记保障的课次。 + */ + @NotBlank(message = "课次编号不能为空") + private String sskcbbh; + + /** + * 教学保障计划列表,至少包含一条记录。 + */ + @NotEmpty(message = "保障计划列表不能为空") + @Valid + private List planList; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportConfirmDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportConfirmDTO.java new file mode 100644 index 000000000..f23781f1b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportConfirmDTO.java @@ -0,0 +1,36 @@ +package com.roomroot.jwgl.dto.courserunning; + +import jakarta.validation.constraints.*; +import lombok.Data; + + + +/** + * 教学保障到位确认参数。 + *

+ * 用于确认某条教学保障计划已经落实到位,记录确认人和确认时间。 + *

+ */ +@Data +public class TeachingSupportConfirmDTO { + + /** + * 课程表_保障明细编号,必填,标识需要确认的保障计划记录。 + */ + @NotBlank(message = "保障计划编号不能为空") + @Size(max = 50, message = "保障计划编号不能超过50个字符") + private String kcbbzmxbh; + + /** + * 确认人编号,必填,标识进行保障确认的人员。 + */ + @NotBlank(message = "确认人编号不能为空") + @Size(max = 50, message = "确认人编号不能超过50个字符") + private String qrrbh; + + /** + * 确认备注,可选,记录保障到位情况的补充说明。 + */ + @Size(max = 500, message = "确认备注不能超过500个字符") + private String qrbz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportPlanDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportPlanDTO.java new file mode 100644 index 000000000..f30ea0761 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportPlanDTO.java @@ -0,0 +1,48 @@ +package com.roomroot.jwgl.dto.courserunning; + +import jakarta.validation.constraints.*; +import lombok.Data; + + + +/** + * 教学保障计划登记参数。 + *

+ * 用于为单个课次登记一条教学保障需求,指定保障提供明细、数量、备注及确认状态。 + *

+ */ +@Data +public class TeachingSupportPlanDTO { + + /** + * 实施_课程表编号(课次编号),必填,标识需要登记保障的课次。 + */ + @NotBlank(message = "课次编号不能为空") + @Size(max = 50, message = "课次编号不能超过50个字符") + private String sskcbbh; + + /** + * 保障提供明细编号,必填,关联保障提供明细表(保障资源库)。 + */ + @NotBlank(message = "保障提供明细编号不能为空") + @Size(max = 50, message = "保障提供明细编号不能超过50个字符") + private String bztgmxbh; + + /** + * 年度,课次所属的教学年度。 + */ + @NotNull(message = "年度不能为空") + private Integer nd; + + /** + * 数量,本课次需要该保障提供的数量。 + */ + @NotNull(message = "数量不能为空") + private Integer sl; + + /** + * 备注,保障计划相关补充说明。 + */ + @Size(max = 500, message = "备注不能超过500个字符") + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportPlanItemDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportPlanItemDTO.java new file mode 100644 index 000000000..d56caf2a5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportPlanItemDTO.java @@ -0,0 +1,40 @@ +package com.roomroot.jwgl.dto.courserunning; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 教学保障计划批量登记条目参数。 + *

+ * 用于批量登记时描述单条保障需求信息。 + *

+ */ +@Data +public class TeachingSupportPlanItemDTO { + + /** + * 保障提供明细编号,必填,关联保障提供明细表(保障资源库)。 + */ + @NotBlank(message = "保障提供明细编号不能为空") + @Size(max = 50, message = "保障提供明细编号不能超过50个字符") + private String bztgmxbh; + + /** + * 年度,课次所属的教学年度。 + */ + @NotNull(message = "年度不能为空") + private Integer nd; + + /** + * 数量,本课次需要该保障提供的数量。 + */ + @NotNull(message = "数量不能为空") + private Integer sl; + + /** + * 备注,保障计划相关补充说明。 + */ + @Size(max = 500, message = "备注不能超过500个字符") + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportUpdateDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportUpdateDTO.java new file mode 100644 index 000000000..a75a93202 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/courserunning/TeachingSupportUpdateDTO.java @@ -0,0 +1,35 @@ +package com.roomroot.jwgl.dto.courserunning; + +import jakarta.validation.constraints.*; +import lombok.Data; + + + +/** + * 教学保障计划修改参数。 + *

+ * 用于修改已登记的教学保障计划,包括调整数量和备注信息。 + *

+ */ +@Data +public class TeachingSupportUpdateDTO { + + /** + * 课程表_保障明细编号,必填,标识需要修改的保障计划记录。 + */ + @NotBlank(message = "保障计划编号不能为空") + @Size(max = 50, message = "保障计划编号不能超过50个字符") + private String kcbbzmxbh; + + /** + * 数量,修改后的保障提供数量。 + */ + @NotNull(message = "数量不能为空") + private Integer sl; + + /** + * 备注,修改后的保障计划备注。 + */ + @Size(max = 500, message = "备注不能超过500个字符") + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentCreateDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentCreateDTO.java new file mode 100644 index 000000000..77f195ce9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentCreateDTO.java @@ -0,0 +1,51 @@ +package com.roomroot.jwgl.dto.department; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 部别新增参数。 + */ +@Data +public class DepartmentCreateDTO { + + /** + * 部别名称,对应“实施_机关.机关名称”。 + */ + @NotBlank(message = "部别名称不能为空") + @Size(max = 50, message = "部别名称不能超过50个字符") + private String name; + + /** + * 机关类别,对应“实施_机关.机关类别”。 + */ + @NotBlank(message = "机关类别不能为空") + @Size(max = 50, message = "机关类别不能超过50个字符") + private String category; + + /** + * 分类号;未传入时使用数据库原有默认语义“0”。 + */ + @Size(max = 50, message = "分类号不能超过50个字符") + private String classificationCode = "0"; + + /** + * 分级号;未传入时使用数据库原有默认语义 0。 + */ + @NotNull(message = "分级号不能为空") + @Min(value = 0, message = "分级号不能小于0") + private Integer levelNumber = 0; + + /** + * 上级部别编号;根节点不填写。 + */ + @Size(max = 36, message = "上级部别编号不能超过36个字符") + private String parentId; + + /** + * 部别简称。 + */ + @Size(max = 500, message = "简称不能超过500个字符") + private String abbreviation; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentIdDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentIdDTO.java new file mode 100644 index 000000000..ad3372da7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentIdDTO.java @@ -0,0 +1,19 @@ +package com.roomroot.jwgl.dto.department; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 部别编号参数。 + */ +@Data +public class DepartmentIdDTO { + + /** + * 部别唯一编号。 + */ + @NotBlank(message = "部别编号不能为空") + @Size(max = 36, message = "部别编号不能超过36个字符") + private String id; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentQueryDTO.java new file mode 100644 index 000000000..2f49aba65 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentQueryDTO.java @@ -0,0 +1,29 @@ +package com.roomroot.jwgl.dto.department; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 部别查询参数。 + */ +@Data +public class DepartmentQueryDTO { + + /** + * 模糊查询关键字,可匹配编号、名称、简称和分类号。 + */ + @Size(max = 100, message = "查询关键字不能超过100个字符") + private String keyword; + + /** + * 机关类别精确查询条件,忽略英文字母大小写。 + */ + @Size(max = 50, message = "机关类别不能超过50个字符") + private String category; + + /** + * true 查询启用数据,false 查询停用数据,null 查询全部。 + */ + private Boolean enabled; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentUpdateDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentUpdateDTO.java new file mode 100644 index 000000000..6666923ed --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/department/DepartmentUpdateDTO.java @@ -0,0 +1,58 @@ +package com.roomroot.jwgl.dto.department; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 部别修改参数。 + */ +@Data +public class DepartmentUpdateDTO { + + /** + * 待修改部别的唯一编号。 + */ + @NotBlank(message = "部别编号不能为空") + @Size(max = 36, message = "部别编号不能超过36个字符") + private String id; + + /** + * 修改后的部别名称。 + */ + @NotBlank(message = "部别名称不能为空") + @Size(max = 50, message = "部别名称不能超过50个字符") + private String name; + + /** + * 修改后的机关类别。 + */ + @NotBlank(message = "机关类别不能为空") + @Size(max = 50, message = "机关类别不能超过50个字符") + private String category; + + /** + * 修改后的分类号。 + */ + @Size(max = 50, message = "分类号不能超过50个字符") + private String classificationCode = "0"; + + /** + * 修改后的分级号。 + */ + @NotNull(message = "分级号不能为空") + @Min(value = 0, message = "分级号不能小于0") + private Integer levelNumber = 0; + + /** + * 修改后的上级部别编号;根节点不填写。 + */ + @Size(max = 36, message = "上级部别编号不能超过36个字符") + private String parentId; + + /** + * 修改后的部别简称。 + */ + @Size(max = 500, message = "简称不能超过500个字符") + private String abbreviation; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelCreateDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelCreateDTO.java new file mode 100644 index 000000000..2ee2cfa6e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelCreateDTO.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.dto.departmentpersonnel; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 部别人员新增参数。 + */ +@Data +public class DepartmentPersonnelCreateDTO { + + /** + * 所属部别编号,对应“实施_机关参谋.实施_机关编号”。 + */ + @NotBlank(message = "所属部别编号不能为空") + @Size(max = 36, message = "所属部别编号不能超过36个字符") + private String departmentId; + + /** + * 人员姓名,对应“实施_机关参谋.用户姓名”。 + */ + @NotBlank(message = "人员姓名不能为空") + @Size(max = 50, message = "人员姓名不能超过50个字符") + private String name; + + /** + * 人员序号,用于同一部别下的稳定排序。 + */ + @Size(max = 50, message = "人员序号不能超过50个字符") + private String sequenceNumber; + + /** + * 人员职务。 + */ + @Size(max = 50, message = "人员职务不能超过50个字符") + private String position; + + /** + * 人员卡号。 + */ + @Size(max = 50, message = "人员卡号不能超过50个字符") + private String cardNumber; + + /** + * 人员姓名拼音。 + */ + @Size(max = 50, message = "人员姓名拼音不能超过50个字符") + private String pinyin; + + /** + * 是否为主官;未传入时默认为否。 + */ + @NotNull(message = "主官标志不能为空") + private Boolean principal = false; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelIdDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelIdDTO.java new file mode 100644 index 000000000..72685bfe3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelIdDTO.java @@ -0,0 +1,20 @@ +package com.roomroot.jwgl.dto.departmentpersonnel; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +/** + * 部别人员编号参数。 + */ +@Data +public class DepartmentPersonnelIdDTO { + + /** + * 人员唯一编号。 + */ + @NotBlank(message = "人员编号不能为空") + @Size(max = 36, message = "人员编号不能超过36个字符") + private String id; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelImportDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelImportDTO.java new file mode 100644 index 000000000..687dc85fd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelImportDTO.java @@ -0,0 +1,23 @@ +package com.roomroot.jwgl.dto.departmentpersonnel; + +import lombok.Data; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.Size; +import java.util.List; + +/** + * 部别人员批量导入参数。 + */ +@Data +public class DepartmentPersonnelImportDTO { + + /** + * 待导入人员列表;单次最多导入1000人。 + */ + @Valid + @NotEmpty(message = "导入人员列表不能为空") + @Size(max = 1000, message = "单次最多导入1000人") + private List personnel; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelQueryDTO.java new file mode 100644 index 000000000..7ba1cc3e4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelQueryDTO.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.dto.departmentpersonnel; + +import lombok.Data; + +import jakarta.validation.constraints.Size; + +/** + * 部别人员查询及导出筛选参数。 + */ +@Data +public class DepartmentPersonnelQueryDTO { + + /** + * 所属部别编号;不填写时查询全部部别。 + */ + @Size(max = 36, message = "所属部别编号不能超过36个字符") + private String departmentId; + + /** + * 查询关键字,可匹配姓名、序号、职务、卡号、拼音和人员编号。 + */ + @Size(max = 50, message = "查询关键字不能超过50个字符") + private String keyword; + + /** + * 离职状态;不填写时同时查询在职和离职人员。 + */ + private Boolean departed; + + /** + * 主官状态;不填写时不限制。 + */ + private Boolean principal; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelUpdateDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelUpdateDTO.java new file mode 100644 index 000000000..33676e527 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/departmentpersonnel/DepartmentPersonnelUpdateDTO.java @@ -0,0 +1,63 @@ +package com.roomroot.jwgl.dto.departmentpersonnel; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 部别人员修改参数。 + */ +@Data +public class DepartmentPersonnelUpdateDTO { + + /** + * 人员唯一编号。 + */ + @NotBlank(message = "人员编号不能为空") + @Size(max = 36, message = "人员编号不能超过36个字符") + private String id; + + /** + * 调整后的所属部别编号。 + */ + @NotBlank(message = "所属部别编号不能为空") + @Size(max = 36, message = "所属部别编号不能超过36个字符") + private String departmentId; + + /** + * 人员姓名。 + */ + @NotBlank(message = "人员姓名不能为空") + @Size(max = 50, message = "人员姓名不能超过50个字符") + private String name; + + /** + * 人员序号。 + */ + @Size(max = 50, message = "人员序号不能超过50个字符") + private String sequenceNumber; + + /** + * 人员职务。 + */ + @Size(max = 50, message = "人员职务不能超过50个字符") + private String position; + + /** + * 人员卡号。 + */ + @Size(max = 50, message = "人员卡号不能超过50个字符") + private String cardNumber; + + /** + * 人员姓名拼音。 + */ + @Size(max = 50, message = "人员姓名拼音不能超过50个字符") + private String pinyin; + + /** + * 是否为主官。 + */ + @NotNull(message = "主官标志不能为空") + private Boolean principal; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictDataBatchQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictDataBatchQueryDTO.java new file mode 100644 index 000000000..fc18c2a61 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictDataBatchQueryDTO.java @@ -0,0 +1,24 @@ +package com.roomroot.jwgl.dto.dict; + +import lombok.Data; + +import jakarta.validation.constraints.NotEmpty; +import java.util.List; + +/** + * 字典数据批量查询参数。 + *

+ * 用于页面初始化时一次性查询多个字典类型的数据, + * 返回结果按类型编码分组,减少前端请求次数,提高页面加载效率。 + *

+ */ +@Data +public class DictDataBatchQueryDTO { + + /** + * 字典类型编码列表。 + *

至少包含一个类型编码,每个编码对应一个需要查询的字典类型。

+ */ + @NotEmpty(message = "字典类型编码列表不能为空") + private List typeCodes; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictDataQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictDataQueryDTO.java new file mode 100644 index 000000000..cb2c34a4b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictDataQueryDTO.java @@ -0,0 +1,50 @@ +package com.roomroot.jwgl.dto.dict; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 字典数据分页查询参数。 + *

+ * 用于字典数据管理页面的分页列表查询,支持按字典类型编码(必填)、 + * 关键字(模糊匹配标签或值)、状态进行筛选。 + *

+ */ +@Data +public class DictDataQueryDTO { + + /** + * 当前页码;未填写时默认查询第一页,最小为1。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数;未填写时默认20条,最小1条,单页最多100条。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max(value = 100, message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); + + /** + * 字典类型编码;必填,用于筛选指定类型的字典数据。 + */ + @NotBlank(message = "字典类型编码不能为空") + @Size(max = 50, message = "字典类型编码不能超过50个字符") + private String dictTypeCode; + + /** + * 查询关键字,可匹配字典标签、字典值。 + *

不填写时不作为筛选条件。

+ */ + @Size(max = 50, message = "查询关键字不能超过50个字符") + private String keyword; + + /** + * 状态(0停用 1正常);不填写时同时查询启用和停用。 + */ + private Integer status; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictDataSaveDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictDataSaveDTO.java new file mode 100644 index 000000000..1aeaa53f9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictDataSaveDTO.java @@ -0,0 +1,89 @@ +package com.roomroot.jwgl.dto.dict; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + + +/** + * 字典数据保存参数(新增/编辑合一)。 + *

+ * 用于字典数据的新增和编辑操作。通过 {@code dictDataId} 区分是新增还是编辑: + * 为空时新增,有值时编辑。保存时会校验关联的字典类型是否存在, + * 若 {@code isDefault} 设置为 1,会自动取消该类型下其他数据的默认标记。 + *

+ */ +@Data +public class DictDataSaveDTO { + + /** + * 字典数据ID;为空时执行新增操作,有值时执行编辑操作。 + */ + private Long dictDataId; + + /** + * 字典类型编码。 + *

用于关联字典类型,保存时会校验该类型是否存在。

+ */ + @NotBlank(message = "字典类型编码不能为空") + @Size(max = 50, message = "字典类型编码不能超过50个字符") + private String dictTypeCode; + + /** + * 字典标签(显示用)。 + *

前端下拉框、单选、多选组件中展示的文本。

+ */ + @NotBlank(message = "字典标签不能为空") + @Size(max = 50, message = "字典标签不能超过50个字符") + private String dictLabel; + + /** + * 字典值(存储用)。 + *

实际存入数据库或用于业务逻辑判断的值。

+ */ + @NotBlank(message = "字典值不能为空") + @Size(max = 50, message = "字典值不能超过50个字符") + private String dictValue; + + /** + * 排序号。 + *

控制数据项在前端下拉框中的展示顺序,数值越小越靠前。

+ */ + @NotNull(message = "排序号不能为空") + private Integer dictSort; + + /** + * 标签样式(success/warning/info/danger)。 + *

用于前端标签组件的颜色样式,可不填。

+ */ + @Size(max = 20, message = "标签样式不能超过20个字符") + private String cssClass; + + /** + * 表格回显样式。 + *

用于前端表格中该字典项的展示样式,可不填。

+ */ + @Size(max = 20, message = "表格回显样式不能超过20个字符") + private String listClass; + + /** + * 是否默认(0否 1是)。 + *

同一类型下只有一个默认选项,设置新的默认值会自动取消旧的。

+ */ + @NotNull(message = "是否默认不能为空") + private Integer isDefault; + + /** + * 状态(0停用 1正常)。 + *

停用后该选项在业务界面不可选。

+ */ + @NotNull(message = "状态不能为空") + private Integer status; + + /** + * 备注。 + */ + @Size(max = 200, message = "备注不能超过200个字符") + private String remark; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictTypeQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictTypeQueryDTO.java new file mode 100644 index 000000000..a40da9f4b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictTypeQueryDTO.java @@ -0,0 +1,53 @@ +package com.roomroot.jwgl.dto.dict; + +import lombok.Data; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +/** + * 字典类型分页查询参数。 + *

+ * 用于字典类型管理页面的分页列表查询,支持按关键字(模糊匹配类型编码或名称)、 + * 所属分组、状态进行筛选。分页参数采用固定结构,与项目其他分页查询保持一致。 + *

+ */ +@Data +public class DictTypeQueryDTO { + + /** + * 当前页码;未填写时默认查询第一页,最小为1。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数;未填写时默认20条,最小1条,单页最多100条。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max(value = 100, message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); + + /** + * 查询关键字,可匹配字典类型编码、字典类型名称。 + *

不填写时不作为筛选条件。

+ */ + @Size(max = 50, message = "查询关键字不能超过50个字符") + private String keyword; + + /** + * 所属分组;不填写时查询全部分组。 + *

常见分组:基础数据、教学数据、学籍数据、师资数据。

+ */ + @Size(max = 30, message = "分组不能超过30个字符") + private String dictGroup; + + /** + * 状态(0停用 1正常);不填写时同时查询启用和停用。 + */ + private Integer status; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictTypeSaveDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictTypeSaveDTO.java new file mode 100644 index 000000000..834a9fcd7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/dict/DictTypeSaveDTO.java @@ -0,0 +1,57 @@ +package com.roomroot.jwgl.dto.dict; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 字典类型保存参数(新增/编辑合一)。 + *

+ * 用于字典类型的新增和编辑操作。通过 {@code dictTypeId} 区分是新增还是编辑: + * 为空时新增,有值时编辑。新增时校验类型编码全局唯一性。 + *

+ */ +@Data +public class DictTypeSaveDTO { + + /** + * 字典类型ID;为空时执行新增操作,有值时执行编辑操作。 + */ + private Long dictTypeId; + + /** + * 字典类型编码(全局唯一,程序引用键)。 + *

新增时必须唯一,编辑时若修改编码需重新校验唯一性。

+ */ + @NotBlank(message = "字典类型编码不能为空") + @Size(max = 50, message = "字典类型编码不能超过50个字符") + private String dictTypeCode; + + /** + * 字典类型名称(用于页面展示)。 + */ + @NotBlank(message = "字典类型名称不能为空") + @Size(max = 50, message = "字典类型名称不能超过50个字符") + private String dictTypeName; + + /** + * 所属分组(基础数据/教学数据/学籍数据等)。 + *

用于对字典类型进行分类展示。

+ */ + @NotBlank(message = "所属分组不能为空") + @Size(max = 30, message = "所属分组不能超过30个字符") + private String dictGroup; + + /** + * 状态(0停用 1正常)。 + *

停用后该类型下的数据项在业务下拉框中不可见。

+ */ + @NotNull(message = "状态不能为空") + private Integer status; + + /** + * 备注。 + */ + @Size(max = 200, message = "备注不能超过200个字符") + private String remark; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/elective/AddClassDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/elective/AddClassDTO.java new file mode 100644 index 000000000..a0ca1c2fe --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/elective/AddClassDTO.java @@ -0,0 +1,19 @@ +package com.roomroot.jwgl.dto.elective; + +import com.roomroot.jwgl.entity.XYDB; +import com.roomroot.jwgl.entity.XYDQB; +import lombok.Data; + +/** + * 新增教学班次请求参数 + *

同时携带学员队表和学员队期表数据,一次请求同时写入两张表。

+ */ +@Data +public class AddClassDTO { + + /** 教学班次信息(学员队表) */ + private XYDB clazz; + + /** 学号规则与注册设置(学员队期表) */ + private XYDQB period; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/faculty/AddTeacherDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/faculty/AddTeacherDTO.java new file mode 100644 index 000000000..08d299c4e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/faculty/AddTeacherDTO.java @@ -0,0 +1,19 @@ +package com.roomroot.jwgl.dto.faculty; + +import com.roomroot.jwgl.entity.JYB; +import com.roomroot.jwgl.entity.JYSX; +import lombok.Data; + +/** + * 新增教员请求参数 + *

同时携带教员表和教员属性表的数据,一次请求同时写入两张表。

+ */ +@Data +public class AddTeacherDTO { + + /** 教员基本信息 */ + private JYB teacher; + + /** 教员属性信息(含职务、技术等级、学历学位等非空字段) */ + private JYSX attributes; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/login/LoginDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/login/LoginDTO.java new file mode 100644 index 000000000..0fb1313b0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/login/LoginDTO.java @@ -0,0 +1,22 @@ +package com.roomroot.jwgl.dto.login; + +import lombok.Data; + +/** + * 登录请求参数 + */ +@Data +public class LoginDTO { + + /** 单位编号(管理员登录时可为空) */ + private String orgId; + + /** 登录名/账号 */ + private String loginName; + + /** 密码 */ + private String password; + + /** 用户类型: ADMIN-行政 TEACHER-教员 STUDENT-学员 MANAGER-管理员 */ + private String userType; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementCreateDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementCreateDTO.java new file mode 100644 index 000000000..89903ab5f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementCreateDTO.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.dto.noticeannouncement; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.CATEGORY_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.CONTENT_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.DEFAULT_CATEGORY; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.PRIORITY_MAX_VALUE; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.REMARK_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.TITLE_MAX_LENGTH; + +/** + * 通知公告新增参数。 + * + *

新增接口只创建拟制公告,公告状态、发布时间、阅读次数和发布范围 + * 均由服务端按照固定业务规则生成,不接收客户端指定值。

+ */ +@Data +public class NoticeAnnouncementCreateDTO { + + /** + * 公告标题。 + */ + @NotBlank(message = "公告标题不能为空") + @Size( + max = TITLE_MAX_LENGTH, + message = "公告标题不能超过100个字符") + private String title; + + /** + * 公告正文。 + */ + @NotBlank(message = "公告内容不能为空") + @Size( + max = CONTENT_MAX_LENGTH, + message = "公告内容不能超过100000个字符") + private String content; + + /** + * 公告类别;未填写时默认使用“公告”。 + */ + @NotBlank(message = "公告类别不能为空") + @Size( + max = CATEGORY_MAX_LENGTH, + message = "公告类别不能超过50个字符") + private String category = DEFAULT_CATEGORY; + + /** + * 公告优先级;数值越大,列表排序越靠前。 + */ + @Min(value = 0, message = "公告优先级不能小于0") + @Max( + value = PRIORITY_MAX_VALUE, + message = "公告优先级不能超过9999") + private Integer priority = Integer.valueOf(0); + + /** + * 公告备注。 + */ + @Size( + max = REMARK_MAX_LENGTH, + message = "公告备注不能超过500个字符") + private String remark; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementIdDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementIdDTO.java new file mode 100644 index 000000000..7eebf1a65 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementIdDTO.java @@ -0,0 +1,26 @@ +package com.roomroot.jwgl.dto.noticeannouncement; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.NOTICE_ID_MAX_LENGTH; + +/** + * 通知公告编号参数。 + * + *

只接收公告业务编号,不接收账户编号或公告状态等可由服务端查询的数据。

+ */ +@Data +public class NoticeAnnouncementIdDTO { + + /** + * 通知公告编号。 + */ + @NotBlank(message = "公告编号不能为空") + @Size( + max = NOTICE_ID_MAX_LENGTH, + message = "公告编号不能超过50个字符") + private String id; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementPriorityDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementPriorityDTO.java new file mode 100644 index 000000000..da3b38483 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementPriorityDTO.java @@ -0,0 +1,36 @@ +package com.roomroot.jwgl.dto.noticeannouncement; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.NOTICE_ID_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.PRIORITY_MAX_VALUE; + +/** + * 通知公告优先级调整参数。 + * + *

优先级设置为0表示取消置顶效果,大于0时按照数值从高到低排列。

+ */ +@Data +public class NoticeAnnouncementPriorityDTO { + + /** + * 待调整的通知公告编号。 + */ + @NotBlank(message = "公告编号不能为空") + @Size( + max = NOTICE_ID_MAX_LENGTH, + message = "公告编号不能超过50个字符") + private String id; + + /** + * 调整后的公告优先级。 + */ + @NotNull(message = "公告优先级不能为空") + @Min(value = 0, message = "公告优先级不能小于0") + @Max( + value = PRIORITY_MAX_VALUE, + message = "公告优先级不能超过9999") + private Integer priority; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementPublishDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementPublishDTO.java new file mode 100644 index 000000000..dfdef62bc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementPublishDTO.java @@ -0,0 +1,47 @@ +package com.roomroot.jwgl.dto.noticeannouncement; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import java.util.List; + +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.MAX_DEPARTMENT_SCOPE_SIZE; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.NOTICE_ID_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.SCOPE_TYPE_MAX_LENGTH; + +/** + * 通知公告发布参数。 + * + *

发布账户由服务端会话识别,接收账户由发布范围计算, + * 客户端不能直接提交接收账户编号。

+ */ +@Data +public class NoticeAnnouncementPublishDTO { + + /** + * 待发布的通知公告编号。 + */ + @NotBlank(message = "公告编号不能为空") + @Size( + max = NOTICE_ID_MAX_LENGTH, + message = "公告编号不能超过50个字符") + private String id; + + /** + * 发布范围类型:ALL、ALL_TEACHERS 或 DEPARTMENTS。 + */ + @NotBlank(message = "发布范围不能为空") + @Size( + max = SCOPE_TYPE_MAX_LENGTH, + message = "发布范围类型不能超过20个字符") + private String scopeType; + + /** + * 指定部别编号集合;仅 DEPARTMENTS 范围使用。 + */ + @Size( + max = MAX_DEPARTMENT_SCOPE_SIZE, + message = "单次最多选择200个部别") + private List departmentIds; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementQueryDTO.java new file mode 100644 index 000000000..890fe2ec1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementQueryDTO.java @@ -0,0 +1,90 @@ +package com.roomroot.jwgl.dto.noticeannouncement; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDate; + +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.CATEGORY_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.MAX_PAGE_SIZE; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.TITLE_MAX_LENGTH; + +/** + * 通知公告分页查询参数。 + * + *

只接收通知公告管理页面的筛选条件,不接收数据库实体和统计结果, + * 保证查询入参与页面出参相互独立。

+ */ +@Data +public class NoticeAnnouncementQueryDTO { + + /** + * 当前页码;未填写时默认查询第一页。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数;未填写时默认20条。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max( + value = MAX_PAGE_SIZE, + message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); + + /** + * 公告标题关键字;未填写时不限制标题。 + */ + @Size( + max = TITLE_MAX_LENGTH, + message = "公告标题不能超过100个字符") + private String title; + + /** + * 最低优先级;未填写时不限制优先级下限。 + */ + @Min(value = 0, message = "最低优先级不能小于0") + private Integer minPriority; + + /** + * 最高优先级;未填写时不限制优先级上限。 + */ + @Min(value = 0, message = "最高优先级不能小于0") + private Integer maxPriority; + + /** + * 发布开始日期;查询结果包含该日期。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate publishStartDate; + + /** + * 发布结束日期;查询结果包含该日期。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd") + private LocalDate publishEndDate; + + /** + * 公告类别;未填写时查询全部类别。 + */ + @Size( + max = CATEGORY_MAX_LENGTH, + message = "公告类别不能超过50个字符") + private String category; + + /** + * 公告状态;未填写时查询全部状态。 + */ + @Size( + max = STATUS_MAX_LENGTH, + message = "公告状态不能超过50个字符") + private String status; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementRecipientInsertDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementRecipientInsertDTO.java new file mode 100644 index 000000000..e76abf71d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementRecipientInsertDTO.java @@ -0,0 +1,23 @@ +package com.roomroot.jwgl.dto.noticeannouncement; + +import lombok.Data; + +/** + * 通知公告接收人批量写入参数。 + * + *

该 DTO 仅在业务层与 Mapper 之间传递服务端生成的接收人快照, + * 不作为 Controller 的客户端请求参数。

+ */ +@Data +public class NoticeAnnouncementRecipientInsertDTO { + + /** + * 通告用户信息记录编号。 + */ + private String id; + + /** + * 接收账户编号。 + */ + private String accountId; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementRecipientQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementRecipientQueryDTO.java new file mode 100644 index 000000000..fad23e652 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementRecipientQueryDTO.java @@ -0,0 +1,55 @@ +package com.roomroot.jwgl.dto.noticeannouncement; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.MAX_PAGE_SIZE; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.NOTICE_ID_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.RECIPIENT_KEYWORD_MAX_LENGTH; + +/** + * 通知公告接收人分页查询参数。 + */ +@Data +public class NoticeAnnouncementRecipientQueryDTO { + + /** + * 待查询接收情况的公告编号。 + */ + @NotBlank(message = "公告编号不能为空") + @Size( + max = NOTICE_ID_MAX_LENGTH, + message = "公告编号不能超过50个字符") + private String id; + + /** + * 当前页码。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max( + value = MAX_PAGE_SIZE, + message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); + + /** + * 接收账户或用户姓名关键字。 + */ + @Size( + max = RECIPIENT_KEYWORD_MAX_LENGTH, + message = "接收人关键字不能超过100个字符") + private String keyword; + + /** + * 阅读状态;为空时查询全部,true 查询已读,false 查询未读。 + */ + private Boolean read; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementUpdateDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementUpdateDTO.java new file mode 100644 index 000000000..a65365ab7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/NoticeAnnouncementUpdateDTO.java @@ -0,0 +1,30 @@ +package com.roomroot.jwgl.dto.noticeannouncement; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.NOTICE_ID_MAX_LENGTH; + +/** + * 通知公告修改参数。 + * + *

继承公告可编辑内容,只额外接收待修改公告编号。 + * 拟制和已下架状态允许修改,已发布状态由业务层拒绝修改。

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NoticeAnnouncementUpdateDTO + extends NoticeAnnouncementCreateDTO { + + /** + * 待修改的通知公告编号。 + */ + @NotBlank(message = "公告编号不能为空") + @Size( + max = NOTICE_ID_MAX_LENGTH, + message = "公告编号不能超过50个字符") + private String id; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/UserNoticeAnnouncementQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/UserNoticeAnnouncementQueryDTO.java new file mode 100644 index 000000000..4b627fb1c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/noticeannouncement/UserNoticeAnnouncementQueryDTO.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.dto.noticeannouncement; + +import lombok.Data; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.CATEGORY_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.MAX_PAGE_SIZE; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.TITLE_MAX_LENGTH; + +/** + * 当前登录用户的通知公告分页查询参数。 + * + *

查询目标账户只从当前服务端会话读取,DTO 不提供账户编号字段。

+ */ +@Data +public class UserNoticeAnnouncementQueryDTO { + + /** + * 当前页码。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max( + value = MAX_PAGE_SIZE, + message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); + + /** + * 公告标题关键字。 + */ + @Size( + max = TITLE_MAX_LENGTH, + message = "公告标题不能超过100个字符") + private String title; + + /** + * 公告类别。 + */ + @Size( + max = CATEGORY_MAX_LENGTH, + message = "公告类别不能超过50个字符") + private String category; + + /** + * 是否只查询未读公告。 + */ + private Boolean unreadOnly = Boolean.FALSE; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogExportDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogExportDTO.java new file mode 100644 index 000000000..c11d3a31e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogExportDTO.java @@ -0,0 +1,57 @@ +package com.roomroot.jwgl.dto.notificationlog; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; + +import static com.roomroot.jwgl.utils.NotificationLogConstants.CATEGORY_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NotificationLogConstants.SEND_STATUS_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NotificationLogConstants.TITLE_MAX_LENGTH; + +/** + * 通知日志导出筛选参数。 + * + *

导出接口只接收业务筛选条件,不接收分页参数; + * 服务端按照固定批次查询全部符合条件的通知日志。

+ */ +@Data +public class NotificationLogExportDTO { + + /** + * 通知标题关键字;未填写时不限制标题。 + */ + @Size( + max = TITLE_MAX_LENGTH, + message = "通知标题不能超过100个字符") + private String title; + + /** + * 通知类别;未填写时查询全部类别。 + */ + @Size( + max = CATEGORY_MAX_LENGTH, + message = "通知类别不能超过50个字符") + private String category; + + /** + * 发送开始时间;导出结果包含等于该时间的日志。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime sendStartTime; + + /** + * 发送结束时间;导出结果包含等于该时间的日志。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime sendEndTime; + + /** + * 发送状态;支持发送成功、部分成功和发送失败。 + */ + @Size( + max = SEND_STATUS_MAX_LENGTH, + message = "发送状态不能超过20个字符") + private String sendStatus; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogIdDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogIdDTO.java new file mode 100644 index 000000000..1d7d021c3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogIdDTO.java @@ -0,0 +1,27 @@ +package com.roomroot.jwgl.dto.notificationlog; + +import lombok.Data; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +import static com.roomroot.jwgl.utils.NotificationLogConstants.NOTIFICATION_ID_MAX_LENGTH; + +/** + * 通知日志编号参数。 + * + *

该 DTO 只接收通知日志对应的通知编号, + * 不允许客户端提交发送、接收或阅读统计结果。

+ */ +@Data +public class NotificationLogIdDTO { + + /** + * 待查询的通知编号。 + */ + @NotBlank(message = "通知日志编号不能为空") + @Size( + max = NOTIFICATION_ID_MAX_LENGTH, + message = "通知日志编号不能超过50个字符") + private String id; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogQueryDTO.java new file mode 100644 index 000000000..9a3e811b4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogQueryDTO.java @@ -0,0 +1,78 @@ +package com.roomroot.jwgl.dto.notificationlog; + +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; + +import static com.roomroot.jwgl.utils.NotificationLogConstants.CATEGORY_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NotificationLogConstants.MAX_PAGE_SIZE; +import static com.roomroot.jwgl.utils.NotificationLogConstants.SEND_STATUS_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NotificationLogConstants.TITLE_MAX_LENGTH; + +/** + * 通知日志发送历史分页查询参数。 + * + *

该 DTO 只接收通知日志页面提交的筛选条件, + * 不接收数据库统计结果或页面展示字段。

+ */ +@Data +public class NotificationLogQueryDTO { + + /** + * 当前页码;未填写时默认查询第一页。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数;未填写时默认20条。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max( + value = MAX_PAGE_SIZE, + message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); + + /** + * 通知标题关键字;未填写时不限制标题。 + */ + @Size( + max = TITLE_MAX_LENGTH, + message = "通知标题不能超过100个字符") + private String title; + + /** + * 通知类别;未填写时查询全部类别。 + */ + @Size( + max = CATEGORY_MAX_LENGTH, + message = "通知类别不能超过50个字符") + private String category; + + /** + * 发送开始时间;查询结果包含等于该时间的日志。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime sendStartTime; + + /** + * 发送结束时间;查询结果包含等于该时间的日志。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime sendEndTime; + + /** + * 发送状态;支持发送成功、部分成功和发送失败。 + */ + @Size( + max = SEND_STATUS_MAX_LENGTH, + message = "发送状态不能超过20个字符") + private String sendStatus; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogRecipientQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogRecipientQueryDTO.java new file mode 100644 index 000000000..c166efe83 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/notificationlog/NotificationLogRecipientQueryDTO.java @@ -0,0 +1,63 @@ +package com.roomroot.jwgl.dto.notificationlog; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +import static com.roomroot.jwgl.utils.NotificationLogConstants.MAX_PAGE_SIZE; +import static com.roomroot.jwgl.utils.NotificationLogConstants.NOTIFICATION_ID_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NotificationLogConstants.RECIPIENT_KEYWORD_MAX_LENGTH; + +/** + * 通知日志接收与阅读明细分页查询参数。 + * + *

该 DTO 只保存页面筛选条件, + * 接收人账户信息和阅读结果统一由数据库查询。

+ */ +@Data +public class NotificationLogRecipientQueryDTO { + + /** + * 待查询的通知编号。 + */ + @NotBlank(message = "通知日志编号不能为空") + @Size( + max = NOTIFICATION_ID_MAX_LENGTH, + message = "通知日志编号不能超过50个字符") + private String id; + + /** + * 当前页码;未填写时默认查询第一页。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数;未填写时默认20条。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max( + value = MAX_PAGE_SIZE, + message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); + + /** + * 登录账号或接收人姓名关键字。 + */ + @Size( + max = RECIPIENT_KEYWORD_MAX_LENGTH, + message = "接收人关键字不能超过100个字符") + private String keyword; + + /** + * 接收状态;为空查询全部,true 查询成功,false 查询失败。 + */ + private Boolean received; + + /** + * 阅读状态;为空查询全部,true 查询已读,false 查询未读。 + */ + private Boolean read; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogExportDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogExportDTO.java new file mode 100644 index 000000000..e233d8f01 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogExportDTO.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.dto.operationlog; + +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + + +import java.time.LocalDateTime; + +/** + * 操作日志导出筛选参数。 + * + *

导出接口只接收日志筛选条件,不接收分页参数。 + * 业务层会按照固定批次查询全部符合条件的数据。

+ */ +@Data +public class OperationLogExportDTO { + + /** + * 操作开始时间;导出结果包含等于该时间的日志。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + /** + * 操作结束时间;导出结果包含等于该时间的日志。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + /** + * 操作人关键字,可匹配登录账号、用户姓名和统一账号。 + */ + @Size(max = 50, message = "操作人关键字不能超过50个字符") + private String operatorKeyword; + + /** + * 业务模块名称;不填写时导出全部模块。 + */ + @Size(max = 20, message = "模块名称不能超过20个字符") + private String module; + + /** + * 操作内容关键字;不填写时不限制操作内容。 + */ + @Size(max = 100, message = "操作内容关键字不能超过100个字符") + private String contentKeyword; + + /** + * 请求地址关键字;不填写时不限制请求地址。 + */ + @Size(max = 200, message = "请求地址关键字不能超过200个字符") + private String urlKeyword; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogIdDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogIdDTO.java new file mode 100644 index 000000000..64723c4af --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogIdDTO.java @@ -0,0 +1,25 @@ +package com.roomroot.jwgl.dto.operationlog; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + + + +import static com.roomroot.jwgl.utils.BasicManagementConstants.IDENTIFIER_MAX_LENGTH; + +/** + * 操作日志唯一编号参数。 + */ +@Data +public class OperationLogIdDTO { + + /** + * 操作日志编号,对应“操作日志表.编号”。 + */ + @NotBlank(message = "操作日志编号不能为空") + @Size( + max = IDENTIFIER_MAX_LENGTH, + message = "操作日志编号不能超过36个字符") + private String id; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogQueryDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogQueryDTO.java new file mode 100644 index 000000000..383f47344 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogQueryDTO.java @@ -0,0 +1,72 @@ +package com.roomroot.jwgl.dto.operationlog; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + + +import java.time.LocalDateTime; + +/** + * 操作日志分页查询参数。 + * + *

只接收页面查询条件,不接收操作人编号或登录记录编号, + * 防止客户端使用不可信身份数据干扰审计查询。

+ */ +@Data +public class OperationLogQueryDTO { + + /** + * 当前页码;未填写时默认查询第一页。 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum = Integer.valueOf(1); + + /** + * 每页记录数;未填写时默认20条,单页最多100条。 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max(value = 100, message = "每页最大为100条") + private Integer pageSize = Integer.valueOf(20); + + /** + * 操作开始时间;查询结果包含等于该时间的日志。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime startTime; + + /** + * 操作结束时间;查询结果包含等于该时间的日志。 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime endTime; + + /** + * 操作人关键字,可匹配登录账号、用户姓名和统一账号。 + */ + @Size(max = 50, message = "操作人关键字不能超过50个字符") + private String operatorKeyword; + + /** + * 业务模块名称;未填写时查询全部模块。 + */ + @Size(max = 20, message = "模块名称不能超过20个字符") + private String module; + + /** + * 操作内容关键字;未填写时不限制操作内容。 + */ + @Size(max = 100, message = "操作内容关键字不能超过100个字符") + private String contentKeyword; + + /** + * 请求地址关键字;未填写时不限制请求地址。 + */ + @Size(max = 200, message = "请求地址关键字不能超过200个字符") + private String urlKeyword; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogRecordDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogRecordDTO.java new file mode 100644 index 000000000..e7e12d373 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/operationlog/OperationLogRecordDTO.java @@ -0,0 +1,46 @@ +package com.roomroot.jwgl.dto.operationlog; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import static com.roomroot.jwgl.utils.BasicManagementConstants.IDENTIFIER_MAX_LENGTH; +import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATION_CONTENT_MAX_LENGTH; +import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATION_URL_MAX_LENGTH; + +/** + * 操作日志内部记录参数。 + * + *

该 DTO 只在服务端自动记录流程中传递当前登录记录编号、 + * 操作内容和请求地址,不接收日志编号、操作时间或客户端身份信息。

+ */ +@Data +public class OperationLogRecordDTO { + + /** + * 当前请求关联的登录记录编号。 + */ + @NotBlank(message = "登录记录编号不能为空") + @Size( + max = IDENTIFIER_MAX_LENGTH, + message = "登录记录编号不能超过36个字符") + private String loginRecordId; + + /** + * 当前请求对应的业务操作内容。 + */ + @NotBlank(message = "操作内容不能为空") + @Size( + max = OPERATION_CONTENT_MAX_LENGTH, + message = "操作内容不能超过2000个字符") + private String operationContent; + + /** + * 当前请求的应用内访问地址。 + */ + @NotBlank(message = "请求地址不能为空") + @Size( + max = OPERATION_URL_MAX_LENGTH, + message = "请求地址不能超过200个字符") + private String url; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SemesterInitializationSaveDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SemesterInitializationSaveDTO.java new file mode 100644 index 000000000..918a0eae4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SemesterInitializationSaveDTO.java @@ -0,0 +1,123 @@ +package com.roomroot.jwgl.dto.systeminitialization; + +import lombok.Data; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.LocalDateTime; + +/** + * 学期初始化参数保存参数。 + */ +@Data +public class SemesterInitializationSaveDTO { + + /** + * 学期所属年度。 + */ + @NotNull(message = "学期年度不能为空") + @Min(value = 2000, message = "学期年度不能小于2000") + @Max(value = 2100, message = "学期年度不能大于2100") + private Integer year; + + /** + * 同一年度内的学期序号。 + */ + @NotNull(message = "学期序号不能为空") + @Min(value = 1, message = "学期序号不能小于1") + @Max(value = 10, message = "学期序号不能大于10") + private Integer sequenceNumber; + + /** + * 开学时间。 + */ + @NotNull(message = "开学时间不能为空") + private LocalDateTime startAt; + + /** + * 学期结束时间。 + */ + @NotNull(message = "学期结束时间不能为空") + private LocalDateTime endAt; + + /** + * 是否设置为当前学期。 + */ + @NotNull(message = "当前学期标志不能为空") + private Boolean currentSemester = Boolean.TRUE; + + /** + * 调课是否不需要审批。 + */ + @NotNull(message = "调课不审批标志不能为空") + private Boolean adjustmentWithoutApproval = Boolean.FALSE; + + /** + * 课时系数方案编号。 + */ + @Size(max = 100, message = "课时系数方案编号不能超过100个字符") + private String hourCoefficientSchemeId; + + /** + * 是否禁止调课。 + */ + @NotNull(message = "禁止调课标志不能为空") + private Boolean adjustmentForbidden = Boolean.FALSE; + + /** + * 终结成绩是否定为及格。 + */ + @NotNull(message = "终结成绩定及格标志不能为空") + private Boolean finalGradeAsPass = Boolean.FALSE; + + /** + * 调课是否需要教务领导审核。 + */ + @NotNull(message = "教务领导审核调课标志不能为空") + private Boolean leadershipReviewRequired = Boolean.FALSE; + + /** + * 教员调整是否不需要审批。 + */ + @NotNull(message = "教员调整不审批标志不能为空") + private Boolean teacherAdjustmentWithoutApproval = Boolean.FALSE; + + /** + * 时间调整是否不需要审批。 + */ + @NotNull(message = "时间调整不审批标志不能为空") + private Boolean timeAdjustmentWithoutApproval = Boolean.FALSE; + + /** + * 保障调整是否不需要审批。 + */ + @NotNull(message = "保障调整不审批标志不能为空") + private Boolean supportAdjustmentWithoutApproval = Boolean.FALSE; + + /** + * 场地调整是否不需要审批。 + */ + @NotNull(message = "场地调整不审批标志不能为空") + private Boolean venueAdjustmentWithoutApproval = Boolean.FALSE; + + /** + * 锁定周数。 + */ + @NotNull(message = "锁定周数不能为空") + @Min(value = 0, message = "锁定周数不能小于0") + @Max(value = 100, message = "锁定周数不能大于100") + private Integer lockedWeeks = Integer.valueOf(0); + + /** + * 锁定计量单位。 + */ + @Size(max = 50, message = "锁定计量单位不能超过50个字符") + private String lockMeasurementUnit; + + /** + * 课时统计时间。 + */ + private LocalDateTime teachingHourStatisticsAt; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationConfigurationSaveDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationConfigurationSaveDTO.java new file mode 100644 index 000000000..88b61f131 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationConfigurationSaveDTO.java @@ -0,0 +1,88 @@ +package com.roomroot.jwgl.dto.systeminitialization; + +import lombok.Data; + +import jakarta.validation.constraints.*; + +/** + * 系统初始化基础参数保存参数。 + */ +@Data +public class SystemInitializationConfigurationSaveDTO { + + /** + * 页面查询到的当前版本编号。 + * + *

系统已经存在配置时必须传入,用于防止旧页面覆盖其他人刚保存的新版本。

+ */ + @Min(value = 1, message = "期望版本编号不能小于1") + private Integer expectedVersionId; + + /** + * 新配置版本值;未填写时沿用当前版本值。 + */ + @Size(max = 100, message = "配置版本值不能超过100个字符") + private String versionValue; + + /** + * 院校名称。 + */ + @NotBlank(message = "院校名称不能为空") + @Size(max = 200, message = "院校名称不能超过200个字符") + private String universityName; + + /** + * 院校代码。 + */ + @NotBlank(message = "院校代码不能为空") + @Size(max = 100, message = "院校代码不能超过100个字符") + private String universityCode; + + /** + * 缺省系统名称。 + */ + @NotBlank(message = "缺省系统名称不能为空") + @Size(max = 200, message = "缺省系统名称不能超过200个字符") + private String defaultSystemName; + + /** + * 系统运行模式。 + */ + @NotBlank(message = "系统运行模式不能为空") + @Size(max = 50, message = "系统运行模式不能超过50个字符") + private String systemMode; + + /** + * 学期名称显示样式。 + */ + @NotBlank(message = "学期名称样式不能为空") + @Size(max = 200, message = "学期名称样式不能超过200个字符") + private String semesterNameStyle; + + /** + * 节次类别。 + */ + @NotBlank(message = "节次类别不能为空") + @Size(max = 100, message = "节次类别不能超过100个字符") + private String periodCategory; + + /** + * 学员队全名称是否包含年级。 + */ + @NotNull(message = "学员队全名称含年级标志不能为空") + private Boolean studentTeamFullNameIncludesGrade; + + /** + * 外部文件存储根路径。 + */ + @NotBlank(message = "外部文件存储根路径不能为空") + @Size(max = 1000, message = "外部文件存储根路径不能超过1000个字符") + private String externalFileStorageRootPath; + + /** + * 本站文件访问路径。 + */ + @NotBlank(message = "本站文件访问路径不能为空") + @Size(max = 1000, message = "本站文件访问路径不能超过1000个字符") + private String localFileAccessPath; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationDepartmentImportRowDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationDepartmentImportRowDTO.java new file mode 100644 index 000000000..2ef15b145 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationDepartmentImportRowDTO.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.dto.systeminitialization; + +import lombok.Data; + +/** + * 系统初始化历史部别导入行。 + */ +@Data +public class SystemInitializationDepartmentImportRowDTO { + + /** 部别编号。 */ + private String id; + + /** 部别名称。 */ + private String name; + + /** 部别类别。 */ + private String category; + + /** 分类号。 */ + private String classificationCode; + + /** 分级号。 */ + private Integer levelNumber; + + /** 上级部别编号。 */ + private String parentId; + + /** 部别简称。 */ + private String abbreviation; + + /** 是否停用。 */ + private Boolean disabled; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationExecutionDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationExecutionDTO.java new file mode 100644 index 000000000..b40921639 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationExecutionDTO.java @@ -0,0 +1,42 @@ +package com.roomroot.jwgl.dto.systeminitialization; + +import lombok.Data; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +/** + * 系统初始化最终执行确认参数。 + */ +@Data +public class SystemInitializationExecutionDTO { + + /** + * 页面预检查时使用的配置版本编号。 + */ + @NotNull(message = "配置版本编号不能为空") + @Min(value = 1, message = "配置版本编号不能小于1") + private Integer expectedConfigurationVersionId; + + /** + * 页面预检查时使用的当前学期年度。 + */ + @NotNull(message = "当前学期年度不能为空") + @Min(value = 2000, message = "当前学期年度不能小于2000") + @Max(value = 2100, message = "当前学期年度不能大于2100") + private Integer semesterYear; + + /** + * 页面预检查时使用的当前学期序号。 + */ + @NotNull(message = "当前学期序号不能为空") + @Min(value = 1, message = "当前学期序号不能小于1") + private Integer semesterSequenceNumber; + + /** + * 是否已经由操作人明确确认执行初始化。 + */ + @NotNull(message = "初始化确认标志不能为空") + private Boolean confirmed; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationHistoricalDataDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationHistoricalDataDTO.java new file mode 100644 index 000000000..f6c8780a7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationHistoricalDataDTO.java @@ -0,0 +1,39 @@ +package com.roomroot.jwgl.dto.systeminitialization; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 已从 Excel 解析并规范化的系统初始化历史基础数据。 + * + *

该 DTO 只在 Controller 入参解析后的业务流程中流转, + * 不作为数据库实体或接口返回对象使用。

+ */ +@Data +public class SystemInitializationHistoricalDataDTO { + + /** + * 原始上传文件名称。 + */ + private String fileName; + + /** + * 待导入部别。 + */ + private List departments = + new ArrayList<>(); + + /** + * 待导入教研室及其所属部别。 + */ + private List + researchOffices = new ArrayList<>(); + + /** + * 待导入部别人员。 + */ + private List personnel = + new ArrayList<>(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationHistoricalImportDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationHistoricalImportDTO.java new file mode 100644 index 000000000..26632e139 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationHistoricalImportDTO.java @@ -0,0 +1,19 @@ +package com.roomroot.jwgl.dto.systeminitialization; + +import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.validation.constraints.NotNull; + +/** + * 系统初始化历史基础数据导入参数。 + */ +@Data +public class SystemInitializationHistoricalImportDTO { + + /** + * 包含“部别”“教研室”“部别人员”工作表的 Excel 文件。 + */ + @NotNull(message = "历史数据文件不能为空") + private MultipartFile file; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationPersonnelImportRowDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationPersonnelImportRowDTO.java new file mode 100644 index 000000000..7d5680948 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationPersonnelImportRowDTO.java @@ -0,0 +1,37 @@ +package com.roomroot.jwgl.dto.systeminitialization; + +import lombok.Data; + +/** + * 系统初始化历史部别人员导入行。 + */ +@Data +public class SystemInitializationPersonnelImportRowDTO { + + /** 人员编号。 */ + private String id; + + /** 所属部别编号。 */ + private String departmentId; + + /** 人员姓名。 */ + private String name; + + /** 显示序号。 */ + private String sequenceNumber; + + /** 职务。 */ + private String position; + + /** 卡号。 */ + private String cardNumber; + + /** 姓名拼音。 */ + private String pinyin; + + /** 是否为主官。 */ + private Boolean principal; + + /** 是否离职。 */ + private Boolean departed; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationResearchOfficeImportRowDTO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationResearchOfficeImportRowDTO.java new file mode 100644 index 000000000..e2523be13 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/dto/systeminitialization/SystemInitializationResearchOfficeImportRowDTO.java @@ -0,0 +1,43 @@ +package com.roomroot.jwgl.dto.systeminitialization; + +import lombok.Data; + +/** + * 系统初始化历史教研室导入行。 + */ +@Data +public class SystemInitializationResearchOfficeImportRowDTO { + + /** 教研室编号。 */ + private String id; + + /** 教研室名称。 */ + private String name; + + /** 所属部别编号。 */ + private String departmentId; + + /** 教研室简称。 */ + private String abbreviation; + + /** 显示序号。 */ + private String sequenceNumber; + + /** 备注。 */ + private String remark; + + /** 是否为虚拟教研室。 */ + private Boolean virtual; + + /** 是否具有机关性质。 */ + private Boolean organNature; + + /** 是否停用。 */ + private Boolean disabled; + + /** 教研室原有部系文本。 */ + private String departmentText; + + /** 待建立隶属关系的服务端编号。 */ + private String relationId; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BCKTJXRZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BCKTJXRZ.java new file mode 100644 index 000000000..ddf389415 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BCKTJXRZ.java @@ -0,0 +1,103 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 班次课堂教学日志 + *

+ * 教学日志核心表,记录每堂课的教学情况。 + * 状态(zt): 0-未填写 1-填写中 2-已上报教研室 3-已上报中层单位 4-已上报业务机关 5-已审核通过 + *

+ */ +@Data +@TableName("班次课堂教学日志") +public class BCKTJXRZ { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("实施_课程编号") + private String sskcbh; + + @TableField("实施_课程表编号") + private String sskcbbh; + + @TableField("学员队编号") + private String xydbh; + + @TableField("年度") + private Integer nd; + + @TableField("日期") + private LocalDateTime rq; + + @TableField("节次") + private Integer jc; + + @TableField("实际时间安排") + private Integer sjsjap; + + @TableField("课程科目编号") + private String kckmbh; + + @TableField("教学方法") + private String jxff; + + @TableField("应到人数") + private Integer ydrs; + + @TableField("实到人数") + private Integer sdrs; + + @TableField("调停课情况") + private String dtkqk; + + @TableField("授课情况_教学态度") + private String skqkjxtd; + + @TableField("授课情况_教学内容") + private String skqkjxnr; + + @TableField("授课情况_教学方法") + private String skqkjxff; + + @TableField("授课情况_课堂管理") + private String skqkktgl; + + @TableField("授课情况_学习收获") + private String skqkxxsh; + + @TableField("领导专家听查课") + private Integer ldzjtck; + + @TableField("跟班队干编号") + private String gbdgbh; + + @TableField("教学状态") + private String jxzt; + + @TableField("意见建议") + private String yjjy; + + /** 状态: 0-未填写 1-填写中 2-已上报教研室 3-已上报中层单位 4-已上报业务机关 5-已审核通过 */ + @TableField("状态") + private String zt; + + @TableField("教务部门反馈意见") + private String jwbmfkyj; + + @TableField("填报学员编号") + private String tbxybh; + + @TableField("核准管理员编号") + private String hzglybh; + + @TableField("教学管理机构编号") + private String jxgljgbh; + + @TableField("与实施计划一致") + private Integer yssjhyz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BCKTJXRZSKJY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BCKTJXRZSKJY.java new file mode 100644 index 000000000..19d46f034 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BCKTJXRZSKJY.java @@ -0,0 +1,44 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 班次课堂教学日志_授课教员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("班次课堂教学日志_授课教员") +public class BCKTJXRZSKJY extends BaseEntity { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("班次课堂教学日志_编号") + private String bcktjxrzbh; + + @TableField("辅助教员编号") + private String fzjybh; + + /** 主教员: 1-主讲教师 0-辅讲教师 */ + @TableField("主教员") + private Integer zjy; + + @TableField("年度") + private Integer nd; + + @TableField(exist = false) + private LocalDateTime createTime; + @TableField(exist = false) + private LocalDateTime updateTime; + @TableField(exist = false) + private String createBy; + @TableField(exist = false) + private String updateBy; + @TableField(exist = false) + private Integer delFlag; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BCXQJCXQB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BCXQJCXQB.java new file mode 100644 index 000000000..a4ac0bff4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BCXQJCXQB.java @@ -0,0 +1,106 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 班次学期教材需求表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("班次学期教材需求表") +public class BCXQJCXQB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 教学任务教材需求编号 + */ + private String jxrwjcxqbh; + + /** + * 班次学期编号 + */ + private String bcxqbh; + + /** + * 班次编号 + */ + private String bcbh; + + /** + * 教材信息编号 + */ + private String jcxxbh; + + /** + * 课编号 + */ + private String kbh; + + /** + * 需求数量 + */ + private Integer xqsl; + + /** + * 年度 + */ + private Integer nd; + + /** + * 教材性质 + */ + private String jcxz; + + /** + * 教材选用说明 + */ + private String jcxysm; + + /** + * 领用类型 + */ + private String lylx; + + /** + * 领用教材库存明细项编号 + */ + private String lyjckcmxxbh; + + /** + * 领用教材库存单编号 + */ + private String lyjckcdbh; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 开课日期 + */ + private LocalDateTime kkrq; + + /** + * 开班日期 + */ + private LocalDateTime kbrq; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BK.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BK.java new file mode 100644 index 000000000..363ac7b07 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BK.java @@ -0,0 +1,81 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 补考 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("补考") +public class BK extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 名称 + */ + private String mc; + + /** + * 补考时间 + */ + private LocalDateTime bksj; + + /** + * 补考地点 + */ + private String bkdd; + + /** + * 补考负责人编号 + */ + private String bkfzrbh; + + /** + * 补考状态 + */ + private String bkzt; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 成绩录入时间 + */ + private LocalDateTime cjlrsj; + + /** + * 成绩审核时间 + */ + private LocalDateTime cjshsj; + + /** + * 备注 + */ + private String bz; + + /** + * 缓考为终结性成绩 + */ + private Integer hkwzjxcj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BKDDB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BKDDB.java new file mode 100644 index 000000000..27fc2192b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BKDDB.java @@ -0,0 +1,39 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 补考地点表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("补考地点表") +public class BKDDB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 补考编号 + */ + private String bkbh; + + /** + * 教室编号 + */ + private String jsbh; + + /** + * 备注 + */ + private String bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BKJKB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BKJKB.java new file mode 100644 index 000000000..d2a0a2c68 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BKJKB.java @@ -0,0 +1,39 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 补考监考表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("补考监考表") +public class BKJKB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 补考编号 + */ + private String bkbh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 备注 + */ + private String bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BKXY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BKXY.java new file mode 100644 index 000000000..94e99949e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BKXY.java @@ -0,0 +1,49 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 补考学员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("补考学员") +public class BKXY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学员课程成绩编号 + */ + private String xykccjbh; + + /** + * 补考编号 + */ + private String bkbh; + + /** + * 考试成绩 + */ + private Float kscj; + + /** + * 备注 + */ + private String bz; + + /** + * 缓考 + */ + private Integer hk; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BZLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BZLB.java new file mode 100644 index 000000000..319fdeae4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BZLB.java @@ -0,0 +1,40 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +/** + * 保障类别 + */ +@Data +@TableName(value = "保障类别", schema = "JW") +public class BZLB { + + /** + * 编号 + */ + @TableId(value = "BH", type = IdType.INPUT) + private String bh; + + /** + * 名称 + */ + @TableField("名称") + private String mc; + + /** + * 部门编号 + */ + @TableField("部门编号") + private String bmbh; + + /** + * 停用 + */ + @TableField("停用") + private Integer ty; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BZMX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BZMX.java new file mode 100644 index 000000000..a9b177d99 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BZMX.java @@ -0,0 +1,44 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 保障明细 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("保障明细") +public class BZMX extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 课堂标准编号 + */ + private String ktbzbh; + + /** + * 保障提供明细编号 + */ + private String bztgmxbh; + + /** + * 数量 + */ + private Integer sl; + + /** + * 备注 + */ + private String bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BZTGMX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BZTGMX.java new file mode 100644 index 000000000..7f8d0b808 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/BZTGMX.java @@ -0,0 +1,89 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 保障提供明细 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("保障提供明细") +public class BZTGMX extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 保障类别编号 + */ + private String bzlbbh; + + /** + * 名称 + */ + private String mc; + + /** + * 单位 + */ + private String dw; + + /** + * 数量 + */ + private Integer sl; + + /** + * 备注 + */ + private String bz; + + /** + * 停用 + */ + private Integer ty; + + /** + * 健康状态 + */ + private Float jkzt; + + /** + * 详细 + */ + private String xx; + + /** + * 耗材 + */ + private Integer hc; + + /** + * 拼音 + */ + private String py; + + /** + * 资源类别 + */ + private String zylb; + + /** + * json字典 + */ + private String jsonzd; + + /** + * 危险品 + */ + private Integer wxp; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CJGGRZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CJGGRZ.java new file mode 100644 index 000000000..07f967295 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CJGGRZ.java @@ -0,0 +1,51 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 成绩更改日志 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("成绩更改日志") +public class CJGGRZ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学员成绩编号 + */ + private String xycjbh; + + /** + * 事由 + */ + private String sy; + + /** + * 时间 + */ + private LocalDateTime sj; + + /** + * 承办人 + */ + private String cbr; + + /** + * 备注 + */ + private String bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPCPRYB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPCPRYB.java new file mode 100644 index 000000000..0f421e02e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPCPRYB.java @@ -0,0 +1,96 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 测评_参评人员表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("测评_参评人员表") +public class CPCPRYB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度测评编号 + */ + private String ndcpbh; + + /** + * 参评类别 + */ + private String cplb; + + /** + * 领导编号 + */ + private String ldbh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 人员姓名 + */ + private String ryxm; + + /** + * 人员类别 + */ + private String rylb; + + /** + * 所属教研室代号 + */ + private String ssjysdh; + + /** + * 所属中层单位编号 + */ + private String sszcdwbh; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 评测得分 + */ + private Float pcdf; + + /** + * 投票率 + */ + private Float tpl; + + /** + * 课时数 + */ + private Integer kss; + + /** + * 被评 + */ + private Integer bp; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPCPYWJGB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPCPYWJGB.java new file mode 100644 index 000000000..57f5044b1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPCPYWJGB.java @@ -0,0 +1,84 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 测评_参评业务机关表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("测评_参评业务机关表") +public class CPCPYWJGB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度测评编号 + */ + private String ndcpbh; + + /** + * 机关编号 + */ + private String jgbh; + + /** + * 序号 + */ + private String xh; + + /** + * 测评项目 + */ + private String cpxm; + + /** + * 测评问题 + */ + private String cpwt; + + /** + * 总人数 + */ + private Integer zrs; + + /** + * 满意人数 + */ + private Integer myrs; + + /** + * 基本满意人数 + */ + private Integer jbmyrs; + + /** + * 不满意人数 + */ + private Integer bmyrs; + + /** + * 满意度 + */ + private Float myd; + + /** + * 不满意度 + */ + private Float bmyd; + + /** + * 投票率 + */ + private Float tpl; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPJGPJJLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPJGPJJLB.java new file mode 100644 index 000000000..3c6f6e65e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPJGPJJLB.java @@ -0,0 +1,39 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 测评_机关评价记录表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("测评_机关评价记录表") +public class CPJGPJJLB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 评测登记编号 + */ + private String pcdjbh; + + /** + * 参评机关编号 + */ + private String cpjgbh; + + /** + * 评价结果 + */ + private String pjjg; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPNDCPB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPNDCPB.java new file mode 100644 index 000000000..a8880c130 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPNDCPB.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 测评_年度测评表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("测评_年度测评表") +public class CPNDCPB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 名称 + */ + private String mc; + + /** + * 说明 + */ + private String sm; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 创建人编号 + */ + private String cjrbh; + + /** + * 测评状态 + */ + private String cpzt; + + /** + * 开始时间 + */ + private LocalDateTime kssj; + + /** + * 结束时间 + */ + private LocalDateTime jssj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPPCDJB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPPCDJB.java new file mode 100644 index 000000000..781539588 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPPCDJB.java @@ -0,0 +1,51 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 测评_评测登记表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("测评_评测登记表") +public class CPPCDJB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 提交时间 + */ + private LocalDateTime tjsj; + + /** + * 撤回时间 + */ + private LocalDateTime chsj; + + /** + * 状态 + */ + private String zt; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPTXPJJLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPTXPJJLB.java new file mode 100644 index 000000000..7a5327778 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CPTXPJJLB.java @@ -0,0 +1,39 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 测评_同行评价记录表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("测评_同行评价记录表") +public class CPTXPJJLB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 评测登记编号 + */ + private String pcdjbh; + + /** + * 被评人员编号 + */ + private String bprybh; + + /** + * 评价结果 + */ + private Float pjjg; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CZRZB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CZRZB.java new file mode 100644 index 000000000..d55e56b3f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/CZRZB.java @@ -0,0 +1,46 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 操作日志表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("操作日志表") +public class CZRZB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 登录记录编号 + */ + private String dljlbh; + + /** + * 操作时间 + */ + private LocalDateTime czsj; + + /** + * 操作内容 + */ + private String cznr; + + /** + * Url + */ + private String url; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DBVersion.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DBVersion.java new file mode 100644 index 000000000..395574461 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DBVersion.java @@ -0,0 +1,231 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * DBVersion + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("DBVersion") +public class DBVersion extends BaseEntity { + + /** + * ID + */ + private Integer iD; + + /** + * VersionDate + */ + private LocalDateTime versionDate; + + /** + * VersionValue + */ + private String versionValue; + + /** + * UniversityName + */ + private String universityName; + + /** + * 学员队全名称含年级 + */ + private Boolean xYDQMCHNJ; + + /** + * 学期名称样式 + */ + private String xQMCYS; + + /** + * 节次类别 + */ + private String jCLB; + + /** + * 外部文件存储根路径 + */ + private String wBWJCCGLJ; + + /** + * 本站文件访问路径 + */ + private String bZWJFWLJ; + + /** + * 登录失败弹窗 + */ + private Boolean dLSBTC; + + /** + * 允许密码尝试次数 + */ + private Integer yXMMCSCS; + + /** + * 用户锁定分钟数 + */ + private Integer yHSDFZS; + + /** + * 允许教研室管理教材 + */ + private Boolean yxJYSGLJC; + + /** + * 使用虚拟教学场地设置班次专用教室 + */ + private Boolean sYXNJXCDSZBCZYJS; + + /** + * 使用虚拟教员设置课程责任教员 + */ + private Boolean sYXNJYSZKCZRJY; + + /** + * 允许多主讲分组教学 + */ + private Boolean yXDZJFZJX; + + /** + * 跟查课录入锁定天数 + */ + private Integer gCKLRSDTS; + + /** + * UniversityCode + */ + private String universityCode; + + /** + * UniversityValidCode + */ + private String universityValidCode; + + /** + * 显示78节 + */ + private Boolean xS78J; + + /** + * 显示晚上 + */ + private Boolean xSWS; + + /** + * 显示夜间 + */ + private Boolean xSYJ; + + /** + * 教学要点名称 + */ + private String jXYDMC; + + /** + * 教材导出包含图片 + */ + private Boolean jCDCBHTP; + + /** + * 建立班次学期信息时自动生成教学计划 + */ + private Boolean jLBCXQXXSZDSCJXJH; + + /** + * 成绩报表显示结论成绩 + */ + private Boolean cJBBxSJLCJ; + + /** + * 缺省系统名称 + */ + private String qSXTMC; + + /** + * Excel课表按开课日期排序 + */ + private Boolean excelKBAKKRQPX; + + /** + * Excel课表显示实施教员数 + */ + private Integer excelKBxSSSJYS; + + /** + * 系统模式 + */ + private String xTMS; + + /** + * 课程表标题样式 + */ + private String kCBBTYS; + + /** + * 课程表副标题样式 + */ + private String kCBFBTYS; + + /** + * 大屏显示教学内容 + */ + private Boolean dPXSJXNR; + + /** + * 大屏显示前景标题颜色 + */ + private String dPXSQJBTYS; + + /** + * 大屏显示前景内容颜色 + */ + private String dPXSQJNRYS; + + /** + * 大屏显示背景颜色 + */ + private String dPXSBJYS; + + /** + * 实践课标记 + */ + private String sJKBJ; + + /** + * 三实课标记 + */ + private String sSKBJ; + + /** + * 增加学时标记 + */ + private String zJXSBJ; + + /** + * 减少学时标记 + */ + private String jSXSBJ; + + /** + * json字典 + */ + private String jsonZD; + + /** + * 学员正课请假模板文件编号 + */ + @TableId(value = "学员正课请假模板文件编号", type = IdType.INPUT) + private String xYZKQJMBWJBH; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DDDLTBB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DDDLTBB.java new file mode 100644 index 000000000..23d753d3a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DDDLTBB.java @@ -0,0 +1,63 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 单点登录同步表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("单点登录同步表") +public class DDDLTBB extends BaseEntity { + + /** + * 统一账号 + */ + private String tYZH; + + /** + * 姓名 + */ + private String xM; + + /** + * 是学员 + */ + private Boolean sXY; + + /** + * 单位全称 + */ + private String dWQC; + + /** + * 学员学号 + */ + private String xYXH; + + /** + * 年级 + */ + private String nJ; + + /** + * 班次 + */ + private String bC; + + /** + * 专业 + */ + private String zY; + + /** + * 数据同步时间 + */ + private LocalDateTime sjTBSJ; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQBZMX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQBZMX.java new file mode 100644 index 000000000..2415b9675 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQBZMX.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 调课申请_保障明细 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("调课申请_保障明细") +public class DKSQBZMX extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 申请编号 + */ + private String sqbh; + + /** + * 保障提供明细编号 + */ + private String bztgmxbh; + + /** + * 数量 + */ + private Integer sl; + + /** + * 备注 + */ + private String bz; + + /** + * 查收状态 + */ + private Integer cszt; + + /** + * 查收时间 + */ + private LocalDateTime cssj; + + /** + * 年度 + */ + private Integer nd; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQFZJY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQFZJY.java new file mode 100644 index 000000000..f9c0524aa --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQFZJY.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 调课申请_辅助教员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("调课申请_辅助教员") +public class DKSQFZJY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 调课申请编号 + */ + private String dksqbh; + + /** + * 辅助教员编号 + */ + private String fzjybh; + + /** + * 查收状态 + */ + private Integer cszt; + + /** + * 查收时间 + */ + private LocalDateTime cssj; + + /** + * 主教员 + */ + private Integer zjy; + + /** + * 移除状态 + */ + private Integer yczt; + + /** + * 年度 + */ + private Integer nd; + + /** + * 备注 + */ + private String bz; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQJS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQJS.java new file mode 100644 index 000000000..eeee15723 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQJS.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 调课申请_教室 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("调课申请_教室") +public class DKSQJS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 调课申请编号 + */ + private String dksqbh; + + /** + * 教室编号 + */ + private String jsbh; + + /** + * 备注 + */ + private String bz; + + /** + * 年度 + */ + private Integer nd; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQXYD.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQXYD.java new file mode 100644 index 000000000..094602c4d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DKSQXYD.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 调课申请_学员队 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("调课申请_学员队") +public class DKSQXYD extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 调课申请编号 + */ + private String dksqbh; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 查收状态 + */ + private Integer cszt; + + /** + * 查收时间 + */ + private LocalDateTime cssj; + + /** + * 移除状态 + */ + private Integer yczt; + + /** + * 年度 + */ + private Integer nd; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DLJLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DLJLB.java new file mode 100644 index 000000000..f60bdf717 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DLJLB.java @@ -0,0 +1,51 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 登录记录表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("登录记录表") +public class DLJLB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 登录信息编号 + */ + private String dLXXBH; + + /** + * 登录时间 + */ + private LocalDateTime dLSJ; + + /** + * IP地址 + */ + private String iPDZ; + + /** + * 内网IP + */ + private String nWIP; + + /** + * 事务ID + */ + private String sWID; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DLXXB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DLXXB.java new file mode 100644 index 000000000..114740f54 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DLXXB.java @@ -0,0 +1,96 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 登录信息表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("登录信息表") +public class DLXXB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 用户密码 + */ + private String yHMM; + + /** + * 用户姓名 + */ + private String yHXM; + + /** + * 密码提示问题 + */ + private String mMTSWT; + + /** + * 密码回答问题 + */ + private String mMHDWT; + + /** + * 电话 + */ + private String dH; + + /** + * 上次登录时间 + */ + private LocalDateTime sCDLSJ; + + /** + * 本次登录时间 + */ + private LocalDateTime bCDLSJ; + + /** + * 失败次数 + */ + private Integer sBCS; + + /** + * 失败时间 + */ + private LocalDateTime sBSJ; + + /** + * 身份证 + */ + private String sFZ; + + /** + * 照片 + */ + private String zP; + + /** + * 账号 + */ + private String zH; + + /** + * 统一账号 + */ + private String tYZH; + + /** + * 自我介绍 + */ + private String zWJS; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DXJSGZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DXJSGZ.java new file mode 100644 index 000000000..b38e59fe9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/DXJSGZ.java @@ -0,0 +1,106 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 单项计算规则 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("单项计算规则") +public class DXJSGZ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 名称 + */ + private String mC; + + /** + * 去除法数量阈值 + */ + private Double qCFSLYZ; + + /** + * 高分去除比例 + */ + private Double gFQCBL; + + /** + * 低分去除比例 + */ + private Double dFQCBL; + + /** + * 整体降分分值线 + */ + private Double allReduce; + + /** + * 整体降分数量比例阈值 + */ + private Double allReduceRatio; + + /** + * 整体降分系数 + */ + private Double allReduceFactor; + + /** + * 整体加分分值线 + */ + private Double allPlus; + + /** + * 整体加分数量比例阈值 + */ + private Double allPlusRatio; + + /** + * 整体加分系数 + */ + private Double allPlusFactor; + + /** + * 优先加分 + */ + private Boolean yXJF; + + /** + * 修改时间 + */ + private LocalDateTime xGSJ; + + /** + * 状态 + */ + private String zT; + + /** + * 生效时间 + */ + private LocalDateTime sXSJ; + + /** + * 废弃时间 + */ + private LocalDateTime fQSJ; + + /** + * 满分 + */ + private Double mF; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDRWB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDRWB.java new file mode 100644 index 000000000..6bfeb569f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDRWB.java @@ -0,0 +1,116 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 服务部队任务表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("服务部队任务表") +public class FWBDRWB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 服务部队编号 + */ + private String fwbdbh; + + /** + * 部门编号 + */ + private String bmbh; + + /** + * 任务内容 + */ + private String rwnr; + + /** + * 计划开始时间 + */ + private LocalDateTime jhkssj; + + /** + * 计划结束时间 + */ + private LocalDateTime jhjssj; + + /** + * 需求专家领域 + */ + private String xqzjly; + + /** + * 需求专家人数 + */ + private Integer xqzjrs; + + /** + * 上报截止时间 + */ + private LocalDateTime sbjzsj; + + /** + * 上报时间 + */ + private LocalDateTime sbsj; + + /** + * 上报情况说明 + */ + private String sbqksm; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 备注 + */ + private String bz; + + /** + * 状态 + */ + private String zt; + + /** + * 受领时间 + */ + private LocalDateTime slsj; + + /** + * 结束时间 + */ + private LocalDateTime jssj; + + /** + * json字典 + */ + private String jsonzd; + + /** + * 完成情况 + */ + private String wcqk; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDWPRYB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDWPRYB.java new file mode 100644 index 000000000..96ce02c14 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDWPRYB.java @@ -0,0 +1,111 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 服务部队外派人员表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("服务部队外派人员表") +public class FWBDWPRYB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 服务部队任务编号 + */ + private String fwbdrwbh; + + /** + * 外派人员编号 + */ + private String wprybh; + + /** + * 外派期间课次冲突数 + */ + private Integer wpqjkccts; + + /** + * 教员身份 + */ + private Integer jysf; + + /** + * 外派期间课次冲突信息 + */ + private String wpqjkcctxx; + + /** + * 备注 + */ + private String bz; + + /** + * 工作核算课时量 + */ + private Float gzhsksl; + + /** + * 完成情况上报状态 + */ + private Integer wcqksbzt; + + /** + * 完成情况上报时间 + */ + private LocalDateTime wcqksbsj; + + /** + * 完成情况 + */ + private String wcqk; + + /** + * json字典 + */ + private String jsonzd; + + /** + * 授课课程科目编号 + */ + private String skkckmbh; + + /** + * 授课任务 + */ + private Integer skrw; + + /** + * 受领任务状态 + */ + private Integer slrwzt; + + /** + * 工作天数 + */ + private Float gzts; + + /** + * 补助课时量 + */ + private Float bzksl; + + /** + * 受领时间 + */ + private LocalDateTime slsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDWPRYSCFJB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDWPRYSCFJB.java new file mode 100644 index 000000000..3cc1a2172 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDWPRYSCFJB.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 服务部队外派人员上传附件表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("服务部队外派人员上传附件表") +public class FWBDWPRYSCFJB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 服务部队外派人员编号 + */ + private String fwbdwprybh; + + /** + * 序号 + */ + private String xh; + + /** + * 分类目录 + */ + private String flml; + + /** + * 名称 + */ + private String mc; + + /** + * 文件编号 + */ + private String wjbh; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDXQB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDXQB.java new file mode 100644 index 000000000..2f8a23680 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDXQB.java @@ -0,0 +1,156 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 服务部队需求表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("服务部队需求表") +public class FWBDXQB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 服务名称 + */ + private String fwmc; + + /** + * 需求方大单位名称 + */ + private String xqfddwmc; + + /** + * 需求方单位名称 + */ + private String xqfdwmc; + + /** + * 需求方联系人 + */ + private String xqflxr; + + /** + * 需求方联系电话 + */ + private String xqflxdh; + + /** + * 来电来函方式 + */ + private String ldlhfs; + + /** + * 请求服务类别编号 + */ + private String qqfwlbbh; + + /** + * 请求服务方式编号 + */ + private String qqfwfsbh; + + /** + * 请求服务内容 + */ + private String qqfwnr; + + /** + * 受众人数 + */ + private Integer szrs; + + /** + * 需求专家领域 + */ + private String xqzjly; + + /** + * 需求专家人数 + */ + private Integer xqzjrs; + + /** + * 计划开始时间 + */ + private LocalDateTime jhkssj; + + /** + * 计划结束时间 + */ + private LocalDateTime jhjssj; + + /** + * 状态 + */ + private String zt; + + /** + * 备注 + */ + private String bz; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 受领时间 + */ + private LocalDateTime slsj; + + /** + * 结束时间 + */ + private LocalDateTime jssj; + + /** + * json字典 + */ + private String jsonzd; + + /** + * 完成情况 + */ + private String wcqk; + + /** + * 需求方级别编号 + */ + private String xqfjbbh; + + /** + * 需求方类别编号 + */ + private String xqflbbh; + + /** + * 需求登记部门编号 + */ + private String xqdjbmbh; + + /** + * 上报时间 + */ + private LocalDateTime sbsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDXQFJB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDXQFJB.java new file mode 100644 index 000000000..af1c5da1b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FWBDXQFJB.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 服务部队需求附件表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("服务部队需求附件表") +public class FWBDXQFJB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 服务部需求队编号 + */ + private String fwbxqdbh; + + /** + * 序号 + */ + private String xh; + + /** + * 分类目录 + */ + private String flml; + + /** + * 名称 + */ + private String mc; + + /** + * 文件编号 + */ + private String wjbh; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSSSXXM.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSSSXXM.java new file mode 100644 index 000000000..845f731e2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSSSXXM.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 方向课题师生双选项目 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("方向课题师生双选项目") +public class FXKTSSSXXM extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 名称 + */ + private String mc; + + /** + * 教学管理机构编号 + */ + private String jxgljgbh; + + /** + * 备注 + */ + private String bz; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 停用时间 + */ + private LocalDateTime tysj; + + /** + * 状态 + */ + private String zt; + + /** + * 学员预选课题数 + */ + private Integer xyyxkts; + + /** + * 说明 + */ + private String sm; + + /** + * 导师带学员限额 + */ + private Integer dsdxyxe; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMBC.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMBC.java new file mode 100644 index 000000000..66282c04b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMBC.java @@ -0,0 +1,39 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 方向课题双选项目_班次 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("方向课题双选项目_班次") +public class FXKTSXXMBC extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 方向课题师生双选项目编号 + */ + private String fxktsssxxmbh; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 备注 + */ + private byte[] bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMDS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMDS.java new file mode 100644 index 000000000..1e0cde2f6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMDS.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 方向课题双选项目_导师 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("方向课题双选项目_导师") +public class FXKTSXXMDS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 方向课题师生双选项目编号 + */ + private String fxktsssxxmbh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 备注 + */ + private String bz; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 停用时间 + */ + private LocalDateTime tysj; + + /** + * 状态 + */ + private String zt; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMFXKT.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMFXKT.java new file mode 100644 index 000000000..03e9fa078 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMFXKT.java @@ -0,0 +1,69 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 方向课题双选项目_方向课题 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("方向课题双选项目_方向课题") +public class FXKTSXXMFXKT extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 名称 + */ + private String mc; + + /** + * 方向课题师生双选项目_导师编号 + */ + private String fxktsssxxmdsbh; + + /** + * 最大人数 + */ + private Integer zdrs; + + /** + * 方向课题师生双选项目编号 + */ + private String fxktsssxxmbh; + + /** + * 试验场地 + */ + private String sycd; + + /** + * 方向课题说明 + */ + private String fxktsm; + + /** + * 备注 + */ + private String bz; + + /** + * 方向课题备注 + */ + private String fxktbz; + + /** + * 确选备注 + */ + private String qxbz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMSXJGB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMSXJGB.java new file mode 100644 index 000000000..6bc9ab7ef --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMSXJGB.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 方向课题双选项目_双选结果表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("方向课题双选项目_双选结果表") +public class FXKTSXXMSXJGB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 方向课题师生双选项目_方向课题编号 + */ + private String fxktsssxxmfxktbh; + + /** + * 学员编号 + */ + private String xybh; + + /** + * 方向课题双选项目_学员编号 + */ + private String fxktsxxmxybh; + + /** + * 方向课题师生双选项目编号 + */ + private String fxktsssxxmbh; + + /** + * 正选状态 + */ + private Integer zxzt; + + /** + * 序号 + */ + private Integer xh; + + /** + * 教员预选状态 + */ + private Integer jyyxzt; + + /** + * 报名时间 + */ + private LocalDateTime bmsj; + + /** + * 预选时间 + */ + private LocalDateTime yxsj; + + /** + * 正选时间 + */ + private LocalDateTime zxsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMXY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMXY.java new file mode 100644 index 000000000..e4b255b12 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FXKTSXXMXY.java @@ -0,0 +1,49 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 方向课题双选项目_学员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("方向课题双选项目_学员") +public class FXKTSXXMXY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 方向课题师生双选项目编号 + */ + private String fxktsssxxmbh; + + /** + * 备注 + */ + private String bz; + + /** + * 方向课题双选项目_班次编号 + */ + private String fxktsxxmbcbh; + + /** + * 学员编号 + */ + private String xybh; + + /** + * 辅导记录 + */ + private String fdjl; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZQZBZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZQZBZ.java new file mode 100644 index 000000000..749f6822f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZQZBZ.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 分值权重标准 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("分值权重标准") +public class FZQZBZ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 序号 + */ + private String xH; + + /** + * 名称 + */ + private String mC; + + /** + * 创建时间 + */ + private LocalDateTime cJSJ; + + /** + * 停用状态 + */ + private Boolean tYZT; + + /** + * 备注 + */ + private String bZ; + + /** + * 停用时间 + */ + private LocalDateTime tYSJ; + + /** + * 说明 + */ + private String sM; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZQZBZQZGZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZQZBZQZGZ.java new file mode 100644 index 000000000..9bff3e6f6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZQZBZQZGZ.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分值权重标准_权重规则 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("分值权重标准_权重规则") +public class FZQZBZQZGZ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 序号 + */ + private String xh; + + /** + * 分值权重标准编号 + */ + private String fzqzbzbh; + + /** + * 名称 + */ + private String mc; + + /** + * 详细信息 + */ + private String xxxx; + + /** + * 权值 + */ + private Float qz; + + /** + * 备注 + */ + private String bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZZHGZBZCPJGYS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZZHGZBZCPJGYS.java new file mode 100644 index 000000000..b8718e2fc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZZHGZBZCPJGYS.java @@ -0,0 +1,39 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分值转换规则标准_测评结果样式 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("分值转换规则标准_测评结果样式") +public class FZZHGZBZCPJGYS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 名称 + */ + private String mC; + + /** + * 备注 + */ + private String bZ; + + /** + * 序号 + */ + private String xH; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZZHGZBZZHGZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZZHGZBZZHGZ.java new file mode 100644 index 000000000..d3c2a9d10 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/FZZHGZBZZHGZ.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 分值转换规则标准_转换规则 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("分值转换规则标准_转换规则") +public class FZZHGZBZZHGZ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 测评结果样式编号 + */ + private String cpjgysbh; + + /** + * 名称 + */ + private String mc; + + /** + * 百分制最低分 + */ + private Integer bfzzdf; + + /** + * 百分制最高分 + */ + private Integer bfzzgf; + + /** + * 备注 + */ + private String bz; + + /** + * 百分制分值 + */ + private Integer bfzfz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKJL.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKJL.java new file mode 100644 index 000000000..8486f0e3d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKJL.java @@ -0,0 +1,86 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 跟查课记录 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("跟查课记录") +public class GCKJL extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 创建人编号 + */ + private String cjrbh; + + /** + * 创建日期 + */ + private LocalDateTime cjrq; + + /** + * 状态 + */ + private String zt; + + /** + * 评语 + */ + private String py; + + /** + * 得分 + */ + private Float df; + + /** + * 备注 + */ + private String bz; + + /** + * 年度 + */ + private Integer nd; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + + /** + * 跟查课时 + */ + private Integer gcks; + + /** + * 部系编号 + */ + private String bxbh; + + /** + * 节次调节 + */ + private Integer jcdj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKJLPJJY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKJLPJJY.java new file mode 100644 index 000000000..877d2b294 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKJLPJJY.java @@ -0,0 +1,69 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 跟查课记录_评价教员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("跟查课记录_评价教员") +public class GCKJLPJJY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 跟查课记录编号 + */ + private String gckjlbh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 主讲 + */ + private Integer zj; + + /** + * 启用 + */ + private Integer qy; + + /** + * 评语 + */ + private String py; + + /** + * 得分 + */ + private Float df; + + /** + * 备注 + */ + private String bz; + + /** + * 教员整改反思 + */ + private String jyzgfs; + + /** + * 反思完成状态 + */ + private Integer fswczt; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKLDJL.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKLDJL.java new file mode 100644 index 000000000..78ff08fce --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKLDJL.java @@ -0,0 +1,111 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 跟查课领导记录 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("跟查课领导记录") +public class GCKLDJL extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 领导编号 + */ + private String ldbh; + + /** + * 课堂编号 + */ + private String ktbh; + + /** + * 创建日期 + */ + private LocalDateTime cjrq; + + /** + * 到位情况 + */ + private Integer dwqk; + + /** + * 状态 + */ + private String zt; + + /** + * 评语 + */ + private String py; + + /** + * 得分 + */ + private Float df; + + /** + * 备注 + */ + private String bz; + + /** + * 年度 + */ + private Integer nd; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + + /** + * 教员提交反思通知 + */ + private Integer jytjfstz; + + /** + * 已阅时间 + */ + private LocalDateTime yysj; + + /** + * 已阅 + */ + private Integer yy; + + /** + * 同行测评 + */ + private Integer txcp; + + /** + * 评学结果 + */ + private String pxjg; + + /** + * 佐证材料编号 + */ + private String zzclbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKLDJLPJJY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKLDJLPJJY.java new file mode 100644 index 000000000..211a905b9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKLDJLPJJY.java @@ -0,0 +1,79 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 跟查课领导记录_评价教员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("跟查课领导记录_评价教员") +public class GCKLDJLPJJY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 跟查课领导记录编号 + */ + private String gckldjlbh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 主讲 + */ + private Integer zj; + + /** + * 启用 + */ + private Integer qy; + + /** + * 评语 + */ + private String py; + + /** + * 得分 + */ + private Float df; + + /** + * 备注 + */ + private String bz; + + /** + * 教员整改反思 + */ + private String jyzgfs; + + /** + * 反思完成状态 + */ + private Integer fswczt; + + /** + * 佐证文件1编号 + */ + private String zzwj1bh; + + /** + * 佐证文件2编号 + */ + private String zzwj2bh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKZJ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKZJ.java new file mode 100644 index 000000000..1f695fb32 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKZJ.java @@ -0,0 +1,101 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 跟查课专家 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("跟查课专家") +public class GCKZJ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 专家编号 + */ + private String zjbh; + + /** + * 跟查课记录编号 + */ + private String gckjlbh; + + /** + * 到位情况 + */ + private Integer dwqk; + + /** + * 评语 + */ + private String py; + + /** + * 得分 + */ + private Float df; + + /** + * 备注 + */ + private String bz; + + /** + * 不采信 + */ + private Integer bcx; + + /** + * 教员提交反思通知 + */ + private Integer jytjfstz; + + /** + * 已阅时间 + */ + private LocalDateTime yysj; + + /** + * 已阅 + */ + private Integer yy; + + /** + * 跟查课时 + */ + private Integer gcks; + + /** + * 评学结果 + */ + private String pxjg; + + /** + * 佐证材料编号 + */ + private String zzclbh; + + /** + * 专家类别 + */ + private String zjlb; + + /** + * 节次调节 + */ + private Integer jcdj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKZJPJJY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKZJPJJY.java new file mode 100644 index 000000000..283f6fc97 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GCKZJPJJY.java @@ -0,0 +1,69 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 跟查课专家_评价教员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("跟查课专家_评价教员") +public class GCKZJPJJY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 跟查课专家编号 + */ + private String gckzjbh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 主讲 + */ + private Integer zj; + + /** + * 启用 + */ + private Integer qy; + + /** + * 评语 + */ + private String py; + + /** + * 得分 + */ + private Float df; + + /** + * 备注 + */ + private String bz; + + /** + * 佐证文件1编号 + */ + private String zzwj1bh; + + /** + * 佐证文件2编号 + */ + private String zzwj2bh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GKLDCKB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GKLDCKB.java new file mode 100644 index 000000000..c4b505a32 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GKLDCKB.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 跟课领导参考表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("跟课领导参考表") +public class GKLDCKB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 人员编号 + */ + private String rybh; + + /** + * 学期编号 + */ + private Integer xqbh; + + /** + * 领导类别编号 + */ + private String ldlbbh; + + /** + * 领导跟查指标单位 + */ + private String ldgczbdw; + + /** + * 领导跟查指标量 + */ + private Integer ldgczbl; + + /** + * 备注 + */ + private String bz; + + /** + * 序号 + */ + private String xh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GKLDLBZBB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GKLDLBZBB.java new file mode 100644 index 000000000..ceef9f6aa --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GKLDLBZBB.java @@ -0,0 +1,49 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 跟课领导类别指标表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("跟课领导类别指标表") +public class GKLDLBZBB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 名称 + */ + private String mC; + + /** + * 禁用状态 + */ + private Boolean jYZT; + + /** + * 指标单位 + */ + private String zBDW; + + /** + * 指标量 + */ + private Integer zBL; + + /** + * 备注 + */ + private String bZ; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GKZJCKB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GKZJCKB.java new file mode 100644 index 000000000..1bc25dc21 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GKZJCKB.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 跟课专家参考表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("跟课专家参考表") +public class GKZJCKB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 人员编号 + */ + private String rybh; + + /** + * 学期编号 + */ + private Integer xqbh; + + /** + * 备注 + */ + private String bz; + + /** + * 序号 + */ + private String xh; + + /** + * 专家类别 + */ + private String zjlb; + + /** + * 督导职务 + */ + private String ddzw; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GZTKJGRY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GZTKJGRY.java new file mode 100644 index 000000000..3f9e72372 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GZTKJGRY.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 关注听课机关人员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("关注听课机关人员") +public class GZTKJGRY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 机关人员编号 + */ + private String jgrybh; + + /** + * 管理员编号 + */ + private String glybh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GZTKJY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GZTKJY.java new file mode 100644 index 000000000..461e9e158 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/GZTKJY.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 关注听课教员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("关注听课教员") +public class GZTKJY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 管理员编号 + */ + private String glybh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCKCD.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCKCD.java new file mode 100644 index 000000000..ba8078164 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCKCD.java @@ -0,0 +1,144 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教材库存单实体类 + */ +@Data +@TableName("教材库存单") +public class JCKCD extends BaseEntity { + + /** + * 教研室代号 + */ + @TableId(value = "ID", type = IdType.INPUT) + private String id; + + /** + * 编号 + */ + @TableField("编号") + private String bh; + + /** + * 名称 + */ + @TableField("名称") + private String mc; + + /** + * 情况说明 + */ + @TableField("情况说明") + private String qksm; + + /** + * 类别 + */ + @TableField("类别") + private String lb; + + /** + * 字别 + */ + @TableField("字别") + private String zb; + + /** + * 承办部门编号 + */ + @TableField("承办部门编号") + private String cbbmbh; + + /** + * 承办人编号 + */ + @TableField("承办人编号") + private String cbrbh; + + /** + * 保管员编号 + */ + @TableField("保管员编号") + private String bgybh; + + /** + * 审批人编号 + */ + @TableField("审批人编号") + private String sprbh; + + /** + * 创建时间 + */ + @TableField("创建时间") + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + @TableField("修改时间") + private LocalDateTime xgsj; + + /** + * 提交时间 + */ + @TableField("提交时间") + private LocalDateTime tjsj; + + /** + * 审批时间 + */ + @TableField("审批时间") + private LocalDateTime spsj; + + /** + * 执行时间 + */ + @TableField("执行时间") + private LocalDateTime zxsj; + + /** + * 结束时间 + */ + @TableField("结束时间") + private LocalDateTime jssj; + + /** + * 状态 + */ + @TableField("状态") + private String zt; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * JSONZD + */ + @TableField("JSONZD") + private String jsonzd; + + /** + * 退回意见 + */ + @TableField("退回意见") + private String thyj; + + /** + * 退回时间 + */ + @TableField("退回时间") + private LocalDateTime thsj; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCKCMXX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCKCMXX.java new file mode 100644 index 000000000..6b92aa6e6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCKCMXX.java @@ -0,0 +1,102 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教材库存明细项实体类 + */ +@Data +@TableName("教材库存明细项") +public class JCKCMXX extends BaseEntity { + + /** + * 教研室代号 + */ + @TableId(value = "ID", type = IdType.INPUT) + private String id; + + /** + * 编号 + */ + @TableField("编号") + private String bh; + + /** + * 教材库存单编号 + */ + @TableField("教材库存单编号") + private String jckcdbbh; + + /** + * 教材信息编号 + */ + @TableField("教材信息编号") + private String jcxxbh; + + /** + * 数量 + */ + @TableField("数量") + private Integer sl; + + /** + * 前置库存明细项编号 + */ + @TableField("前置库存明细项编号") + private String qzkcmxxbh; + + /** + * 还回完毕 + */ + @TableField("还回完毕") + private Boolean hwbw; + + /** + * 还回完毕时间 + */ + @TableField("还回完毕时间") + private LocalDateTime hwbwsj; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * JSONZD + */ + @TableField("JSONZD") + private String jsonzd; + + /** + * 快照_事务后库存数量 + */ + @TableField("快照_事务后库存数量") + private Integer kzswHkcsl; + + /** + * 快照_事务后借出数量 + */ + @TableField("快照_事务后借出数量") + private Integer kzswHjcsl; + + /** + * 快照_事务前库存数量 + */ + @TableField("快照_事务前库存数量") + private Integer kzswQkcsl; + + /** + * 快照_事务前借出数量 + */ + @TableField("快照_事务前借出数量") + private Integer kzswQjcsl; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCSJB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCSJB.java new file mode 100644 index 000000000..4e5ea0f95 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCSJB.java @@ -0,0 +1,64 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 节次时间表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("节次时间表") +public class JCSJB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 双节次 + */ + private Boolean sjc; + + /** + * 节次索引 + */ + private String jcsy; + + /** + * 开始时间 + */ + private String kssj; + + /** + * 结束时间 + */ + private String jssj; + + /** + * 备注 + */ + private String bz; + + /** + * 名称 + */ + private String mc; + + /** + * 简称 + */ + private String jc; + + /** + * 时段 + */ + private String sd; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCXX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCXX.java new file mode 100644 index 000000000..ec5bb25e0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JCXX.java @@ -0,0 +1,307 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 教材信息实体类 + */ +@Data +@TableName("教材信息") +public class JCXX extends BaseEntity { + + /** + * 教研室代号 + */ + @TableId(value = "ID", type = IdType.INPUT) + private String id; + + /** + * 编号 + */ + @TableField("编号") + private String bh; + + /** + * 名称 + */ + @TableField("名称") + private String mc; + + /** + * ISBN + */ + @TableField("ISBN") + private String isbn; + + /** + * 出版社 + */ + @TableField("出版社") + private String cbs; + + /** + * 版次 + */ + @TableField("版次") + private String bc; + + /** + * 开本 + */ + @TableField("开本") + private String kb; + + /** + * 作者 + */ + @TableField("作者") + private String zz; + + /** + * 定价 + */ + @TableField("定价") + private BigDecimal dj; + + /** + * 教材分类 + */ + @TableField("教材分类") + private String jcfl; + + /** + * 教材类型 + */ + @TableField("教材类型") + private String jclx; + + /** + * 教材编写类型 + */ + @TableField("教材编写类型") + private String jcbxlx; + + /** + * 教材重点类型 + */ + @TableField("教材重点类型") + private String jczdlx; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 停用 + */ + @TableField("停用") + private Boolean ty; + + /** + * 停用日期 + */ + @TableField("停用日期") + private LocalDateTime tyrq; + + /** + * 计量单位 + */ + @TableField("计量单位") + private String jldw; + + /** + * 出版日期 + */ + @TableField("出版日期") + private LocalDateTime cbrq; + + /** + * 创建时间 + */ + @TableField("创建时间") + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + @TableField("修改时间") + private LocalDateTime xgsj; + + /** + * 教材来源 + */ + @TableField("教材来源") + private String jcly; + + /** + * 教材层次 + */ + @TableField("教材层次") + private String jccc; + + /** + * 责任教员及联系方式 + */ + @TableField("责任教员及联系方式") + private String zrjyjlxff; + + /** + * 文件编号 + */ + @TableField("文件编号") + private String wjbh; + + /** + * 封面图片文件编号 + */ + @TableField("封面图片文件编号") + private String fmtpwjbh; + + /** + * 出版方式 + */ + @TableField("出版方式") + private String cbfs; + + /** + * 教材代码 + */ + @TableField("教材代码") + private String jcdm; + + /** + * 编写单位 + */ + @TableField("编写单位") + private String bxdw; + + /** + * 教学管理机构编号 + */ + @TableField("教学管理机构编号") + private String jxgljgbh; + + /** + * 教研室代号 + */ + @TableField("教研室代号") + private String jysdh; + + /** + * 版权图片文件编号 + */ + @TableField("版权图片文件编号") + private String bqtpwjbh; + + /** + * 自编教材编审状态 + */ + @TableField("自编教材编审状态") + private String zbjcbxzt; + + /** + * 评选情况 + */ + @TableField("评选情况") + private String xqk; + + /** + * json字典 + */ + @TableField("json字典") + private String jsonzd; + + /** + * 自编立项日期 + */ + @TableField("自编立项日期") + private LocalDateTime zblxrq; + + /** + * 教材使用情况 + */ + @TableField("教材使用情况") + private String jcsyqk; + + /** + * 教材密级 + */ + @TableField("教材密级") + private String jcmj; + + /** + * 颁发审定单位 + */ + @TableField("颁发审定单位") + private String bfsddw; + + /** + * 需彩印 + */ + @TableField("需彩印") + private Boolean xcy; + + /** + * 封面PSD文件编号 + */ + @TableField("封面PSD文件编号") + private String fmPSDwjbh; + + /** + * 封面PDF文件编号 + */ + @TableField("封面PDF文件编号") + private String fmPDFwjbh; + + /** + * 内容PDF文件编号 + */ + @TableField("内容PDF文件编号") + private String nrPDFwjbh; + + /** + * 内容WORD文件编号 + */ + @TableField("内容WORD文件编号") + private String nrWORDwjbh; + + /** + * 锁定数量 + */ + @TableField("锁定数量") + private Integer sdsl; + + /** + * 借出数量 + */ + @TableField("借出数量") + private Integer jcsl; + + /** + * 库存数量 + */ + @TableField("库存数量") + private Integer kcsj; + + /** + * 库存位置 + */ + @TableField("库存位置") + private String kcwz; + + /** + * 默认领用类型 + */ + @TableField("默认领用类型") + private String mrlylx; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JG.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JG.java new file mode 100644 index 000000000..800fedd5f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JG.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 结果 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("结果") +public class JG extends BaseEntity { + + /** + * 年度 + */ + private Integer nD; + + /** + * 学员队编号 + */ + private String xYDBH; + + /** + * 课编号 + */ + private String kBH; + + /** + * 学期第次 + */ + private Integer xQDC; + + /** + * 教员编号 + */ + private String jYBH; + + /** + * 课次序号 + */ + private Integer kCXH; + + /** + * 教室编号 + */ + private String jSBH; + + /** + * 人数 + */ + private Integer rS; + + /** + * 周课时 + */ + private Integer zKS; + + /** + * 学时 + */ + private Integer xS; + + /** + * 课类型 + */ + private String kLX; + + /** + * 简称 + */ + private String jC; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JGRYDLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JGRYDLB.java new file mode 100644 index 000000000..935b3e58a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JGRYDLB.java @@ -0,0 +1,29 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 机关人员登录表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("机关人员登录表") +public class JGRYDLB extends BaseEntity { + + /** + * 机关人员编号 + */ + @TableId(value = "机关人员编号", type = IdType.INPUT) + private String jgrybh; + + /** + * 登录信息编号 + */ + private String dlxxbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JQB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JQB.java new file mode 100644 index 000000000..4cd07d7ff --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JQB.java @@ -0,0 +1,74 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 假期表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("假期表") +public class JQB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 假期时间 + */ + private Integer jqsj; + + /** + * 假期名称 + */ + private String jqmc; + + /** + * 加粗 + */ + private Integer jc; + + /** + * 备注 + */ + private String bz; + + /** + * 可排课 + */ + private Integer kpk; + + /** + * 学员队学期编号 + */ + private String xydxqbh; + + /** + * 备注显示 + */ + private Integer bzxs; + + /** + * 自动排课 + */ + private Integer zdpk; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSB.java new file mode 100644 index 000000000..0cb90b581 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSB.java @@ -0,0 +1,120 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教室表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教室表") +public class JSB extends BaseEntity { + + /** + * 教室编号 + */ + @TableId(value = "ID", type = IdType.INPUT) + private String id; + + /** + * 教室编号 + */ + @TableField(value = "教室编号") + private String jsbh; + + /** + * 教室名称 + */ + @TableField("教室名称") + private String jsmc; + + /** + * 教学楼代号 + */ + @TableField("教学楼代号") + private String jxldh; + + /** + * 容纳人数 + */ + @TableField("容纳人数") + private Integer rnrs; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 设施设备 + */ + @TableField("设施设备") + private String sssb; + + /** + * 停用 + */ + @TableField("停用") + private Boolean ty; + + /** + * 拼音 + */ + @TableField("拼音") + private String py; + + /** + * 序号 + */ + @TableField("序号") + private String xh; + + /** + * 额外允许开班数 + */ + @TableField("额外允许开班数") + private Integer ewyxkbs; + + /** + * 教学场地类型 + */ + @TableField("教学场地类型") + private String jxcdlx; + + /** + * 面积 + */ + @TableField("面积") + private Double mj; + + /** + * 考试容纳人数 + */ + @TableField("考试容纳人数") + private Integer ksrnrs; + + /** + * 虚实类型 + */ + @TableField("虚实类型") + private Boolean xslx; + + /** + * 简称 + */ + @TableField("简称") + private String jc; + + /** + * json字典 + */ + @TableField("JSONZD") + private String jsonzd; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSBKCB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSBKCB.java new file mode 100644 index 000000000..ee18bc58c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSBKCB.java @@ -0,0 +1,62 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教室输出课程表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教室输出课程表") +public class JSBKCB extends BaseEntity { + + /** + * 主键ID + */ + @TableId(value = "ID", type = IdType.INPUT) + private String id; + + /** + * 年度 + */ + @TableField("年度") + private Integer nd; + + /** + * 教室编号 + */ + @TableField("教室编号") + private String jsbh; + + /** + * 课程表 + */ + @TableField("课程表") + private String kcb; + + /** + * 更新时间 + */ + @TableField("更新时间") + private LocalDateTime gxsj; + + /** + * 编号 + */ + @TableField("编号") + private String bh; + + /** + * 有课表 + */ + @TableField("有课表") + private Boolean ykb; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSFSKSXS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSFSKSXS.java new file mode 100644 index 000000000..825355b27 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSFSKSXS.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 讲授方式课时系数 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("讲授方式课时系数") +public class JSFSKSXS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 方案编号 + */ + private String fabh; + + /** + * 讲授方式名称 + */ + private String jsfsmc; + + /** + * 系数 + */ + private Float xs; + + /** + * 不计合班系数 + */ + private Integer bjhbxs; + + /** + * 不计班级人数系数 + */ + private Integer bjbjrsxs; + + /** + * 备注 + */ + private String bz; + + /** + * 按天记课时量 + */ + private Float atjksl; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSSCKCB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSSCKCB.java new file mode 100644 index 000000000..183fdc050 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSSCKCB.java @@ -0,0 +1,51 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教室输出课程表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教室输出课程表") +public class JSSCKCB extends BaseEntity { + + /** + * 年度 + */ + private Integer nd; + + /** + * 教室编号 + */ + private String jsbh; + + /** + * 课程表 + */ + private String kcb; + + /** + * 更新时间 + */ + private LocalDateTime gxsj; + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 有课表 + */ + private Integer ykb; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSZW.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSZW.java new file mode 100644 index 000000000..43eaa5d8d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSZW.java @@ -0,0 +1,39 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 技术职务 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("技术职务") +public class JSZW extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 代码 + */ + private String dM; + + /** + * 名称 + */ + private String mC; + + /** + * 备注 + */ + private String bZ; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSZWKSFBZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSZWKSFBZ.java new file mode 100644 index 000000000..caeac3a70 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JSZWKSFBZ.java @@ -0,0 +1,52 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 技术职务课时费标准 + *

+ * 按技术职务定义课时费标准(课时费、超量课时费)。 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("技术职务课时费标准") +public class JSZWKSFBZ extends BaseEntity { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("技术职务编号") + private String jszwbh; + + @TableField("课时费标准编号") + private String ksfbzbh; + + @TableField("标准课时量") + private Float bzksl; + + @TableField("课时费") + private Float ksf; + + @TableField("超量课时费") + private Float clksf; + + @TableField("备注") + private String bz; + + @TableField(exist = false) + private LocalDateTime createTime; + @TableField(exist = false) + private LocalDateTime updateTime; + @TableField(exist = false) + private String createBy; + @TableField(exist = false) + private String updateBy; + @TableField(exist = false) + private Integer delFlag; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXCDL.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXCDL.java new file mode 100644 index 000000000..9b2b1d44a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXCDL.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教学场地历 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教学场地历") +public class JXCDL extends BaseEntity { + + /** + * 主键ID + */ + @TableId(value = "ID", type = IdType.INPUT) + private String id; + + /** + * 编号 + */ + @TableField("编号") + private String bh; + + /** + * 名称 + */ + @TableField("名称") + private String mc; + + /** + * 可排课 + */ + @TableField("可排课") + private Boolean kpk; + + /** + * 日期 + */ + @TableField("日期") + @JsonFormat(pattern = "yyyy-MM-dd[ HH:mm:ss]") + private LocalDateTime rq; + + /** + * 节次 + */ + @TableField("节次") + private Integer jc; + + /** + * 创建时间 + */ + @TableField("创建时间") + private LocalDateTime cjsj; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 教室编号 + */ + @TableField("教室编号") + private String jsbh; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXCDLBRW.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXCDLBRW.java new file mode 100644 index 000000000..e0388a8b9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXCDLBRW.java @@ -0,0 +1,111 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教学场地录播任务 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教学场地录播任务") +public class JXCDLBRW extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 实施_课程表_教室编号 + */ + private String sskcbjsbh; + + /** + * 教室编号 + */ + private String jsbh; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + + /** + * 录播设备编号 + */ + private String lbsbbh; + + /** + * 开始录制时间 + */ + private LocalDateTime kslzsj; + + /** + * 计划结束录制时间 + */ + private LocalDateTime jhjslzsj; + + /** + * 实际结束录制实际 + */ + private LocalDateTime sjjslzsj; + + /** + * 录制视频路数 + */ + private Integer lzspls; + + /** + * 录制状态 + */ + private String lzzt; + + /** + * 录制参数1 + */ + private String lzcs1; + + /** + * 录制参数2 + */ + private String lzcs2; + + /** + * 录制参数3 + */ + private String lzcs3; + + /** + * 录制参数4 + */ + private String lzcs4; + + /** + * 录制参数5 + */ + private String lzcs5; + + /** + * 录制参数6 + */ + private String lzcs6; + + /** + * 视频地址集 + */ + private String spdzj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXGLJG.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXGLJG.java new file mode 100644 index 000000000..48a8088a7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXGLJG.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教学管理机构 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教学管理机构") +public class JXGLJG extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 名称 + */ + private String mc; + + /** + * 停用 + */ + private Integer ty; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 停用时间 + */ + private LocalDateTime tysj; + + /** + * 备注 + */ + private String bz; + + /** + * 机关部门编号 + */ + private String jgbmbh; + + /** + * 考评中心部门编号 + */ + private String kpzxbmbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXLB.java new file mode 100644 index 000000000..343265afe --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXLB.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教学楼表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教学楼表") +public class JXLB extends BaseEntity { + + /** + * ID + * */ + @TableId(value = "ID", type = IdType.INPUT) + private String id; + + /** + * 教学楼代号 + */ + @TableField("教学楼代号") + private String jxldh; + + /** + * 教学楼名称 + */ + @TableField("教学楼名称") + private String jxlmc; + + /** + * 地点 + */ + @TableField("地点") + private String dd; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 序号 + */ + @TableField("序号") + private String xh; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRW.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRW.java new file mode 100644 index 000000000..c30f30804 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRW.java @@ -0,0 +1,69 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教学任务 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName(value = "教学任务", schema = "JW") +public class JXRW extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 任务名称 + */ + @TableField("任务名称") + private String rwmc; + + /** + * 年度 + */ + @TableField("年度") + private Integer nd; + + /** + * 状态 + */ + @TableField("状态") + private String zt; + + /** + * 发布时间 + */ + @TableField("发布时间") + private LocalDateTime fbsj; + + /** + * 创建时间 + */ + @TableField("创建时间") + private LocalDateTime cjsj; + + /** + * 结束时间 + */ + @TableField("结束时间") + private LocalDateTime jssj; + + /** + * 教材需求生成时间 + */ + @TableField("教材需求生成时间") + private LocalDateTime jcxqscsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRWJCXQB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRWJCXQB.java new file mode 100644 index 000000000..ed34388dd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRWJCXQB.java @@ -0,0 +1,91 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教学任务教材需求表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教学任务教材需求表") +public class JXRWJCXQB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 教学任务编号 + */ + private String jxrwbh; + + /** + * 教材信息编号 + */ + private String jcxxbh; + + /** + * 需求数量 + */ + private Integer xqsl; + + /** + * 年度 + */ + private Integer nd; + + /** + * 领用类型 + */ + private String lylx; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 核准需求数量 + */ + private Integer hzxqsl; + + /** + * 采购印刷数量 + */ + private Integer cgyssl; + + /** + * 最早开课日期 + */ + private LocalDateTime zzkkrq; + + /** + * 最早开班日期 + */ + private LocalDateTime zzkbrq; + + /** + * 采购印刷教材库存明细项编号 + */ + private String cgysjckcmxxbh; + + /** + * 采购印刷教材库存单编号 + */ + private String cgysjckcdbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRZJYKSBZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRZJYKSBZ.java new file mode 100644 index 000000000..a4018a4c7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRZJYKSBZ.java @@ -0,0 +1,83 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 教学日志教员课时补助 + *

+ * 教学日志汇总中每位教员的课时补助明细。 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教学日志教员课时补助") +public class JXRZJYKSBZ extends BaseEntity { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("教学日志课时补助审批编号") + private String jxrzksbzspbh; + + @TableField("教员编号") + private String jybh; + + @TableField("技术职务编号") + private String jszwbh; + + @TableField("是否优质") + private Integer sfyz; + + @TableField("课程科目编号") + private String kckmbh; + + @TableField("非课程任务名称") + private String fkcrwmc; + + @TableField("课时量") + private Float ksl; + + @TableField("基本标准") + private Float jbbz; + + @TableField("增发标准") + private Float zfbz; + + @TableField("课时补助金额") + private BigDecimal ksbzje; + + @TableField("培训期班") + private String pxqb; + + @TableField("部系名称") + private String bxmc; + + @TableField("教研室名称") + private String jysmc; + + @TableField("教学学时") + private Integer jxxs; + + @TableField("主讲") + private Integer zj; + + @TableField("讲授方式") + private String jsfs; + + @TableField(exist = false) + private LocalDateTime createTime; + @TableField(exist = false) + private LocalDateTime updateTime; + @TableField(exist = false) + private String createBy; + @TableField(exist = false) + private String updateBy; + @TableField(exist = false) + private Integer delFlag; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRZKSBZSPB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRZKSBZSPB.java new file mode 100644 index 000000000..6f145620e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRZKSBZSPB.java @@ -0,0 +1,73 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教学日志课时补助审批表 + *

+ * 教学日志汇总主表,记录一个核算周期内的课时补助汇总信息。 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教学日志课时补助审批表") +public class JXRZKSBZSPB extends BaseEntity { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("名称") + private String mc; + + @TableField("核算开始日期") + private LocalDateTime hsksrq; + + @TableField("核算结束日期") + private LocalDateTime hsjsrq; + + @TableField("总课时量") + private Float zksl; + + @TableField("优质课时量") + private Float yzksl; + + @TableField("总补助金额") + private Float zbzje; + + @TableField("优质课时量最高比例") + private Float yzkslzgbl; + + @TableField("优质课时量比例") + private Float yzkslbl; + + @TableField("课时补助标准编号") + private String ksbzbzbh; + + @TableField("课时核算标准编号") + private String kshsbzbh; + + @TableField("核算日期") + private LocalDateTime hsrq; + + @TableField("状态") + private String zt; + + @TableField("任务类别") + private String rwlb; + + @TableField(exist = false) + private LocalDateTime createTime; + @TableField(exist = false) + private LocalDateTime updateTime; + @TableField(exist = false) + private String createBy; + @TableField(exist = false) + private String updateBy; + @TableField(exist = false) + private Integer delFlag; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRZXYXXQK.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRZXYXXQK.java new file mode 100644 index 000000000..8b106f667 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXRZXYXXQK.java @@ -0,0 +1,41 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教学日志_学员学习情况 + *

+ * 教学日志中记录的学员个体学习情况明细。 + * 学习效果: A-优秀 B-良好 C-合格 D-不合格 + *

+ */ +@Data +@TableName("教学日志_学员学习情况") +public class JXRZXYXXQK { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("教学日志编号") + private String jxrzbh; + + @TableField("学员编号") + private String xybh; + + /** 学习效果: A-优秀 B-良好 C-合格 D-不合格 */ + @TableField("学习效果") + private String xyxg; + + @TableField("学习情况") + private String xxqk; + + @TableField("到课情况") + private Integer dkqk; + + @TableField("请假情况") + private Integer qjqk; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXSSJH.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXSSJH.java new file mode 100644 index 000000000..5b86acbb6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXSSJH.java @@ -0,0 +1,158 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 导入数据表-教学实施计划实体类 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("导入数据表教学实施计划") +public class JXSSJH extends BaseEntity { + + /** + * 标识号 + */ + @TableId(value = "标识号", type = IdType.INPUT) + private String bsh; + + /** + * 序号 + */ + @TableField("序号") + private Integer xh; + + /** + * 年度 + */ + @TableField("年度") + private Integer nd; + + /** + * 原序号 + */ + @TableField("原序号") + private String yxh; + + /** + * 日期 + */ + @TableField("日期") + private LocalDateTime rq; + + /** + * 原节次 + */ + @TableField("原节次") + private String yjc; + + /** + * 节次 + */ + @TableField("节次") + private Integer jc; + + /** + * 课程名称 + */ + @TableField("课程名称") + private String kcmc; + + /** + * 班次 + */ + @TableField("班次") + private String bc; + + /** + * 责任单位 + */ + @TableField("责任单位") + private String zrdw; + + /** + * 授课教员 + */ + @TableField("授课教员") + private String skjy; + + /** + * 教学场地 + */ + @TableField("教学场地") + private String jxcd; + + /** + * 教学内容 + */ + @TableField("教学内容") + private String jxnr; + + /** + * 教学方法 + */ + @TableField("教学方法") + private String jxff; + + /** + * 教员备注 + */ + @TableField("教员备注") + private String jybz; + + /** + * 教务备注 + */ + @TableField("教务备注") + private String jwbz; + + /** + * 检查结果 + */ + @TableField("检查结果") + private String jcjg; + + /** + * 已通过检查 + */ + @TableField("已通过检查") + private Boolean ytgjc; + + /** + * 已被忽略 + */ + @TableField("已被忽略") + private Boolean ybhl; + + /** + * 已被导入 + */ + @TableField("已被导入") + private Boolean ybdr; + + /** + * 已被处理 + */ + @TableField("已被处理") + private Boolean ybcl; + + /** + * 实施_课程表编号 + */ + @TableField("课程表编号") + private String kcbbh; + + /** + * 登录编号 + */ + @TableField("登录编号") + private String dlbh; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXSSJHTJB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXSSJHTJB.java new file mode 100644 index 000000000..7b5fabe4a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JXSSJHTJB.java @@ -0,0 +1,161 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教学实施计划统计表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教学实施计划统计表") +public class JXSSJHTJB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + + /** + * 总学时 + */ + private Integer zxs; + + /** + * 总节次 + */ + private Integer zjc; + + /** + * 已办理计划调整数 + */ + private Integer ybljhdzs; + + /** + * 调课数 + */ + private Integer dks; + + /** + * 教学日志填写数 + */ + private Integer jxrztxs; + + /** + * 已审核教学日志数 + */ + private Integer yshjxrzs; + + /** + * 听查课数 + */ + private Integer tcks; + + /** + * 教学督导数 + */ + private Integer jxdds; + + /** + * 开课课程门数 + */ + private Integer kkkcms; + + /** + * 课程考核次数 + */ + private Integer kckhcs; + + /** + * 动用教学场地数 + */ + private Integer dyjxcds; + + /** + * 上课教员数 + */ + private Integer skjys; + + /** + * 教学班次数 + */ + private Integer jxbcs; + + /** + * 实体教学班次数 + */ + private Integer stjxbcs; + + /** + * 选修教学班次数 + */ + private Integer xxjxbcs; + + /** + * 学员总人数 + */ + private Integer xyzrs; + + /** + * 教学请假人次 + */ + private Integer jxqjrc; + + /** + * 教学请假学员人数 + */ + private Integer jxqjxyrs; + + /** + * 缺课人次 + */ + private Integer qkrc; + + /** + * 缺课人数 + */ + private Integer qkrs; + + /** + * 已请假缺课人次 + */ + private Integer yqjqkrc; + + /** + * 已请假缺课人数 + */ + private Integer yqjqkrs; + + /** + * 未请假缺课人次 + */ + private Integer wqjqkrc; + + /** + * 未请假缺课人数 + */ + private Integer wqjqkrs; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYB.java new file mode 100644 index 000000000..da5e103bf --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYB.java @@ -0,0 +1,130 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教员表 + *

+ * 存储教员的基本信息。 + * 关联教研室表(教研室代号)。 + *

+ */ +@Data +@TableName("教员表") +public class JYB { + + /** 教员编号 */ + @TableId(value = "教员编号", type = IdType.INPUT) + private String jybh; + + /** 教员姓名 */ + @TableField("教员姓名") + private String jyxm; + + /** 教研室代号 */ + @TableField("教研室代号") + private String jysdh; + + /** 备注 */ + @TableField("备注") + private String bz; + + /** 职称 */ + @TableField("职称") + private String zc; + + /** 离职状态 */ + @TableField("离职状态") + private Integer lzzt; + + /** 序号 */ + @TableField("序号") + private String xh; + + /** 专业技术职务等级 */ + @TableField("专业技术职务等级") + private String zyjszwdj; + + /** 虚实类型 */ + @TableField("虚实类型") + private Integer xslx; + + /** 教员类别 */ + @TableField("教员类别") + private String jylb; + + /** 卡号 */ + @TableField("卡号") + private String kh; + + /** 拼音 */ + @TableField("拼音") + private String py; + + /** 在职类别 */ + @TableField("在职类别") + private String zzlb; + + /** 额外允许开班数 */ + @TableField("额外允许开班数") + private Integer ewyxkbs; + + /** 手机号 */ + @TableField("手机号") + private String sjh; + + /** 导师类别 */ + @TableField("导师类别") + private String dslb; + + /** 学缘 */ + @TableField("学缘") + private String xy; + + /** 身份证号 */ + @TableField("身份证号") + private String sfzh; + + /** 性别 */ + @TableField("性别") + private Integer xb; + + /** 外训教员 */ + @TableField("外训教员") + private Integer wxjy; + + /** 外训译员 */ + @TableField("外训译员") + private Integer wxyy; + + /** 政治类教员 */ + @TableField("政治类教员") + private Integer zzljy; + + /** 序号标识 */ + @TableField(value = "序号标识", updateStrategy = FieldStrategy.NEVER) + private Integer xhbs; + + /** json字典 */ + @TableField("JSONZD") + private String jsonzd; + + /** 专业技术职务等级时间 */ + @TableField("专业技术职务等级时间") + private LocalDateTime zyjszwdjsj; + + /** 职称时间 */ + @TableField("职称时间") + private LocalDateTime zcsj; + + /** 上一个教研室代号(异动前归属) */ + @TableField("上一个部别编号") + private String previousDept; + + /** 异动历史JSON数组,每条记录含 from/to/time/reason */ + @TableField("异动历史") + private String transferHistory; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYDLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYDLB.java new file mode 100644 index 000000000..cb133da27 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYDLB.java @@ -0,0 +1,29 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教员登录表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教员登录表") +public class JYDLB extends BaseEntity { + + /** + * 教员编号 + */ + @TableId(value = "教员编号", type = IdType.INPUT) + private String jybh; + + /** + * 登录信息编号 + */ + private String dlxxbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYKSBZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYKSBZ.java new file mode 100644 index 000000000..2738bdc1d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYKSBZ.java @@ -0,0 +1,43 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教员课时补助 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教员课时补助") +public class JYKSBZ extends BaseEntity { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("教员编号") + private String jybh; + + @TableField("补助项目编号") + private String bzxmbh; + + @TableField("补助课时量") + private Float bzksl; + + @TableField("备注") + private String bz; + + @TableField(exist = false) + private LocalDateTime createTime; + @TableField(exist = false) + private LocalDateTime updateTime; + @TableField(exist = false) + private String createBy; + @TableField(exist = false) + private String updateBy; + @TableField(exist = false) + private Integer delFlag; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYL.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYL.java new file mode 100644 index 000000000..3d03d0fe8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYL.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教员历 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教员历") +public class JYL extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 名称 + */ + private String mc; + + /** + * 可排课 + */ + private Integer kpk; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 备注 + */ + private String bz; + + /** + * 教员编号 + */ + private String jybh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYNZKSF.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYNZKSF.java new file mode 100644 index 000000000..c75bdda73 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYNZKSF.java @@ -0,0 +1,113 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 教员年终课时费 + *

+ * 每位教员的年终课时费核算明细。 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教员年终课时费") +public class JYNZKSF extends BaseEntity { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("年终课时费编号") + private String nzksfbh; + + @TableField("教员编号") + private String jybh; + + @TableField("技术职务编号") + private String jszwbh; + + @TableField("绩效评定等级") + private String jxpddj; + + @TableField("上学期课时量") + private Float sxqksl; + + @TableField("下学期课时量") + private Float xxqksl; + + @TableField("上学期实际教学课时量") + private Float sxqsjjxksl; + + @TableField("上学期补助教学课时量") + private Float sxqbzjxksl; + + @TableField("上学期相关教学课时量") + private Float sxqxgjxksl; + + @TableField("上学期不计酬劳课时量") + private Float sxqbjclksl; + + @TableField("下学期实际教学课时量") + private Float xxqsjjxksl; + + @TableField("下学期补助教学课时量") + private Float xxqbzjxksl; + + @TableField("下学期相关教学课时量") + private Float xxqxgjxksl; + + @TableField("下学期不计酬劳课时量") + private Float xxqbjclksl; + + @TableField("教学课时量") + private Float jxksl; + + @TableField("上学期教学课时量") + private Float sxqjxksl; + + @TableField("下学期教学课时量") + private Float xxqjxksl; + + @TableField("总计课时量") + private Float zjksl; + + @TableField("实际教学课时量") + private Float sjjxksl; + + @TableField("补助教学课时量") + private Float bzjxksl; + + @TableField("相关教学课时量") + private Float xgjxksl; + + @TableField("不计酬劳课时量") + private Float bjclksl; + + @TableField("超课时量") + private Float cksl; + + @TableField("计算公式") + private String jsgs; + + @TableField("课时费") + private BigDecimal ksf; + + @TableField("备注") + private String bz; + + @TableField(exist = false) + private LocalDateTime createTime; + @TableField(exist = false) + private LocalDateTime updateTime; + @TableField(exist = false) + private String createBy; + @TableField(exist = false) + private String updateBy; + @TableField(exist = false) + private Integer delFlag; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSB.java new file mode 100644 index 000000000..97970baa2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSB.java @@ -0,0 +1,55 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教研室表 + */ +@Data +@TableName("教研室表") +public class JYSB { + + /** 教研室代号 */ + @TableId(value = "教研室代号", type = IdType.INPUT) + private String jysdh; + + /** 教研室名称 */ + @TableField("教研室名称") + private String jysmc; + + /** 备注 */ + @TableField("备注") + private String bz; + + /** 停用 */ + @TableField("停用") + private Boolean ty; + + /** 停用时间 */ + @TableField("停用时间") + private LocalDateTime tysj; + + /** 序号 */ + @TableField("序号") + private String xh; + + /** 部系 */ + @TableField("部系") + private String bx; + + /** 虚实类型 */ + @TableField("虚实类型") + private Boolean xslx; + + /** 简称 */ + @TableField("简称") + private String jc; + + /** 机关性质 */ + @TableField("机关性质") + private Boolean jgxz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSCKCB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSCKCB.java new file mode 100644 index 000000000..30e5fb76a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSCKCB.java @@ -0,0 +1,41 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教员输出课程表 + *

+ * 存储教员的课程表信息,每年一条记录。 + *

+ */ +@Data +@TableName("教员输出课程表") +public class JYSCKCB { + + /** 年度 */ + @TableField("年度") + private Integer nd; + + /** 教员编号 */ + @TableField("教员编号") + private String jybh; + + /** 课程表(JSON格式) */ + @TableField("课程表") + private String kcb; + + /** 更新时间 */ + @TableField("更新时间") + private LocalDateTime gxsj; + + /** 编号 */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** 有课表 */ + @TableField("有课表") + private Integer ykb; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSDLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSDLB.java new file mode 100644 index 000000000..2573905e3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSDLB.java @@ -0,0 +1,29 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 教研室登录表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教研室登录表") +public class JYSDLB extends BaseEntity { + + /** + * 教研室代号 + */ + @TableId(value = "教研室代号", type = IdType.INPUT) + private String jysdh; + + /** + * 登录信息编号 + */ + private String dlxxbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSRWS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSRWS.java new file mode 100644 index 000000000..c7b983c3c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSRWS.java @@ -0,0 +1,51 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教研室任务书 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName(value = "教研室任务书", schema = "JW") +public class JYSRWS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 教学任务编号 + */ + @TableField("教学任务编号") + private String jxrwbh; + + /** + * 教研室代号 + */ + @TableField("教研室代号") + private String jysdh; + + /** + * 状态 + */ + @TableField("状态") + private String zt; + + /** + * 上报时间 + */ + @TableField("上报时间") + private LocalDateTime sbsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSX.java new file mode 100644 index 000000000..4d3060bcf --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYSX.java @@ -0,0 +1,90 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教员属性 + *

+ * 存储教员的扩展属性信息(职务、技术等级、学历学位等)。 + * 与教员表通过 教员编号 一对一关联。 + *

+ */ +@Data +@TableName("教员属性") +public class JYSX { + + /** 教员编号 */ + @TableId(value = "教员编号") + private String jybh; + + /** 主任 */ + @TableField("主任") + private Integer zr; + + /** 职务 */ + @TableField("职务") + private String zw; + + /** 职务时间 */ + @TableField("职务时间") + private LocalDateTime zwsj; + + /** 技术职务 */ + @TableField("技术职务") + private String jszw; + + /** 技术职务时间 */ + @TableField("技术职务时间") + private LocalDateTime jszwsj; + + /** 技术等级 */ + @TableField("技术等级") + private String jsdj; + + /** 技术等级时间 */ + @TableField("技术等级时间") + private LocalDateTime jsdjsj; + + /** 军衔文职 */ + @TableField("军衔文职") + private String jxwz; + + /** 军衔文职时间 */ + @TableField("军衔文职时间") + private LocalDateTime jxwzsj; + + /** 出生时间 */ + @TableField("出生时间") + private LocalDateTime cssj; + + /** 学历 */ + @TableField("学历") + private String xl; + + /** 学位 */ + @TableField("学位") + private String xw; + + /** 入伍工作时间 */ + @TableField("入伍工作时间") + private LocalDateTime rwgzsj; + + /** 任代职经历 */ + @TableField("任代职经历") + private String rdzjl; + + /** 发表论文 */ + @TableField("发表论文") + private String fblw; + + /** 教龄 */ + @TableField("教龄") + private Integer jl; + + /** 教龄计算日期 */ + @TableField("教龄计算日期") + private LocalDateTime jljsrq; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYZJZGB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYZJZGB.java new file mode 100644 index 000000000..539be357e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JYZJZGB.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 教员主讲资格表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("教员主讲资格表") +public class JYZJZGB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 课编号 + */ + private String kbh; + + /** + * 主讲范围 + */ + private String zjfw; + + /** + * 主讲课题集 + */ + private String zjktj; + + /** + * 备注 + */ + private String bz; + + /** + * 状态 + */ + private String zt; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JZGZL.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JZGZL.java new file mode 100644 index 000000000..1bd7de7ed --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/JZGZL.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 教师工作量 + *

+ * 教师工作量统计主表,记录各类工作量的统计基数和汇总数据。 + *

+ */ +@Data +@TableName("教师工作量") +public class JZGZL { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("教员编号") + private String jybh; + + @TableField("年度") + private Integer nd; + + @TableField("学期") + private String xq; + + /** 课程教学工作量(自动统计) */ + @TableField("课程教学工作量") + private Float kcjxgzl; + + /** 科研学术工作量(导入/人工) */ + @TableField("科研学术工作量") + private Float kyxsgzl; + + /** 指导学员工作量(联教联训/人工) */ + @TableField("指导学员工作量") + private Float zdyxgzl; + + /** 教学建设工作量(人工录入) */ + @TableField("教学建设工作量") + private Float jxjssgzl; + + /** 咨询服务工作量(人工录入) */ + @TableField("咨询服务工作量") + private Float zxfwgzl; + + /** 总工作量 */ + @TableField("总工作量") + private Float zgzl; + + @TableField("创建时间") + private LocalDateTime cjsj; + + @TableField("修改时间") + private LocalDateTime xgsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KB.java new file mode 100644 index 000000000..c28f50590 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KB.java @@ -0,0 +1,218 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课表") +public class KB extends BaseEntity { + + /** + * 课编号 + */ + private String kbh; + + /** + * 课名称 + */ + private String kmc; + + /** + * 教研室代号 + */ + private String jysdh; + + /** + * 备注 + */ + private String bz; + + /** + * 序号 + */ + private String xh; + + /** + * 学分 + */ + private Float xf; + + /** + * 简称 + */ + private String jc; + + /** + * 学时 + */ + private Integer xs; + + /** + * 培训层次 + */ + private String pxcc; + + /** + * 培训类型 + */ + private String pxlx; + + /** + * 课程类型 + */ + private String kclx; + + /** + * 考试课时 + */ + private Integer ksks; + + /** + * 拼音 + */ + private String py; + + /** + * 规范名称 + */ + private String gfmc; + + /** + * 成绩分制 + */ + private String cjfz; + + /** + * 不计入学员平均分 + */ + private Integer bjrxypjf; + + /** + * 教学管理部门编号 + */ + private String jxglbmbh; + + /** + * 理论学时 + */ + private Integer llxs; + + /** + * 实践学时 + */ + private Integer sjxs; + + /** + * 科目代码 + */ + private String kmdm; + + /** + * 周课时 + */ + private Integer zks; + + /** + * 课类型 + */ + private String klx; + + /** + * 考试课时不显示 + */ + private Integer ksksbxs; + + /** + * 课程统一编号 + */ + private String kctybh; + + /** + * 适用对象 + */ + private String sydx; + + /** + * 辅导答疑课时 + */ + private Integer fddyks; + + /** + * 自修课时 + */ + private Integer zxks; + + /** + * 版本 + */ + private String bb; + + /** + * 修订日期 + */ + private LocalDateTime xdrq; + + /** + * 序号标识 + */ + private Integer xhbs; + + /** + * 节次优选级 + */ + private Integer jcyxj; + + /** + * 课程支持方式 + */ + private String kczcfs; + + /** + * 课程建设方式 + */ + private String kcjsfs; + + /** + * json字典 + */ + private String jsonzd; + + /** + * 政治类课程 + */ + private Integer zzlkc; + + /** + * 考核方式 + */ + private String khfs; + + /** + * 形成性成绩分值 + */ + private Float xcxcjfz; + + /** + * 终结性成绩分值 + */ + private Float zjxcjfz; + + /** + * 密级 + */ + private String mj; + + /** + * 评选情况 + */ + private String pxqk; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCB.java new file mode 100644 index 000000000..11ae6afd9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCB.java @@ -0,0 +1,93 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 课程表 + */ +@Data +@TableName(value = "课程表", schema = "JW") +public class KCB { + + /** + * 年度 + */ + @TableField("年度") + private Integer nd; + + /** + * 安排时间 + */ + @TableField("安排时间") + private Integer apsj; + + /** + * 教员编号 + */ + @TableField("教员编号") + private String jybh; + + /** + * 课次序号 + */ + @TableField("课次序号") + private Integer kcxh; + + /** + * 教室编号 + */ + @TableField("教室编号") + private String jsbh; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 创建者名称 + */ + @TableField("创建者名称") + private String cjzmc; + + /** + * 授课教员编号 + */ + @TableField("授课教员编号") + private String skjybh; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("创建时间") + private LocalDateTime cjsj; + + /** + * 变动时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("变动时间") + private LocalDateTime bdsj; + + /** + * 节次调节 + */ + @TableField("节次调节") + private Integer jctj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBBZMX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBBZMX.java new file mode 100644 index 000000000..32f47d5a3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBBZMX.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课程表_保障明细 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课程表_保障明细") +public class KCBBZMX extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 课程表编号 + */ + private String kcbbh; + + /** + * 保障提供明细编号 + */ + private String bztgmxbh; + + /** + * 数量 + */ + private Integer sl; + + /** + * 备注 + */ + private String bz; + + /** + * 年度 + */ + private Integer nd; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBMBB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBMBB.java new file mode 100644 index 000000000..d8be705af --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBMBB.java @@ -0,0 +1,256 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课程表模版表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课程表模版表") +public class KCBMBB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 模版名称 + */ + private String mBMC; + + /** + * 标题位置 + */ + private String bTWZ; + + /** + * 专业信息位置 + */ + private String zYXXWZ; + + /** + * 课表时间行号 + */ + private String kBSJHH; + + /** + * 显示夜间 + */ + private Boolean xSYJ; + + /** + * 末尾列位置 + */ + private String mWLWZ; + + /** + * 课程行位置 + */ + private String kCHWZ; + + /** + * 课程序号列位置 + */ + private String kCXHLWZ; + + /** + * 课程名称列位置 + */ + private String kCMCLWZ; + + /** + * 课程简称列位置 + */ + private String kCJCLWZ; + + /** + * 课程学时列位置 + */ + private String kCXSLWZ; + + /** + * 课程类型列位置 + */ + private String kCLXLWZ; + + /** + * 课程责任教研室列位置 + */ + private String kCZRJYSLWZ; + + /** + * 课程责任教员列位置 + */ + private String kCZRJYLWZ; + + /** + * 课程授课教员列位置 + */ + private String kCSKJYLWZ; + + /** + * 课程人数列位置 + */ + private String kCRSLWZ; + + /** + * 课程场地列位置 + */ + private String kCCDLWZ; + + /** + * 全局备注位置 + */ + private String qJBZWZ; + + /** + * 更新时间 + */ + private LocalDateTime gXSJ; + + /** + * 上传者 + */ + private String sCZ; + + /** + * 备注 + */ + private String bZ; + + /** + * 模版 + */ + private String mB; + + /** + * 模版存在 + */ + private Boolean mBCZ; + + /** + * 模版可用 + */ + private Boolean mBKY; + + /** + * 备注纵向样式 + */ + private Boolean bZZXYS; + + /** + * 无课站位字符 + */ + private String wKZWZF; + + /** + * 队别位置 + */ + private String dBWZ; + + /** + * 课程合班信息位置 + */ + private String kCHBXXWZ; + + /** + * 标题格式 + */ + private String bTGS; + + /** + * 副标题格式 + */ + private String fBTGS; + + /** + * 专用教室位置 + */ + private String zYJSWZ; + + /** + * 附加备注1位置 + */ + private String fJBZ1WZ; + + /** + * 附加备注2位置 + */ + private String fJBZ2WZ; + + /** + * 课表样式 + */ + private String kBYS; + + /** + * 课表包含选修 + */ + private Boolean kBBHXX; + + /** + * 纵向显示78节 + */ + private Boolean zXXS78J; + + /** + * 纵向显示晚上 + */ + private Boolean zXXSWS; + + /** + * 纵向显示天数 + */ + private Integer zXXSTS; + + /** + * 课程全部场地列位置 + */ + private String kCQBCDLWZ; + + /** + * 课程表样式 + */ + private String kCBYS; + + /** + * 课程主讲教员列位置 + */ + private String kCZJJYLWZ; + + /** + * 课程辅导教员列位置 + */ + private String kCFDJYLWZ; + + /** + * 课程周次范围列位置 + */ + private String kCZCFWLWZ; + + /** + * 课程表起始列位置 + */ + private String kCBQSLWZ; + + /** + * 课程表一周正课天数 + */ + private Integer kCBYZZKTS; + + /** + * json字典 + */ + private String jsonZD; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZ.java new file mode 100644 index 000000000..9cc946d38 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZ.java @@ -0,0 +1,80 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.util.List; + +/** + * 课程标准 + *

+ * 课程标准的版本管理,同一课程(课编号)可有多条记录作为不同版本。 + *

+ */ +@Data +@TableName("课程标准") +public class KCBZ { + + /** 编号 */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** 课编号(关联课程表) */ + @TableField("课编号") + private String kbh; + + /** 名称 */ + @TableField("名称") + private String mc; + + /** 适用专业 */ + @TableField("适用专业") + private String syzy; + + /** 课次数 */ + @TableField("课次数") + private Integer kcs; + + /** 备注 */ + @TableField("备注") + private String bz; + + /** 执行人编号 */ + @TableField("执行人编号") + private String zxrbh; + + /** 执行时间 */ + @TableField("执行时间") + private String zxsj; + + /** 执行状态 */ + @TableField("执行状态") + private String zxzt; + + /** 上报状态 */ + @TableField("上报状态") + private String sbzt; + + /** 上报时间 */ + @TableField("上报时间") + private String sbsj; + + /** 上报人编号 */ + @TableField("上报人编号") + private String sbrbh; + + /** 撤回原因 */ + @TableField("撤回原因") + private String chyy; + + /** 正文 */ + @TableField("正文") + private String zw; + + /** JSONZD */ + @TableField("JSONZD") + private String jsonzd; +//课堂标准 + @TableField(exist = false) + List ktList; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZFJ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZFJ.java new file mode 100644 index 000000000..27e911236 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZFJ.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课程标准附件 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课程标准附件") +public class KCBZFJ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 课程标准编号 + */ + private String kcbzbh; + + /** + * 文档数据 + */ + private String wdsj; + + /** + * 文档名称 + */ + private String wdmc; + + /** + * MIME类型 + */ + private String mimelx; + + /** + * 备注 + */ + private String bz; + + /** + * 创建日期 + */ + private LocalDateTime cjrq; + + /** + * 序号 + */ + private String xh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZSP.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZSP.java new file mode 100644 index 000000000..b63305a7c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZSP.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课程标准审批 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课程标准审批") +public class KCBZSP extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 课程标准编号 + */ + private String kcbzbh; + + /** + * 机关编号 + */ + private String jgbh; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 审批时间 + */ + private LocalDateTime spsj; + + /** + * 审批状态 + */ + private Integer spzt; + + /** + * 审批人编号 + */ + private String sprbh; + + /** + * 发回意见 + */ + private String fhyj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZXX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZXX.java new file mode 100644 index 000000000..239c73979 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCBZXX.java @@ -0,0 +1,64 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 课程表中小学 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课程表中小学") +public class KCBZXX extends BaseEntity { + + /** + * 年度 + */ + private Integer nd; + + /** + * 周天 + */ + private Integer zt; + + /** + * 节次 + */ + private Integer jc; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 课次序号 + */ + private Integer kcxh; + + /** + * 教室编号 + */ + private String jsbh; + + /** + * 课编号 + */ + private String kbh; + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 预排 + */ + private Integer yp; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCCJ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCCJ.java new file mode 100644 index 000000000..ef569f5b9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCCJ.java @@ -0,0 +1,136 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课程成绩 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课程成绩") +public class KCCJ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 登记教员编号 + */ + private String djjybh; + + /** + * 上报状态 + */ + private Integer sbzt; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 上报时间 + */ + private LocalDateTime sbsj; + + /** + * 核准状态 + */ + private Integer hzzt; + + /** + * 核准人编号 + */ + private String hzrbh; + + /** + * 核准时间 + */ + private LocalDateTime hzsj; + + /** + * 成绩说明 + */ + private String cjsm; + + /** + * 备注 + */ + private String bz; + + /** + * 形成性成绩分值 + */ + private Float xcxcjfz; + + /** + * 终结性成绩分值 + */ + private Float zjxcjfz; + + /** + * 成绩分析 + */ + private String cjfx; + + /** + * 问题建议 + */ + private String wtjy; + + /** + * 终结成绩定及格 + */ + private Integer zjcjdjg; + + /** + * 成绩分制 + */ + private String cjfz; + + /** + * 不计入学员平均分 + */ + private Integer bjrxypjf; + + /** + * 发布状态 + */ + private Integer fbzt; + + /** + * 教研室主任编号 + */ + private String jyszrbh; + + /** + * 录完状态 + */ + private Integer lwzt; + + /** + * 退回状态 + */ + private Integer thzt; + + /** + * 退回意见 + */ + private String thyj; + + /** + * 年度 + */ + private Integer nd; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCGCCJ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCGCCJ.java new file mode 100644 index 000000000..783155497 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCGCCJ.java @@ -0,0 +1,101 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课程过程成绩 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课程过程成绩") +public class KCGCCJ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 成绩名称 + */ + private String cjmc; + + /** + * 课程编号 + */ + private String kcbh; + + /** + * 登记教员编号 + */ + private String djjybh; + + /** + * 测试时间 + */ + private LocalDateTime cssj; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 成绩说明 + */ + private String cjsm; + + /** + * 备注 + */ + private String bz; + + /** + * 原始成绩单位 + */ + private String yscjdw; + + /** + * 成绩分析 + */ + private String cjfx; + + /** + * 问题建议 + */ + private String wtjy; + + /** + * 成绩分制 + */ + private String cjfz; + + /** + * 录入完成状态 + */ + private Integer lrwczt; + + /** + * 成绩类别 + */ + private String cjlb; + + /** + * 成绩占比 + */ + private Float cjzb; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCJC.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCJC.java new file mode 100644 index 000000000..c4afb2a31 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCJC.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课程教材 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课程教材") +public class KCJC extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 课编号 + */ + private String kbh; + + /** + * 教材编号 + */ + private String jcbh; + + /** + * 教材选用说明 + */ + private String jcxysm; + + /** + * 备注 + */ + private String bz; + + /** + * 停用 + */ + private Integer ty; + + /** + * 停用日期 + */ + private LocalDateTime tyrq; + + /** + * 教材性质 + */ + private String jcxz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCJXYX_CZRZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCJXYX_CZRZ.java new file mode 100644 index 000000000..6e90e0d18 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KCJXYX_CZRZ.java @@ -0,0 +1,75 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课程教学运行_操作日志 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课程教学运行_操作日志") +public class KCJXYX_CZRZ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 统一编号 + */ + @TableField("统一编号") + private String tybh; + + /** + * 操作类型 + */ + @TableField("操作类型") + private String czlx; + + /** + * 操作内容 + */ + @TableField("操作内容") + private String cznr; + + /** + * 操作人编号 + */ + @TableField("操作人编号") + private String czrbh; + + /** + * 操作时间 + */ + @TableField("操作时间") + private LocalDateTime czsj; + + /** + * 操作IP + */ + @TableField("操作IP") + private String czip; + + /** + * 原数据快照 + */ + @TableField("原数据快照") + private String ysjkz; + + /** + * 新数据快照 + */ + @TableField("新数据快照") + private String xsjkz; + +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KLXB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KLXB.java new file mode 100644 index 000000000..89a0c372b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KLXB.java @@ -0,0 +1,26 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 课类型表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课类型表") +public class KLXB extends BaseEntity { + + /** + * 课类型 + */ + private String kLX; + + /** + * 备注 + */ + private String bZ; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSB.java new file mode 100644 index 000000000..95bfce42f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSB.java @@ -0,0 +1,49 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 考试表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("考试表") +public class KSB extends BaseEntity { + + /** + * 考试编号 + */ + @TableId(value = "考试编号", type = IdType.INPUT) + private String kSBH; + + /** + * 年度 + */ + private Integer nD; + + /** + * 课编号 + */ + private String kBH; + + /** + * 简称 + */ + private String jC; + + /** + * 考试时间 + */ + private Integer kSSJ; + + /** + * 备注 + */ + private String bZ; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSBZXM.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSBZXM.java new file mode 100644 index 000000000..5e7f92cf4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSBZXM.java @@ -0,0 +1,91 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课时补助项目 + *

+ * 课时补助项目定义,区分联教联训等不同补助类型。 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课时补助项目") +public class KSBZXM extends BaseEntity { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("名称") + private String mc; + + @TableField("性质") + private String xz; + + @TableField("依据") + private String yj; + + @TableField("开始日期") + private LocalDateTime ksrq; + + @TableField("结束日期") + private LocalDateTime jsrq; + + @TableField("天数") + private Float ts; + + @TableField("基本课时量") + private Float jbksl; + + @TableField("课时类型") + private String kslx; + + @TableField("说明") + private String sm; + + @TableField("备注") + private String bz; + + @TableField("创建时间") + private LocalDateTime cjsj; + + @TableField("修改时间") + private LocalDateTime xgsj; + + @TableField("提交人账号") + private String tjrzh; + + @TableField("提交状态") + private Integer tjzt; + + @TableField("提交时间") + private LocalDateTime tjsj; + + @TableField("审批人编号") + private String sprbh; + + @TableField("审批状态") + private Integer spzt; + + @TableField("审批时间") + private LocalDateTime spsj; + + @TableField("项目类型") + private String xmlx; + + @TableField(exist = false) + private LocalDateTime createTime; + @TableField(exist = false) + private LocalDateTime updateTime; + @TableField(exist = false) + private String createBy; + @TableField(exist = false) + private String updateBy; + @TableField(exist = false) + private Integer delFlag; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSFBZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSFBZ.java new file mode 100644 index 000000000..5918fe013 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSFBZ.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 课时费标准(课时核算标准) + *

+ * 定义课时费核算的基准参数,包括各种绩效等级对应的补助标准。 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课时费标准") +public class KSFBZ extends BaseEntity { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("名称") + private String mc; + + @TableField("默认标准课时量") + private Double mrbzksl; + + @TableField("默认课时费标准") + private Double mrksfbz; + + @TableField("默认超量课时费标准") + private Double mrclksfbz; + + @TableField("相关教学补助课时费标准") + private Double xgjxbzksfbz; + + @TableField("绩效优秀补助") + private Double jxyxbz; + + @TableField("绩效良好补助") + private Double jxlhbz; + + @TableField("绩效中等补助") + private Double jxzdbz; + + @TableField("绩效及格补助") + private Double jxjgbz; + + @TableField("绩效不及格补助") + private Double jxbjgbz; + + @TableField("说明") + private String sm; + + @TableField("备注") + private String bz; + + @TableField("创建时间") + private LocalDateTime cjsj; + + @TableField("修改时间") + private LocalDateTime xgsj; + + @TableField(exist = false) + private LocalDateTime createTime; + @TableField(exist = false) + private LocalDateTime updateTime; + @TableField(exist = false) + private String createBy; + @TableField(exist = false) + private String updateBy; + @TableField(exist = false) + private Integer delFlag; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSHBZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSHBZ.java new file mode 100644 index 000000000..98184725f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSHBZ.java @@ -0,0 +1,53 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 课时核算标准 + *

+ * 定义课时核算的各项系数,包括主讲/辅讲教师课时系数、讲授方式系数等。 + *

+ */ +@Data +@TableName("课时核算标准") +public class KSHBZ { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("名称") + private String mc; + + /** 主讲教师课时系数 */ + @TableField("主讲教师课时系数") + private Float zjjyksxs; + + /** 辅讲教师课时系数 */ + @TableField("辅讲教师课时系数") + private Float fjjyksxs; + + /** 讲授方式系数(JSON格式:{"讲授":1.0,"实验":0.8,"实践":0.6}) */ + @TableField("讲授方式系数") + private String jsfsxs; + + /** 考试主讲系数 */ + @TableField("考试主讲系数") + private Float kszjxs; + + /** 考试指导教师系数 */ + @TableField("考试指导教师系数") + private Float kszdjsxs; + + @TableField("说明") + private String sm; + + @TableField("创建时间") + private LocalDateTime cjsj; + + @TableField("修改时间") + private LocalDateTime xgsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSTJB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSTJB.java new file mode 100644 index 000000000..254350686 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSTJB.java @@ -0,0 +1,44 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 课时统计表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课时统计表") +public class KSTJB extends BaseEntity { + + /** + * 年份 + */ + private Integer nF; + + /** + * 教员编号 + */ + @TableId(value = "教员编号", type = IdType.INPUT) + private String jYBH; + + /** + * 上半年 + */ + private Double sBN; + + /** + * 下半年 + */ + private Double xBN; + + /** + * 职称 + */ + private String zC; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSXSFA.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSXSFA.java new file mode 100644 index 000000000..d958f678f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KSXSFA.java @@ -0,0 +1,117 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 课时系数方案 + */ +@Data +@TableName("课时系数方案") +public class KSXSFA { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 名称 + */ + @TableField("名称") + private String mc; + + /** + * 说明 + */ + @TableField("说明") + private String sm; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("创建时间") + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("修改时间") + private LocalDateTime xgsj; + + /** + * 辅讲课时系数 + */ + @TableField("辅讲课时系数") + private Double fjksxs; + + /** + * 满班学员人数 + */ + @TableField("满班学员人数") + private Integer mbxyrs; + + /** + * 递增10学员班级系数 + */ + @TableField("递增10学员班级系数") + private Double dz10xybjxs; + + /** + * 班级系数上限 + */ + @TableField("班级系数上限") + private Double bjxssx; + + /** + * 第78节课时量 + */ + @TableField("第78节课时量") + private Double d78jksl; + + /** + * 夜间课时量 + */ + @TableField("夜间课时量") + private Double yjksl; + + /** + * 主讲课时系数 + */ + @TableField("主讲课时系数") + private Double zjksxs; + + /** + * 默认课堂教学方法系数 + */ + @TableField("默认课堂教学方法系数") + private Double mrktjxffxs; + + /** + * 合班系数增量 + */ + @TableField("合班系数增量") + private Double hbxszl; + + /** + * 合班系数上限 + */ + @TableField("合班系数上限") + private Double hbxssx; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTBZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTBZ.java new file mode 100644 index 000000000..e12396d4e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTBZ.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +/** + * 课堂标准 + *

+ * 课程标准下的每一次课的详细内容。 + *

+ */ +@Data +@TableName("课堂标准") +public class KTBZ { + + /** 编号 */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** 课程标准编号(关联课程标准.编号) */ + @TableField("课程标准编号") + private String kcbh; + + /** 课堂节次 */ + @TableField("课堂节次") + private Integer ktjc; + + /** 教学内容 */ + @TableField("教学内容") + private String jxnr; + + /** 教学要点 */ + @TableField("教学要点") + private String jxyd; + + /** 教学方法 */ + @TableField("教学方法") + private String jxff; + + /** 主讲教员数 */ + @TableField("主讲教员数") + private Integer zjjys; + + /** 辅讲教员数 */ + @TableField("辅讲教员数") + private Integer fjjys; + + /** 备注 */ + @TableField("备注") + private String bz; + + /** 教学目的 */ + @TableField("教学目的") + private String jxmd; + + /** 教学要求 */ + @TableField("教学要求") + private String jxyq; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTJXFF.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTJXFF.java new file mode 100644 index 000000000..a45a302af --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTJXFF.java @@ -0,0 +1,51 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 课堂教学方法 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课堂教学方法") +public class KTJXFF extends BaseEntity { + + /** + * 名称 + */ + private String mC; + + /** + * 日课表序号 + */ + private Integer rKBXH; + + /** + * 显示颜色 + */ + private String xSYS; + + /** + * 背景颜色 + */ + private String bJYS; + + /** + * 考核 + */ + private Boolean kH; + + /** + * 讲授方式 + */ + private String jSFS; + + /** + * 需审批 + */ + private Boolean xSP; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTJXFFKSXS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTJXFFKSXS.java new file mode 100644 index 000000000..2144980a4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTJXFFKSXS.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 课堂教学方法课时系数 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课堂教学方法课时系数") +public class KTJXFFKSXS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 方案编号 + */ + private String fabh; + + /** + * 课堂教学方法名称 + */ + private String ktjxffmc; + + /** + * 系数 + */ + private Float xs; + + /** + * 不计合班系数 + */ + private Integer bjhbxs; + + /** + * 不计班级人数系数 + */ + private Integer bjbjrsxs; + + /** + * 备注 + */ + private String bz; + + /** + * 按天记课时量 + */ + private Float atjksl; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTRZXYKQB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTRZXYKQB.java new file mode 100644 index 000000000..d1ff829ab --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KTRZXYKQB.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 课堂日志学员考勤表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("课堂日志学员考勤表") +public class KTRZXYKQB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 日志编号 + */ + private String rzbh; + + /** + * 学员编号 + */ + private String xybh; + + /** + * 学习情况 + */ + private String xxqk; + + /** + * 到课情况 + */ + private Integer dkqk; + + /** + * 请示情况 + */ + private Integer qsqk; + + /** + * 备注 + */ + private String bz; + + /** + * 学习效果 + */ + private String xxxg; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKJJXSBB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKJJXSBB.java new file mode 100644 index 000000000..153fdce07 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKJJXSBB.java @@ -0,0 +1,136 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * KY_科技奖项申报表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("KY_科技奖项申报表") +public class KYKJJXSBB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 申报年度 + */ + private Integer sbnd; + + /** + * 属军事类 + */ + private Integer sjsl; + + /** + * 申请日期 + */ + private LocalDateTime sqrq; + + /** + * 立项日期 + */ + private LocalDateTime lxrq; + + /** + * 中检日期 + */ + private LocalDateTime zjrq; + + /** + * 结题日期 + */ + private LocalDateTime jtrq; + + /** + * 状态 + */ + private String zt; + + /** + * 项目类别 + */ + private String xmlb; + + /** + * 项目级别 + */ + private String xmjb; + + /** + * 项目分类 + */ + private String xmfl; + + /** + * 项目性质 + */ + private String xmxz; + + /** + * 项目来源 + */ + private String xmly; + + /** + * 经费 + */ + private Float jf; + + /** + * 立项申报管理编号 + */ + private String lxsbglbh; + + /** + * 中期申报管理编号 + */ + private String zqsbglbh; + + /** + * 验收申报管理编号 + */ + private String yssbglbh; + + /** + * 立项撤销理由 + */ + private String lxcxly; + + /** + * 项目立项编号 + */ + private String xmlxbh; + + /** + * 中检撤销理由 + */ + private String zjcxly; + + /** + * 项目中检编号 + */ + private String xmzjbh; + + /** + * 验收撤销理由 + */ + private String yscxly; + + /** + * 项目验收编号 + */ + private String xmysbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKYCGB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKYCGB.java new file mode 100644 index 000000000..0b04eda6e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKYCGB.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * KY_科研成果表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("KY_科研成果表") +public class KYKYCGB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 属军事类 + */ + private Integer sjsl; + + /** + * 成果类别 + */ + private String cglb; + + /** + * 奖项名称 + */ + private String jxmc; + + /** + * 奖励等级 + */ + private String jldj; + + /** + * 奖励等次 + */ + private String jldc; + + /** + * 人员排名 + */ + private String rypm; + + /** + * 状态 + */ + private String zt; + + /** + * 获奖日期 + */ + private LocalDateTime hjrq; + + /** + * 科技申报编号 + */ + private String kjsbbh; + + /** + * 撤销理由 + */ + private String cxly; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKYJFB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKYJFB.java new file mode 100644 index 000000000..158e02c1f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKYJFB.java @@ -0,0 +1,106 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * KY_科研经费表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("KY_科研经费表") +public class KYKYJFB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 名称 + */ + private String mC; + + /** + * 经费来源 + */ + private String jFLY; + + /** + * 经费负责人 + */ + private String jFFZR; + + /** + * 下拨经费 + */ + private Double xBJF; + + /** + * 下拨时间 + */ + private LocalDateTime xBSJ; + + /** + * 用途 + */ + private String yT; + + /** + * 是否为专项经费 + */ + private Boolean sFWZXJF; + + /** + * 计划完成日期 + */ + private LocalDateTime jHWCRQ; + + /** + * 实际完成日期 + */ + private LocalDateTime sJWCRQ; + + /** + * 已使用经费 + */ + private Double ySYJF; + + /** + * 剩余经费 + */ + private Double sYJF; + + /** + * 创建时间 + */ + private LocalDateTime cJSJ; + + /** + * 修改时间 + */ + private LocalDateTime xGSJ; + + /** + * 备注 + */ + private String bZ; + + /** + * 经费变动记录 + */ + private String jFBDJL; + + /** + * 是否完成 + */ + private Boolean sFWC; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKYXMB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKYXMB.java new file mode 100644 index 000000000..bb3bf06c5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYKYXMB.java @@ -0,0 +1,136 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * KY_科研项目表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("KY_科研项目表") +public class KYKYXMB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 申报年度 + */ + private Integer sbnd; + + /** + * 属军事类 + */ + private Integer sjsl; + + /** + * 申请日期 + */ + private LocalDateTime sqrq; + + /** + * 立项日期 + */ + private LocalDateTime lxrq; + + /** + * 中检日期 + */ + private LocalDateTime zjrq; + + /** + * 结题日期 + */ + private LocalDateTime jtrq; + + /** + * 状态 + */ + private String zt; + + /** + * 项目类别 + */ + private String xmlb; + + /** + * 项目级别 + */ + private String xmjb; + + /** + * 项目分类 + */ + private String xmfl; + + /** + * 项目性质 + */ + private String xmxz; + + /** + * 项目来源 + */ + private String xmly; + + /** + * 经费 + */ + private Float jf; + + /** + * 立项申报管理编号 + */ + private String lxsbglbh; + + /** + * 中期申报管理编号 + */ + private String zqsbglbh; + + /** + * 验收申报管理编号 + */ + private String yssbglbh; + + /** + * 立项撤销理由 + */ + private String lxcxly; + + /** + * 项目立项编号 + */ + private String xmlxbh; + + /** + * 中检撤销理由 + */ + private String zjcxly; + + /** + * 项目中检编号 + */ + private String xmzjbh; + + /** + * 验收撤销理由 + */ + private String yscxly; + + /** + * 项目验收编号 + */ + private String xmysbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYSBGLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYSBGLB.java new file mode 100644 index 000000000..d12898dea --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYSBGLB.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * KY_申报管理表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("KY_申报管理表") +public class KYSBGLB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 申报年度 + */ + private Integer sbnd; + + /** + * 要求 + */ + private String yq; + + /** + * 开始日期 + */ + private LocalDateTime ksrq; + + /** + * 结束日期 + */ + private LocalDateTime jsrq; + + /** + * 申报管理状态 + */ + private String sbglzt; + + /** + * 申报管理类别 + */ + private String sbgllb; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYSYSJSB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYSYSJSB.java new file mode 100644 index 000000000..51e1b6351 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYSYSJSB.java @@ -0,0 +1,74 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * KY_实验室建设表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("KY_实验室建设表") +public class KYSYSJSB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 申报年度 + */ + private Integer sbnd; + + /** + * 建设内容 + */ + private String jsnr; + + /** + * 经费 + */ + private Float jf; + + /** + * 实验室建设状态 + */ + private String sysjszt; + + /** + * 申报管理编号 + */ + private String sbglbh; + + /** + * 立项撤销理由 + */ + private String lxcxly; + + /** + * 实验室申报编号 + */ + private String syssbbh; + + /** + * 验收撤销理由 + */ + private String yscxly; + + /** + * 验收管理编号 + */ + private String ysglbh; + + /** + * 实验室验收编号 + */ + private String sysysbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYXSJLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYXSJLB.java new file mode 100644 index 000000000..40a2cb685 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYXSJLB.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * KY_学术交流表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("KY_学术交流表") +public class KYXSJLB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 会务组分工 + */ + private String hwzfg; + + /** + * 会务费 + */ + private Float hwf; + + /** + * 参会人员名单 + */ + private String chrymd; + + /** + * 会议开始时间 + */ + private LocalDateTime hykssj; + + /** + * 会议结束时间 + */ + private LocalDateTime hyjssj; + + /** + * 会议地点 + */ + private String hydd; + + /** + * 会议级别 + */ + private String hyjb; + + /** + * 状态 + */ + private String zt; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYZDKB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYZDKB.java new file mode 100644 index 000000000..c9eda26c1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/KYZDKB.java @@ -0,0 +1,96 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * KY_制度库表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("KY_制度库表") +public class KYZDKB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 序号 + */ + private String xh; + + /** + * 分类目录 + */ + private String flml; + + /** + * 名称 + */ + private String mc; + + /** + * 文件编号 + */ + private String wjbh; + + /** + * 关键字 + */ + private String gjz; + + /** + * 摘要 + */ + private String zy; + + /** + * 来源 + */ + private String ly; + + /** + * 发布时间 + */ + private LocalDateTime fbsj; + + /** + * 文号 + */ + private String wh; + + /** + * 级别 + */ + private String jb; + + /** + * 有效标志 + */ + private Integer yxbz; + + /** + * 备注 + */ + private String bz; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LBSBXXB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LBSBXXB.java new file mode 100644 index 000000000..c03d63a21 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LBSBXXB.java @@ -0,0 +1,124 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 录播设备信息表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("录播设备信息表") +public class LBSBXXB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 教室编号 + */ + private String jsbh; + + /** + * 设备标识 + */ + private String sbbs; + + /** + * 设备品牌 + */ + private String sbpp; + + /** + * 设备类型 + */ + private String sblx; + + /** + * 设备版本 + */ + private String sbbb; + + /** + * 设备状态 + */ + private String sbzt; + + /** + * 设备UserName + */ + private String sbusername; + + /** + * 设备Password + */ + private String sbpassword; + + /** + * 设备IP + */ + private String sbip; + + /** + * 设备端口 + */ + private Integer sbdk; + + /** + * 视频路数 + */ + private String spls; + + /** + * 设备参数1 + */ + private String sbcs1; + + /** + * 设备参数2 + */ + private String sbcs2; + + /** + * 设备参数3 + */ + private String sbcs3; + + /** + * 设备参数4 + */ + private String sbcs4; + + /** + * 设备参数5 + */ + private String sbcs5; + + /** + * 设备参数6 + */ + private String sbcs6; + + /** + * 资源管理平台编号 + */ + private String zyglptbh; + + /** + * 直播地址集 + */ + private String zbdzj; + + /** + * json字典 + */ + private String jsonzd; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LBZYGLPT.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LBZYGLPT.java new file mode 100644 index 000000000..352cfc424 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LBZYGLPT.java @@ -0,0 +1,109 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 录播资源管理平台 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("录播资源管理平台") +public class LBZYGLPT extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 平台标识 + */ + private String pTBS; + + /** + * 平台品牌 + */ + private String pTPP; + + /** + * 平台类型 + */ + private String pTLX; + + /** + * 平台版本 + */ + private String pTBB; + + /** + * 平台状态 + */ + private String pTZT; + + /** + * 平台UserName + */ + private String pTUserName; + + /** + * 平台Password + */ + private String pTPassword; + + /** + * 平台IP + */ + private String pTIP; + + /** + * 平台端口 + */ + private Integer pTDK; + + /** + * 平台参数1 + */ + private String pTCS1; + + /** + * 平台参数2 + */ + private String pTCS2; + + /** + * 平台参数3 + */ + private String pTCS3; + + /** + * 平台参数4 + */ + private String pTCS4; + + /** + * 平台参数5 + */ + private String pTCS5; + + /** + * 平台参数6 + */ + private String pTCS6; + + /** + * 资源平台独立完成录播 + */ + private Boolean zYPTDLWCLB; + + /** + * json字典 + */ + private String jsonZD; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LDGCKCPMB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LDGCKCPMB.java new file mode 100644 index 000000000..9484b135c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LDGCKCPMB.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 领导跟查课_测评模板 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("领导跟查课_测评模板") +public class LDGCKCPMB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 名称 + */ + private String mc; + + /** + * 文件编号 + */ + private String wjbh; + + /** + * 停用状态 + */ + private Integer tyzt; + + /** + * 备注 + */ + private String bz; + + /** + * 停用时间 + */ + private LocalDateTime tysj; + + /** + * 通告内容 + */ + private String tgnr; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LLB.java new file mode 100644 index 000000000..f270773ce --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/LLB.java @@ -0,0 +1,286 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 履历表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("履历表") +public class LLB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 登录信息编号 + */ + private String dlxxbh; + + /** + * 目录 + */ + private String ml; + + /** + * 名称 + */ + private String mc; + + /** + * 签发单位 + */ + private String qfdw; + + /** + * 类型1 + */ + private String lx1; + + /** + * 类型2 + */ + private String lx2; + + /** + * 类型3 + */ + private String lx3; + + /** + * 类型4 + */ + private String lx4; + + /** + * 类型5 + */ + private String lx5; + + /** + * 文本1 + */ + private String wb1; + + /** + * 文本2 + */ + private String wb2; + + /** + * 文本3 + */ + private String wb3; + + /** + * 文本4 + */ + private String wb4; + + /** + * 文本5 + */ + private String wb5; + + /** + * 文本6 + */ + private String wb6; + + /** + * 长文本1 + */ + private String cwb1; + + /** + * 长文本2 + */ + private String cwb2; + + /** + * 长文本3 + */ + private String cwb3; + + /** + * 长文本4 + */ + private String cwb4; + + /** + * 日期1 + */ + private LocalDateTime rq1; + + /** + * 日期2 + */ + private LocalDateTime rq2; + + /** + * 日期3 + */ + private LocalDateTime rq3; + + /** + * 日期4 + */ + private LocalDateTime rq4; + + /** + * 整数1 + */ + private Integer zs1; + + /** + * 整数2 + */ + private Integer zs2; + + /** + * 整数3 + */ + private Integer zs3; + + /** + * 整数4 + */ + private Integer zs4; + + /** + * 整数5 + */ + private Integer zs5; + + /** + * 浮点数1 + */ + private Float fds1; + + /** + * 浮点数2 + */ + private Float fds2; + + /** + * 浮点数3 + */ + private Float fds3; + + /** + * 浮点数4 + */ + private Float fds4; + + /** + * 浮点数5 + */ + private Float fds5; + + /** + * 布尔1 + */ + private Integer be1; + + /** + * 布尔2 + */ + private Integer be2; + + /** + * 布尔3 + */ + private Integer be3; + + /** + * 布尔4 + */ + private Integer be4; + + /** + * 布尔5 + */ + private Integer be5; + + /** + * 说明 + */ + private String sm; + + /** + * 开始日期 + */ + private LocalDateTime ksrq; + + /** + * 结束日期 + */ + private LocalDateTime jsrq; + + /** + * 备注 + */ + private String bz; + + /** + * 审核状态 + */ + private String shzt; + + /** + * 审核人员编号 + */ + private String shrybh; + + /** + * 审核部门编号 + */ + private String shbmbh; + + /** + * 审核时间 + */ + private LocalDateTime shsj; + + /** + * 照片文件1编号 + */ + private String zpwj1bh; + + /** + * 照片文件2编号 + */ + private String zpwj2bh; + + /** + * 照片文件3编号 + */ + private String zpwj3bh; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改日期 + */ + private LocalDateTime xgrq; + + /** + * json字典 + */ + private String jsonzd; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/MHKSQB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/MHKSQB.java new file mode 100644 index 000000000..b933e846c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/MHKSQB.java @@ -0,0 +1,146 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 免缓考申请表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("免缓考申请表") +public class MHKSQB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 班次编号 + */ + private String bcbh; + + /** + * 学员编号 + */ + private String xybh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 科目编号 + */ + private String kmbh; + + /** + * 实施课程编号 + */ + private String sskcbh; + + /** + * 申请类型 + */ + private String sqlx; + + /** + * 申请理由 + */ + private String sqly; + + /** + * 申请状态 + */ + private String sqzt; + + /** + * 备注 + */ + private String bz; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 队干部编号 + */ + private String dgbbh; + + /** + * 队干部审核意见 + */ + private String dgbshyj; + + /** + * 队干部审核时间 + */ + private LocalDateTime dgbshsj; + + /** + * 任课教员编号 + */ + private String rkjybh; + + /** + * 任课教员审核意见 + */ + private String rkjyshyj; + + /** + * 任课教员审核时间 + */ + private LocalDateTime rkjyshsj; + + /** + * 管理员帐号 + */ + private String glyzh; + + /** + * 教务审核意见 + */ + private String jwshyj; + + /** + * 教务审核时间 + */ + private LocalDateTime jwshsj; + + /** + * json字典 + */ + private String jsonzd; + + /** + * 佐证材料1文件编号 + */ + private String zzcl1wjbh; + + /** + * 佐证材料2文件编号 + */ + private String zzcl2wjbh; + + /** + * 佐证材料3文件编号 + */ + private String zzcl3wjbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/MemberOnlineInfo.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/MemberOnlineInfo.java new file mode 100644 index 000000000..ce7d88269 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/MemberOnlineInfo.java @@ -0,0 +1,63 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * MemberOnlineInfo + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("MemberOnlineInfo") +public class MemberOnlineInfo extends BaseEntity { + + /** + * SessionId + */ + private String sessionId; + + /** + * 账号 + */ + private String zH; + + /** + * 姓名 + */ + private String xM; + + /** + * 单位 + */ + private String dW; + + /** + * 角色类别 + */ + private String jSLB; + + /** + * IP + */ + private String iP; + + /** + * 开始访问时间 + */ + private LocalDateTime kSFWSJ; + + /** + * 最后访问时间 + */ + private LocalDateTime zHFWSJ; + + /** + * 最后访问路径 + */ + private String zHFWLJ; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/N2JYJXZLCPCJ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/N2JYJXZLCPCJ.java new file mode 100644 index 000000000..0d402edf6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/N2JYJXZLCPCJ.java @@ -0,0 +1,144 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * N2教员教学质量测评成绩 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("N2教员教学质量测评成绩") +public class N2JYJXZLCPCJ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 项目编号 + */ + private String xmbh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 参评 + */ + private Integer cp; + + /** + * 职称 + */ + private String zc; + + /** + * 本科课程课时 + */ + private Float bkkcks; + + /** + * 任职课程课时 + */ + private Float rzkcks; + + /** + * 研究生课程课时 + */ + private Float yjskcks; + + /** + * 士官课程课时 + */ + private Float sgkcks; + + /** + * 总课时 + */ + private Float zks; + + /** + * 课堂讲授任务量系数 + */ + private Float ktjsrwlxs; + + /** + * 专家评教成绩 + */ + private Float zjpjcj; + + /** + * 同行评议成绩 + */ + private Float txpycj; + + /** + * 学员评教成绩 + */ + private Float xypjcj; + + /** + * 综合成绩 + */ + private Float zhcj; + + /** + * 单位排名 + */ + private Integer dwpm; + + /** + * 最终结论 + */ + private String zzjl; + + /** + * 备注 + */ + private String bz; + + /** + * 专业技术职务等级 + */ + private String zyjszwdj; + + /** + * 教研室代号 + */ + private String jysdh; + + /** + * 院系编号 + */ + private String yxbh; + + /** + * 院系填报情况编号 + */ + private String yxtbqkbh; + + /** + * 教学督导课时 + */ + private Float jxddks; + + /** + * 初始综合成绩 + */ + private Float cszhcj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/N2XQJYJXZLCPXM.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/N2XQJYJXZLCPXM.java new file mode 100644 index 000000000..7c7252136 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/N2XQJYJXZLCPXM.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * N2学期教员教学质量测评项目 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("N2学期教员教学质量测评项目") +public class N2XQJYJXZLCPXM extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 名称 + */ + private String mc; + + /** + * 起始时间 + */ + private LocalDateTime qssj; + + /** + * 终止时间 + */ + private LocalDateTime zzsj; + + /** + * 状态 + */ + private String zt; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 备注 + */ + private String bz; + + /** + * 核准时间 + */ + private LocalDateTime hzsj; + + /** + * 发布时间 + */ + private LocalDateTime fbsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/N2YXTBQK.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/N2YXTBQK.java new file mode 100644 index 000000000..31e157c61 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/N2YXTBQK.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * N2院系填报情况 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("N2院系填报情况") +public class N2YXTBQK extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 项目编号 + */ + private String xmbh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 院系编号 + */ + private String yxbh; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 上报情况 + */ + private Integer sbqk; + + /** + * 上报时间 + */ + private LocalDateTime sbsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NDZHPD.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NDZHPD.java new file mode 100644 index 000000000..a2977628d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NDZHPD.java @@ -0,0 +1,81 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 年度综合评定 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("年度综合评定") +public class NDZHPD extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 名称 + */ + private String mc; + + /** + * 起始时间 + */ + private LocalDateTime qssj; + + /** + * 终止时间 + */ + private LocalDateTime zzsj; + + /** + * 分值估算时间 + */ + private LocalDateTime fzgssj; + + /** + * 同行评估编号 + */ + private String txpgbh; + + /** + * 综合评定规则编号 + */ + private String zhpdgzbh; + + /** + * 状态 + */ + private String zt; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 备注 + */ + private String bz; + + /** + * 生效时间 + */ + private LocalDateTime sxsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NDZHPDJY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NDZHPDJY.java new file mode 100644 index 000000000..82782aac7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NDZHPDJY.java @@ -0,0 +1,108 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +/** + * 年度综合评定_教员 + *

+ * 存储教员的年度综合评定结果,包括各类评估分值和评定结论。 + * 关联年度综合评定_教研室表(年度综合评定_教研室编号)。 + *

+ */ +@Data +@TableName("年度综合评定_教员") +public class NDZHPDJY { + + /** 编号 */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** 年度综合评定_教研室编号 */ + @TableField("年度综合评定_教研室编号") + private String ndzhpd_jysbh; + + /** 教员编号 */ + @TableField("教员编号") + private String jybh; + + /** 参评 */ + @TableField("参评") + private Integer cp; + + /** 有教学责任事故 */ + @TableField("有教学责任事故") + private Integer yjxzrsg; + + /** 教学责任事故说明 */ + @TableField("教学责任事故说明") + private String jxzrsgsm; + + /** 全年只承担辅讲 */ + @TableField("全年只承担辅讲") + private Integer qnzcdfj; + + /** 教学竞赛获奖 */ + @TableField("教学竞赛获奖") + private Integer jxjshj; + + /** 教学竞赛获奖说明 */ + @TableField("教学竞赛获奖说明") + private String jxjshjsm; + + /** 免评 */ + @TableField("免评") + private Integer mp; + + /** 职称 */ + @TableField("职称") + private String zc; + + /** 领导评估分 */ + @TableField("领导评估分") + private Double ldpgf; + + /** 专家评估分 */ + @TableField("专家评估分") + private Double zjpgf; + + /** 同行评估分 */ + @TableField("同行评估分") + private Double txpgf; + + /** 学员评估分 */ + @TableField("学员评估分") + private Double xypgf; + + /** 综合分 */ + @TableField("综合分") + private Double zhf; + + /** 学员评估分未达标 */ + @TableField("学员评估分未达标") + private Integer xypgfwdb; + + /** 排序最后比例 */ + @TableField("排序最后比例") + private Integer pxzhbl; + + /** A比例之外 */ + @TableField("A比例之外") + private Integer ablzw; + + /** 预评估结论 */ + @TableField("预评估结论") + private String ypgjl; + + /** 最终结论 */ + @TableField("最终结论") + private String zzjl; + + /** 备注 */ + @TableField("备注") + private String bz; + + /** 专业技术职务等级 */ + @TableField("专业技术职务等级") + private String zyjszwdj; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NDZHPDJYS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NDZHPDJYS.java new file mode 100644 index 000000000..68d62a0fb --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NDZHPDJYS.java @@ -0,0 +1,94 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 年度综合评定_教研室 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("年度综合评定_教研室") +public class NDZHPDJYS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度综合评定编号 + */ + private String ndzhpdbh; + + /** + * 教研室编号 + */ + private String jysbh; + + /** + * 有教学责任事故 + */ + private Integer yjxzrsg; + + /** + * 教学责任事故说明 + */ + private String jxzrsgsm; + + /** + * 参评 + */ + private Integer cp; + + /** + * 总人数 + */ + private Integer zrs; + + /** + * A级人数 + */ + private Integer ajrs; + + /** + * B级人数 + */ + private Integer bjrs; + + /** + * C级以下人数 + */ + private Integer cjyxrs; + + /** + * A率 + */ + private Float al; + + /** + * AB率 + */ + private Float abl; + + /** + * 平均分 + */ + private Float pjf; + + /** + * 结论 + */ + private String jl; + + /** + * 备注 + */ + private String bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NZKSF.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NZKSF.java new file mode 100644 index 000000000..f5068be61 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/NZKSF.java @@ -0,0 +1,109 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 年终课时费 + *

+ * 年终课时费核算主表,按学期统计课时量。 + *

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("年终课时费") +public class NZKSF extends BaseEntity { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("名称") + private String mc; + + @TableField("上学期年度") + private Integer sxqnd; + + @TableField("下学期年度") + private Integer xxqnd; + + @TableField("课时费标准编号") + private String ksfbzbh; + + @TableField("不计酬劳课时量") + private Float bjclksl; + + @TableField("上学期课时量") + private Float sxqksl; + + @TableField("下学期课时量") + private Float xxqksl; + + @TableField("上学期实际教学课时量") + private Float sxqsjjxksl; + + @TableField("上学期补助教学课时量") + private Float sxqbzjxksl; + + @TableField("上学期相关教学课时量") + private Float sxqxgjxksl; + + @TableField("上学期不计酬劳课时量") + private Float sxqbjclksl; + + @TableField("下学期实际教学课时量") + private Float xxqsjjxksl; + + @TableField("下学期补助教学课时量") + private Float xxqbzjxksl; + + @TableField("下学期相关教学课时量") + private Float xxqxgjxksl; + + @TableField("下学期不计酬劳课时量") + private Float xxqbjclksl; + + @TableField("教学课时量") + private Float jxksl; + + @TableField("上学期教学课时量") + private Float sxqjxksl; + + @TableField("下学期教学课时量") + private Float xxqjxksl; + + @TableField("总计课时量") + private Float zjksl; + + @TableField("实际教学课时量") + private Float sjjxksl; + + @TableField("补助教学课时量") + private Float bzjxksl; + + @TableField("相关教学课时量") + private Float xgjxksl; + + @TableField("剩余课时量") + private Float syksl; + + @TableField("说明") + private String sm; + + @TableField("备注") + private String bz; + + @TableField(exist = false) + private LocalDateTime createTime; + @TableField(exist = false) + private LocalDateTime updateTime; + @TableField(exist = false) + private String createBy; + @TableField(exist = false) + private String updateBy; + @TableField(exist = false) + private Integer delFlag; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/PJBJPKYS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/PJBJPKYS.java new file mode 100644 index 000000000..c5b7ba0d1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/PJBJPKYS.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 普教班级排课预设 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("普教班级排课预设") +public class PJBJPKYS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 周天 + */ + private Integer zt; + + /** + * 节次 + */ + private Integer jc; + + /** + * 班级编号 + */ + private String bjbh; + + /** + * 班级学期编号 + */ + private String bjxqbh; + + /** + * 禁排 + */ + private Integer jp; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/PJKCAPYS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/PJKCAPYS.java new file mode 100644 index 000000000..2129bbadd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/PJKCAPYS.java @@ -0,0 +1,44 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 普教课程安排预设 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("普教课程安排预设") +public class PJKCAPYS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 课编号 + */ + private String kbh; + + /** + * 周天 + */ + private Integer zt; + + /** + * 节次 + */ + private Integer jc; + + /** + * 禁排 + */ + private Integer jp; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/PXRW.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/PXRW.java new file mode 100644 index 000000000..8b9d38b0e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/PXRW.java @@ -0,0 +1,106 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 培训任务 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("培训任务") +public class PXRW extends BaseEntity { + + /** + * 标识号 + */ + @TableId(value = "标识号", type = IdType.INPUT) + private String bsh; + + /** + * 序号 + */ + private String xh; + + /** + * 任务来源 + */ + private String rwly; + + /** + * 年级 + */ + private String nj; + + /** + * 在校状态 + */ + private String zxzt; + + /** + * 任务类别 + */ + private String rwlb; + + /** + * 入学日期 + */ + private LocalDateTime rxrq; + + /** + * 毕业日期 + */ + private LocalDateTime byrq; + + /** + * 备注 + */ + private String bz; + + /** + * 学科专业信息标识号 + */ + private String xkzyxxbsh; + + /** + * 名称 + */ + private String mc; + + /** + * 计划人数 + */ + private Integer jhrs; + + /** + * 任务说明 + */ + private String rwsm; + + /** + * 计划班次数 + */ + private Integer jhbcs; + + /** + * json字典 + */ + private String jsonzd; + + /** + * 所属单位 + */ + private String ssdw; + + /** + * 主体培训任务 + */ + private Integer ztpxrw; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/QJKTB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/QJKTB.java new file mode 100644 index 000000000..d3cfd1b9f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/QJKTB.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +/** + * 请假课堂表 + */ +@Data +@TableName("请假课堂表") +public class QJKTB { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 请假编号 + */ + @TableField("请假编号") + private String qjbh; + + /** + * 实施_课堂编号 + */ + @TableField("实施_课堂编号") + private String ssktbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/QJXYB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/QJXYB.java new file mode 100644 index 000000000..3ade39388 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/QJXYB.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +/** + * 请假学员表 + */ +@Data +@TableName("请假学员表") +public class QJXYB { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 请假编号 + */ + @TableField("请假编号") + private String qjbh; + + /** + * 学员编号 + */ + @TableField("学员编号") + private String xybh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/RYDASHBMSZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/RYDASHBMSZ.java new file mode 100644 index 000000000..ffcecec7a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/RYDASHBMSZ.java @@ -0,0 +1,41 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 人员档案审核部门设置 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("人员档案审核部门设置") +public class RYDASHBMSZ extends BaseEntity { + + /** + * 档案目录 + */ + @TableId(value = "档案目录", type = IdType.INPUT) + private String daml; + + /** + * 审核部门编号 + */ + private String shbmbh; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改日期 + */ + private LocalDateTime xgrq; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSDKSQ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSDKSQ.java new file mode 100644 index 000000000..8abcfde66 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSDKSQ.java @@ -0,0 +1,186 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 实施_调课申请 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("实施_调课申请") +public class SSDKSQ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 实施_课程表编号 + */ + private String sskcbbh; + + /** + * 预调时间安排 + */ + private Integer ydsjap; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 执行人编号 + */ + private String zxrbh; + + /** + * 执行时间 + */ + private LocalDateTime zxsj; + + /** + * 执行状态 + */ + private Integer zxzt; + + /** + * 事由 + */ + private String sy; + + /** + * 教研室批准状态 + */ + private Integer jyspzzt; + + /** + * 教研室批准时间 + */ + private LocalDateTime jyspzsj; + + /** + * 教研室批准人编号 + */ + private String jyspzrbh; + + /** + * 教学系批准状态 + */ + private Integer jxxspzzt; + + /** + * 教学系批准时间 + */ + private LocalDateTime jxxspzsj; + + /** + * 教学系批准人编号 + */ + private String jxxspzrbh; + + /** + * 备注 + */ + private String bz; + + /** + * 申请教员编号 + */ + private String sqjybh; + + /** + * 延时状态 + */ + private Integer yszt; + + /** + * 教研室查收状态 + */ + private Integer jyscszt; + + /** + * 教研室查收时间 + */ + private LocalDateTime jyscssj; + + /** + * 教研室查收人编号 + */ + private String jyscsrbh; + + /** + * 删除状态 + */ + private Integer sczt; + + /** + * 发回状态 + */ + private Integer fhzt; + + /** + * 发回意见 + */ + private String fhyj; + + /** + * 发回时间 + */ + private LocalDateTime fhsj; + + /** + * 调课 + */ + private Integer dk; + + /** + * 年度 + */ + private Integer nd; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + + /** + * 教学内容 + */ + private String jxnr; + + /** + * 教学要点 + */ + private String jxyd; + + /** + * 教学方法 + */ + private String jxff; + + /** + * 教学保障备注 + */ + private String jxbzbz; + + /** + * 用车信息 + */ + private String ycxx; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSDKSQSP.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSDKSQSP.java new file mode 100644 index 000000000..1a7f64a1e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSDKSQSP.java @@ -0,0 +1,81 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 实施_调课申请审批 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("实施_调课申请审批") +public class SSDKSQSP extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 实施_调课申请编号 + */ + private String ssdksqbh; + + /** + * 机关编号 + */ + private String jgbh; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 审批时间 + */ + private LocalDateTime spsj; + + /** + * 审批状态 + */ + private Integer spzt; + + /** + * 审批人编号 + */ + private String sprbh; + + /** + * 查收状态 + */ + private Integer cszt; + + /** + * 查收时间 + */ + private LocalDateTime cssj; + + /** + * 查收人编号 + */ + private String csrbh; + + /** + * 发回意见 + */ + private String fhyj; + + /** + * 年度 + */ + private Integer nd; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJG.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJG.java new file mode 100644 index 000000000..5d41005b6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJG.java @@ -0,0 +1,86 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 实施_机关 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("实施_机关") +public class SSJG extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 机关名称 + */ + @TableField("机关名称") + private String jGMC; + + /** + * 机关类别 + */ + @TableField("机关类别") + private String jGLB; + + /** + * 分类号 + */ + @TableField("分类号") + private String fLH; + + /** + * 分级号 + */ + @TableField("分级号") + private Integer fJH; + + /** + * 上级机关编号 + */ + @TableField("上级机关编号") + private String sJJGBH; + + /** + * 停用 + */ + @TableField("停用") + private Boolean tY; + + /** + * 停用时间 + */ + @TableField("停用时间") + private LocalDateTime tYSJ; + + /** + * 简称 + */ + @TableField("简称") + private String jC; + + /** + * 逻辑删除标记:0-正常,1-已删除。 + * 覆盖 BaseEntity 的 delFlag 字段以添加 @TableLogic 注解, + * 使 MyBatis-Plus 的 BaseMapper 方法自动过滤已删除记录。 + * 手写 XML 查询需自行添加 DEL_FLAG 过滤条件。 + */ + @TableLogic + @TableField("DEL_FLAG") + private Integer delFlag; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJGCM.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJGCM.java new file mode 100644 index 000000000..99864f305 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJGCM.java @@ -0,0 +1,73 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 实施_机关参谋 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("实施_机关参谋") +public class SSJGCM extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 实施_机关编号 + */ + private String ssjgbh; + + /** + * 用户姓名 + */ + private String yhxm; + + /** + * 序号 + */ + private String xh; + + /** + * 离职状态 + */ + private Integer lzzt; + + /** + * 职务 + */ + private String zw; + + /** + * 卡号 + */ + private String kh; + + /** + * 拼音 + */ + private String py; + + /** + * 主官 + */ + private Integer zg; + + /** 上一个实施_机关编号(异动前归属) */ + @TableField("上一个部别编号") + private String previousDept; + + /** 异动历史JSON数组,每条记录含 from/to/time/reason */ + @TableField("异动历史") + private String transferHistory; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJGJYS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJGJYS.java new file mode 100644 index 000000000..a42df0d92 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJGJYS.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 实施_机关_教研室 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("实施_机关_教研室") +public class SSJGJYS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 实施_机关编号 + */ + private String sSJGBH; + + /** + * 教研室编号 + */ + private String jYSBH; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJGXYD.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJGXYD.java new file mode 100644 index 000000000..744e7d4e7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSJGXYD.java @@ -0,0 +1,29 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 实施_机关_学员队 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("实施_机关_学员队") +public class SSJGXYD extends BaseEntity { + + /** + * 学员队编号 + */ + @TableId(value = "学员队编号", type = IdType.INPUT) + private String xydbh; + + /** + * 实施_机关编号 + */ + private String ssjgbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKC.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKC.java new file mode 100644 index 000000000..348b0196f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKC.java @@ -0,0 +1,221 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 实施_课程 + */ +@Data +@TableName(value = "实施_课程", schema = "JW") +public class SSKC { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + @TableField("年度") + private Integer nd; + + /** + * 教员编号 + */ + @TableField("教员编号") + private String jybh; + + /** + * 教员课次序号 + */ + @TableField("教员课次序号") + private Integer jykcxh; + + /** + * 科目编号 + */ + @TableField("科目编号") + private String kmbh; + + /** + * 教室编号 + */ + @TableField("教室编号") + private String jsbh; + + /** + * 学时 + */ + @TableField("学时") + private Integer xs; + + /** + * 课类型 + */ + @TableField("课类型") + private String klx; + + /** + * 课程标准编号 + */ + @TableField("课程标准编号") + private String ktbzbh; + + /** + * 课程课时系数 + */ + @TableField("课程课时系数") + private Float kcksxs; + + /** + * 课程学员测评状态 + */ + @TableField("课程学员测评状态") + private String kcxycpzt; + + /** + * 学分 + */ + @TableField("学分") + private Float xf; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("创建时间") + private LocalDateTime cjsj; + + /** + * 变动时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("变动时间") + private LocalDateTime bdsj; + + /** + * 课表变动时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("课表变动时间") + private LocalDateTime kbbdsj; + + /** + * 测评结果样式编号 + */ + @TableField("测评结果样式编号") + private String cpjgygbh; + + /** + * 理论学时 + */ + @TableField("理论学时") + private Integer llxs; + + /** + * 实践学时 + */ + @TableField("实践学时") + private Integer sjxs; + + /** + * 考核学时 + */ + @TableField("考核学时") + private Integer khxs; + + /** + * 运行学时 + */ + @TableField("运行学时") + private Integer yxxs; + + /** + * 运行理论学时 + */ + @TableField("运行理论学时") + private Integer yxllxs; + + /** + * 运行实践学时 + */ + @TableField("运行实践学时") + private Integer yxsjxs; + + /** + * 运行考核学时 + */ + @TableField("运行考核学时") + private Integer yxkhxs; + + /** + * 成绩分制 + */ + @TableField("成绩分制") + private String cjfz; + + /** + * 不计入学员平均分(0-否,1-是) + */ + @TableField("不计入学员平均分") + private Integer bjrxypjf; + + /** + * 周课时 + */ + @TableField("周课时") + private Integer zks; + + /** + * 预计测评结束时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("预计测评结束时间") + private LocalDateTime yjcpjssj; + + /** + * 分值权重标准编号 + */ + @TableField("分值权重标准编号") + private String fzqzbzbh; + + /** + * json字典 + */ + @TableField("json字典") + private String jsonzd; + + /** + * 评学综合得分 + */ + @TableField("评学综合得分") + private Float pxxhdf; + + /** + * 确认评学综合(0-否,1-是) + */ + @TableField("确认评学综合") + private Integer qrpxzh; + + /** + * 责任教员编号 + */ + @TableField("责任教员编号") + private String zrjybh; + + /** + * 教员评学_结束状态(0-否,1-是) + */ + @TableField("教员评学_结束状态") + private Integer jypxjszt; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCB.java new file mode 100644 index 000000000..55a353d96 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCB.java @@ -0,0 +1,174 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 实施_课程表 + */ +@Data +@TableName("实施_课程表") +public class SSKCB { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 实施_课程编号 + */ + @TableField("实施_课程编号") + private String sskcbh; + + /** + * 计划时间安排 + */ + @TableField("计划时间安排") + private Integer jhsjap; + + /** + * 实际时间安排 + */ + @TableField("实际时间安排") + private Integer sjsjap; + + /** + * 教学内容 + */ + @TableField("教学内容") + private String jxnr; + + /** + * 教学要点 + */ + @TableField("教学要点") + private String jxyd; + + /** + * 教学方法 + */ + @TableField("教学方法") + private String jxff; + + /** + * 教员备注 + */ + @TableField("教员备注") + private String jybz; + + /** + * 教务备注 + */ + @TableField("教务备注") + private String jwbz; + + /** + * 删除状态 + */ + @TableField("删除状态") + private Integer sczt; + + /** + * 课堂标准编号 + */ + @TableField("课堂标准编号") + private String ktbzbh; + + /** + * 课堂日志编号 + */ + @TableField("课堂日志编号") + private String ktrzbh; + + /** + * 日期 + */ + @TableField("日期") + private LocalDateTime rq; + + /** + * 节次 + */ + @TableField("节次") + private Integer jc; + + /** + * 创建时间 + */ + @TableField("创建时间") + private LocalDateTime cjsj; + + /** + * 变动时间 + */ + @TableField("变动时间") + private LocalDateTime bdsj; + + /** + * 年度 + */ + @TableField("年度") + private Integer nd; + + /** + * 学员测评状态 + */ + @TableField("学员测评状态") + private String xycpzt; + + /** + * 结束测评时间 + */ + @TableField("结束测评时间") + private LocalDateTime jscpsj; + + /** + * 预计测评结束时间 + */ + @TableField("预计测评结束时间") + private LocalDateTime yjcpjssj; + + /** + * 节次调节 + */ + @TableField("节次调节") + private Integer jcdj; + + /** + * 教学保障备注 + */ + @TableField("教学保障备注") + private String jxbzbz; + + /** + * 用车信息 + */ + @TableField("用车信息") + private String ycxx; + + /** + * json字典 + */ + @TableField("JSONZD") + private String jsonzd; + + /** + * 班组模式 + */ + @TableField("班组模式") + private String bzms; + + /** + * 班组划分信息 + */ + @TableField("班组划分信息") + private String bzhfxx; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCBFZJY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCBFZJY.java new file mode 100644 index 000000000..e71d199ec --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCBFZJY.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 实施_课程表_辅助教员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("实施_课程表_辅助教员") +public class SSKCBFZJY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 实施_课程表编号 + */ + private String sskcbbh; + + /** + * 辅助教员编号 + */ + private String fzjybh; + + /** + * 主教员 + */ + private Integer zjy; + + /** + * 年度 + */ + private Integer nd; + + /** + * 参与学员测评 + */ + private Integer cyxycp; + + /** + * 学员测评平均分 + */ + private Float xycppjf; + + /** + * 备注 + */ + private String bz; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCBJS.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCBJS.java new file mode 100644 index 000000000..df8682be5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCBJS.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 实施_课程表_教室 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("实施_课程表_教室") +public class SSKCBJS extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 实施_课程表编号 + */ + private String sskcbbh; + + /** + * 教室编号 + */ + private String jsbh; + + /** + * 备注 + */ + private String bz; + + /** + * 年度 + */ + private Integer nd; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCBXYD.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCBXYD.java new file mode 100644 index 000000000..8230f67c8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCBXYD.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 实施_课程表_学员队 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("实施_课程表_学员队") +public class SSKCBXYD extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 实施_课程表编号 + */ + private String sskcbbh; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 人数 + */ + private Integer rs; + + /** + * 简称 + */ + private String jc; + + /** + * 年度 + */ + private Integer nd; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc2; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCB_RZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCB_RZ.java new file mode 100644 index 000000000..b3c11c5b1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCB_RZ.java @@ -0,0 +1,124 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 实施_课程表_日志(教学日志明细表) + *

包含教学日志全部字段及变更追踪信息。

+ */ +@Data +@TableName("实施_课程表_日志") +public class SSKCB_RZ { + + @TableId(value = "编号", type = IdType.ASSIGN_UUID) + private String bh; + + @TableField("统一编号") + private String tybh; + + @TableField("教员编号") + private String jybh; + + @TableField("正常状态") + private Integer zczt; + + @TableField("日志记录") + private String rzjl; + + @TableField("备注") + private String bz; + + @TableField("创建时间") + private LocalDateTime cjsj; + + @TableField("修改时间") + private LocalDateTime xgsj; + + @TableField("整体评价") + private String ztpj; + + @TableField("讲授方式") + private String jsfs; + + @TableField("实到人数") + private Integer sdrs; + + @TableField("基础课时") + private Integer jcks; + + @TableField("按天记") + private Integer atj; + + @TableField("日期") + private LocalDateTime rq; + + @TableField("起始节次") + private Integer qsjc; + + @TableField("结束节次") + private Integer jsjc; + + /** 状态:系统创建、已上报、已审核、已退回 */ + @TableField("状态") + private String zt; + + @TableField("教研室核准时间") + private LocalDateTime jyshzsj; + + @TableField("授课课题") + private String skkt; + + @TableField("教学日志创建方式") + private String jxrzcjfs; + + @TableField("授课地点") + private String skdd; + + @TableField("课程科目编号") + private String kckmbh; + + @TableField("任务类别") + private String rwlb; + + @TableField("应到人数") + private Integer ydrs; + + @TableField("培训期班信息") + private String pxqbxx; + + @TableField("查课领导或专家") + private String ckldhzj; + + @TableField("授课时间变动") + private Integer sksjbd; + + @TableField("授课地点变动") + private Integer skddbd; + + @TableField("课程变动") + private Integer kcbd; + + @TableField("授课课题变动") + private Integer skktbd; + + @TableField("任务类别变动") + private Integer rwlbbd; + + @TableField("培训期班变动") + private Integer pxqbbd; + + @TableField("授课方式变动") + private Integer skfsbd; + + @TableField("分组与指导教员变动") + private Integer fzyzdjybd; + + @TableField("管理机构编号") + private String gljgbh; + + @TableField("退回意见") + private String thyj; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCXYD.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCXYD.java new file mode 100644 index 000000000..9c420a430 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSKCXYD.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 实施_课程学员队 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("实施_课程学员队") +public class SSKCXYD extends BaseEntity { + + /** + * 编号 + */ + private String bh; + + /** + * 实施_课程编号 + */ + private String sskcbh; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 简称 + */ + private String jc; + + /** + * 人数 + */ + private Integer rs; + + /** + * 年度 + */ + private Integer nd; + + /** + * 学期第次 + */ + private Integer xqdc; + + /** + * 学分 + */ + private Float xf; + + /** + * 学员_课程板块测评编号 + */ + private String xykcbkcpbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSOAppInfo.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSOAppInfo.java new file mode 100644 index 000000000..0f88b1d3d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSOAppInfo.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * SSO_AppInfo + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("SSO_AppInfo") +public class SSOAppInfo extends BaseEntity { + + /** + * ID + */ + private String iD; + + /** + * AppKey + */ + private String appKey; + + /** + * AppSecret + */ + private String appSecret; + + /** + * Title + */ + private String title; + + /** + * Remark + */ + private String remark; + + /** + * Icon + */ + private String icon; + + /** + * ReturnUrl + */ + private String returnUrl; + + /** + * IsEnable + */ + private Boolean isEnable; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSOUserAuthOperate.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSOUserAuthOperate.java new file mode 100644 index 000000000..251370637 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSOUserAuthOperate.java @@ -0,0 +1,36 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * SSO_UserAuthOperate + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("SSO_UserAuthOperate") +public class SSOUserAuthOperate extends BaseEntity { + + /** + * ID + */ + private String iD; + + /** + * SessionKey + */ + private String sessionKey; + + /** + * Remark + */ + private String remark; + + /** + * IpAddress + */ + private String ipAddress; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSOUserAuthSession.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSOUserAuthSession.java new file mode 100644 index 000000000..9ee810327 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SSOUserAuthSession.java @@ -0,0 +1,53 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * SSO_UserAuthSession + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("SSO_UserAuthSession") +public class SSOUserAuthSession extends BaseEntity { + + /** + * SessionKey + */ + private String sessionKey; + + /** + * AppKey + */ + private String appKey; + + /** + * LoginID + */ + private String loginID; + + /** + * UserID + */ + private String userID; + + /** + * UserType + */ + private String userType; + + /** + * IpAddress + */ + private String ipAddress; + + /** + * InvalidTime + */ + private LocalDateTime invalidTime; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ScheduleAdjustStrategy.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ScheduleAdjustStrategy.java new file mode 100644 index 000000000..953a6e161 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ScheduleAdjustStrategy.java @@ -0,0 +1,74 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 实施_调课策略 + *

+ * 管理调课的时间段限制策略与免审批策略。 + * 策略类型为 TIME_LIMIT 时,用于限制教员在规定时间后不得调整下周课程; + * 策略类型为 AUTO_APPROVE 时,用于在生效范围内自动通过调课申请,跳过三级审批。 + *

+ */ +@Data +@TableName("实施_调课策略") +public class ScheduleAdjustStrategy { + + /** 策略编号 */ + @TableId(value = "策略编号", type = IdType.INPUT) + private String clbh; + + /** 策略名称 */ + @TableField("策略名称") + private String clmc; + + /** + * 策略类型:TIME_LIMIT=时间段限制,AUTO_APPROVE=免审批 + */ + @TableField("策略类型") + private String cllx; + + /** 是否启用:0=停用,1=启用 */ + @TableField("是否启用") + private Integer sfqy; + + /** + * 截止星期:0-6(周日到周六),仅 TIME_LIMIT 类型使用。 + * 含义:当前时间超过该星期时,不允许调整下周课程。为空表示不限制星期。 + */ + @TableField("截止星期") + private Integer jzxq; + + /** + * 截止节次:仅 TIME_LIMIT 类型使用。 + * 含义:到达截止星期当天的该节次后不允许调整下周课程。为空表示不限制节次。 + */ + @TableField("截止节次") + private Integer jzjc; + + /** 生效开始时间(包含),为空表示不限制开始时间 */ + @TableField("生效开始时间") + private LocalDateTime sxkssj; + + /** 生效结束时间(包含),为空表示不限制结束时间 */ + @TableField("生效结束时间") + private LocalDateTime sxjssj; + + /** 备注 */ + @TableField("备注") + private String bz; + + /** 创建时间 */ + @TableField("创建时间") + private LocalDateTime cjsj; + + /** 更新时间 */ + @TableField("更新时间") + private LocalDateTime gxsj; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SysDictData.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SysDictData.java new file mode 100644 index 000000000..f7ce435b9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SysDictData.java @@ -0,0 +1,126 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 字典数据表 + *

+ * 存储系统字典项的具体数据,包括字典标签、字典值、排序、样式、状态等信息。 + * 关联字典类型表(dict_type_code)。 + *

+ */ +@Data +@TableName("sys_dict_data") +public class SysDictData { + + /** + * 字典数据ID(主键,自增) + */ + @TableId(value = "dict_data_id", type = IdType.AUTO) + private Long dictDataId; + + /** + * 字典类型编码 + */ + @TableField("dict_type_code") + private String dictTypeCode; + + /** + * 字典标签(显示用) + */ + @TableField("dict_label") + private String dictLabel; + + /** + * 字典值(存储用) + */ + @TableField("dict_value") + private String dictValue; + + /** + * 排序 + */ + @TableField("dict_sort") + private Integer dictSort; + + /** + * 标签样式(success/warning/info/danger) + */ + @TableField("css_class") + private String cssClass; + + /** + * 表格回显样式 + */ + @TableField("list_class") + private String listClass; + + /** + * 是否默认(0否 1是) + */ + @TableField("is_default") + private Integer isDefault; + + /** + * 状态(0停用 1正常) + */ + @TableField("status") + private Integer status; + + /** + * 备注 + */ + @TableField("remark") + private String remark; + + /** + * 版本号(用于版本控制,初始为1,每次编辑+1) + */ + @TableField("version") + private Integer version; + + /** + * 历史版本快照(JSON格式,记录每次编辑前的完整内容) + */ + @TableField("prev_snapshot") + private String prevSnapshot; + + /** + * 逻辑删除标记(0未删除 1已删除) + */ + @TableLogic + @TableField("is_deleted") + private Integer isDeleted; + + /** + * 创建人(自动填充,仅在插入时赋值) + */ + @TableField(value = "create_by", fill = FieldFill.INSERT) + private Long createBy; + + /** + * 创建时间(自动填充,仅在插入时赋值) + */ + @TableField(value = "create_time", fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新人(自动填充,插入和更新时均赋值) + */ + @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE) + private Long updateBy; + + /** + * 更新时间(自动填充,插入和更新时均赋值) + */ + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SysDictType.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SysDictType.java new file mode 100644 index 000000000..d37c1a7ca --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/SysDictType.java @@ -0,0 +1,102 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 字典类型表 + *

+ * 存储系统字典的分类信息,包括字典类型编码、名称、分组、状态等。 + * 字典类型编码全局唯一,用于与字典数据表关联。 + *

+ */ +@Data +@TableName("sys_dict_type") +public class SysDictType { + + /** + * 字典类型ID(主键,自增) + */ + @TableId(value = "dict_type_id", type = IdType.AUTO) + private Long dictTypeId; + + /** + * 字典类型编码(全局唯一,程序引用键) + */ + @TableField("dict_type_code") + private String dictTypeCode; + + /** + * 字典类型名称 + */ + @TableField("dict_type_name") + private String dictTypeName; + + /** + * 所属分组(基础数据/教学数据/学籍数据) + */ + @TableField("dict_group") + private String dictGroup; + + /** + * 状态(0停用 1正常) + */ + @TableField("status") + private Integer status; + + /** + * 备注 + */ + @TableField("remark") + private String remark; + + /** + * 版本号(用于版本控制,初始为1,每次编辑+1) + */ + @TableField("version") + private Integer version; + + /** + * 历史版本快照(JSON格式,记录每次编辑前的完整内容) + */ + @TableField("prev_snapshot") + private String prevSnapshot; + + /** + * 逻辑删除标记(0未删除 1已删除) + */ + @TableLogic + @TableField("is_deleted") + private Integer isDeleted; + + /** + * 创建人(自动填充,仅在插入时赋值) + */ + @TableField(value = "create_by", fill = FieldFill.INSERT) + private Long createBy; + + /** + * 创建时间(自动填充,仅在插入时赋值) + */ + @TableField(value = "create_time", fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新人(自动填充,插入和更新时均赋值) + */ + @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE) + private Long updateBy; + + /** + * 更新时间(自动填充,插入和更新时均赋值) + */ + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TCKGLY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TCKGLY.java new file mode 100644 index 000000000..80e80044e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TCKGLY.java @@ -0,0 +1,29 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 听查课管理员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("听查课管理员") +public class TCKGLY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 姓名 + */ + private String xM; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TCKGLYDLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TCKGLYDLB.java new file mode 100644 index 000000000..325f2a0c8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TCKGLYDLB.java @@ -0,0 +1,29 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 听查课管理员登录表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("听查课管理员登录表") +public class TCKGLYDLB extends BaseEntity { + + /** + * 听查课管理员编号 + */ + private String tCKGLYBH; + + /** + * 登录信息编号 + */ + @TableId(value = "登录信息编号", type = IdType.INPUT) + private String dLXXBH; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TGB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TGB.java new file mode 100644 index 000000000..fe6e92389 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TGB.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 通告表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("通告表") +public class TGB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 标题 + */ + private String bT; + + /** + * 内容 + */ + private String nR; + + /** + * 发布时间 + */ + private LocalDateTime fBSJ; + + /** + * 创建时间 + */ + private LocalDateTime cJSJ; + + /** + * 优先级 + */ + private Integer yXJ; + + /** + * 阅读次数 + */ + private Integer yDCS; + + /** + * 状态 + */ + private String zT; + + /** + * 类别 + */ + private String lB; + + /** + * 备注 + */ + private String bZ; + + /** + * 全体 + */ + private Boolean qT; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TGYHXX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TGYHXX.java new file mode 100644 index 000000000..395e6d9e8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TGYHXX.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 通告用户信息 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("通告用户信息") +public class TGYHXX extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 通告编号 + */ + private String tGBH; + + /** + * 用户编号 + */ + private String yHBH; + + /** + * 已接收 + */ + private Boolean yJS; + + /** + * 接收时间 + */ + private LocalDateTime JSSJ; + + /** + * 已阅读 + */ + private Boolean yYD; + + /** + * 阅读时间 + */ + private LocalDateTime yDSJ; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TKJL.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TKJL.java new file mode 100644 index 000000000..e2f55f236 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/TKJL.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 听课记录 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("听课记录") +public class TKJL extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 听课用户编号 + */ + private String tkyhbh; + + /** + * 课堂编号 + */ + private String ktbh; + + /** + * 创建日期 + */ + private LocalDateTime cjrq; + + /** + * 审查人编号 + */ + private String scrbh; + + /** + * 审查时间 + */ + private LocalDateTime scsj; + + /** + * 审查状态 + */ + private Integer sczt; + + /** + * 审查结果 + */ + private Integer scjg; + + /** + * 备注 + */ + private String bz; + + /** + * 提交状态 + */ + private Integer tjzt; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WJ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WJ.java new file mode 100644 index 000000000..169332f28 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WJ.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 文件 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("文件") +public class WJ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 文件数据 + */ + private String wJSJ; + + /** + * 文件名称 + */ + private String wJMC; + + /** + * 文件后缀 + */ + private String wJHZ; + + /** + * MIME类型 + */ + private String mIMELX; + + /** + * MD5 + */ + private String mD5; + + /** + * Size + */ + private Integer size; + + /** + * 上传时间 + */ + private LocalDateTime sCSJ; + + /** + * 外部存储 + */ + private Boolean wBCC; + + /** + * 外部存储文件名称 + */ + private String wBCCWJMC; + + /** + * 子目录 + */ + private String zML; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WSRYZCB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WSRYZCB.java new file mode 100644 index 000000000..7e1ba460f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WSRYZCB.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文书_人员组成表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("文书_人员组成表") +public class WSRYZCB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 序号 + */ + private String xH; + + /** + * 主事 + */ + private Boolean zS; + + /** + * 人员编号 + */ + private String rYBH; + + /** + * 职责 + */ + private String zZ; + + /** + * 备注 + */ + private String bZ; + + /** + * 文书_事项编号 + */ + private String wSSXBH; + + /** + * 排名 + */ + private Integer pM; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WSSXB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WSSXB.java new file mode 100644 index 000000000..73bc43798 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WSSXB.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 文书_事项表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("文书_事项表") +public class WSSXB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 统一编号 + */ + private String tYBH; + + /** + * 名称 + */ + private String mC; + + /** + * 事项类别 + */ + private String sXLB; + + /** + * 创建时间 + */ + private LocalDateTime cJSJ; + + /** + * 修改时间 + */ + private LocalDateTime xGSJ; + + /** + * 关键字 + */ + private String gJZ; + + /** + * 摘要 + */ + private String zY; + + /** + * 备注 + */ + private String bZ; + + /** + * json字典 + */ + private String jsonZD; + + /** + * 日志 + */ + private String rZ; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WSWDB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WSWDB.java new file mode 100644 index 000000000..1ba990a22 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/WSWDB.java @@ -0,0 +1,64 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 文书_文档表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("文书_文档表") +public class WSWDB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 序号 + */ + private String xH; + + /** + * 分类目录 + */ + private String fLML; + + /** + * 名称 + */ + private String mC; + + /** + * 文件编号 + */ + private String wJBH; + + /** + * 关键字 + */ + private String gJZ; + + /** + * 摘要 + */ + private String zY; + + /** + * 备注 + */ + private String bZ; + + /** + * 文书_事项编号 + */ + private String wSSXBH; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XKZYXX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XKZYXX.java new file mode 100644 index 000000000..914cfc74b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XKZYXX.java @@ -0,0 +1,92 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +/** + * 学科专业信息 + *

+ * 存储学科/专业的全部基础信息。 + * 逻辑删除字段:停用(0-启用,1-停用/删除)。 + *

+ */ +@Data +@TableName("学科专业信息") +public class XKZYXX { + + /** 标识号(后台自动生成UUID) */ + @TableId(value = "标识号", type = IdType.ASSIGN_UUID) + private String bsh; + + /** 序号 */ + @TableField("序号") + private Integer xh; + + /** 专业代码 */ + @TableField("专业代码") + private String zydm; + + /** 专业名称 */ + @TableField("专业名称") + private String zymc; + + /** 专业方向 */ + @TableField("专业方向") + private String zyfx; + + /** 停用(0-启用,1-停用/删除) */ + @TableField("停用") + private Integer ty; + + /** 停用时间 */ + @TableField("停用时间") + private String tysj; + + /** 学年制 */ + @TableField("学年制") + private String xnz; + + /** 学期数 */ + @TableField("学期数") + private Integer xqs; + + /** 培训层次 */ + @TableField("培训层次") + private String pxcc; + + /** 培训类型 */ + @TableField("培训类型") + private String pxlx; + + /** 教学管理机构编号 */ + @TableField("教学管理机构编号") + private String jxgljgbh; + + /** 备注 */ + @TableField("备注") + private String bz; + + /** 培训类型2 */ + @TableField("培训类型2") + private String pxlx2; + + /** 学员类别 */ + @TableField("学员类别") + private String xylb; + + /** 自定义分类 */ + @TableField("自定义分类") + private String zdyfl; + + /** json字典 */ + @TableField("JSONZD") + private String jsonzd; + + /** 主干专业 */ + @TableField("主干专业") + private String zgzy; + + /** 规范名称 */ + @TableField("规范名称") + private String gfmc; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQ.java new file mode 100644 index 000000000..c7bbedf7c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQ.java @@ -0,0 +1,126 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学期实体类 + */ +@Data +@TableName("学期") +public class XQ extends BaseEntity { + + /** + * 年度(主键) + */ + @TableId(value = "年度", type = IdType.INPUT) + private String nd; + + /** + * 当前学期 + */ + @TableField("当前学期") + private Boolean dqxq; + + /** + * 开学日期 + */ + @TableField("开学日期") + private LocalDateTime kxrq; + + /** + * 结束日期 + */ + @TableField("结束日期") + private LocalDateTime jsrq; + + /** + * 调课不审批 + */ + @TableField("调课不审批") + private Boolean tkbsp; + + /** + * 课时系数方案编号 + */ + @TableField("课时系数方案编号") + private String ksxsfabh; + + /** + * 禁止调课 + */ + @TableField("禁止调课") + private Boolean jztk; + + /** + * 终结成绩定及格 + */ + @TableField("终结成绩定及格") + private Boolean zjcjdjg; + + /** + * 教务领导审核调课 + */ + @TableField("教务领导审核调课") + private Boolean jwldshtk; + + /** + * 调课不审批_教员 + */ + @TableField("调课不审批_教员") + private Boolean tkbspJy; + + /** + * 调课不审批_时间 + */ + @TableField("调课不审批_时间") + private Boolean tkbspSj; + + /** + * 调课不审批_保障 + */ + @TableField("调课不审批_保障") + private Boolean tkbspBz; + + /** + * 锁定周数 + */ + @TableField("锁定周数") + private Integer sdzs; + + /** + * 调课不审批_场地 + */ + @TableField("调课不审批_场地") + private Boolean tkbspCd; + + /** + * 序号 + */ + @TableField("序号") + private Integer xh; + + /** + * 锁定计量单位 + */ + @TableField("锁定计量单位") + private String sdjldw; + + /** + * json字典 + */ + @TableField("JSONZD") + private String jsonzd; + + /** + * 课时统计时间 + */ + @TableField("课时统计时间") + private LocalDateTime kstjsj; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQJYKCSKQKB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQJYKCSKQKB.java new file mode 100644 index 000000000..7ba5588ac --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQJYKCSKQKB.java @@ -0,0 +1,69 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学期教员课程授课情况表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学期教员课程授课情况表") +public class XQJYKCSKQKB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 学期教员信息编号 + */ + private String xqjyxxbh; + + /** + * 课编号 + */ + private String kbh; + + /** + * 课程名称 + */ + private String kcmc; + + /** + * 项目类型 + */ + private String xmlx; + + /** + * 折算总课时 + */ + private Float zszks; + + /** + * 总课时 + */ + private Float zks; + + /** + * 期班 + */ + private String qb; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQJYXXB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQJYXXB.java new file mode 100644 index 000000000..1f1e984fa --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQJYXXB.java @@ -0,0 +1,136 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学期教员信息表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学期教员信息表") +public class XQJYXXB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 职称 + */ + private String zc; + + /** + * 专业技术职务等级 + */ + private String zyjszwdj; + + /** + * 备注 + */ + private String bz; + + /** + * 课程辅讲课时 + */ + private Float kcfjks; + + /** + * 课程主讲课时 + */ + private Float kczjks; + + /** + * 课程总课时 + */ + private Float kczks; + + /** + * 课程折算总课时 + */ + private Float kczszks; + + /** + * 项目补助课时 + */ + private Float xmbzks; + + /** + * 折算总课时 + */ + private Float zszks; + + /** + * 督导教员类别 + */ + private String ddjylb; + + /** + * json字典 + */ + private String jsonzd; + + /** + * 督导申报状态 + */ + private String ddsbzt; + + /** + * 督导申报时间 + */ + private LocalDateTime ddsbsj; + + /** + * 督导申报内容 + */ + private String ddsbnr; + + /** + * 督导申报审核意见 + */ + private String ddsbshyj; + + /** + * 督导申报审核时间 + */ + private LocalDateTime ddsbshsj; + + /** + * 督导派遣专家编号列表 + */ + private String ddpqzjbhlb; + + /** + * 督导申报类别编号 + */ + private String ddsblbbh; + + /** + * 督导专家级别 + */ + private String ddzjjb; + + /** + * 需督导评估等级 + */ + private Integer xddpgdj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQWTKYYDJB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQWTKYYDJB.java new file mode 100644 index 000000000..719c19ce8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQWTKYYDJB.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学期未听课原因登记表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学期未听课原因登记表") +public class XQWTKYYDJB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 周次 + */ + private Integer zc; + + /** + * 听课用户编号 + */ + private String tkyhbh; + + /** + * 开始日期 + */ + private LocalDateTime ksrq; + + /** + * 结束日期 + */ + private LocalDateTime jsrq; + + /** + * 未听课原因 + */ + private String wtkyy; + + /** + * 变动时间 + */ + private LocalDateTime bdsj; + + /** + * 备注 + */ + private String bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQXLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQXLB.java new file mode 100644 index 000000000..78df21e2a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XQXLB.java @@ -0,0 +1,78 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学期校历表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学期校历表") +public class XQXLB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + @TableField("年度") + private Integer nd; + + /** + * 假期时间 + */ + @TableField("假期时间") + private String jqsj; + + /** + * 假期名称 + */ + @TableField("假期名称") + private String jqmc; + + /** + * 加粗 + */ + @TableField("加粗") + private Boolean jc; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 可排课 + */ + @TableField("可排课") + private Boolean kpk; + + /** + * 备注显示 + */ + @TableField("备注显示") + private Boolean bzxs; + + /** + * 自动排课 + */ + @TableField("自动排课") + private Boolean zdpk; + + /** + * 节次 + */ + @TableField("节次") + private String courseClass; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYCPCPXMB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYCPCPXMB.java new file mode 100644 index 000000000..20bda2d27 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYCPCPXMB.java @@ -0,0 +1,86 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员测评_测评项模板 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员测评_测评项模板") +public class XYCPCPXMB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 名称 + */ + private String mC; + + /** + * 课程板块测评_分值权重标准编号 + */ + private String kCBKCPFZQZBZBH; + + /** + * 课程板块测评_测评结果样式编号 + */ + private String kCBKCPCPJGYSBH; + + /** + * 学员评教员_分值权重标准编号 + */ + private String xYPJYFZQZBZBH; + + /** + * 学员评教员_测评结果样式编号 + */ + private String xYPJYCPJGYSBH; + + /** + * 创建时间 + */ + private LocalDateTime cJSJ; + + /** + * 停用状态 + */ + private Boolean tYZT; + + /** + * 备注 + */ + private String bZ; + + /** + * 停用时间 + */ + private LocalDateTime tYSJ; + + /** + * 评课程通告内容 + */ + private String pKCTGNR; + + /** + * 评授课教员通告内容 + */ + private String pSKJYTGNR; + + /** + * json字典 + */ + private String jsonZD; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDB.java new file mode 100644 index 000000000..18c1c1f17 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDB.java @@ -0,0 +1,80 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学员队表(教学班次) + */ +@Data +@TableName("学员队表") +public class XYDB { + + @TableId(value = "学员队编号", type = IdType.INPUT) + private String xydbh; + + @TableField("学员队名称") + private String xydmc; + + @TableField("学员队人数") + private Integer xydrs; + + @TableField("年级") + private String nj; + + @TableField("专业代号") + private String zydh; + + @TableField("专业教室编号") + private String zyjsbh; + + @TableField("备注") + private String bz; + + @TableField("在校状态") + private String zxzt; + + @TableField("序号") + private String xh; + + @TableField("任务类别") + private String rwlb; + + @TableField("虚实类型") + private Integer xslx; + + @TableField("入学日期") + private LocalDateTime rxrq; + + @TableField("毕业日期") + private LocalDateTime byrq; + + @TableField("学员队类型") + private String xydlx; + + @TableField("培训任务标识号") + private String pxrwbsh; + + @TableField("节次类别") + private String jclb; + + @TableField("系统模式") + private String xtms; + + @TableField("JSONZD") + private String jsonzd; + + @TableField("简称") + private String jc; + + @TableField("所属单位") + private String ssdw; + + @TableField("主体培训任务") + private Integer ztpxrw; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDDLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDDLB.java new file mode 100644 index 000000000..0c7ce354d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDDLB.java @@ -0,0 +1,29 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学员队登录表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员队登录表") +public class XYDDLB extends BaseEntity { + + /** + * 学员队编号 + */ + @TableId(value = "学员队编号", type = IdType.INPUT) + private String xydbh; + + /** + * 登录信息编号 + */ + private String dlxxbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDMYDCPCPB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDMYDCPCPB.java new file mode 100644 index 000000000..f984dd133 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDMYDCPCPB.java @@ -0,0 +1,44 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学员队满意度测评_测评表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员队满意度测评_测评表") +public class XYDMYDCPCPB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 测评项编号 + */ + private String cpxbh; + + /** + * 参评学员编号 + */ + private String cpxybh; + + /** + * 得分 + */ + private Integer df; + + /** + * 意见和建议 + */ + private String yjhjy; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDNDXQJBXXB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDNDXQJBXXB.java new file mode 100644 index 000000000..d9cdd2aa4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDNDXQJBXXB.java @@ -0,0 +1,147 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.roomroot.jwgl.unit.BaseEntity; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 学员队年度学期基本信息表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName(value = "学员队年度学期基本信息表", schema = "JW") +public class XYDNDXQJBXXB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + @TableField("年度") + private Integer nd; + + /** + * 学员队编号 + */ + @TableField("学员队编号") + private String xydbh; + + /** + * 开学日期 + */ + @TableField("开学日期") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private LocalDate kxrq; + + /** + * 结束日期 + */ + @TableField("结束日期") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private LocalDate jsrq; + + /** + * 学期第次 + */ + @TableField("学期第次") + private Integer xqdc; + + /** + * 专用教室编号 + */ + @TableField("专用教室编号") + private String zyjsbh; + + /** + * 教学任务编号 + */ + @TableField("教学任务编号") + private String jxrwbh; + + /** + * 变动时间 + */ + @TableField("变动时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime bdsj; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 序号标识 + */ + @TableField("序号标识") + private Integer xhbs; + + /** + * 预设编班号 + */ + @TableField("预设编班号") + private Integer ysbbh; + + /** + * 节次类别 + */ + @TableField("节次类别") + private String jclb; + + /** + * 系统模式 + */ + @TableField("系统模式") + private String xtms; + + /** + * 开放教员排课 + */ + @TableField("开放教员排课") + private Integer kfjypk; + + /** + * json字典 + */ + @TableField("JSONZD") + private String jsonzd; + + /** + * 分析报告生成时间 + */ + @TableField("分析报告生成时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + private LocalDateTime fxbgscsj; + + /** + * 分析报告文档1编号 + */ + @TableField("分析报告文档1编号") + private String fxbgwd1bh; + + /** + * 分析报告文档2编号 + */ + @TableField("分析报告文档2编号") + private String fxbgwd2bh; + + /** + * 分析报告文档3编号 + */ + @TableField("分析报告文档3编号") + private String fxbgwd3bh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDPBXX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDPBXX.java new file mode 100644 index 000000000..765de6eca --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDPBXX.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员队拼班信息 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员队拼班信息") +public class XYDPBXX extends BaseEntity { + + /** + * 学员队编号 + */ + @TableId(value = "学员队编号", type = IdType.INPUT) + private String xydbh; + + /** + * 状态 + */ + private String zt; + + /** + * 计划开始时间 + */ + private LocalDateTime jhkssj; + + /** + * 计划结束时间 + */ + private LocalDateTime jhjssj; + + /** + * 结果确认时间 + */ + private LocalDateTime jgqrsj; + + /** + * 要求说明 + */ + private String yqsm; + + /** + * 备注 + */ + private String bz; + + /** + * 白名单条件 + */ + private String bmdtj; + + /** + * 黑名单条件 + */ + private String hmdtj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDQB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDQB.java new file mode 100644 index 000000000..9483386ad --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDQB.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学员队期表(教学班次附加信息:学号规则、注册设置) + */ +@Data +@TableName("学员队期表") +public class XYDQB { + + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + @TableField("名称") + private String mc; + + @TableField("学员队编号") + private String xydbh; + + /** 学号前缀 */ + @TableField("学号前缀") + private String xhqz; + + /** 学号位数(自动累加位数) */ + @TableField("学号位数") + private Integer xhws; + + @TableField("入学日期") + private LocalDateTime rxrq; + + @TableField("毕业日期") + private LocalDateTime byrq; + + @TableField("在校状态") + private Integer zxzt; + + /** 允许注册(0-关闭 1-开启) */ + @TableField("允许注册") + private Integer yxzc; + + @TableField("结束注册时间") + private LocalDateTime jszcsj; + + @TableField("备注") + private String bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDRWB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDRWB.java new file mode 100644 index 000000000..e0d6e3a96 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDRWB.java @@ -0,0 +1,273 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员队任务表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName(value = "学员队任务表", schema = "JW") +public class XYDRWB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + @TableField("年度") + private Integer nd; + + /** + * 学员队编号 + */ + @TableField("学员队编号") + private String xydbh; + + /** + * 课编号 + */ + @TableField("课编号") + private String kbh; + + /** + * 学期第次 + */ + @TableField("学期第次") + private Integer xqdc; + + /** + * 教员编号 + */ + @TableField("教员编号") + private String jybh; + + /** + * 课次序号 + */ + @TableField("课次序号") + private Integer kcxh; + + /** + * 教室编号 + */ + @TableField("教室编号") + private String jsbh; + + /** + * 人数 + */ + @TableField("人数") + private Integer rs; + + /** + * 周课时 + */ + @TableField("周课时") + private Integer zks; + + /** + * 学时 + */ + @TableField("学时") + private Integer xs; + + /** + * 课类型 + */ + @TableField("课类型") + private String klx; + + /** + * 简称 + */ + @TableField("简称") + private String jc; + + /** + * 考试地点 + */ + @TableField("考试地点") + private String ksdd; + + /** + * 考试编号 + */ + @TableField("考试编号") + private String ksbh; + + /** + * 选择 + */ + @TableField("选择") + private Integer xz; + + /** + * 教研室代号 + */ + @TableField("教研室代号") + private String jysdh; + + /** + * 教研室计划教员编号 + */ + @TableField("教研室计划教员编号") + private String jysjhjybh; + + /** + * 教研室计划备注 + */ + @TableField("教研室计划备注") + private String jysjhbz; + + /** + * 计划课次序号 + */ + @TableField("计划课次序号") + private Integer jhkcxh; + + /** + * 序号标识 + */ + @TableField("序号标识") + private Integer xhbs; + + /** + * 学员队学期编号 + */ + @TableField("学员队学期编号") + private String xydxqbh; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 学分 + */ + @TableField("学分") + private Float xf; + + /** + * 创建时间 + */ + @TableField("创建时间") + private LocalDateTime cjsj; + + /** + * 变动时间 + */ + @TableField("变动时间") + private LocalDateTime bdsj; + + /** + * 课表变动时间 + */ + @TableField("课表变动时间") + private LocalDateTime kbbdsj; + + /** + * 考试课时 + */ + @TableField("考试课时") + private Integer ksks; + + /** + * 编组 + */ + @TableField("编组") + private Integer bz2; + + /** + * 成绩分制 + */ + @TableField("成绩分制") + private String cjfz; + + /** + * 不计入学员平均分 + */ + @TableField("不计入学员平均分") + private Integer bjrxypjf; + + /** + * 理论学时 + */ + @TableField("理论学时") + private Integer llxs; + + /** + * 实践学时 + */ + @TableField("实践学时") + private Integer sjxs; + + /** + * 考试课时不显示 + */ + @TableField("考试课时不显示") + private Integer ksksbxs; + + /** + * 配档序号 + */ + @TableField("配档序号") + private Integer pdxh; + + /** + * 配档起始周 + */ + @TableField("配档起始周") + private Integer pdqsz; + + /** + * 配档按周 + */ + @TableField("配档按周") + private Integer pdaz; + + /** + * 配档占正课时间 + */ + @TableField("配档占正课时间") + private Integer pdzzksj; + + /** + * 配档编组 + */ + @TableField("配档编组") + private Integer pdbz; + + /** + * 配当同班异课选修编组 + */ + @TableField("配当同班异课选修编组") + private Integer pdtbykxxbz; + + /** + * 配档自定义学时分布数据 + */ + @TableField("配档自定义学时分布数据") + private String pdzdyxsfbsj; + + /** + * 配档启用自定义学时 + */ + @TableField("配档启用自定义学时") + private Integer pdqyzdyxs; + +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDSCKCB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDSCKCB.java new file mode 100644 index 000000000..fd559d07b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYDSCKCB.java @@ -0,0 +1,111 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员队输出课程表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName(value = "学员队输出课程表", schema = "JW") +public class XYDSCKCB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + @TableField("年度") + private Integer nd; + + /** + * 学员队编号 + */ + @TableField("学员队编号") + private String xydbh; + + /** + * 课程表 + */ + @TableField("课程表") + private String kcb; + + /** + * 更新时间 + */ + @TableField("更新时间") + private LocalDateTime gxsj; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 页号 + */ + @TableField("页号") + private Integer yh; + + /** + * 有课表 + */ + @TableField("有课表") + private Integer ykb; + + /** + * 课程表模版编号 + */ + @TableField("课程表模版编号") + private String kcbmbbh; + + /** + * 附加备注1 + */ + @TableField("附加备注1") + private String fjbz1; + + /** + * 附加备注2 + */ + @TableField("附加备注2") + private String fjbz2; + + /** + * 运行课程表 + */ + @TableField("运行课程表") + private String yxkcb; + + /** + * 运行备注 + */ + @TableField("运行备注") + private String yxbz; + + /** + * 运行课表更新时间 + */ + @TableField("运行课表更新时间") + private LocalDateTime yxkbgxsj; + + /** + * 有运行课表 + */ + @TableField("有运行课表") + private Integer yyxkb; + +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYFBLJB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYFBLJB.java new file mode 100644 index 000000000..de97af818 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYFBLJB.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员分班留级表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员分班留级表") +public class XYFBLJB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 学员信息表编号 + */ + private String xyxxbbh; + + /** + * 类别 + */ + private String lb; + + /** + * 原因 + */ + private String yy; + + /** + * 备注 + */ + private String bz; + + /** + * 状态 + */ + private String zt; + + /** + * 启用时间 + */ + private LocalDateTime qysj; + + /** + * 撤销时间 + */ + private LocalDateTime cxsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYJXQJB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYJXQJB.java new file mode 100644 index 000000000..254bf3c1c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYJXQJB.java @@ -0,0 +1,118 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学员教学请假表 + */ +@Data +@TableName(value = "学员教学请假表", schema = "JW") +public class XYJXQJB { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.ASSIGN_UUID) + private String bh; + + /** + * 名称 + */ + @TableField("名称") + private String mc; + + /** + * 起始时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("起始时间") + private LocalDateTime qssj; + + /** + * 结束时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("结束时间") + private LocalDateTime jssj; + + /** + * 状态 + */ + @TableField("状态") + private String zt; + + /** + * 原因 + */ + @TableField("原因") + private String yy; + + /** + * 请假类型 + */ + @TableField("请假类型") + private String qjlx; + + /** + * 备注 + */ + @TableField("备注") + private String bz; + + /** + * 学员队编号 + */ + @TableField("学员队编号") + private String xydbh; + + /** + * 课堂数 + */ + @TableField("课堂数") + private Integer kts; + + /** + * 学员人数 + */ + @TableField("学员人数") + private Integer xyrs; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("创建时间") + private LocalDateTime cjsj; + + /** + * 上报时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("上报时间") + private LocalDateTime sbsj; + + /** + * 目的地 + */ + @TableField("目的地") + private String mdd; + + /** + * 佐证文件1编号 + */ + @TableField("佐证文件1编号") + private String zzwj1bh; + + /** + * 佐证文件2编号 + */ + @TableField("佐证文件2编号") + private String zzwj2bh; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCBKCP.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCBKCP.java new file mode 100644 index 000000000..479d9060b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCBKCP.java @@ -0,0 +1,96 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员_课程板块测评 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员_课程板块测评") +public class XYKCBKCP extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 课程名称 + */ + private String kcmc; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 评价状态 + */ + private String pjzt; + + /** + * 结束日期 + */ + private LocalDateTime jsrq; + + /** + * 测评结果样式编号 + */ + private String cpjgysbh; + + /** + * 分值权重标准编号 + */ + private String fzqzbzbh; + + /** + * 预计测评结束时间 + */ + private LocalDateTime yjcpjssj; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 得分 + */ + private Float df; + + /** + * 课题数 + */ + private Integer kts; + + /** + * 已测课题数 + */ + private Integer yckts; + + /** + * 课题测评统计Json + */ + private String ktcptjjson; + + /** + * json字典 + */ + private String jsonzd; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCBKCPQZX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCBKCPQZX.java new file mode 100644 index 000000000..d31351331 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCBKCPQZX.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员_课程板块测评_权重项 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员_课程板块测评_权重项") +public class XYKCBKCPQZX extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学员_课程板块测评编号 + */ + private String xykcbkcpbh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 学员信息编号 + */ + private String xyxxbh; + + /** + * 分值权重标准_权重规则编号 + */ + private String fzqzbzqzgzbh; + + /** + * 得分 + */ + private Float df; + + /** + * 评价状态 + */ + private String pjzt; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 评价时间 + */ + private LocalDateTime pjsj; + + /** + * 原因 + */ + private String yy; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCCJ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCCJ.java new file mode 100644 index 000000000..fa76fa748 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCCJ.java @@ -0,0 +1,109 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学员课程成绩 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员课程成绩") +public class XYKCCJ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 课程成绩编号 + */ + private String kccjbh; + + /** + * 学员编号 + */ + private String xybh; + + /** + * 考试成绩 + */ + private Float kscj; + + /** + * 平时成绩 + */ + private Float pscj; + + /** + * 最终成绩 + */ + private Float zzcj; + + /** + * 备注 + */ + private String bz; + + /** + * 补考成绩 + */ + private Float bkcj; + + /** + * 补考次数 + */ + private Integer bkcs; + + /** + * 考试情况 + */ + private String ksqk; + + /** + * 原始考试成绩 + */ + private Float yskscj; + + /** + * 原始平时成绩 + */ + private Float yspscj; + + /** + * 期望学分 + */ + private Float qwxf; + + /** + * 年度 + */ + private Integer nd; + + /** + * 课类型 + */ + private String klx; + + /** + * 科目编号 + */ + private String kmbh; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 已通过 + */ + private Integer ytg; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCGCCJ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCGCCJ.java new file mode 100644 index 000000000..47bdd6f82 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYKCGCCJ.java @@ -0,0 +1,109 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学员课程过程成绩 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员课程过程成绩") +public class XYKCGCCJ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 课程过程成绩编号 + */ + private String kcgccjbh; + + /** + * 学员编号 + */ + private String xybh; + + /** + * 原始成绩 + */ + private String yscj; + + /** + * 最终成绩 + */ + private Float zzcj; + + /** + * 备注 + */ + private String bz; + + /** + * 补考成绩1 + */ + private Float bkcj1; + + /** + * 补考成绩2 + */ + private Float bkcj2; + + /** + * 补考成绩3 + */ + private Float bkcj3; + + /** + * 补考次数 + */ + private Integer bkcs; + + /** + * 考试情况 + */ + private String ksqk; + + /** + * 年度 + */ + private Integer nd; + + /** + * 课类型 + */ + private String klx; + + /** + * 科目编号 + */ + private String kmbh; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 补考原始成绩1 + */ + private String bkyscj1; + + /** + * 补考原始成绩2 + */ + private String bkyscj2; + + /** + * 补考原始成绩3 + */ + private String bkyscj3; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPCPBC.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPCPBC.java new file mode 100644 index 000000000..2f63a3e55 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPCPBC.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员满意度测评_参评班次 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员满意度测评_参评班次") +public class XYMYDCPCPBC extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 项目编号 + */ + private String xmbh; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 参评学员总数 + */ + private Integer cpxyzs; + + /** + * 有效评价学员数 + */ + private Integer yxpjxys; + + /** + * 备注 + */ + private String bz; + + /** + * 状态 + */ + private String zt; + + /** + * 开始时间 + */ + private LocalDateTime kssj; + + /** + * 结束时间 + */ + private LocalDateTime jssj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPCPXY.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPCPXY.java new file mode 100644 index 000000000..ea51f5bcf --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPCPXY.java @@ -0,0 +1,51 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员满意度测评_参评学员 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员满意度测评_参评学员") +public class XYMYDCPCPXY extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 项目编号 + */ + private String xmbh; + + /** + * 参评班级编号 + */ + private String cpbjbh; + + /** + * 学员编号 + */ + private String xybh; + + /** + * 提交状态 + */ + private Integer tjzt; + + /** + * 提交时间 + */ + private LocalDateTime tjsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPXM.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPXM.java new file mode 100644 index 000000000..7c7d47938 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPXM.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员满意度测评项目 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员满意度测评项目") +public class XYMYDCPXM extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 名称 + */ + private String mC; + + /** + * 创建时间 + */ + private LocalDateTime cJSJ; + + /** + * 状态 + */ + private String zT; + + /** + * 备注 + */ + private String bZ; + + /** + * 结束时间 + */ + private LocalDateTime jSSJ; + + /** + * 测评结果样式编号 + */ + private String cPJGYSBH; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPXMCPX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPXMCPX.java new file mode 100644 index 000000000..3849460bd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYMYDCPXMCPX.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员满意度测评项目_测评项 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员满意度测评项目_测评项") +public class XYMYDCPXMCPX extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 项目编号 + */ + private String xmbh; + + /** + * 名称 + */ + private String mc; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 测评总数 + */ + private Integer cpzs; + + /** + * 有效测评数 + */ + private Integer yxcps; + + /** + * 总分 + */ + private Float zf; + + /** + * 平均分 + */ + private Float pjf; + + /** + * 备注 + */ + private String bz; + + /** + * 结果集 + */ + private String jgj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYPBB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYPBB.java new file mode 100644 index 000000000..830386822 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYPBB.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员拼班表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员拼班表") +public class XYPBB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 学员信息编号 + */ + private String xyxxbh; + + /** + * 状态 + */ + private String zt; + + /** + * 离开原因 + */ + private String lkyy; + + /** + * 离开时间 + */ + private LocalDateTime lksj; + + /** + * 加入时间 + */ + private LocalDateTime jrsj; + + /** + * 加入原因 + */ + private String jryy; + + /** + * 备注 + */ + private String bz; + + /** + * 确认时间 + */ + private LocalDateTime qrsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYQJSPB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYQJSPB.java new file mode 100644 index 000000000..86d533492 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYQJSPB.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学员请假审批表 + */ +@Data +@TableName(value = "学员请假审批表", schema = "JW") +public class XYQJSPB { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.ASSIGN_UUID) + private String bh; + + /** + * 请假编号 + */ + @TableField("请假编号") + private String qjbh; + + /** + * 发回意见 + */ + @TableField("发回意见") + private String fhyj; + + /** + * 机关编号 + */ + @TableField("机关编号") + private String jgbh; + + /** + * 审批时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("审批时间") + private LocalDateTime spsj; + + /** + * 审批状态 + */ + @TableField("审批状态") + private String spzt; + + /** + * 审批人编号 + */ + @TableField("审批人编号") + private String sprbh; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYCPB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYCPB.java new file mode 100644 index 000000000..86c56e067 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYCPB.java @@ -0,0 +1,81 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员_授课教员_测评表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员_授课教员_测评表") +public class XYSKJYCPB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学期编号 + */ + private Integer xqbh; + + /** + * 学员信息编号 + */ + private String xyxxbh; + + /** + * 实施_课程编号 + */ + private String sskcbh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 课时数 + */ + private Integer kss; + + /** + * 结束日期 + */ + private LocalDateTime jsrq; + + /** + * 评价状态 + */ + private String pjzt; + + /** + * 得分 + */ + private Float df; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 评价时间 + */ + private LocalDateTime pjsj; + + /** + * 原因 + */ + private String yy; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYCPBQZX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYCPBQZX.java new file mode 100644 index 000000000..7f229ab6c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYCPBQZX.java @@ -0,0 +1,44 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学员_授课教员_测评表_权重项 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员_授课教员_测评表_权重项") +public class XYSKJYCPBQZX extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学员_授课教员_测评表编号 + */ + private String xyskjycpbbh; + + /** + * 分值权重标准_权重规则编号 + */ + private String fzqzbzqzgzbh; + + /** + * 得分 + */ + private Float df; + + /** + * 原因 + */ + private String yy; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYJCCPB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYJCCPB.java new file mode 100644 index 000000000..e84b6f71d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYJCCPB.java @@ -0,0 +1,106 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员_授课教员_节次_测评表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员_授课教员_节次_测评表") +public class XYSKJYJCCPB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 实施_课程表编号 + */ + private String sskcbbh; + + /** + * 日期 + */ + private LocalDateTime rq; + + /** + * 节次 + */ + private Integer jc; + + /** + * 学期编号 + */ + private Integer xqbh; + + /** + * 学员信息编号 + */ + private String xyxxbh; + + /** + * 教员编号 + */ + private String jybh; + + /** + * 课时数 + */ + private Integer kss; + + /** + * 得分 + */ + private Float df; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 评价时间 + */ + private LocalDateTime pjsj; + + /** + * 原因 + */ + private String yy; + + /** + * 课编号 + */ + private String kbh; + + /** + * 测评结果样式编号 + */ + private String cpjgysbh; + + /** + * 评价状态 + */ + private String pjzt; + + /** + * 学员队编号 + */ + private String xydbh; + + /** + * 分值权重标准编号 + */ + private String fzqzbzbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYJCCPBQZX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYJCCPBQZX.java new file mode 100644 index 000000000..21fbf063f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYSKJYJCCPBQZX.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 学员_授课教员_节次_测评表_权重项 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员_授课教员_节次_测评表_权重项") +public class XYSKJYJCCPBQZX extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学员_授课教员_节次_测评编号 + */ + private String xyskjyjccpbh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 学员信息编号 + */ + private String xyxxbh; + + /** + * 分值权重标准_权重规则编号 + */ + private String fzqzbzqzgzbh; + + /** + * 得分 + */ + private Float df; + + /** + * 原因 + */ + private String yy; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXJYDSQB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXJYDSQB.java new file mode 100644 index 000000000..97c76d1b8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXJYDSQB.java @@ -0,0 +1,96 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员学籍异动申请表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员学籍异动申请表") +public class XYXJYDSQB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学员编号 + */ + private String xybh; + + /** + * 申请类型 + */ + private String sqlx; + + /** + * 事由 + */ + private String sy; + + /** + * 备注 + */ + private String bz; + + /** + * 状态 + */ + private String zt; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 提交时间 + */ + private LocalDateTime tjsj; + + /** + * 学员队审核意见 + */ + private String xydshyj; + + /** + * 学员队干部编号 + */ + private String xydgbbh; + + /** + * 学员队审核时间 + */ + private LocalDateTime xydshsj; + + /** + * 管理员审核时间 + */ + private LocalDateTime glyshsj; + + /** + * 管理员审核意见 + */ + private String glyshyj; + + /** + * 管理员账户编号 + */ + private String glyzhbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXJYJJGB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXJYJJGB.java new file mode 100644 index 000000000..e6e9a75d5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXJYJJGB.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员学籍预警结果表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员学籍预警结果表") +public class XYXJYJJGB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 年度 + */ + private Integer nd; + + /** + * 预警条件编号 + */ + private String yjtjbh; + + /** + * 学员编号 + */ + private String xybh; + + /** + * 创建时间 + */ + private LocalDateTime cjsj; + + /** + * 发布 + */ + private Integer fb; + + /** + * 发布时间 + */ + private LocalDateTime fbsj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXJYJTJB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXJYJTJB.java new file mode 100644 index 000000000..58ac514b7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXJYJTJB.java @@ -0,0 +1,116 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员学籍预警条件表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员学籍预警条件表") +public class XYXJYJTJB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 名称 + */ + private String mC; + + /** + * 培训类型 + */ + private String pXLX; + + /** + * 培训层次 + */ + private String pXCC; + + /** + * 当前学期考试不及格门数上限 + */ + private Integer dQXQKSBJGMSSX; + + /** + * 当前学期考试不及格门数下限 + */ + private Integer dQXQKSBJGMSXX; + + /** + * 当前学期考查不及格门数上限 + */ + private Integer dQXQKCBJGMSSX; + + /** + * 当前学期考查不及格门数下限 + */ + private Integer dQXQKCBJGMSXX; + + /** + * 当前学期不及格门数上限 + */ + private Integer dQXQBJGMSSX; + + /** + * 当前学期不及格门数下限 + */ + private Integer dQXQBJGMSXX; + + /** + * 全部考试不及格门数上限 + */ + private Integer qBKSBJGMSSX; + + /** + * 全部考试不及格门数下限 + */ + private Integer qBKSBJGMSXX; + + /** + * 全部考查不及格门数上限 + */ + private Integer qBKCBJGMSSX; + + /** + * 全部考查不及格门数下限 + */ + private Integer qBKCBJGMSXX; + + /** + * 全部不及格门数上限 + */ + private Integer qBBJGMSSX; + + /** + * 全部不及格门数下限 + */ + private Integer qBBJGMSXX; + + /** + * 停用 + */ + private Boolean tY; + + /** + * 版本 + */ + private String bB; + + /** + * 修改时间 + */ + private LocalDateTime xGSJ; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXX.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXX.java new file mode 100644 index 000000000..2fc711164 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXX.java @@ -0,0 +1,219 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; + +/** + * 学员信息表 + *

+ * 存储学员从入学至毕业的全过程信息,包括学籍、分班、论文等。 + *

+ */ +@Data +@TableName("学员信息表") +public class XYXX { + + /** 编号 */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** 学号 */ + @TableField("学号") + private String xh; + + /** 姓名 */ + @TableField("姓名") + private String xm; + + /** 登录信息编号 */ + @TableField("登录信息编号") + private String dlxxbh; + + /** 学员队期编号 */ + @TableField("学员队期编号") + private String xydqbh; + + /** 证件号码 */ + @TableField("证件号码") + private String zjhm; + + /** 政治面貌 */ + @TableField("政治面貌") + private String zzmm; + + /** 党团时间 */ + @TableField("党团时间") + private String dtsj; + + /** 入伍年月日 */ + @TableField("入伍年月日") + private String rwynr; + + /** 性别 */ + @TableField("性别") + private String xb; + + /** 出生日期 */ + @TableField("出生日期") + private String csrq; + + /** 名族 */ + @TableField("名族") + private String mz; + + /** 籍贯 */ + @TableField("籍贯") + private String jg; + + /** 原部职别 */ + @TableField("原部职别") + private String ybzb; + + /** 邮政编码 */ + @TableField("邮政编码") + private String yzbm; + + /** 文化程度 */ + @TableField("文化程度") + private String whcd; + + /** 学位 */ + @TableField("学位") + private String xw; + + /** 学员类别 */ + @TableField("学员类别") + private String xylb; + + /** 军衔文职 */ + @TableField("军衔文职") + private String jxwz; + + /** 所属军区 */ + @TableField("所属军区") + private String ssjq; + + /** 骨干任职 */ + @TableField("骨干任职") + private String ggrz; + + /** 本科毕业院校 */ + @TableField("本科毕业院校") + private String bkbyyx; + + /** 国防生 */ + @TableField("国防生") + private String gfs; + + /** 考生号 */ + @TableField("考生号") + private String ksh; + + /** 备注 */ + @TableField("备注") + private String bz; + + /** 身份证号 */ + @TableField("身份证号") + private String sfzh; + + /** 退学状态 */ + @TableField("退学状态") + private String txzt; + + /** 留级状态 */ + @TableField("留级状态") + private String ljzt; + + /** 分班状态 */ + @TableField("分班状态") + private String fbzt; + + /** 论文编号 */ + @TableField("论文编号") + private String lwbh; + + /** 论文题目 */ + @TableField("论文题目") + private String lwtm; + + /** 论文摘要 */ + @TableField("论文摘要") + private String lwzy; + + /** 拼音 */ + @TableField("拼音") + private String py; + + /** 成绩报告单编号 */ + @TableField("成绩报告单编号") + private String cjbgdbh; + + /** 允许查看成绩 */ + @TableField("允许查看成绩") + private String yxckcj; + + /** json字典 */ + @TableField("json字典") + private String jsonzd; + + /** 当前班次编号 */ + @TableField("当前班次编号") + private String dqbzbh; + + /** 论文答辩文档编号 */ + @TableField("论文答辩文档编号") + private String lwdbwdbh; + + /** 标签 */ + @TableField("标签") + private String bq; + + /** 联系电话 */ + @TableField("联系电话") + private String lxdh; + + /** 通信地址 */ + @TableField("通信地址") + private String txdz; + + /** 单位联络人信息 */ + @TableField("单位联络人信息") + private String dwllrxx; + + /** 奖励处分情况 */ + @TableField("奖励处分情况") + private String jlcfqk; + + /** 已注册 */ + @TableField("已注册") + private String yzc; + + /** 军人证件类型 */ + @TableField("军人证件类型") + private String jrzjlx; + + /** 曾用名 */ + @TableField("曾用名") + private String cym; + + /** 入伍地 */ + @TableField("入伍地") + private String rwd; + + /** 出生地 */ + @TableField("出生地") + private String csd; + + /** 生源地 */ + @TableField("生源地") + private String syd; + + /** 上一个学员队期编号(异动前归属) */ + @TableField("上一个部别编号") + private String previousDept; + + /** 异动历史JSON数组,每条记录含 from/to/time/reason */ + @TableField("异动历史") + private String transferHistory; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXXB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXXB.java new file mode 100644 index 000000000..514a0ba12 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXXB.java @@ -0,0 +1,266 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员信息表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员信息表") +public class XYXXB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学号 + */ + private String xh; + + /** + * 姓名 + */ + private String xm; + + /** + * 登录信息编号 + */ + private String dlxxbh; + + /** + * 学员队期编号 + */ + private String xydqbh; + + /** + * 证件号码 + */ + private String zjhm; + + /** + * 政治面貌 + */ + private String zzmm; + + /** + * 党团时间 + */ + private LocalDateTime dtsj; + + /** + * 入伍年月日 + */ + private LocalDateTime rwnyr; + + /** + * 性别 + */ + private Integer xb; + + /** + * 出生日期 + */ + private LocalDateTime csrq; + + /** + * 名族 + */ + private String mz; + + /** + * 籍贯 + */ + private String jg; + + /** + * 原部职别 + */ + private String ybzb; + + /** + * 邮政编码 + */ + private String yzbm; + + /** + * 文化程度 + */ + private String whcd; + + /** + * 学位 + */ + private String xw; + + /** + * 学员类别 + */ + private String xylb; + + /** + * 军衔文职 + */ + private String jxwz; + + /** + * 所属军区 + */ + private String ssjq; + + /** + * 骨干任职 + */ + private String ggrz; + + /** + * 本科毕业院校 + */ + private String bkbyyx; + + /** + * 国防生 + */ + private String gfs; + + /** + * 考生号 + */ + private String ksh; + + /** + * 备注 + */ + private String bz; + + /** + * 身份证号 + */ + private String sfzh; + + /** + * 退学状态 + */ + private Integer txzt; + + /** + * 留级状态 + */ + private Integer ljzt; + + /** + * 分班状态 + */ + private Integer fbzt; + + /** + * 论文编号 + */ + private String lwbh; + + /** + * 论文题目 + */ + private String lwtm; + + /** + * 论文摘要 + */ + private String lwzy; + + /** + * 拼音 + */ + private String py; + + /** + * 成绩报告单编号 + */ + private String cjbgdbh; + + /** + * 允许查看成绩 + */ + private Integer yxckcj; + + /** + * json字典 + */ + private String jsonzd; + + /** + * 当前班次编号 + */ + private String dqbcbh; + + /** + * 论文答辩文档编号 + */ + private String lwdbwdbh; + + /** + * 标签 + */ + private String bq; + + /** + * 联系电话 + */ + private String lxdh; + + /** + * 通信地址 + */ + private String txdz; + + /** + * 单位联络人信息 + */ + private String dwllrxx; + + /** + * 奖励处分情况 + */ + private String jlcfqk; + + /** + * 已注册 + */ + private Integer yzc; + + /** + * 军人证件类型 + */ + private String jrzjlx; + + /** + * 曾用名 + */ + private String zym; + + /** + * 入伍地 + */ + private String rwd; + + /** + * 出生地 + */ + private String csd; + + /** + * 生源地 + */ + private String syd; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXXBB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXXBB.java new file mode 100644 index 000000000..40708f190 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXXBB.java @@ -0,0 +1,191 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员信息备表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员信息备表") +public class XYXXBB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 出生地代码 + */ + private String csddm; + + /** + * 出生地 + */ + private String csd; + + /** + * 军人证件类型 + */ + private String jrzjlx; + + /** + * 学生来源 + */ + private String xsly; + + /** + * 培训层次 + */ + private String pxcc; + + /** + * 来源地区代码 + */ + private String lydqdm; + + /** + * 来源地 + */ + private String lyd; + + /** + * 入学方式 + */ + private String rxfs; + + /** + * 入伍地区 + */ + private String rwdq; + + /** + * 军人类型 + */ + private String jrlx; + + /** + * 干部类别 + */ + private String gblb; + + /** + * 职务等级 + */ + private String zwdj; + + /** + * 技术职务 + */ + private String jszw; + + /** + * 技术等级 + */ + private String jsdj; + + /** + * 学习形式 + */ + private String xxxs; + + /** + * 办学类型 + */ + private String bxlx; + + /** + * 家庭地址 + */ + private String jtdz; + + /** + * 家庭电话 + */ + private String jtdh; + + /** + * 培养目标 + */ + private String pymb; + + /** + * 入学前最高学历 + */ + private String rxqzgxl; + + /** + * 前毕业时间 + */ + private LocalDateTime qbysj; + + /** + * 前毕业院校 + */ + private String qbyyx; + + /** + * 前毕业类型 + */ + private String qbylx; + + /** + * 前学历证书号 + */ + private String qxlzsh; + + /** + * 培训类型 + */ + private String pxlx; + + /** + * 是否飞行员 + */ + private Integer sffxy; + + /** + * 是否超报 + */ + private Integer sfcb; + + /** + * 批准书号 + */ + private String pzsh; + + /** + * 学历层次 + */ + private String xlcc; + + /** + * 参加高考省市 + */ + private String cjgkss; + + /** + * 入学前是否军人 + */ + private Integer rxqsfjr; + + /** + * 是否合训 + */ + private Integer sfhx; + + /** + * 入学成绩 + */ + private String rxcj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXXGGRZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXXGGRZ.java new file mode 100644 index 000000000..5c6de27fb --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/XYXXGGRZ.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 学员信息更改日志 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("学员信息更改日志") +public class XYXXGGRZ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 学员信息编号 + */ + private String xyxxbh; + + /** + * 事由 + */ + private String sy; + + /** + * 时间 + */ + private LocalDateTime sj; + + /** + * 承办人 + */ + private String cbr; + + /** + * 备注 + */ + private String bz; + + /** + * 类型 + */ + private String lx; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YCB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YCB.java new file mode 100644 index 000000000..0afabf1a8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YCB.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 异常表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("异常表") +public class YCB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * URL + */ + private String uRL; + + /** + * 异常类型 + */ + private String yCLX; + + /** + * 内容 + */ + private String nR; + + /** + * 创建时间 + */ + private LocalDateTime cJSJ; + + /** + * 用户ID + */ + private String yHID; + + /** + * 用户姓名 + */ + private String yHXM; + + /** + * 用户类别 + */ + private String yHLB; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YHB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YHB.java new file mode 100644 index 000000000..174e3f382 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YHB.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("用户表") +public class YHB extends BaseEntity { + + /** + * 用户帐号 + */ + @TableId(value = "用户帐号", type = IdType.INPUT) + private String yhzh; + + /** + * 用户密码 + */ + private String yhmm; + + /** + * 用户姓名 + */ + private String yhxm; + + /** + * 用户权限 + */ + private Integer yhqx; + + /** + * 密码提示问题 + */ + private String mmtswt; + + /** + * 密码回答问题 + */ + private String mmhdwt; + + /** + * 电话 + */ + private String dh; + + /** + * 教学管理机构编号 + */ + private String jxgljgbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YHDLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YHDLB.java new file mode 100644 index 000000000..b8cf9e6e2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YHDLB.java @@ -0,0 +1,29 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 用户登录表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("用户登录表") +public class YHDLB extends BaseEntity { + + /** + * 用户账号 + */ + @TableId(value = "用户账号", type = IdType.INPUT) + private String yhzh; + + /** + * 登录信息编号 + */ + private String dlxxbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YJFKB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YJFKB.java new file mode 100644 index 000000000..108d310c0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/YJFKB.java @@ -0,0 +1,51 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 意见反馈表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("意见反馈表") +public class YJFKB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bH; + + /** + * 类别 + */ + private String lB; + + /** + * 主题 + */ + private String zT; + + /** + * 内容 + */ + private String nR; + + /** + * 登录信息表编号 + */ + private String dLXXBBH; + + /** + * 创建时间 + */ + private LocalDateTime cJSJ; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHPDGZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHPDGZ.java new file mode 100644 index 000000000..5d20ab373 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHPDGZ.java @@ -0,0 +1,126 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 综合评定规则 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("综合评定规则") +public class ZHPDGZ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 名称 + */ + private String mc; + + /** + * 专家评估分单项计算规则编号 + */ + private String zjpgfdxjsgzbh; + + /** + * 领导评估分单项计算规则编号 + */ + private String ldpgfdxjsgzbh; + + /** + * 学员评估分单项计算规则编号 + */ + private String xypgfdxjsgzbh; + + /** + * A级分数线 + */ + private Float ajfsx; + + /** + * B级分数线 + */ + private Float bjfsx; + + /** + * C级分数线 + */ + private Float cjfsx; + + /** + * D级分数线 + */ + private Float djfsx; + + /** + * 学员评估分C级分数线 + */ + private Float xypgfcjfsx; + + /** + * 排序最后C级比例 + */ + private Float pxzhcjbl; + + /** + * 排序最前A级比例 + */ + private Float pxzqajbl; + + /** + * 教学竞赛获奖A级分数线 + */ + private Float jxjshjajfsx; + + /** + * 教学竞赛获奖A级级别线 + */ + private String jxjshjajjbx; + + /** + * 修改时间 + */ + private LocalDateTime xgsj; + + /** + * 备注 + */ + private String bz; + + /** + * 状态 + */ + private String zt; + + /** + * 生效时间 + */ + private LocalDateTime sxsj; + + /** + * 废弃时间 + */ + private LocalDateTime fqsj; + + /** + * 有责任事故最高等级 + */ + private String yzrsgzgdj; + + /** + * 全年辅讲最高等级 + */ + private String qnfjzgdj; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHPDGZJYSJLRDGZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHPDGZJYSJLRDGZ.java new file mode 100644 index 000000000..eefd4aa10 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHPDGZJYSJLRDGZ.java @@ -0,0 +1,64 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 综合评定规则_教研室结论认定规则 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("综合评定规则_教研室结论认定规则") +public class ZHPDGZJYSJLRDGZ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 综合评定规则编号 + */ + private String zhpdgzbh; + + /** + * 序号 + */ + private String xh; + + /** + * 目标级别 + */ + private String mbjb; + + /** + * 人均最低级别 + */ + private String rjzdjb; + + /** + * AB级别最低比例 + */ + private Float abjbzdbl; + + /** + * A级别最低比例 + */ + private Float ajbzdbl; + + /** + * 启用责任事故否定 + */ + private Integer qyzrsgfd; + + /** + * 备注 + */ + private String bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHPDGZZHJSGZ.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHPDGZZHJSGZ.java new file mode 100644 index 000000000..04673d1fe --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHPDGZZHJSGZ.java @@ -0,0 +1,79 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 综合评定规则_综合计算规则 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("综合评定规则_综合计算规则") +public class ZHPDGZZHJSGZ extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 综合评定规则编号 + */ + private String zhpdgzbh; + + /** + * 序号 + */ + private String xh; + + /** + * 缺领导评估分 + */ + private Integer qldpgf; + + /** + * 缺专家评估分 + */ + private Integer qzjpgf; + + /** + * 缺同行评估分 + */ + private Integer qtxpgf; + + /** + * 缺学员评估分 + */ + private Integer qxypgf; + + /** + * 领导评估分系数 + */ + private Float ldpgfxs; + + /** + * 专家评估分系数 + */ + private Float zjpgfxs; + + /** + * 同行评估分系数 + */ + private Float txpgfxs; + + /** + * 学员评估分系数 + */ + private Float xypgfxs; + + /** + * 备注 + */ + private String bz; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHZTB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHZTB.java new file mode 100644 index 000000000..4980ccc3d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZHZTB.java @@ -0,0 +1,50 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 账户状态表 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("账户状态表") +public class ZHZTB extends BaseEntity { + + /** + * 登录信息编号(主键,关联登录信息表.编号) + */ + @TableId(value = "登录信息编号", type = IdType.INPUT) + private String dLXXBH; + + /** + * 启用状态(1=启用,0=停用) + */ + private Integer qYZT; + + /** + * 停用原因 + */ + private String tYYY; + + /** + * 停用时间 + */ + private LocalDateTime tYSJ; + + /** + * 合并目标登录信息编号(关联登录信息表.编号) + */ + private String hBMBDLXXBH; + + /** + * 合并时间 + */ + private LocalDateTime hBSJ; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZJDDCPMB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZJDDCPMB.java new file mode 100644 index 000000000..0e1a5bfc8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZJDDCPMB.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 专家督导_测评模板 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("专家督导_测评模板") +public class ZJDDCPMB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 名称 + */ + private String mc; + + /** + * 文件编号 + */ + private String wjbh; + + /** + * 停用状态 + */ + private Boolean tyzt; + + /** + * 备注 + */ + private String bz; + + /** + * 停用时间 + */ + private LocalDateTime tysj; + + /** + * 通告内容 + */ + private String tgnr; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZYB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZYB.java new file mode 100644 index 000000000..2e982227e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZYB.java @@ -0,0 +1,143 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 专业表 + *

+ * 存储专业/培养方案的全部基础信息。 + * 多版本通过培训类型(培训类型)区分。 + * 逻辑删除字段:停用(0-启用,1-停用/删除)。 + *

+ */ +@Data +@TableName("专业表") +public class ZYB { + + /** 专业代号(主键) */ + @TableId(value = "专业代号", type = IdType.AUTO) + private String zydh; + + /** 专业名称 */ + @TableField("专业名称") + private String zymc; + + /** 专业方向 */ + @TableField("专业方向") + private String zyfx; + + /** 学年制 */ + @TableField("学年制") + private String xnz; + + /** 学期数 */ + @TableField("学期数") + private Integer xqs; + + /** 专业备注 */ + @TableField("专业备注") + private String zybz; + + /** 专业规范 */ + @TableField("专业规范") + private String zygf; + + /** 专业代码 */ + @TableField("专业代码") + private String zydm; + + /** 专业版本 */ + @TableField("专业版本") + private String zybb; + + /** 停用(0-启用,1-停用/删除) */ + @TableField("停用") + private Boolean ty; + + /** 启用时间 */ + @TableField("启用时间") + private LocalDateTime qysj; + + /** 停用时间 */ + @TableField("停用时间") + private LocalDateTime tysj; + + /** 教学管理机构编号 */ + @TableField("教学管理机构编号") + private String jxgljgbh; + + /** 培训层次 */ + @TableField("培训层次") + private String pxcc; + + /** 培训类型 */ + @TableField("培训类型") + private String pxlx; + + /** 学科专业信息标识号 */ + @TableField("学科专业信息标识号") + private String xkzyxxbsh; + + /** 培训类型2 */ + @TableField("培训类型2") + private String pxlx2; + + /** 学员类别 */ + @TableField("学员类别") + private String xylb; + + /** 自定义分类 */ + @TableField("自定义分类") + private String zdyfl; + + /** 专业标识号 */ + @TableField("专业标识号") + private String zybsh; + + /** 序号标识 */ + @TableField("序号标识") + private Integer xhbs; + + /** 简称 */ + @TableField("简称") + private String jc; + + /** 节次类别 */ + @TableField("节次类别") + private String jclb; + + /** 系统模式 */ + @TableField("系统模式") + private String xtms; + + /** json字典 */ + @TableField("JSONZD") + private String jsonzd; + + /** 主干专业 */ + @TableField("主干专业") + private Boolean zgzy; + + /** 规范名称 */ + @TableField("规范名称") + private String gfmc; + + /** 教学大纲编号 */ + @TableField("教学大纲编号") + private String jxdgbh; + + /** 人培编号 */ + @TableField("人培编号") + private String rpbh; + + /** 培养目标 */ + @TableField("培养目标") + private String pymb; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZYBZLB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZYBZLB.java new file mode 100644 index 000000000..9cb9be35d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZYBZLB.java @@ -0,0 +1,34 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.roomroot.jwgl.unit.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 专业保障类别 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("专业保障类别") +public class ZYBZLB extends BaseEntity { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.INPUT) + private String bh; + + /** + * 专业代号 + */ + private String zydh; + + /** + * 保障类别编号 + */ + private String bzlbbh; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZYJXJHB.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZYJXJHB.java new file mode 100644 index 000000000..d058edcc8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/ZYJXJHB.java @@ -0,0 +1,152 @@ +package com.roomroot.jwgl.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableField; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 专业教学计划表 + */ +@Data +@TableName(value = "专业教学计划表", schema = "JW") +public class ZYJXJHB { + + /** + * 编号 + */ + @TableId(value = "编号", type = IdType.ASSIGN_ID) + private String bh; + + /** + * 专业代号 + */ + @TableField("专业代号") + private String zydh; + + /** + * 课编号 + */ + @TableField("课编号") + private String kbh; + + /** + * 学期第次 + */ + @TableField("学期第次") + private Integer xqdc; + + /** + * 课类型 + */ + @TableField("课类型") + private String klx; + + /** + * 学时 + */ + @TableField("学时") + private Integer xs; + + /** + * 简称 + */ + @TableField("简称") + private String jc; + + /** + * 停用(0-正常,1-停用) + */ + @TableField("停用") + private Integer ty; + + /** + * 启用时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("启用时间") + private LocalDateTime qysj; + + /** + * 停用时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") + @TableField("停用时间") + private LocalDateTime tysj; + + /** + * 学分 + */ + @TableField("学分") + private Float xf; + + /** + * 课程定位 + */ + @TableField("课程定位") + private String kcdw; + + /** + * 考试课时 + */ + @TableField("考试课时") + private Integer ksks; + + /** + * 模块 + */ + @TableField("模块") + private String mk; + + /** + * 成绩分制 + */ + @TableField("成绩分制") + private String cjfz; + + /** + * 不计入学员平均分(0-否,1-是) + */ + @TableField("不计入学员平均分") + private Integer bjrxypjf; + + /** + * 理论学时 + */ + @TableField("理论学时") + private Integer llxs; + + /** + * 实践学时 + */ + @TableField("实践学时") + private Integer sjxs; + + /** + * 周课时 + */ + @TableField("周课时") + private Integer zks; + + /** + * 考试课时不显示(0-否,1-是) + */ + @TableField("考试课时不显示") + private Integer ksksbxs; + + /** + * 大纲课程(0-否,1-是) + */ + @TableField("大纲课程") + private Integer dgkc; + + /** + * 教研室代号 + */ + @TableField("教研室代号") + private String jysdh; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/accountmanagement/AccountRecord.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/accountmanagement/AccountRecord.java new file mode 100644 index 000000000..88253ca95 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/accountmanagement/AccountRecord.java @@ -0,0 +1,109 @@ +package com.roomroot.jwgl.entity.accountmanagement; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 账户管理内部持久化记录。 + * + *

该类型只在 Mapper 与业务层之间传递,不直接接收请求或返回页面。

+ */ +@Data +public class AccountRecord { + + /** + * 登录信息编号。 + */ + private String id; + + /** + * BCrypt 编码后的不可逆密码摘要。 + */ + private String password; + + /** + * 用户姓名。 + */ + private String userName; + + /** + * 密码提示问题。 + */ + private String passwordQuestion; + + /** + * 密码提示答案。 + */ + private String passwordAnswer; + + /** + * 联系电话。 + */ + private String phone; + + /** + * 上次登录时间。 + */ + private LocalDateTime lastLoginAt; + + /** + * 本次登录时间。 + */ + private LocalDateTime currentLoginAt; + + /** + * 连续登录失败次数。 + */ + private Integer failedAttempts; + + /** + * 最近一次登录失败时间。 + */ + private LocalDateTime failedAt; + + /** + * 身份证号码。 + */ + private String idCard; + + /** + * 登录账号。 + */ + private String loginName; + + /** + * 统一身份账号。 + */ + private String unifiedAccount; + + /** + * 自我介绍。 + */ + private String introduction; + + /** + * 账户是否启用。 + */ + private Boolean enabled; + + /** + * 停用原因。 + */ + private String disabledReason; + + /** + * 停用时间。 + */ + private LocalDateTime disabledAt; + + /** + * 合并后的目标账户编号。 + */ + private String mergedToAccountId; + + /** + * 账户合并时间。 + */ + private LocalDateTime mergedAt; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/accountmanagement/AccountRoleRecord.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/accountmanagement/AccountRoleRecord.java new file mode 100644 index 000000000..68a296e8b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/entity/accountmanagement/AccountRoleRecord.java @@ -0,0 +1,43 @@ +package com.roomroot.jwgl.entity.accountmanagement; + +import lombok.Data; + +/** + * 账户角色批量查询内部持久化记录。 + * + *

该类型只负责携带账户编号和角色字段,供业务层按账户分组, + * 不直接接收页面请求,也不作为接口返回对象。

+ */ +@Data +public class AccountRoleRecord { + + /** + * 角色所属登录账户编号。 + */ + private String accountId; + + /** + * 角色类型编码。 + */ + private String roleType; + + /** + * 角色对应身份编号。 + */ + private String targetId; + + /** + * 角色对应身份名称。 + */ + private String targetName; + + /** + * 身份所属部别、教研室、班次等数据范围说明。 + */ + private String dataScope; + + /** + * 角色身份当前是否仍处于可用状态。 + */ + private Boolean enabled; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/interceptor/AccountManagementAuthorizationInterceptor.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/interceptor/AccountManagementAuthorizationInterceptor.java new file mode 100644 index 000000000..152d3aa5b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/interceptor/AccountManagementAuthorizationInterceptor.java @@ -0,0 +1,448 @@ +package com.roomroot.jwgl.interceptor; + +import com.roomroot.jwgl.mapper.AccountManagementAuthorizationMapper; +import com.roomroot.jwgl.unit.Result; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; +import tools.jackson.databind.ObjectMapper; + + +import java.io.IOException; + +import static com.roomroot.jwgl.utils.AccountManagementConstants.ACCOUNT_ADMINISTRATOR_ROLE_CATEGORY; +import static com.roomroot.jwgl.utils.AccountManagementConstants.ACCOUNT_PERMISSION_DENIED_MESSAGE; +import static com.roomroot.jwgl.utils.AccountManagementConstants.ACCOUNT_UNAVAILABLE_MESSAGE; +import static com.roomroot.jwgl.utils.AccountManagementConstants.AUTHENTICATION_REQUIRED_MESSAGE; + +/** + * 在线账户状态和账户管理权限拦截器。 + * + *

该拦截器复用系统现有 Servlet 会话和 MemberOnlineInfo 在线用户记录, + * 对所有已经建立会话的请求校验账户是否仍然可用,并且只允许 + * “教务处超级管理员”访问账户管理接口。

+ */ +@Component +public class AccountManagementAuthorizationInterceptor + implements HandlerInterceptor { + + /** + * 账户管理接口统一路径前缀。 + */ + private static final String ACCOUNT_MANAGEMENT_PATH = + "/account-management"; + + /** + * SSO 会话解析接口路径。 + * + *

该接口本身负责建立本地登录状态,因此必须允许匿名请求进入。

+ */ + private static final String SSO_SESSION_RESOLVE_PATH = + ACCOUNT_MANAGEMENT_PATH + "/resolve-sso-session"; + + /** + * 本地账号密码登录接口路径。 + * + *

该接口负责建立本项目自己的 Servlet Session,因此没有登录 Session 时也必须允许进入。

+ */ + private static final String LOCAL_LOGIN_PATH = + ACCOUNT_MANAGEMENT_PATH + "/login"; + + /** + * 本地登录验证码接口路径。 + * + *

该接口需要为匿名用户创建验证码 Session,因此必须在登录校验前放行。

+ */ + private static final String LOGIN_CAPTCHA_PATH = + ACCOUNT_MANAGEMENT_PATH + "/login-captcha"; + + /** + * 退出登录接口路径。 + * + *

退出操作需要允许无效或已过期会话进入 Controller,才能实现幂等退出。

+ */ + private static final String LOGOUT_PATH = + ACCOUNT_MANAGEMENT_PATH + "/logout"; + + /** + * 当前登录账户修改密码接口路径。 + * + *

该接口属于个人账户操作,只要求账户已经登录且可用,不要求管理员角色。

+ */ + private static final String PASSWORD_CHANGE_PATH = + ACCOUNT_MANAGEMENT_PATH + "/change-password"; + + /** + * 当前登录账户查询本人登录信息接口路径。 + * + *

该接口只读取当前会话对应账户的数据,因此不要求账户管理员角色。

+ */ + private static final String LOGIN_INFORMATION_PATH = + ACCOUNT_MANAGEMENT_PATH + "/login-information"; + + /** + * 账户管理鉴权专用数据访问层。 + */ + private final AccountManagementAuthorizationMapper authorizationMapper; + + /** + * 将统一返回对象序列化为 JSON 的转换器。 + */ + private final ObjectMapper objectMapper; + + /** + * 构造账户管理接口鉴权拦截器。 + * + * @param authorizationMapper 账户管理鉴权专用数据访问层 + * @param objectMapper JSON 转换器 + */ + public AccountManagementAuthorizationInterceptor( + AccountManagementAuthorizationMapper authorizationMapper, + ObjectMapper objectMapper) { + // 第一步:保存鉴权 Mapper,后续根据当前会话查询可信角色信息。 + this.authorizationMapper = authorizationMapper; + + // 第二步:保存系统统一 JSON 转换器,保证错误响应格式与其他接口一致。 + this.objectMapper = objectMapper; + } + + /** + * 在 Controller 执行前校验在线账户状态和账户管理权限。 + * + * @param request 当前 HTTP 请求 + * @param response 当前 HTTP 响应 + * @param handler 当前请求处理器 + * @return {@code true} 表示允许继续执行,{@code false} 表示已经拒绝请求 + * @throws IOException 写入 JSON 响应失败时抛出 + */ + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler) throws IOException { + /* + * 第一步:浏览器的 CORS 预检请求不包含业务登录信息, + * 应直接放行并交由现有 CorsFilter 返回跨域响应头。 + */ + if (HttpMethod.OPTIONS.matches(request.getMethod())) { + return true; + } + + /* + * 第二步:允许退出登录接口匿名进入。 + * 即使本地 Session 已过期或在线记录已被清理,也应返回统一的退出成功结果。 + */ + if (isLogoutRequest(request)) { + return true; + } + + /* + * 第三步:允许 SSO 会话解析接口在没有本地 Session 时进入 Controller。 + * 该接口会先校验服务端 SSO 会话,再创建本地 Session 和在线记录。 + */ + if (isSsoSessionResolveRequest(request)) { + return true; + } + + /* + * 第四步:登录验证码接口需要在没有在线账户记录时进入 Controller, + * 由业务层创建仅保存一次性验证码的 Servlet Session。 + */ + if (isLoginCaptchaRequest(request)) { + return true; + } + + /* + * 第五步:本地账号密码登录接口需要在没有现有在线账户记录时进入 Controller, + * 由业务层完成密码校验并创建新的本地登录 Session。 + */ + if (isLocalLoginRequest(request)) { + return true; + } + + /* + * 第六步:判断当前请求是否属于账户管理接口。 + * 普通业务接口只校验已有在线账户状态,不改变原有匿名访问规则。 + */ + boolean accountManagementRequest = + isAccountManagementRequest(request); + + /* + * 第七步:只读取已经存在的 Servlet 会话。 + * 使用 false 可以避免未登录请求被动创建一个新的空会话。 + */ + HttpSession session = request.getSession(false); + if (session == null) { + // 普通业务匿名请求保持项目原有访问方式,不由账户管理模块额外拦截。 + if (!accountManagementRequest) { + return true; + } + + // 账户管理请求没有有效会话时,返回标准 HTTP 401 和统一业务响应。 + writeErrorResponse( + response, + HttpStatus.UNAUTHORIZED, + Result.unauthorized(AUTHENTICATION_REQUIRED_MESSAGE)); + return false; + } + + /* + * 第八步:使用服务端生成的 SessionId 查询在线账户状态。 + * 这样即使外部登录流程重新写入了停用账户的在线记录, + * 该账户也会在访问任何业务接口前再次被账户状态表拦截。 + */ + String sessionId = session.getId(); + Integer accessStatus = + authorizationMapper.selectAccountAccessStatusBySessionId( + sessionId); + + /* + * 第九步:Servlet 会话没有对应在线记录时按失效登录处理。 + * 账户停用、密码重置和角色调整都会清理在线记录, + * 因此不能只保留内存会话后继续访问其他业务接口。 + */ + if (accessStatus == null) { + // 主动作废残留 Servlet 会话,避免浏览器继续携带无效会话编号。 + invalidateSession(session); + + // 所有携带失效登录会话的请求统一返回 HTTP 401。 + writeErrorResponse( + response, + HttpStatus.UNAUTHORIZED, + Result.unauthorized(AUTHENTICATION_REQUIRED_MESSAGE)); + return false; + } + + /* + * 第十步:已停用或已合并账户不允许继续访问任何业务接口。 + * 同时删除在线记录并作废 Servlet 会话,形成账户状态与登录状态闭环。 + */ + if (accessStatus == 0) { + // 删除当前会话对应的数据库在线记录。 + authorizationMapper.deleteOnlineSessionBySessionId(sessionId); + + // 作废 Servlet 容器中的会话,要求用户重新认证。 + invalidateSession(session); + + // 返回明确的账户不可用提示,便于前端区分普通会话过期。 + writeErrorResponse( + response, + HttpStatus.UNAUTHORIZED, + Result.unauthorized(ACCOUNT_UNAVAILABLE_MESSAGE)); + return false; + } + + /* + * 第十一步:普通业务接口完成在线账户状态检查后直接放行。 + * 具体业务权限将在后续 RBAC 功能中按角色和数据范围继续校验。 + */ + if (!accountManagementRequest) { + return true; + } + + /* + * 第十二步:个人账户接口只要求当前账户已经登录且可用。 + * 账户编号由业务层根据当前 SessionId 读取,不能操作或查看其他账户。 + */ + if (isPersonalAccountRequest(request)) { + return true; + } + + /* + * 第十三步:其他账户管理接口继续读取可信的在线角色类别。 + * 权限信息来自数据库,客户端不能通过自行填写账号或角色请求头绕过校验。 + */ + String roleCategory = + authorizationMapper.selectRoleCategoryBySessionId( + sessionId); + if (roleCategory == null || roleCategory.trim().isEmpty()) { + // 会话没有对应在线记录时,按无效或已退出登录处理。 + writeErrorResponse( + response, + HttpStatus.UNAUTHORIZED, + Result.unauthorized(AUTHENTICATION_REQUIRED_MESSAGE)); + return false; + } + + /* + * 第十四步:严格校验账户管理专用管理员角色。 + * 其他已登录角色只能使用各自业务功能,不能读取或修改系统账户。 + */ + if (!ACCOUNT_ADMINISTRATOR_ROLE_CATEGORY.equals( + roleCategory.trim())) { + // 已登录但角色权限不足时,返回标准 HTTP 403。 + writeErrorResponse( + response, + HttpStatus.FORBIDDEN, + Result.forbidden(ACCOUNT_PERMISSION_DENIED_MESSAGE)); + return false; + } + + // 第十五步:账户状态、登录状态和管理员权限均通过后,继续执行账户管理接口。 + return true; + } + + /** + * 判断当前请求是否为退出登录请求。 + * + * @param request 当前 HTTP 请求 + * @return {@code true} 表示允许匿名进入退出接口 + */ + private boolean isLogoutRequest(HttpServletRequest request) { + // 只放行精确的退出路径,避免扩大匿名白名单范围。 + return LOGOUT_PATH.equals(getApplicationPath(request)); + } + + /** + * 判断当前请求是否为匿名 SSO 会话解析请求。 + * + * @param request 当前 HTTP 请求 + * @return {@code true} 表示允许匿名进入 SSO 解析接口 + */ + private boolean isSsoSessionResolveRequest( + HttpServletRequest request) { + // 第一步:取得去除上下文路径后的应用请求地址。 + String applicationPath = getApplicationPath(request); + + // 第二步:只放行精确的 SSO 解析地址,避免扩大匿名白名单范围。 + return SSO_SESSION_RESOLVE_PATH.equals(applicationPath); + } + + /** + * 判断当前请求是否为本地账号密码登录请求。 + * + * @param request 当前 HTTP 请求 + * @return {@code true} 表示允许匿名进入本地登录接口 + */ + private boolean isLocalLoginRequest( + HttpServletRequest request) { + // 仅放行精确的本地登录路径,避免扩大匿名白名单范围。 + return LOCAL_LOGIN_PATH.equals(getApplicationPath(request)); + } + + /** + * 判断当前请求是否为登录验证码生成请求。 + * + * @param request 当前 HTTP 请求 + * @return {@code true} 表示允许匿名获取登录验证码 + */ + private boolean isLoginCaptchaRequest( + HttpServletRequest request) { + // 仅放行精确的验证码路径,其他账户管理接口仍执行登录和状态校验。 + return LOGIN_CAPTCHA_PATH.equals(getApplicationPath(request)); + } + + /** + * 判断当前请求是否为个人账户操作请求。 + * + * @param request 当前 HTTP 请求 + * @return {@code true} 表示只需要校验登录状态和账户状态 + */ + private boolean isPersonalAccountRequest( + HttpServletRequest request) { + // 第一步:取得去除上下文路径后的应用请求地址。 + String applicationPath = getApplicationPath(request); + + /* + * 第二步:只放行精确的个人修改密码和本人登录信息路径, + * 其他账户管理接口仍执行原有管理员角色校验。 + */ + return PASSWORD_CHANGE_PATH.equals(applicationPath) + || LOGIN_INFORMATION_PATH.equals(applicationPath); + } + + /** + * 判断当前请求是否属于账户管理接口。 + * + * @param request 当前 HTTP 请求 + * @return {@code true} 表示属于账户管理接口 + */ + private boolean isAccountManagementRequest( + HttpServletRequest request) { + // 第一步:取得去除上下文路径后的应用请求地址。 + String applicationPath = getApplicationPath(request); + + /* + * 第二步:同时匹配账户管理根路径和其全部子路径, + * 避免把 /account-management-other 之类的路径错误识别为账户管理接口。 + */ + return ACCOUNT_MANAGEMENT_PATH.equals(applicationPath) + || applicationPath.startsWith( + ACCOUNT_MANAGEMENT_PATH + "/"); + } + + /** + * 取得不包含应用上下文路径的请求地址。 + * + * @param request 当前 HTTP 请求 + * @return 应用内部请求地址 + */ + private String getApplicationPath(HttpServletRequest request) { + // 第一步:读取包含应用上下文路径的原始请求地址。 + String requestUri = request.getRequestURI(); + + // 第二步:读取应用上下文路径;根路径部署时通常为空字符串。 + String contextPath = request.getContextPath(); + + /* + * 第三步:移除上下文路径,保证根路径部署和例如 /jw 部署时 + * 都能够使用同一账户管理路径规则。 + */ + String applicationPath = requestUri; + if (contextPath != null + && !contextPath.isEmpty() + && requestUri.startsWith(contextPath)) { + applicationPath = requestUri.substring(contextPath.length()); + } + + // 第四步:返回应用内部请求地址,供不同白名单和权限规则复用。 + return applicationPath; + } + + /** + * 安全作废已经存在的 Servlet 会话。 + * + * @param session 需要作废的 Servlet 会话 + */ + private void invalidateSession(HttpSession session) { + try { + // 作废会话中的全部认证状态和业务属性。 + session.invalidate(); + } catch (IllegalStateException ignored) { + /* + * 并发请求可能已经提前作废同一个会话。 + * 该状态已经满足安全目标,因此无需再次向外抛出异常。 + */ + } + } + + /** + * 写入账户管理鉴权失败的统一 JSON 响应。 + * + * @param response 当前 HTTP 响应 + * @param status HTTP 状态 + * @param result 统一业务返回对象 + * @throws IOException JSON 写入失败时抛出 + */ + private void writeErrorResponse(HttpServletResponse response, + HttpStatus status, + Result result) + throws IOException { + // 第一步:设置真实 HTTP 状态,便于前端统一处理 401 和 403。 + response.setStatus(status.value()); + + // 第二步:明确使用 UTF-8 JSON,避免中文提示出现乱码。 + response.setCharacterEncoding("UTF-8"); + response.setHeader( + HttpHeaders.CONTENT_TYPE, + MediaType.APPLICATION_JSON_VALUE); + + // 第三步:使用系统 ObjectMapper 将统一 Result 对象直接写入响应体。 + objectMapper.writeValue(response.getWriter(), result); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/interceptor/OperationLogInterceptor.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/interceptor/OperationLogInterceptor.java new file mode 100644 index 000000000..9ec2c7db6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/interceptor/OperationLogInterceptor.java @@ -0,0 +1,248 @@ +package com.roomroot.jwgl.interceptor; + +import com.roomroot.jwgl.dto.operationlog.OperationLogRecordDTO; +import com.roomroot.jwgl.service.OperationLogService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.ModelAndView; + + + +import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATION_LOG_LOGIN_RECORD_ID_REQUEST_ATTRIBUTE; +import static com.roomroot.jwgl.utils.OperationLogUtil.isOperationFailed; +import static com.roomroot.jwgl.utils.OperationLogUtil.resolveApplicationUrl; +import static com.roomroot.jwgl.utils.OperationLogUtil.resolveCurrentLoginRecordId; +import static com.roomroot.jwgl.utils.OperationLogUtil.resolveOperationContent; +import static com.roomroot.jwgl.utils.OperationLogUtil.resolveRequestLoginRecordId; +import static com.roomroot.jwgl.utils.OperationLogUtil.shouldRecordOperation; + +/** + * 操作日志统一请求采集拦截器。 + * + *

在 Controller 执行前暂存可信的登录记录编号, + * 并在 Controller 成功返回或处理失败后统一生成和写入操作日志。

+ */ +@Slf4j +@Component +public class OperationLogInterceptor implements HandlerInterceptor { + + /** + * 操作日志业务服务。 + */ + private final OperationLogService operationLogService; + + /** + * 构造操作日志统一请求采集拦截器。 + * + * @param operationLogService 操作日志业务服务 + */ + public OperationLogInterceptor( + OperationLogService operationLogService) { + // 保存操作日志业务服务,供请求完成后统一写入成功或失败日志。 + this.operationLogService = operationLogService; + } + + /** + * 在 Controller 执行前暂存当前请求关联的登录记录编号。 + * + * @param request 当前 HTTP 请求 + * @param response 当前 HTTP 响应 + * @param handler 当前请求处理器 + * @return 始终返回 true,不改变原有业务请求执行结果 + */ + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler) { + // 第一步:静态资源和框架内部处理器不属于业务操作日志范围。 + if (!(handler instanceof HandlerMethod)) { + return true; + } + + /* + * 第二步:只为关键业务操作暂存登录上下文。 + * 普通列表、详情、树形和选项查询不会生成操作日志。 + */ + HandlerMethod handlerMethod = (HandlerMethod) handler; + if (!shouldRecordOperation(request, handlerMethod)) { + return true; + } + + /* + * 第三步:从当前已经存在的 Session 安全读取登录记录编号。 + * 没有有效登录上下文时返回 null,并保持原请求继续执行。 + */ + String loginRecordId = + resolveCurrentLoginRecordId(request); + if (loginRecordId == null) { + return true; + } + + /* + * 第四步:把登录记录编号暂存在当前请求范围内。 + * 即使退出登录随后作废 Session,请求完成阶段仍可取得该编号。 + */ + request.setAttribute( + OPERATION_LOG_LOGIN_RECORD_ID_REQUEST_ATTRIBUTE, + loginRecordId); + + // 第五步:采集过程不承担鉴权职责,始终允许原业务请求继续执行。 + return true; + } + + /** + * 在 Controller 正常返回后自动写入操作日志。 + * + * @param request 当前 HTTP 请求 + * @param response 当前 HTTP 响应 + * @param handler 当前请求处理器 + * @param modelAndView 当前请求返回的视图;REST 接口通常为 null + */ + @Override + public void postHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler, + ModelAndView modelAndView) { + /* + * 第一步:只记录实际由 Controller 方法正常处理完成的业务请求。 + * 静态资源和框架内部处理器不属于业务操作日志范围。 + */ + if (!(handler instanceof HandlerMethod)) { + return; + } + + /* + * 第二步:复用统一写入流程记录成功操作。 + * 失败请求不会执行 postHandle,而是在请求完成阶段单独补充失败日志。 + */ + writeOperationLog( + request, + (HandlerMethod) handler, + false); + } + + /** + * 在请求完成后补充记录处理失败的业务操作。 + * + * @param request 当前 HTTP 请求 + * @param response 当前 HTTP 响应 + * @param handler 当前请求处理器 + * @param exception 请求处理过程中未被解析的异常;正常或已解析时可能为 null + */ + @Override + public void afterCompletion(HttpServletRequest request, + HttpServletResponse response, + Object handler, + Exception exception) { + /* + * 第一步:只处理实际进入 Controller 方法的业务请求。 + * 静态资源和框架内部处理器不属于业务操作日志范围。 + */ + if (!(handler instanceof HandlerMethod)) { + return; + } + + /* + * 第二步:全局异常处理器会标记已解析异常, + * 未被异常处理器解析的异常则直接通过 exception 参数识别。 + */ + boolean failed = + exception != null || isOperationFailed(request); + if (!failed) { + return; + } + + // 第三步:复用统一写入流程记录失败操作,并与成功日志保持相同归属规则。 + writeOperationLog( + request, + (HandlerMethod) handler, + true); + } + + /** + * 按照统一规则组装并写入成功或失败操作日志。 + * + * @param request 当前 HTTP 请求 + * @param handlerMethod 当前 Controller 方法 + * @param failed 当前业务操作是否失败 + */ + private void writeOperationLog( + HttpServletRequest request, + HandlerMethod handlerMethod, + boolean failed) { + /* + * 第一步:复用工具类中的统一关键操作规则。 + * 普通查询和操作日志自身的列表、详情、模块选项不会触发写入。 + */ + if (!shouldRecordOperation(request, handlerMethod)) { + return; + } + + /* + * 第二步:优先从当前 Session 读取登录记录编号。 + * 登录接口执行成功后会新建登录记录并写入 Session,因此必须优先读取最新值。 + */ + String loginRecordId = + resolveCurrentLoginRecordId(request); + + /* + * 第三步:当前 Session 已失效时读取 Controller 执行前暂存的编号。 + * 该回退规则保证退出登录作废 Session 后仍能记录本次退出操作。 + */ + if (loginRecordId == null) { + loginRecordId = + resolveRequestLoginRecordId(request); + } + + // 第四步:匿名请求没有可信登录记录,直接跳过且不创建无归属日志。 + if (loginRecordId == null) { + return; + } + + // 第五步:生成不包含域名、部署上下文和查询参数的应用内请求地址。 + String applicationUrl = + resolveApplicationUrl(request); + + /* + * 第六步:根据请求地址、HTTP 方法和 Controller 方法生成操作内容。 + * 生成过程不会读取请求参数或请求体,避免记录敏感数据。 + */ + String operationContent = + resolveOperationContent( + request, handlerMethod); + + // 第七步:失败操作只追加结果标识,不记录异常消息或客户端提交内容。 + if (failed) { + operationContent = + operationContent + "(失败)"; + } + + // 第八步:组装仅供服务端内部日志写入流程使用的 DTO。 + OperationLogRecordDTO record = + new OperationLogRecordDTO(); + record.setLoginRecordId(loginRecordId); + record.setOperationContent(operationContent); + record.setUrl(applicationUrl); + + /* + * 第九步:尝试调用操作日志业务服务完成统一校验、编号生成和数据库写入。 + * 操作日志属于辅助审计能力,其写入失败不能改变原业务请求结果。 + */ + try { + operationLogService.record(record); + } catch (RuntimeException exception) { + /* + * 第十步:只在服务器日志中记录应用内地址和异常堆栈,便于后续排查。 + * 不记录请求参数、请求体或登录记录编号,避免辅助日志再次暴露敏感数据。 + */ + log.error( + "操作日志写入失败,已忽略且不影响原业务请求,url={}", + applicationUrl, + exception); + } + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementAuthorizationMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementAuthorizationMapper.java new file mode 100644 index 000000000..6d1c6b880 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementAuthorizationMapper.java @@ -0,0 +1,44 @@ +package com.roomroot.jwgl.mapper; + +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 账户管理接口鉴权数据访问接口。 + * + *

该 Mapper 只读取现有在线用户记录,不参与登录、账户资料或角色绑定业务。

+ */ +@Mapper +public interface AccountManagementAuthorizationMapper { + + /** + * 查询当前在线会话关联账户是否允许继续访问系统。 + * + *

没有在线记录时返回 {@code null};状态表尚未初始化的旧账户按可用处理; + * 已停用或已合并账户返回 {@code 0},正常账户返回 {@code 1}。

+ * + * @param sessionId 当前 Web 会话编号 + * @return {@code 1} 表示账户可用,{@code 0} 表示账户不可用, + * {@code null} 表示没有在线记录 + */ + Integer selectAccountAccessStatusBySessionId( + @Param("sessionId") String sessionId); + + /** + * 根据当前 Web 会话编号查询在线用户的角色类别。 + * + * @param sessionId 当前 Web 会话编号 + * @return 在线角色类别;会话没有对应在线记录时返回 {@code null} + */ + String selectRoleCategoryBySessionId( + @Param("sessionId") String sessionId); + + /** + * 根据当前 Web 会话编号删除失效的在线用户记录。 + * + * @param sessionId 当前 Web 会话编号 + * @return 实际删除的在线记录数量 + */ + int deleteOnlineSessionBySessionId( + @Param("sessionId") String sessionId); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementAuthorizationMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementAuthorizationMapper.xml new file mode 100644 index 000000000..2b0e50b13 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementAuthorizationMapper.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + DELETE FROM "MemberOnlineInfo" + WHERE "SessionId" = #{sessionId} + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementMapper.java new file mode 100644 index 000000000..ec6bff57e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementMapper.java @@ -0,0 +1,371 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.SSOUserAuthSession; +import com.roomroot.jwgl.entity.DBVersion; +import com.roomroot.jwgl.entity.accountmanagement.AccountRecord; +import com.roomroot.jwgl.entity.accountmanagement.AccountRoleRecord; +import com.roomroot.jwgl.vo.accountmanagement.AccountLoginHistoryVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountRoleOptionVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountRoleVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 账户管理数据访问接口。 + */ +@Mapper +public interface AccountManagementMapper { + + /** + * 分页查询账户基础信息和管理状态。 + * + *

查询条件和稳定排序在数据库中完成,分页语句由项目现有的 + * MyBatis-Plus 达梦分页拦截器统一生成。

+ * + * @param page MyBatis-Plus 分页参数 + * @param keyword 账户或角色关键字 + * @param enabled 账户启用状态 + * @param roleType 角色类型 + * @param targetId 角色身份编号 + * @return 当前页账户持久化记录 + */ + Page selectAccountPage( + Page page, + @Param("keyword") String keyword, + @Param("enabled") Boolean enabled, + @Param("roleType") String roleType, + @Param("targetId") String targetId); + + /** + * 根据编号查询账户基础信息和管理状态。 + * + * @param id 登录信息编号 + * @return 账户持久化记录 + */ + AccountRecord selectAccountById(@Param("id") String id); + + /** + * 根据登录账号查询账户及其管理状态。 + * + *

该查询必须返回密码摘要,供业务层使用 PasswordEncoder 校验原始密码; + * 密码摘要只在内部 AccountRecord 中流转,不直接返回页面。

+ * + * @param loginName 登录账号 + * @return 匹配的账户记录;不存在时返回 null + */ + AccountRecord selectAccountByLoginName( + @Param("loginName") String loginName); + + /** + * 查询本地密码登录使用的系统安全参数。 + * + *

只读取允许密码尝试次数和用户锁定分钟数, + * 不把整个系统版本配置引入账户管理业务。

+ * + * @return 最新系统版本记录中的登录安全参数;没有配置时返回 null + */ + DBVersion selectLoginSecurityPolicy(); + + /** + * 统计同名登录账号。 + * + * @param loginName 登录账号 + * @param excludeId 修改时需要排除的当前账户编号 + * @return 同名账号数量 + */ + int countByLoginName(@Param("loginName") String loginName, + @Param("excludeId") String excludeId); + + /** + * 统计同名统一账号。 + * + * @param unifiedAccount 统一账号 + * @param excludeId 修改时需要排除的当前账户编号 + * @return 同名统一账号数量 + */ + int countByUnifiedAccount( + @Param("unifiedAccount") String unifiedAccount, + @Param("excludeId") String excludeId); + + /** + * 新增登录账户。 + * + * @param account 账户持久化记录 + * @return 影响行数 + */ + int insertAccount(@Param("account") AccountRecord account); + + /** + * 修改账户基本资料,不修改密码、状态和登录统计。 + * + * @param account 账户持久化记录 + * @return 影响行数 + */ + int updateAccount(@Param("account") AccountRecord account); + + /** + * 重置账户密码并清除临时登录失败锁定信息。 + * + * @param id 登录信息编号 + * @param password 重置后的 BCrypt 密码摘要 + * @return 影响行数 + */ + int resetPassword(@Param("id") String id, + @Param("password") String password); + + /** + * 原子累计一次本地密码登录失败。 + * + *

失败次数直接在数据库当前值上加一,避免同一账户并发登录时 + * 由业务层先读后写造成失败次数丢失。

+ * + * @param id 登录信息编号 + * @param failedAt 本次登录失败时间 + * @return 影响行数 + */ + int recordLoginFailure( + @Param("id") String id, + @Param("failedAt") LocalDateTime failedAt); + + /** + * 清除已经超过锁定期限的连续登录失败状态。 + * + *

更新条件同时检查失败次数和失败时间,避免并发登录请求 + * 把另一个请求刚刚写入的新失败状态错误清除。

+ * + * @param id 登录信息编号 + * @param allowedAttempts 允许密码尝试次数 + * @param expiredBefore 此时间及以前的锁定状态已经到期 + * @return 影响行数 + */ + int clearExpiredLoginFailureState( + @Param("id") String id, + @Param("allowedAttempts") Integer allowedAttempts, + @Param("expiredBefore") LocalDateTime expiredBefore); + + /** + * 记录一次成功登录并清除连续失败状态。 + * + * @param id 登录信息编号 + * @param loginAt 本次登录成功时间 + * @return 影响行数 + */ + int recordLoginSuccess( + @Param("id") String id, + @Param("loginAt") LocalDateTime loginAt); + + /** + * 写入一条成功登录历史记录。 + * + * @param id 登录记录编号 + * @param accountId 登录信息编号 + * @param loginAt 登录成功时间 + * @param ipAddress 登录请求来源 IP + * @param intranetIpAddress 客户端内网 IP;无法取得时为空 + * @param transactionId 本次登录事务标识 + * @return 影响行数 + */ + int insertLoginHistory( + @Param("id") String id, + @Param("accountId") String accountId, + @Param("loginAt") LocalDateTime loginAt, + @Param("ipAddress") String ipAddress, + @Param("intranetIpAddress") String intranetIpAddress, + @Param("transactionId") String transactionId); + + /** + * 分页查询指定账户的成功登录历史。 + * + * @param page MyBatis-Plus 分页参数 + * @param accountId 当前登录账户编号 + * @return 按登录时间倒序排列的当前页登录记录 + */ + Page selectLoginHistoryPage( + Page page, + @Param("accountId") String accountId); + + /** + * 根据登录账号删除账户的全部在线会话记录。 + * + * @param loginName 登录账号 + * @return 删除的在线会话数量 + */ + int deleteOnlineSessionsByLoginName( + @Param("loginName") String loginName); + + /** + * 根据当前 Servlet 会话删除对应的在线记录。 + * + * @param sessionId Servlet 会话编号 + * @return 删除的在线会话数量 + */ + int deleteOnlineSessionBySessionId( + @Param("sessionId") String sessionId); + + /** + * 新增当前 Servlet 会话对应的在线记录。 + * + * @param sessionId Servlet 会话编号 + * @param loginName 登录账号 + * @param userName 用户姓名 + * @param unit 在线用户所属单位或数据范围 + * @param roleCategory SSO 角色类别 + * @param ipAddress 当前请求 IP + * @param startAccessTime 开始访问时间 + * @param lastAccessTime 最后访问时间 + * @param lastAccessPath 最后访问路径 + * @return 影响行数 + */ + int insertOnlineSession( + @Param("sessionId") String sessionId, + @Param("loginName") String loginName, + @Param("userName") String userName, + @Param("unit") String unit, + @Param("roleCategory") String roleCategory, + @Param("ipAddress") String ipAddress, + @Param("startAccessTime") LocalDateTime startAccessTime, + @Param("lastAccessTime") LocalDateTime lastAccessTime, + @Param("lastAccessPath") String lastAccessPath); + + /** + * 根据当前 Servlet 会话查询对应的账户编号。 + * + * @param sessionId Servlet 会话编号 + * @return 当前会话对应的账户编号;没有匹配账户时返回 null + */ + String selectAccountIdBySessionId( + @Param("sessionId") String sessionId); + + /** + * 根据会话标识查询统一身份认证会话。 + * + * @param sessionKey SSO 会话标识 + * @return SSO 会话;不存在时返回 null + */ + SSOUserAuthSession selectSsoSessionBySessionKey( + @Param("sessionKey") String sessionKey); + + /** + * 根据一个或多个 SSO 身份候选值查询本地统一账号。 + * + *

查询结果只包含账户解析和状态校验所需字段, + * 不读取密码、密码提示答案等敏感信息。

+ * + * @param unifiedAccounts SSO 会话中的统一账号候选值 + * @return 匹配的本地账户 + */ + List selectAccountsByUnifiedAccounts( + @Param("unifiedAccounts") List unifiedAccounts); + + /** + * 统计账户管理状态记录。 + * + * @param accountId 登录信息编号 + * @return 状态记录数量 + */ + int countAccountStatus(@Param("accountId") String accountId); + + /** + * 新增账户管理状态。 + * + * @param account 账户持久化记录 + * @return 影响行数 + */ + int insertAccountStatus(@Param("account") AccountRecord account); + + /** + * 修改账户启停或合并状态。 + * + * @param account 账户持久化记录 + * @return 影响行数 + */ + int updateAccountStatus(@Param("account") AccountRecord account); + + /** + * 查询账户已绑定的全部角色和数据范围。 + * + * @param accountId 登录信息编号 + * @return 角色绑定列表 + */ + List selectRolesByAccountId( + @Param("accountId") String accountId); + + /** + * 批量查询指定账户的全部角色和数据范围。 + * + * @param accountIds 当前页账户编号 + * @return 带所属账户编号的角色持久化记录 + */ + List selectRoleRecordsByAccountIds( + @Param("accountIds") List accountIds); + + /** + * 分页查询指定角色类型的可选身份。 + * + *

关键字筛选、绑定状态筛选、异常重复数据去重和稳定排序均在 + * 数据库中完成,分页语句由项目现有的 MyBatis-Plus 达梦分页 + * 拦截器统一生成。

+ * + * @param page MyBatis-Plus 分页参数 + * @param roleType 角色类型 + * @param keyword 身份编号、名称、数据范围或绑定账号关键字 + * @param unboundOnly 是否只查询未绑定身份 + * @param accountId 当前正在配置的账户编号 + * @return 当前页角色身份选项 + */ + Page selectRoleOptionPage( + Page page, + @Param("roleType") String roleType, + @Param("keyword") String keyword, + @Param("unboundOnly") Boolean unboundOnly, + @Param("accountId") String accountId); + + /** + * 查询指定角色类型的可选身份。 + * + * @param roleType 角色类型 + * @param targetId 指定身份编号;为空时查询该类型全部身份 + * @return 角色身份选项 + */ + List selectRoleOptions( + @Param("roleType") String roleType, + @Param("targetId") String targetId); + + /** + * 删除账户在指定角色类型下的全部绑定。 + * + * @param roleType 角色类型 + * @param accountId 登录信息编号 + * @return 影响行数 + */ + int deleteRoleBindings(@Param("roleType") String roleType, + @Param("accountId") String accountId); + + /** + * 写入一条角色身份绑定。 + * + * @param roleType 角色类型 + * @param targetId 身份编号 + * @param accountId 登录信息编号 + * @return 影响行数 + */ + int saveRoleBinding(@Param("roleType") String roleType, + @Param("targetId") String targetId, + @Param("accountId") String accountId); + + /** + * 将指定角色类型下的源账户绑定迁移到目标账户。 + * + * @param roleType 角色类型 + * @param sourceAccountId 源账户编号 + * @param targetAccountId 目标账户编号 + * @return 影响行数 + */ + int moveRoleBindings(@Param("roleType") String roleType, + @Param("sourceAccountId") String sourceAccountId, + @Param("targetAccountId") String targetAccountId); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementMapper.xml new file mode 100644 index 000000000..2e840f972 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/AccountManagementMapper.xml @@ -0,0 +1,1027 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + d."编号", d."用户密码", d."用户姓名", d."密码提示问题", + d."密码回答问题", d."电话", d."上次登录时间", d."本次登录时间", + d."失败次数", d."失败时间", d."身份证", d."账号", + d."统一账号", d."自我介绍", + CASE + WHEN s."登录信息编号" IS NULL THEN 1 + ELSE s."启用状态" + END AS "账户启用状态", + s."停用原因" AS "账户停用原因", + s."停用时间" AS "账户停用时间", + s."合并目标登录信息编号" AS "合并目标账户编号", + s."合并时间" AS "账户合并时间" + + + + + + AND CASE + WHEN s."登录信息编号" IS NULL THEN 1 + ELSE s."启用状态" + END = + CASE WHEN #{enabled} = 1 THEN 1 ELSE 0 END + + + + AND EXISTS ( + SELECT 1 + FROM ( + + ) role_filter + WHERE role_filter."accountId" = d."编号" + AND role_filter."roleType" = #{roleType} + + AND role_filter."targetId" = #{targetId} + + ) + + + + AND ( + LOWER(COALESCE(d."账号", '')) + LIKE LOWER(CONCAT('%', #{keyword}, '%')) + OR LOWER(COALESCE(d."用户姓名", '')) + LIKE LOWER(CONCAT('%', #{keyword}, '%')) + OR LOWER(COALESCE(d."统一账号", '')) + LIKE LOWER(CONCAT('%', #{keyword}, '%')) + OR LOWER(COALESCE(d."电话", '')) + LIKE LOWER(CONCAT('%', #{keyword}, '%')) + OR LOWER(COALESCE(d."身份证", '')) + LIKE LOWER(CONCAT('%', #{keyword}, '%')) + OR EXISTS ( + SELECT 1 + FROM ( + + ) keyword_role + WHERE keyword_role."accountId" = d."编号" + AND ( + LOWER(COALESCE(keyword_role."roleType", '')) + LIKE LOWER(CONCAT('%', #{keyword}, '%')) + OR LOWER(COALESCE(keyword_role."roleName", '')) + LIKE LOWER(CONCAT('%', #{keyword}, '%')) + OR LOWER(COALESCE(keyword_role."targetId", '')) + LIKE LOWER(CONCAT('%', #{keyword}, '%')) + OR LOWER(COALESCE(keyword_role."targetName", '')) + LIKE LOWER(CONCAT('%', #{keyword}, '%')) + OR LOWER(COALESCE(keyword_role."dataScope", '')) + LIKE LOWER(CONCAT('%', #{keyword}, '%')) + ) + ) + ) + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO "登录信息表" ( + "编号", "用户密码", "用户姓名", "密码提示问题", + "密码回答问题", "电话", "上次登录时间", "本次登录时间", + "失败次数", "失败时间", "身份证", "账号", + "统一账号", "自我介绍", "照片" + ) VALUES ( + #{account.id}, #{account.password}, #{account.userName}, + #{account.passwordQuestion}, #{account.passwordAnswer}, + #{account.phone}, #{account.lastLoginAt}, #{account.currentLoginAt}, + #{account.failedAttempts}, #{account.failedAt}, #{account.idCard}, + #{account.loginName}, #{account.unifiedAccount}, + #{account.introduction}, '' + ) + + + + + UPDATE "登录信息表" + SET "用户姓名" = #{account.userName}, + "密码提示问题" = #{account.passwordQuestion}, + "密码回答问题" = #{account.passwordAnswer}, + "电话" = #{account.phone}, + "身份证" = #{account.idCard}, + "账号" = #{account.loginName}, + "统一账号" = #{account.unifiedAccount}, + "自我介绍" = #{account.introduction} + WHERE "编号" = #{account.id} + + + + + UPDATE "登录信息表" + SET "用户密码" = #{password}, + "失败次数" = 0, + "失败时间" = CURRENT_TIMESTAMP + WHERE "编号" = #{id} + + + + + UPDATE "登录信息表" + SET "失败次数" = COALESCE("失败次数", 0) + 1, + "失败时间" = #{failedAt} + WHERE "编号" = #{id} + + + + + UPDATE "登录信息表" + SET "失败次数" = 0, + "失败时间" = #{expiredBefore} + WHERE "编号" = #{id} + AND COALESCE("失败次数", 0) >= #{allowedAttempts} + AND "失败时间" IS NOT NULL + AND "失败时间" <= #{expiredBefore} + + + + + UPDATE "登录信息表" + SET "上次登录时间" = "本次登录时间", + "本次登录时间" = #{loginAt}, + "失败次数" = 0, + "失败时间" = #{loginAt} + WHERE "编号" = #{id} + + + + + INSERT INTO "登录记录表" ( + "编号", "登录信息编号", "登录时间", + "IP地址", "内网IP", "事务ID" + ) VALUES ( + #{id}, #{accountId}, #{loginAt}, + #{ipAddress}, #{intranetIpAddress}, #{transactionId} + ) + + + + + + + + DELETE FROM "MemberOnlineInfo" + WHERE LOWER(TRIM("账号")) = LOWER(#{loginName}) + + + + + DELETE FROM "MemberOnlineInfo" + WHERE "SessionId" = #{sessionId} + + + + + INSERT INTO "MemberOnlineInfo" ( + "SessionId", "账号", "姓名", "单位", "角色类别", "IP", + "开始访问时间", "最后访问时间", "最后访问路径" + ) VALUES ( + #{sessionId}, #{loginName}, #{userName}, #{unit}, + #{roleCategory}, #{ipAddress}, #{startAccessTime}, + #{lastAccessTime}, #{lastAccessPath} + ) + + + + + + + + + + + + + + + + + INSERT INTO "账户状态表" ( + "登录信息编号", "启用状态", "停用原因", "停用时间", + "合并目标登录信息编号", "合并时间" + ) VALUES ( + #{account.id}, + CASE WHEN #{account.enabled} = 1 THEN 1 ELSE 0 END, + #{account.disabledReason}, #{account.disabledAt}, + #{account.mergedToAccountId}, #{account.mergedAt} + ) + + + + + UPDATE "账户状态表" + SET "启用状态" = + CASE WHEN #{account.enabled} = 1 THEN 1 ELSE 0 END, + "停用原因" = #{account.disabledReason}, + "停用时间" = #{account.disabledAt}, + "合并目标登录信息编号" = #{account.mergedToAccountId}, + "合并时间" = #{account.mergedAt} + WHERE "登录信息编号" = #{account.id} + + + + + SELECT b."登录信息编号" AS "accountId", + 'DEPARTMENT_PERSONNEL' AS "roleType", + '机关人员' AS "roleName", + p."编号" AS "targetId", + COALESCE(p."用户姓名", p."编号") AS "targetName", + COALESCE(dep."机关名称", p."实施_机关编号") AS "dataScope", + CASE + WHEN COALESCE(p."离职状态", 0) = 0 + AND COALESCE(dep."停用", 0) = 0 THEN 1 + ELSE 0 + END AS "enabled" + FROM "机关人员登录表" b + JOIN "实施_机关参谋" p ON p."编号" = b."机关人员编号" + LEFT JOIN "实施_机关" dep ON dep."编号" = p."实施_机关编号" + + UNION ALL + + SELECT b."登录信息编号" AS "accountId", + 'TEACHER' AS "roleType", + '教员' AS "roleName", + t."教员编号" AS "targetId", + t."教员姓名" AS "targetName", + COALESCE(o."教研室名称", t."教研室代号") AS "dataScope", + CASE + WHEN COALESCE(t."离职状态", 0) = 0 + AND COALESCE(o."停用", 0) = 0 THEN 1 + ELSE 0 + END AS "enabled" + FROM "教员登录表" b + JOIN "教员表" t ON t."教员编号" = b."教员编号" + LEFT JOIN "教研室表" o ON o."教研室代号" = t."教研室代号" + + UNION ALL + + SELECT b."登录信息编号" AS "accountId", + 'RESEARCH_OFFICE' AS "roleType", + '教研室' AS "roleName", + o."教研室代号" AS "targetId", + o."教研室名称" AS "targetName", + COALESCE(dep."机关名称", o."部系") AS "dataScope", + CASE WHEN COALESCE(o."停用", 0) = 0 THEN 1 ELSE 0 END + AS "enabled" + FROM "教研室登录表" b + JOIN "教研室表" o ON o."教研室代号" = b."教研室代号" + LEFT JOIN "实施_机关_教研室" r + ON r."教研室编号" = o."教研室代号" + LEFT JOIN "实施_机关" dep ON dep."编号" = r."实施_机关编号" + + UNION ALL + + SELECT b."登录信息编号" AS "accountId", + 'TEACHING_CLASS' AS "roleType", + '教学班次' AS "roleName", + c."学员队编号" AS "targetId", + c."学员队名称" AS "targetName", + COALESCE(dep."机关名称", c."所属单位") AS "dataScope", + 1 AS "enabled" + FROM "学员队登录表" b + JOIN "学员队表" c ON c."学员队编号" = b."学员队编号" + LEFT JOIN "实施_机关_学员队" r + ON r."学员队编号" = c."学员队编号" + LEFT JOIN "实施_机关" dep ON dep."编号" = r."实施_机关编号" + + UNION ALL + + SELECT b."登录信息编号" AS "accountId", + 'INSPECTION_ADMINISTRATOR' AS "roleType", + '听查课管理员' AS "roleName", + a."编号" AS "targetId", + a."姓名" AS "targetName", + NULL AS "dataScope", + 1 AS "enabled" + FROM "听查课管理员登录表" b + JOIN "听查课管理员" a + ON a."编号" = b."听查课管理员编号" + + UNION ALL + + SELECT s."登录信息编号" AS "accountId", + 'STUDENT' AS "roleType", + '学员' AS "roleName", + s."编号" AS "targetId", + s."姓名" AS "targetName", + COALESCE(c."学员队名称", s."当前班次编号") AS "dataScope", + CASE WHEN COALESCE(s."退学状态", 0) = 0 THEN 1 ELSE 0 END + AS "enabled" + FROM "学员信息表" s + LEFT JOIN "学员队表" c ON c."学员队编号" = s."当前班次编号" + + UNION ALL + + SELECT b."登录信息编号" AS "accountId", + 'LEGACY_USER' AS "roleType", + '旧用户' AS "roleName", + u."用户帐号" AS "targetId", + COALESCE(u."用户姓名", u."用户帐号") AS "targetName", + u."教学管理机构编号" AS "dataScope", + 1 AS "enabled" + FROM "用户登录表" b + JOIN "用户表" u ON u."用户帐号" = b."用户账号" + + + + + + + + + + + + + SELECT 'DEPARTMENT_PERSONNEL' AS "roleType", + p."编号" AS "targetId", + COALESCE(p."用户姓名", p."编号") AS "targetName", + COALESCE(dep."机关名称", p."实施_机关编号") AS "dataScope", + CASE + WHEN COALESCE(p."离职状态", 0) = 0 + AND COALESCE(dep."停用", 0) = 0 THEN 1 + ELSE 0 + END AS "enabled", + b."登录信息编号" AS "boundAccountId", + d."账号" AS "boundLoginName" + FROM "实施_机关参谋" p + LEFT JOIN "实施_机关" dep + ON dep."编号" = p."实施_机关编号" + LEFT JOIN "机关人员登录表" b + ON b."机关人员编号" = p."编号" + LEFT JOIN "登录信息表" d + ON d."编号" = b."登录信息编号" + + + SELECT 'TEACHER' AS "roleType", + t."教员编号" AS "targetId", + t."教员姓名" AS "targetName", + COALESCE(o."教研室名称", t."教研室代号") AS "dataScope", + CASE + WHEN COALESCE(t."离职状态", 0) = 0 + AND COALESCE(o."停用", 0) = 0 THEN 1 + ELSE 0 + END AS "enabled", + b."登录信息编号" AS "boundAccountId", + d."账号" AS "boundLoginName" + FROM "教员表" t + LEFT JOIN "教研室表" o + ON o."教研室代号" = t."教研室代号" + LEFT JOIN "教员登录表" b + ON b."教员编号" = t."教员编号" + LEFT JOIN "登录信息表" d + ON d."编号" = b."登录信息编号" + + + SELECT 'RESEARCH_OFFICE' AS "roleType", + o."教研室代号" AS "targetId", + o."教研室名称" AS "targetName", + COALESCE(dep."机关名称", o."部系") AS "dataScope", + CASE WHEN COALESCE(o."停用", 0) = 0 THEN 1 ELSE 0 END + AS "enabled", + b."登录信息编号" AS "boundAccountId", + d."账号" AS "boundLoginName" + FROM "教研室表" o + LEFT JOIN "实施_机关_教研室" r + ON r."教研室编号" = o."教研室代号" + LEFT JOIN "实施_机关" dep + ON dep."编号" = r."实施_机关编号" + LEFT JOIN "教研室登录表" b + ON b."教研室代号" = o."教研室代号" + LEFT JOIN "登录信息表" d + ON d."编号" = b."登录信息编号" + + + SELECT 'TEACHING_CLASS' AS "roleType", + c."学员队编号" AS "targetId", + c."学员队名称" AS "targetName", + COALESCE(dep."机关名称", c."所属单位") AS "dataScope", + 1 AS "enabled", + b."登录信息编号" AS "boundAccountId", + d."账号" AS "boundLoginName" + FROM "学员队表" c + LEFT JOIN "实施_机关_学员队" r + ON r."学员队编号" = c."学员队编号" + LEFT JOIN "实施_机关" dep + ON dep."编号" = r."实施_机关编号" + LEFT JOIN "学员队登录表" b + ON b."学员队编号" = c."学员队编号" + LEFT JOIN "登录信息表" d + ON d."编号" = b."登录信息编号" + + + SELECT 'INSPECTION_ADMINISTRATOR' AS "roleType", + a."编号" AS "targetId", + a."姓名" AS "targetName", + NULL AS "dataScope", + 1 AS "enabled", + b."登录信息编号" AS "boundAccountId", + d."账号" AS "boundLoginName" + FROM "听查课管理员" a + LEFT JOIN "听查课管理员登录表" b + ON b."听查课管理员编号" = a."编号" + LEFT JOIN "登录信息表" d + ON d."编号" = b."登录信息编号" + + + SELECT 'STUDENT' AS "roleType", + s."编号" AS "targetId", + s."姓名" AS "targetName", + COALESCE(c."学员队名称", s."当前班次编号") AS "dataScope", + CASE WHEN COALESCE(s."退学状态", 0) = 0 THEN 1 ELSE 0 END + AS "enabled", + s."登录信息编号" AS "boundAccountId", + d."账号" AS "boundLoginName" + FROM "学员信息表" s + LEFT JOIN "学员队表" c + ON c."学员队编号" = s."当前班次编号" + LEFT JOIN "登录信息表" d + ON d."编号" = s."登录信息编号" + + + SELECT 'LEGACY_USER' AS "roleType", + u."用户帐号" AS "targetId", + COALESCE(u."用户姓名", u."用户帐号") AS "targetName", + u."教学管理机构编号" AS "dataScope", + 1 AS "enabled", + b."登录信息编号" AS "boundAccountId", + d."账号" AS "boundLoginName" + FROM "用户表" u + LEFT JOIN "用户登录表" b + ON b."用户账号" = u."用户帐号" + LEFT JOIN "登录信息表" d + ON d."编号" = b."登录信息编号" + + + SELECT NULL AS "roleType", NULL AS "targetId", + NULL AS "targetName", NULL AS "dataScope", + 0 AS "enabled", NULL AS "boundAccountId", + NULL AS "boundLoginName" + FROM "登录信息表" + WHERE 1 = 0 + + + + + + + + + + + + + + + DELETE FROM "机关人员登录表" + WHERE "登录信息编号" = #{accountId} + + + DELETE FROM "教员登录表" + WHERE "登录信息编号" = #{accountId} + + + DELETE FROM "教研室登录表" + WHERE "登录信息编号" = #{accountId} + + + DELETE FROM "学员队登录表" + WHERE "登录信息编号" = #{accountId} + + + DELETE FROM "听查课管理员登录表" + WHERE "登录信息编号" = #{accountId} + + + UPDATE "学员信息表" + SET "登录信息编号" = NULL + WHERE "登录信息编号" = #{accountId} + + + DELETE FROM "用户登录表" + WHERE "登录信息编号" = #{accountId} + + + UPDATE "登录信息表" + SET "编号" = "编号" + WHERE 1 = 0 + + + + + + + + + INSERT INTO "机关人员登录表" ( + "机关人员编号", "登录信息编号" + ) VALUES ( + #{targetId}, #{accountId} + ) + + + INSERT INTO "教员登录表" ( + "教员编号", "登录信息编号" + ) VALUES ( + #{targetId}, #{accountId} + ) + + + INSERT INTO "教研室登录表" ( + "教研室代号", "登录信息编号" + ) VALUES ( + #{targetId}, #{accountId} + ) + + + INSERT INTO "学员队登录表" ( + "学员队编号", "登录信息编号" + ) VALUES ( + #{targetId}, #{accountId} + ) + + + INSERT INTO "听查课管理员登录表" ( + "听查课管理员编号", "登录信息编号" + ) VALUES ( + #{targetId}, #{accountId} + ) + + + UPDATE "学员信息表" + SET "登录信息编号" = #{accountId} + WHERE "编号" = #{targetId} + AND "登录信息编号" IS NULL + + + INSERT INTO "用户登录表" ( + "用户账号", "登录信息编号" + ) VALUES ( + #{targetId}, #{accountId} + ) + + + UPDATE "登录信息表" + SET "编号" = "编号" + WHERE 1 = 0 + + + + + + + + + UPDATE "机关人员登录表" + SET "登录信息编号" = #{targetAccountId} + WHERE "登录信息编号" = #{sourceAccountId} + + + UPDATE "教员登录表" + SET "登录信息编号" = #{targetAccountId} + WHERE "登录信息编号" = #{sourceAccountId} + + + UPDATE "教研室登录表" + SET "登录信息编号" = #{targetAccountId} + WHERE "登录信息编号" = #{sourceAccountId} + + + UPDATE "学员队登录表" + SET "登录信息编号" = #{targetAccountId} + WHERE "登录信息编号" = #{sourceAccountId} + + + UPDATE "听查课管理员登录表" + SET "登录信息编号" = #{targetAccountId} + WHERE "登录信息编号" = #{sourceAccountId} + + + UPDATE "学员信息表" + SET "登录信息编号" = #{targetAccountId} + WHERE "登录信息编号" = #{sourceAccountId} + + + UPDATE "用户登录表" + SET "登录信息编号" = #{targetAccountId} + WHERE "登录信息编号" = #{sourceAccountId} + + + UPDATE "登录信息表" + SET "编号" = "编号" + WHERE 1 = 0 + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZMapper.java new file mode 100644 index 000000000..b81886302 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZMapper.java @@ -0,0 +1,19 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.BCKTJXRZ; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 班次课堂教学日志 Mapper + */ +@Mapper +public interface BCKTJXRZMapper extends BaseMapper { + + /** + * 分页查询教学日志(支持状态筛选) + */ + Page selectPageList(Page page, @Param("cond") BCKTJXRZ cond); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZMapper.xml new file mode 100644 index 000000000..bd2687115 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZMapper.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 实施_课程编号, 实施_课程表编号, 学员队编号, 年度, 日期, 节次, 实际时间安排, + 课程科目编号, 教学方法, 应到人数, 实到人数, 调停课情况, 授课情况_教学态度, + 授课情况_教学内容, 授课情况_教学方法, 授课情况_课堂管理, 授课情况_学习收获, + 领导专家听查课, 跟班队干编号, 教学状态, 意见建议, 状态, 教务部门反馈意见, + 填报学员编号, 核准管理员编号, 教学管理机构编号, 与实施计划一致 + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZSKJYMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZSKJYMapper.java new file mode 100644 index 000000000..e4d09dfee --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZSKJYMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.BCKTJXRZSKJY; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface BCKTJXRZSKJYMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZSKJYMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZSKJYMapper.xml new file mode 100644 index 000000000..a7a9975de --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BCKTJXRZSKJYMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + +编号, 班次课堂教学日志_编号, 辅助教员编号, 主教员, 年度 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZLBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZLBMapper.java new file mode 100644 index 000000000..976284255 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZLBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.BZLB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 保障类别 Mapper + */ +@Mapper +public interface BZLBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZLBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZLBMapper.xml new file mode 100644 index 000000000..e8acf21e3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZLBMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + 编号, 名称, 部门编号, 停用 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZTGMXMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZTGMXMapper.java new file mode 100644 index 000000000..b9e8fb484 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZTGMXMapper.java @@ -0,0 +1,31 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.BZTGMX; +import com.roomroot.jwgl.vo.courserunning.TeachingSupportResourceVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 保障提供明细 Mapper。 + *

+ * 提供保障资源库(保障提供明细)的查询能力,支持按保障类别筛选。 + *

+ */ +@Mapper +public interface BZTGMXMapper extends BaseMapper { + + /** + * 查询保障提供资源列表。 + *

+ * 关联保障类别表获取类别名称,支持按保障类别编号筛选, + * 仅返回未停用的保障提供明细。 + *

+ * + * @param bzlbbh 保障类别编号,为空时查询全部 + * @return 保障提供资源列表 + */ + List selectResourceList(@Param("bzlbbh") String bzlbbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZTGMXMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZTGMXMapper.xml new file mode 100644 index 000000000..407cdbf22 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BZTGMXMapper.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BuildMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BuildMapper.java new file mode 100644 index 000000000..0b4b0935e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BuildMapper.java @@ -0,0 +1,30 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JXLB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教学楼表 Mapper + */ +@Mapper +public interface BuildMapper extends BaseMapper { + + /** + * 分页查询教学楼列表 + * + * @param page 分页参数 + * @param jxlb 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jxlb") JXLB jxlb); + + /** + * 获取最大序号 + * + * @return 最大序号 + */ + String selectMaxXh(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BuildMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BuildMapper.xml new file mode 100644 index 000000000..9c383ec3a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/BuildMapper.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + +教学楼代号, 教学楼名称, 地点, 备注, 序号,ID, DEL_FLAG + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomCalendarMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomCalendarMapper.java new file mode 100644 index 000000000..78aab1498 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomCalendarMapper.java @@ -0,0 +1,41 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JXCDL; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 教学场地历 Mapper + */ +@Mapper +public interface ClassRoomCalendarMapper extends BaseMapper { + + /** + * 分页查询教学场地历列表 + * + * @param page 分页参数 + * @param jxcdl 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jxcdl") JXCDL jxcdl); + + /** + * 根据教室编号查询教学场地历列表 + * + * @param jsbh 教室编号 + * @return 教学场地历列表 + */ + List selectByJsbh(@Param("jsbh") String jsbh); + + /** + * 根据日期查询教学场地历列表 + * + * @param rq 日期 + * @return 教学场地历列表 + */ + List selectByRq(@Param("rq") String rq); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomCalendarMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomCalendarMapper.xml new file mode 100644 index 000000000..979bcb032 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomCalendarMapper.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + ID, 编号, 名称, 可排课, 日期, 节次, 创建时间, 备注, 教室编号 + + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomLessonMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomLessonMapper.java new file mode 100644 index 000000000..6c2607d02 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomLessonMapper.java @@ -0,0 +1,32 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JSBKCB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教室输出课程表 Mapper + */ +@Mapper +public interface ClassRoomLessonMapper extends BaseMapper { + + /** + * 分页查询教室输出课程表列表 + * + * @param page 分页参数 + * @param jsbkcb 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jsbkcb") JSBKCB jsbkcb); + + /** + * 根据教室编号和年度查询 + * + * @param jsbh 教室编号 + * @param nd 年度 + * @return 教室输出课程表 + */ + JSBKCB selectByJsbhAndNd(@Param("jsbh") String jsbh, @Param("nd") Integer nd); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomLessonMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomLessonMapper.xml new file mode 100644 index 000000000..9f96e2b1d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomLessonMapper.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + ID, 年度, 教室编号, 课程表, 更新时间, 编号, 有课表 + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomMapper.java new file mode 100644 index 000000000..afb46e1a0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomMapper.java @@ -0,0 +1,23 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JSB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教室表 Mapper + */ +@Mapper +public interface ClassRoomMapper extends BaseMapper { + + /** + * 分页查询教室列表 + * + * @param page 分页参数 + * @param jsb 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jsb") JSB jsb); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomMapper.xml new file mode 100644 index 000000000..fa9220ee8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassRoomMapper.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 教室编号, 教室名称, 教学楼代号, 容纳人数, 备注, 设施设备, 停用, 拼音, 序号, + 额外允许开班数, 教学场地类型, 面积, 考试容纳人数, 虚实类型, 简称, JSONZD, ID, DEL_FLAG + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterCalendarMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterCalendarMapper.java new file mode 100644 index 000000000..05ee5a834 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterCalendarMapper.java @@ -0,0 +1,48 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.XYDRWB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 学员队任务表 Mapper + */ +@Mapper +public interface ClassSemesterCalendarMapper extends BaseMapper { + + /** + * 查询所有有效数据(DEL_FLAG = 0) + * + * @return 所有有效数据列表 + */ + List selectAllValid(@Param("xydrwb") XYDRWB xydrwb); + + void deleteByBcxqBh(@Param("bh") String bh); + + /** + * 根据学员队学期编号和日期范围查询 + * + * @param xydxqbh 学员队学期编号 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 任务记录列表 + */ + List selectByBcxqbhAndDateRange(@Param("xydxqbh") String xydxqbh, + @Param("startDate") String startDate, + @Param("endDate") String endDate); + + /** + * 根据学员队学期编号删除日期范围之外的记录 + * + * @param xydxqbh 学员队学期编号 + * @param startDate 开始日期 + * @param endDate 结束日期 + * @return 删除数量 + */ + int deleteOutsideDateRange(@Param("xydxqbh") String xydxqbh, + @Param("startDate") String startDate, + @Param("endDate") String endDate); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterCalendarMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterCalendarMapper.xml new file mode 100644 index 000000000..ca9a5fdee --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterCalendarMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 年度, 学员队编号, 课编号, 学期第次, 教员编号, 课次序号, 教室编号, 人数, 周课时, 学时, + 课类型, 简称, 考试地点, 考试编号, 选择, 教研室代号, 教研室计划教员编号, 教研室计划备注, + 计划课次序号, 序号标识, 学员队学期编号, 备注, 学分, 创建时间, 变动时间, 课表变动时间, + 考试课时, 编组, 成绩分制, 不计入学员平均分, 理论学时, 实践学时, 考试课时不显示, + 配档序号, 配档起始周, 配档按周, 配档占正课时间, 配档编组, 配当同班异课选修编组, + 配档自定义学时分布数据, 配档启用自定义学时, DEL_FLAG + + + + + + DELETE FROM 学员队任务表 + WHERE 学员队学期编号 = #{bh} + + + + + + + + DELETE FROM 学员队任务表 + WHERE 学员队学期编号 = #{xydxqbh} + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterMapper.java new file mode 100644 index 000000000..22ee3bbbc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterMapper.java @@ -0,0 +1,42 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDNDXQJBXXB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 学员队年度学期基本信息 Mapper + */ +@Mapper +public interface ClassSemesterMapper extends BaseMapper { + + /** + * 分页查询列表 + * + * @param page 分页参数 + * @param xydndxqjbxxb 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("xydndxqjbxxb") XYDNDXQJBXXB xydndxqjbxxb); + + /** + * 查询所有有效数据(DEL_FLAG = 0) + * + * @return 所有有效数据列表 + */ + List selectAllValid(); + + /** + * 根据学员队编号、年度、学期第次查询 + * + * @param xydbh 学员队编号 + * @param nd 年度 + * @param xqdc 学期第次 + * @return 学员队年度学期基本信息 + */ + XYDNDXQJBXXB selectByBcAndNdAndXqdc(@Param("xydbh") String xydbh, @Param("nd") String nd, @Param("xqdc") String xqdc); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterMapper.xml new file mode 100644 index 000000000..915d465d4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ClassSemesterMapper.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 年度, 学员队编号, 开学日期, 结束日期, 学期第次, 专用教室编号, 教学任务编号, + 变动时间, 备注, 序号标识, 预设编班号, 节次类别, 系统模式, 开放教员排课, JSONZD, + 分析报告生成时间, 分析报告文档1编号, 分析报告文档2编号, 分析报告文档3编号, DEL_FLAG + + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DBVersionMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DBVersionMapper.java new file mode 100644 index 000000000..023fe2c40 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DBVersionMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.DBVersion; +import org.apache.ibatis.annotations.Mapper; + +/** + * DBVersion Mapper + */ +@Mapper +public interface DBVersionMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DBVersionMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DBVersionMapper.xml new file mode 100644 index 000000000..acde9eb0f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DBVersionMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ID, VersionDate, VersionValue, UniversityName, 学员队全名称含年级, 学期名称样式, 节次类别, 外部文件存储根路径, 本站文件访问路径, 登录失败弹窗, 允许密码尝试次数, 用户锁定分钟数, 允许教研室管理教材, 使用虚拟教学场地设置班次专用教室, 使用虚拟教员设置课程责任教员, 允许多主讲分组教学, 跟查课录入锁定天数, UniversityCode, UniversityValidCode, 显示78节, 显示晚上, 显示夜间, 教学要点名称, 教材导出包含图片, 建立班次学期信息时自动生成教学计划, 成绩报表显示结论成绩, 缺省系统名称, Excel课表按开课日期排序, Excel课表显示实施教员数, 系统模式, 课程表标题样式, 课程表副标题样式, 大屏显示教学内容, 大屏显示前景标题颜色, 大屏显示前景内容颜色, 大屏显示背景颜色, 实践课标记, 三实课标记, 增加学时标记, 减少学时标记, json字典, 学员正课请假模板文件编号 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DDDLTBBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DDDLTBBMapper.java new file mode 100644 index 000000000..561cf9afd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DDDLTBBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.DDDLTBB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 单点登录同步表 Mapper + */ +@Mapper +public interface DDDLTBBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DDDLTBBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DDDLTBBMapper.xml new file mode 100644 index 000000000..c502ccf4a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DDDLTBBMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + +统一账号, 姓名, 是学员, 单位全称, 学员学号, 年级, 班次, 专业, 数据同步时间 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQBZMXMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQBZMXMapper.java new file mode 100644 index 000000000..a37b6cd69 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQBZMXMapper.java @@ -0,0 +1,27 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.DKSQBZMX; +import com.roomroot.jwgl.vo.courserunning.AdjustSupportVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 调课申请_保障明细 Mapper。 + *

+ * 提供调课申请中保障明细关联数据的CRUD操作及自定义查询。 + *

+ */ +@Mapper +public interface DKSQBZMXMapper extends BaseMapper { + + /** + * 根据调课申请编号查询保障明细列表。 + * + * @param dksqbh 调课申请编号 + * @return 保障明细列表 + */ + List selectByDksqbh(@Param("dksqbh") String dksqbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQBZMXMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQBZMXMapper.xml new file mode 100644 index 000000000..4a544ab1f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQBZMXMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQFZJYMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQFZJYMapper.java new file mode 100644 index 000000000..60d7198a4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQFZJYMapper.java @@ -0,0 +1,27 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.DKSQFZJY; +import com.roomroot.jwgl.vo.courserunning.AdjustTeacherVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 调课申请_辅助教员 Mapper。 + *

+ * 提供调课申请中辅助教员关联数据的CRUD操作及自定义查询。 + *

+ */ +@Mapper +public interface DKSQFZJYMapper extends BaseMapper { + + /** + * 根据调课申请编号查询辅助教员列表。 + * + * @param dksqbh 调课申请编号 + * @return 辅助教员列表 + */ + List selectByDksqbh(@Param("dksqbh") String dksqbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQFZJYMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQFZJYMapper.xml new file mode 100644 index 000000000..78943a055 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQFZJYMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQJSMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQJSMapper.java new file mode 100644 index 000000000..812a33c50 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQJSMapper.java @@ -0,0 +1,27 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.DKSQJS; +import com.roomroot.jwgl.vo.courserunning.AdjustRoomVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 调课申请_教室 Mapper。 + *

+ * 提供调课申请中教室关联数据的CRUD操作及自定义查询。 + *

+ */ +@Mapper +public interface DKSQJSMapper extends BaseMapper { + + /** + * 根据调课申请编号查询教室列表。 + * + * @param dksqbh 调课申请编号 + * @return 教室列表 + */ + List selectByDksqbh(@Param("dksqbh") String dksqbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQJSMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQJSMapper.xml new file mode 100644 index 000000000..3fb2f2d90 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQJSMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQXYDMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQXYDMapper.java new file mode 100644 index 000000000..fb0de2fc7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQXYDMapper.java @@ -0,0 +1,27 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.DKSQXYD; +import com.roomroot.jwgl.vo.courserunning.AdjustTeamVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 调课申请_学员队 Mapper。 + *

+ * 提供调课申请中学员队关联数据的CRUD操作及自定义查询。 + *

+ */ +@Mapper +public interface DKSQXYDMapper extends BaseMapper { + + /** + * 根据调课申请编号查询学员队列表。 + * + * @param dksqbh 调课申请编号 + * @return 学员队列表 + */ + List selectByDksqbh(@Param("dksqbh") String dksqbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQXYDMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQXYDMapper.xml new file mode 100644 index 000000000..c2480439f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DKSQXYDMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLJLBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLJLBMapper.java new file mode 100644 index 000000000..7a8416c39 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLJLBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.DLJLB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 登录记录表 Mapper + */ +@Mapper +public interface DLJLBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLJLBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLJLBMapper.xml new file mode 100644 index 000000000..e14f81bdf --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLJLBMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + +编号, 登录信息编号, 登录时间, IP地址, 内网IP, 事务ID + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLXXBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLXXBMapper.java new file mode 100644 index 000000000..1f3cfded7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLXXBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.DLXXB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 登录信息表 Mapper + */ +@Mapper +public interface DLXXBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLXXBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLXXBMapper.xml new file mode 100644 index 000000000..aa27aa6dc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DLXXBMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + +编号, 用户密码, 用户姓名, 密码提示问题, 密码回答问题, 电话, 上次登录时间, 本次登录时间, 失败次数, 失败时间, 身份证, 照片, 账号, 统一账号, 自我介绍 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DXJSGZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DXJSGZMapper.java new file mode 100644 index 000000000..4a2f90e64 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DXJSGZMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.DXJSGZ; +import org.apache.ibatis.annotations.Mapper; + +/** + * 单项计算规则 Mapper + */ +@Mapper +public interface DXJSGZMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DXJSGZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DXJSGZMapper.xml new file mode 100644 index 000000000..696f0bac5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DXJSGZMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +编号, 名称, 去除法数量阈值, 高分去除比例, 低分去除比例, 整体降分分值线, 整体降分数量比例阈值, 整体降分系数, 整体加分分值线, 整体加分数量比例阈值, 整体加分系数, 优先加分, 修改时间, 状态, 生效时间, 废弃时间, 满分 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DictDataMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DictDataMapper.java new file mode 100644 index 000000000..f0175a3fd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DictDataMapper.java @@ -0,0 +1,18 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SysDictData; +import com.roomroot.jwgl.service.impl.DictDataServiceImpl; +import org.apache.ibatis.annotations.Mapper; + +/** + * 字典数据数据访问接口。 + *

+ * 继承 MyBatis-Plus 的 {@link BaseMapper},提供对 sys_dict_data 表的 + * 基础 CRUD 操作。复杂的分页查询和条件筛选在 {@link DictDataServiceImpl} 中 + * 通过 MyBatis-Plus 的 LambdaQueryWrapper 动态构建。 + *

+ */ +@Mapper +public interface DictDataMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DictTypeMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DictTypeMapper.java new file mode 100644 index 000000000..13b954868 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/DictTypeMapper.java @@ -0,0 +1,18 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SysDictType; +import com.roomroot.jwgl.service.impl.DictTypeServiceImpl; +import org.apache.ibatis.annotations.Mapper; + +/** + * 字典类型数据访问接口。 + *

+ * 继承 MyBatis-Plus 的 {@link BaseMapper},提供对 sys_dict_type 表的 + * 基础 CRUD 操作。复杂的分页查询和条件筛选在 {@link DictTypeServiceImpl} 中 + * 通过 MyBatis-Plus 的 LambdaQueryWrapper 动态构建。 + *

+ */ +@Mapper +public interface DictTypeMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/FZQZBZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/FZQZBZMapper.xml new file mode 100644 index 000000000..7833f374c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/FZQZBZMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + +编号, 序号, 名称, 创建时间, 停用状态, 备注, 停用时间, 说明 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/FZQZBZQZGZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/FZQZBZQZGZMapper.xml new file mode 100644 index 000000000..fe777c1a9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/FZQZBZQZGZMapper.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + +编号, 序号, 分值权重标准编号, 名称, 详细信息, 权值, 备注 + + + + + + + + + + + + INSERT INTO 分值权重标准_权重规则 (编号, 序号, 分值权重标准编号, 名称, 详细信息, 权值, 备注) + SELECT #{rule.bh}, #{rule.xh}, #{rule.fzqzbzbh}, #{rule.mc}, #{rule.xxxx}, #{rule.qz}, #{rule.bz} + FROM DUAL + WHERE NOT EXISTS ( + SELECT 1 FROM 分值权重标准_权重规则 + WHERE 编号 = #{rule.bh} + ) + + + + UPDATE 分值权重标准_权重规则 + SET 序号 = #{rule.xh}, + 分值权重标准编号 = #{rule.fzqzbzbh}, + 名称 = #{rule.mc}, + 详细信息 = #{rule.xxxx}, + 权值 = #{rule.qz}, + 备注 = #{rule.bz} + WHERE 编号 = #{rule.bh} + + + + + + + + DELETE FROM 分值权重标准_权重规则 + WHERE 分值权重标准编号 = #{standardId} + + + + DELETE FROM 分值权重标准_权重规则 + WHERE 编号 = #{id} + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/FZZHGZBZCPJGYSMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/FZZHGZBZCPJGYSMapper.xml new file mode 100644 index 000000000..f77b6f195 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/FZZHGZBZCPJGYSMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + +编号, 名称, 备注, 序号 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/GKLDLBZBBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/GKLDLBZBBMapper.java new file mode 100644 index 000000000..f7440eeb5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/GKLDLBZBBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.GKLDLBZBB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 跟课领导类别指标表 Mapper + */ +@Mapper +public interface GKLDLBZBBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/GKLDLBZBBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/GKLDLBZBBMapper.xml new file mode 100644 index 000000000..8bf757f8f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/GKLDLBZBBMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + +编号, 名称, 禁用状态, 指标单位, 指标量, 备注 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JCSJBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JCSJBMapper.xml new file mode 100644 index 000000000..649c839cc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JCSJBMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + +编号, 双节次, 节次索引, 开始时间, 结束时间, 备注, 名称, 简称, 时段 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JGMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JGMapper.java new file mode 100644 index 000000000..4fb18ea7b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JGMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.JG; +import org.apache.ibatis.annotations.Mapper; + +/** + * 结果 Mapper + */ +@Mapper +public interface JGMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JGMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JGMapper.xml new file mode 100644 index 000000000..dd45875c4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JGMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + +年度, 学员队编号, 课编号, 学期第次, 教员编号, 课次序号, 教室编号, 人数, 周课时, 学时, 课类型, 简称 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JSZWKSFBZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JSZWKSFBZMapper.java new file mode 100644 index 000000000..8781d6718 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JSZWKSFBZMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.JSZWKSFBZ; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface JSZWKSFBZMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JSZWKSFBZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JSZWKSFBZMapper.xml new file mode 100644 index 000000000..2a8f80e94 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JSZWKSFBZMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + +编号, 技术职务编号, 课时费标准编号, 标准课时量, 课时费, 超量课时费, 备注 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JSZWMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JSZWMapper.xml new file mode 100644 index 000000000..5a48cf602 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JSZWMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + +编号, 代码, 名称, 备注 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZJYKSBZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZJYKSBZMapper.java new file mode 100644 index 000000000..9cc8c7e7e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZJYKSBZMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.JXRZJYKSBZ; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface JXRZJYKSBZMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZJYKSBZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZJYKSBZMapper.xml new file mode 100644 index 000000000..864465d30 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZJYKSBZMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +编号, 教学日志课时补助审批编号, 教员编号, 技术职务编号, 是否优质, 课程科目编号, 非课程任务名称, 课时量, 基本标准, 增发标准, 课时补助金额, 培训期班, 部系名称, 教研室名称, 教学学时, 主讲, 讲授方式 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZKSBZSPBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZKSBZSPBMapper.java new file mode 100644 index 000000000..945342c7f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZKSBZSPBMapper.java @@ -0,0 +1,19 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JXRZKSBZSPB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教学日志课时补助审批表 Mapper + */ +@Mapper +public interface JXRZKSBZSPBMapper extends BaseMapper { + + /** + * 分页查询课时补助审批表 + */ + Page selectPageList(Page page, @Param("cond") JXRZKSBZSPB cond); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZKSBZSPBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZKSBZSPBMapper.xml new file mode 100644 index 000000000..aa8300348 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZKSBZSPBMapper.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + 编号, 名称, 核算开始日期, 核算结束日期, 总课时量, 优质课时量, 总补助金额, + 优质课时量最高比例, 优质课时量比例, 课时补助标准编号, 课时核算标准编号, + 核算日期, 状态, 任务类别 + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZXYXXQKMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZXYXXQKMapper.java new file mode 100644 index 000000000..b19958180 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZXYXXQKMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.JXRZXYXXQK; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface JXRZXYXXQKMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZXYXXQKMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZXYXXQKMapper.xml new file mode 100644 index 000000000..275dee8f2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXRZXYXXQKMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + +编号, 教学日志编号, 学员编号, 学习效果, 学习情况, 到课情况, 请假情况 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXSSJHTJBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXSSJHTJBMapper.java new file mode 100644 index 000000000..d81facf31 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXSSJHTJBMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.JXSSJHTJB; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface JXSSJHTJBMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXSSJHTJBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXSSJHTJBMapper.xml new file mode 100644 index 000000000..9cea087ca --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JXSSJHTJBMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +编号, 日期, 节次, 总学时, 总节次, 已办理计划调整数, 调课数, 教学日志填写数, 已审核教学日志数, 听查课数, 教学督导数, 开课课程门数, 课程考核次数, 动用教学场地数, 上课教员数, 教学班次数, 实体教学班次数, 选修教学班次数, 学员总人数, 教学请假人次, 教学请假学员人数, 缺课人次, 缺课人数, 已请假缺课人次, 已请假缺课人数, 未请假缺课人次, 未请假缺课人数, 修改时间 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYBMapper.java new file mode 100644 index 000000000..538a746dc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYBMapper.java @@ -0,0 +1,30 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JYB; +import com.roomroot.jwgl.vo.faculty.TeacherListVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教员表 Mapper + *

+ * 提供教员数据的CRUD操作及分页查询。 + *

+ */ +@Mapper +public interface JYBMapper extends BaseMapper { + + /** + * 分页查询教员列表(支持条件筛选) + * + * @param page MyBatis-Plus 分页对象 + * @param jyb 教员查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jyb") JYB jyb); + + /** 教员列表双表联查分页 */ + Page selectTeacherPageList(Page page, @Param("jyb") JYB cond); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYBMapper.xml new file mode 100644 index 000000000..76f21a189 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYBMapper.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 教员编号, 教员姓名, 教研室代号, 备注, 职称, 离职状态, 序号, 专业技术职务等级, + 虚实类型, 教员类别, 卡号, 拼音, 在职类别, 额外允许开班数, 手机号, 导师类别, + 学缘, 身份证号, 性别, 外训教员, 外训译员, 政治类教员, 序号标识, JSONZD, + 专业技术职务等级时间, 职称时间 + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYKSBZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYKSBZMapper.java new file mode 100644 index 000000000..860618a14 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYKSBZMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.JYKSBZ; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface JYKSBZMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYKSBZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYKSBZMapper.xml new file mode 100644 index 000000000..3136baae5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYKSBZMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + +编号, 教员编号, 补助项目编号, 补助课时量, 备注 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYNZKSFMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYNZKSFMapper.java new file mode 100644 index 000000000..e1313f245 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYNZKSFMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.JYNZKSF; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface JYNZKSFMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYNZKSFMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYNZKSFMapper.xml new file mode 100644 index 000000000..84d1f409a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYNZKSFMapper.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +编号, 年终课时费编号, 教员编号, 技术职务编号, 绩效评定等级, 上学期课时量, 下学期课时量, 上学期实际教学课时量, 上学期补助教学课时量, 上学期相关教学课时量, 上学期不计酬劳课时量, 下学期实际教学课时量, 下学期补助教学课时量, 下学期相关教学课时量, 下学期不计酬劳课时量, 教学课时量, 上学期教学课时量, 下学期教学课时量, 总计课时量, 实际教学课时量, 补助教学课时量, 相关教学课时量, 不计酬劳课时量, 超课时量, 计算公式, 课时费, 备注 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSBMapper.java new file mode 100644 index 000000000..cc5e27dc3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSBMapper.java @@ -0,0 +1,28 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JYSB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教研室表 Mapper + *

+ * 提供教研室数据的CRUD操作。 + * 基础CRUD继承 MyBatis-Plus BaseMapper, + * 分页条件查询通过 XML 映射文件实现。 + *

+ */ +@Mapper +public interface JYSBMapper extends BaseMapper { + + /** + * 分页查询教研室列表(支持条件筛选) + * + * @param page MyBatis-Plus 分页对象 + * @param jysb 教研室查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jysb") JYSB jysb); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSBMapper.xml new file mode 100644 index 000000000..a78a4d4ea --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSBMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + +教研室代号, 教研室名称, 备注, 停用, 停用时间, 序号, 部系, 虚实类型, 简称, 机关性质 + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSCKCBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSCKCBMapper.java new file mode 100644 index 000000000..e03eb0f09 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSCKCBMapper.java @@ -0,0 +1,26 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JYSCKCB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教员输出课程表 Mapper + *

+ * 提供教员课程表数据的查询操作。 + *

+ */ +@Mapper +public interface JYSCKCBMapper extends BaseMapper { + + /** + * 分页查询教员输出课程表列表 + * + * @param page MyBatis-Plus 分页对象 + * @param jysckcb 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jysckcb") JYSCKCB jysckcb); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSCKCBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSCKCBMapper.xml new file mode 100644 index 000000000..5e806ce81 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSCKCBMapper.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + 年度, 教员编号, 课程表, 更新时间, 编号, 有课表 + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSXMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSXMapper.java new file mode 100644 index 000000000..f74e95b1b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSXMapper.java @@ -0,0 +1,26 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JYSX; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教员属性 Mapper + *

+ * 提供教员属性数据的CRUD操作及分页查询。 + *

+ */ +@Mapper +public interface JYSXMapper extends BaseMapper { + + /** + * 分页查询教员属性列表(支持条件筛选) + * + * @param page MyBatis-Plus 分页对象 + * @param jysx 教员属性查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jysx") JYSX jysx); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSXMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSXMapper.xml new file mode 100644 index 000000000..cb81f6046 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JYSXMapper.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 教员编号, 主任, 职务, 职务时间, 技术职务, 技术职务时间, 技术等级, 技术等级时间, + 军衔文职, 军衔文职时间, 出生时间, 学历, 学位, 入伍工作时间, 任代职经历, + 发表论文, 教龄, 教龄计算日期 + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JZGZLMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JZGZLMapper.java new file mode 100644 index 000000000..58450ae33 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JZGZLMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.JZGZL; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface JZGZLMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JZGZLMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JZGZLMapper.xml new file mode 100644 index 000000000..42657e4d0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/JZGZLMapper.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + +编号, 教员编号, 年度, 学期, 课程教学工作量, 科研学术工作量, 指导学员工作量, 教学建设工作量, 咨询服务工作量, 总工作量, 创建时间, 修改时间 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBBZMXMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBBZMXMapper.java new file mode 100644 index 000000000..eb33477ab --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBBZMXMapper.java @@ -0,0 +1,47 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KCBBZMX; +import com.roomroot.jwgl.vo.courserunning.TeachingSupportHistoryVO; +import com.roomroot.jwgl.vo.courserunning.TeachingSupportPlanVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 课程表_保障明细 Mapper。 + *

+ * 提供课次教学保障计划数据的CRUD操作及自定义查询。 + *

+ */ +@Mapper +public interface KCBBZMXMapper extends BaseMapper { + + /** + * 根据课次编号查询教学保障计划列表。 + *

+ * 关联保障提供明细表获取保障名称、单位,关联保障类别表获取类别名称, + * 关联教员表获取确认人姓名。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + * @return 教学保障计划列表 + */ + List selectBySskcbbh(@Param("sskcbbh") String sskcbbh); + + /** + * 根据实施_课程编号查询历史登记过的保障物资(历史联想)。 + *

+ * 关联实施_课程表获取同实施课程下所有历史课次已登记的保障明细, + * 按保障提供明细编号聚合去重,统计历史使用次数、最近登记时间和平均数量, + * 排除当前课次的登记记录,避免重复推荐。 + *

+ * + * @param sskcbh 实施_课程编号 + * @param sskcbbh 当前课次编号(排除当前课次) + * @return 历史联想保障物资列表 + */ + List selectHistoryBySskcbh(@Param("sskcbh") String sskcbh, + @Param("sskcbbh") String sskcbbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBBZMXMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBBZMXMapper.xml new file mode 100644 index 000000000..25626646d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBBZMXMapper.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBMBBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBMBBMapper.java new file mode 100644 index 000000000..97bc0412c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBMBBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KCBMBB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 课程表模版表 Mapper + */ +@Mapper +public interface KCBMBBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBMBBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBMBBMapper.xml new file mode 100644 index 000000000..3e7b9a91b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBMBBMapper.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +编号, 模版名称, 标题位置, 专业信息位置, 课表时间行号, 显示夜间, 末尾列位置, 课程行位置, 课程序号列位置, 课程名称列位置, 课程简称列位置, 课程学时列位置, 课程类型列位置, 课程责任教研室列位置, 课程责任教员列位置, 课程授课教员列位置, 课程人数列位置, 课程场地列位置, 全局备注位置, 更新时间, 上传者, 备注, 模版, 模版存在, 模版可用, 备注纵向样式, 无课站位字符, 队别位置, 课程合班信息位置, 标题格式, 副标题格式, 专用教室位置, 附加备注1位置, 附加备注2位置, 课表样式, 课表包含选修, 纵向显示78节, 纵向显示晚上, 纵向显示天数, 课程全部场地列位置, 课程表样式, 课程主讲教员列位置, 课程辅导教员列位置, 课程周次范围列位置, 课程表起始列位置, 课程表一周正课天数, json字典 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBMapper.java new file mode 100644 index 000000000..b69a01ebe --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KCB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 课程表Mapper + */ +@Mapper +public interface KCBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBZMapper.java new file mode 100644 index 000000000..fb23c193a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBZMapper.java @@ -0,0 +1,26 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.KCBZ; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 课程标准 Mapper + */ +@Mapper +public interface KCBZMapper extends BaseMapper { + + /** + * 分页查询课程标准列表 + */ + Page selectPageList(Page page, @Param("kcbz") KCBZ kcbz); + + /** + * 查询某课程的所有版本 + */ + List selectVersionsByCourse(@Param("kbh") String kbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBZMapper.xml new file mode 100644 index 000000000..1cc1bb35d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCBZMapper.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 课编号, 名称, 适用专业, 课次数, 备注, 执行人编号, 执行时间, + 执行状态, 上报状态, 上报时间, 上报人编号, 撤回原因, 正文, JSONZD + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCJXYX_CZRZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCJXYX_CZRZMapper.java new file mode 100644 index 000000000..0b08fe0d8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCJXYX_CZRZMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KCJXYX_CZRZ; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface KCJXYX_CZRZMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCJXYX_CZRZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCJXYX_CZRZMapper.xml new file mode 100644 index 000000000..966734a70 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KCJXYX_CZRZMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + +编号, 统一编号, 操作类型, 操作内容, 操作人编号, 操作时间, 操作IP, 原数据快照, 新数据快照 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KLXBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KLXBMapper.java new file mode 100644 index 000000000..b7879dbf6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KLXBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KLXB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 课类型表 Mapper + */ +@Mapper +public interface KLXBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KLXBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KLXBMapper.xml new file mode 100644 index 000000000..c7b83af11 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KLXBMapper.xml @@ -0,0 +1,14 @@ + + + + + + + + + + +课类型, 备注 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSBMapper.java new file mode 100644 index 000000000..d1dd12a9a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KSB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 考试表 Mapper + */ +@Mapper +public interface KSBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSBZXMMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSBZXMMapper.java new file mode 100644 index 000000000..0e06d94a9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSBZXMMapper.java @@ -0,0 +1,16 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KSBZXM; +import org.apache.ibatis.annotations.Mapper; + +/** + * 账户管理接口鉴权数据访问接口。 + * + *

该 Mapper 只读取现有在线用户记录,不参与登录、账户资料或角色绑定业务。

+ */ +@Mapper +public interface KSBZXMMapper extends BaseMapper { + + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSBZXMMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSBZXMMapper.xml new file mode 100644 index 000000000..4eeffea14 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSBZXMMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +编号, 名称, 性质, 依据, 开始日期, 结束日期, 天数, 基本课时量, 课时类型, 说明, 备注, 创建时间, 修改时间, 提交人账号, 提交状态, 提交时间, 审批人编号, 审批状态, 审批时间, 项目类型 + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSFBZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSFBZMapper.java new file mode 100644 index 000000000..803b308a0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSFBZMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KSFBZ; +import org.apache.ibatis.annotations.Mapper; + +/** + * 课时费标准 Mapper + */ +@Mapper +public interface KSFBZMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSFBZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSFBZMapper.xml new file mode 100644 index 000000000..c3ca12c9d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSFBZMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + +编号, 名称, 默认标准课时量, 默认课时费标准, 默认超量课时费标准, 相关教学补助课时费标准, 绩效优秀补助, 绩效良好补助, 绩效中等补助, 绩效及格补助, 绩效不及格补助, 说明, 备注, 创建时间, 修改时间 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSHBZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSHBZMapper.java new file mode 100644 index 000000000..e6d04c81a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSHBZMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KSHBZ; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface KSHBZMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSHBZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSHBZMapper.xml new file mode 100644 index 000000000..2ac2e1d2b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSHBZMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + +编号, 名称, 主讲教师课时系数, 辅讲教师课时系数, 讲授方式系数, 考试主讲系数, 考试指导教师系数, 说明, 创建时间, 修改时间 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSTJBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSTJBMapper.java new file mode 100644 index 000000000..10eea5f50 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSTJBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KSTJB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 课时统计表 Mapper + */ +@Mapper +public interface KSTJBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSTJBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSTJBMapper.xml new file mode 100644 index 000000000..e91175f25 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSTJBMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + +年份, 教员编号, 上半年, 下半年, 职称 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSXSFAMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSXSFAMapper.java new file mode 100644 index 000000000..3de1b5c28 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSXSFAMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KSXSFA; +import org.apache.ibatis.annotations.Mapper; + +/** + * 课时系数方案 Mapper + */ +@Mapper +public interface KSXSFAMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSXSFAMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSXSFAMapper.xml new file mode 100644 index 000000000..cc7c1250c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KSXSFAMapper.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + +编号, 名称, 说明, 备注, 创建时间, 修改时间, 辅讲课时系数, 满班学员人数, 递增10学员班级系数, 班级系数上限, 第78节课时量, 夜间课时量, 主讲课时系数, 默认课堂教学方法系数, 合班系数增量, 合班系数上限 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTBZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTBZMapper.java new file mode 100644 index 000000000..5ba9c7977 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTBZMapper.java @@ -0,0 +1,25 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KTBZ; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 课堂标准 Mapper + */ +@Mapper +public interface KTBZMapper extends BaseMapper { + + /** + * 根据课程标准编号查询课堂列表 + */ + List selectByKcbh(@Param("kcbh") String kcbh); + + /** + * 根据课程标准编号物理删除所有课堂 + */ + int deleteByKcbh(@Param("kcbh") String kcbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTBZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTBZMapper.xml new file mode 100644 index 000000000..031a0c1a1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTBZMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + 编号, 课程标准编号, 课堂节次, 教学内容, 教学要点, 教学方法, + 主讲教员数, 辅讲教员数, 备注, 教学目的, 教学要求 + + + + + + + + DELETE FROM 课堂标准 + WHERE 课程标准编号 = #{kcbh} + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTJXFFMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTJXFFMapper.xml new file mode 100644 index 000000000..19bf2717a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTJXFFMapper.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + +名称, 日课表序号, 显示颜色, 背景颜色, 考核, 讲授方式, 需审批 + + + + + + + + + + INSERT INTO 课堂教学方法 ( + 名称, 日课表序号, 显示颜色, 背景颜色, + 考核, 讲授方式, 需审批 + ) + SELECT + #{teachingMethod.mC}, #{teachingMethod.rKBXH}, + #{teachingMethod.xSYS}, #{teachingMethod.bJYS}, + #{teachingMethod.kH}, #{teachingMethod.jSFS}, + #{teachingMethod.xSP} + FROM DUAL + WHERE NOT EXISTS ( + SELECT 1 + FROM 课堂教学方法 + WHERE 名称 = #{teachingMethod.mC} + ) + + + + UPDATE 课堂教学方法 + SET 名称 = #{teachingMethod.mC}, + 日课表序号 = #{teachingMethod.rKBXH}, + 显示颜色 = #{teachingMethod.xSYS}, + 背景颜色 = #{teachingMethod.bJYS}, + 考核 = #{teachingMethod.kH}, + 讲授方式 = #{teachingMethod.jSFS}, + 需审批 = #{teachingMethod.xSP} + WHERE 名称 = #{originalName} + + + + + + DELETE FROM 课堂教学方法 + WHERE 名称 = #{name} + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTRZXYKQBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTRZXYKQBMapper.java new file mode 100644 index 000000000..ef88cf1d2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KTRZXYKQBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KTRZXYKQB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 课堂日志学员考勤表数据访问接口 + */ +@Mapper +public interface KTRZXYKQBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KYKYJFBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KYKYJFBMapper.java new file mode 100644 index 000000000..842ba6b84 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KYKYJFBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.KYKYJFB; +import org.apache.ibatis.annotations.Mapper; + +/** + * KY_科研经费表 Mapper + */ +@Mapper +public interface KYKYJFBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KYKYJFBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KYKYJFBMapper.xml new file mode 100644 index 000000000..8bd63de7d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/KYKYJFBMapper.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + +编号, 名称, 经费来源, 经费负责人, 下拨经费, 下拨时间, 用途, 是否为专项经费, 计划完成日期, 实际完成日期, 已使用经费, 剩余经费, 创建时间, 修改时间, 备注, 经费变动记录, 是否完成 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/LBZYGLPTMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/LBZYGLPTMapper.java new file mode 100644 index 000000000..604c791f8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/LBZYGLPTMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.LBZYGLPT; +import org.apache.ibatis.annotations.Mapper; + +/** + * 录播资源管理平台 Mapper + */ +@Mapper +public interface LBZYGLPTMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/LBZYGLPTMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/LBZYGLPTMapper.xml new file mode 100644 index 000000000..7ee4f0ba0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/LBZYGLPTMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +编号, 平台标识, 平台品牌, 平台类型, 平台版本, 平台状态, 平台UserName, 平台Password, 平台IP, 平台端口, 平台参数1, 平台参数2, 平台参数3, 平台参数4, 平台参数5, 平台参数6, 资源平台独立完成录播, json字典 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/MemberOnlineInfoMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/MemberOnlineInfoMapper.java new file mode 100644 index 000000000..2f30d1d68 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/MemberOnlineInfoMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.MemberOnlineInfo; +import org.apache.ibatis.annotations.Mapper; + +/** + * MemberOnlineInfo Mapper + */ +@Mapper +public interface MemberOnlineInfoMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/MemberOnlineInfoMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/MemberOnlineInfoMapper.xml new file mode 100644 index 000000000..c921c9a1a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/MemberOnlineInfoMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + +SessionId, 账号, 姓名, 单位, 角色类别, IP, 开始访问时间, 最后访问时间, 最后访问路径 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NDZHPDJYMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NDZHPDJYMapper.java new file mode 100644 index 000000000..d24bddcb8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NDZHPDJYMapper.java @@ -0,0 +1,26 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.NDZHPDJY; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 年度综合评定_教员 Mapper + *

+ * 提供年度综合评定数据的查询操作。 + *

+ */ +@Mapper +public interface NDZHPDJYMapper extends BaseMapper { + + /** + * 分页查询年度综合评定_教员列表 + * + * @param page MyBatis-Plus 分页对象 + * @param ndzhpdy 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("ndzhpdy") NDZHPDJY ndzhpdy); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NDZHPDJYMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NDZHPDJYMapper.xml new file mode 100644 index 000000000..49164a39c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NDZHPDJYMapper.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 年度综合评定_教研室编号, 教员编号, 参评, 有教学责任事故, 教学责任事故说明, + 全年只承担辅讲, 教学竞赛获奖, 教学竞赛获奖说明, 免评, 职称, 领导评估分, + 专家评估分, 同行评估分, 学员评估分, 综合分, 学员评估分未达标, 排序最后比例, + A比例之外, 预评估结论, 最终结论, 备注, 专业技术职务等级 + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NZKSFMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NZKSFMapper.java new file mode 100644 index 000000000..2c9037483 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NZKSFMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.NZKSF; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface NZKSFMapper extends BaseMapper {} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NZKSFMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NZKSFMapper.xml new file mode 100644 index 000000000..c15907de4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NZKSFMapper.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +编号, 名称, 上学期年度, 下学期年度, 课时费标准编号, 不计酬劳课时量, 上学期课时量, 下学期课时量, 上学期实际教学课时量, 上学期补助教学课时量, 上学期相关教学课时量, 上学期不计酬劳课时量, 下学期实际教学课时量, 下学期补助教学课时量, 下学期相关教学课时量, 下学期不计酬劳课时量, 教学课时量, 上学期教学课时量, 下学期教学课时量, 总计课时量, 实际教学课时量, 补助教学课时量, 相关教学课时量, 剩余课时量, 说明, 备注 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NoticeAnnouncementMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NoticeAnnouncementMapper.java new file mode 100644 index 000000000..64762d589 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NoticeAnnouncementMapper.java @@ -0,0 +1,204 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementCreateDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementQueryDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientInsertDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientQueryDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementUpdateDTO; +import com.roomroot.jwgl.dto.noticeannouncement.UserNoticeAnnouncementQueryDTO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementDetailVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementRecipientVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementStatisticsVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementVO; +import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementDetailVO; +import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 通知公告管理 Mapper。 + * + *

统一维护通告表、通告用户信息表和发布范围账户解析 SQL, + * 不依赖存在乱码和公共字段不匹配问题的旧实体通用 CRUD。

+ */ +@Mapper +public interface NoticeAnnouncementMapper { + + /** + * 分页查询通知公告及其接收、阅读统计。 + */ + Page selectNoticeAnnouncementPage( + Page page, + @Param("query") NoticeAnnouncementQueryDTO query, + @Param("publishStartTime") + LocalDateTime publishStartTime, + @Param("publishEndExclusiveTime") + LocalDateTime publishEndExclusiveTime); + + /** + * 根据公告编号查询后台详情。 + */ + NoticeAnnouncementDetailVO selectNoticeAnnouncementDetail( + @Param("id") String id); + + /** + * 锁定公告并查询当前状态,供状态流转事务使用。 + */ + String selectNoticeStatusForUpdate( + @Param("id") String id); + + /** + * 新增一条拟制公告。 + */ + int insertNoticeAnnouncement( + @Param("id") String id, + @Param("notice") NoticeAnnouncementCreateDTO notice, + @Param("now") LocalDateTime now, + @Param("status") String status); + + /** + * 修改拟制或已下架公告内容。 + */ + int updateNoticeAnnouncement( + @Param("notice") NoticeAnnouncementUpdateDTO notice); + + /** + * 发布公告并重置本轮阅读次数。 + */ + int updateNoticePublishState( + @Param("id") String id, + @Param("status") String status, + @Param("publishTime") LocalDateTime publishTime, + @Param("all") boolean all); + + /** + * 修改公告状态。 + */ + int updateNoticeStatus( + @Param("id") String id, + @Param("status") String status); + + /** + * 修改公告优先级。 + */ + int updateNoticePriority( + @Param("id") String id, + @Param("priority") Integer priority); + + /** + * 删除公告主体。 + */ + int deleteNoticeAnnouncement( + @Param("id") String id); + + /** + * 根据当前 Servlet Session 查询真实账户编号。 + */ + String selectAccountIdBySessionId( + @Param("sessionId") String sessionId); + + /** + * 统计指定编号中存在且启用的部别数量。 + */ + int countActiveDepartments( + @Param("departmentIds") + List departmentIds); + + /** + * 查询全校全部有效账户编号。 + */ + List selectAllActiveAccountIds(); + + /** + * 查询全部有效教员账户编号。 + */ + List selectAllActiveTeacherAccountIds(); + + /** + * 查询指定部别关联的全部有效账户编号。 + */ + List selectActiveAccountIdsByDepartments( + @Param("departmentIds") + List departmentIds); + + /** + * 删除公告原有接收人快照。 + */ + int deleteNoticeRecipients( + @Param("noticeId") String noticeId); + + /** + * 批量写入公告接收人快照。 + */ + int insertNoticeRecipients( + @Param("noticeId") String noticeId, + @Param("recipients") + List + recipients, + @Param("receivedTime") + LocalDateTime receivedTime); + + /** + * 分页查询公告接收和阅读明细。 + */ + Page + selectNoticeRecipientPage( + Page page, + @Param("query") + NoticeAnnouncementRecipientQueryDTO query); + + /** + * 汇总公告接收和阅读人数。 + */ + NoticeAnnouncementStatisticsVO + selectNoticeStatistics( + @Param("noticeId") String noticeId); + + /** + * 分页查询当前账户可见的已发布公告。 + */ + Page selectUserNoticePage( + Page page, + @Param("accountId") String accountId, + @Param("query") + UserNoticeAnnouncementQueryDTO query, + @Param("publishedStatus") + String publishedStatus); + + /** + * 锁定当前账户可见的已发布公告。 + */ + String selectUserNoticeStatusForUpdate( + @Param("noticeId") String noticeId, + @Param("accountId") String accountId, + @Param("publishedStatus") + String publishedStatus); + + /** + * 首次打开公告时标记当前账户已经阅读。 + */ + int markNoticeRecipientRead( + @Param("noticeId") String noticeId, + @Param("accountId") String accountId, + @Param("readTime") LocalDateTime readTime); + + /** + * 增加公告累计打开次数。 + */ + int incrementNoticeReadCount( + @Param("noticeId") String noticeId); + + /** + * 查询当前账户可见的公告详情。 + */ + UserNoticeAnnouncementDetailVO + selectUserNoticeDetail( + @Param("noticeId") String noticeId, + @Param("accountId") String accountId, + @Param("publishedStatus") + String publishedStatus); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NoticeAnnouncementMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NoticeAnnouncementMapper.xml new file mode 100644 index 000000000..b5362b5e6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NoticeAnnouncementMapper.xml @@ -0,0 +1,711 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO "通告表" ( + "编号", "标题", "内容", "发布时间", "创建时间", + "优先级", "阅读次数", "状态", "类别", "备注", "全体" + ) VALUES ( + #{id}, #{notice.title}, #{notice.content}, #{now}, #{now}, + #{notice.priority}, 0, #{status}, #{notice.category}, + #{notice.remark}, 0 + ) + + + + + UPDATE "通告表" + SET "标题" = #{notice.title}, + "内容" = #{notice.content}, + "优先级" = #{notice.priority}, + "类别" = #{notice.category}, + "备注" = #{notice.remark} + WHERE "编号" = #{notice.id} + + + + + UPDATE "通告表" + SET "状态" = #{status}, + "发布时间" = #{publishTime}, + "全体" = #{all}, + "阅读次数" = 0 + WHERE "编号" = #{id} + + + + + UPDATE "通告表" + SET "状态" = #{status} + WHERE "编号" = #{id} + + + + + UPDATE "通告表" + SET "优先级" = #{priority} + WHERE "编号" = #{id} + + + + + DELETE FROM "通告表" + WHERE "编号" = #{id} + + + + + + + + + + + + + + + + + + + + DELETE FROM "通告用户信息" + WHERE "通告编号" = #{noticeId} + + + + + INSERT INTO "通告用户信息" ( + "编号", "通告编号", "用户编号", + "已接收", "接收时间", "已阅读", "阅读时间" + ) + + SELECT + #{recipient.id}, + #{noticeId}, + #{recipient.accountId}, + 1, + #{receivedTime}, + 0, + NULL + FROM DUAL + + + + + + + + + + + + + + + + + + UPDATE "通告用户信息" + SET "已接收" = 1, + "接收时间" = NVL("接收时间", #{readTime}), + "已阅读" = 1, + "阅读时间" = NVL("阅读时间", #{readTime}) + WHERE "通告编号" = #{noticeId} + AND "用户编号" = #{accountId} + AND NVL("已阅读", 0) = 0 + + + + + UPDATE "通告表" + SET "阅读次数" = NVL("阅读次数", 0) + 1 + WHERE "编号" = #{noticeId} + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NotificationLogMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NotificationLogMapper.java new file mode 100644 index 000000000..2f3266bec --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NotificationLogMapper.java @@ -0,0 +1,64 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogQueryDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogRecipientQueryDTO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogDetailVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogRecipientVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogStatisticsVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 通知日志管理 Mapper。 + * + *

基于通告表和通告用户信息表查询已经实际生成接收记录的发送历史, + * 不重复维护通知公告的写入和状态流转 SQL。

+ */ +@Mapper +public interface NotificationLogMapper { + + /** + * 分页查询通知发送历史及其接收、阅读统计。 + * + * @param page MyBatis-Plus 分页对象 + * @param query 已完成规范化的查询参数 + * @return 通知发送历史分页数据 + */ + Page selectNotificationLogPage( + Page page, + @Param("query") NotificationLogQueryDTO query); + + /** + * 根据通知编号查询单条发送日志详情。 + * + * @param id 已经完成规范化的通知编号 + * @return 通知主体、发送、接收和阅读详情 + */ + NotificationLogDetailVO selectNotificationLogDetail( + @Param("id") String id); + + /** + * 分页查询指定通知的接收对象和阅读明细。 + * + * @param page MyBatis-Plus 分页对象 + * @param query 已经完成规范化的明细查询参数 + * @return 按账户去重后的接收与阅读明细 + */ + Page + selectNotificationLogRecipientPage( + Page page, + @Param("query") + NotificationLogRecipientQueryDTO query); + + /** + * 按通知日志筛选条件汇总发送与阅读数据。 + * + * @param query 已经完成规范化的筛选条件 + * @return 通知数量、发送结果和阅读情况汇总 + */ + NotificationLogStatisticsVO + selectNotificationLogStatistics( + @Param("query") NotificationLogQueryDTO query); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NotificationLogMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NotificationLogMapper.xml new file mode 100644 index 000000000..24d8875a9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/NotificationLogMapper.xml @@ -0,0 +1,359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + recipient_status."noticeId", + COUNT(1) AS "recipientCount", + NVL(SUM(recipient_status."received"), 0) + AS "receivedCount", + NVL(SUM(recipient_status."read"), 0) + AS "readerCount", + MIN(recipient_status."receivedTime") + AS "firstReceivedTime" + FROM ( + SELECT + recipient."通告编号" AS "noticeId", + recipient."用户编号" AS "accountId", + MAX( + CASE + WHEN recipient."已接收" = 1 THEN 1 + ELSE 0 + END + ) AS "received", + MAX( + CASE + WHEN recipient."已阅读" = 1 THEN 1 + ELSE 0 + END + ) AS "read", + MIN( + CASE + WHEN recipient."已接收" = 1 + THEN recipient."接收时间" + ELSE NULL + END + ) AS "receivedTime" + FROM "通告用户信息" recipient + GROUP BY + recipient."通告编号", + recipient."用户编号" + ) recipient_status + GROUP BY recipient_status."noticeId" + + + + + + + + AND LOWER(notice."标题") LIKE + '%' || LOWER(#{query.title}) || '%' + + + + + AND notice."类别" = #{query.category} + + + + + AND NVL(statistics."firstReceivedTime", + notice."发布时间") + >= #{query.sendStartTime} + + + + + AND NVL(statistics."firstReceivedTime", + notice."发布时间") + <= #{query.sendEndTime} + + + + + AND statistics."recipientCount" > 0 + AND statistics."receivedCount" + = statistics."recipientCount" + + + + + AND statistics."receivedCount" > 0 + AND statistics."receivedCount" + < statistics."recipientCount" + + + + + AND statistics."receivedCount" = 0 + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OfficeTaskBookMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OfficeTaskBookMapper.java new file mode 100644 index 000000000..9b8371a8f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OfficeTaskBookMapper.java @@ -0,0 +1,41 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JYSRWS; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 教研室任务书 Mapper + */ +@Mapper +public interface OfficeTaskBookMapper extends BaseMapper { + + /** + * 分页查询列表 + * + * @param page 分页参数 + * @param jysrws 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jysrws") JYSRWS jysrws); + + /** + * 根据教学任务编号查询 + * + * @param jxrwbh 教学任务编号 + * @return 教研室任务书列表 + */ + List selectByJxrwbh(@Param("jxrwbh") String jxrwbh); + + /** + * 根据教研室代号查询 + * + * @param jysdh 教研室代号 + * @return 教研室任务书列表 + */ + List selectByJysdh(@Param("jysdh") String jysdh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OfficeTaskBookMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OfficeTaskBookMapper.xml new file mode 100644 index 000000000..f588e5a46 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OfficeTaskBookMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + 编号, 教学任务编号, 教研室代号, 状态, 上报时间, DEL_FLAG + + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationAnalysisMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationAnalysisMapper.java new file mode 100644 index 000000000..611abb358 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationAnalysisMapper.java @@ -0,0 +1,151 @@ +package com.roomroot.jwgl.mapper; + +import com.roomroot.jwgl.vo.courserunning.CourseCompletionByDeptVO; +import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustTrendVO; +import com.roomroot.jwgl.vo.courserunning.VenueUsageRateVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 运行分析数据访问接口。 + *

+ * 提供教学运行数据统计分析所需的数据访问方法,包括排课完成率、场地使用率、调课频次趋势等。 + *

+ */ +@Mapper +public interface OperationAnalysisMapper { + + /** + * 查询各教学系排课完成率。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 各教学系排课完成率列表 + */ + List selectCourseCompletionByDept( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询场地使用率分布。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 场地使用率分布列表 + */ + List selectVenueUsageRate( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询学期调课频次趋势。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 调课频次趋势列表 + */ + List selectScheduleAdjustTrend( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询整体排课完成率。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 整体排课完成率 + */ + Double selectOverallCourseCompletionRate( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询学员缺勤率。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 学员缺勤率 + */ + Double selectStudentAbsenceRate( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询场地平均使用率。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 场地平均使用率 + */ + Double selectVenueAvgUsageRate( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询教师周均课时。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 教师周均课时 + */ + Double selectAvgWeeklyHoursPerTeacher( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询调课总次数。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 调课总次数 + */ + Integer selectTotalScheduleAdjustCount( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询教学保障计划总数。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 教学保障计划总数 + */ + Integer selectTotalSupportPlanCount( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询学员请假总次数。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 学员请假总次数 + */ + Integer selectTotalLeaveCount( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询待审批调课申请数。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 待审批调课申请数 + */ + Integer selectPendingAdjustCount( + @Param("nd") String nd, + @Param("xqd") String xqd); + + /** + * 查询待审批请假申请数。 + * + * @param nd 年度 + * @param xqd 学期第次 + * @return 待审批请假申请数 + */ + Integer selectPendingLeaveCount( + @Param("nd") String nd, + @Param("xqd") String xqd); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationAnalysisMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationAnalysisMapper.xml new file mode 100644 index 000000000..7a7dbb2e8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationAnalysisMapper.xml @@ -0,0 +1,165 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationLogMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationLogMapper.java new file mode 100644 index 000000000..72ed5b003 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationLogMapper.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.vo.operationlog.OperationLogDetailVO; +import com.roomroot.jwgl.vo.operationlog.OperationLogVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; + +/** + * 操作日志管理 Mapper。 + * + *

统一维护操作日志的分页查询、详情查询和单条写入, + * 不拆分新的业务 Mapper。

+ */ +@Mapper +public interface OperationLogMapper { + + /** + * 分页查询操作日志及其关联的登录、账户信息。 + * + * @param page 分页对象 + * @param startTime 操作开始时间 + * @param endTime 操作结束时间 + * @param operatorKeyword 操作人关键字 + * @param module 业务模块名称 + * @param contentKeyword 操作内容关键字 + * @param urlKeyword 请求地址关键字 + * @return 满足条件的操作日志分页数据 + */ + Page selectOperationLogPage( + Page page, + @Param("startTime") LocalDateTime startTime, + @Param("endTime") LocalDateTime endTime, + @Param("operatorKeyword") String operatorKeyword, + @Param("module") String module, + @Param("contentKeyword") String contentKeyword, + @Param("urlKeyword") String urlKeyword); + + /** + * 根据编号查询操作日志及其关联的登录、账户信息。 + * + * @param id 操作日志编号 + * @return 操作日志详情;日志不存在时返回 null + */ + OperationLogDetailVO selectOperationLogById( + @Param("id") String id); + + /** + * 写入一条操作日志。 + * + * @param id 操作日志编号 + * @param loginRecordId 当前登录记录编号 + * @param operationAt 操作发生时间 + * @param operationContent 操作内容 + * @param url 请求地址 + * @return 影响行数 + */ + int insertOperationLog( + @Param("id") String id, + @Param("loginRecordId") String loginRecordId, + @Param("operationAt") LocalDateTime operationAt, + @Param("operationContent") String operationContent, + @Param("url") String url); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationLogMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationLogMapper.xml new file mode 100644 index 000000000..6c3ab3daa --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/OperationLogMapper.xml @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO "操作日志表" ( + "编号", "登录记录编号", "操作时间", "操作内容", "Url" + ) VALUES ( + #{id}, #{loginRecordId}, #{operationAt}, + #{operationContent}, #{url} + ) + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/QJKTBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/QJKTBMapper.java new file mode 100644 index 000000000..f48ec62f5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/QJKTBMapper.java @@ -0,0 +1,23 @@ +package com.roomroot.jwgl.mapper; + +import com.roomroot.jwgl.entity.QJKTB; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 请假课堂表数据访问接口。 + *

+ * 提供请假关联课次的数据访问方法。 + *

+ */ +@Mapper +public interface QJKTBMapper extends BaseMapper { + + /** + * 根据请假编号删除关联课次记录。 + * + * @param qjbh 请假编号 + */ + void deleteByQjbh(@Param("qjbh") String qjbh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/QJKTBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/QJKTBMapper.xml new file mode 100644 index 000000000..cf391a62a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/QJKTBMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + +编号, 请假编号, 实施_课堂编号 + + + + DELETE FROM 请假课堂表 + WHERE 请假编号 = #{qjbh} + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/QJXYBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/QJXYBMapper.java new file mode 100644 index 000000000..ca7e7861b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/QJXYBMapper.java @@ -0,0 +1,9 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.QJXYB; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface QJXYBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQMapper.java new file mode 100644 index 000000000..88d2b0ee5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQMapper.java @@ -0,0 +1,60 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.SSDKSQ; +import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustApplyVO; +import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustDetailVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 实施_调课申请 Mapper。 + *

+ * 提供调课申请数据的CRUD操作及调课管理相关的自定义查询。 + *

+ */ +@Mapper +public interface SSDKSQMapper extends BaseMapper { + + /** + * 分页查询调课申请列表。 + *

+ * 支持按实施_课程表编号、申请教员编号、年度、教研室审批状态、 + * 教研室查收状态、删除状态和申请日期范围筛选。 + * 关联课程表获取课程名称,关联教员表获取申请教员姓名。 + *

+ * + * @param page MyBatis-Plus 分页对象 + * @param sskcbbh 实施_课程表编号 + * @param sqjybh 申请教员编号 + * @param nd 年度 + * @param jyspzzt 教研室审批状态 + * @param jyscszt 教研室查收状态 + * @param sczt 删除状态 + * @param cjsjStart 申请日期范围开始 + * @param cjsjEnd 申请日期范围结束 + * @return 分页调课申请列表 + */ + Page selectPageList( + Page page, + @Param("sskcbbh") String sskcbbh, + @Param("sqjybh") String sqjybh, + @Param("nd") Integer nd, + @Param("jyspzzt") Integer jyspzzt, + @Param("jyscszt") Integer jyscszt, + @Param("sczt") Integer sczt, + @Param("cjsjStart") String cjsjStart, + @Param("cjsjEnd") String cjsjEnd); + + /** + * 根据调课申请编号查询调课申请详情。 + *

+ * 关联课程表获取课程名称,关联教员表获取申请教员姓名。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @return 调课申请详情 + */ + ScheduleAdjustDetailVO selectDetailById(@Param("ssdksqbh") String ssdksqbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQMapper.xml new file mode 100644 index 000000000..8182e7c52 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQMapper.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQSPMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQSPMapper.java new file mode 100644 index 000000000..5000e557f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQSPMapper.java @@ -0,0 +1,30 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSDKSQSP; +import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustAuditVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 实施_调课申请审批 Mapper。 + *

+ * 提供调课申请审批记录数据的CRUD操作及自定义查询。 + *

+ */ +@Mapper +public interface SSDKSQSPMapper extends BaseMapper { + + /** + * 根据调课申请编号查询审批记录列表。 + *

+ * 关联机构表获取机关名称,关联教员表获取审批人姓名。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @return 审批记录列表 + */ + List selectBySsdksqbh(@Param("ssdksqbh") String ssdksqbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQSPMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQSPMapper.xml new file mode 100644 index 000000000..e17421a9e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSDKSQSPMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGCMMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGCMMapper.java new file mode 100644 index 000000000..6e35034f2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGCMMapper.java @@ -0,0 +1,23 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSJGCM; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface SSJGCMMapper extends BaseMapper { + + SSJGCM selectPersonnelById(@Param("id") String id); + + List selectAllPersonnel(); + + int insertPersonnel(@Param("personnel") SSJGCM personnel); + + int updatePersonnel(@Param("personnel") SSJGCM personnel); + + int updateDepartureStatus(@Param("id") String id, + @Param("departed") Integer departed); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGCMMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGCMMapper.xml new file mode 100644 index 000000000..37c82670e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGCMMapper.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + +编号, 实施_机关编号, 用户姓名, 序号, 离职状态, 职务, 卡号, 拼音, 主官 + + + + + + + + INSERT INTO 实施_机关参谋 (编号, 实施_机关编号, 用户姓名, 序号, 离职状态, 职务, 卡号, 拼音, 主官) + VALUES (#{personnel.bh}, #{personnel.ssjgbh}, #{personnel.yhxm}, #{personnel.xh}, #{personnel.lzzt}, #{personnel.zw}, #{personnel.kh}, #{personnel.py}, #{personnel.zg}) + + + + UPDATE 实施_机关参谋 + SET 实施_机关编号 = #{personnel.ssjgbh}, + 用户姓名 = #{personnel.yhxm}, + 序号 = #{personnel.xh}, + 离职状态 = #{personnel.lzzt}, + 职务 = #{personnel.zw}, + 卡号 = #{personnel.kh}, + 拼音 = #{personnel.py}, + 主官 = #{personnel.zg} + WHERE 编号 = #{personnel.bh} + + + + UPDATE 实施_机关参谋 + SET 离职状态 = #{departed} + WHERE 编号 = #{id} + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGJYSMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGJYSMapper.java new file mode 100644 index 000000000..0b7ca4645 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGJYSMapper.java @@ -0,0 +1,27 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSJGJYS; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface SSJGJYSMapper extends BaseMapper { + + List selectResearchOfficeRelationsByDepartmentId( + @Param("departmentId") String departmentId); + + List selectResearchOfficeRelationsByResearchOfficeId( + @Param("researchOfficeId") String researchOfficeId); + + List selectAllResearchOfficeRelations(); + + SSJGJYS selectResearchOfficeRelationById(@Param("id") String id); + + int insertResearchOfficeRelationIfUnassigned( + @Param("relation") SSJGJYS relation); + + int deleteResearchOfficeRelationById(@Param("id") String id); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGJYSMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGJYSMapper.xml new file mode 100644 index 000000000..5a5ca1c8d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGJYSMapper.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + +编号, 实施_机关编号, 教研室编号 + + + + + + + + + + + + INSERT INTO 实施_机关_教研室 ( + 编号, 实施_机关编号, 教研室编号 + ) + SELECT + #{relation.bH}, #{relation.sSJGBH}, #{relation.jYSBH} + FROM DUAL + WHERE NOT EXISTS ( + SELECT 1 + FROM 实施_机关_教研室 + WHERE 教研室编号 = #{relation.jYSBH} + ) + + + + DELETE FROM 实施_机关_教研室 + WHERE 编号 = #{id} + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGMapper.java new file mode 100644 index 000000000..6adcd06c4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGMapper.java @@ -0,0 +1,41 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSJG; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.util.List; + +@Mapper +public interface SSJGMapper extends BaseMapper { + + SSJG selectDepartmentById(@Param("id") String id); + + List selectAllDepartments(); + + int insertDepartment(@Param("department") SSJG department); + + int updateDepartment(@Param("department") SSJG department); + + int updateDepartmentStatus(@Param("id") String id, + @Param("disabled") Boolean disabled, + @Param("disabledAt") LocalDateTime disabledAt); + + int countSiblingName(@Param("parentId") String parentId, + @Param("name") String name, + @Param("excludeId") String excludeId); + + int countActiveChildren(@Param("id") String id); + + int countDepartmentReferences(@Param("id") String id); + + /** + * 逻辑删除部别:将 DEL_FLAG 置为 1。 + * + * @param id 部别编号 + * @return 受影响行数 + */ + int deleteDepartmentLogically(@Param("id") String id); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGMapper.xml new file mode 100644 index 000000000..59a2b944a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGMapper.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + +编号, 机关名称, 机关类别, 分类号, 分级号, 上级机关编号, 停用, 停用时间, 简称, DEL_FLAG + + + + + + + + INSERT INTO 实施_机关 ( + 编号, 机关名称, 机关类别, 分类号, 分级号, + 上级机关编号, 停用, 停用时间, 简称 + ) VALUES ( + #{department.bH}, #{department.jGMC}, #{department.jGLB}, + #{department.fLH}, #{department.fJH}, #{department.sJJGBH}, + #{department.tY}, #{department.tYSJ}, #{department.jC} + ) + + + + UPDATE 实施_机关 + SET 机关名称 = #{department.jGMC}, + 机关类别 = #{department.jGLB}, + 分类号 = #{department.fLH}, + 分级号 = #{department.fJH}, + 上级机关编号 = #{department.sJJGBH}, + 简称 = #{department.jC} + WHERE 编号 = #{department.bH} + + + + UPDATE 实施_机关 + SET 停用 = #{disabled}, + 停用时间 = #{disabledAt} + WHERE 编号 = #{id} + + + + + + + + + + UPDATE 实施_机关 + SET DEL_FLAG = 1 + WHERE 编号 = #{id} + AND (DEL_FLAG = 0 OR DEL_FLAG IS NULL) + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGXYDMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGXYDMapper.java new file mode 100644 index 000000000..454d83e39 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGXYDMapper.java @@ -0,0 +1,27 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSJGXYD; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface SSJGXYDMapper extends BaseMapper { + + List selectTeachingClassRelationsByDepartmentId( + @Param("departmentId") String departmentId); + + List selectAllTeachingClassRelations(); + + SSJGXYD selectTeachingClassRelationByTeachingClassId( + @Param("teachingClassId") String teachingClassId); + + int insertTeachingClassRelationIfUnassigned( + @Param("relation") SSJGXYD relation); + + int deleteTeachingClassRelationByDepartmentAndTeachingClassId( + @Param("departmentId") String departmentId, + @Param("teachingClassId") String teachingClassId); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGXYDMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGXYDMapper.xml new file mode 100644 index 000000000..55621b8e8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSJGXYDMapper.xml @@ -0,0 +1,50 @@ + + + + + + + + + + +学员队编号, 实施_机关编号 + + + + + + + + + + INSERT INTO 实施_机关_学员队 (学员队编号, 实施_机关编号) + SELECT #{relation.xydbh}, #{relation.ssjgbh} + FROM DUAL + WHERE NOT EXISTS ( + SELECT 1 FROM 实施_机关_学员队 + WHERE 学员队编号 = #{relation.xydbh} + ) + + + + DELETE FROM 实施_机关_学员队 + WHERE 实施_机关编号 = #{departmentId} + AND 学员队编号 = #{teachingClassId} + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBFZJYMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBFZJYMapper.java new file mode 100644 index 000000000..5f9aaded1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBFZJYMapper.java @@ -0,0 +1,16 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSKCBFZJY; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface SSKCBFZJYMapper extends BaseMapper { + + List selectBySskcbh(@Param("sskcbh") String sskcbh); + + int deleteBySskcbbh(@Param("sskcbbh") String sskcbbh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBFZJYMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBFZJYMapper.xml new file mode 100644 index 000000000..82e74a934 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBFZJYMapper.xml @@ -0,0 +1,13 @@ + + + + + + + + DELETE FROM 实施_课程表_辅助教员 WHERE 实施_课程表编号 = #{sskcbbh} + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBJSMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBJSMapper.java new file mode 100644 index 000000000..e3abb07af --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBJSMapper.java @@ -0,0 +1,16 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSKCBJS; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface SSKCBJSMapper extends BaseMapper { + + List selectBySskcbh(@Param("sskcbh") String sskcbh); + + int deleteBySskcbbh(@Param("sskcbbh") String sskcbbh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBJSMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBJSMapper.xml new file mode 100644 index 000000000..da527f7fb --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBJSMapper.xml @@ -0,0 +1,13 @@ + + + + + + + + DELETE FROM 实施_课程表_教室 WHERE 实施_课程表编号 = #{sskcbbh} + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBMapper.java new file mode 100644 index 000000000..1a3114a07 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBMapper.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.SSKCB; +import com.roomroot.jwgl.vo.courserunning.TeachingPlanDetailVO; +import com.roomroot.jwgl.vo.courserunning.TeachingPlanLessonVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 实施_课程表 Mapper。 + *

+ * 提供课次数据的CRUD操作及教学实施计划相关的自定义查询。 + *

+ */ +@Mapper +public interface SSKCBMapper extends BaseMapper { + + /** + * 分页查询课次列表(用于教学实施计划填写)。 + *

+ * 关联实施_课程表和课程表,返回课次基本信息及课程名称。 + * 支持按实施课程编号、年度、教学方法、日期范围、填写状态筛选。 + *

+ * + * @param page MyBatis-Plus 分页对象 + * @param sskcbh 实施_课程编号 + * @param nd 年度 + * @param jxff 教学方法 + * @param rqStart 日期范围开始 + * @param rqEnd 日期范围结束 + * @param txzt 填写状态 + * @return 分页课次列表 + */ + Page selectPageListForPlan( + Page page, + @Param("sskcbh") String sskcbh, + @Param("nd") Integer nd, + @Param("jxff") String jxff, + @Param("rqStart") String rqStart, + @Param("rqEnd") String rqEnd, + @Param("txzt") Integer txzt); + + /** + * 根据课次编号查询教学实施计划详情。 + *

+ * 关联实施_课程表、实施_课程表和课程表,返回课次的完整实施计划信息及课程名称。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + * @return 教学实施计划详情 + */ + TeachingPlanDetailVO selectDetailById(@Param("sskcbbh") String sskcbbh); + + /** + * 根据实施_课程编号查询课次数量。 + *

+ * 用于计算课次序号(第几次课)。 + *

+ * + * @param sskcbh 实施_课程编号 + * @return 课次数量 + */ + Integer countBySskcbh(@Param("sskcbh") String sskcbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBMapper.xml new file mode 100644 index 000000000..753db9cad --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBMapper.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBXYDMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBXYDMapper.java new file mode 100644 index 000000000..e02587afc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBXYDMapper.java @@ -0,0 +1,20 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSKCBXYD; +import com.roomroot.jwgl.vo.elective.ClassScheduleVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface SSKCBXYDMapper extends BaseMapper { + + List selectBySskcbh(@Param("sskcbh") String sskcbh); + + int deleteBySskcbbh(@Param("sskcbbh") String sskcbbh); + + /** 查询班次关联的课程表(联表) */ + List selectScheduleByXydbh(@Param("xydbh") String xydbh, @Param("nd") Integer nd); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBXYDMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBXYDMapper.xml new file mode 100644 index 000000000..d0a85aca2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCBXYDMapper.xml @@ -0,0 +1,41 @@ + + + + + + + + DELETE FROM "实施_课程表_学员队" WHERE "实施_课程表编号" = #{sskcbbh} + + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCB_RZMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCB_RZMapper.java new file mode 100644 index 000000000..2d67bc0c1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCB_RZMapper.java @@ -0,0 +1,21 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.SSKCB_RZ; +import com.roomroot.jwgl.vo.log.TeachingLogListVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 实施_课程表_日志 Mapper + */ +@Mapper +public interface SSKCB_RZMapper extends BaseMapper { + + /** 分页查询教学日志列表(联合实施_课程表、课表等) */ + Page selectTeachingLogList(Page page, @Param("cond") TeachingLogListVO cond); + + /** 查询当前最大编号 */ + Integer selectMaxBh(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCB_RZMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCB_RZMapper.xml new file mode 100644 index 000000000..3f1841194 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCB_RZMapper.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r."编号" AS bh, r."统一编号" AS tybh, r."教员编号" AS jybh, + r."正常状态" AS zczt, r."日志记录" AS rzjl, r."备注" AS bz, + r."创建时间" AS cjsj, r."修改时间" AS xgsj, r."整体评价" AS ztpj, + r."讲授方式" AS jsfs, r."实到人数" AS sdrs, r."基础课时" AS jcks, + r."按天记" AS atj, r."日期" AS rq, r."起始节次" AS qsjc, + r."结束节次" AS jsjc, r."状态" AS zt, r."教研室核准时间" AS jyshzsj, + r."授课课题" AS skkt, r."教学日志创建方式" AS jxrzcjfs, + r."授课地点" AS skdd, r."课程科目编号" AS kckmbh, r."任务类别" AS rwlb, + r."应到人数" AS ydrs, r."培训期班信息" AS pxqbxx, + r."查课领导或专家" AS ckldhzj, + r."授课时间变动" AS sksjbd, r."授课地点变动" AS skddbd, + r."课程变动" AS kcbd, r."授课课题变动" AS skktbd, + r."任务类别变动" AS rwlbbd, r."培训期班变动" AS pxqbbd, + r."授课方式变动" AS skfsbd, r."分组与指导教员变动" AS fzyzdjybd, + r."管理机构编号" AS gljgbh, r."退回意见" AS thyj, + COALESCE(j."教员姓名", '') AS jyxm, + COALESCE(k."课名称", '') AS kcmc, + COALESCE(js."教研室名称", '') AS jysmc + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCMapper.java new file mode 100644 index 000000000..ef7d8a154 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCMapper.java @@ -0,0 +1,18 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSKC; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface SSKCMapper extends BaseMapper { + + List selectBySemester(@Param("nd") Integer nd, @Param("xqdc") Integer xqdc); + + List selectByClassBh(@Param("classBh") String classBh, @Param("nd") Integer nd); + + SSKC selectByCourseBhAndClassBh(@Param("courseBh") String courseBh, @Param("classBh") String classBh, @Param("nd") Integer nd); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCMapper.xml new file mode 100644 index 000000000..307ca4cfa --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCMapper.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCXYDMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCXYDMapper.java new file mode 100644 index 000000000..f97866f6d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCXYDMapper.java @@ -0,0 +1,8 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSKCXYD; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface SSKCXYDMapper extends BaseMapper {} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCXYDMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCXYDMapper.xml new file mode 100644 index 000000000..0a5fc8581 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSKCXYDMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + +编号, 实施_课程编号, 学员队编号, 简称, 人数, 年度, 学期第次, 学分, 学员_课程板块测评编号 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOAppInfoMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOAppInfoMapper.java new file mode 100644 index 000000000..19f6c5006 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOAppInfoMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSOAppInfo; +import org.apache.ibatis.annotations.Mapper; + +/** + * SSO_AppInfo Mapper + */ +@Mapper +public interface SSOAppInfoMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOAppInfoMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOAppInfoMapper.xml new file mode 100644 index 000000000..a7fdf0e6e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOAppInfoMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + +ID, AppKey, AppSecret, Title, Remark, Icon, ReturnUrl, IsEnable, CreateTime + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthOperateMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthOperateMapper.java new file mode 100644 index 000000000..a173b3f2c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthOperateMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSOUserAuthOperate; +import org.apache.ibatis.annotations.Mapper; + +/** + * SSO_UserAuthOperate Mapper + */ +@Mapper +public interface SSOUserAuthOperateMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthOperateMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthOperateMapper.xml new file mode 100644 index 000000000..3b3d59f6a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthOperateMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + +ID, SessionKey, Remark, IpAddress, CreateTime + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthSessionMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthSessionMapper.java new file mode 100644 index 000000000..ed5d27780 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthSessionMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.SSOUserAuthSession; +import org.apache.ibatis.annotations.Mapper; + +/** + * SSO_UserAuthSession Mapper + */ +@Mapper +public interface SSOUserAuthSessionMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthSessionMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthSessionMapper.xml new file mode 100644 index 000000000..8e7dad1e3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SSOUserAuthSessionMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + +SessionKey, AppKey, LoginID, UserID, UserType, IpAddress, InvalidTime, CreateTime + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ScheduleAdjustStrategyMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ScheduleAdjustStrategyMapper.java new file mode 100644 index 000000000..ddca3f581 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ScheduleAdjustStrategyMapper.java @@ -0,0 +1,16 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.ScheduleAdjustStrategy; +import org.apache.ibatis.annotations.Mapper; + +/** + * 实施_调课策略 Mapper。 + *

+ * 提供调课策略的基础 CRUD 能力,时间段限制与免审批策略的匹配查询 + * 由 Service 层调用 BaseMapper 方法完成。 + *

+ */ +@Mapper +public interface ScheduleAdjustStrategyMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterCalendarMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterCalendarMapper.java new file mode 100644 index 000000000..855bb813d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterCalendarMapper.java @@ -0,0 +1,35 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XQXLB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 学期校历表 Mapper + */ +@Mapper +public interface SemesterCalendarMapper extends BaseMapper { + + /** + * 分页查询课程标准列表 + */ + Page selectPageList(Page page, @Param("xqxlb") XQXLB xqxlb); + + XQXLB selectById(@Param("bh") String bh); + + /** + * 根据年度和假期时间范围查询 + * + * @param nd 年度 + * @param jqsjStart 假期时间开始 + * @param jqsjEnd 假期时间结束 + * @return 学期校历列表 + */ + List selectByNdAndJqsjRange(@Param("nd") Integer nd, + @Param("jqsjStart") String jqsjStart, + @Param("jqsjEnd") String jqsjEnd); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterCalendarMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterCalendarMapper.xml new file mode 100644 index 000000000..9c9089d7d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterCalendarMapper.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + 编号, 年度, 假期时间, 假期名称, 加粗, 备注, 可排课, 备注显示, 自动排课, 节次 + + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterMapper.java new file mode 100644 index 000000000..ee60b5f81 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterMapper.java @@ -0,0 +1,23 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XQ; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 学期 Mapper + */ +@Mapper +public interface SemesterMapper extends BaseMapper { + + /** + * 分页查询学期列表 + * + * @param page 分页参数 + * @param xq 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("xq") XQ xq); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterMapper.xml new file mode 100644 index 000000000..68d64bbba --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SemesterMapper.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 年度, 当前学期, 开学日期, 结束日期, 调课不审批, 课时系数方案编号, + 禁止调课, 终结成绩定及格, 教务领导审核调课, 调课不审批_教员, + 调课不审批_时间, 调课不审批_保障, 锁定周数, 调课不审批_场地, + 序号, 锁定计量单位, JSONZD, 课时统计时间 + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamOutputScheduleMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamOutputScheduleMapper.java new file mode 100644 index 000000000..87314a588 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamOutputScheduleMapper.java @@ -0,0 +1,40 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDSCKCB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 学员队输出课程表 Mapper + */ +@Mapper +public interface StudentTeamOutputScheduleMapper extends BaseMapper { + + /** + * 分页查询 + * + * @param page 分页参数 + * @param xydsckcb 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("xydsckcb") XYDSCKCB xydsckcb); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 学员队输出课程表 + */ + XYDSCKCB selectByBh(@Param("bh") String bh); + + /** + * 根据年度和学员队编号查询 + * + * @param nd 年度 + * @param xydbh 学员队编号 + * @return 学员队输出课程表 + */ + XYDSCKCB selectByNdAndXydbh(@Param("nd") Integer nd, @Param("xydbh") String xydbh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamOutputScheduleMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamOutputScheduleMapper.xml new file mode 100644 index 000000000..f318310b1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamOutputScheduleMapper.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 年度, 学员队编号, 课程表, 更新时间, 备注, 页号, 有课表, 课程表模版编号, + 附加备注1, 附加备注2, 运行课程表, 运行备注, 运行课表更新时间, 有运行课表, DEL_FLAG + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamTaskMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamTaskMapper.java new file mode 100644 index 000000000..7604c85dc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamTaskMapper.java @@ -0,0 +1,40 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDRWB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 学员队任务表 Mapper + */ +@Mapper +public interface StudentTeamTaskMapper extends BaseMapper { + + /** + * 分页查询(按序号标识排序) + * + * @param page 分页参数 + * @param xydrwb 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("xydrwb") XYDRWB xydrwb); + + /** + * 分页查询(按计划课次序号排序) + * + * @param page 分页参数 + * @param xydrwb 查询条件 + * @return 分页结果 + */ + Page selectPageListGroup(Page page, @Param("xydrwb") XYDRWB xydrwb); + + /** + * 查询指定年度的最大课次序号 + * + * @param nd 年度 + * @return 最大课次序号 + */ + Integer selectMaxKcxhByNd(@Param("nd") Integer nd); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamTaskMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamTaskMapper.xml new file mode 100644 index 000000000..f658fb333 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/StudentTeamTaskMapper.xml @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 年度, 学员队编号, 课编号, 学期第次, 教员编号, 课次序号, 教室编号, 人数, 周课时, + 学时, 课类型, 简称, 考试地点, 考试编号, 选择, 教研室代号, 教研室计划教员编号, + 教研室计划备注, 计划课次序号, 序号标识, 学员队学期编号, 备注, 学分, 创建时间, + 变动时间, 课表变动时间, 考试课时, 编组, 成绩分制, 不计入学员平均分, 理论学时, + 实践学时, 考试课时不显示, 配档序号, 配档起始周, 配档按周, 配档占正课时间, 配档编组, + 配当同班异课选修编组, 配档自定义学时分布数据, 配档启用自定义学时, DEL_FLAG + + + + + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SystemInitializationMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SystemInitializationMapper.java new file mode 100644 index 000000000..1232a3f30 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SystemInitializationMapper.java @@ -0,0 +1,212 @@ +package com.roomroot.jwgl.mapper; + +import com.roomroot.jwgl.dto.systeminitialization.SemesterInitializationSaveDTO; +import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationConfigurationSaveDTO; +import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationDepartmentImportRowDTO; +import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationPersonnelImportRowDTO; +import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationResearchOfficeImportRowDTO; +import com.roomroot.jwgl.vo.systeminitialization.SemesterInitializationVO; +import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationConfigurationVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 系统初始化管理 Mapper。 + * + *

通过专用映射维护系统版本、学期和历史基础数据, + * 避免依赖原有生成 Mapper 中不稳定的中文字段属性映射。

+ */ +@Mapper +public interface SystemInitializationMapper { + + /** + * 查询系统版本表中当前生效的基础参数。 + * + * @return 当前系统基础参数;系统版本表为空时返回 null + */ + SystemInitializationConfigurationVO + selectCurrentConfiguration(); + + /** + * 系统尚无版本记录时写入首个基础参数版本。 + * + * @param dto 已规范化的配置参数 + * @param versionDate 版本生成时间 + * @return 影响行数 + */ + int insertInitialConfiguration( + @Param("dto") + SystemInitializationConfigurationSaveDTO dto, + @Param("versionDate") LocalDateTime versionDate); + + /** + * 复制当前版本未编辑字段并写入下一条配置版本。 + * + * @param dto 已规范化的配置参数 + * @param versionDate 版本生成时间 + * @return 影响行数 + */ + int insertNextConfigurationVersion( + @Param("dto") + SystemInitializationConfigurationSaveDTO dto, + @Param("versionDate") LocalDateTime versionDate); + + /** + * 查询当前学期。 + * + * @return 当前学期;尚未设置时返回 null + */ + SemesterInitializationVO selectCurrentSemester(); + + /** + * 按年度和序号查询学期。 + * + * @param year 年度 + * @param sequenceNumber 学期序号 + * @return 指定学期;不存在时返回 null + */ + SemesterInitializationVO selectSemesterByKey( + @Param("year") Integer year, + @Param("sequenceNumber") Integer sequenceNumber); + + /** + * 统计与待保存日期范围重叠的其他学期。 + * + * @param dto 待保存学期 + * @return 重叠学期数量 + */ + int countOverlappingSemesters( + @Param("dto") SemesterInitializationSaveDTO dto); + + /** + * 清除目标学期之外的当前学期标志。 + * + * @param year 目标年度 + * @param sequenceNumber 目标学期序号 + * @return 影响行数 + */ + int clearOtherCurrentSemesters( + @Param("year") Integer year, + @Param("sequenceNumber") Integer sequenceNumber); + + /** + * 新增学期。 + * + * @param dto 学期参数 + * @return 影响行数 + */ + int insertSemester( + @Param("dto") SemesterInitializationSaveDTO dto); + + /** + * 修改已有学期。 + * + * @param dto 学期参数 + * @return 影响行数 + */ + int updateSemester( + @Param("dto") SemesterInitializationSaveDTO dto); + + /** + * 查询全部部别编号。 + * + * @return 已有部别编号 + */ + List selectDepartmentIds(); + + /** + * 查询全部教研室编号。 + * + * @return 已有教研室编号 + */ + List selectResearchOfficeIds(); + + /** + * 查询全部部别人员编号。 + * + * @return 已有人员编号 + */ + List selectPersonnelIds(); + + /** + * 批量写入历史部别。 + * + * @param rows 已按父子关系排序的部别 + * @param importedAt 导入时间 + * @return 影响行数 + */ + int insertHistoricalDepartments( + @Param("rows") + List rows, + @Param("importedAt") LocalDateTime importedAt); + + /** + * 批量写入历史教研室。 + * + * @param rows 教研室 + * @param importedAt 导入时间 + * @return 影响行数 + */ + int insertHistoricalResearchOffices( + @Param("rows") + List rows, + @Param("importedAt") LocalDateTime importedAt); + + /** + * 批量写入历史教研室隶属关系。 + * + * @param rows 已生成关系编号的教研室 + * @return 影响行数 + */ + int insertHistoricalResearchOfficeRelations( + @Param("rows") + List rows); + + /** + * 批量写入历史部别人员。 + * + * @param rows 部别人员 + * @return 影响行数 + */ + int insertHistoricalPersonnel( + @Param("rows") + List rows); + + /** + * 验证数据库连接和基础 SQL 执行能力。 + * + * @return 固定值 1 + */ + int selectDatabaseHealth(); + + /** + * 统计部别数量。 + * + * @return 部别数量 + */ + int countDepartments(); + + /** + * 统计教研室数量。 + * + * @return 教研室数量 + */ + int countResearchOffices(); + + /** + * 统计部别人员数量。 + * + * @return 部别人员数量 + */ + int countPersonnel(); + + /** + * 统计基础组织数据中的失效引用。 + * + * @return 失效引用数量 + */ + int countBrokenBaseReferences(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SystemInitializationMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SystemInitializationMapper.xml new file mode 100644 index 000000000..5cef1fe4a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/SystemInitializationMapper.xml @@ -0,0 +1,502 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + currentVersion."ID" AS "versionId", + currentVersion."VersionDate" AS "versionDate", + currentVersion."VersionValue" AS "versionValue", + currentVersion."UniversityName" AS "universityName", + currentVersion."UniversityCode" AS "universityCode", + currentVersion."缺省系统名称" AS "defaultSystemName", + currentVersion."系统模式" AS "systemMode", + currentVersion."学期名称样式" AS "semesterNameStyle", + currentVersion."节次类别" AS "periodCategory", + currentVersion."学员队全名称含年级" + AS "studentTeamFullNameIncludesGrade", + currentVersion."外部文件存储根路径" + AS "externalFileStorageRootPath", + currentVersion."本站文件访问路径" + AS "localFileAccessPath" + + + + + semester."年度" AS "year", + semester."序号" AS "sequenceNumber", + semester."当前学期" AS "currentSemester", + semester."开学日期" AS "startAt", + semester."结束日期" AS "endAt", + semester."调课不审批" AS "adjustmentWithoutApproval", + semester."课时系数方案编号" + AS "hourCoefficientSchemeId", + semester."禁止调课" AS "adjustmentForbidden", + semester."终结成绩定及格" AS "finalGradeAsPass", + semester."教务领导审核调课" + AS "leadershipReviewRequired", + semester."调课不审批_教员" + AS "teacherAdjustmentWithoutApproval", + semester."调课不审批_时间" + AS "timeAdjustmentWithoutApproval", + semester."调课不审批_保障" + AS "supportAdjustmentWithoutApproval", + semester."调课不审批_场地" + AS "venueAdjustmentWithoutApproval", + semester."锁定周数" AS "lockedWeeks", + semester."锁定计量单位" AS "lockMeasurementUnit", + semester."课时统计时间" AS "teachingHourStatisticsAt" + + + + + + + + INSERT INTO "DBVersion" ( + "ID", "VersionDate", "VersionValue", + "UniversityName", "UniversityCode", + "缺省系统名称", "系统模式", "学期名称样式", + "节次类别", "学员队全名称含年级", + "外部文件存储根路径", "本站文件访问路径" + ) + SELECT + ( + SELECT NVL(MAX(historyVersion."ID"), 0) + 1 + FROM "DBVersion" historyVersion + ), + #{versionDate}, + #{dto.versionValue}, + #{dto.universityName}, + #{dto.universityCode}, + #{dto.defaultSystemName}, + #{dto.systemMode}, + #{dto.semesterNameStyle}, + #{dto.periodCategory}, + #{dto.studentTeamFullNameIncludesGrade}, + #{dto.externalFileStorageRootPath}, + #{dto.localFileAccessPath} + FROM DUAL + WHERE NOT EXISTS ( + SELECT 1 + FROM "DBVersion" + ) + + + + + INSERT INTO "DBVersion" ( + "ID", "VersionDate", "VersionValue", "UniversityName", + "学员队全名称含年级", "学期名称样式", "节次类别", + "外部文件存储根路径", "本站文件访问路径", + "登录失败弹窗", "允许密码尝试次数", "用户锁定分钟数", + "允许教研室管理教材", + "使用虚拟教学场地设置班次专用教室", + "使用虚拟教员设置课程责任教员", + "允许多主讲分组教学", "跟查课录入锁定天数", + "UniversityCode", "UniversityValidCode", + "显示78节", "显示晚上", "显示夜间", "教学要点名称", + "教材导出包含图片", + "建立班次学期信息时自动生成教学计划", + "成绩报表显示结论成绩", "缺省系统名称", + "Excel课表按开课日期排序", "Excel课表显示实施教员数", + "系统模式", "课程表标题样式", "课程表副标题样式", + "大屏显示教学内容", "大屏显示前景标题颜色", + "大屏显示前景内容颜色", "大屏显示背景颜色", + "实践课标记", "三实课标记", "增加学时标记", + "减少学时标记", "json字典", "学员正课请假模板文件编号" + ) + SELECT + currentVersion."ID" + 1, + #{versionDate}, + #{dto.versionValue}, + #{dto.universityName}, + #{dto.studentTeamFullNameIncludesGrade}, + #{dto.semesterNameStyle}, + #{dto.periodCategory}, + #{dto.externalFileStorageRootPath}, + #{dto.localFileAccessPath}, + currentVersion."登录失败弹窗", + currentVersion."允许密码尝试次数", + currentVersion."用户锁定分钟数", + currentVersion."允许教研室管理教材", + currentVersion."使用虚拟教学场地设置班次专用教室", + currentVersion."使用虚拟教员设置课程责任教员", + currentVersion."允许多主讲分组教学", + currentVersion."跟查课录入锁定天数", + #{dto.universityCode}, + currentVersion."UniversityValidCode", + currentVersion."显示78节", + currentVersion."显示晚上", + currentVersion."显示夜间", + currentVersion."教学要点名称", + currentVersion."教材导出包含图片", + currentVersion."建立班次学期信息时自动生成教学计划", + currentVersion."成绩报表显示结论成绩", + #{dto.defaultSystemName}, + currentVersion."Excel课表按开课日期排序", + currentVersion."Excel课表显示实施教员数", + #{dto.systemMode}, + currentVersion."课程表标题样式", + currentVersion."课程表副标题样式", + currentVersion."大屏显示教学内容", + currentVersion."大屏显示前景标题颜色", + currentVersion."大屏显示前景内容颜色", + currentVersion."大屏显示背景颜色", + currentVersion."实践课标记", + currentVersion."三实课标记", + currentVersion."增加学时标记", + currentVersion."减少学时标记", + currentVersion."json字典", + currentVersion."学员正课请假模板文件编号" + FROM "DBVersion" currentVersion + WHERE currentVersion."ID" = #{dto.expectedVersionId} + AND currentVersion."ID" = ( + SELECT MAX(historyVersion."ID") + FROM "DBVersion" historyVersion + ) + + + + + + + + + + + + + + UPDATE "学期" + SET "当前学期" = 0 + WHERE COALESCE("当前学期", 0) != 0 + AND NOT ( + "年度" = #{year} + AND "序号" = #{sequenceNumber} + ) + + + + + INSERT INTO "学期" ( + "年度", "当前学期", "开学日期", "结束日期", + "调课不审批", "课时系数方案编号", "禁止调课", + "终结成绩定及格", "教务领导审核调课", + "调课不审批_教员", "调课不审批_时间", + "调课不审批_保障", "锁定周数", "调课不审批_场地", + "序号", "锁定计量单位", "课时统计时间" + ) VALUES ( + #{dto.year}, #{dto.currentSemester}, + #{dto.startAt}, #{dto.endAt}, + #{dto.adjustmentWithoutApproval}, + #{dto.hourCoefficientSchemeId}, + #{dto.adjustmentForbidden}, + #{dto.finalGradeAsPass}, + #{dto.leadershipReviewRequired}, + #{dto.teacherAdjustmentWithoutApproval}, + #{dto.timeAdjustmentWithoutApproval}, + #{dto.supportAdjustmentWithoutApproval}, + #{dto.lockedWeeks}, + #{dto.venueAdjustmentWithoutApproval}, + #{dto.sequenceNumber}, + #{dto.lockMeasurementUnit}, + #{dto.teachingHourStatisticsAt} + ) + + + + + UPDATE "学期" + SET "当前学期" = #{dto.currentSemester}, + "开学日期" = #{dto.startAt}, + "结束日期" = #{dto.endAt}, + "调课不审批" = #{dto.adjustmentWithoutApproval}, + "课时系数方案编号" = #{dto.hourCoefficientSchemeId}, + "禁止调课" = #{dto.adjustmentForbidden}, + "终结成绩定及格" = #{dto.finalGradeAsPass}, + "教务领导审核调课" = #{dto.leadershipReviewRequired}, + "调课不审批_教员" = + #{dto.teacherAdjustmentWithoutApproval}, + "调课不审批_时间" = + #{dto.timeAdjustmentWithoutApproval}, + "调课不审批_保障" = + #{dto.supportAdjustmentWithoutApproval}, + "锁定周数" = #{dto.lockedWeeks}, + "调课不审批_场地" = + #{dto.venueAdjustmentWithoutApproval}, + "锁定计量单位" = #{dto.lockMeasurementUnit}, + "课时统计时间" = #{dto.teachingHourStatisticsAt} + WHERE "年度" = #{dto.year} + AND "序号" = #{dto.sequenceNumber} + + + + + + + + + + + + + + INSERT INTO "实施_机关" ( + "编号", "机关名称", "机关类别", "分类号", "分级号", + "上级机关编号", "停用", "停用时间", "简称" + ) + + SELECT + #{row.id}, #{row.name}, #{row.category}, + #{row.classificationCode}, #{row.levelNumber}, + #{row.parentId}, #{row.disabled}, + + + #{importedAt} + + NULL + , + #{row.abbreviation} + FROM DUAL + + + + + + INSERT INTO "教研室表" ( + "教研室代号", "教研室名称", "备注", "停用", "停用时间", + "序号", "部系", "虚实类型", "简称", "机关性质" + ) + + SELECT + #{row.id}, #{row.name}, #{row.remark}, #{row.disabled}, + + + #{importedAt} + + NULL + , + #{row.sequenceNumber}, #{row.departmentText}, + #{row.virtual}, #{row.abbreviation}, #{row.organNature} + FROM DUAL + + + + + + INSERT INTO "实施_机关_教研室" ( + "编号", "实施_机关编号", "教研室编号" + ) + + SELECT + #{row.relationId}, #{row.departmentId}, #{row.id} + FROM DUAL + + + + + + INSERT INTO "实施_机关参谋" ( + "编号", "实施_机关编号", "用户姓名", "序号", "离职状态", + "职务", "卡号", "拼音", "主官" + ) + + SELECT + #{row.id}, #{row.departmentId}, #{row.name}, + #{row.sequenceNumber}, + + + 1 + + 0 + , + #{row.position}, #{row.cardNumber}, #{row.pinyin}, + + + 1 + + 0 + + FROM DUAL + + + + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYDLBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYDLBMapper.java new file mode 100644 index 000000000..389319fc5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYDLBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.TCKGLYDLB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 听查课管理员登录表 Mapper + */ +@Mapper +public interface TCKGLYDLBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYDLBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYDLBMapper.xml new file mode 100644 index 000000000..a32812931 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYDLBMapper.xml @@ -0,0 +1,14 @@ + + + + + + + + + + +听查课管理员编号, 登录信息编号 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYMapper.java new file mode 100644 index 000000000..dd9b9fc65 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.TCKGLY; +import org.apache.ibatis.annotations.Mapper; + +/** + * 听查课管理员 Mapper + */ +@Mapper +public interface TCKGLYMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYMapper.xml new file mode 100644 index 000000000..131c100b4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TCKGLYMapper.xml @@ -0,0 +1,14 @@ + + + + + + + + + + +编号, 姓名 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGBMapper.java new file mode 100644 index 000000000..568d68d94 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.TGB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 通告表 Mapper + */ +@Mapper +public interface TGBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGBMapper.xml new file mode 100644 index 000000000..027f5acb4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGBMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + +编号, 标题, 内容, 发布时间, 创建时间, 优先级, 阅读次数, 状态, 类别, 备注, 全体 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGYHXXMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGYHXXMapper.java new file mode 100644 index 000000000..4eb6b9b5b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGYHXXMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.TGYHXX; +import org.apache.ibatis.annotations.Mapper; + +/** + * 通告用户信息 Mapper + */ +@Mapper +public interface TGYHXXMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGYHXXMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGYHXXMapper.xml new file mode 100644 index 000000000..1736544d4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TGYHXXMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + +编号, 通告编号, 用户编号, 已接收, 接收时间, 已阅读, 阅读时间 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeacherHoursMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeacherHoursMapper.java new file mode 100644 index 000000000..aba05bdca --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeacherHoursMapper.java @@ -0,0 +1,25 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XQJYXXB; +import com.roomroot.jwgl.vo.log.TeacherHoursVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教员课时查询 Mapper + *

学期教员信息表 JOIN 教员表 JOIN 教研室表,按条件分页查询教员课时信息。

+ */ +@Mapper +public interface TeacherHoursMapper extends BaseMapper { + + /** + * 分页查询教员课时信息 + * + * @param page 分页参数 + * @param cond 查询条件(含列表展示字段与课时量范围条件) + * @return 分页结果 + */ + Page selectTeacherHoursPage(Page page, @Param("cond") TeacherHoursVO cond); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeacherHoursMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeacherHoursMapper.xml new file mode 100644 index 000000000..867e42fdc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeacherHoursMapper.xml @@ -0,0 +1,101 @@ + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingMaterialMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingMaterialMapper.java new file mode 100644 index 000000000..ebfdd8e0d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingMaterialMapper.java @@ -0,0 +1,30 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JCXX; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教材信息 Mapper + */ +@Mapper +public interface TeachingMaterialMapper extends BaseMapper { + + /** + * 分页查询教材信息列表 + * + * @param page 分页参数 + * @param jcxx 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jcxx") JCXX jcxx); + + /** + * 获取最大编号 + * + * @return 最大编号 + */ + String selectMaxBh(); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingMaterialMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingMaterialMapper.xml new file mode 100644 index 000000000..50ac256b7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingMaterialMapper.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 名称, ISBN, 出版社, 版次, 开本, 作者, 定价, 教材分类, 教材类型, + 教材编写类型, 教材重点类型, 备注, 停用, 停用日期, 计量单位, 出版日期, + 创建时间, 修改时间, 教材来源, 教材层次, 责任教员及联系方式, 文件编号, + 封面图片文件编号, 出版方式, 教材代码, 编写单位, 教学管理机构编号, + 教研室代号, 版权图片文件编号, 自编教材编审状态, 评选情况, json字典, + 自编立项日期, 教材使用情况, 教材密级, 颁发审定单位, 需彩印, + 封面PSD文件编号, 封面PDF文件编号, 内容PDF文件编号, 内容WORD文件编号, + 锁定数量, 借出数量, 库存数量, 库存位置, 默认领用类型, ID, DEL_FLAG + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingPlanMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingPlanMapper.java new file mode 100644 index 000000000..ceda1d996 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingPlanMapper.java @@ -0,0 +1,23 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JXSSJH; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 导入数据表-教学实施计划 Mapper + */ +@Mapper +public interface TeachingPlanMapper extends BaseMapper { + + /** + * 分页查询列表 + * + * @param page 分页参数 + * @param drsjbjxssjh 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("drsjbjxssjh") JXSSJH drsjbjxssjh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingPlanMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingPlanMapper.xml new file mode 100644 index 000000000..d977d8378 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingPlanMapper.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标识号, 序号, 年度, 原序号, 日期, 原节次, 节次, 课程名称, 班次, + 责任单位, 授课教员, 教学场地, 教学内容, 教学方法, 教员备注, + 教务备注, 检查结果, 已通过检查, 已被忽略, 已被导入, 已被处理, + 课程表编号, 登录编号, DEL_FLAG + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockDetailMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockDetailMapper.java new file mode 100644 index 000000000..a71f81730 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockDetailMapper.java @@ -0,0 +1,40 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JCKCMXX; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 教材库存明细项 Mapper + */ +@Mapper +public interface TeachingStockDetailMapper extends BaseMapper { + + /** + * 分页查询教材库存明细项列表 + * + * @param page 分页参数 + * @param jckcmxx 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jckcmxx") JCKCMXX jckcmxx); + + /** + * 获取最大编号 + * + * @return 最大编号 + */ + String selectMaxBh(); + + /** + * 根据教材库存单编号查询 + * + * @param jckcdbbh 教材库存单编号 + * @return 教材库存明细项列表 + */ + List selectByJckcdbbh(@Param("jckcdbbh") String jckcdbbh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockDetailMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockDetailMapper.xml new file mode 100644 index 000000000..1497033bb --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockDetailMapper.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + ID, 编号, 教材库存单编号, 教材信息编号, 数量, 前置库存明细项编号, + 还回完毕, 还回完毕时间, 备注, JSONZD, + 快照_事务后库存数量, 快照_事务后借出数量, + 快照_事务前库存数量, 快照_事务前借出数量, DEL_FLAG + + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockMapper.java new file mode 100644 index 000000000..fafa8b667 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockMapper.java @@ -0,0 +1,30 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JCKCD; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教材库存单 Mapper + */ +@Mapper +public interface TeachingStockMapper extends BaseMapper { + + /** + * 分页查询教材库存单列表 + * + * @param page 分页参数 + * @param jckcd 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jckcd") JCKCD jckcd); + + /** + * 获取最大编号 + * + * @return 最大编号 + */ + String selectMaxBh(); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockMapper.xml new file mode 100644 index 000000000..7fb6ca874 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingStockMapper.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ID, 编号, 名称, 情况说明, 类别, 字别, 承办部门编号, 承办人编号, + 保管员编号, 审批人编号, 创建时间, 修改时间, 提交时间, 审批时间, + 执行时间, 结束时间, 状态, 备注, JSONZD, 退回意见, 退回时间, DEL_FLAG + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingTaskMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingTaskMapper.java new file mode 100644 index 000000000..373cfa1d4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingTaskMapper.java @@ -0,0 +1,23 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JXRW; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 教学任务 Mapper + */ +@Mapper +public interface TeachingTaskMapper extends BaseMapper { + + /** + * 分页查询列表 + * + * @param page 分页参数 + * @param jxrw 查询条件 + * @return 分页结果 + */ + Page selectPageList(Page page, @Param("jxrw") JXRW jxrw); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingTaskMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingTaskMapper.xml new file mode 100644 index 000000000..1d358ebf3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/TeachingTaskMapper.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + 编号, 任务名称, 年度, 状态, 发布时间, 创建时间, 结束时间, 教材需求生成时间, DEL_FLAG + + + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WJMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WJMapper.java new file mode 100644 index 000000000..dfacbda61 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WJMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.WJ; +import org.apache.ibatis.annotations.Mapper; + +/** + * 文件 Mapper + */ +@Mapper +public interface WJMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WJMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WJMapper.xml new file mode 100644 index 000000000..fe7566e0f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WJMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + +编号, 文件数据, 文件名称, 文件后缀, MIME类型, MD5, Size, 上传时间, 外部存储, 外部存储文件名称, 子目录 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSRYZCBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSRYZCBMapper.java new file mode 100644 index 000000000..2c2385826 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSRYZCBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.WSRYZCB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 文书_人员组成表 Mapper + */ +@Mapper +public interface WSRYZCBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSRYZCBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSRYZCBMapper.xml new file mode 100644 index 000000000..a071d8669 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSRYZCBMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + +编号, 序号, 主事, 人员编号, 职责, 备注, 文书_事项编号, 排名 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSSXBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSSXBMapper.java new file mode 100644 index 000000000..d658478fa --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSSXBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.WSSXB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 文书_事项表 Mapper + */ +@Mapper +public interface WSSXBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSSXBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSSXBMapper.xml new file mode 100644 index 000000000..ba8d98424 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSSXBMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + +编号, 统一编号, 名称, 事项类别, 创建时间, 修改时间, 关键字, 摘要, 备注, json字典, 日志 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSWDBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSWDBMapper.java new file mode 100644 index 000000000..d29199ce1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSWDBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.WSWDB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 文书_文档表 Mapper + */ +@Mapper +public interface WSWDBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSWDBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSWDBMapper.xml new file mode 100644 index 000000000..1f1873bd4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/WSWDBMapper.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + +编号, 序号, 分类目录, 名称, 文件编号, 关键字, 摘要, 备注, 文书_事项编号 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XKZYXXMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XKZYXXMapper.java new file mode 100644 index 000000000..c1423ecec --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XKZYXXMapper.java @@ -0,0 +1,39 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XKZYXX; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 学科专业信息 Mapper + *

+ * 提供学科专业数据的CRUD操作。 + * 基础CRUD继承 MyBatis-Plus BaseMapper, + * 分页条件查询通过 XML 映射文件实现。 + * 查询均使用停用=0(启用状态)过滤已删除数据。 + *

+ */ +@Mapper +public interface XKZYXXMapper extends BaseMapper { + + + + /** + * 分页查询学科专业列表(支持条件筛选) + *

+ * 有参数则按条件过滤,无参数则查询全部。 + * 使用 MyBatis-Plus 分页插件自动拼接分页SQL, + * 查询结果自动注入 page 对象的 records 和 total 属性。 + * 仅查询停用=0(启用状态)的数据。 + *

+ * + * @param page MyBatis-Plus 分页对象 + * - name: 专业/学科名称(模糊匹配) + * - direction: 方向名称(模糊匹配) + * @return 分页对象 + */ + + Page selectPageList(Page page, @Param("xkzyxx") XKZYXX xkzyxx); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XKZYXXMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XKZYXXMapper.xml new file mode 100644 index 000000000..68617ba7f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XKZYXXMapper.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 标识号, 序号, 专业代码, 专业名称, 专业方向, 停用, 停用时间, 学年制, 学期数, + 培训层次, 培训类型, 教学管理机构编号, 备注, 培训类型2, 学员类别, 自定义分类, + JSONZD, 主干专业, 规范名称 + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XQMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XQMapper.java new file mode 100644 index 000000000..94fef10ac --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XQMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.XQ; +import org.apache.ibatis.annotations.Mapper; + +/** + * 学期 Mapper + */ +@Mapper +public interface XQMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XQMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XQMapper.xml new file mode 100644 index 000000000..2f53d7f0a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XQMapper.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + +年度, 当前学期, 开学日期, 结束日期, 调课不审批, 课时系数方案编号, 禁止调课, 终结成绩定及格, 教务领导审核调课, 调课不审批_教员, 调课不审批_时间, 调课不审批_保障, 锁定周数, 调课不审批_场地, 序号, 锁定计量单位, json字典, 课时统计时间 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYCPCPXMBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYCPCPXMBMapper.java new file mode 100644 index 000000000..d2c830d2a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYCPCPXMBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.XYCPCPXMB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 学员测评_测评项模板 Mapper + */ +@Mapper +public interface XYCPCPXMBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYCPCPXMBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYCPCPXMBMapper.xml new file mode 100644 index 000000000..e45b90447 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYCPCPXMBMapper.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + +编号, 名称, 课程板块测评_分值权重标准编号, 课程板块测评_测评结果样式编号, 学员评教员_分值权重标准编号, 学员评教员_测评结果样式编号, 创建时间, 停用状态, 备注, 停用时间, 评课程通告内容, 评授课教员通告内容, json字典 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDBMapper.java new file mode 100644 index 000000000..a2cb8d2ff --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDBMapper.java @@ -0,0 +1,28 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDB; +import com.roomroot.jwgl.vo.elective.ElectiveClassVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +@Mapper +public interface XYDBMapper extends BaseMapper { + + Page selectPageList(Page page, @Param("cond") XYDB cond); + + /** + * 双表联查分页(学员队表 LEFT JOIN 学员队期表) + */ + Page selectPageListWithPeriod(Page page, @Param("cond") ElectiveClassVO cond); + + /** + * 根据日期范围查询学员队(字符串格式) + * + * @param startTime 开始时间(字符串格式,如:2026-04-01 00:00) + * @param endTime 结束时间(字符串格式,如:2026-07-01 00:00) + * @return 学员队列表 + */ + java.util.List selectByDateRange(@Param("startTime") String startTime, @Param("endTime") String endTime); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDBMapper.xml new file mode 100644 index 000000000..b381fbe6e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDBMapper.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + "学员队编号", "学员队名称", "学员队人数", "年级", "专业代号", + "专业教室编号", "备注", "在校状态", "序号", "任务类别", + "虚实类型", "入学日期", "毕业日期", "学员队类型", "培训任务标识号", + "节次类别", "系统模式", "JSONZD", "简称", "所属单位", "主体培训任务" + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDQBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDQBMapper.java new file mode 100644 index 000000000..c55827d4f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDQBMapper.java @@ -0,0 +1,9 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.XYDQB; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface XYDQBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDQBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDQBMapper.xml new file mode 100644 index 000000000..1bf6fadf3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYDQBMapper.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + +编号, 名称, 学员队编号, 学号前缀, 学号位数, 入学日期, 毕业日期, 在校状态, 允许注册, 结束注册时间, 备注 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYJXQJBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYJXQJBMapper.java new file mode 100644 index 000000000..ebd3f61e1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYJXQJBMapper.java @@ -0,0 +1,72 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYJXQJB; +import com.roomroot.jwgl.vo.courserunning.StudentLeaveApplyVO; +import com.roomroot.jwgl.vo.courserunning.StudentLeaveDetailVO; +import com.roomroot.jwgl.vo.elective.StudentAbsenceVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 学员教学请假表Mapper + */ +@Mapper +public interface XYJXQJBMapper extends BaseMapper { + + /** + * 根据学员队编号和年度查询请假列表 + * + * @param xydbh 学员队编号 + * @param nd 年度 + * @return 请假列表 + */ + List selectByXydbh(@Param("xydbh") String xydbh, @Param("nd") Integer nd); + + /** + * 根据学员队编号和年度查询缺勤学员列表 + * + * @param xydbh 学员队编号 + * @param nd 年度 + * @return 缺勤学员列表 + */ + List selectAbsenceByXydbh(@Param("xydbh") String xydbh, @Param("nd") Integer nd); + + /** + * 分页查询学员请假申请列表 + * + * @param page 分页对象 + * @param qjbh 请假编号 + * @param xybh 学员编号 + * @param xyxm 学员姓名 + * @param xydbh 学员队编号 + * @param spzt 审批状态 + * @param nd 年度 + * @param cjsjStart 创建时间开始 + * @param cjsjEnd 创建时间结束 + * @return 分页结果 + */ + Page selectPageList( + Page page, + @Param("qjbh") String qjbh, + @Param("xybh") String xybh, + @Param("xyxm") String xyxm, + @Param("xydbh") String xydbh, + @Param("qjlx") String qjlx, + @Param("spzt") Integer spzt, + @Param("nd") Integer nd, + @Param("cjsjStart") String cjsjStart, + @Param("cjsjEnd") String cjsjEnd + ); + + /** + * 查询请假申请详情(含关联课次和审批信息) + * + * @param qjbh 请假编号 + * @return 详情VO + */ + StudentLeaveDetailVO selectDetailById(@Param("qjbh") String qjbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYJXQJBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYJXQJBMapper.xml new file mode 100644 index 000000000..2ff00805f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYJXQJBMapper.xml @@ -0,0 +1,223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCCJMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCCJMapper.java new file mode 100644 index 000000000..8c71102c8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCCJMapper.java @@ -0,0 +1,18 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.XYKCCJ; +import com.roomroot.jwgl.vo.elective.StudentGradeVO; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface XYKCCJMapper extends BaseMapper { + + List selectByStudentId(@Param("xybh") String xybh); + + /** 按学员队查询成绩(含学员姓名学号) */ + List selectGradeByXydbh(@Param("xydbh") String xydbh, @Param("kmbh") String kmbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCCJMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCCJMapper.xml new file mode 100644 index 000000000..251a21cbd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCCJMapper.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 课程成绩编号, 学员编号, 考试成绩, 平时成绩, 最终成绩, 备注, + 补考成绩, 补考次数, 考试情况, 原始考试成绩, 原始平时成绩, 期望学分, + 年度, 课类型, 科目编号, 学员队编号, 已通过 + + + + + + + + + + + + + + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCGCCJMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCGCCJMapper.java new file mode 100644 index 000000000..9517fe756 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCGCCJMapper.java @@ -0,0 +1,14 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.XYKCGCCJ; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +@Mapper +public interface XYKCGCCJMapper extends BaseMapper { + + List selectByStudentId(@Param("xybh") String xybh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCGCCJMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCGCCJMapper.xml new file mode 100644 index 000000000..9d2092da6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYKCGCCJMapper.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 课程过程成绩编号, 学员编号, 原始成绩, 最终成绩, 备注, + 补考成绩1, 补考成绩2, 补考成绩3, 补考次数, 考试情况, + 年度, 课类型, 科目编号, 学员队编号, + 补考原始成绩1, 补考原始成绩2, 补考原始成绩3 + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYMYDCPXMMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYMYDCPXMMapper.java new file mode 100644 index 000000000..51c3742df --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYMYDCPXMMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.XYMYDCPXM; +import org.apache.ibatis.annotations.Mapper; + +/** + * 学员满意度测评项目 Mapper + */ +@Mapper +public interface XYMYDCPXMMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYMYDCPXMMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYMYDCPXMMapper.xml new file mode 100644 index 000000000..29d5793fc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYMYDCPXMMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + +编号, 名称, 创建时间, 状态, 备注, 结束时间, 测评结果样式编号 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYQJSPBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYQJSPBMapper.java new file mode 100644 index 000000000..0e2a44086 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYQJSPBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.XYQJSPB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 学员请假审批表Mapper + */ +@Mapper +public interface XYQJSPBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYQJSPBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYQJSPBMapper.xml new file mode 100644 index 000000000..2ec58a2aa --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYQJSPBMapper.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + +编号, 请假编号, 发回意见, 机关编号, 审批时间, 审批状态, 审批人编号 + + + \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXJYJTJBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXJYJTJBMapper.java new file mode 100644 index 000000000..5ccadafe7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXJYJTJBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.XYXJYJTJB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 学员学籍预警条件表 Mapper + */ +@Mapper +public interface XYXJYJTJBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXJYJTJBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXJYJTJBMapper.xml new file mode 100644 index 000000000..04eb0c865 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXJYJTJBMapper.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +编号, 名称, 培训类型, 培训层次, 当前学期考试不及格门数上限, 当前学期考试不及格门数下限, 当前学期考查不及格门数上限, 当前学期考查不及格门数下限, 当前学期不及格门数上限, 当前学期不及格门数下限, 全部考试不及格门数上限, 全部考试不及格门数下限, 全部考查不及格门数上限, 全部考查不及格门数下限, 全部不及格门数上限, 全部不及格门数下限, 停用, 版本, 修改时间 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXXMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXXMapper.java new file mode 100644 index 000000000..5150ba6b6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXXMapper.java @@ -0,0 +1,19 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYXX; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 学员信息表 Mapper + */ +@Mapper +public interface XYXXMapper extends BaseMapper { + + /** + * 分页查询学员信息(支持条件筛选) + */ + Page selectPageList(Page page, @Param("xyxx") XYXX xyxx); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXXMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXXMapper.xml new file mode 100644 index 000000000..139cf22fa --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/XYXXMapper.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 编号, 学号, 姓名, 登录信息编号, 学员队期编号, 证件号码, 政治面貌, 党团时间, + 入伍年月日, 性别, 出生日期, 名族, 籍贯, 原部职别, 邮政编码, 文化程度, 学位, + 学员类别, 军衔文职, 所属军区, 骨干任职, 本科毕业院校, 国防生, 考生号, 备注, + 身份证号, 退学状态, 留级状态, 分班状态, 论文编号, 论文题目, 论文摘要, 拼音, + 成绩报告单编号, 允许查看成绩, json字典, 当前班次编号, 论文答辩文档编号, 标签, + 联系电话, 通信地址, 单位联络人信息, 奖励处分情况, 已注册, 军人证件类型, 曾用名, + 入伍地, 出生地, 生源地 + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YCBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YCBMapper.java new file mode 100644 index 000000000..ec99a117e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YCBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.YCB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 异常表 Mapper + */ +@Mapper +public interface YCBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YCBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YCBMapper.xml new file mode 100644 index 000000000..cc834bb86 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YCBMapper.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + +编号, URL, 异常类型, 内容, 创建时间, 用户ID, 用户姓名, 用户类别 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YJFKBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YJFKBMapper.java new file mode 100644 index 000000000..7553ffdb5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YJFKBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.YJFKB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 意见反馈表 Mapper + */ +@Mapper +public interface YJFKBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YJFKBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YJFKBMapper.xml new file mode 100644 index 000000000..ddb1b5ce8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/YJFKBMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + +编号, 类别, 主题, 内容, 登录信息表编号, 创建时间 + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ZYBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ZYBMapper.java new file mode 100644 index 000000000..2c09d78f3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ZYBMapper.java @@ -0,0 +1,32 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.ZYB; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +/** + * 专业表 Mapper + *

+ * 提供专业/培养方案数据的CRUD操作。 + * 基础CRUD继承 MyBatis-Plus BaseMapper, + * 分页条件查询通过 XML 映射文件实现。 + *

+ */ +@Mapper +public interface ZYBMapper extends BaseMapper { + + /** + * 分页查询专业表列表(支持条件筛选) + *

+ * 有参数则按条件过滤,无参数则查询全部。 + * 使用 MyBatis-Plus 分页插件自动拼接分页SQL。 + *

+ * + * @param page MyBatis-Plus 分页对象 + * @param zyb 查询条件 + * @return 分页对象 + */ + Page selectPageList(Page page, @Param("zyb") ZYB zyb); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ZYBMapper.xml b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ZYBMapper.xml new file mode 100644 index 000000000..8ad3b6970 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ZYBMapper.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 专业代号, 专业名称, 专业方向, 学年制, 学期数, 专业备注, 专业规范, 专业代码, 专业版本, + 停用, 启用时间, 停用时间, 教学管理机构编号, 培训层次, 培训类型, 学科专业信息标识号, + 培训类型2, 学员类别, 自定义分类, 专业标识号, 序号标识, 简称, 节次类别, 系统模式, + JSONZD, 主干专业, 规范名称, 教学大纲编号, 人培编号, 培养目标 + + + + + + diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ZYJXJHBMapper.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ZYJXJHBMapper.java new file mode 100644 index 000000000..2b2cb0c6a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/mapper/ZYJXJHBMapper.java @@ -0,0 +1,12 @@ +package com.roomroot.jwgl.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.jwgl.entity.ZYJXJHB; +import org.apache.ibatis.annotations.Mapper; + +/** + * 专业教学计划表 Mapper + */ +@Mapper +public interface ZYJXJHBMapper extends BaseMapper { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/AccountManagementService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/AccountManagementService.java new file mode 100644 index 000000000..32991bfed --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/AccountManagementService.java @@ -0,0 +1,184 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.accountmanagement.AccountCreateDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountDisableDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountIdDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountLoginDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountLoginInformationQueryDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountMergeDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountPasswordChangeDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountPasswordResetDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountQueryDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountRoleConfigurationDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountRoleOptionQueryDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountSsoSessionResolveDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountUpdateDTO; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.accountmanagement.AccountMergeResultVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountLoginCaptchaVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountLoginInformationVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountLoginVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountRoleOptionVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountRoleTypeVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountRoleVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountSsoSessionVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountVO; + +import java.util.List; + +/** + * 账户管理服务。 + * + *

账户资料、启停、密码重置、角色配置和账户合并均集中在当前服务中, + * 避免同一业务功能被拆分为多个相互依赖的业务类。

+ */ +public interface AccountManagementService { + + /** + * 查询账户列表。 + * + * @param dto 账户查询条件 + * @return 经过筛选和稳定排序的账户分页结果 + */ + PageResult list(AccountQueryDTO dto); + + /** + * 查询账户详情。 + * + * @param dto 账户编号参数 + * @return 包含角色和数据范围的账户详情 + */ + AccountVO get(AccountIdDTO dto); + + /** + * 新增账户并配置初始角色。 + * + * @param dto 账户新增参数 + * @return 新增后的账户详情 + */ + AccountVO add(AccountCreateDTO dto); + + /** + * 修改账户基本信息。 + * + * @param dto 账户修改参数 + * @return 修改后的账户详情 + */ + AccountVO update(AccountUpdateDTO dto); + + /** + * 停用账户。 + * + * @param dto 账户停用参数 + * @return 停用后的账户详情 + */ + AccountVO disable(AccountDisableDTO dto); + + /** + * 启用账户。 + * + * @param dto 账户编号参数 + * @return 启用后的账户详情 + */ + AccountVO enable(AccountIdDTO dto); + + /** + * 重置账户密码。 + * + * @param dto 密码重置参数 + * @return 密码重置后的账户详情 + */ + AccountVO resetPassword(AccountPasswordResetDTO dto); + + /** + * 当前登录账户修改自己的密码。 + * + * @param dto 原密码、新密码和确认密码 + * @return 密码修改后的脱敏账户详情 + */ + AccountVO changePassword(AccountPasswordChangeDTO dto); + + /** + * 查询当前登录账户的登录信息和成功登录历史。 + * + * @param dto 登录历史分页参数 + * @return 当前账户的登录统计和成功登录历史 + */ + AccountLoginInformationVO getLoginInformation( + AccountLoginInformationQueryDTO dto); + + /** + * 生成本地账户登录使用的一次性图形验证码。 + * + * @return 验证码编号、PNG 图片数据和有效秒数 + */ + AccountLoginCaptchaVO generateLoginCaptcha(); + + /** + * 使用本地登录账号和密码建立账户管理登录会话。 + * + * @param dto 本地登录参数 + * @return 脱敏后的本地账户登录结果 + */ + AccountLoginVO login(AccountLoginDTO dto); + + /** + * 校验统一身份认证会话并解析唯一的本地账户。 + * + *

由于现有资料没有明确 LoginID 和 UserID 中哪一个固定对应本地统一账号, + * 当前业务会同时把两者作为候选,并拒绝候选值指向不同本地账户的场景。 + * 校验成功后同时建立本项目的 Servlet Session 和在线用户记录。

+ * + * @param dto SSO 会话解析参数 + * @return SSO 会话及其对应的本地账户信息 + */ + AccountSsoSessionVO resolveSsoSession( + AccountSsoSessionResolveDTO dto); + + /** + * 退出当前 HTTP 会话。 + * + *

退出接口不接收业务参数,也不返回业务数据,因此不新增 DTO 或 VO。

+ */ + void logout(); + + /** + * 查询系统支持的全部角色类型。 + * + * @return 按页面展示顺序排列的角色类型列表 + */ + List listRoleTypes(); + + /** + * 查询指定角色类型下可供配置的身份。 + * + * @param dto 角色身份查询条件 + * @return 经过数据库筛选、去重和稳定排序的角色身份分页结果 + */ + PageResult listRoleOptions( + AccountRoleOptionQueryDTO dto); + + /** + * 查询账户已经绑定的角色和数据范围。 + * + * @param dto 账户编号参数 + * @return 账户角色列表 + */ + List listRoles(AccountIdDTO dto); + + /** + * 整体替换账户角色和数据范围。 + * + * @param dto 账户角色配置参数 + * @return 配置完成后的账户详情 + */ + AccountVO configureRoles(AccountRoleConfigurationDTO dto); + + /** + * 将源账户角色迁移到目标账户并停用源账户。 + * + * @param dto 账户合并参数 + * @return 源账户和目标账户的最新信息 + */ + AccountMergeResultVO merge(AccountMergeDTO dto); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/AffiliationSettingService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/AffiliationSettingService.java new file mode 100644 index 000000000..36bac9a2f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/AffiliationSettingService.java @@ -0,0 +1,157 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.affiliationsetting.PersonnelAffiliationDTO; +import com.roomroot.jwgl.dto.affiliationsetting.PersonnelTransferDTO; +import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationAddDTO; +import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationIdDTO; +import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeAffiliationQueryDTO; +import com.roomroot.jwgl.dto.affiliationsetting.ResearchOfficeOptionQueryDTO; +import com.roomroot.jwgl.dto.affiliationsetting.TeachingClassAffiliationDTO; +import com.roomroot.jwgl.vo.affiliationsetting.AffiliationDepartmentVO; +import com.roomroot.jwgl.vo.affiliationsetting.PersonnelAffiliationVO; +import com.roomroot.jwgl.vo.affiliationsetting.ResearchOfficeAffiliationVO; +import com.roomroot.jwgl.vo.affiliationsetting.ResearchOfficeVO; +import com.roomroot.jwgl.vo.affiliationsetting.TeachingClassAffiliationVO; +import com.roomroot.jwgl.vo.affiliationsetting.TransferHistoryVO; + +import java.util.List; + +/** + * 隶属关系设置统一服务。 + */ +public interface AffiliationSettingService { + + /** + * 查询可供隶属关系页面选择的启用部别。 + * + * @param dto 查询条件 + * @return 可选部别列表 + */ + List listResearchOfficeDepartments( + ResearchOfficeOptionQueryDTO dto); + + /** + * 查询指定部别已经关联的教研室。 + * + * @param dto 部别编号及查询条件 + * @return 已建立的教研室隶属关系列表 + */ + List listResearchOfficeAffiliations( + ResearchOfficeAffiliationQueryDTO dto); + + /** + * 查询尚未分配且处于启用状态的教研室。 + * + * @param dto 查询条件 + * @return 可添加的教研室列表 + */ + List listAvailableResearchOffices(ResearchOfficeOptionQueryDTO dto); + + /** + * 新增教研室隶属关系。 + * + * @param dto 部别编号和教研室编号 + * @return 新增后的隶属关系 + */ + ResearchOfficeAffiliationVO addResearchOfficeAffiliation( + ResearchOfficeAffiliationAddDTO dto); + + /** + * 解除教研室隶属关系。 + * + * @param dto 隶属关系编号 + * @return 被解除的隶属关系 + */ + ResearchOfficeAffiliationVO removeResearchOfficeAffiliation( + ResearchOfficeAffiliationIdDTO dto); + + /** + * 查询可供教学班次隶属关系页面选择的启用部别。 + * + * @param dto 查询条件 + * @return 可选部别列表 + */ + List listTeachingClassDepartments( + TeachingClassAffiliationDTO dto); + + /** + * 查询指定部别已经关联的教学班次。 + * + * @param dto 部别编号及查询条件 + * @return 已建立的教学班次隶属关系列表 + */ + List listTeachingClassAffiliations( + TeachingClassAffiliationDTO dto); + + /** + * 查询尚未分配给任何部别的教学班次。 + * + * @param dto 查询条件 + * @return 可添加的教学班次列表 + */ + List listAvailableTeachingClasses( + TeachingClassAffiliationDTO dto); + + /** + * 新增教学班次隶属关系。 + * + * @param dto 部别编号和教学班次编号 + * @return 新增后的隶属关系 + */ + TeachingClassAffiliationVO addTeachingClassAffiliation( + TeachingClassAffiliationDTO dto); + + /** + * 解除教学班次隶属关系。 + * + * @param dto 部别编号和教学班次编号 + * @return 被解除的隶属关系 + */ + TeachingClassAffiliationVO removeTeachingClassAffiliation( + TeachingClassAffiliationDTO dto); + + // ============ 人员隶属关系统一接口(方向1新增)============ + + /** + * 查询人员当前隶属关系。 + *

按人员类型+部门编号筛选,返回当前归属该部门的人员列表。

+ * + * @param dto 查询条件(personnelType必填,departmentId可选筛选) + * @return 人员隶属关系列表 + */ + List listPersonnelAffiliations(PersonnelAffiliationDTO dto); + + /** + * 分配人员到部门/组织单元(首次归属,不写异动历史)。 + * + * @param dto 分配参数(personnelType+personnelId+departmentId必填) + * @return 分配后的人员隶属关系 + */ + PersonnelAffiliationVO assignPersonnel(PersonnelAffiliationDTO dto); + + /** + * 解除人员隶属关系(清空当前归属,不写异动历史)。 + * + * @param dto 解除参数(personnelType+personnelId必填) + * @return 解除后的人员隶属关系 + */ + PersonnelAffiliationVO unassignPersonnel(PersonnelAffiliationDTO dto); + + /** + * 人员异动调整(核心接口)。 + *

更新当前归属 + 记录旧归属到previousDept + 追加异动历史JSON + + * 校验新教研室/学员队在SSJGJYS/SSJGXYD中已有部门关系。

+ * + * @param dto 异动参数(含reason必填) + * @return 异动后的人员隶属关系 + */ + PersonnelAffiliationVO transferPersonnel(PersonnelTransferDTO dto); + + /** + * 查询人员异动历史。 + * + * @param dto 查询参数(personnelType+personnelId必填) + * @return 异动历史VO(含JSON原文) + */ + TransferHistoryVO getTransferHistory(PersonnelAffiliationDTO dto); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/BZLBService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/BZLBService.java new file mode 100644 index 000000000..61c12c205 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/BZLBService.java @@ -0,0 +1,58 @@ +package com.roomroot.jwgl.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.BZLB; + +import java.util.List; + +/** + * 保障类别 Service + */ +public interface BZLBService { + + /** + * 新增 + * + * @param bzlb 保障类别 + */ + void add(BZLB bzlb); + + /** + * 删除 + * + * @param bh 编号 + */ + void delete(String bh); + + /** + * 更新 + * + * @param bzlb 保障类别 + */ + void update(BZLB bzlb); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 保障类别 + */ + BZLB getByBh(String bh); + + /** + * 分页查询 + * + * @param pageNum 页码 + * @param pageSize 每页数量 + * @param bzlb 查询条件 + * @return 分页结果 + */ + Page list(int pageNum, int pageSize, BZLB bzlb); + + /** + * 查询所有 + * + * @return 所有保障类别 + */ + List all(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/BuildService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/BuildService.java new file mode 100644 index 000000000..906ad8fa1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/BuildService.java @@ -0,0 +1,84 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JXLB; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.web.multipart.MultipartFile; + + +import java.util.List; + +/** + * 教学楼表服务接口 + */ +public interface BuildService { + + /** + * 新增教学楼 + * + * @param jxlb 教学楼 + */ + void add(JXLB jxlb); + + /** + * 删除教学楼 + * + * @param id 主键 + */ + void delete(String id); + + /** + * 更新教学楼 + * + * @param jxlb 教学楼 + */ + void update(JXLB jxlb); + + /** + * 根据教学楼代号查询 + * + * @param id 唯一标识 + * @return 教学楼 + */ + JXLB getById(String id); + + /** + * 分页查询教学楼列表 + * + * @param query 分页参数 + * @param jxlb 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, JXLB jxlb); + + /** + * 查询所有教学楼列表 + * + * @param jxlb 查询条件 + * @return 教学楼列表 + */ + List list(JXLB jxlb); + + /** + * 导入Excel文件 + * + * @param file Excel文件 + * @return 导入数量 + */ + int importExcel(MultipartFile file); + + /** + * 下载模板文件 + * + * @param response HTTP响应 + */ + void downloadTemplate(HttpServletResponse response); + + /** + * 生成下一个序号/教学楼代号 + * + * @return 格式化后的序号 + */ + String generateNextXh(); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassRoomCalendarService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassRoomCalendarService.java new file mode 100644 index 000000000..9500682bf --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassRoomCalendarService.java @@ -0,0 +1,75 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JXCDL; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.List; + +/** + * 教学场地历服务接口 + */ +public interface ClassRoomCalendarService { + + /** + * 新增教学场地历 + * + * @param jxcdl 教学场地历 + */ + void add(JXCDL jxcdl); + + /** + * 删除教学场地历 + * + * @param id 主键ID + */ + void delete(String id); + + /** + * 更新教学场地历 + * + * @param jxcdl 教学场地历 + */ + void update(JXCDL jxcdl); + + /** + * 根据ID查询 + * + * @param id 主键ID + * @return 教学场地历 + */ + JXCDL getById(String id); + + /** + * 分页查询教学场地历列表 + * + * @param query 分页参数 + * @param jxcdl 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, JXCDL jxcdl); + + /** + * 查询所有教学场地历列表 + * + * @param jxcdl 查询条件 + * @return 教学场地历列表 + */ + List list(JXCDL jxcdl); + + /** + * 根据教室编号查询教学场地历列表 + * + * @param jsbh 教室编号 + * @return 教学场地历列表 + */ + List listByJsbh(String jsbh); + + /** + * 根据日期查询教学场地历列表 + * + * @param rq 日期 + * @return 教学场地历列表 + */ + List listByRq(String rq); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassRoomLessonService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassRoomLessonService.java new file mode 100644 index 000000000..7a6b235a2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassRoomLessonService.java @@ -0,0 +1,68 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JSBKCB; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.List; + +/** + * 教室输出课程表服务接口 + */ +public interface ClassRoomLessonService { + + /** + * 新增教室输出课程表 + * + * @param jsbkcb 教室输出课程表 + */ + void add(JSBKCB jsbkcb); + + /** + * 删除教室输出课程表 + * + * @param id 主键ID + */ + void delete(String id); + + /** + * 更新教室输出课程表 + * + * @param jsbkcb 教室输出课程表 + */ + void update(JSBKCB jsbkcb); + + /** + * 根据ID查询 + * + * @param id 主键ID + * @return 教室输出课程表 + */ + JSBKCB getById(String id); + + /** + * 根据教室编号和年度查询 + * + * @param jsbh 教室编号 + * @param nd 年度 + * @return 教室输出课程表 + */ + JSBKCB getByJsbhAndNd(String jsbh, Integer nd); + + /** + * 分页查询教室输出课程表列表 + * + * @param query 分页参数 + * @param jsbkcb 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, JSBKCB jsbkcb); + + /** + * 查询所有教室输出课程表列表 + * + * @param jsbkcb 查询条件 + * @return 教室输出课程表列表 + */ + List list(JSBKCB jsbkcb); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassRoomService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassRoomService.java new file mode 100644 index 000000000..032206ba5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassRoomService.java @@ -0,0 +1,67 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JSB; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.List; + +/** + * 教室表服务接口 + */ +public interface ClassRoomService { + + /** + * 新增教室 + * + * @param jsb 教室 + */ + void add(JSB jsb); + + /** + * 删除教室 + * + * @param id 唯一标识符 + */ + void delete(String id); + + /** + * 更新教室 + * + * @param jsb 教室 + */ + void update(JSB jsb); + + /** + * 根据教室编号查询 + * + * @param id 教室编号 + * @return 教室 + */ + JSB getById(String id); + + /** + * 分页查询教室列表 + * + * @param query 分页参数 + * @param jsb 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, JSB jsb); + + /** + * 查询所有教室列表 + * + * @param jsb 查询条件 + * @return 教室列表 + */ + List list(JSB jsb); + + /** + * 根据教学楼代号查询教室列表 + * + * @param jxldh 教学楼代号 + * @return 教室列表 + */ + List listByJxldh(String jxldh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassSemesterCalendarService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassSemesterCalendarService.java new file mode 100644 index 000000000..e007841eb --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassSemesterCalendarService.java @@ -0,0 +1,52 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.XYDRWB; + +import java.util.List; +import java.util.Map; + +/** + * 学员队任务表服务接口 + */ +public interface ClassSemesterCalendarService { + + /** + * 更新 + * + * @param xydrwb 实体 + */ + void update(XYDRWB xydrwb); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 实体 + */ + XYDRWB getByBh(String bh); + + /** + * 条目批次复制 + * + * @param sourceBhList 源学员队任务表编号列表 + * @param targetBhList 目标学员队期表编号列表 + * @return 包含count(复制数量)、failedSourceBhs(不存在的源编号)、failedTargetBhs(不存在的目标编号) + */ + Map batchCopy(List sourceBhList, List targetBhList); + + /** + * 按学期批次复制 + * + * @param sourceXydxqbh 源学员队期表编号 + * @param targetBhList 目标学员队期表编号列表 + * @return 包含count(复制数量)、failedTargetBhs(不存在的目标编号) + */ + Map batchCopyBySemester(String sourceXydxqbh, List targetBhList); + + /** + * 查询所有有效数据(DEL_FLAG = 0) + * + * @return 所有有效数据列表 + */ + List listAllValid(XYDRWB xydrwb); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassSemesterService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassSemesterService.java new file mode 100644 index 000000000..f37a735e5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ClassSemesterService.java @@ -0,0 +1,116 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.XYDNDXQJBXXB; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.List; + +/** + * 班次学期服务接口 + */ +public interface ClassSemesterService { + + /** + * 新增 + * + * @param xydndxqjbxxb 实体 + */ + void add(XYDNDXQJBXXB xydndxqjbxxb); + + /** + * 删除 + * + * @param bh 编号 + */ + void delete(String bh); + + /** + * 批量删除 + * + * @param bhList 编号列表 + */ + void batchDelete(List bhList); + + /** + * 更新 + * + * @param xydndxqjbxxb 实体 + */ + void update(XYDNDXQJBXXB xydndxqjbxxb); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 实体 + */ + XYDNDXQJBXXB getByBh(String bh); + + /** + * 分页查询列表 + * + * @param query 分页参数 + * @param xydndxqjbxxb 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, XYDNDXQJBXXB xydndxqjbxxb); + + /** + * 查询所有有效数据(DEL_FLAG = 0) + * + * @return 所有有效数据列表 + */ + List listAllValid(); + + /** + * 根据选修班次列表批量添加班次学期 + * + * @param startTime 时间范围开始 + * @param endTime 时间范围结束 + * @param nd 年度 + * @param xqdc 学期第次 + * @param xqkssj 学期开始时间 + * @param xqjssj 学期结束时间 + * @return 添加数量 + */ + int batchAddFromElective(java.time.LocalDateTime startTime, java.time.LocalDateTime endTime, + String nd, String xqdc, String xqkssj, String xqjssj); + + /** + * 批量修改开放教员排课状态 + * + * @param bhList 编号列表 + * @param kfjypk 开放教员排课(0-否,1-是) + * @return 更新数量 + */ + int batchUpdateKfjypk(List bhList, Integer kfjypk); + + /** + * 批量修改学期第次 + * + * @param bhList 编号列表 + * @param xqdc 学期第次 + * @return 更新数量 + */ + int batchUpdateXqdc(List bhList, Integer xqdc); + + /** + * 批量修改教学任务编号 + * + * @param bhList 编号列表 + * @param jxrwbh 教学任务编号 + * @return 更新数量 + */ + int batchUpdateJxrwbh(List bhList, String jxrwbh); + + /** + * 批量修改班次学期的日期范围,并同步更新日历记录 + * + * @param bhList 编号列表 + * @param xqkssj 学期开始时间 + * @param xqjssj 学期结束时间 + * @return 更新数量 + */ + int batchUpdateDateRange(List bhList, String xqkssj, String xqjssj); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/CourseRunningImportService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/CourseRunningImportService.java new file mode 100644 index 000000000..177918c8c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/CourseRunningImportService.java @@ -0,0 +1,548 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.courserunning.ClassesImportDTO; +import com.roomroot.jwgl.dto.courserunning.ImportRemoveDTO; +import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustApplyDTO; +import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustAuditDTO; +import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustQueryDTO; +import com.roomroot.jwgl.dto.courserunning.SemesterImportDTO; +import com.roomroot.jwgl.dto.courserunning.SingleCourseImportDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingPlanBatchFillDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingPlanFillDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingPlanQueryDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingSupportBatchPlanDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingSupportConfirmDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingSupportPlanDTO; +import com.roomroot.jwgl.dto.courserunning.TeachingSupportUpdateDTO; +import com.roomroot.jwgl.dto.courserunning.StudentLeaveApplyDTO; +import com.roomroot.jwgl.dto.courserunning.StudentLeaveAuditDTO; +import com.roomroot.jwgl.dto.courserunning.StudentLeaveQueryDTO; +import com.roomroot.jwgl.dto.courserunning.OperationAnalysisQueryDTO; +import com.roomroot.jwgl.dto.courserunning.ScheduleAdjustStrategyDTO; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.courserunning.CourseRunningImportResultVO; +import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustApplyVO; +import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustDetailVO; +import com.roomroot.jwgl.vo.courserunning.TeachingPlanDetailVO; +import com.roomroot.jwgl.vo.courserunning.TeachingPlanFillResultVO; +import com.roomroot.jwgl.vo.courserunning.TeachingPlanLessonVO; +import com.roomroot.jwgl.vo.courserunning.TeachingSupportConfirmResultVO; +import com.roomroot.jwgl.vo.courserunning.TeachingSupportHistoryVO; +import com.roomroot.jwgl.vo.courserunning.TeachingSupportPlanVO; +import com.roomroot.jwgl.vo.courserunning.TeachingSupportResourceVO; +import com.roomroot.jwgl.vo.courserunning.StudentLeaveApplyVO; +import com.roomroot.jwgl.vo.courserunning.StudentLeaveDetailVO; +import com.roomroot.jwgl.vo.courserunning.OperationOverviewVO; +import com.roomroot.jwgl.vo.courserunning.CourseCompletionByDeptVO; +import com.roomroot.jwgl.vo.courserunning.VenueUsageRateVO; +import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustTrendVO; +import com.roomroot.jwgl.vo.courserunning.ScheduleAdjustStrategyVO; + +import java.util.List; + +/** + * 课程教学运行管理服务接口。 + *

+ * 提供课程教学运行管理的全部业务能力,包括:
+ * 1. 教学运行数据导入:从课程教学计划编制子系统向课程教学运行管理子系统导入数据;
+ * 2. 教学实施计划填写:对已导入的课次进行教学实施计划内容的填写、查询和提交。 + *

+ */ +public interface CourseRunningImportService { + + // ==================== 教学运行数据导入 ==================== + + /** + * 按学期导入课程。 + *

+ * 从课程教学计划编制子系统导入指定年度学期的所有课程安排数据。 + *

+ * + * @param params 学期导入参数 + * @return 导入结果 + */ + CourseRunningImportResultVO importBySemester(SemesterImportDTO params); + + /** + * 按教学班次导入课程。 + *

+ * 从课程教学计划编制子系统导入指定教学班次的课程安排数据。 + *

+ * + * @param params 教学班次导入参数 + * @return 导入结果 + */ + CourseRunningImportResultVO importByClasses(ClassesImportDTO params); + + /** + * 导入单门课程。 + *

+ * 从课程教学计划编制子系统导入指定教学班次的单门课程安排数据。 + *

+ * + * @param params 单门课程导入参数 + * @return 导入结果 + */ + CourseRunningImportResultVO importSingleCourse(SingleCourseImportDTO params); + + /** + * 删除已导入课程。 + *

+ * 删除已导入到课程教学运行管理子系统的课次数据,支持按学期、班次或单个课次删除。 + *

+ * + * @param params 删除参数 + */ + void removeImportedCourses(ImportRemoveDTO params); + + // ==================== 教学实施计划填写 ==================== + + /** + * 分页查询待填写或已填写的课次列表。 + *

+ * 根据查询条件(实施课程编号、年度、教学方法、日期范围、填写状态等) + * 分页查询课次列表,返回课次基本信息及填写状态。 + *

+/** + * 分页查询待填写或已填写的课次列表。 + * + * @param query 查询参数(包含分页信息和筛选条件) + * @return 分页课次列表 + */ + PageResult queryLessonList(TeachingPlanQueryDTO query); + + /** + * 按查询条件导出课次数据为 Excel 文件。 + * + * @param query 查询参数(包含筛选条件) + * @return Excel 文件字节数组 + */ + byte[] exportLessonListToExcel(TeachingPlanQueryDTO query); + + /** + * 查询单个课次的教学实施计划详情。 + *

+ * 根据课次编号查询该课次的完整教学实施计划信息,包括教学内容、教学要点、 + * 教学方法、日期节次、班组信息、保障需求及教员备注等。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + * @return 教学实施计划详情 + */ + TeachingPlanDetailVO getTeachingPlanDetail(String sskcbbh); + + /** + * 单条填写教学实施计划。 + *

+ * 对单个课次填写教学实施计划内容,保存后填写状态更新为"已填写"。 + *

+ * + * @param dto 教学实施计划填写参数 + * @return 填写结果 + */ + TeachingPlanFillResultVO fillTeachingPlan(TeachingPlanFillDTO dto); + + /** + * 批量填写教学实施计划。 + *

+ * 一次性提交多个课次的教学实施计划填写数据,逐条处理并返回整体执行结果。 + *

+ * + * @param dto 批量教学实施计划填写参数 + * @return 批量填写结果 + */ + TeachingPlanFillResultVO batchFillTeachingPlan(TeachingPlanBatchFillDTO dto); + + /** + * 提交教学实施计划。 + *

+ * 将已填写的教学实施计划提交审核,提交后填写状态更新为"已提交", + * 已提交的实施计划原则上不允许再修改,如需修改需先撤回。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + */ + void submitTeachingPlan(String sskcbbh); + + // ==================== 调课管理 ==================== + + /** + * 分页查询调课申请列表。 + *

+ * 根据查询条件(实施_课程表编号、申请教员、年度、审批状态、查收状态、 + * 删除状态、申请日期范围等)分页查询调课申请列表。 + *

+ * + * @param query 查询参数(包含分页信息和筛选条件) + * @return 分页调课申请列表 + */ + PageResult queryScheduleAdjustList(ScheduleAdjustQueryDTO query); + + /** + * 查询单个调课申请的完整详情。 + *

+ * 根据调课申请编号查询该申请的完整信息,包括申请信息、调整后的内容、 + * 教研室审批状态、机关审批记录、关联的教室、辅助教员、学员队、保障明细等。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @return 调课申请详情 + */ + ScheduleAdjustDetailVO getScheduleAdjustDetail(String ssdksqbh); + + /** + * 提交调课申请。 + *

+ * 教员提交调课申请,包含课次编号、调课事由、新的日期节次、新的教室、 + * 辅助教员、学员队、保障明细等完整信息。 + * 同时更新课次数据,将课次的日期节次替换为调整后的新值。 + *

+ * + * @param dto 调课申请参数 + * @return 调课申请编号 + */ + String submitScheduleAdjust(ScheduleAdjustApplyDTO dto); + + /** + * 教研室审批调课申请。 + *

+ * 教研室审批教员提交的调课申请,包含同意、发回、拒绝三种处理方式。 + * 同意时同步更新课次数据,发回时记录发回原因,拒绝时标记申请为已拒绝。 + *

+ * + * @param dto 教研室审批参数 + */ + void auditByTeachingOffice(ScheduleAdjustAuditDTO dto); + + /** + * 教研室查收调课申请。 + *

+ * 教研室查收调课申请,标记查收状态和查收时间。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @param csrbh 查收人编号 + */ + void receiveByTeachingOffice(String ssdksqbh, String csrbh); + + /** + * 教学系审批调课申请。 + *

+ * 教学系审批教研室已同意的调课申请,包含同意、发回、拒绝三种处理方式。 + * 教学系同意后流转到机关审批。 + *

+ * + * @param dto 教学系审批参数 + */ + void auditByTeachingDepartment(ScheduleAdjustAuditDTO dto); + + /** + * 机关审批调课申请。 + *

+ * 机关审批教研室已同意的调课申请,包含同意、发回、拒绝三种处理方式。 + * 机关全部同意后调课申请最终生效,课次按调整后的内容执行。 + *

+ * + * @param dto 机关审批参数 + */ + void auditByAuthority(ScheduleAdjustAuditDTO dto); + + /** + * 机关查收调课申请。 + *

+ * 机关查收调课申请,标记查收状态和查收时间。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @param jgbh 机关编号 + * @param csrbh 查收人编号 + */ + void receiveByAuthority(String ssdksqbh, String jgbh, String csrbh); + + /** + * 撤销调课申请。 + *

+ * 教员撤销自己提交的、未经过审批的调课申请。 + * 软删除调课申请,标记删除状态为1。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @param sqjybh 申请教员编号(用于权限校验) + */ + void cancelScheduleAdjust(String ssdksqbh, String sqjybh); + + // ==================== 调课策略管理 ==================== + + /** + * 查询调课策略列表。 + *

+ * 支持按策略类型筛选,返回所有策略(含停用)。 + *

+ * + * @param cllx 策略类型:TIME_LIMIT/AUTO_APPROVE,为空时查询全部 + * @return 调课策略列表 + */ + List listScheduleAdjustStrategies(String cllx); + + /** + * 保存调课策略(新增或修改)。 + *

+ * 策略编号为空时执行新增,非空时执行修改。 + *

+ * + * @param dto 策略参数 + * @return 策略编号 + */ + String saveScheduleAdjustStrategy(ScheduleAdjustStrategyDTO dto); + + /** + * 删除调课策略。 + * + * @param clbh 策略编号 + */ + void deleteScheduleAdjustStrategy(String clbh); + + // ==================== 教学保障管理 ==================== + + /** + * 查询教学保障提供资源列表。 + *

+ * 从保障提供明细表中查询可供选择的保障资源,支持按保障类别编号筛选。 + *

+ * + * @param bzlbbh 保障类别编号,为空时查询全部 + * @return 保障提供资源列表 + */ + List querySupportResourceList(String bzlbbh); + + /** + * 查询课次的教学保障计划列表。 + *

+ * 根据课次编号查询该课次已登记的教学保障计划明细,包括保障名称、单位、 + * 数量、备注、确认状态及确认信息等。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + * @return 教学保障计划列表 + */ + List queryTeachingSupportPlanList(String sskcbbh); + + /** + * 登记单条教学保障计划。 + *

+ * 为指定课次登记一条教学保障需求,指定保障提供明细、数量、备注等信息。 + *

+ * + * @param dto 教学保障计划登记参数 + * @return 课程表_保障明细编号 + */ + String saveTeachingSupportPlan(TeachingSupportPlanDTO dto); + + /** + * 批量登记教学保障计划。 + *

+ * 一次性为指定课次登记多条教学保障需求。 + *

+ * + * @param dto 教学保障计划批量登记参数 + */ + void batchSaveTeachingSupportPlan(TeachingSupportBatchPlanDTO dto); + + /** + * 修改教学保障计划。 + *

+ * 修改已登记的教学保障计划,包括调整数量和备注信息。 + * 已确认的教学保障计划不允许修改,如需修改需先取消确认。 + *

+ * + * @param dto 教学保障计划修改参数 + */ + void updateTeachingSupportPlan(TeachingSupportUpdateDTO dto); + + /** + * 删除教学保障计划。 + *

+ * 删除已登记的教学保障计划。 + * 已确认的教学保障计划不允许删除,如需删除需先取消确认。 + *

+ * + * @param kcbbzmxbh 课程表_保障明细编号 + */ + void deleteTeachingSupportPlan(String kcbbzmxbh); + + /** + * 确认教学保障到位。 + *

+ * 确认某条教学保障计划已经落实到位,记录确认人、确认时间和确认备注。 + * 已确认的保障计划不允许重复确认。 + *

+ * + * @param dto 教学保障到位确认参数 + */ + void confirmTeachingSupport(TeachingSupportConfirmDTO dto); + + /** + * 批量确认教学保障到位。 + *

+ * 一次性确认多条教学保障计划已经落实到位,逐条处理并返回整体执行结果。 + *

+ * + * @param dtoList 教学保障到位确认参数列表 + * @return 批量确认结果 + */ + TeachingSupportConfirmResultVO batchConfirmTeachingSupport(List dtoList); + + /** + * 取消教学保障到位确认。 + *

+ * 取消某条教学保障计划的到位确认,恢复为未确认状态。 + *

+ * + * @param kcbbzmxbh 课程表_保障明细编号 + */ + void cancelConfirmTeachingSupport(String kcbbzmxbh); + + /** + * 查询教学保障历史联想列表。 + *

+ * 根据当前课次编号,查询同一实施课程下其他历史课次已登记过的保障物资, + * 按保障提供明细编号聚合去重,统计历史使用次数、最近登记时间和平均数量, + * 供教师在登记新保障时快速联想选择,避免重复录入。 + *

+ * + * @param sskcbbh 当前课次编号(实施_课程表编号) + * @return 历史联想保障物资列表 + */ + List listTeachingSupportHistory(String sskcbbh); + + // ==================== 学员正课请假 ==================== + + /** + * 分页查询学员请假申请列表。 + *

+ * 根据查询条件分页查询学员正课请假申请记录,支持按请假编号、学员编号、 + * 学员姓名、学员队编号、审批状态、年度、创建时间范围等多条件筛选。 + *

+ * + * @param query 查询参数(包含分页信息和筛选条件) + * @return 分页学员请假申请列表 + */ + PageResult queryStudentLeaveList(StudentLeaveQueryDTO query); + + /** + * 查询学员请假申请详情。 + *

+ * 根据请假编号查询该申请的完整信息,包括学员信息、请假时间范围、 + * 审批状态、审批记录及关联课次列表。 + *

+ * + * @param qjbh 请假编号 + * @return 学员请假申请详情 + */ + StudentLeaveDetailVO getStudentLeaveDetail(String qjbh); + + /** + * 学员提交请假申请。 + *

+ * 学员在线提交正课请假申请,包含请假原因、时间范围、请假类型、 + * 关联课次等信息。系统自动生成请假编号并记录申请时间。 + *

+ * + * @param dto 学员请假申请参数 + * @return 请假编号 + */ + String submitStudentLeave(StudentLeaveApplyDTO dto); + + /** + * 教师审批学员请假申请。 + *

+ * 教师审批学员提交的正课请假申请,包含同意、拒绝、发回修改三种处理方式。 + * 审批通过后联动更新考勤记录。 + *

+ * + * @param dto 教师审批参数 + */ + void auditStudentLeave(StudentLeaveAuditDTO dto); + + /** + * 学员撤回请假申请。 + *

+ * 学员撤回自己提交的、未经过审批的请假申请。 + *

+ * + * @param qjbh 请假编号 + * @param xybh 学员编号(用于权限校验) + */ + void cancelStudentLeave(String qjbh, String xybh); + + /** + * 学员查询自己的请假申请列表。 + *

+ * 学员自助查询接口,只能查看自己提交的请假记录,支持分页和按审批状态筛选。 + * 返回结果中请假类型已翻译为字典标签。 + *

+ * + * @param query 查询参数(学员编号xybh必填,分页信息和审批状态可选) + * @return 分页学员请假申请列表 + */ + PageResult queryStudentOwnLeaveList(StudentLeaveQueryDTO query); + + // ==================== 运行分析 ==================== + + /** + * 获取教学运行概览指标。 + *

+ * 返回教学运行关键指标的汇总数据,包括整体排课完成率、学员缺勤率、 + * 场地平均使用率、教师周均课时等核心指标。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 运行概览指标 + */ + OperationOverviewVO getOperationOverview(OperationAnalysisQueryDTO query); + + /** + * 获取各教学系排课完成率对比数据。 + *

+ * 返回各教学系的排课完成率,用于柱状图展示。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 各教学系排课完成率列表 + */ + List getCourseCompletionByDept(OperationAnalysisQueryDTO query); + + /** + * 获取场地使用率分布数据。 + *

+ * 返回不同场地类型的使用率分布情况,用于饼图展示。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 场地使用率分布列表 + */ + List getVenueUsageRate(OperationAnalysisQueryDTO query); + + /** + * 获取学期调课频次趋势数据。 + *

+ * 返回学期内各时间段的调课频次变化趋势,用于折线图展示。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 调课频次趋势列表 + */ + List getScheduleAdjustTrend(OperationAnalysisQueryDTO query); + + /** + * 导出运行分析报表为 Excel 文件。 + *

+ * 汇总运行概览、各教学系排课完成率、场地使用率分布、调课频次趋势四类数据, + * 分别写入同一 Excel 文件的多个工作表,便于教学管理决策存档与汇报。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return Excel 文件字节数组 + */ + byte[] exportAnalysisToExcel(OperationAnalysisQueryDTO query); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/CourseTeachingService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/CourseTeachingService.java new file mode 100644 index 000000000..2ce976591 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/CourseTeachingService.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.KCBZ; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.List; + +/** + * 课程教学管理服务接口 + *

+ * 提供课程标准的增加、删除(填写撤回原因)、更新、查询(含课堂列表)及多版本管理。 + *

+ */ +public interface CourseTeachingService { + + /** + * 新增课程标准(含课堂列表) + * + * @param kcbz 课程标准(ktList 附带课堂列表) + */ + void add(KCBZ kcbz); + + /** + * 撤回(删除)课程标准——填写撤回原因 + * + * @param bh 课程标准编号 + * @param chyy 撤回原因 + */ + void delete(String bh, String chyy); + + /** + * 更新课程标准(先删后插课堂列表) + * + * @param kcbz 课程标准(ktList 附带课堂列表) + */ + void update(KCBZ kcbz); + + /** + * 查询课程标准详情(含课堂列表) + * + * @param bh 编号 + * @return 课程标准(内嵌 ktList) + */ + KCBZ getById(String bh); + + /** + * 分页查询课程标准(含课堂列表) + * + * @param query 分页参数 + * @param kcbz 查询条件 + * @return 分页结果(每项内嵌 ktList) + */ + PageResult pageList(PageQuery query, KCBZ kcbz); + + /** + * 查询某课程的所有版本 + * + * @param kbh 课编号 + * @return 版本列表 + */ + List getVersionsByCourse(String kbh); + + /** + * 从课程表自动生成课程标准 + * + * @param kbh 课编号 + * @return 课程标准编号 + */ + String generateFromCourse(String kbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DepartmentManagementService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DepartmentManagementService.java new file mode 100644 index 000000000..75ba53c96 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DepartmentManagementService.java @@ -0,0 +1,82 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.department.DepartmentCreateDTO; +import com.roomroot.jwgl.dto.department.DepartmentIdDTO; +import com.roomroot.jwgl.dto.department.DepartmentQueryDTO; +import com.roomroot.jwgl.dto.department.DepartmentUpdateDTO; +import com.roomroot.jwgl.vo.department.DepartmentTreeVO; +import com.roomroot.jwgl.vo.department.DepartmentVO; + +import java.util.List; + +/** + * 部别管理服务。 + */ +public interface DepartmentManagementService { + + /** + * 新增部别。 + * + * @param dto 新增参数 + * @return 新增后的部别信息 + */ + DepartmentVO add(DepartmentCreateDTO dto); + + /** + * 修改部别。 + * + * @param dto 修改参数 + * @return 修改后的部别信息 + */ + DepartmentVO update(DepartmentUpdateDTO dto); + + /** + * 查询单个部别。 + * + * @param dto 部别编号参数 + * @return 部别详情 + */ + DepartmentVO get(DepartmentIdDTO dto); + + /** + * 查询部别平铺列表。 + * + * @param dto 查询条件 + * @return 部别列表 + */ + List list(DepartmentQueryDTO dto); + + /** + * 查询部别树。 + * + * @param dto 查询条件 + * @return 部别树 + */ + List tree(DepartmentQueryDTO dto); + + /** + * 停用部别。 + * + * @param dto 部别编号参数 + * @return 停用后的部别信息 + */ + DepartmentVO disable(DepartmentIdDTO dto); + + /** + * 启用部别。 + * + * @param dto 部别编号参数 + * @return 启用后的部别信息 + */ + DepartmentVO enable(DepartmentIdDTO dto); + + /** + * 删除部别(逻辑删除)。 + *

删除前必须确认不存在启用中的下级部别, + * 并且不存在教研室、学员队、机关参谋或教学管理机构引用。

+ * + * @param dto 部别编号参数 + * @return 被删除的部别信息 + */ + DepartmentVO delete(DepartmentIdDTO dto); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DepartmentPersonnelService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DepartmentPersonnelService.java new file mode 100644 index 000000000..2037ac943 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DepartmentPersonnelService.java @@ -0,0 +1,107 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelCreateDTO; +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelIdDTO; +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelImportDTO; +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelQueryDTO; +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelUpdateDTO; +import com.roomroot.jwgl.vo.departmentpersonnel.DepartmentPersonnelDepartmentVO; +import com.roomroot.jwgl.vo.departmentpersonnel.DepartmentPersonnelVO; + +import java.util.List; + +/** + * 部别人员管理服务。 + */ +public interface DepartmentPersonnelService { + + /** + * 查询人员管理页面可选择的启用部别。 + * + * @param dto 部别查询条件 + * @return 启用部别列表 + */ + List listDepartments( + DepartmentPersonnelQueryDTO dto); + + /** + * 查询部别人员列表。 + * + * @param dto 人员查询条件 + * @return 人员列表 + */ + List list(DepartmentPersonnelQueryDTO dto); + + /** + * 查询部别人员详情。 + * + * @param dto 人员编号参数 + * @return 人员详情 + */ + DepartmentPersonnelVO get(DepartmentPersonnelIdDTO dto); + + /** + * 新增部别人员。 + * + * @param dto 人员新增参数 + * @return 新增后的人员信息 + */ + DepartmentPersonnelVO add(DepartmentPersonnelCreateDTO dto); + + /** + * 修改部别人员。 + * + * @param dto 人员修改参数 + * @return 修改后的人员信息 + */ + DepartmentPersonnelVO update(DepartmentPersonnelUpdateDTO dto); + + /** + * 将人员标记为离职。 + * + * @param dto 人员编号参数 + * @return 离职后的人员信息 + */ + DepartmentPersonnelVO leave(DepartmentPersonnelIdDTO dto); + + /** + * 将人员恢复为在职。 + * + * @param dto 人员编号参数 + * @return 复职后的人员信息 + */ + DepartmentPersonnelVO restore(DepartmentPersonnelIdDTO dto); + + /** + * 批量导入部别人员。 + * + * @param dto 批量导入参数 + * @return 成功导入的人员列表 + */ + List batchImport(DepartmentPersonnelImportDTO dto); + + /** + * 通过Excel文件导入部别人员。 + * + * @param inputStream Excel文件输入流 + * @param fileName 文件名 + * @return 成功导入的人员列表 + */ + List importFromExcel(java.io.InputStream inputStream, String fileName); + + /** + * 按查询条件导出人员数据。 + * + * @param dto 导出筛选条件 + * @return 排序后的人员数据 + */ + List export(DepartmentPersonnelQueryDTO dto); + + /** + * 按查询条件导出人员数据为 Excel 文件。 + * + * @param dto 导出筛选条件 + * @return Excel 文件字节数组 + */ + byte[] exportToExcel(DepartmentPersonnelQueryDTO dto); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DictDataService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DictDataService.java new file mode 100644 index 000000000..b8f3e710c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DictDataService.java @@ -0,0 +1,106 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.dict.DictDataBatchQueryDTO; +import com.roomroot.jwgl.dto.dict.DictDataQueryDTO; +import com.roomroot.jwgl.dto.dict.DictDataSaveDTO; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.dict.DictDataVO; + +import java.util.List; +import java.util.Map; + +/** + * 字典数据服务。 + *

+ * 负责字典数据的查询(单类型、分页、批量)、保存和删除业务逻辑。 + * 字典数据是字典类型下的具体选项,包含标签(显示文本)、值(存储值)、 + * 排序、状态、是否默认等属性,为前端下拉框、单选、多选等组件提供数据源。 + *

+ */ +public interface DictDataService { + + /** + * 按类型编码查询全部启用的字典数据(不分页,用于前端下拉框)。 + *

+ * 返回指定字典类型下状态为启用的所有数据项,按排序号升序排列。 + * 仅返回启用状态的数据,保证前端下拉框中不展示已停用的选项。 + *

+ * + * @param dictTypeCode 字典类型编码 + * @return 字典数据列表 + */ + List listByType(String dictTypeCode); + + /** + * 分页查询字典数据列表(管理页面用)。 + *

+ * 根据查询条件进行分页筛选,支持按字典类型编码、关键字(匹配标签或值)、 + * 状态进行过滤,结果按排序号升序排列。 + * 与 {@link #listByType} 不同,此接口可查看启用和停用的全部数据项。 + *

+ * + * @param dto 查询条件,包含分页参数和筛选条件 + * @return 字典数据分页结果 + */ + PageResult page(DictDataQueryDTO dto); + + /** + * 批量查询多个类型的字典数据。 + *

+ * 根据传入的多个字典类型编码,一次性查询所有类型的启用数据, + * 返回结果按类型编码分组,便于前端页面初始化时批量获取多个下拉框数据。 + *

+ * + * @param dto 批量查询参数,包含字典类型编码列表 + * @return 按类型编码分组的数据 + */ + Map> batchList(DictDataBatchQueryDTO dto); + + /** + * 保存字典数据(新增或编辑)。 + *

+ * 当 {@code dto.dictDataId} 为空时执行新增操作, + * 当 {@code dto.dictDataId} 有值时执行编辑操作。 + * 保存时会校验关联的字典类型是否存在; + * 若 {@code isDefault} 设置为 1,会自动取消该类型下其他数据的默认标记, + * 保证同一类型下只有一个默认选项。 + *

+ * + * @param dto 保存参数 + * @return 保存后的字典数据 + */ + DictDataVO save(DictDataSaveDTO dto); + + /** + * 删除字典数据。 + *

+ * 根据字典数据ID删除单条记录,删除前校验数据是否存在。 + *

+ * + * @param dictDataId 字典数据ID + */ + void delete(Long dictDataId); + + /** + * 获取字典数据的历史版本列表。 + *

+ * 返回该字典数据所有的历史快照信息,用于版本回溯查看。 + *

+ * + * @param dictDataId 字典数据ID + * @return 历史版本快照列表 + */ + List> getHistory(Long dictDataId); + + /** + * 回滚字典数据到指定版本。 + *

+ * 根据目标版本号,从历史快照中恢复数据,并创建新的版本记录。 + *

+ * + * @param dictDataId 字典数据ID + * @param targetVersion 目标版本号 + * @return 回滚后的字典数据详情 + */ + DictDataVO rollback(Long dictDataId, Integer targetVersion); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DictTypeService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DictTypeService.java new file mode 100644 index 000000000..07c2ca1a6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DictTypeService.java @@ -0,0 +1,80 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.dict.DictTypeQueryDTO; +import com.roomroot.jwgl.dto.dict.DictTypeSaveDTO; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.dict.DictTypeVO; + +import java.util.List; +import java.util.Map; + +/** + * 字典类型服务。 + *

+ * 负责字典类型的查询、保存和删除业务逻辑。 + * 字典类型是对系统字典的分类,如课程类型、职称等级、教室用途等, + * 每个类型下可维护多条具体的字典数据项。 + *

+ */ +public interface DictTypeService { + + /** + * 分页查询字典类型列表。 + *

+ * 根据查询条件进行分页筛选,支持按关键字(匹配类型编码或名称)、 + * 所属分组、状态进行过滤,结果按创建时间降序排列。 + *

+ * + * @param dto 查询条件,包含分页参数和筛选条件 + * @return 字典类型分页结果 + */ + PageResult page(DictTypeQueryDTO dto); + + /** + * 保存字典类型(新增或编辑)。 + *

+ * 当 {@code dto.dictTypeId} 为空时执行新增操作, + * 当 {@code dto.dictTypeId} 有值时执行编辑操作。 + * 新增时校验字典类型编码全局唯一性; + * 编辑时若修改了编码,同样需要校验新编码的唯一性。 + *

+ * + * @param dto 保存参数 + * @return 保存后的字典类型 + */ + DictTypeVO save(DictTypeSaveDTO dto); + + /** + * 删除字典类型。 + *

+ * 删除前校验字典类型是否存在,并检查该类型下是否有关联的字典数据项。 + * 若存在关联数据项,则抛出业务异常,禁止删除,防止影响已引用该字典的业务数据。 + *

+ * + * @param dictTypeId 字典类型ID + */ + void delete(Long dictTypeId); + + /** + * 获取字典类型的历史版本列表。 + *

+ * 返回该字典类型所有的历史快照信息,用于版本回溯查看。 + *

+ * + * @param dictTypeId 字典类型ID + * @return 历史版本快照列表 + */ + List> getHistory(Long dictTypeId); + + /** + * 回滚字典类型到指定版本。 + *

+ * 根据目标版本号,从历史快照中恢复数据,并创建新的版本记录。 + *

+ * + * @param dictTypeId 字典类型ID + * @param targetVersion 目标版本号 + * @return 回滚后的字典类型详情 + */ + DictTypeVO rollback(Long dictTypeId, Integer targetVersion); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DisciplineService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DisciplineService.java new file mode 100644 index 000000000..0cfa7f28c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/DisciplineService.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.XKZYXX; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import org.springframework.stereotype.Service; + +/** + * 学科专业管理服务接口 + *

+ * 提供学科、专业的增加、删除(逻辑删除)、更新、分页查询等功能。 + * 对应数据表:学科专业信息 + *

+ */ +@Service +public interface DisciplineService { + + /** + * 新增学科专业 + * + * @param entity 学科专业实体 + */ + void add(XKZYXX entity); + + /** + * 逻辑删除(停用)学科专业 + * + * @param bSH 标识号 + */ + void disable(String bSH); + + /** + * 更新学科专业 + * + * @param entity 学科专业实体 + */ + void update(XKZYXX entity); + + /** + * 根据标识号查询学科专业详情 + * + * @param bSH 标识号 + * @return 学科专业实体 + */ + XKZYXX getById(String bSH); + + /** + * 分页条件查询学科专业列表 + * + * @param query 分页参数 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, XKZYXX xkzyxx); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ElectiveService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ElectiveService.java new file mode 100644 index 000000000..3edea6c8d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ElectiveService.java @@ -0,0 +1,131 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.elective.AddClassDTO; +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.elective.*; +import com.roomroot.jwgl.vo.elective.*; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 教学班次管理服务接口 + */ +public interface ElectiveService { + + // ========== 教学班次(学员队表) ========== + + /** 新增教学班次(同时写入学员队表和学员队期表) */ + void add(AddClassDTO dto); + + void disable(String xydbh); + + void update(XYDB entity); + + XYDB getById(String xydbh); + + PageResult pageList(PageQuery query, XYDB cond); + + /** 双表联查分页列表(含学号规则、注册开关) */ + PageResult pageListWithPeriod(PageQuery query, ElectiveClassVO cond); + + /** + * 根据时间范围过滤学员队表 + *

+ * 查找入学日期和毕业日期包含指定时间范围的学员队 + * 即:入学日期 <= startTime 且 毕业日期 >= endTime + *

+ * + * @param startTime 时间范围开始 + * @param endTime 时间范围结束 + * @return 符合条件的学员队列表 + */ + List filterByDateRange(LocalDateTime startTime, LocalDateTime endTime); + + // ========== 学员队期(学号规则+注册设置) ========== + + XYDQB getPeriodByXydbh(String xydbh); + + void updatePeriod(XYDQB entity); + + /** 设置是否允许学员自行注册 */ + void setRegistrationEnabled(String xydbh, Integer enabled); + + /** 生成下一个学员学号(格式:入学年份年月日 + 三位流水号) */ + String generateNextStudentNumber(String xydbh); + + // ========== 关联信息查询 ========== + + /** 查询班次的课程列表(通过实施_课程学员队关联) */ + List getClassCourses(String xydbh, Integer nd); + + /** 查询班次的学期课程表 */ + List getClassSchedule(String xydbh, Integer nd); + + /** 查询班次的学员列表 */ + List getClassStudents(String xydbh); + + /** 查询班次的学员成绩 */ + List getClassGrades(String xydbh, String kmbh); + + /** 查询班次的学员缺勤记录 */ + List getClassAbsences(String xydbh, Integer nd); + + // ========== 教学班次学期信息设置 ========== + + /** + * 查询教学楼列表(教学班次学期信息设置窗口 - 教学楼下拉框) + * + * @return 教学楼列表 + */ + List listBuildings(); + + /** + * 查询教室列表(教学班次学期信息设置窗口 - 专用教室选择) + *

支持按教学楼代号过滤,以及按教室名称拼音首字母快速查找。

+ * + * @param jxldh 教学楼代号(可选) + * @param py 教室名称拼音首字母(可选,模糊匹配) + * @return 教室列表 + */ + List listRooms(String jxldh, String py); + + /** + * 获取教学班次学期信息设置窗口的初始化默认值 + *

+ * 根据班次学制(入学/毕业日期)计算: + *

    + *
  • 学制半年以内:学期第次固定为第一学期,开学日期=入学日期,结束日期=毕业日期
  • + *
  • 学制半年以上:按入学日期智能推断当前学期第次(可手动修改), + * 开学/结束日期默认取窗口预设日期(presetKxrq/presetJsrq)
  • + *
  • 第二以上学期:默认预设上学期的专用教室
  • + *
+ *

+ * + * @param xydbh 班次编号(学员队编号) + * @param presetKxrq 窗口预设开学日期(yyyy-MM-dd,半年以上班次使用,可选) + * @param presetJsrq 窗口预设结束日期(yyyy-MM-dd,半年以上班次使用,可选) + * @return 初始化默认值 + */ + SemesterInitVO getSemesterInit(String xydbh, String presetKxrq, String presetJsrq); + + /** + * 创建班次学期信息(教学班次学期信息设置窗口 - 确定按钮) + *

+ * 自动处理默认逻辑: + *

    + *
  • 学制半年以内:学期第次强制为第一学期(传入其他值将校验失败), + * 开学/结束日期为空时默认取班次的入学/毕业日期
  • + *
  • 学制半年以上:学期第次为空时按入学日期智能推断; + * 开学/结束日期需由窗口预设值传入,不允许为空
  • + *
  • 第二以上学期:专用教室为空时,默认预设上学期的专用教室
  • + *
  • 同一班次+年度+学期第次已存在时拒绝重复创建
  • + *
+ *

+ * + * @param xydndxqjbxxb 班次学期信息(编号/变动时间/删除标记等由服务端自动填充) + */ + void addSemester(XYDNDXQJBXXB xydndxqjbxxb); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/FacultyService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/FacultyService.java new file mode 100644 index 000000000..df330d3a5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/FacultyService.java @@ -0,0 +1,99 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.faculty.AddTeacherDTO; +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.faculty.TeacherListVO; + +/** + * 教研室与教员管理服务接口 + *

+ * 提供教研室、教员的增加、删除、更新、查询等功能管理, + * 以及教员属性、年度综合评定、课程表等关联信息的查询。 + *

+ */ +public interface FacultyService { + + // ==================== 教研室管理 ==================== + + /** + * 新增教研室 + */ + void addJYSB(JYSB entity); + + /** + * 停用(逻辑删除)教研室 + */ + void disableJYSB(String jYSDH); + + /** + * 更新教研室 + */ + void updateJYSB(JYSB entity); + + /** + * 根据代号查询教研室详情 + */ + JYSB getJYSBById(String jYSDH); + + /** + * 分页条件查询教研室列表 + */ + PageResult pageJYSB(PageQuery query, JYSB cond); + + // ==================== 教员管理 ==================== + + /** + * 新增教员(同时创建教员属性记录) + *

接收前端传回的教员基本信息和属性信息,一次性写入两张表。

+ */ + void addJYB(AddTeacherDTO dto); + + /** + * 离职(逻辑删除)教员 + */ + void disableJYB(String jYBH); + + /** + * 更新教员基本信息 + */ + void updateJYB(JYB entity); + + /** + * 根据编号查询教员详情(含属性信息) + */ + JYB getJYBById(String jYBH); + + /** + * 分页条件查询教员列表 + */ + /** 分页条件查询教员列表(三表联查) */ + PageResult pageJYBDetail(PageQuery query, JYB cond); + + // ==================== 教员属性管理 ==================== + + /** + * 根据教员编号查询教员属性 + */ + JYSX getJYSXByJYBH(String jYBH); + + /** + * 更新教员属性 + */ + void updateJYSX(JYSX entity); + + // ==================== 年度综合评定查询 ==================== + + /** + * 分页查询年度综合评定_教员 + */ + PageResult pageNDZHPDJY(PageQuery query, NDZHPDJY cond); + + // ==================== 教员课程表查询 ==================== + + /** + * 分页查询教员输出课程表 + */ + PageResult pageJYSCKCB(PageQuery query, JYSCKCB cond); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/KCBService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/KCBService.java new file mode 100644 index 000000000..6571f31c1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/KCBService.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.KCB; + +import java.util.List; + +/** + * 课程表服务接口 + */ +public interface KCBService { + + /** + * 新增 + * + * @param kcb 实体 + */ + void add(KCB kcb); + + /** + * 删除 + * + * @param kbh 课编号 + */ + void delete(String kbh); + + /** + * 批量删除 + * + * @param kbhList 课编号列表 + */ + void batchDelete(List kbhList); + + /** + * 更新 + * + * @param kcb 实体 + */ + void update(KCB kcb); + + /** + * 根据课编号查询 + * + * @param kbh 课编号 + * @return 实体 + */ + KCB getByKbh(String kbh); + + /** + * 查询所有数据 + * + * @return 所有数据列表 + */ + List list(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/LeaveService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/LeaveService.java new file mode 100644 index 000000000..3aa5e1264 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/LeaveService.java @@ -0,0 +1,116 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.LeaveDTO; +import com.roomroot.jwgl.dto.LeaveQueryDTO; +import com.roomroot.jwgl.entity.XYJXQJB; +import com.roomroot.jwgl.entity.XYQJSPB; + +import java.util.List; + +/** + * 请假服务接口 + */ +public interface LeaveService { + + /** + * 新增请假(主表+课堂+学员) + * + * @param dto 请假DTO + */ + void add(LeaveDTO dto); + + /** + * 更新请假主表 + * + * @param xyjxqjb 实体 + * @return 是否成功 + */ + boolean update(XYJXQJB xyjxqjb); + + /** + * 删除请假(主表+课堂+学员+审批) + * + * @param bh 编号 + * @return 是否成功 + */ + boolean delete(String bh); + + /** + * 根据编号查询请假主表 + * + * @param bh 编号 + * @return 实体 + */ + XYJXQJB getByBh(String bh); + + /** + * 查询所有请假主表数据 + * + * @return 列表 + */ + List list(); + + /** + * 根据条件查询请假主表数据 + * 支持起始时间、结束时间、创建时间范围查询 + * + * @param queryDTO 查询条件 + * @return 列表 + */ + List list(LeaveQueryDTO queryDTO); + + /** + * 根据请假编号查询请假课堂列表 + * + * @param qjbh 请假编号 + * @return 课堂编号列表 + */ + List listClassroomBhsByQjbh(String qjbh); + + /** + * 根据请假编号查询请假学员列表 + * + * @param qjbh 请假编号 + * @return 学员编号列表 + */ + List listStudentBhsByQjbh(String qjbh); + + // ===== 审批表 CRUD ===== + + /** + * 新增审批 + * + * @param xyqjspb 审批实体 + */ + void addApproval(XYQJSPB xyqjspb); + + /** + * 删除审批 + * + * @param bh 编号 + */ + void deleteApproval(String bh); + + /** + * 更新审批 + * + * @param xyqjspb 审批实体 + */ + void updateApproval(XYQJSPB xyqjspb); + + /** + * 根据编号查询审批 + * + * @param bh 编号 + * @return 审批实体 + */ + XYQJSPB getApprovalByBh(String bh); + + /** + * 根据请假编号查询审批列表 + * + * @param qjbh 请假编号 + * @return 审批列表 + */ + List listApprovalsByQjbh(String qjbh); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/LogService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/LogService.java new file mode 100644 index 000000000..0b6041aab --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/LogService.java @@ -0,0 +1,172 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.log.TeacherHoursVO; +import com.roomroot.jwgl.vo.log.TeachingLogListVO; +import org.springframework.stereotype.Service; + +/** + * 教学日志管理服务接口 + *

+ * 涵盖教学日志核心管理、课时核算标准、课时补助标准、联教联训、教师工作量等全功能。 + *

+ */ +@Service +public interface LogService { + + // ==================== 教学日志核心 ==================== + + /** 新增教学日志 */ + void addTeachingLog(BCKTJXRZ entity); + /** 更新教学日志 */ + void updateTeachingLog(BCKTJXRZ entity); + /** 根据编号查询 */ + BCKTJXRZ getTeachingLogById(String bh); + /** 分页条件查询(基于班次课堂教学日志) */ + PageResult pageTeachingLog(PageQuery query, BCKTJXRZ cond); + /** 分页条件查询教学日志列表(基于实施_课程表_日志联查,展示字段更丰富) */ + PageResult pageTeachingLogDetail(PageQuery query, TeachingLogListVO cond); + + // ==================== 教学日志(基于实施_课程表_日志 36字段表) ==================== + + /** 新增教学日志 */ + void addLog(SSKCB_RZ entity); + /** 退回教学日志(填写退回意见) */ + void rejectLog(String bh, String thyj); + /** 根据课表自动创建教学日志 */ + SSKCB_RZ autoCreateLog(String tybh); + /** 导入教学日志 */ + void importLog(java.util.List list); + /** 批量上报教学日志(根据主键编号组将状态修改为已上报,仅对待上报数据生效,返回实际更新条数) */ + int batchReportLogs(java.util.List bhList); + /** 批量审核教学日志(根据主键编号组将状态修改为已审核,仅对已上报数据生效,返回实际更新条数) */ + int batchAuditLogs(java.util.List bhList); + /** 从 Excel 导入教学日志 */ + void importLogFromExcel(org.springframework.web.multipart.MultipartFile file); + /** 导出教学日志 Excel */ + void exportLogExcel(TeachingLogListVO cond, jakarta.servlet.http.HttpServletResponse response); + /** 提交审核(状态流转) */ + void submitApproval(String bh, String targetStatus); + /** 审核通过 */ + void approve(String bh, String feedback); + /** 审核退回 */ + void reject(String bh, String feedback); + /** 自动创建教学日志(基于课表信息) */ + BCKTJXRZ autoCreateFromSchedule(String sskcbh); + /** 对照课表查看教学日志填报状态 */ + PageResult checkLogStatusBySchedule(PageQuery query, String jybh, Integer nd); + + // ==================== 教学日志授课教员 ==================== + + void addLogTeacher(BCKTJXRZSKJY entity); + PageResult pageLogTeacher(PageQuery query, BCKTJXRZSKJY cond); + + // ==================== 教学日志学员学习情况 ==================== + + void addStudentLearningStatus(JXRZXYXXQK entity); + PageResult pageStudentLearningStatus(PageQuery query, String jxrzbh); + + // ==================== 课时核算标准 ==================== + + void addCalculationStandard(KSHBZ entity); + void updateCalculationStandard(KSHBZ entity); + KSHBZ getCalculationStandardById(String bh); + PageResult pageCalculationStandard(PageQuery query, KSHBZ cond); + + // ==================== 课时系数方案 ==================== + + /** 新增课时系数方案 */ + void addCoefficientPlan(KSXSFA entity); + /** 更新课时系数方案(需含编号) */ + void updateCoefficientPlan(KSXSFA entity); + /** 根据编号查询课时系数方案 */ + KSXSFA getCoefficientPlanById(String bh); + /** 分页条件查询课时系数方案列表(名称模糊匹配) */ + PageResult pageCoefficientPlan(PageQuery query, KSXSFA cond); + + // ==================== 课时费标准 ==================== + + void addFeeStandard(KSFBZ entity); + void updateFeeStandard(KSFBZ entity); + KSFBZ getFeeStandardById(String bh); + PageResult pageFeeStandard(PageQuery query, KSFBZ cond); + + // ==================== 技术职务课时费标准 ==================== + + void addTechPosFeeStandard(JSZWKSFBZ entity); + void updateTechPosFeeStandard(JSZWKSFBZ entity); + JSZWKSFBZ getTechPosFeeStandardById(String bh); + PageResult pageTechPosFeeStandard(PageQuery query, JSZWKSFBZ cond); + + // ==================== 课时补助项目 ==================== + + void addSubsidyProject(KSBZXM entity); + void updateSubsidyProject(KSBZXM entity); + KSBZXM getSubsidyProjectById(String bh); + PageResult pageSubsidyProject(PageQuery query, KSBZXM cond); + + // ==================== 教学日志课时补助审批表(汇总) ==================== + + void addSubsidyApproval(JXRZKSBZSPB entity); + void updateSubsidyApproval(JXRZKSBZSPB entity); + JXRZKSBZSPB getSubsidyApprovalById(String bh); + PageResult pageSubsidyApproval(PageQuery query, JXRZKSBZSPB cond); + /** 计算汇总数据(总课时量、优质课时量、总补助金额) */ + void calculateSummary(String bh); + + // ==================== 教学日志教员课时补助明细 ==================== + + PageResult pageSubsidyDetail(PageQuery query, JXRZJYKSBZ cond); + + // ==================== 教员课时补助 ==================== + + void addTeacherSubsidy(JYKSBZ entity); + PageResult pageTeacherSubsidy(PageQuery query, JYKSBZ cond); + + + + // ==================== 年终课时费 ==================== + + void addYearEndFee(NZKSF entity); + void updateYearEndFee(NZKSF entity); + NZKSF getYearEndFeeById(String bh); + PageResult pageYearEndFee(PageQuery query, NZKSF cond); + + // ==================== 教员年终课时费 ==================== + + PageResult pageTeacherYearEndFee(PageQuery query, JYNZKSF cond); + + // ==================== 教师工作量 ==================== + + void addWorkload(JZGZL entity); + void updateWorkload(JZGZL entity); + JZGZL getWorkloadById(String bh); + PageResult pageWorkload(PageQuery query, JZGZL cond); + /** 自动统计课程教学工作量 */ + void calcCourseWorkload(String jybh, Integer nd, String xq); + /** 比对各类教师工作量情况 */ + PageResult compareWorkload(PageQuery query, Integer nd, String xq); + + // ==================== 教学实施计划统计 ==================== + + PageResult pageTeachingStats(PageQuery query, JXSSJHTJB cond); + + // ==================== 教员课时查询管理 ==================== + + /** + * 分页查询教员课时信息 + *

+ * 数据来源:学期教员信息表 JOIN 教员表 JOIN 教研室表。 + * 列表展示教研室名称、教员姓名、教员类别、当前/学期职称、当前/学期专业技术职务等级、 + * 主讲课时、辅讲课时、课程总课时、课程折算总课时、项目补助课时、总课时。 + * 支持按教员姓名、教研室、部系、年度(学期)、职称、职务等级以及各课时量数字范围(xx课时至xx课时)筛选。 + *

+ * + * @param query 分页参数 + * @param cond 查询条件(含课时量范围) + * @return 分页结果 + */ + PageResult pageTeacherHours(PageQuery query, TeacherHoursVO cond); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/NoticeAnnouncementService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/NoticeAnnouncementService.java new file mode 100644 index 000000000..1d3cd6ffb --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/NoticeAnnouncementService.java @@ -0,0 +1,98 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementCreateDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementIdDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPriorityDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPublishDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementQueryDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientQueryDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementUpdateDTO; +import com.roomroot.jwgl.dto.noticeannouncement.UserNoticeAnnouncementQueryDTO; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementDetailVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementPublishResultVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementRecipientVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementStatisticsVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementVO; +import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementDetailVO; +import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementVO; + +/** + * 通知公告管理服务。 + * + *

在同一个业务服务中维护公告生命周期、发布范围、接收人快照、 + * 用户阅读和统计查询,不为同一功能重复创建 Service 类。

+ */ +public interface NoticeAnnouncementService { + + /** + * 分页查询后台通知公告。 + */ + PageResult list( + NoticeAnnouncementQueryDTO dto); + + /** + * 查询通知公告后台详情。 + */ + NoticeAnnouncementDetailVO get( + NoticeAnnouncementIdDTO dto); + + /** + * 新增一条拟制公告。 + */ + NoticeAnnouncementDetailVO add( + NoticeAnnouncementCreateDTO dto); + + /** + * 修改拟制或已下架公告。 + */ + NoticeAnnouncementDetailVO update( + NoticeAnnouncementUpdateDTO dto); + + /** + * 发布公告并生成接收人快照。 + */ + NoticeAnnouncementPublishResultVO publish( + NoticeAnnouncementPublishDTO dto); + + /** + * 下架已发布公告。 + */ + NoticeAnnouncementDetailVO down( + NoticeAnnouncementIdDTO dto); + + /** + * 删除拟制或已下架公告。 + */ + void delete(NoticeAnnouncementIdDTO dto); + + /** + * 调整公告优先级。 + */ + NoticeAnnouncementDetailVO updatePriority( + NoticeAnnouncementPriorityDTO dto); + + /** + * 分页查询公告接收人和阅读明细。 + */ + PageResult recipients( + NoticeAnnouncementRecipientQueryDTO dto); + + /** + * 查询公告接收和阅读统计。 + */ + NoticeAnnouncementStatisticsVO statistics( + NoticeAnnouncementIdDTO dto); + + /** + * 分页查询当前登录账户可见的公告。 + */ + PageResult userList( + UserNoticeAnnouncementQueryDTO dto); + + /** + * 当前登录账户打开公告并完成阅读记录。 + */ + UserNoticeAnnouncementDetailVO read( + NoticeAnnouncementIdDTO dto); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/NotificationLogService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/NotificationLogService.java new file mode 100644 index 000000000..0b1d68a71 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/NotificationLogService.java @@ -0,0 +1,67 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.notificationlog.NotificationLogExportDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogIdDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogQueryDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogRecipientQueryDTO; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogDetailVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogRecipientVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogStatisticsVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogVO; + +import java.util.List; + +/** + * 通知日志管理服务。 + * + *

同一个业务服务将逐步维护通知发送历史、详情、接收阅读明细、 + * 汇总统计和导出能力,不为每个小功能重复创建 Service 类。

+ */ +public interface NotificationLogService { + + /** + * 分页查询已经实际发送的通知日志。 + * + * @param dto 通知日志查询条件 + * @return 经过筛选、统计补全和稳定排序的分页结果 + */ + PageResult list( + NotificationLogQueryDTO dto); + + /** + * 根据通知编号查询单条发送日志详情。 + * + * @param dto 通知日志编号参数 + * @return 通知主体和完整发送、阅读统计 + */ + NotificationLogDetailVO get( + NotificationLogIdDTO dto); + + /** + * 分页查询指定通知的接收对象和阅读明细。 + * + * @param dto 通知编号、分页和接收阅读筛选条件 + * @return 按账户去重的接收与阅读明细 + */ + PageResult recipients( + NotificationLogRecipientQueryDTO dto); + + /** + * 按当前筛选条件汇总通知发送和阅读情况。 + * + * @param dto 通知日志筛选条件 + * @return 通知数量、发送结果和阅读情况汇总 + */ + NotificationLogStatisticsVO statistics( + NotificationLogExportDTO dto); + + /** + * 按当前筛选条件查询全部待导出的通知日志。 + * + * @param dto 通知日志导出筛选条件 + * @return 与列表筛选和排序规则一致的全部通知日志 + */ + List export( + NotificationLogExportDTO dto); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/OfficeTaskBookService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/OfficeTaskBookService.java new file mode 100644 index 000000000..11287a9ba --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/OfficeTaskBookService.java @@ -0,0 +1,74 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JYSRWS; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.List; + +/** + * 教研室任务书服务接口 + */ +public interface OfficeTaskBookService { + + /** + * 新增 + * + * @param jysrws 实体 + */ + void add(JYSRWS jysrws); + + /** + * 删除 + * + * @param bh 编号 + */ + void delete(String bh); + + /** + * 批量删除 + * + * @param bhList 编号列表 + */ + void batchDelete(List bhList); + + /** + * 更新 + * + * @param jysrws 实体 + */ + void update(JYSRWS jysrws); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 实体 + */ + JYSRWS getByBh(String bh); + + /** + * 分页查询列表 + * + * @param query 分页参数 + * @param jysrws 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, JYSRWS jysrws); + + /** + * 根据教学任务编号查询 + * + * @param jxrwbh 教学任务编号 + * @return 教研室任务书列表 + */ + List listByJxrwbh(String jxrwbh); + + /** + * 根据教研室代号查询 + * + * @param jysdh 教研室代号 + * @return 教研室任务书列表 + */ + List listByJysdh(String jysdh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/OperationLogService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/OperationLogService.java new file mode 100644 index 000000000..0a18025e8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/OperationLogService.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.operationlog.OperationLogExportDTO; +import com.roomroot.jwgl.dto.operationlog.OperationLogIdDTO; +import com.roomroot.jwgl.dto.operationlog.OperationLogQueryDTO; +import com.roomroot.jwgl.dto.operationlog.OperationLogRecordDTO; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.operationlog.OperationLogDetailVO; +import com.roomroot.jwgl.vo.operationlog.OperationLogModuleVO; +import com.roomroot.jwgl.vo.operationlog.OperationLogVO; + +import java.util.List; + +/** + * 操作日志管理服务。 + * + *

统一维护操作日志的查询、详情、导出和内部写入能力, + * 不为同一业务功能重复创建多个业务服务类。

+ */ +public interface OperationLogService { + + /** + * 分页查询操作日志。 + * + * @param dto 查询条件 + * @return 经过筛选和稳定排序的操作日志分页结果 + */ + PageResult list(OperationLogQueryDTO dto); + + /** + * 查询操作日志页面可使用的模块选项。 + * + * @return 与模块筛选校验规则保持一致的模块选项 + */ + List modules(); + + /** + * 根据编号查询操作日志详情。 + * + * @param dto 操作日志编号参数 + * @return 操作日志、登录记录及操作账户的完整审计信息 + */ + OperationLogDetailVO get(OperationLogIdDTO dto); + + /** + * 按筛选条件查询全部待导出的操作日志。 + * + * @param dto 导出筛选条件 + * @return 经过统一筛选、排序和模块识别的操作日志列表 + */ + List export(OperationLogExportDTO dto); + + /** + * 写入一条由服务端自动采集的操作日志。 + * + * @param dto 当前登录记录编号、操作内容和请求地址 + */ + void record(OperationLogRecordDTO dto); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/SemesterCalendarService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/SemesterCalendarService.java new file mode 100644 index 000000000..b63850bf4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/SemesterCalendarService.java @@ -0,0 +1,58 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.XQXLB; + +import java.util.List; + +/** + * 学期校历表服务接口 + */ +public interface SemesterCalendarService { + + /** + * 新增学期校历 + * + * @param startDate 学期起始日期 + * @param endDate 学期结束日期 + */ + void add(String startDate,String endDate); + + /** + * 删除学期校历 + * + * @param bh 编号 + */ + void delete(String bh); + + /** + * 更新学期校历 + * + * @param xqxlb 学期校历 + */ + void update(XQXLB xqxlb); + + /** + * 根据编号查询学期校历 + * + * @param bh 编号 + * @return 学期校历 + */ + XQXLB getById(String bh); + + /** + * 根据年度查询学期校历列表(全量) + * + * @param nd 年度 + * @return 学期校历列表 + */ + List listByNd(Integer nd); + + /** + * 导入Excel文件解析 + * + * @param file Excel文件 + * @return 导入数量 + */ + int importExcel(org.springframework.web.multipart.MultipartFile file); + +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/SemesterService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/SemesterService.java new file mode 100644 index 000000000..37efb769d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/SemesterService.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.roomroot.jwgl.entity.XQ; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.List; + +/** + * 学期服务接口 + */ +public interface SemesterService extends IService { + + /** + * 新增学期 + * + * @param xq 学期 + */ + void add(XQ xq); + + /** + * 删除学期 + * + * @param nd 年度(主键) + */ + void delete(Integer nd); + + /** + * 更新学期 + * + * @param xq 学期 + */ + void update(XQ xq); + + /** + * 根据年度查询 + * + * @param nd 年度(主键) + * @return 学期 + */ + XQ getByNd(Integer nd); + + /** + * 分页查询学期列表 + * + * @param query 分页参数 + * @param xq 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, XQ xq); + + /** + * 查询所有学期列表 + * + * @return 学期列表 + */ + List list(); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/StudentRecordsService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/StudentRecordsService.java new file mode 100644 index 000000000..8a3e1ffd4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/StudentRecordsService.java @@ -0,0 +1,57 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.XYXX; +import com.roomroot.jwgl.entity.XYKCCJ; +import com.roomroot.jwgl.entity.XYKCGCCJ; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 学员档案管理服务接口 + *

+ * 提供学员信息的增删改查、学籍异动管理, + * 以及学员课程、成绩、课程表查询和 Excel 导出功能。 + *

+ */ +public interface StudentRecordsService { + + /** 新增学员 */ + void add(XYXX xyxx); + + /** 删除学员(退学处理) */ + void delete(String bh); + + /** 更新学员信息 */ + void update(XYXX xyxx); + + /** 查询学员详情 */ + XYXX getById(String bh); + + /** 分页查询学员列表 */ + PageResult pageList(PageQuery query, XYXX xyxx); + + /** 学籍异动——留级 */ + void retainGrade(String bh); + + /** 学籍异动——降级 */ + void downgrade(String bh); + + /** 学籍异动——退学 */ + void dropOut(String bh); + + // ========== 学员课程成绩查询 ========== + + /** 查询学员的所有课程成绩 */ + List getCourseGrades(String xybh); + + /** 查询学员的所有课程过程成绩 */ + List getCourseProcessGrades(String xybh); + + // ========== Excel 导出 ========== + + /** 导出学员课程成绩 Excel */ + void exportGradesExcel(String xybh, HttpServletResponse response); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/StudentTeamOutputScheduleService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/StudentTeamOutputScheduleService.java new file mode 100644 index 000000000..4222e4844 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/StudentTeamOutputScheduleService.java @@ -0,0 +1,67 @@ +package com.roomroot.jwgl.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDSCKCB; + +import java.util.List; + +/** + * 学员队输出课程表 Service + */ +public interface StudentTeamOutputScheduleService { + + /** + * 新增 + * + * @param xydsckcb 学员队输出课程表 + */ + void add(XYDSCKCB xydsckcb); + + /** + * 删除 + * + * @param bh 编号 + */ + void delete(String bh); + + /** + * 更新 + * + * @param xydsckcb 学员队输出课程表 + */ + void update(XYDSCKCB xydsckcb); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 学员队输出课程表 + */ + XYDSCKCB getByBh(String bh); + + /** + * 根据年度和学员队编号查询 + * + * @param nd 年度 + * @param xydbh 学员队编号 + * @return 学员队输出课程表 + */ + XYDSCKCB getByNdAndXydbh(Integer nd, String xydbh); + + /** + * 分页查询 + * + * @param pageNum 页码 + * @param pageSize 每页数量 + * @param xydsckcb 查询条件 + * @return 分页结果 + */ + Page list(int pageNum, int pageSize, XYDSCKCB xydsckcb); + + /** + * 查询所有 + * + * @return 所有学员队输出课程表 + */ + List all(); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/StudentTeamTaskService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/StudentTeamTaskService.java new file mode 100644 index 000000000..01ff1de35 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/StudentTeamTaskService.java @@ -0,0 +1,117 @@ +package com.roomroot.jwgl.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDRWB; + +import java.util.List; + +/** + * 学员队任务表 Service + */ +public interface StudentTeamTaskService { + + /** + * 新增 + * + * @param xydrwb 学员队任务 + */ + void add(XYDRWB xydrwb); + + /** + * 删除 + * + * @param bh 编号 + */ + void delete(String bh); + + /** + * 更新 + * + * @param xydrwb 学员队任务 + */ + void update(XYDRWB xydrwb); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 学员队任务 + */ + XYDRWB getByBh(String bh); + + /** + * 分页查询(按序号标识排序) + * + * @param pageNum 页码 + * @param pageSize 每页数量 + * @param xydrwb 查询条件 + * @return 分页结果 + */ + Page list(int pageNum, int pageSize, XYDRWB xydrwb); + + /** + * 分页查询(按计划课次序号排序) + * + * @param pageNum 页码 + * @param pageSize 每页数量 + * @param xydrwb 查询条件 + * @return 分页结果 + */ + Page groupList(int pageNum, int pageSize, XYDRWB xydrwb); + + /** + * 批量预设合班 + *

+ * 根据编号列表,按年度排序生成统一的计划课次序号 + *

+ * + * @param bhList 编号列表 + * @return 更新数量 + */ + int batchPresetMerge(List bhList); + + /** + * 批量预设拆班 + *

+ * 根据编号列表,清空计划课次序号 + *

+ * + * @param bhList 编号列表 + * @return 更新数量 + */ + int batchPresetSplit(List bhList); + + /** + * 查询所有 + * + * @return 所有学员队任务 + */ + List all(); + + /** + * 自动生成必修课程 + *

+ * 根据学员队编号从学员队表获取专业代号, + * 再根据专业代号和学期第次查询专业教学计划表, + * 为当前班次自动生成必修课程,插入到学员队任务表。 + * 已存在的课程(按课编号判断)不会重复添加。 + *

+ * + * @param xydbh 学员队编号 + * @param xqdc 学期第次 + * @return 新增数量 + */ + int autoGenerateRequiredCourses(String xydbh, Integer xqdc); + + /** + * 批量复制课程 + *

+ * 根据传入的编号集合,复制课程信息,替换学员队编号为新的值 + *

+ * + * @param bhList 源编号集合 + * @param newXydbh 新学员队编号 + * @return 新生成的编号集合 + */ + List copy(List bhList, String newXydbh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/SystemInitializationService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/SystemInitializationService.java new file mode 100644 index 000000000..85c97ff71 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/SystemInitializationService.java @@ -0,0 +1,78 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.dto.systeminitialization.SemesterInitializationSaveDTO; +import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationConfigurationSaveDTO; +import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationExecutionDTO; +import com.roomroot.jwgl.dto.systeminitialization.SystemInitializationHistoricalImportDTO; +import com.roomroot.jwgl.vo.systeminitialization.SemesterInitializationVO; +import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationConfigurationVO; +import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationExecutionResultVO; +import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationHistoricalImportResultVO; +import com.roomroot.jwgl.vo.systeminitialization.SystemInitializationPrecheckVO; + +/** + * 系统初始化管理服务。 + * + *

同一个业务服务统一维护系统基础参数、学期初始化、 + * 历史基础数据导入、预检查和最终执行确认。

+ */ +public interface SystemInitializationService { + + /** + * 查询当前生效的系统基础参数。 + * + * @return 当前系统基础参数 + */ + SystemInitializationConfigurationVO getCurrentConfiguration(); + + /** + * 保存新的系统基础参数版本。 + * + * @param dto 系统基础参数 + * @return 新的当前配置 + */ + SystemInitializationConfigurationVO saveConfiguration( + SystemInitializationConfigurationSaveDTO dto); + + /** + * 查询当前学期。 + * + * @return 当前学期 + */ + SemesterInitializationVO getCurrentSemester(); + + /** + * 保存指定年度和序号的学期。 + * + * @param dto 学期初始化参数 + * @return 保存后的学期 + */ + SemesterInitializationVO saveSemester( + SemesterInitializationSaveDTO dto); + + /** + * 导入历史基础数据。 + * + * @param dto 历史数据 Excel 文件 + * @return 导入结果 + */ + SystemInitializationHistoricalImportResultVO + importHistoricalData( + SystemInitializationHistoricalImportDTO dto); + + /** + * 执行系统初始化预检查。 + * + * @return 预检查汇总结果 + */ + SystemInitializationPrecheckVO precheck(); + + /** + * 完成系统初始化最终确认。 + * + * @param dto 页面预检查快照和确认标志 + * @return 初始化执行结果 + */ + SystemInitializationExecutionResultVO execute( + SystemInitializationExecutionDTO dto); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingMaterialService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingMaterialService.java new file mode 100644 index 000000000..ce86b980a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingMaterialService.java @@ -0,0 +1,84 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JCXX; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.servlet.http.HttpServletResponse; +import java.util.List; + +/** + * 教材信息服务接口 + */ +public interface TeachingMaterialService { + + /** + * 新增教材信息 + * + * @param jcxx 教材信息 + */ + void add(JCXX jcxx); + + /** + * 删除教材信息 + * + * @param id 编号 + */ + void delete(String id); + + /** + * 更新教材信息 + * + * @param jcxx 教材信息 + */ + void update(JCXX jcxx); + + /** + * 根据编号查询 + * + * @param id 唯一标识码 + * @return 教材信息 + */ + JCXX getById(String id); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 教材信息 + */ + JCXX getByBh(String bh); + + /** + * 分页查询教材信息列表 + * + * @param query 分页参数 + * @param jcxx 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, JCXX jcxx); + + /** + * 查询所有教材信息列表 + * + * @param jcxx 查询条件 + * @return 教材信息列表 + */ + List list(JCXX jcxx); + + /** + * 导入Excel文件 + * + * @param file Excel文件 + * @return 导入数量 + */ + int importExcel(MultipartFile file); + + /** + * 下载模板文件 + * + * @param response HTTP响应 + */ + void downloadTemplate(HttpServletResponse response); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingPlanService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingPlanService.java new file mode 100644 index 000000000..7522c0755 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingPlanService.java @@ -0,0 +1,70 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JXSSJH; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * 导入数据表-教学实施计划服务接口 + */ +public interface TeachingPlanService { + + /** + * 新增 + * + * @param drsjbjxssjh 实体 + */ + void add(JXSSJH drsjbjxssjh); + + /** + * 删除 + * + * @param bsh 标识号 + */ + void delete(String bsh); + + /** + * 更新 + * + * @param drsjbjxssjh 实体 + */ + void update(JXSSJH drsjbjxssjh); + + /** + * 根据标识号查询 + * + * @param bsh 标识号 + * @return 实体 + */ + JXSSJH getByBsh(String bsh); + + /** + * 分页查询列表 + * + * @param query 分页参数 + * @param drsjbjxssjh 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, JXSSJH drsjbjxssjh); + + /** + * 查询所有列表 + * + * @param drsjbjxssjh 查询条件 + * @return 列表 + */ + List list(JXSSJH drsjbjxssjh); + + /** + * 从Excel导入教学实施计划数据。 + * 标识号和登录编号自动生成,其他字段从Excel读取。 + * + * @param file Excel文件 + * @return 导入的记录数 + * @throws Exception 解析或导入异常 + */ + int importFromExcel(MultipartFile file) throws Exception; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingStockDetailService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingStockDetailService.java new file mode 100644 index 000000000..8488caa16 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingStockDetailService.java @@ -0,0 +1,75 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JCKCMXX; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.List; + +/** + * 教材库存明细项服务接口 + */ +public interface TeachingStockDetailService { + + /** + * 新增教材库存明细项 + * + * @param jckcmxx 教材库存明细项 + */ + void add(JCKCMXX jckcmxx); + + /** + * 删除教材库存明细项 + * + * @param id 主键 + */ + void delete(String id); + + /** + * 更新教材库存明细项 + * + * @param jckcmxx 教材库存明细项 + */ + void update(JCKCMXX jckcmxx); + + /** + * 根据ID查询 + * + * @param id 主键 + * @return 教材库存明细项 + */ + JCKCMXX getById(String id); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 教材库存明细项 + */ + JCKCMXX getByBh(String bh); + + /** + * 根据教材库存单编号查询 + * + * @param jckcdbbh 教材库存单编号 + * @return 教材库存明细项列表 + */ + List getByJckcdbbh(String jckcdbbh); + + /** + * 分页查询教材库存明细项列表 + * + * @param query 分页参数 + * @param jckcmxx 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, JCKCMXX jckcmxx); + + /** + * 查询所有教材库存明细项列表 + * + * @param jckcmxx 查询条件 + * @return 教材库存明细项列表 + */ + List list(JCKCMXX jckcmxx); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingStockService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingStockService.java new file mode 100644 index 000000000..328929de9 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingStockService.java @@ -0,0 +1,67 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JCKCD; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.List; + +/** + * 教材库存单服务接口 + */ +public interface TeachingStockService { + + /** + * 新增教材库存单 + * + * @param jckcd 教材库存单 + */ + void add(JCKCD jckcd); + + /** + * 删除教材库存单 + * + * @param id 主键 + */ + void delete(String id); + + /** + * 更新教材库存单 + * + * @param jckcd 教材库存单 + */ + void update(JCKCD jckcd); + + /** + * 根据ID查询 + * + * @param id 主键 + * @return 教材库存单 + */ + JCKCD getById(String id); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 教材库存单 + */ + JCKCD getByBh(String bh); + + /** + * 分页查询教材库存单列表 + * + * @param query 分页参数 + * @param jckcd 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, JCKCD jckcd); + + /** + * 查询所有教材库存单列表 + * + * @param jckcd 查询条件 + * @return 教材库存单列表 + */ + List list(JCKCD jckcd); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingTaskService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingTaskService.java new file mode 100644 index 000000000..1723a4226 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TeachingTaskService.java @@ -0,0 +1,74 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.JXRW; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.List; + +/** + * 教学任务服务接口 + */ +public interface TeachingTaskService { + + /** + * 新增 + * + * @param jxrw 实体 + */ + void add(JXRW jxrw); + + /** + * 删除 + * + * @param bh 编号 + */ + void delete(String bh); + + /** + * 批量删除 + * + * @param bhList 编号列表 + */ + void batchDelete(List bhList); + + /** + * 更新 + * + * @param jxrw 实体 + */ + void update(JXRW jxrw); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 实体 + */ + JXRW getByBh(String bh); + + /** + * 分页查询列表 + * + * @param query 分页参数 + * @param jxrw 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, JXRW jxrw); + + /** + * 查询所有有效数据(DEL_FLAG = 0) + * + * @return 所有有效数据列表 + */ + List listAllValid(); + + /** + * 发布教学任务及教研室任务书 + * 根据教学任务编号修改状态为"发布",并批量更新所有关联的教研室任务书状态为"发布" + * + * @param bh 教学任务编号 + * @return 更新数量 + */ + int batchPublish(String bh); +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TrainingProgramService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TrainingProgramService.java new file mode 100644 index 000000000..6ab673595 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/TrainingProgramService.java @@ -0,0 +1,56 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.ZYB; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import org.springframework.stereotype.Service; + +/** + * 培养方案管理服务接口 + *

+ * 提供专业/培养方案的增加、删除(逻辑删除)、更新、分页查询等功能。 + * 对应数据表:专业表 + * 多版本通过培训类型(培训类型)字段区分。 + *

+ */ +@Service +public interface TrainingProgramService { + + /** + * 新增培养方案(专业) + * + * @param entity 专业表实体 + */ + void add(ZYB entity); + + /** + * 逻辑删除(停用)培养方案 + * + * @param zYDH 专业代号 + */ + void disable(String zYDH); + + /** + * 更新培养方案 + * + * @param entity 专业表实体 + */ + void update(ZYB entity); + + /** + * 根据专业代号查询培养方案详情 + * + * @param zYDH 专业代号 + * @return 专业表实体 + */ + ZYB getById(String zYDH); + + /** + * 分页条件查询培养方案列表 + * + * @param query 分页参数 + * @param cond 查询条件 + * @return 分页结果 + */ + PageResult pageList(PageQuery query, ZYB cond); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ZYJXJHBService.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ZYJXJHBService.java new file mode 100644 index 000000000..6fe74b485 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/ZYJXJHBService.java @@ -0,0 +1,63 @@ +package com.roomroot.jwgl.service; + +import com.roomroot.jwgl.entity.ZYJXJHB; + +import java.util.List; + +/** + * 专业教学计划表服务接口 + */ +public interface ZYJXJHBService { + + /** + * 新增 + * + * @param zyjxjhb 实体 + */ + void add(ZYJXJHB zyjxjhb); + + /** + * 删除(设置停用标识为1) + * + * @param bh 编号 + */ + void delete(String bh); + + /** + * 批量删除(设置停用标识为1) + * + * @param bhList 编号列表 + */ + void batchDelete(List bhList); + + /** + * 更新 + * + * @param zyjxjhb 实体 + */ + void update(ZYJXJHB zyjxjhb); + + /** + * 根据编号查询 + * + * @param bh 编号 + * @return 实体 + */ + ZYJXJHB getByBh(String bh); + + /** + * 查询所有数据 + * + * @return 所有数据列表 + */ + List list(); + + /** + * 根据专业代号和停用标识查询 + * + * @param zydh 专业代号 + * @param ty 停用标识(0-正常,1-停用) + * @return 列表 + */ + List listByZydhAndTy(String zydh, Integer ty); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/AccountManagementServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/AccountManagementServiceImpl.java new file mode 100644 index 000000000..790d186cc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/AccountManagementServiceImpl.java @@ -0,0 +1,2103 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.accountmanagement.*; +import com.roomroot.jwgl.entity.DBVersion; +import com.roomroot.jwgl.entity.SSOUserAuthSession; +import com.roomroot.jwgl.entity.accountmanagement.AccountRecord; +import com.roomroot.jwgl.entity.accountmanagement.AccountRoleRecord; +import com.roomroot.jwgl.mapper.AccountManagementMapper; +import com.roomroot.jwgl.service.AccountManagementService; +import com.roomroot.jwgl.unit.AccountLoginFailedException; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.AccountLoginCaptchaUtil; +import com.roomroot.jwgl.utils.PageResultUtil; +import com.roomroot.jwgl.utils.UuidUtil; +import com.roomroot.jwgl.vo.accountmanagement.*; +import com.roomroot.jwgl.vo.accountmanagement.*; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import java.time.LocalDateTime; +import java.util.*; + +import static com.roomroot.jwgl.utils.AccountManagementConstants.*; +import static com.roomroot.jwgl.utils.AccountManagementUtil.*; + +/** + * 账户管理服务实现。 + */ +@Service +public class AccountManagementServiceImpl + implements AccountManagementService { + + /** + * 账户管理专用数据访问对象。 + */ + @Resource + private AccountManagementMapper accountManagementMapper; + + /** + * 系统统一密码编码器。 + */ + @Resource + private PasswordEncoder passwordEncoder; + + /** + * 查询账户列表。 + * + *

处理顺序:校验分页及查询条件、数据库筛选和分页、 + * 批量读取当前页角色、按账户装配独立 VO、返回统一分页结果。

+ * + * @param dto 账户查询条件 + * @return 经过筛选和稳定排序的账户分页结果 + */ + @Override + public PageResult list(AccountQueryDTO dto) { + // 第一步:兼容业务层直接传入 null,并使用 DTO 中定义的默认分页参数。 + AccountQueryDTO query = + dto == null ? new AccountQueryDTO() : dto; + + // 第二步:校验页码,避免绕过控制器参数校验时生成无效分页语句。 + Integer pageNum = query.getPageNum(); + if (pageNum == null || pageNum.intValue() < 1) { + throw new BusinessException(BAD_REQUEST, "页码最小为1"); + } + + // 第三步:校验每页记录数,保持与项目统一分页上限一致。 + Integer pageSize = query.getPageSize(); + if (pageSize == null + || pageSize.intValue() < 1 + || pageSize.intValue() > 100) { + throw new BusinessException( + BAD_REQUEST, "每页大小必须在1到100之间"); + } + + // 第四步:规范化可选关键字,并执行与数据库字段一致的长度校验。 + String keyword = optionalText( + query.getKeyword(), + ACCOUNT_TEXT_MAX_LENGTH, + "查询关键字"); + + // 第五步:规范化可选角色类型,存在值时校验是否属于系统支持类型。 + String roleType = optionalRoleType( + query.getRoleType()); + + // 第六步:规范化可选角色身份编号。 + String targetId = optionalText( + query.getTargetId(), + ACCOUNT_TEXT_MAX_LENGTH, + "角色身份编号"); + + // 第七步:仅填写身份编号而未填写角色类型时无法确定查询范围。 + if (targetId != null && roleType == null) { + throw new BusinessException( + BAD_REQUEST, "填写角色身份编号时必须同时填写角色类型"); + } + + // 第八步:创建分页对象,由达梦分页拦截器生成总数和当前页查询语句。 + Page page = + new Page<>(pageNum.longValue(), pageSize.longValue()); + + // 第九步:在数据库中完成账户、启停状态、角色和关键字筛选。 + Page selectedPage = + accountManagementMapper.selectAccountPage( + page, + keyword, + query.getEnabled(), + roleType, + targetId); + + // 第十步:兼容模拟测试或异常 Mapper 返回 null 分页对象的情况。 + Page resultPage = + selectedPage == null ? page : selectedPage; + + // 第十一步:取得当前页账户,并把 null 集合统一转换为空集合。 + List selectedAccounts = resultPage.getRecords(); + List accounts = selectedAccounts == null + ? Collections.emptyList() + : selectedAccounts; + + // 第十二步:收集有效账户编号,供角色查询一次性批量使用。 + List accountIds = new ArrayList<>(); + for (AccountRecord account : accounts) { + // 空记录或空编号无法关联角色,因此不加入批量查询参数。 + if (account != null && account.getId() != null) { + accountIds.add(account.getId()); + } + } + + // 第十三步:一次查询当前页全部角色,并按账户编号完成分组和排序。 + Map> rolesByAccountId = + selectRolesByAccountIds(accountIds); + + // 第十四步:创建当前页对外账户 VO 集合。 + List records = new ArrayList<>(); + + // 第十五步:逐个账户装配对应角色,并转换为不含敏感字段的 VO。 + for (AccountRecord account : accounts) { + // Mapper 返回的空记录不能转换为页面数据。 + if (account == null) { + continue; + } + + // 未查询到角色时使用空集合,确保 VO 中角色字段始终非 null。 + List roles = + rolesByAccountId.get(account.getId()); + if (roles == null) { + roles = Collections.emptyList(); + } + + // 将内部持久化记录和角色 VO 组合为对外账户 VO。 + records.add(toAccountVO(account, roles)); + } + + // 第十六步:返回项目统一分页结构,总数由分页拦截器统计。 + return PageResultUtil.fromRecords( + resultPage, + records, + pageNum, + pageSize); + } + + /** + * 查询账户详情。 + * + * @param dto 账户编号参数 + * @return 包含角色和数据范围的账户详情 + */ + @Override + public AccountVO get(AccountIdDTO dto) { + // 第一步:兼容业务层直接传入 null 的场景,并校验账户编号。 + String id = requireAccountId( + dto == null ? null : dto.getId(), "账户编号"); + + // 第二步:查询账户基础信息、启停状态和全部角色后返回独立 VO。 + return loadAccountView(id); + } + + /** + * 新增账户并配置初始角色。 + * + *

处理顺序:校验参数、规范化账户资料、检查账号唯一性、 + * 预校验全部角色、写入账户、初始化状态、保存角色、返回最新详情。

+ * + * @param dto 账户新增参数 + * @return 新增后的账户详情 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AccountVO add(AccountCreateDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "新增参数不能为空"); + } + + // 第二步:生成登录信息表主键,后续角色预校验和写入复用同一编号。 + String accountId = UuidUtil.getOriginalUUID(); + + /* + * 第三步:将新增 DTO 转换为内部持久化记录。 + * DTO 仅用于接收请求,AccountRecord 仅在 Mapper 和业务层之间流转。 + */ + AccountRecord account = + buildNewAccount(accountId, dto, passwordEncoder); + + // 第四步:检查登录账号和非空统一账号在其他账户中不存在。 + ensureAccountNamesUnique( + account.getLoginName(), + account.getUnifiedAccount(), + null); + + /* + * 第五步:在写入任何数据前预校验全部初始角色。 + * 这样可以避免角色无效时产生只有账户资料但没有正确角色的半成品数据。 + */ + List roles = + validateRoleAssignments(dto.getRoles(), accountId); + + /* + * 第六步:写入登录信息表,并将并发请求触发的数据库唯一键冲突 + * 转换为与写入前校验一致的 409 业务异常。 + */ + int affectedRows = executeAccountNameUniqueWrite( + () -> accountManagementMapper.insertAccount(account)); + + // 第七步:校验数据库实际影响行数,防止未成功新增唯一目标记录。 + if (affectedRows != 1) { + throw new BusinessException("新增账户失败"); + } + + // 第八步:显式初始化账户为启用状态,并保留独立启停管理能力。 + saveAccountStatus(account); + + // 第九步:在当前事务中保存已经通过完整预校验的初始角色。 + replaceRoleBindings(accountId, roles); + + // 第十步:重新查询数据库并返回不包含密码和密码答案的账户 VO。 + return loadAccountView(accountId); + } + + /** + * 修改账户基本信息。 + * + * @param dto 账户修改参数 + * @return 修改后的账户详情 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AccountVO update(AccountUpdateDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "修改参数不能为空"); + } + + // 第二步:校验账户编号,并确认待修改账户真实存在。 + String accountId = requireAccountId(dto.getId(), "账户编号"); + AccountRecord existingAccount = requireAccount(accountId); + + // 第三步:已合并源账户作为历史记录保留,不再允许修改基本资料。 + ensureNotMerged(existingAccount, "已合并账户不能修改"); + + /* + * 第四步:将修改 DTO 转换为内部持久化记录。 + * 当前对象只设置允许普通修改的字段,不设置密码、状态和登录统计。 + */ + AccountRecord account = buildUpdatedAccount(accountId, dto); + + // 第五步:排除当前账户后检查登录账号和非空统一账号唯一性。 + ensureAccountNamesUnique( + account.getLoginName(), + account.getUnifiedAccount(), + accountId); + + /* + * 第六步:更新账户基本资料,并将并发请求触发的数据库唯一键冲突 + * 转换为与写入前校验一致的 409 业务异常。 + */ + int affectedRows = executeAccountNameUniqueWrite( + () -> accountManagementMapper.updateAccount(account)); + + // 第七步:校验数据库实际影响行数,防止覆盖已经变化的账户资料。 + if (affectedRows != 1) { + throw new BusinessException( + CONFLICT, "账户信息已经发生变化,请刷新后重试"); + } + + /* + * 第八步:登录账号发生变化时清理旧账号和新账号的在线会话。 + * 这样可以避免在线记录继续保存旧账号,并要求用户使用新账号重新登录。 + */ + String oldLoginName = + normalizeText(existingAccount.getLoginName()); + String newLoginName = normalizeText(account.getLoginName()); + if (!newLoginName.equals(oldLoginName)) { + clearOnlineSessions(oldLoginName, newLoginName); + } + + // 第九步:重新查询并返回修改后的完整账户 VO。 + return loadAccountView(accountId); + } + + /** + * 停用账户。 + * + * @param dto 账户停用参数 + * @return 停用后的账户详情 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AccountVO disable(AccountDisableDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "停用参数不能为空"); + } + + // 第二步:校验账户编号,并确认账户真实存在。 + String accountId = requireAccountId(dto.getId(), "账户编号"); + AccountRecord account = requireAccount(accountId); + + // 第三步:禁止当前登录管理员停用自己的账户,避免管理员立即失去操作能力。 + ensureNotCurrentSessionAccount( + accountId, "不能停用当前登录账户"); + + /* + * 第四步:已经停用时仍清理可能残留的在线会话,然后返回最新数据。 + * 这样既保持状态修改幂等,也能修复旧版本或异常流程留下的在线记录。 + */ + if (!isAccountEnabled(account)) { + clearOnlineSessions(account.getLoginName()); + return loadAccountView(accountId); + } + + // 第五步:规范化可选停用原因,并校验状态表字段长度。 + String reason = optionalText( + dto.getReason(), REASON_MAX_LENGTH, "停用原因"); + + /* + * 第六步:只维护专用账户状态,不修改失败次数、失败时间或密码。 + * 登录失败字段只表示临时登录失败状态,不能承担永久停用语义。 + */ + account.setEnabled(Boolean.FALSE); + account.setDisabledReason(reason); + account.setDisabledAt(LocalDateTime.now()); + + // 第七步:新增或更新账户状态记录,并检查数据库实际影响行数。 + saveAccountStatus(account); + + // 第八步:清理该登录账号的全部在线会话,使停用状态立即生效。 + clearOnlineSessions(account.getLoginName()); + + // 第九步:重新查询并返回停用后的账户详情。 + return loadAccountView(accountId); + } + + /** + * 启用普通停用账户。 + * + * @param dto 账户编号参数 + * @return 启用后的账户详情 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AccountVO enable(AccountIdDTO dto) { + // 第一步:校验账户编号,并确认账户真实存在。 + String accountId = requireAccountId( + dto == null ? null : dto.getId(), "账户编号"); + AccountRecord account = requireAccount(accountId); + + /* + * 第二步:合并源账户必须永久保持停用。 + * 重新启用会让同一个历史用户同时拥有源账户和目标账户,破坏合并语义。 + */ + ensureNotMerged(account, "已合并账户不能重新启用"); + + // 第三步:已经启用时直接返回最新数据,保证重复启用请求幂等。 + if (isAccountEnabled(account)) { + return loadAccountView(accountId); + } + + // 第四步:恢复启用状态,并清除普通停用原因和停用时间。 + account.setEnabled(Boolean.TRUE); + account.setDisabledReason(null); + account.setDisabledAt(null); + + // 第五步:新增或更新账户状态记录,并检查数据库实际影响行数。 + saveAccountStatus(account); + + // 第六步:重新查询并返回启用后的账户详情。 + return loadAccountView(accountId); + } + + /** + * 重置账户密码。 + * + * @param dto 密码重置参数 + * @return 密码重置后的账户详情 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AccountVO resetPassword(AccountPasswordResetDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException( + BAD_REQUEST, "密码重置参数不能为空"); + } + + // 第二步:校验账户编号,并确认待重置账户真实存在。 + String accountId = requireAccountId(dto.getId(), "账户编号"); + AccountRecord account = requireAccount(accountId); + + // 第三步:已合并源账户不再承担登录职责,因此不允许重置密码。 + ensureNotMerged(account, "已合并账户不能重置密码"); + + // 第四步:校验必填新密码的长度、字节数和复杂度。 + String password = requiredPassword(dto.getNewPassword(), "重置密码"); + + /* + * 第五步:使用 BCrypt 编码新密码。 + * 随机盐由编码器自动生成,相同原始密码每次产生的摘要也不相同。 + */ + String encodedPassword = passwordEncoder.encode(password); + + // 第六步:写入密码摘要并清除临时登录失败次数。 + if (accountManagementMapper.resetPassword( + accountId, encodedPassword) != 1) { + throw new BusinessException( + CONFLICT, "账户信息已经发生变化,请刷新后重试"); + } + + /* + * 第七步:清理该账户的全部在线会话。 + * 密码重置后必须重新认证,避免原会话继续使用已经失效的认证状态。 + */ + clearOnlineSessions(account.getLoginName()); + + // 第八步:重新查询并返回不包含密码的账户详情。 + return loadAccountView(accountId); + } + + /** + * 当前登录账户修改自己的密码。 + * + *

账户身份只从服务端 Servlet Session 和在线记录中取得, + * 不允许客户端指定账户编号;修改成功后清理该账户全部在线会话。

+ * + * @param dto 原密码、新密码和确认密码 + * @return 密码修改后的脱敏账户详情 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AccountVO changePassword(AccountPasswordChangeDTO dto) { + // 第一步:防止业务层被直接调用时传入空参数。 + if (dto == null) { + throw new BusinessException( + BAD_REQUEST, "密码修改参数不能为空"); + } + + /* + * 第二步:从当前 Servlet Session 对应的在线记录读取真实账户编号。 + * 不使用请求 DTO 传递账户编号,避免越权修改其他账户密码。 + */ + String accountId = requireCurrentSessionAccountId(); + + // 第三步:查询当前账户完整内部记录,供状态和原密码校验使用。 + AccountRecord account = requireAccount(accountId); + + /* + * 第四步:停用账户或已经合并的源账户不能继续承担登录职责。 + * 拦截器已经执行一次检查,业务层仍保留校验以防止被内部代码绕过。 + */ + if (!isAccountEnabled(account)) { + throw new BusinessException( + CONFLICT, ACCOUNT_UNAVAILABLE_MESSAGE); + } + ensureNotMerged(account, "已合并账户不能修改密码"); + + // 第五步:校验原密码的必填和长度边界,但不执行新密码复杂度检查。 + String currentPassword = requiredExistingPassword( + dto.getCurrentPassword(), "原密码"); + + /* + * 第六步:使用项目统一 PasswordEncoder 校验原密码。 + * 密码摘要不存在或比较失败时只返回原密码错误,不暴露内部摘要信息。 + */ + if (account.getPassword() == null + || !passwordEncoder.matches( + currentPassword, account.getPassword())) { + throw new BusinessException( + BAD_REQUEST, "原密码错误"); + } + + // 第七步:按照统一长度、字节数和复杂度规则校验新密码。 + String newPassword = + requiredPassword(dto.getNewPassword(), "新密码"); + + // 第八步:对确认密码执行相同的完整规则校验,兼容业务层直接调用。 + String confirmPassword = + requiredPassword(dto.getConfirmPassword(), "确认密码"); + + // 第九步:新密码和确认密码必须逐字符完全一致。 + if (!newPassword.equals(confirmPassword)) { + throw new BusinessException( + BAD_REQUEST, "新密码和确认密码不一致"); + } + + // 第十步:新密码不能与当前密码相同,避免无效的密码修改操作。 + if (passwordEncoder.matches( + newPassword, account.getPassword())) { + throw new BusinessException( + CONFLICT, "新密码不能与原密码相同"); + } + + /* + * 第十一步:使用 BCrypt 编码新密码。 + * 编码器自动生成随机盐,业务层和数据库均不保存明文密码。 + */ + String encodedPassword = + passwordEncoder.encode(newPassword); + + /* + * 第十二步:复用现有密码更新语句写入新摘要, + * 同时清除旧密码登录产生的连续失败次数和失败时间。 + */ + if (accountManagementMapper.resetPassword( + accountId, encodedPassword) != 1) { + throw new BusinessException( + CONFLICT, "账户信息已经发生变化,请刷新后重试"); + } + + /* + * 第十三步:删除当前账户全部在线记录。 + * 当前请求可以正常返回,后续请求会被拦截器识别为会话失效并要求重新登录。 + */ + clearOnlineSessions(account.getLoginName()); + + // 第十四步:返回不包含密码摘要和密码答案的最新账户信息。 + return loadAccountView(accountId); + } + + /** + * 查询当前登录账户的登录信息和成功登录历史。 + * + *

处理顺序:校验分页参数、从服务端会话取得账户编号、 + * 查询当前账户登录统计、分页读取该账户成功登录记录、装配独立 VO。

+ * + * @param dto 登录历史分页参数 + * @return 当前账户的登录统计和成功登录历史 + */ + @Override + public AccountLoginInformationVO getLoginInformation( + AccountLoginInformationQueryDTO dto) { + /* + * 第一步:兼容业务层被直接调用时传入 null。 + * 空参数使用 DTO 定义的默认页码和每页记录数。 + */ + AccountLoginInformationQueryDTO query = dto == null + ? new AccountLoginInformationQueryDTO() : dto; + + // 第二步:校验页码,避免绕过控制器参数校验时生成无效分页语句。 + Integer pageNum = query.getPageNum(); + if (pageNum == null || pageNum.intValue() < 1) { + throw new BusinessException(BAD_REQUEST, "页码最小为1"); + } + + // 第三步:校验每页记录数,并保持与账户管理其他分页接口相同的上限。 + Integer pageSize = query.getPageSize(); + if (pageSize == null + || pageSize.intValue() < 1 + || pageSize.intValue() > 100) { + throw new BusinessException( + BAD_REQUEST, "每页大小必须在1到100之间"); + } + + /* + * 第四步:只从服务端当前会话和在线记录取得真实账户编号。 + * 客户端不能通过查询参数指定其他账户。 + */ + String accountId = requireCurrentSessionAccountId(); + + // 第五步:查询当前账户资料和上次、本次登录时间,并确认账户仍然存在。 + AccountRecord account = requireAccount(accountId); + + // 第六步:创建登录历史分页对象,由分页拦截器生成总数和当前页语句。 + Page page = + new Page<>(pageNum.longValue(), pageSize.longValue()); + + // 第七步:在数据库中只查询当前账户的成功登录记录。 + Page selectedPage = + accountManagementMapper.selectLoginHistoryPage( + page, accountId); + + // 第八步:兼容模拟测试或异常 Mapper 返回空分页对象的情况。 + Page resultPage = + selectedPage == null ? page : selectedPage; + + // 第九步:将空记录集合统一转换为空集合,保证分页返回结构稳定。 + List selectedRecords = + resultPage.getRecords(); + List records = + selectedRecords == null + ? Collections.emptyList() + : selectedRecords; + + // 第十步:创建独立汇总 VO,不直接向页面暴露内部账户持久化对象。 + AccountLoginInformationVO result = + new AccountLoginInformationVO(); + + // 第十一步:复制当前账户展示所需的身份和登录统计字段。 + result.setAccountId(account.getId()); + result.setLoginName(account.getLoginName()); + result.setUserName(account.getUserName()); + result.setLastLoginAt(account.getLastLoginAt()); + result.setCurrentLoginAt(account.getCurrentLoginAt()); + + // 第十二步:使用项目统一分页结构封装当前账户的成功登录历史。 + result.setLoginHistory(PageResultUtil.fromRecords( + resultPage, + records, + pageNum, + pageSize)); + + // 第十三步:返回当前账户的登录信息和登录历史。 + return result; + } + + /** + * 生成本地账户登录使用的一次性图形验证码。 + * + *

验证码答案只以摘要形式保存在当前 Servlet Session, + * 页面只能取得验证码编号和图片;新验证码会覆盖同一 Session 中的旧验证码。

+ * + * @return 验证码编号、PNG 图片数据和有效秒数 + */ + @Override + public AccountLoginCaptchaVO generateLoginCaptcha() { + /* + * 调用验证码工具类完成字符生成、图片绘制、摘要计算和 Session 保存, + * 业务实现层只负责返回工具类生成的独立验证码 VO。 + */ + return AccountLoginCaptchaUtil.generate(); + } + + /** + * 使用本地账号密码建立账户管理登录状态。 + * + *

本地登录完成账号密码校验、账户状态检查、登录统计维护、 + * 一次性验证码校验、账户临时锁定、角色读取和本地会话建立。

+ * + * @param dto 本地账号密码登录参数 + * @return 脱敏后的本地账户登录结果 + */ + @Override + @Transactional( + rollbackFor = Exception.class, + noRollbackFor = AccountLoginFailedException.class) + public AccountLoginVO login(AccountLoginDTO dto) { + // 第一步:规范化登录账号,但不改变密码原始内容。 + String loginName = requiredText( + dto == null ? null : dto.getLoginName(), + ACCOUNT_TEXT_MAX_LENGTH, + "登录账号"); + + // 第二步:校验密码是否存在和长度有效,密码本身不执行 trim。 + String password = requiredLoginPassword( + dto == null ? null : dto.getPassword()); + + /* + * 第三步:校验并立即消费当前 Session 中的一次性验证码。 + * 验证码校验在账户查询和密码摘要比较前完成,减少自动化密码尝试。 + * 验证码编号为空时跳过校验,兼容不使用验证码的登录接口。 + */ + String captchaId = dto == null ? null : dto.getCaptchaId(); + if (captchaId != null && !captchaId.trim().isEmpty()) { + AccountLoginCaptchaUtil.validateAndConsume( + captchaId, + dto == null ? null : dto.getCaptchaCode()); + } + + // 第四步:按忽略大小写和首尾空格的规则查询完整内部账户记录。 + AccountRecord account = + accountManagementMapper.selectAccountByLoginName(loginName); + + // 第五步:账号不存在时返回与密码错误相同的提示,避免泄露账号是否存在。 + if (account == null) { + throw new BusinessException( + BAD_REQUEST, ACCOUNT_LOGIN_FAILED_MESSAGE); + } + + // 第六步:旧账户没有专用状态记录时按启用处理。 + if (account.getEnabled() == null) { + account.setEnabled(Boolean.TRUE); + } + + /* + * 第七步:停用账户和已经合并的源账户不允许继续建立本地登录状态。 + * 当前项目没有完整 RBAC 权限体系,因此账户管理接口仍通过现有角色类别拦截机制保护。 + */ + if (!isAccountEnabled(account) + || normalizeText(account.getMergedToAccountId()) != null + || account.getMergedAt() != null) { + throw new BusinessException( + CONFLICT, ACCOUNT_UNAVAILABLE_MESSAGE); + } + + /* + * 第八步:读取系统版本表中的本地密码尝试次数和锁定分钟数。 + * 参数缺失或非正数时按未启用临时锁定处理,兼容没有配置该功能的旧数据库。 + */ + DBVersion loginSecurityPolicy = + accountManagementMapper.selectLoginSecurityPolicy(); + Integer allowedAttempts = loginSecurityPolicy == null + ? null : loginSecurityPolicy.getYXMMCSCS(); + Integer lockMinutes = loginSecurityPolicy == null + ? null : loginSecurityPolicy.getYHSDFZS(); + boolean temporaryLockEnabled = + isPositive(allowedAttempts) && isPositive(lockMinutes); + + // 第九步:固定本次认证判断时间,保证锁定到期判断和失败记录使用同一时间基准。 + LocalDateTime authenticationTime = LocalDateTime.now(); + + if (temporaryLockEnabled) { + /* + * 第十步:先原子清理已经到期的锁定状态。 + * 截止时间等于当前认证时间减去配置的锁定分钟数。 + */ + LocalDateTime expiredBefore = + authenticationTime.minusMinutes( + lockMinutes.longValue()); + int clearedRows = + accountManagementMapper.clearExpiredLoginFailureState( + account.getId(), + allowedAttempts, + expiredBefore); + + // 第十一步:唯一账户最多只能清理一条失败状态,异常行数表示数据已经不一致。 + if (clearedRows < 0 || clearedRows > 1) { + throw new BusinessException( + CONFLICT, "账户登录失败状态异常,请重试"); + } + + // 第十二步:数据库已经清除到期锁定时,同步当前内存记录并从零重新计数。 + if (clearedRows == 1) { + account.setFailedAttempts(Integer.valueOf(0)); + account.setFailedAt(null); + } + + /* + * 第十三步:达到允许尝试次数且最近失败时间仍晚于到期截止点时拒绝登录。 + * 锁定判断发生在密码摘要比较之前,锁定期内不会继续消耗密码校验资源。 + */ + if (isAccountTemporarilyLocked( + account, allowedAttempts, expiredBefore)) { + throw new BusinessException( + CONFLICT, ACCOUNT_TEMPORARILY_LOCKED_MESSAGE); + } + } + + /* + * 第十四步:使用项目统一 PasswordEncoder 比较原始密码和 BCrypt 摘要。 + * 不在业务层自行解密或比较明文密码,也不把密码摘要返回给页面。 + */ + if (account.getPassword() == null + || !passwordEncoder.matches( + password, account.getPassword())) { + /* + * 第十五步:密码错误时原子累计失败次数并记录失败时间。 + * 专用登录失败异常不会回滚当前更新,其他业务异常仍按原事务规则回滚。 + */ + int affectedRows = + accountManagementMapper.recordLoginFailure( + account.getId(), authenticationTime); + if (affectedRows != 1) { + throw new BusinessException( + CONFLICT, "登录失败状态记录失败,请重试"); + } + + /* + * 第十六步:本次失败达到允许尝试次数时立即提示账户已经临时锁定。 + * 未启用临时锁定或尚未达到阈值时继续使用统一账号密码错误提示。 + */ + int previousFailedAttempts = + account.getFailedAttempts() == null + ? 0 : Math.max( + account.getFailedAttempts().intValue(), 0); + if (temporaryLockEnabled + && previousFailedAttempts + 1 + >= allowedAttempts.intValue()) { + throw new AccountLoginFailedException( + ACCOUNT_TEMPORARILY_LOCKED_MESSAGE); + } + + // 第十七步:返回统一错误提示,不向调用方泄露密码校验细节。 + throw new AccountLoginFailedException(); + } + + // 第十八步:查询账户已有角色,供在线记录单位和登录结果 VO 使用。 + List roles = selectRoles(account.getId()); + + /* + * 第十九步:建立本项目自己的 Servlet Session、在线记录和成功登录统计。 + * 使用账户管理超级管理员角色类别兼容当前拦截器;这不代表项目已经建立完整 RBAC。 + */ + establishLocalLoginSession( + account, + roles, + ACCOUNT_ADMINISTRATOR_ROLE_CATEGORY, + null); + + // 第二十步:只返回账户标识、展示信息和角色,不返回任何敏感认证字段。 + return toAccountLoginVO(account, roles); + } + + /** + * 校验统一身份认证会话并解析唯一的本地账户。 + * + *

处理顺序:校验会话标识、查询并检查有效期、整理 LoginID 和 UserID + * 候选值、按本地统一账号查询、阻止跨账户歧义、检查账户状态、 + * 建立本地 Servlet 登录状态、装配不包含敏感字段的返回 VO。

+ * + * @param dto SSO 会话解析参数 + * @return SSO 会话及其对应的本地账户信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AccountSsoSessionVO resolveSsoSession( + AccountSsoSessionResolveDTO dto) { + // 第一步:兼容业务层被直接调用时传入 null,并校验会话标识长度。 + String sessionKey = requiredText( + dto == null ? null : dto.getSessionKey(), + ACCOUNT_TEXT_MAX_LENGTH, + "SSO会话标识"); + + // 第二步:根据 SSO 会话主键读取服务端保存的可信认证记录。 + SSOUserAuthSession ssoSession = + accountManagementMapper.selectSsoSessionBySessionKey( + sessionKey); + + // 第三步:不存在的会话不能用于解析本地账户。 + if (ssoSession == null) { + throw new BusinessException( + NOT_FOUND, "SSO会话不存在"); + } + + /* + * 第四步:校验 SSO 会话有效期。 + * 有效期缺失、等于当前时间或早于当前时间时均视为已经失效。 + */ + LocalDateTime invalidTime = ssoSession.getInvalidTime(); + if (invalidTime == null + || !invalidTime.isAfter(LocalDateTime.now())) { + throw new BusinessException( + CONFLICT, "SSO会话已失效"); + } + + /* + * 第五步:同时整理 LoginID 和 UserID。 + * 当前资料没有说明哪一个字段固定对应本地统一账号,因此不能只选其一。 + */ + List unifiedAccountCandidates = + collectSsoUnifiedAccountCandidates(ssoSession); + + // 第六步:SSO 会话没有有效身份候选值时拒绝继续解析。 + if (unifiedAccountCandidates.isEmpty()) { + throw new BusinessException( + CONFLICT, "SSO会话缺少可识别的统一身份标识"); + } + + // 第七步:只使用本地登录信息表的统一账号字段查询候选账户。 + List selectedAccounts = + accountManagementMapper.selectAccountsByUnifiedAccounts( + unifiedAccountCandidates); + + /* + * 第八步:按本地账户编号去重。 + * LoginID 和 UserID 相同或同时指向同一账户时只能保留一个结果。 + */ + Map accountsById = + new LinkedHashMap<>(); + if (selectedAccounts != null) { + for (AccountRecord account : selectedAccounts) { + // 空记录或空账户编号不能作为可信的本地账户解析结果。 + if (account == null + || normalizeText(account.getId()) == null) { + continue; + } + + // 同一账户的重复查询行只保留第一条稳定结果。 + accountsById.putIfAbsent(account.getId(), account); + } + } + + // 第九步:没有候选值匹配本地统一账号时返回明确的未绑定提示。 + if (accountsById.isEmpty()) { + throw new BusinessException( + NOT_FOUND, "SSO身份尚未绑定本地账户"); + } + + /* + * 第十步:两个 SSO 身份候选值指向不同账户时拒绝自动选择。 + * 该校验可以防止未知字段映射规则导致用户被解析成他人账户。 + */ + if (accountsById.size() > 1) { + throw new BusinessException( + CONFLICT, + "SSO身份标识对应多个本地账户,请管理员核对统一账号"); + } + + // 第十一步:取得经过唯一性校验的本地账户。 + AccountRecord account = + accountsById.values().iterator().next(); + + // 第十二步:兼容没有专用状态记录的旧账户,并统一按启用状态处理。 + if (account.getEnabled() == null) { + account.setEnabled(Boolean.TRUE); + } + + /* + * 第十三步:停用账户和已合并源账户不能通过 SSO 恢复访问能力。 + * 合并状态同时检查目标编号和合并时间,兼容不完整的历史状态记录。 + */ + if (!isAccountEnabled(account) + || normalizeText(account.getMergedToAccountId()) != null + || account.getMergedAt() != null) { + throw new BusinessException( + CONFLICT, "SSO身份对应的本地账户已停用或已合并"); + } + + // 第十四步:查询本地账户全部角色,供后续角色选择和在线会话创建使用。 + List roles = + selectRoles(account.getId()); + + // 第十五步:使用 SSO 角色类别建立当前请求的本地 Servlet 登录状态。 + establishLocalLoginSession( + account, + roles, + ssoSession.getUserType(), + ssoSession.getIpAddress()); + + // 第十六步:装配不包含密码和 SSO 会话标识的独立返回 VO。 + return toAccountSsoSessionVO( + ssoSession, account, roles); + } + + /** + * 退出当前 HTTP 会话。 + * + *

退出流程同时清理数据库在线记录和 Servlet Session,避免只清理其中一处造成残留登录状态。

+ */ + @Override + @Transactional(rollbackFor = Exception.class) + public void logout() { + /* + * 第一步:读取当前请求上下文。 + * 没有 HTTP 请求时按幂等退出处理,不主动创建新的登录会话。 + */ + ServletRequestAttributes attributes = + (ServletRequestAttributes) RequestContextHolder + .getRequestAttributes(); + if (attributes == null) { + return; + } + + // 第二步:取得当前请求,并且只读取已经存在的 Servlet Session。 + HttpServletRequest request = attributes.getRequest(); + HttpSession session = request.getSession(false); + if (session == null) { + return; + } + + // 第三步:记录当前 SessionId,供数据库删除在线记录使用。 + String sessionId = session.getId(); + + /* + * 第四步:删除当前会话对应的在线记录。 + * 删除不到记录时也保持幂等,不把重复退出视为业务异常。 + */ + accountManagementMapper.deleteOnlineSessionBySessionId(sessionId); + + // 第五步:作废 Servlet Session,阻止浏览器继续携带有效本地登录状态。 + invalidateSession(session); + } + + /** + * 查询系统支持的全部角色类型。 + * + * @return 按页面展示顺序排列的角色类型列表 + */ + @Override + public List listRoleTypes() { + // 第一步:创建与常量定义数量一致的角色类型返回集合。 + List result = + new ArrayList<>(ROLE_TYPES.size()); + + // 第二步:按照常量工具类维护的固定页面顺序转换角色类型。 + for (String roleType : ROLE_TYPES) { + // 为每个角色类型创建独立 VO,避免向页面暴露可变常量结构。 + AccountRoleTypeVO vo = new AccountRoleTypeVO(); + vo.setCode(roleType); + vo.setName(ROLE_NAMES.get(roleType)); + result.add(vo); + } + + // 第三步:返回七类角色类型选项。 + return result; + } + + /** + * 查询指定角色类型下可供配置的身份。 + * + * @param dto 角色身份查询条件 + * @return 经过数据库筛选、去重和稳定排序的角色身份分页结果 + */ + @Override + public PageResult listRoleOptions( + AccountRoleOptionQueryDTO dto) { + // 第一步:角色类型为必填条件,因此不接受空查询对象。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "查询参数不能为空"); + } + + // 第二步:校验页码,避免业务层被直接调用时生成无效分页语句。 + Integer pageNum = dto.getPageNum(); + if (pageNum == null || pageNum.intValue() < 1) { + throw new BusinessException(BAD_REQUEST, "页码最小为1"); + } + + // 第三步:校验每页记录数,并限制单次身份查询最多返回100条。 + Integer pageSize = dto.getPageSize(); + if (pageSize == null + || pageSize.intValue() < 1 + || pageSize.intValue() > 100) { + throw new BusinessException( + BAD_REQUEST, "每页大小必须在1到100之间"); + } + + // 第四步:规范化并校验角色类型。 + String roleType = requireRoleType(dto.getRoleType()); + + // 第五步:规范化可选关键字,并校验最大长度。 + String keyword = optionalText( + dto.getKeyword(), + ACCOUNT_TEXT_MAX_LENGTH, + "查询关键字"); + + // 第六步:规范化当前正在配置的可选账户编号。 + String accountId = optionalAccountId( + dto.getAccountId(), "当前账户编号"); + + // 第七步:填写当前账户编号时确认该账户真实存在。 + if (accountId != null) { + requireAccount(accountId); + } + + // 第八步:创建分页对象,由达梦分页拦截器生成总数和当前页查询语句。 + Page page = + new Page<>(pageNum.longValue(), pageSize.longValue()); + + /* + * 第九步:在数据库中完成关键字、绑定状态筛选和异常重复数据去重。 + * 这样不会再把指定角色类型的全部身份加载到应用内存后处理。 + */ + Page selectedPage = + accountManagementMapper.selectRoleOptionPage( + page, + roleType, + keyword, + dto.getUnboundOnly(), + accountId); + + // 第十步:兼容模拟测试或异常 Mapper 返回空分页对象的情况。 + Page resultPage = + selectedPage == null ? page : selectedPage; + + // 第十一步:将空记录集合统一转换为空集合,保证返回分页结构稳定。 + List selectedOptions = + resultPage.getRecords(); + List options = selectedOptions == null + ? Collections.emptyList() + : selectedOptions; + + // 第十二步:补充统一角色名称,避免七类身份 SQL 重复维护中文名称。 + for (AccountRoleOptionVO option : options) { + // 空记录无法作为页面角色身份选项,直接跳过名称补充。 + if (option == null) { + continue; + } + option.setRoleType(roleType); + option.setRoleName(ROLE_NAMES.get(roleType)); + } + + // 第十三步:返回项目统一分页结构。 + return PageResultUtil.fromRecords( + resultPage, + options, + pageNum, + pageSize); + } + + /** + * 查询账户已经绑定的角色和数据范围。 + * + * @param dto 账户编号参数 + * @return 账户角色列表 + */ + @Override + public List listRoles(AccountIdDTO dto) { + // 第一步:校验账户编号,并确认账户真实存在。 + String accountId = requireAccountId( + dto == null ? null : dto.getId(), "账户编号"); + requireAccount(accountId); + + // 第二步:查询、补充名称并稳定排序后返回账户全部角色。 + return selectRoles(accountId); + } + + /** + * 整体替换账户角色和数据范围。 + * + * @param dto 账户角色配置参数 + * @return 配置完成后的账户详情 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AccountVO configureRoles(AccountRoleConfigurationDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException( + BAD_REQUEST, "角色配置参数不能为空"); + } + + // 第二步:校验账户编号,并确认待配置账户真实存在。 + String accountId = + requireAccountId(dto.getAccountId(), "账户编号"); + AccountRecord account = requireAccount(accountId); + + // 第三步:已合并源账户不能重新获得角色和数据范围。 + ensureNotMerged(account, "已合并账户不能配置角色"); + + /* + * 第四步:在删除任何原有绑定前完整预校验全部新角色。 + * 任一身份不存在、不可用、重复或已绑定其他账户时均不会改动原数据。 + */ + List roles = + validateRoleAssignments(dto.getRoles(), accountId); + + // 第五步:在同一事务中整体替换账户的七类角色绑定。 + replaceRoleBindings(accountId, roles); + + /* + * 第六步:角色和数据范围改变后清理该账户的在线会话。 + * 用户重新登录后才能按照最新角色生成在线身份,避免沿用旧权限。 + */ + clearOnlineSessions(account.getLoginName()); + + // 第七步:重新查询并返回配置完成后的账户详情。 + return loadAccountView(accountId); + } + + /** + * 将源账户角色迁移到目标账户并停用源账户。 + * + *

源账户不物理删除,因为登录信息编号已经被多张历史业务表引用。 + * 合并仅迁移可管理的身份绑定,并在专用状态表中记录目标账户和时间。

+ * + * @param dto 账户合并参数 + * @return 源账户和目标账户的最新信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public AccountMergeResultVO merge(AccountMergeDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "合并参数不能为空"); + } + + // 第二步:分别校验并规范化源账户和目标账户编号。 + String sourceAccountId = requireAccountId( + dto.getSourceAccountId(), "源账户编号"); + String targetAccountId = requireAccountId( + dto.getTargetAccountId(), "目标账户编号"); + + // 第三步:源账户与目标账户相同时不存在实际合并意义。 + if (sourceAccountId.equals(targetAccountId)) { + throw new BusinessException( + BAD_REQUEST, "源账户和目标账户不能相同"); + } + + // 第四步:确认源账户和目标账户都真实存在。 + AccountRecord sourceAccount = requireAccount(sourceAccountId); + AccountRecord targetAccount = requireAccount(targetAccountId); + + // 第五步:禁止当前登录账户作为合并源账户,避免当前会话被立即停用。 + ensureNotCurrentSessionAccount( + sourceAccountId, "不能将当前登录账户作为合并源账户"); + + // 第六步:已经合并过的源账户不能再次迁移角色。 + ensureNotMerged(sourceAccount, "源账户已经完成合并"); + + // 第七步:目标账户不能是另一次合并留下的历史源账户。 + ensureNotMerged(targetAccount, "目标账户已经被合并到其他账户"); + + // 第八步:目标账户必须处于启用状态,才能接收登录角色。 + if (!isAccountEnabled(targetAccount)) { + throw new BusinessException( + CONFLICT, "目标账户已停用,不能接收账户合并"); + } + + // 第九步:规范化可选合并原因。 + String reason = optionalText( + dto.getReason(), REASON_MAX_LENGTH, "合并原因"); + + /* + * 第十步:在数据库更新前检查合并后的角色数量和单身份角色约束。 + * 这样可以提前返回清晰业务错误,而不是依赖数据库约束异常回滚。 + */ + List sourceRoles = selectRoles(sourceAccountId); + List targetRoles = selectRoles(targetAccountId); + ensureMergeRoleCapacity(sourceRoles, targetRoles); + + /* + * 第十一步:逐类迁移源账户角色。 + * 当前方法运行在同一事务中,任意类型迁移失败都会回滚此前迁移结果。 + */ + for (String roleType : ROLE_TYPES) { + accountManagementMapper.moveRoleBindings( + roleType, sourceAccountId, targetAccountId); + } + + // 第十二步:记录统一的合并时间,保证停用时间和合并时间一致。 + LocalDateTime mergedAt = LocalDateTime.now(); + + // 第十三步:未填写原因时生成可识别目标账号的默认合并说明。 + if (reason == null) { + reason = "账户已合并至" + + displayAccountName(targetAccount); + } + + /* + * 第十四步:停用并标记源账户的合并目标。 + * 源账户基础记录继续保留,其他历史业务表无需级联修改。 + */ + sourceAccount.setEnabled(Boolean.FALSE); + sourceAccount.setDisabledReason(reason); + sourceAccount.setDisabledAt(mergedAt); + sourceAccount.setMergedToAccountId(targetAccountId); + sourceAccount.setMergedAt(mergedAt); + saveAccountStatus(sourceAccount); + + /* + * 第十五步:同时清理源账户和目标账户的在线会话。 + * 源账户已经停用,目标账户的角色和数据范围也已经发生变化, + * 两者都必须重新认证后才能使用最新账户状态。 + */ + clearOnlineSessions( + sourceAccount.getLoginName(), + targetAccount.getLoginName()); + + // 第十六步:重新查询源账户和已接收全部角色的目标账户。 + AccountMergeResultVO result = new AccountMergeResultVO(); + result.setSourceAccount(loadAccountView(sourceAccountId)); + result.setTargetAccount(loadAccountView(targetAccountId)); + + // 第十七步:返回账户合并后的完整结果。 + return result; + } + + /** + * 检查登录账号和统一账号唯一性。 + * + * @param loginName 登录账号 + * @param unifiedAccount 统一账号;允许为空 + * @param excludeId 修改时排除的当前账户编号 + */ + private void ensureAccountNamesUnique( + String loginName, + String unifiedAccount, + String excludeId) { + // 第一步:检查登录账号是否已经被其他账户使用。 + if (accountManagementMapper.countByLoginName( + loginName, excludeId) > 0) { + throw new BusinessException(CONFLICT, "登录账号已存在"); + } + + // 第二步:未填写统一账号时不执行统一账号唯一性查询。 + if (unifiedAccount == null) { + return; + } + + // 第三步:检查非空统一账号是否已经被其他账户使用。 + if (accountManagementMapper.countByUnifiedAccount( + unifiedAccount, excludeId) > 0) { + throw new BusinessException(CONFLICT, "统一账号已存在"); + } + } + + /** + * 完整预校验并规范化角色配置。 + * + * @param assignments 原始角色配置 + * @param accountId 待绑定账户编号 + * @return 可以安全写入的规范化角色配置 + */ + private List validateRoleAssignments( + List assignments, + String accountId) { + // 第一步:未提供角色列表时按照空配置处理。 + if (assignments == null) { + return new ArrayList<>(); + } + + // 第二步:限制单账户角色数量,避免超大请求长时间占用事务。 + if (assignments.size() > ROLE_ASSIGNMENT_MAX_SIZE) { + throw new BusinessException( + BAD_REQUEST, "单个账户最多配置500个角色身份"); + } + + // 第三步:创建角色重复键集合和规范化结果集合。 + Set bindingKeys = new HashSet<>(); + List normalizedAssignments = + new ArrayList<>(assignments.size()); + + // 第四步:记录听查课管理员身份数量,以符合该登录表的一对一账户约束。 + int inspectionAdministratorCount = 0; + + // 第五步:逐个校验角色类型、身份存在性、可用状态和绑定归属。 + for (int index = 0; index < assignments.size(); index++) { + // 当前配置项为空时返回带实际序号的参数错误。 + AccountRoleAssignmentDTO assignment = assignments.get(index); + if (assignment == null) { + throw new BusinessException( + BAD_REQUEST, + "第" + (index + 1) + "个角色配置不能为空"); + } + + // 规范化并校验当前角色类型。 + String roleType = requireRoleType(assignment.getRoleType()); + + // 规范化并校验角色身份编号。 + String targetId = requiredText( + assignment.getTargetId(), + ACCOUNT_TEXT_MAX_LENGTH, + "角色身份编号"); + + // 同一角色类型和身份编号不能在本次请求中重复出现。 + String bindingKey = roleBindingKey(roleType, targetId); + if (!bindingKeys.add(bindingKey)) { + throw new BusinessException( + BAD_REQUEST, + "角色身份重复:" + ROLE_NAMES.get(roleType) + + "-" + targetId); + } + + // 听查课管理员登录表以登录信息编号为主标识,一个账户只能配置一个。 + if (ROLE_INSPECTION_ADMINISTRATOR.equals(roleType)) { + inspectionAdministratorCount++; + if (inspectionAdministratorCount > 1) { + throw new BusinessException( + BAD_REQUEST, + "听查课管理员角色只能配置一个身份"); + } + } + + /* + * 查询精确身份并确认身份存在、可用且没有绑定其他账户。 + * 全部配置项均通过后才会进入实际删除和写入阶段。 + */ + validateRoleIdentity(roleType, targetId, accountId); + + // 创建新的规范化 DTO,避免修改 Controller 传入的原始请求对象。 + AccountRoleAssignmentDTO normalized = + new AccountRoleAssignmentDTO(); + normalized.setRoleType(roleType); + normalized.setTargetId(targetId); + normalizedAssignments.add(normalized); + } + + // 第六步:返回已经完整预校验的角色配置。 + return normalizedAssignments; + } + + /** + * 校验一个角色身份是否可以绑定到指定账户。 + * + * @param roleType 角色类型 + * @param targetId 身份编号 + * @param accountId 待绑定账户编号 + */ + private void validateRoleIdentity( + String roleType, String targetId, String accountId) { + // 第一步:按角色类型和身份编号精确查询身份及其绑定信息。 + List options = + selectRoleOptions(roleType, targetId); + + // 第二步:记录是否找到身份、是否存在可用记录及是否发现其他账户绑定。 + boolean found = false; + boolean enabled = false; + AccountRoleOptionVO conflictingOption = null; + + // 第三步:检查查询返回的全部匹配记录。 + for (AccountRoleOptionVO option : options) { + // 空记录或身份编号不匹配时不能作为当前身份依据。 + if (option == null || !targetId.equals(option.getTargetId())) { + continue; + } + + // 标记身份真实存在。 + found = true; + + // 任一关联记录显示身份可用时记录可用状态。 + if (Boolean.TRUE.equals(option.getEnabled())) { + enabled = true; + } + + // 身份已经绑定其他账户时记录冲突详情。 + String boundAccountId = normalizeText(option.getBoundAccountId()); + if (boundAccountId != null + && !boundAccountId.equals(accountId)) { + conflictingOption = option; + } + } + + // 第四步:身份不存在时返回资源不存在错误。 + if (!found) { + throw new BusinessException( + NOT_FOUND, + ROLE_NAMES.get(roleType) + "身份不存在:" + targetId); + } + + // 第五步:离职、停用或退学等不可用身份不能用于新角色配置。 + if (!enabled) { + throw new BusinessException( + CONFLICT, + ROLE_NAMES.get(roleType) + "身份当前不可用:" + targetId); + } + + // 第六步:身份绑定其他账户时返回可识别冲突账户的提示。 + if (conflictingOption != null) { + String boundAccountName = + normalizeText(conflictingOption.getBoundLoginName()); + if (boundAccountName == null) { + boundAccountName = + conflictingOption.getBoundAccountId(); + } + throw new BusinessException( + CONFLICT, + ROLE_NAMES.get(roleType) + "身份已绑定账户:" + + boundAccountName); + } + } + + /** + * 整体替换账户角色绑定。 + * + * @param accountId 账户编号 + * @param assignments 已完成预校验的角色配置 + */ + private void replaceRoleBindings( + String accountId, + List assignments) { + /* + * 第一步:逐类清空账户原有身份绑定。 + * 当前方法由事务方法调用,后续任一写入失败都会恢复全部原有绑定。 + */ + for (String roleType : ROLE_TYPES) { + accountManagementMapper.deleteRoleBindings( + roleType, accountId); + } + + // 第二步:按照请求顺序写入已经通过预校验的全部角色身份。 + for (AccountRoleAssignmentDTO assignment : assignments) { + // 单条绑定未成功写入时抛出异常,使整个角色配置事务回滚。 + if (accountManagementMapper.saveRoleBinding( + assignment.getRoleType(), + assignment.getTargetId(), + accountId) != 1) { + throw new BusinessException( + CONFLICT, + "角色身份绑定状态已经发生变化,请刷新后重试"); + } + } + } + + /** + * 检查账户合并后的角色数量和单身份角色约束。 + * + * @param sourceRoles 源账户角色 + * @param targetRoles 目标账户角色 + */ + private void ensureMergeRoleCapacity( + List sourceRoles, + List targetRoles) { + // 第一步:使用角色类型和身份编号统计合并后的唯一角色数量。 + Set bindingKeys = new HashSet<>(); + addRoleKeys(bindingKeys, sourceRoles); + addRoleKeys(bindingKeys, targetRoles); + + // 第二步:合并后角色数量不能超过单账户角色配置上限。 + if (bindingKeys.size() > ROLE_ASSIGNMENT_MAX_SIZE) { + throw new BusinessException( + CONFLICT, "合并后的角色身份数量超过500个"); + } + + /* + * 第三步:听查课管理员登录表以账户编号为主标识。 + * 源账户和目标账户同时具有该身份时无法无损迁移全部绑定。 + */ + int inspectionCount = + countRoleType(sourceRoles, ROLE_INSPECTION_ADMINISTRATOR) + + countRoleType( + targetRoles, ROLE_INSPECTION_ADMINISTRATOR); + if (inspectionCount > 1) { + throw new BusinessException( + CONFLICT, + "源账户和目标账户均配置了听查课管理员,不能直接合并"); + } + } + + /** + * 将角色列表加入唯一键集合。 + * + * @param bindingKeys 角色唯一键集合 + * @param roles 账户角色列表 + */ + private void addRoleKeys( + Set bindingKeys, List roles) { + // 第一步:逐个读取有效角色,忽略历史数据中的空记录。 + for (AccountRoleVO role : roles) { + if (role == null) { + continue; + } + + // 第二步:角色类型和身份编号均有效时加入唯一键集合。 + String roleType = normalizeText(role.getRoleType()); + String targetId = normalizeText(role.getTargetId()); + if (roleType != null && targetId != null) { + bindingKeys.add(roleBindingKey(roleType, targetId)); + } + } + } + + /** + * 统计指定角色类型的绑定数量。 + * + * @param roles 账户角色列表 + * @param roleType 角色类型 + * @return 指定类型角色数量 + */ + private int countRoleType( + List roles, String roleType) { + // 第一步:初始化指定角色类型计数。 + int count = 0; + + // 第二步:逐个统计类型匹配的有效角色。 + for (AccountRoleVO role : roles) { + if (role != null && roleType.equals(role.getRoleType())) { + count++; + } + } + + // 第三步:返回统计结果。 + return count; + } + + /** + * 新增或更新账户专用状态记录。 + * + * @param account 包含最新启停和合并状态的账户记录 + */ + private void saveAccountStatus(AccountRecord account) { + /* + * 第一步:优先更新已有状态记录。 + * 通过写入结果判断记录是否存在,避免先查询再写入形成并发窗口。 + */ + int affectedRows = + accountManagementMapper.updateAccountStatus(account); + + /* + * 第二步:更新不到目标记录时尝试新增状态记录。 + * 正常情况下只有首次维护账户状态时才会进入当前分支。 + */ + if (affectedRows == 0) { + try { + affectedRows = + accountManagementMapper.insertAccountStatus(account); + } catch (DuplicateKeyException exception) { + /* + * 第三步:并发请求已经先完成新增时,唯一键冲突说明目标记录 + * 已经存在;重新执行更新即可完成当前请求的最终状态写入。 + */ + affectedRows = + accountManagementMapper.updateAccountStatus(account); + } + } + + // 第四步:状态未写入唯一目标行时返回业务冲突。 + if (affectedRows != 1) { + throw new BusinessException( + CONFLICT, "账户状态已经发生变化,请刷新后重试"); + } + } + + /** + * 清理一个或多个登录账号的全部在线会话。 + * + *

同一账号在一次业务操作中只清理一次;删除数量允许为零, + * 因为账户当前没有登录会话属于正常状态。

+ * + * @param loginNames 需要清理在线会话的登录账号 + */ + private void clearOnlineSessions(String... loginNames) { + // 第一步:调用方未提供账号集合时无需访问在线用户表。 + if (loginNames == null || loginNames.length == 0) { + return; + } + + /* + * 第二步:创建忽略大小写的已处理账号集合。 + * 账户合并或账号改名时可能传入重复账号,去重可以避免重复删除。 + */ + Set clearedLoginNames = new HashSet<>(); + + // 第三步:逐个规范化登录账号并删除对应在线会话。 + for (String loginName : loginNames) { + // 空账号不能用于在线会话清理。 + String normalizedLoginName = normalizeText(loginName); + if (normalizedLoginName == null) { + continue; + } + + // 使用固定区域规则生成忽略大小写的去重键。 + String loginNameKey = + normalizedLoginName.toLowerCase(Locale.ROOT); + if (!clearedLoginNames.add(loginNameKey)) { + continue; + } + + /* + * 删除当前账号全部在线记录。 + * Mapper 返回零表示账户当前不在线,不属于业务异常。 + */ + accountManagementMapper.deleteOnlineSessionsByLoginName( + normalizedLoginName); + } + } + + /** + * 建立当前请求对应的本地登录状态。 + * + *

外部 SSO 会话或本地密码都只负责完成身份校验,后续请求依赖本项目自己的 + * Servlet Session 和 MemberOnlineInfo 在线记录,因此两者必须统一建立, + * 并使用同一规则维护成功登录时间和连续失败状态。

+ * + * @param account 已通过状态校验的本地账户 + * @param roles 本地账户已经绑定的角色 + * @param fallbackIpAddress 外部认证流程提供的备用 IP + */ + private void establishLocalLoginSession( + AccountRecord account, + List roles, + String providedRoleCategory, + String fallbackIpAddress) { + // 第一步:校验登录流程提供的在线角色类别。 + String roleCategory = requiredText( + providedRoleCategory, + ACCOUNT_TEXT_MAX_LENGTH, + "登录角色类别"); + + /* + * 第二步:读取当前 HTTP 请求。 + * SSO 解析接口必须通过真实 HTTP 请求调用,才能把登录状态交给浏览器。 + */ + ServletRequestAttributes attributes = + (ServletRequestAttributes) RequestContextHolder + .getRequestAttributes(); + if (attributes == null) { + throw new BusinessException( + BAD_REQUEST, "无法建立本地登录会话"); + } + + // 第三步:取得当前请求,后续使用容器生成新的 Session 编号。 + HttpServletRequest request = attributes.getRequest(); + + /* + * 第四步:确定在线记录的单位。 + * 当前在线表只有一个单位字段,使用第一个可用本地角色的数据范围保存, + * 没有数据范围时允许为空,不伪造不存在的单位信息。 + */ + String unit = resolveOnlineUnit(roles); + + // 第五步:使用当前请求地址作为在线用户的初始访问路径。 + String requestPath = normalizeText(request.getRequestURI()); + + // 第六步:优先记录当前请求来源 IP,无法取得时回退到外部登录流程提供的 IP。 + String ipAddress = normalizeText(request.getRemoteAddr()); + if (ipAddress == null) { + ipAddress = normalizeText(fallbackIpAddress); + } + + // 第七步:生成统一的登录开始时间和最后访问时间。 + LocalDateTime accessTime = LocalDateTime.now(); + + /* + * 第八步:替换已有 Servlet Session。 + * 先删除旧在线记录,再作废旧 Session,避免旧账户身份继续留在数据库中。 + */ + HttpSession oldSession = request.getSession(false); + if (oldSession != null) { + accountManagementMapper.deleteOnlineSessionBySessionId( + oldSession.getId()); + invalidateSession(oldSession); + } + + HttpSession newSession = null; + try { + // 第九步:由 Servlet 容器创建新的登录 Session,防止会话固定攻击。 + newSession = request.getSession(true); + + /* + * 第十步:把账户登录信息写入在线表。 + * 不保存 SSO SessionKey,后续请求只依赖本项目自己的会话编号。 + */ + int affectedRows = + accountManagementMapper.insertOnlineSession( + newSession.getId(), + account.getLoginName(), + account.getUserName(), + unit, + roleCategory, + ipAddress, + accessTime, + accessTime, + requestPath); + + // 第十一步:在线记录未成功写入时不允许返回登录成功。 + if (affectedRows != 1) { + throw new BusinessException( + CONFLICT, "本地登录状态建立失败"); + } + + /* + * 第十二步:维护成功登录统计。 + * 原本的本次登录时间转为上次登录时间,再写入当前时间并清除失败状态。 + */ + int statisticsRows = + accountManagementMapper.recordLoginSuccess( + account.getId(), accessTime); + + // 第十三步:成功统计未写入唯一账户时,不允许保留当前登录状态。 + if (statisticsRows != 1) { + throw new BusinessException( + CONFLICT, "登录成功状态记录失败"); + } + + // 第十四步:提前生成登录记录编号,供登录历史和当前 Session 共同使用。 + String loginRecordId = UuidUtil.getOriginalUUID(); + + /* + * 第十五步:写入本次成功登录历史。 + * 登录记录主键使用前一步生成的编号,事务标识单独生成; + * 来源 IP 与在线记录保持一致,客户端内网 IP 无法由普通 HTTP 请求 + * 可靠取得,因此保持为空。 + */ + int historyRows = + accountManagementMapper.insertLoginHistory( + loginRecordId, + account.getId(), + accessTime, + ipAddress == null ? "unknown" : ipAddress, + null, + UuidUtil.getOriginalUUID()); + + // 第十六步:登录历史未写入唯一记录时,不允许返回登录成功。 + if (historyRows != 1) { + throw new BusinessException( + CONFLICT, "登录历史记录失败"); + } + + /* + * 第十七步:只在登录历史成功写入后保存登录记录编号, + * 供后续操作日志准确关联当前这一次登录。 + */ + newSession.setAttribute( + LOGIN_RECORD_ID_SESSION_ATTRIBUTE, + loginRecordId); + + // 第十八步:同步内存中的账户统计,保证当前业务流程使用最新状态。 + account.setLastLoginAt(account.getCurrentLoginAt()); + account.setCurrentLoginAt(accessTime); + account.setFailedAttempts(Integer.valueOf(0)); + account.setFailedAt(null); + } catch (RuntimeException exception) { + /* + * 第十九步:在线记录、成功统计或登录历史写入失败时作废新 Session, + * 防止浏览器得到一个没有数据库身份记录的半登录状态。 + */ + if (newSession != null) { + invalidateSession(newSession); + } + throw exception; + } + } + + /** + * 判断系统参数是否为有效正整数。 + * + * @param value 待检查的系统参数 + * @return 大于零时返回 true,否则返回 false + */ + private boolean isPositive(Integer value) { + // 只有明确配置的正整数才能启用对应账户安全规则。 + return value != null && value.intValue() > 0; + } + + /** + * 判断账户当前是否仍处于本地密码临时锁定期。 + * + * @param account 当前账户失败状态 + * @param allowedAttempts 允许密码尝试次数 + * @param expiredBefore 锁定状态到期截止时间 + * @return 达到失败阈值且最近失败时间尚未到期时返回 true + */ + private boolean isAccountTemporarilyLocked( + AccountRecord account, + Integer allowedAttempts, + LocalDateTime expiredBefore) { + // 第一步:缺少失败次数或没有达到阈值时不进入临时锁定。 + Integer failedAttempts = account.getFailedAttempts(); + if (failedAttempts == null + || failedAttempts.intValue() + < allowedAttempts.intValue()) { + return false; + } + + // 第二步:缺少失败时间时无法计算锁定期限,按未锁定兼容旧异常数据。 + LocalDateTime failedAt = account.getFailedAt(); + if (failedAt == null) { + return false; + } + + // 第三步:最近失败时间晚于到期截止点时,账户仍处于锁定期。 + return failedAt.isAfter(expiredBefore); + } + + /** + * 取得在线记录使用的单位或数据范围。 + * + * @param roles 本地账户角色 + * @return 第一个可用角色的数据范围;没有有效数据范围时返回 null + */ + private String resolveOnlineUnit(List roles) { + // 第一步:没有角色时不伪造在线单位。 + if (roles == null || roles.isEmpty()) { + return null; + } + + // 第二步:按照已经整理好的角色顺序查找第一个可用数据范围。 + for (AccountRoleVO role : roles) { + if (role == null || !Boolean.TRUE.equals(role.getEnabled())) { + continue; + } + + String dataScope = normalizeText(role.getDataScope()); + if (dataScope != null) { + return dataScope; + } + } + + // 第三步:所有角色都没有数据范围时保持数据库字段为空。 + return null; + } + + /** + * 禁止当前 Servlet 会话对应的账户停用自己。 + * + *

账户管理接口已经由拦截器校验管理员权限, + * 当前方法进一步从服务端会话和在线用户表取得真实操作账户, + * 不接受客户端提交的操作人编号。

+ * + * @param targetAccountId 待校验账户编号 + * @param conflictMessage 当前账户不能执行操作时的提示 + */ + private void ensureNotCurrentSessionAccount( + String targetAccountId, String conflictMessage) { + // 第一步:复用统一会话身份读取方法取得当前真实操作账户。 + String currentAccountId = requireCurrentSessionAccountId(); + + // 第二步:目标账户与当前操作账户相同时拒绝当前操作。 + if (targetAccountId.equals(currentAccountId)) { + throw new BusinessException( + CONFLICT, conflictMessage); + } + } + + /** + * 从当前 Servlet 会话对应的在线记录取得真实账户编号。 + * + *

该方法只信任服务端 SessionId 和数据库在线记录, + * 不读取客户端提交的账户编号、登录账号或角色参数。

+ * + * @return 当前登录账户编号 + */ + private String requireCurrentSessionAccountId() { + // 第一步:读取当前线程绑定的 HTTP 请求,避免从请求参数获取账户身份。 + ServletRequestAttributes attributes = + (ServletRequestAttributes) RequestContextHolder + .getRequestAttributes(); + if (attributes == null) { + throw new BusinessException( + BAD_REQUEST, "无法识别当前操作会话"); + } + + // 第二步:取得当前 HTTP 请求和已有 Servlet 会话,不主动创建新会话。 + HttpServletRequest request = attributes.getRequest(); + HttpSession session = request.getSession(false); + if (session == null) { + throw new BusinessException( + BAD_REQUEST, AUTHENTICATION_REQUIRED_MESSAGE); + } + + // 第三步:使用服务端会话编号查询数据库中的真实登录账户。 + String currentAccountId = + accountManagementMapper.selectAccountIdBySessionId( + session.getId()); + if (currentAccountId == null + || currentAccountId.trim().isEmpty()) { + throw new BusinessException( + BAD_REQUEST, AUTHENTICATION_REQUIRED_MESSAGE); + } + + // 第四步:返回去除首尾空白后的账户编号,供个人操作和自我保护校验复用。 + return currentAccountId.trim(); + } + + /** + * 查询账户详情并转换为 VO。 + * + * @param accountId 账户编号 + * @return 包含全部角色的账户 VO + */ + private AccountVO loadAccountView(String accountId) { + // 第一步:查询账户基础信息和账户管理状态。 + AccountRecord account = requireAccount(accountId); + + // 第二步:查询账户全部角色和数据范围。 + List roles = selectRoles(accountId); + + // 第三步:将内部持久化记录转换为不包含敏感字段的账户 VO。 + return toAccountVO(account, roles); + } + + /** + * 根据编号查询必然存在的账户。 + * + * @param accountId 账户编号 + * @return 账户持久化记录 + */ + private AccountRecord requireAccount(String accountId) { + // 第一步:按登录信息编号查询账户及其管理状态。 + AccountRecord account = + accountManagementMapper.selectAccountById(accountId); + + // 第二步:查询不到账户时返回资源不存在错误。 + if (account == null) { + throw new BusinessException(NOT_FOUND, "账户不存在"); + } + + // 第三步:旧账户没有状态记录时按照启用状态处理。 + if (account.getEnabled() == null) { + account.setEnabled(Boolean.TRUE); + } + + // 第四步:返回已经规范化管理状态的账户记录。 + return account; + } + + /** + * 查询并整理账户角色。 + * + * @param accountId 账户编号 + * @return 补充名称并完成排序的角色列表 + */ + private List selectRoles(String accountId) { + // 第一步:调用 Mapper 查询账户全部身份绑定。 + List selectedRoles = + accountManagementMapper.selectRolesByAccountId(accountId); + + // 第二步:创建可修改结果集合,并兼容 Mapper 返回 null。 + List roles = selectedRoles == null + ? new ArrayList() + : new ArrayList<>(selectedRoles); + + // 第三步:复用统一处理方法规范化角色、补充名称并稳定排序。 + normalizeAndSortRoles(roles); + + // 第四步:返回整理完成的角色列表。 + return roles; + } + + /** + * 批量查询当前页账户角色并按账户编号分组。 + * + * @param accountIds 当前页账户编号 + * @return 账户编号到已整理角色列表的有序映射 + */ + private Map> selectRolesByAccountIds( + List accountIds) { + // 第一步:创建有序分组结果,保持与当前页账户顺序一致。 + Map> rolesByAccountId = + new LinkedHashMap<>(); + + // 第二步:当前页没有有效账户时不执行无意义的角色查询。 + if (accountIds == null || accountIds.isEmpty()) { + return rolesByAccountId; + } + + // 第三步:预先为每个账户创建空角色集合,确保无角色账户也有分组。 + for (String accountId : accountIds) { + rolesByAccountId.put( + accountId, new ArrayList()); + } + + // 第四步:调用 Mapper 一次性读取当前页全部账户角色。 + List selectedRoles = + accountManagementMapper.selectRoleRecordsByAccountIds( + accountIds); + + // 第五步:Mapper 返回 null 时保留已经初始化的空分组。 + if (selectedRoles == null) { + return rolesByAccountId; + } + + // 第六步:逐条转换内部角色记录,并加入所属账户分组。 + for (AccountRoleRecord selectedRole : selectedRoles) { + // 空记录或无法关联账户的记录不能参与页面装配。 + if (selectedRole == null + || selectedRole.getAccountId() == null) { + continue; + } + + // 只接收本次查询账户集合中的角色,避免异常数据混入结果。 + List accountRoles = + rolesByAccountId.get(selectedRole.getAccountId()); + if (accountRoles == null) { + continue; + } + + // 将持久化角色记录转换为独立 VO 后加入对应账户。 + accountRoles.add(toAccountRoleVO(selectedRole)); + } + + // 第七步:按照统一角色顺序整理每个账户的角色集合。 + for (List roles : rolesByAccountId.values()) { + normalizeAndSortRoles(roles); + } + + // 第八步:返回完成分组和排序的当前页角色。 + return rolesByAccountId; + } + + /** + * 查询角色身份选项。 + * + * @param roleType 角色类型 + * @param targetId 精确身份编号;查询全部时为空 + * @return 非 null 角色身份选项集合 + */ + private List selectRoleOptions( + String roleType, String targetId) { + // 第一步:调用 Mapper 查询指定角色类型的身份及绑定信息。 + List options = + accountManagementMapper.selectRoleOptions( + roleType, targetId); + + // 第二步:Mapper 返回 null 时转换为空集合。 + return options == null + ? Collections.emptyList() + : options; + } + + /** + * 确保账户不是已合并源账户。 + * + * @param account 账户记录 + * @param message 不允许操作时的提示 + */ + private void ensureNotMerged( + AccountRecord account, String message) { + // 存在合并目标编号或合并时间时均视为已经完成账户合并。 + if (normalizeText(account.getMergedToAccountId()) != null + || account.getMergedAt() != null) { + throw new BusinessException(CONFLICT, message); + } + } + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/AffiliationSettingServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/AffiliationSettingServiceImpl.java new file mode 100644 index 000000000..7842b2d99 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/AffiliationSettingServiceImpl.java @@ -0,0 +1,2165 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.roomroot.jwgl.dto.affiliationsetting.*; +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.mapper.*; +import com.roomroot.jwgl.vo.affiliationsetting.*; +import com.roomroot.jwgl.mapper.*; +import com.roomroot.jwgl.service.AffiliationSettingService; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.utils.UuidUtil; +import com.roomroot.jwgl.vo.affiliationsetting.*; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +import static com.roomroot.jwgl.utils.BasicManagementConstants.*; + +/** + * 隶属关系设置统一服务实现。 + * + *

该类统一实现教研室和教学班次两类隶属关系业务, + * 共享部别查询、文本规范化和基础排序能力。

+ */ +@Service +public class AffiliationSettingServiceImpl implements AffiliationSettingService { + + @Resource + private SSJGMapper ssjgMapper; + + @Resource + private JYSBMapper jysbMapper; + + @Resource + private XYDBMapper xydbMapper; + + @Resource + private SSJGJYSMapper ssjgJysMapper; + + @Resource + private SSJGXYDMapper ssjgXydMapper; + + @Resource + private JYBMapper jybMapper; + + @Resource + private XYXXMapper xyxxMapper; + + @Resource + private SSJGCMMapper ssjgcmMapper; + + /** 异动历史JSON时间格式 */ + private static final DateTimeFormatter HISTORY_TIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + /** 人员类型常量 */ + private static final String TYPE_TEACHER = "TEACHER"; + private static final String TYPE_STUDENT = "STUDENT"; + private static final String TYPE_DEPT_PERSONNEL = "DEPARTMENT_PERSONNEL"; + + /** + * 查询可供隶属关系页面选择的启用部别。 + * + *

处理顺序:读取全部部别、排除停用部别、应用关键字过滤、 + * 转换为部别 VO、按照组织顺序排序。

+ * + * @param dto 查询条件 + * @return 可选部别列表 + */ + @Override + public List listResearchOfficeDepartments( + ResearchOfficeOptionQueryDTO dto) { + // 第一步:读取全部部别,统一处理 Mapper 返回 null 的情况。 + List departments = selectAllDepartments(); + + // 第二步:规范化可选关键字,空字符串按照未提供查询条件处理。 + String keyword = normalizeText(dto == null ? null : dto.getKeyword()); + + // 第三步:创建最终返回的部别 VO 集合。 + List result = new ArrayList<>(); + + // 第四步:逐个过滤停用部别和不匹配关键字的部别。 + for (SSJG department : departments) { + // 数据集合中的空记录不能转换为页面选项。 + if (department == null) { + continue; + } + + // 停用部别不能继续建立新的教研室隶属关系。 + if (Boolean.TRUE.equals(department.getTY())) { + continue; + } + + // 未命中编号、名称、简称或类别关键字时跳过当前部别。 + if (!matchesDepartment(department, keyword)) { + continue; + } + + // 将数据库实体转换为页面专用部别 VO。 + result.add(toDepartmentVO(department)); + } + + // 第五步:按照分级号、分类号和名称依次排序,保证下拉选项顺序稳定。 + Collections.sort(result, new Comparator() { + @Override + public int compare(AffiliationDepartmentVO left, + AffiliationDepartmentVO right) { + return compareDepartments(left, right); + } + }); + + // 第六步:返回完成过滤和排序的启用部别列表。 + return result; + } + + /** + * 查询指定部别已经关联的教研室。 + * + *

处理顺序:校验部别编号、确认部别存在、读取关系和教研室、 + * 应用关键字过滤、转换为关系 VO、按照教研室顺序排序。

+ * + * @param dto 部别编号及查询条件 + * @return 已建立的教研室隶属关系列表 + */ + @Override + public List listResearchOfficeAffiliations( + ResearchOfficeAffiliationQueryDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "查询参数不能为空"); + } + + // 第二步:校验并规范化当前选择的部别编号。 + String departmentId = requireDepartmentId(dto.getDepartmentId()); + + // 第三步:确认部别真实存在,避免对无效编号返回容易误解的空列表。 + SSJG department = requireDepartment(departmentId); + + // 第四步:读取当前部别的全部隶属关系。 + List relations = + selectResearchOfficeRelationsByDepartmentId(departmentId); + + // 第五步:一次性读取并索引教研室,避免逐条关系查询形成 N+1 问题。 + Map researchOfficeMap = indexResearchOffices(selectAllResearchOffices()); + + // 第六步:规范化可选关键字。 + String keyword = normalizeText(dto.getKeyword()); + + // 第七步:创建最终返回的关系 VO 集合。 + List result = new ArrayList<>(); + + // 第八步:逐条组装当前部别的教研室隶属关系。 + for (SSJGJYS relation : relations) { + // 异常空记录不参与页面结果组装。 + if (relation == null) { + continue; + } + + // 根据关系中的教研室编号取得完整教研室信息。 + JYSB researchOffice = researchOfficeMap.get(normalizeText(relation.getJYSBH())); + + // 应用编号、名称、简称和系别关键字过滤。 + if (!matchesResearchOffice(researchOffice, relation.getJYSBH(), keyword)) { + continue; + } + + // 将关系、部别和教研室实体组合为对外返回的关系 VO。 + result.add(toResearchOfficeAffiliationVO( + relation, department, researchOffice)); + } + + // 第九步:按照教研室序号、名称和编号依次排序。 + Collections.sort(result, new Comparator() { + @Override + public int compare(ResearchOfficeAffiliationVO left, + ResearchOfficeAffiliationVO right) { + return compareAffiliations(left, right); + } + }); + + // 第十步:返回指定部别的已关联教研室列表。 + return result; + } + + /** + * 查询尚未分配且处于启用状态的教研室。 + * + *

处理顺序:读取全部教研室和关系、收集已分配编号、排除停用及已分配数据、 + * 应用关键字过滤、转换为教研室 VO、按照页面展示顺序排序。

+ * + * @param dto 查询条件 + * @return 可添加的教研室列表 + */ + @Override + public List listAvailableResearchOffices(ResearchOfficeOptionQueryDTO dto) { + // 第一步:读取全部教研室。 + List researchOffices = selectAllResearchOffices(); + + // 第二步:读取全部隶属关系,并收集已经分配的教研室编号。 + Set assignedResearchOfficeIds = collectAssignedResearchOfficeIds( + selectAllResearchOfficeRelations()); + + // 第三步:规范化可选关键字。 + String keyword = normalizeText(dto == null ? null : dto.getKeyword()); + + // 第四步:创建最终返回的可选教研室 VO 集合。 + List result = new ArrayList<>(); + + // 第五步:逐个筛选尚未分配且启用的教研室。 + for (JYSB researchOffice : researchOffices) { + // 数据集合中的空记录不能作为可选项。 + if (researchOffice == null) { + continue; + } + + // 规范化教研室编号,缺少编号的数据不能建立有效外键关系。 + String researchOfficeId = normalizeText(researchOffice.getJysdh()); + if (researchOfficeId == null) { + continue; + } + + // 停用教研室不能建立新的隶属关系。 + if (Boolean.TRUE.equals(researchOffice.getTy())) { + continue; + } + + // 已经隶属于任意部别的教研室不能再次出现在未分配列表中。 + if (assignedResearchOfficeIds.contains(researchOfficeId)) { + continue; + } + + // 未命中编号、名称、简称或系别关键字时跳过当前教研室。 + if (!matchesResearchOffice(researchOffice, researchOfficeId, keyword)) { + continue; + } + + // 将数据库实体转换为页面可选教研室 VO。 + result.add(toResearchOfficeVO(researchOffice)); + } + + // 第六步:按照序号、名称和编号依次排序。 + Collections.sort(result, new Comparator() { + @Override + public int compare(ResearchOfficeVO left, ResearchOfficeVO right) { + return compareResearchOffices(left, right); + } + }); + + // 第七步:返回未分配且启用的教研室列表。 + return result; + } + + /** + * 新增教研室隶属关系。 + * + *

处理顺序:校验参数、确认部别及教研室存在、校验双方启用状态、 + * 防止重复分配、构造关系实体、写入数据库、返回新增关系 VO。

+ * + * @param dto 部别编号和教研室编号 + * @return 新增后的隶属关系 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public ResearchOfficeAffiliationVO addResearchOfficeAffiliation( + ResearchOfficeAffiliationAddDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "新增参数不能为空"); + } + + // 第二步:校验并规范化部别编号和教研室编号。 + String departmentId = requireDepartmentId(dto.getDepartmentId()); + String researchOfficeId = requireResearchOfficeId(dto.getResearchOfficeId()); + + // 第三步:确认目标部别存在并且处于启用状态。 + SSJG department = requireDepartment(departmentId); + ensureDepartmentEnabledForResearchOffice(department); + + // 第四步:确认目标教研室存在并且处于启用状态。 + JYSB researchOffice = requireResearchOffice(researchOfficeId); + ensureResearchOfficeEnabled(researchOffice); + + // 第五步:检查教研室是否已经隶属于当前部别或其他部别。 + List existingRelations = + selectResearchOfficeRelationsByResearchOfficeId(researchOfficeId); + ensureResearchOfficeUnassigned(existingRelations, departmentId); + + /* + * 第六步:将新增 DTO 转换为隶属关系实体。 + * DTO 只负责接收请求,SSJGJYS 仅在持久化流程内部使用。 + */ + SSJGJYS relation = new SSJGJYS(); + relation.setBH(UuidUtil.getOriginalUUID()); + relation.setSSJGBH(departmentId); + relation.setJYSBH(researchOfficeId); + + /* + * 第七步:以“教研室仍未分配”为条件执行新增。 + * 影响行数为 0 通常表示校验后发生了并发分配,需要返回明确的冲突提示。 + */ + int affectedRows = + ssjgJysMapper.insertResearchOfficeRelationIfUnassigned( + relation); + if (affectedRows != 1) { + List concurrentRelations = + selectResearchOfficeRelationsByResearchOfficeId( + researchOfficeId); + if (!concurrentRelations.isEmpty()) { + throw new BusinessException(CONFLICT, "该教研室已被分配,请刷新后重试"); + } + throw new BusinessException("添加教研室隶属关系失败"); + } + + // 第八步:使用已确认的部别和教研室实体组装新增后的关系 VO。 + return toResearchOfficeAffiliationVO( + relation, department, researchOffice); + } + + /** + * 解除教研室隶属关系。 + * + *

处理顺序:校验关系编号、确认关系存在、读取关联展示信息、 + * 删除关系记录、返回被解除的关系 VO。

+ * + * @param dto 隶属关系编号 + * @return 被解除的隶属关系 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public ResearchOfficeAffiliationVO removeResearchOfficeAffiliation( + ResearchOfficeAffiliationIdDTO dto) { + // 第一步:兼容业务层被直接调用时传入 null 的场景,并校验关系编号。 + String relationId = requireRelationId(dto == null ? null : dto.getId()); + + // 第二步:查询并确认待解除的隶属关系真实存在。 + SSJGJYS relation = requireRelation(relationId); + + /* + * 第三步:在删除前读取部别和教研室信息。 + * 即使历史数据存在孤立关系,也允许删除该关系,因此这里不强制要求关联实体存在。 + */ + SSJG department = ssjgMapper.selectDepartmentById( + normalizeText(relation.getSSJGBH())); + JYSB researchOffice = jysbMapper.selectById( + normalizeText(relation.getJYSBH())); + + // 第四步:在删除前组装返回 VO,保留被解除关系的完整展示信息。 + ResearchOfficeAffiliationVO removedRelation = + toResearchOfficeAffiliationVO( + relation, department, researchOffice); + + // 第五步:按关系唯一编号执行精确删除,并校验数据库实际影响行数。 + if (ssjgJysMapper.deleteResearchOfficeRelationById( + relationId) != 1) { + throw new BusinessException("解除教研室隶属关系失败"); + } + + // 第六步:返回已经解除的关系信息。 + return removedRelation; + } + + /** + * 查询可供教学班次隶属关系页面选择的启用部别。 + * + *

处理顺序:读取全部部别、排除停用部别、应用关键字过滤、 + * 转换为共享部别 VO、按照组织顺序稳定排序。

+ * + * @param dto 查询条件 + * @return 可选部别列表 + */ + @Override + public List listTeachingClassDepartments( + TeachingClassAffiliationDTO dto) { + // 第一步:读取全部部别,并统一处理 Mapper 返回 null 的情况。 + List departments = selectAllDepartments(); + + // 第二步:规范化可选关键字,空字符串按照未提供查询条件处理。 + String keyword = normalizeText(dto == null ? null : dto.getKeyword()); + + // 第三步:创建最终返回的共享部别 VO 集合。 + List result = new ArrayList<>(); + + // 第四步:逐个过滤停用部别和不匹配关键字的部别。 + for (SSJG department : departments) { + // 数据集合中的空记录不能转换为页面选项。 + if (department == null) { + continue; + } + + // 停用部别不能继续建立新的教学班次隶属关系。 + if (Boolean.TRUE.equals(department.getTY())) { + continue; + } + + // 未命中编号、名称、简称或类别关键字时跳过当前部别。 + if (!matchesDepartment(department, keyword)) { + continue; + } + + // 将数据库实体转换为隶属关系设置功能共享的部别 VO。 + result.add(toDepartmentVO(department)); + } + + // 第五步:按照分级号、分类号和名称依次排序,保证下拉选项顺序稳定。 + Collections.sort(result, new Comparator() { + @Override + public int compare(AffiliationDepartmentVO left, + AffiliationDepartmentVO right) { + return compareDepartments(left, right); + } + }); + + // 第六步:返回完成过滤和排序的启用部别列表。 + return result; + } + + /** + * 查询指定部别已经关联的教学班次。 + * + *

处理顺序:校验部别、读取关系和教学班次、建立班次索引、 + * 应用关键字及状态过滤、转换为关系 VO、按照班次顺序稳定排序。

+ * + * @param dto 部别编号及查询条件 + * @return 已建立的教学班次隶属关系列表 + */ + @Override + public List listTeachingClassAffiliations( + TeachingClassAffiliationDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "查询参数不能为空"); + } + + // 第二步:校验并规范化当前选择的部别编号。 + String departmentId = requireDepartmentId(dto.getDepartmentId()); + + // 第三步:确认部别真实存在,避免无效编号被误解为空列表。 + SSJG department = requireDepartment(departmentId); + + // 第四步:读取当前部别的全部教学班次隶属关系。 + List relations = + selectTeachingClassRelationsByDepartmentId(departmentId); + + // 第五步:一次性读取并索引教学班次,避免逐条关系查询形成 N+1 问题。 + Map teachingClassMap = + indexTeachingClasses(selectAllTeachingClasses()); + + // 第六步:规范化可选关键字和在校状态筛选条件。 + String keyword = normalizeText(dto.getKeyword()); + String status = normalizeText(dto.getStatus()); + + // 第七步:创建最终返回的关系 VO 集合。 + List result = new ArrayList<>(); + + // 第八步:逐条组装当前部别的教学班次隶属关系。 + for (SSJGXYD relation : relations) { + // 异常空记录不参与页面结果组装。 + if (relation == null) { + continue; + } + + // 根据关系中的教学班次编号取得完整班次信息。 + String teachingClassId = normalizeText(relation.getXydbh()); + XYDB teachingClass = teachingClassMap.get(teachingClassId); + + // 应用编号、名称及其他主要展示字段的关键字过滤。 + if (!matchesTeachingClass( + teachingClass, relation.getXydbh(), keyword)) { + continue; + } + + // 应用在校状态精确过滤;孤立关系仅在未指定状态时保留。 + if (!matchesStatus(teachingClass, status)) { + continue; + } + + // 将关系、部别和教学班次实体组合为对外返回的关系 VO。 + result.add(toTeachingClassAffiliationVO( + relation, department, teachingClass)); + } + + // 第九步:按照序号、名称和编号依次排序。 + Collections.sort(result, new Comparator() { + @Override + public int compare(TeachingClassAffiliationVO left, + TeachingClassAffiliationVO right) { + return compareTeachingClasses(left, right); + } + }); + + // 第十步:返回指定部别的已关联教学班次列表。 + return result; + } + + /** + * 查询尚未分配给任何部别的教学班次。 + * + *

处理顺序:读取全部教学班次和关系、收集已分配编号、 + * 排除无效及已分配数据、应用筛选条件、转换为 VO、稳定排序。

+ * + * @param dto 查询条件 + * @return 可添加的教学班次列表 + */ + @Override + public List listAvailableTeachingClasses( + TeachingClassAffiliationDTO dto) { + // 第一步:读取全部教学班次。 + List teachingClasses = selectAllTeachingClasses(); + + // 第二步:读取全部隶属关系,并收集已经分配的教学班次编号。 + Set assignedTeachingClassIds = + collectAssignedTeachingClassIds( + selectAllTeachingClassRelations()); + + // 第三步:规范化可选关键字和在校状态筛选条件。 + String keyword = normalizeText(dto == null ? null : dto.getKeyword()); + String status = normalizeText(dto == null ? null : dto.getStatus()); + + // 第四步:创建最终返回的可选教学班次 VO 集合。 + List result = new ArrayList<>(); + + // 第五步:逐个筛选尚未分配的有效教学班次。 + for (XYDB teachingClass : teachingClasses) { + // 数据集合中的空记录不能作为可选项。 + if (teachingClass == null) { + continue; + } + + // 规范化教学班次编号,缺少编号的数据不能建立有效关系。 + String teachingClassId = normalizeText(teachingClass.getXydbh()); + if (teachingClassId == null) { + continue; + } + + // 已经隶属于任意部别的教学班次不能再次出现在未分配列表中。 + if (assignedTeachingClassIds.contains(teachingClassId)) { + continue; + } + + // 未命中主要展示字段关键字时跳过当前教学班次。 + if (!matchesTeachingClass( + teachingClass, teachingClassId, keyword)) { + continue; + } + + // 未命中指定在校状态时跳过当前教学班次。 + if (!matchesStatus(teachingClass, status)) { + continue; + } + + // 将数据库教学班次实体转换为不包含部别信息的页面 VO。 + result.add(toTeachingClassVO(teachingClass)); + } + + // 第六步:按照序号、名称和编号依次排序。 + Collections.sort(result, new Comparator() { + @Override + public int compare(TeachingClassAffiliationVO left, + TeachingClassAffiliationVO right) { + return compareTeachingClasses(left, right); + } + }); + + // 第七步:返回尚未分配的教学班次列表。 + return result; + } + + /** + * 新增教学班次隶属关系。 + * + *

处理顺序:校验参数、确认部别和班次存在、校验部别启用状态、 + * 防止重复分配、构造关系实体、条件写入数据库、返回关系 VO。

+ * + * @param dto 部别编号和教学班次编号 + * @return 新增后的隶属关系 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public TeachingClassAffiliationVO addTeachingClassAffiliation( + TeachingClassAffiliationDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "新增参数不能为空"); + } + + // 第二步:校验并规范化部别编号和教学班次编号。 + String departmentId = requireDepartmentId(dto.getDepartmentId()); + String teachingClassId = + requireTeachingClassId(dto.getTeachingClassId()); + + // 第三步:确认目标部别存在并且处于启用状态。 + SSJG department = requireDepartment(departmentId); + ensureDepartmentEnabledForTeachingClass(department); + + // 第四步:确认目标教学班次真实存在。 + XYDB teachingClass = requireTeachingClass(teachingClassId); + + // 第五步:检查教学班次是否已经隶属于当前部别或其他部别。 + SSJGXYD existingRelation = + ssjgXydMapper + .selectTeachingClassRelationByTeachingClassId( + teachingClassId); + ensureTeachingClassUnassigned(existingRelation, departmentId); + + /* + * 第六步:将统一请求 DTO 中的关系字段转换为持久化实体。 + * DTO 只负责接收请求,SSJGXYD 仅在持久化流程内部使用。 + */ + SSJGXYD relation = new SSJGXYD(); + relation.setXydbh(teachingClassId); + relation.setSsjgbh(departmentId); + + /* + * 第七步:以“教学班次仍未分配”为条件执行新增。 + * 影响行数不是 1 时再次读取当前关系,用于区分并发冲突和普通写入失败。 + */ + int affectedRows = + ssjgXydMapper + .insertTeachingClassRelationIfUnassigned(relation); + if (affectedRows != 1) { + SSJGXYD concurrentRelation = + ssjgXydMapper + .selectTeachingClassRelationByTeachingClassId( + teachingClassId); + if (concurrentRelation != null) { + throw new BusinessException( + CONFLICT, "该教学班次已被分配,请刷新后重试"); + } + throw new BusinessException("添加教学班次隶属关系失败"); + } + + // 第八步:使用已确认的部别和教学班次实体组装新增后的关系 VO。 + return toTeachingClassAffiliationVO( + relation, department, teachingClass); + } + + /** + * 解除教学班次隶属关系。 + * + *

处理顺序:校验关系双方编号、确认关系存在、核对当前归属、 + * 读取展示信息、按照双方编号精确删除、返回被解除的关系 VO。

+ * + * @param dto 部别编号和教学班次编号 + * @return 被解除的隶属关系 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public TeachingClassAffiliationVO removeTeachingClassAffiliation( + TeachingClassAffiliationDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "解除参数不能为空"); + } + + // 第二步:校验并规范化请求中的部别编号和教学班次编号。 + String departmentId = requireDepartmentId(dto.getDepartmentId()); + String teachingClassId = + requireTeachingClassId(dto.getTeachingClassId()); + + // 第三步:按照教学班次主键查询并确认待解除关系真实存在。 + SSJGXYD relation = + ssjgXydMapper + .selectTeachingClassRelationByTeachingClassId( + teachingClassId); + if (relation == null) { + throw new BusinessException(NOT_FOUND, "教学班次隶属关系不存在"); + } + + /* + * 第四步:核对数据库中的当前部别与请求部别是否一致。 + * 页面数据过期时停止删除,避免旧请求删除已经转移到其他部别的关系。 + */ + String currentDepartmentId = normalizeText(relation.getSsjgbh()); + if (!departmentId.equals(currentDepartmentId)) { + throw new BusinessException( + CONFLICT, "教学班次归属已经发生变化,请刷新后重试"); + } + + /* + * 第五步:在删除前读取部别和教学班次展示信息。 + * 即使历史数据已经成为孤立关系,也允许管理员解除,因此不强制关联实体必须存在。 + */ + SSJG department = + ssjgMapper.selectDepartmentById(departmentId); + XYDB teachingClass = + xydbMapper.selectById( + teachingClassId); + + // 第六步:在删除前组装返回 VO,保留被解除关系的完整展示信息。 + TeachingClassAffiliationVO removedRelation = + toTeachingClassAffiliationVO( + relation, department, teachingClass); + + // 第七步:按照关系双方编号精确删除,并校验数据库实际影响行数。 + int affectedRows = + ssjgXydMapper + .deleteTeachingClassRelationByDepartmentAndTeachingClassId( + departmentId, teachingClassId); + if (affectedRows != 1) { + throw new BusinessException("解除教学班次隶属关系失败"); + } + + // 第八步:返回已经解除的关系信息。 + return removedRelation; + } + + /** + * 查询必然存在的部别。 + * + * @param id 部别编号 + * @return 部别实体 + */ + private SSJG requireDepartment(String id) { + // 第一步:根据唯一编号查询部别。 + SSJG department = ssjgMapper.selectDepartmentById(id); + + // 第二步:未查询到数据时返回明确的 404 业务错误。 + if (department == null) { + throw new BusinessException(NOT_FOUND, "部别不存在"); + } + + // 第三步:返回已确认存在的部别实体。 + return department; + } + + /** + * 查询必然存在的教研室。 + * + * @param id 教研室编号 + * @return 教研室实体 + */ + private JYSB requireResearchOffice(String id) { + // 第一步:根据教研室代号查询教研室。 + JYSB researchOffice = jysbMapper.selectById(id); + + // 第二步:未查询到数据时返回明确的 404 业务错误。 + if (researchOffice == null) { + throw new BusinessException(NOT_FOUND, "教研室不存在"); + } + + // 第三步:返回已确认存在的教研室实体。 + return researchOffice; + } + + /** + * 查询必然存在的教研室隶属关系。 + * + * @param id 隶属关系编号 + * @return 隶属关系实体 + */ + private SSJGJYS requireRelation(String id) { + // 第一步:根据唯一编号查询隶属关系。 + SSJGJYS relation = + ssjgJysMapper.selectResearchOfficeRelationById(id); + + // 第二步:未查询到数据时返回明确的 404 业务错误。 + if (relation == null) { + throw new BusinessException(NOT_FOUND, "教研室隶属关系不存在"); + } + + // 第三步:返回已确认存在的隶属关系实体。 + return relation; + } + + /** + * 校验部别是否允许建立新的教研室隶属关系。 + * + * @param department 部别实体 + */ + private void ensureDepartmentEnabledForResearchOffice(SSJG department) { + // 停用部别不能继续接收新的教研室。 + if (Boolean.TRUE.equals(department.getTY())) { + throw new BusinessException(CONFLICT, "部别已停用,不能添加教研室"); + } + } + + /** + * 校验教研室是否允许建立新的隶属关系。 + * + * @param researchOffice 教研室实体 + */ + private void ensureResearchOfficeEnabled(JYSB researchOffice) { + // 停用教研室不能被分配到部别。 + if (Boolean.TRUE.equals(researchOffice.getTy())) { + throw new BusinessException(CONFLICT, "教研室已停用,不能添加"); + } + } + + /** + * 校验教研室尚未隶属于任何部别。 + * + * @param relations 教研室已有关系 + * @param departmentId 当前准备关联的部别编号 + */ + private void ensureResearchOfficeUnassigned(List relations, String departmentId) { + // 没有已有关系时可以继续新增。 + if (relations.isEmpty()) { + return; + } + + // 第一步:检查是否已经存在当前部别下的相同关系。 + for (SSJGJYS relation : relations) { + if (relation != null + && departmentId.equals(normalizeText(relation.getSSJGBH()))) { + throw new BusinessException(CONFLICT, "该教研室已隶属于当前部别"); + } + } + + // 第二步:存在其他关系时,说明该教研室已经隶属于其他部别。 + throw new BusinessException(CONFLICT, "该教研室已隶属于其他部别"); + } + + /** + * 查询全部部别,并统一处理 Mapper 返回 null 的情况。 + * + * @return 非 null 的部别集合 + */ + private List selectAllDepartments() { + // 第一步:调用 Mapper 读取全部部别。 + List departments = ssjgMapper.selectAllDepartments(); + + // 第二步:Mapper 返回 null 时转换为空集合,减少后续判空分支。 + return departments == null ? Collections.emptyList() : departments; + } + + /** + * 查询全部教研室,并统一处理 Mapper 返回 null 的情况。 + * + * @return 非 null 的教研室集合 + */ + private List selectAllResearchOffices() { + // 第一步:调用 Mapper 读取全部教研室。 + List researchOffices = + jysbMapper.selectList(null); + + // 第二步:Mapper 返回 null 时转换为空集合,减少后续判空分支。 + return researchOffices == null ? Collections.emptyList() : researchOffices; + } + + /** + * 查询指定部别的全部隶属关系,并统一处理 null。 + * + * @param departmentId 部别编号 + * @return 非 null 的隶属关系集合 + */ + private List selectResearchOfficeRelationsByDepartmentId( + String departmentId) { + // 第一步:调用 Mapper 查询指定部别下的全部关系。 + List relations = + ssjgJysMapper.selectResearchOfficeRelationsByDepartmentId( + departmentId); + + // 第二步:Mapper 返回 null 时转换为空集合。 + return relations == null ? Collections.emptyList() : relations; + } + + /** + * 查询指定教研室的全部隶属关系,并统一处理 null。 + * + * @param researchOfficeId 教研室编号 + * @return 非 null 的隶属关系集合 + */ + private List selectResearchOfficeRelationsByResearchOfficeId( + String researchOfficeId) { + // 第一步:调用 Mapper 查询指定教研室的全部关系。 + List relations = + ssjgJysMapper + .selectResearchOfficeRelationsByResearchOfficeId( + researchOfficeId); + + // 第二步:Mapper 返回 null 时转换为空集合。 + return relations == null ? Collections.emptyList() : relations; + } + + /** + * 查询全部隶属关系,并统一处理 Mapper 返回 null 的情况。 + * + * @return 非 null 的隶属关系集合 + */ + private List selectAllResearchOfficeRelations() { + // 第一步:调用 Mapper 读取全部隶属关系。 + List relations = + ssjgJysMapper.selectAllResearchOfficeRelations(); + + // 第二步:Mapper 返回 null 时转换为空集合。 + return relations == null ? Collections.emptyList() : relations; + } + + /** + * 按教研室编号建立实体索引。 + * + * @param researchOffices 教研室集合 + * @return 教研室编号与实体的映射 + */ + private Map indexResearchOffices(List researchOffices) { + // 第一步:创建用于快速查询教研室的编号索引。 + Map result = new HashMap<>(); + + // 第二步:逐个读取教研室并规范化编号。 + for (JYSB researchOffice : researchOffices) { + if (researchOffice == null) { + continue; + } + + // 缺少有效编号的异常数据不能写入索引。 + String id = normalizeText(researchOffice.getJysdh()); + if (id != null) { + result.put(id, researchOffice); + } + } + + // 第三步:返回构建完成的教研室索引。 + return result; + } + + /** + * 收集已经建立隶属关系的教研室编号。 + * + * @param relations 全部隶属关系 + * @return 已分配教研室编号集合 + */ + private Set collectAssignedResearchOfficeIds(List relations) { + // 第一步:创建结果集合,并利用 Set 自动去除重复编号。 + Set result = new HashSet<>(); + + // 第二步:逐条读取关系中的教研室编号。 + for (SSJGJYS relation : relations) { + if (relation == null) { + continue; + } + + // 仅将非空的有效教研室编号加入结果集合。 + String researchOfficeId = normalizeText(relation.getJYSBH()); + if (researchOfficeId != null) { + result.add(researchOfficeId); + } + } + + // 第三步:返回全部已分配教研室编号。 + return result; + } + + /** + * 将部别实体转换为页面选择项 VO。 + * + * @param department 部别实体 + * @return 部别选择项 VO + */ + private AffiliationDepartmentVO toDepartmentVO(SSJG department) { + // 第一步:创建独立的返回对象,避免对外暴露数据库实体。 + AffiliationDepartmentVO vo = new AffiliationDepartmentVO(); + + // 第二步:复制部别编号、名称、简称和类别。 + vo.setId(department.getBH()); + vo.setName(department.getJGMC()); + vo.setAbbreviation(department.getJC()); + vo.setCategory(department.getJGLB()); + + // 第三步:复制排序字段和上级关系字段。 + vo.setClassificationCode(department.getFLH()); + vo.setLevelNumber(department.getFJH()); + vo.setParentId(normalizeText(department.getSJJGBH())); + + // 第四步:返回组装完成的部别 VO。 + return vo; + } + + /** + * 将教研室实体转换为基础展示 VO。 + * + * @param researchOffice 教研室实体 + * @return 教研室 VO + */ + private ResearchOfficeVO toResearchOfficeVO(JYSB researchOffice) { + // 第一步:创建独立的返回对象,避免对外暴露数据库实体。 + ResearchOfficeVO vo = new ResearchOfficeVO(); + + // 第二步:复制截图列表所需的编号、名称、简称和系别。 + vo.setId(researchOffice.getJysdh()); + vo.setName(researchOffice.getJysmc()); + vo.setAbbreviation(researchOffice.getJc()); + vo.setDepartment(researchOffice.getBx()); + + // 第三步:将停用标志转换为更直观的启用状态,并保留停用时间。 + vo.setEnabled(!Boolean.TRUE.equals(researchOffice.getTy())); + vo.setDisabledAt(researchOffice.getTysj()); + + // 第四步:复制虚实类型、备注、序号和机关性质。 + vo.setVirtual(researchOffice.getXslx()); + vo.setRemark(researchOffice.getBz()); + vo.setSequenceNumber(researchOffice.getXh()); + vo.setOrganNature(researchOffice.getJgxz()); + + // 第五步:返回组装完成的教研室 VO。 + return vo; + } + + /** + * 将关系、部别和教研室实体组合为关系 VO。 + * + * @param relation 隶属关系实体 + * @param department 部别实体;清理孤立关系时允许为 null + * @param researchOffice 教研室实体;清理孤立关系时允许为 null + * @return 教研室隶属关系 VO + */ + private ResearchOfficeAffiliationVO toResearchOfficeAffiliationVO( + SSJGJYS relation, + SSJG department, + JYSB researchOffice) { + // 第一步:创建独立的关系返回对象。 + ResearchOfficeAffiliationVO vo = new ResearchOfficeAffiliationVO(); + + // 第二步:复制关系编号和关系表中保存的双方编号。 + vo.setId(relation.getBH()); + vo.setDepartmentId(relation.getSSJGBH()); + vo.setResearchOfficeId(relation.getJYSBH()); + + // 第三步:关联部别仍然存在时补充部别名称。 + if (department != null) { + vo.setDepartmentName(department.getJGMC()); + } + + /* + * 第四步:关联教研室仍然存在时补充完整展示字段。 + * 关联实体缺失时仍返回关系表中的编号,便于管理员识别并解除孤立关系。 + */ + if (researchOffice != null) { + vo.setResearchOfficeName(researchOffice.getJysmc()); + vo.setAbbreviation(researchOffice.getJc()); + vo.setDepartment(researchOffice.getBx()); + vo.setEnabled(!Boolean.TRUE.equals(researchOffice.getTy())); + vo.setDisabledAt(researchOffice.getTysj()); + vo.setVirtual(researchOffice.getXslx()); + vo.setRemark(researchOffice.getBz()); + vo.setSequenceNumber(researchOffice.getXh()); + vo.setOrganNature(researchOffice.getJgxz()); + } + + // 第五步:返回完成组装的关系 VO。 + return vo; + } + + /** + * 查询必然存在的教学班次。 + * + * @param id 教学班次编号 + * @return 教学班次实体 + */ + private XYDB requireTeachingClass(String id) { + // 第一步:根据唯一编号查询教学班次。 + XYDB teachingClass = + xydbMapper.selectById(id); + + // 第二步:未查询到数据时返回明确的 404 业务错误。 + if (teachingClass == null) { + throw new BusinessException(NOT_FOUND, "教学班次不存在"); + } + + // 第三步:返回已经确认存在的教学班次实体。 + return teachingClass; + } + + /** + * 校验部别可以建立新的教学班次隶属关系。 + * + * @param department 部别实体 + */ + private void ensureDepartmentEnabledForTeachingClass(SSJG department) { + // 停用部别只允许查看和清理历史关系,不能继续新增教学班次归属。 + if (Boolean.TRUE.equals(department.getTY())) { + throw new BusinessException( + CONFLICT, "部别已停用,不能添加教学班次"); + } + } + + /** + * 校验教学班次尚未建立隶属关系。 + * + * @param existingRelation 已存在的关系;未分配时为 null + * @param targetDepartmentId 本次目标部别编号 + */ + private void ensureTeachingClassUnassigned( + SSJGXYD existingRelation, + String targetDepartmentId) { + // 未查询到关系时表示教学班次可以建立新的归属。 + if (existingRelation == null) { + return; + } + + // 规范化现有关系中的部别编号,用于区分同部别重复添加和跨部别重复分配。 + String currentDepartmentId = + normalizeText(existingRelation.getSsjgbh()); + + // 已经归属当前部别时返回更明确的重复操作提示。 + if (targetDepartmentId.equals(currentDepartmentId)) { + throw new BusinessException( + CONFLICT, "该教学班次已隶属于当前部别"); + } + + // 已经归属其他部别或存在异常关系时禁止再次分配。 + throw new BusinessException( + CONFLICT, "该教学班次已隶属于其他部别"); + } + + /** + * 查询全部教学班次,并统一处理 Mapper 返回 null 的情况。 + * + * @return 非 null 的教学班次集合 + */ + private List selectAllTeachingClasses() { + // 第一步:调用 Mapper 读取全部教学班次。 + List teachingClasses = + xydbMapper.selectList(null); + + // 第二步:Mapper 返回 null 时转换为空集合。 + return teachingClasses == null + ? Collections.emptyList() + : teachingClasses; + } + + /** + * 查询指定部别的教学班次关系,并统一处理 Mapper 返回 null 的情况。 + * + * @param departmentId 部别编号 + * @return 非 null 的隶属关系集合 + */ + private List selectTeachingClassRelationsByDepartmentId( + String departmentId) { + // 第一步:调用 Mapper 查询指定部别的全部教学班次关系。 + List relations = + ssjgXydMapper + .selectTeachingClassRelationsByDepartmentId( + departmentId); + + // 第二步:Mapper 返回 null 时转换为空集合。 + return relations == null + ? Collections.emptyList() + : relations; + } + + /** + * 查询全部教学班次关系,并统一处理 Mapper 返回 null 的情况。 + * + * @return 非 null 的隶属关系集合 + */ + private List selectAllTeachingClassRelations() { + // 第一步:调用 Mapper 读取全部教学班次隶属关系。 + List relations = + ssjgXydMapper.selectAllTeachingClassRelations(); + + // 第二步:Mapper 返回 null 时转换为空集合。 + return relations == null + ? Collections.emptyList() + : relations; + } + + /** + * 按教学班次编号建立实体索引。 + * + * @param teachingClasses 教学班次集合 + * @return 教学班次编号与实体的映射 + */ + private Map indexTeachingClasses( + List teachingClasses) { + // 第一步:创建用于快速查询教学班次的编号索引。 + Map result = new HashMap<>(); + + // 第二步:逐个读取教学班次并规范化编号。 + for (XYDB teachingClass : teachingClasses) { + // 空记录不能写入教学班次索引。 + if (teachingClass == null) { + continue; + } + + // 仅将具有有效编号的教学班次写入索引。 + String id = normalizeText(teachingClass.getXydbh()); + if (id != null) { + result.put(id, teachingClass); + } + } + + // 第三步:返回构建完成的教学班次索引。 + return result; + } + + /** + * 收集已经建立隶属关系的教学班次编号。 + * + * @param relations 全部教学班次隶属关系 + * @return 已分配教学班次编号集合 + */ + private Set collectAssignedTeachingClassIds( + List relations) { + // 第一步:创建结果集合,并利用 Set 自动去除异常重复编号。 + Set result = new HashSet<>(); + + // 第二步:逐条读取关系中的教学班次编号。 + for (SSJGXYD relation : relations) { + // 空关系记录不参与编号收集。 + if (relation == null) { + continue; + } + + // 仅将非空的有效教学班次编号加入结果集合。 + String teachingClassId = normalizeText(relation.getXydbh()); + if (teachingClassId != null) { + result.add(teachingClassId); + } + } + + // 第三步:返回全部已分配教学班次编号。 + return result; + } + + /** + * 将教学班次实体转换为基础展示 VO。 + * + * @param teachingClass 教学班次实体 + * @return 不包含部别信息的教学班次 VO + */ + private TeachingClassAffiliationVO toTeachingClassVO( + XYDB teachingClass) { + // 第一步:创建独立返回对象,避免对外暴露数据库实体。 + TeachingClassAffiliationVO vo = + new TeachingClassAffiliationVO(); + + // 第二步:复制教学班次的全部业务展示字段。 + copyTeachingClassFields(vo, teachingClass); + + // 第三步:返回组装完成的教学班次 VO。 + return vo; + } + + /** + * 将关系、部别和教学班次实体组合为关系 VO。 + * + * @param relation 隶属关系实体 + * @param department 部别实体;清理孤立关系时允许为 null + * @param teachingClass 教学班次实体;清理孤立关系时允许为 null + * @return 教学班次隶属关系 VO + */ + private TeachingClassAffiliationVO toTeachingClassAffiliationVO( + SSJGXYD relation, + SSJG department, + XYDB teachingClass) { + // 第一步:创建独立的关系返回对象。 + TeachingClassAffiliationVO vo = + new TeachingClassAffiliationVO(); + + // 第二步:复制关系表中保存的双方编号。 + vo.setDepartmentId(relation.getSsjgbh()); + vo.setTeachingClassId(relation.getXydbh()); + + // 第三步:关联部别仍然存在时补充部别名称。 + if (department != null) { + vo.setDepartmentName(department.getJGMC()); + } + + /* + * 第四步:关联教学班次仍然存在时补充完整展示字段。 + * 实体缺失时保留关系表中的编号,便于管理员识别并解除孤立关系。 + */ + if (teachingClass != null) { + copyTeachingClassFields(vo, teachingClass); + } + + // 第五步:返回组装完成的关系 VO。 + return vo; + } + + /** + * 复制教学班次实体中的页面展示字段。 + * + * @param vo 目标 VO + * @param teachingClass 来源教学班次实体 + */ + private void copyTeachingClassFields( + TeachingClassAffiliationVO vo, + XYDB teachingClass) { + // 第一步:复制教学班次标识、名称、简称、人数和年级。 + vo.setTeachingClassId(teachingClass.getXydbh()); + vo.setTeachingClassName(teachingClass.getXydmc()); + vo.setAbbreviation(teachingClass.getJc()); + vo.setStudentCount(teachingClass.getXydrs()); + vo.setGrade(teachingClass.getNj()); + + // 第二步:复制专业、教室、备注、状态和排序字段。 + vo.setMajorCode(teachingClass.getZydh()); + vo.setClassroomId(teachingClass.getZyjsbh()); + vo.setRemark(teachingClass.getBz()); + vo.setStatus(teachingClass.getZxzt()); + vo.setSequenceNumber(teachingClass.getXh()); + + // 第三步:复制任务类别、虚实类型和培养日期字段。 + vo.setTaskCategory(teachingClass.getRwlb()); + vo.setVirtualType(teachingClass.getXslx()); + vo.setAdmissionDate(teachingClass.getRxrq()); + vo.setGraduationDate(teachingClass.getByrq()); + + // 第四步:复制教学班次类型、培训任务和系统配置字段。 + vo.setTeachingClassType(teachingClass.getXydlx()); + vo.setTrainingTaskId(teachingClass.getPxrwbsh()); + vo.setSessionCategory(teachingClass.getJclb()); + vo.setSystemMode(teachingClass.getXtms()); + + // 第五步:复制所属单位和主体培训任务标志。 + vo.setOrganization(teachingClass.getSsdw()); + vo.setPrimaryTrainingTask(teachingClass.getZtpxrw()); + } + + /** + * 判断教学班次是否匹配页面查询关键字。 + * + * @param teachingClass 待判断教学班次;孤立关系场景允许为 null + * @param fallbackId 关系表中保存的教学班次编号 + * @param keyword 已规范化的关键字 + * @return 匹配时返回 true + */ + private boolean matchesTeachingClass( + XYDB teachingClass, + String fallbackId, + String keyword) { + // 未提供关键字时默认匹配全部教学班次。 + if (keyword == null) { + return true; + } + + // 即使教学班次实体缺失,仍允许通过关系表中的班次编号检索。 + if (containsIgnoreCase(fallbackId, keyword)) { + return true; + } + + // 教学班次实体缺失且编号未命中时不能继续匹配其他字段。 + if (teachingClass == null) { + return false; + } + + /* + * 关键字覆盖编号、名称、简称、年级、专业、所属单位、 + * 任务类别和教学班次类型,满足页面主要检索需求。 + */ + return containsIgnoreCase(teachingClass.getXydbh(), keyword) + || containsIgnoreCase(teachingClass.getXydmc(), keyword) + || containsIgnoreCase(teachingClass.getJc(), keyword) + || containsIgnoreCase(teachingClass.getNj(), keyword) + || containsIgnoreCase(teachingClass.getZydh(), keyword) + || containsIgnoreCase(teachingClass.getSsdw(), keyword) + || containsIgnoreCase(teachingClass.getRwlb(), keyword) + || containsIgnoreCase(teachingClass.getXydlx(), keyword); + } + + /** + * 判断教学班次是否匹配指定在校状态。 + * + * @param teachingClass 待判断教学班次;孤立关系场景允许为 null + * @param status 已规范化的在校状态 + * @return 匹配时返回 true + */ + private boolean matchesStatus(XYDB teachingClass, String status) { + // 未提供状态条件时不限制教学班次的在校状态。 + if (status == null) { + return true; + } + + // 实体缺失或状态为空时不能命中明确的状态筛选条件。 + if (teachingClass == null || teachingClass.getZxzt() == null) { + return false; + } + + // 在校状态采用忽略英文大小写的精确比较,避免模糊状态误匹配。 + return teachingClass.getZxzt().trim().equalsIgnoreCase(status); + } + + /** + * 判断部别是否匹配页面查询关键字。 + * + * @param department 待判断部别 + * @param keyword 已规范化的关键字 + * @return 匹配时返回 true + */ + private boolean matchesDepartment(SSJG department, String keyword) { + // 未提供关键字时默认匹配全部启用部别。 + if (keyword == null) { + return true; + } + + // 关键字可匹配部别编号、名称、简称和机关类别。 + return containsIgnoreCase(department.getBH(), keyword) + || containsIgnoreCase(department.getJGMC(), keyword) + || containsIgnoreCase(department.getJC(), keyword) + || containsIgnoreCase(department.getJGLB(), keyword); + } + + /** + * 判断教研室是否匹配页面查询关键字。 + * + * @param researchOffice 待判断教研室;孤立关系场景允许为 null + * @param fallbackId 关系表中保存的教研室编号 + * @param keyword 已规范化的关键字 + * @return 匹配时返回 true + */ + private boolean matchesResearchOffice(JYSB researchOffice, + String fallbackId, + String keyword) { + // 未提供关键字时默认匹配全部教研室关系。 + if (keyword == null) { + return true; + } + + // 即使教研室实体缺失,仍允许通过关系表中的教研室编号检索。 + if (containsIgnoreCase(fallbackId, keyword)) { + return true; + } + + // 教研室实体缺失且编号未命中时不能继续匹配其他字段。 + if (researchOffice == null) { + return false; + } + + // 关键字可匹配教研室编号、名称、简称和系别。 + return containsIgnoreCase(researchOffice.getJysdh(), keyword) + || containsIgnoreCase(researchOffice.getJysmc(), keyword) + || containsIgnoreCase(researchOffice.getJc(), keyword) + || containsIgnoreCase(researchOffice.getBx(), keyword); + } + + /** + * 判断源文本是否包含指定关键字,并忽略英文字母大小写。 + * + * @param source 源文本 + * @param keyword 查询关键字 + * @return 包含关键字时返回 true + */ + private boolean containsIgnoreCase(String source, String keyword) { + // 源文本为空时不能匹配;否则统一按 ROOT 区域规则转为小写后比较。 + return source != null + && source.toLowerCase(Locale.ROOT).contains(keyword.toLowerCase(Locale.ROOT)); + } + + /** + * 比较两个可选部别。 + * + * @param left 左侧部别 + * @param right 右侧部别 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareDepartments(AffiliationDepartmentVO left, + AffiliationDepartmentVO right) { + // 第一优先级:分级号较小的部别排在前面。 + int levelResult = compareNullableInteger(left.getLevelNumber(), right.getLevelNumber()); + if (levelResult != 0) { + return levelResult; + } + + // 第二优先级:分级号相同时按照分类号排序。 + int classificationResult = compareNullableText( + left.getClassificationCode(), right.getClassificationCode()); + if (classificationResult != 0) { + return classificationResult; + } + + // 第三优先级:前两项相同时按照部别名称排序。 + return compareNullableText(left.getName(), right.getName()); + } + + /** + * 比较两个已建立的教研室隶属关系。 + * + * @param left 左侧关系 + * @param right 右侧关系 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareAffiliations(ResearchOfficeAffiliationVO left, + ResearchOfficeAffiliationVO right) { + // 第一优先级:按照教研室序号排序。 + int sequenceResult = + compareNullableText(left.getSequenceNumber(), right.getSequenceNumber()); + if (sequenceResult != 0) { + return sequenceResult; + } + + // 第二优先级:序号相同时按照教研室名称排序。 + int nameResult = + compareNullableText(left.getResearchOfficeName(), right.getResearchOfficeName()); + if (nameResult != 0) { + return nameResult; + } + + // 第三优先级:前两项相同时按照教研室编号排序。 + return compareNullableText(left.getResearchOfficeId(), right.getResearchOfficeId()); + } + + /** + * 比较两个可选教研室。 + * + * @param left 左侧教研室 + * @param right 右侧教研室 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareResearchOffices(ResearchOfficeVO left, ResearchOfficeVO right) { + // 第一优先级:按照教研室序号排序。 + int sequenceResult = + compareNullableText(left.getSequenceNumber(), right.getSequenceNumber()); + if (sequenceResult != 0) { + return sequenceResult; + } + + // 第二优先级:序号相同时按照教研室名称排序。 + int nameResult = compareNullableText(left.getName(), right.getName()); + if (nameResult != 0) { + return nameResult; + } + + // 第三优先级:前两项相同时按照教研室编号排序。 + return compareNullableText(left.getId(), right.getId()); + } + + /** + * 比较两个教学班次 VO。 + * + * @param left 左侧教学班次 + * @param right 右侧教学班次 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareTeachingClasses( + TeachingClassAffiliationVO left, + TeachingClassAffiliationVO right) { + // 第一优先级:按照教学班次序号排序。 + int sequenceResult = + compareNullableText( + left.getSequenceNumber(), + right.getSequenceNumber()); + if (sequenceResult != 0) { + return sequenceResult; + } + + // 第二优先级:序号相同时按照教学班次名称排序。 + int nameResult = + compareNullableText( + left.getTeachingClassName(), + right.getTeachingClassName()); + if (nameResult != 0) { + return nameResult; + } + + // 第三优先级:前两项相同时按照教学班次编号排序。 + return compareNullableText( + left.getTeachingClassId(), + right.getTeachingClassId()); + } + + /** + * 比较两个允许为空的整数,空值统一排在非空值之后。 + * + * @param left 左侧整数 + * @param right 右侧整数 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareNullableInteger(Integer left, Integer right) { + // 两侧都为空时视为相等。 + if (left == null && right == null) { + return 0; + } + + // 仅左侧为空时,将左侧排在后面。 + if (left == null) { + return 1; + } + + // 仅右侧为空时,将右侧排在后面。 + if (right == null) { + return -1; + } + + // 两侧均有值时使用整数自然顺序。 + return left.compareTo(right); + } + + /** + * 比较两个允许为空的文本,空值统一排在非空值之后。 + * + * @param left 左侧文本 + * @param right 右侧文本 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareNullableText(String left, String right) { + // 两侧都为空时视为相等。 + if (left == null && right == null) { + return 0; + } + + // 仅左侧为空时,将左侧排在后面。 + if (left == null) { + return 1; + } + + // 仅右侧为空时,将右侧排在后面。 + if (right == null) { + return -1; + } + + // 优先忽略英文字母大小写进行比较。 + int ignoreCaseResult = left.compareToIgnoreCase(right); + + // 忽略大小写后相等时再比较原文本,保证排序结果稳定。 + return ignoreCaseResult == 0 ? left.compareTo(right) : ignoreCaseResult; + } + + /** + * 校验并规范化部别编号。 + * + * @param id 原始部别编号 + * @return 有效部别编号 + */ + private String requireDepartmentId(String id) { + // 使用统一标识符校验规则,并提供部别专用提示。 + return requireIdentifier(id, IDENTIFIER_MAX_LENGTH, "部别编号"); + } + + /** + * 校验并规范化教研室编号。 + * + * @param id 原始教研室编号 + * @return 有效教研室编号 + */ + private String requireResearchOfficeId(String id) { + // 使用统一标识符校验规则,并提供教研室专用提示。 + return requireIdentifier(id, RESEARCH_OFFICE_ID_MAX_LENGTH, "教研室编号"); + } + + /** + * 校验并规范化教学班次编号。 + * + * @param id 原始教学班次编号 + * @return 有效教学班次编号 + */ + private String requireTeachingClassId(String id) { + // 使用统一标识符校验规则,并提供教学班次专用提示。 + return requireIdentifier( + id, TEACHING_CLASS_ID_MAX_LENGTH, "教学班次编号"); + } + + /** + * 校验并规范化隶属关系编号。 + * + * @param id 原始隶属关系编号 + * @return 有效隶属关系编号 + */ + private String requireRelationId(String id) { + // 使用统一标识符校验规则,并提供隶属关系专用提示。 + return requireIdentifier(id, IDENTIFIER_MAX_LENGTH, "隶属关系编号"); + } + + /** + * 校验并规范化通用标识符。 + * + * @param id 原始标识符 + * @param maxLength 最大允许长度 + * @param fieldName 用于错误提示的字段名称 + * @return 去除首尾空白后的有效标识符 + */ + private String requireIdentifier(String id, int maxLength, String fieldName) { + // 第一步:去除首尾空白,并将空字符串统一转换为 null。 + String normalizedId = normalizeText(id); + + // 第二步:规范化后为空时返回参数错误。 + if (normalizedId == null) { + throw new BusinessException(BAD_REQUEST, fieldName + "不能为空"); + } + + // 第三步:超过数据库字段允许长度时返回参数错误。 + if (normalizedId.length() > maxLength) { + throw new BusinessException(BAD_REQUEST, fieldName + "格式不正确"); + } + + // 第四步:返回通过校验的规范化标识符。 + return normalizedId; + } + + /** + * 统一规范化可选文本。 + * + * @param value 原始文本 + * @return 去除首尾空白后的文本;null 或空字符串返回 null + */ + private String normalizeText(String value) { + // 原始值为 null 时直接返回 null。 + if (value == null) { + return null; + } + + // 去除用户输入或数据库文本中的首尾空白。 + String normalized = value.trim(); + + // 去除空白后没有有效内容时统一返回 null。 + return normalized.isEmpty() ? null : normalized; + } + + // ============================================================ + // 人员隶属关系统一接口实现(方向1新增) + // ============================================================ + + /** + * 查询人员当前隶属关系。 + *

按人员类型路由到对应表查询,departmentId可选用于筛选当前归属。

+ */ + @Override + public List listPersonnelAffiliations(PersonnelAffiliationDTO dto) { + // 校验人员类型 + requireValidPersonnelType(dto.getPersonnelType()); + + List result = new ArrayList<>(); + String keyword = normalizeText(dto.getKeyword()); + + switch (dto.getPersonnelType()) { + case TYPE_TEACHER: + // 查询全部教员,按教研室筛选 + List teachers = jybMapper.selectList(null); + for (JYB teacher : teachers) { + if (teacher == null) { + continue; + } + // departmentId 非空时仅返回该教研室下教员 + if (dto.getDepartmentId() != null + && !dto.getDepartmentId().equals(teacher.getJysdh())) { + continue; + } + // 关键字过滤(姓名/拼音) + if (keyword != null && !matchKeyword(teacher.getJyxm(), teacher.getPy(), keyword)) { + continue; + } + result.add(buildTeacherVO(teacher)); + } + break; + + case TYPE_STUDENT: + // 查询全部学员,按学员队期筛选 + List students = xyxxMapper.selectList(null); + for (XYXX student : students) { + if (student == null) { + continue; + } + if (dto.getDepartmentId() != null + && !dto.getDepartmentId().equals(student.getXydqbh())) { + continue; + } + if (keyword != null && !matchKeyword(student.getXm(), student.getPy(), keyword)) { + continue; + } + result.add(buildStudentVO(student)); + } + break; + + case TYPE_DEPT_PERSONNEL: + // 查询全部机关参谋,按实施_机关编号筛选 + List personnelList = ssjgcmMapper.selectList(null); + for (SSJGCM personnel : personnelList) { + if (personnel == null) { + continue; + } + if (dto.getDepartmentId() != null + && !dto.getDepartmentId().equals(personnel.getSsjgbh())) { + continue; + } + if (keyword != null && !matchKeyword(personnel.getYhxm(), personnel.getPy(), keyword)) { + continue; + } + result.add(buildDeptPersonnelVO(personnel)); + } + break; + + default: + throw new BusinessException(BAD_REQUEST, "不支持的人员类型: " + dto.getPersonnelType()); + } + + return result; + } + + /** + * 分配人员到部门(首次归属,不写异动历史)。 + *

若人员当前已有归属,拒绝分配,提示走异动接口。

+ */ + @Override + @Transactional(rollbackFor = Exception.class) + public PersonnelAffiliationVO assignPersonnel(PersonnelAffiliationDTO dto) { + requireValidPersonnelType(dto.getPersonnelType()); + requireNonBlank(dto.getDepartmentId(), "部门编号不能为空"); + requireNonBlank(dto.getPersonnelId(), "人员编号不能为空"); + + switch (dto.getPersonnelType()) { + case TYPE_TEACHER: + JYB teacher = requireTeacher(dto.getPersonnelId()); + // 已有归属时拒绝分配,避免覆盖原归属导致历史丢失 + if (teacher.getJysdh() != null && !teacher.getJysdh().isEmpty()) { + throw new BusinessException(CONFLICT, + "教员已有归属教研室,请使用异动调整接口"); + } + teacher.setJysdh(dto.getDepartmentId()); + jybMapper.updateById(teacher); + return buildTeacherVO(teacher); + + case TYPE_STUDENT: + XYXX student = requireStudent(dto.getPersonnelId()); + if (student.getXydqbh() != null && !student.getXydqbh().isEmpty()) { + throw new BusinessException(CONFLICT, + "学员已有归属学员队期,请使用异动调整接口"); + } + student.setXydqbh(dto.getDepartmentId()); + xyxxMapper.updateById(student); + return buildStudentVO(student); + + case TYPE_DEPT_PERSONNEL: + SSJGCM personnel = requireDeptPersonnel(dto.getPersonnelId()); + if (personnel.getSsjgbh() != null && !personnel.getSsjgbh().isEmpty()) { + throw new BusinessException(CONFLICT, + "机关参谋已有归属部门,请使用异动调整接口"); + } + personnel.setSsjgbh(dto.getDepartmentId()); + ssjgcmMapper.updateById(personnel); + return buildDeptPersonnelVO(personnel); + + default: + throw new BusinessException(BAD_REQUEST, "不支持的人员类型"); + } + } + + /** + * 解除人员隶属关系(清空当前归属,不写异动历史)。 + *

previousDept 保留不变,作为最近一次异动前的记录。

+ */ + @Override + @Transactional(rollbackFor = Exception.class) + public PersonnelAffiliationVO unassignPersonnel(PersonnelAffiliationDTO dto) { + requireValidPersonnelType(dto.getPersonnelType()); + requireNonBlank(dto.getPersonnelId(), "人员编号不能为空"); + + switch (dto.getPersonnelType()) { + case TYPE_TEACHER: + JYB teacher = requireTeacher(dto.getPersonnelId()); + jybMapper.update(null, new LambdaUpdateWrapper() + .set(JYB::getJysdh, "") + .eq(JYB::getJybh, dto.getPersonnelId())); + teacher.setJysdh(null); + return buildTeacherVO(teacher); + + case TYPE_STUDENT: + XYXX student = requireStudent(dto.getPersonnelId()); + xyxxMapper.update(null, new LambdaUpdateWrapper() + .set(XYXX::getXydqbh, "") + .eq(XYXX::getBh, dto.getPersonnelId())); + student.setXydqbh(null); + return buildStudentVO(student); + + case TYPE_DEPT_PERSONNEL: + SSJGCM personnel = requireDeptPersonnel(dto.getPersonnelId()); + ssjgcmMapper.update(null, new LambdaUpdateWrapper() + .set(SSJGCM::getSsjgbh, "") + .eq(SSJGCM::getBh, dto.getPersonnelId())); + personnel.setSsjgbh(null); + return buildDeptPersonnelVO(personnel); + + default: + throw new BusinessException(BAD_REQUEST, "不支持的人员类型"); + } + } + + /** + * 人员异动调整(核心接口)。 + *

完整流程:校验人员存在 → 取旧归属 → 追加异动历史JSON → + * 更新previousDept和当前归属 → 校验新归属在关联表中已有部门关系。

+ */ + @Override + @Transactional(rollbackFor = Exception.class) + public PersonnelAffiliationVO transferPersonnel(PersonnelTransferDTO dto) { + requireValidPersonnelType(dto.getPersonnelType()); + requireNonBlank(dto.getPersonnelId(), "人员编号不能为空"); + requireNonBlank(dto.getToDepartmentId(), "目标部门编号不能为空"); + requireNonBlank(dto.getReason(), "异动原因不能为空"); + + String transferTime = LocalDateTime.now().format(HISTORY_TIME_FORMATTER); + + switch (dto.getPersonnelType()) { + case TYPE_TEACHER: + return transferTeacher(dto, transferTime); + + case TYPE_STUDENT: + return transferStudent(dto, transferTime); + + case TYPE_DEPT_PERSONNEL: + return transferDeptPersonnel(dto, transferTime); + + default: + throw new BusinessException(BAD_REQUEST, "不支持的人员类型"); + } + } + + /** + * 查询人员异动历史。 + */ + @Override + public TransferHistoryVO getTransferHistory(PersonnelAffiliationDTO dto) { + requireValidPersonnelType(dto.getPersonnelType()); + requireNonBlank(dto.getPersonnelId(), "人员编号不能为空"); + + TransferHistoryVO vo = new TransferHistoryVO(); + vo.setPersonnelType(dto.getPersonnelType()); + vo.setPersonnelId(dto.getPersonnelId()); + + switch (dto.getPersonnelType()) { + case TYPE_TEACHER: + JYB teacher = requireTeacher(dto.getPersonnelId()); + vo.setPersonnelName(teacher.getJyxm()); + vo.setTransferHistory(teacher.getTransferHistory()); + break; + + case TYPE_STUDENT: + XYXX student = requireStudent(dto.getPersonnelId()); + vo.setPersonnelName(student.getXm()); + vo.setTransferHistory(student.getTransferHistory()); + break; + + case TYPE_DEPT_PERSONNEL: + SSJGCM personnel = requireDeptPersonnel(dto.getPersonnelId()); + vo.setPersonnelName(personnel.getYhxm()); + vo.setTransferHistory(personnel.getTransferHistory()); + break; + + default: + throw new BusinessException(BAD_REQUEST, "不支持的人员类型"); + } + + return vo; + } + + // ============ 异动调整私有方法(按人员类型拆分,保持可读性)============ + + /** + * 教员异动:更新教研室代号 + 校验新教研室在SSJGJYS中已有部门关系。 + */ + private PersonnelAffiliationVO transferTeacher(PersonnelTransferDTO dto, String transferTime) { + JYB teacher = requireTeacher(dto.getPersonnelId()); + String oldDept = teacher.getJysdh(); + + // 追加异动历史JSON + teacher.setTransferHistory(appendHistoryJson( + teacher.getTransferHistory(), oldDept, dto.getToDepartmentId(), + transferTime, dto.getReason())); + // 记录上一个归属 + teacher.setPreviousDept(oldDept); + // 更新当前归属 + teacher.setJysdh(dto.getToDepartmentId()); + jybMapper.updateById(teacher); + + // 校验新教研室在SSJGJYS中已建立部门关系,否则账户管理JOIN取不到dataScope + ensureResearchOfficeHasDepartment(dto.getToDepartmentId()); + + return buildTeacherVO(teacher); + } + + /** + * 学员异动:更新学员队期编号 + 校验新学员队在SSJGXYD中已有部门关系。 + */ + private PersonnelAffiliationVO transferStudent(PersonnelTransferDTO dto, String transferTime) { + XYXX student = requireStudent(dto.getPersonnelId()); + String oldDept = student.getXydqbh(); + + student.setTransferHistory(appendHistoryJson( + student.getTransferHistory(), oldDept, dto.getToDepartmentId(), + transferTime, dto.getReason())); + student.setPreviousDept(oldDept); + student.setXydqbh(dto.getToDepartmentId()); + xyxxMapper.updateById(student); + + // 校验新学员队在SSJGXYD中已建立部门关系 + ensureTeachingClassHasDepartment(dto.getToDepartmentId()); + + return buildStudentVO(student); + } + + /** + * 机关参谋异动:更新实施_机关编号(无需同步关联表,机关参谋直接挂部门)。 + */ + private PersonnelAffiliationVO transferDeptPersonnel(PersonnelTransferDTO dto, String transferTime) { + SSJGCM personnel = requireDeptPersonnel(dto.getPersonnelId()); + String oldDept = personnel.getSsjgbh(); + + personnel.setTransferHistory(appendHistoryJson( + personnel.getTransferHistory(), oldDept, dto.getToDepartmentId(), + transferTime, dto.getReason())); + personnel.setPreviousDept(oldDept); + personnel.setSsjgbh(dto.getToDepartmentId()); + ssjgcmMapper.updateById(personnel); + + return buildDeptPersonnelVO(personnel); + } + + // ============ 关联表校验(防护点:保证AccountManagement/NoticeAnnouncement能JOIN到部门)============ + + /** + * 校验教研室在SSJGJYS中已有所属部门。 + *

若无关系记录,账户管理RESEARCH_OFFICE角色查询的dataScope将为空, + * 通知公告按部门推送时也会漏掉该教研室账户。

+ */ + private void ensureResearchOfficeHasDepartment(String researchOfficeId) { + List relations = ssjgJysMapper.selectResearchOfficeRelationsByResearchOfficeId( + researchOfficeId); + if (relations == null || relations.isEmpty()) { + throw new BusinessException(BAD_REQUEST, + "目标教研室尚未建立部门隶属关系,请先在教研室隶属关系设置中分配部门"); + } + } + + /** + * 校验学员队在SSJGXYD中已有所属部门。 + */ + private void ensureTeachingClassHasDepartment(String teachingClassId) { + SSJGXYD relation = ssjgXydMapper.selectTeachingClassRelationByTeachingClassId( + teachingClassId); + if (relation == null) { + throw new BusinessException(BAD_REQUEST, + "目标学员队尚未建立部门隶属关系,请先在教学班次隶属关系设置中分配部门"); + } + } + + // ============ VO组装(统一三类人员返回结构)============ + + private PersonnelAffiliationVO buildTeacherVO(JYB teacher) { + PersonnelAffiliationVO vo = new PersonnelAffiliationVO(); + vo.setPersonnelType(TYPE_TEACHER); + vo.setPersonnelId(teacher.getJybh()); + vo.setPersonnelName(teacher.getJyxm()); + vo.setCurrentDepartmentId(toNullIfEmpty(teacher.getJysdh())); + vo.setPreviousDepartmentId(toNullIfEmpty(teacher.getPreviousDept())); + // 查教研室名称用于展示 + if (teacher.getJysdh() != null && !teacher.getJysdh().isEmpty()) { + JYSB office = jysbMapper.selectById(teacher.getJysdh()); + if (office != null) { + vo.setCurrentDepartmentName(office.getJysmc()); + } + } + return vo; + } + + private PersonnelAffiliationVO buildStudentVO(XYXX student) { + PersonnelAffiliationVO vo = new PersonnelAffiliationVO(); + vo.setPersonnelType(TYPE_STUDENT); + vo.setPersonnelId(student.getBh()); + vo.setPersonnelName(student.getXm()); + vo.setCurrentDepartmentId(toNullIfEmpty(student.getXydqbh())); + vo.setPreviousDepartmentId(toNullIfEmpty(student.getPreviousDept())); + return vo; + } + + private PersonnelAffiliationVO buildDeptPersonnelVO(SSJGCM personnel) { + PersonnelAffiliationVO vo = new PersonnelAffiliationVO(); + vo.setPersonnelType(TYPE_DEPT_PERSONNEL); + vo.setPersonnelId(personnel.getBh()); + vo.setPersonnelName(personnel.getYhxm()); + vo.setCurrentDepartmentId(toNullIfEmpty(personnel.getSsjgbh())); + vo.setPreviousDepartmentId(toNullIfEmpty(personnel.getPreviousDept())); + // 查机关名称用于展示 + if (personnel.getSsjgbh() != null && !personnel.getSsjgbh().isEmpty()) { + SSJG department = ssjgMapper.selectById(personnel.getSsjgbh()); + if (department != null) { + vo.setCurrentDepartmentName(department.getJGMC()); + } + } + return vo; + } + + private String toNullIfEmpty(String value) { + return (value == null || value.isEmpty()) ? null : value; + } + + // ============ 异动历史JSON处理(无第三方库依赖,手工拼接保证可控)============ + + /** + * 追加一条异动历史记录到JSON数组。 + *

格式:[{"from":"旧编号","to":"新编号","time":"时间","reason":"原因"}] + * 空值或空数组时创建新数组。

+ */ + private String appendHistoryJson(String existing, String from, String to, + String time, String reason) { + String entry = String.format( + "{\"from\":%s,\"to\":%s,\"time\":%s,\"reason\":%s}", + toJsonString(from), toJsonString(to), + toJsonString(time), toJsonString(reason)); + + if (existing == null || existing.isEmpty() || "[]".equals(existing)) { + return "[" + entry + "]"; + } + // 在最后一个 ] 前插入新条目 + return existing.substring(0, existing.length() - 1) + "," + entry + "]"; + } + + /** 字符串转JSON安全格式(处理null和转义引号) */ + private String toJsonString(String value) { + if (value == null) { + return "null"; + } + return "\"" + value.replace("\\", "\\\\").replace("\"", "\\\"") + "\""; + } + + // ============ 通用校验与工具方法 ============ + + private void requireValidPersonnelType(String type) { + if (type == null || type.isEmpty()) { + throw new BusinessException(BAD_REQUEST, "人员类型不能为空"); + } + if (!TYPE_TEACHER.equals(type) && !TYPE_STUDENT.equals(type) + && !TYPE_DEPT_PERSONNEL.equals(type)) { + throw new BusinessException(BAD_REQUEST, + "不支持的人员类型: " + type + ",支持:TEACHER/STUDENT/DEPARTMENT_PERSONNEL"); + } + } + + private void requireNonBlank(String value, String message) { + if (value == null || value.isEmpty()) { + throw new BusinessException(BAD_REQUEST, message); + } + } + + private JYB requireTeacher(String id) { + JYB teacher = jybMapper.selectById(id); + if (teacher == null) { + throw new BusinessException(NOT_FOUND, "教员不存在: " + id); + } + return teacher; + } + + private XYXX requireStudent(String id) { + XYXX student = xyxxMapper.selectById(id); + if (student == null) { + throw new BusinessException(NOT_FOUND, "学员不存在: " + id); + } + return student; + } + + private SSJGCM requireDeptPersonnel(String id) { + SSJGCM personnel = ssjgcmMapper.selectById(id); + if (personnel == null) { + throw new BusinessException(NOT_FOUND, "机关参谋不存在: " + id); + } + return personnel; + } + + /** 关键字匹配(姓名或拼音包含) */ + private boolean matchKeyword(String name, String pinyin, String keyword) { + if (keyword == null) { + return true; + } + String lowerKeyword = keyword.toLowerCase(Locale.ROOT); + if (name != null && name.toLowerCase(Locale.ROOT).contains(lowerKeyword)) { + return true; + } + if (pinyin != null && pinyin.toLowerCase(Locale.ROOT).contains(lowerKeyword)) { + return true; + } + return false; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/BZLBServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/BZLBServiceImpl.java new file mode 100644 index 000000000..15a5e59dc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/BZLBServiceImpl.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.BZLB; +import com.roomroot.jwgl.mapper.BZLBMapper; +import com.roomroot.jwgl.service.BZLBService; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 保障类别 Service 实现 + */ +@Service +public class BZLBServiceImpl implements BZLBService { + + @Resource + private BZLBMapper bzlbMapper; + + @Override + public void add(BZLB bzlb) { + bzlb.setBh(UuidUtil.getUUID()); + if (bzlb.getTy() == null) { + bzlb.setTy(0); + } + bzlbMapper.insert(bzlb); + } + + @Override + public void delete(String bh) { + bzlbMapper.deleteById(bh); + } + + @Override + public void update(BZLB bzlb) { + bzlbMapper.updateById(bzlb); + } + + @Override + public BZLB getByBh(String bh) { + return bzlbMapper.selectById(bh); + } + + @Override + public Page list(int pageNum, int pageSize, BZLB bzlb) { + Page page = new Page<>(pageNum, pageSize); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (bzlb != null) { + wrapper.like(bzlb.getMc() != null, BZLB::getMc, bzlb.getMc()); + wrapper.eq(bzlb.getBmbh() != null, BZLB::getBmbh, bzlb.getBmbh()); + wrapper.eq(bzlb.getTy() != null, BZLB::getTy, bzlb.getTy()); + } + wrapper.orderByDesc(BZLB::getBh); + return bzlbMapper.selectPage(page, wrapper); + } + + @Override + public List all() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(BZLB::getTy, 0); + return bzlbMapper.selectList(wrapper); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/BuildServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/BuildServiceImpl.java new file mode 100644 index 000000000..57bee3907 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/BuildServiceImpl.java @@ -0,0 +1,211 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JXLB; +import com.roomroot.jwgl.mapper.BuildMapper; +import com.roomroot.jwgl.service.BuildService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.UuidUtil; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLEncoder; +import java.util.List; + +/** + * 教学楼表服务实现类 + */ +@Service +public class BuildServiceImpl implements BuildService { + + @Resource + private BuildMapper buildMapper; + + @Override + public void add(JXLB jxlb) { + String uuid = UuidUtil.getUUID(); + jxlb.setId(uuid); + jxlb.setDelFlag(0); + buildMapper.insert(jxlb); + } + + @Override + public void delete(String id) { + JXLB jxlb = getById(id); + jxlb.setDelFlag(1); + buildMapper.updateById(jxlb); + } + + @Override + public void update(JXLB jxlb) { + buildMapper.updateById(jxlb); + } + + @Override + public JXLB getById(String id) { + return buildMapper.selectById(id); + } + + @Override + public PageResult pageList(PageQuery query, JXLB jxlb) { + jxlb.setDelFlag(0); + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = buildMapper.selectPageList(page, jxlb); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List list(JXLB jxlb) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jxlb != null) { + if (jxlb.getJxldh() != null && !jxlb.getJxldh().isEmpty()) { + wrapper.like(JXLB::getJxldh, jxlb.getJxldh()); + } + if (jxlb.getJxlmc() != null && !jxlb.getJxlmc().isEmpty()) { + wrapper.like(JXLB::getJxlmc, jxlb.getJxlmc()); + } + if (jxlb.getDd() != null && !jxlb.getDd().isEmpty()) { + wrapper.like(JXLB::getDd, jxlb.getDd()); + } + } + wrapper.orderByAsc(JXLB::getXh); + return buildMapper.selectList(wrapper); + } + + @Override + public int importExcel(MultipartFile file) { + try (InputStream is = file.getInputStream(); + Workbook workbook = file.getOriginalFilename().endsWith(".xlsx") + ? new XSSFWorkbook(is) : new HSSFWorkbook(is)) { + + Sheet sheet = workbook.getSheetAt(0); + int count = 0; + + // 在循环外获取起始序号 + int nextNum = getNextNumber(); + + // 从第二行开始读取数据(跳过表头) + for (int i = 1; i <= sheet.getLastRowNum(); i++) { + Row row = sheet.getRow(i); + if (row == null) continue; + + JXLB jxlb = new JXLB(); + + // 生成序号(数字字符串)和教学楼代号(三位补零) + jxlb.setXh(String.valueOf(nextNum)); + jxlb.setJxldh(String.format("%03d", nextNum)); + nextNum++; // 递增序号 + + // 教学楼名称 + jxlb.setJxlmc(getCellValue(row, 2)); + + // 地点 + jxlb.setDd(getCellValue(row, 3)); + + // 备注 + jxlb.setBz(getCellValue(row, 4)); + + jxlb.setId(UuidUtil.getUUID()); + jxlb.setDelFlag(0); + + buildMapper.insert(jxlb); + count++; + } + return count; + } catch (Exception e) { + throw new RuntimeException("Excel解析失败: " + e.getMessage(), e); + } + } + + @Override + public void downloadTemplate(HttpServletResponse response) { + try { + // 从resources/template目录读取模板文件 + ClassPathResource resource = new ClassPathResource("template/template_jxcdgl.xls"); + InputStream is = resource.getInputStream(); + + // 设置响应头 + response.setContentType("application/vnd.ms-excel"); + response.setHeader("Content-Disposition", "attachment;filename=" + + URLEncoder.encode("教学场地管理模板.xls", "UTF-8")); + + // 输出文件 + OutputStream os = response.getOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + os.flush(); + is.close(); + os.close(); + } catch (Exception e) { + throw new RuntimeException("下载模板失败: " + e.getMessage(), e); + } + } + + @Override + public String generateNextXh() { + int nextNum = getNextNumber(); + return String.format("%03d", nextNum); + } + + /** + * 获取下一个序号(数字) + */ + private int getNextNumber() { + String maxXh = buildMapper.selectMaxXh(); + if (maxXh == null || maxXh.isEmpty()) { + return 1; + } + try { + return Integer.parseInt(maxXh) + 1; + } catch (NumberFormatException e) { + return 1; + } + } + + /** + * 获取单元格字符串值 + */ + private String getCellValue(Row row, int index) { + Cell cell = row.getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); + if (cell == null) { + return null; + } + + CellType type = cell.getCellType(); + // 处理公式单元格 + if (type == CellType.FORMULA) { + type = cell.getCachedFormulaResultType(); + } + + switch (type) { + case NUMERIC: + double num = cell.getNumericCellValue(); + // 整数就去掉小数点后多余的 .0 + if (num == Math.floor(num)) { + return String.valueOf((long) num); + } else { + return String.valueOf(num); + } + case STRING: + return cell.getStringCellValue().trim(); + case BOOLEAN: + return String.valueOf(cell.getBooleanCellValue()); + default: + return null; + } + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassRoomCalendarServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassRoomCalendarServiceImpl.java new file mode 100644 index 000000000..8234f6b02 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassRoomCalendarServiceImpl.java @@ -0,0 +1,101 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JXCDL; +import com.roomroot.jwgl.mapper.ClassRoomCalendarMapper; +import com.roomroot.jwgl.service.ClassRoomCalendarService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 教学场地历服务实现类 + */ +@Service +public class ClassRoomCalendarServiceImpl implements ClassRoomCalendarService { + + @Resource + private ClassRoomCalendarMapper jxcdlMapper; + + @Override + public void add(JXCDL jxcdl) { + jxcdl.setId(UuidUtil.getUUID()); + jxcdl.setDelFlag(0); + jxcdl.setCjsj(LocalDateTime.now()); + jxcdlMapper.insert(jxcdl); + } + + @Override + public void delete(String id) { + JXCDL jxcdl = getById(id); + jxcdl.setDelFlag(1); + jxcdlMapper.updateById(jxcdl); + } + + @Override + public void update(JXCDL jxcdl) { + jxcdlMapper.updateById(jxcdl); + } + + @Override + public JXCDL getById(String id) { + return jxcdlMapper.selectById(id); + } + + @Override + public PageResult pageList(PageQuery query, JXCDL jxcdl) { + jxcdl.setDelFlag(0); + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = jxcdlMapper.selectPageList(page, jxcdl); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List list(JXCDL jxcdl) { + LambdaQueryWrapper wrapper = buildQueryWrapper(jxcdl); + return jxcdlMapper.selectList(wrapper); + } + + @Override + public List listByJsbh(String jsbh) { + return jxcdlMapper.selectByJsbh(jsbh); + } + + @Override + public List listByRq(String rq) { + return jxcdlMapper.selectByRq(rq); + } + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(JXCDL jxcdl) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jxcdl != null) { + if (jxcdl.getBh() != null && !jxcdl.getBh().isEmpty()) { + wrapper.like(JXCDL::getBh, jxcdl.getBh()); + } + if (jxcdl.getMc() != null && !jxcdl.getMc().isEmpty()) { + wrapper.like(JXCDL::getMc, jxcdl.getMc()); + } + if (jxcdl.getJsbh() != null && !jxcdl.getJsbh().isEmpty()) { + wrapper.eq(JXCDL::getJsbh, jxcdl.getJsbh()); + } + if (jxcdl.getKpk() != null) { + wrapper.eq(JXCDL::getKpk, jxcdl.getKpk()); + } + if (jxcdl.getJc() != null) { + wrapper.eq(JXCDL::getJc, jxcdl.getJc()); + } + } + wrapper.orderByAsc(JXCDL::getRq, JXCDL::getJc); + return wrapper; + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassRoomJSBKCBServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassRoomJSBKCBServiceImpl.java new file mode 100644 index 000000000..864374058 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassRoomJSBKCBServiceImpl.java @@ -0,0 +1,90 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JSBKCB; +import com.roomroot.jwgl.mapper.ClassRoomLessonMapper; +import com.roomroot.jwgl.service.ClassRoomLessonService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 教室输出课程表服务实现类 + */ +@Service +public class ClassRoomJSBKCBServiceImpl implements ClassRoomLessonService { + + @Resource + private ClassRoomLessonMapper jsbkcbMapper; + + @Override + public void add(JSBKCB jsbkcb) { + jsbkcb.setDelFlag(0); + jsbkcb.setId(UuidUtil.getUUID()); + jsbkcb.setGxsj(LocalDateTime.now()); + jsbkcbMapper.insert(jsbkcb); + } + + @Override + public void delete(String id) { + JSBKCB jsbkcb = getById(id); + jsbkcb.setDelFlag(1); + jsbkcbMapper.updateById(jsbkcb); + } + + @Override + public void update(JSBKCB jsbkcb) { + jsbkcbMapper.updateById(jsbkcb); + } + + @Override + public JSBKCB getById(String id) { + return jsbkcbMapper.selectById(id); + } + + @Override + public JSBKCB getByJsbhAndNd(String jsbh, Integer nd) { + return jsbkcbMapper.selectByJsbhAndNd(jsbh, nd); + } + + @Override + public PageResult pageList(PageQuery query, JSBKCB jsbkcb) { + jsbkcb.setDelFlag(0); + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = jsbkcbMapper.selectPageList(page, jsbkcb); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List list(JSBKCB jsbkcb) { + LambdaQueryWrapper wrapper = buildQueryWrapper(jsbkcb); + return jsbkcbMapper.selectList(wrapper); + } + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(JSBKCB jsbkcb) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jsbkcb != null) { + if (jsbkcb.getNd() != null) { + wrapper.eq(JSBKCB::getNd, jsbkcb.getNd()); + } + if (jsbkcb.getJsbh() != null && !jsbkcb.getJsbh().isEmpty()) { + wrapper.like(JSBKCB::getJsbh, jsbkcb.getJsbh()); + } + if (jsbkcb.getYkb() != null) { + wrapper.eq(JSBKCB::getYkb, jsbkcb.getYkb()); + } + } + wrapper.orderByDesc(JSBKCB::getGxsj); + return wrapper; + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassRoomServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassRoomServiceImpl.java new file mode 100644 index 000000000..746c8169d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassRoomServiceImpl.java @@ -0,0 +1,97 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JSB; +import com.roomroot.jwgl.mapper.ClassRoomMapper; +import com.roomroot.jwgl.service.ClassRoomService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 教室表服务实现类 + */ +@Service +public class ClassRoomServiceImpl implements ClassRoomService { + + @Resource + private ClassRoomMapper jsbMapper; + + @Override + public void add(JSB jsb) { + jsb.setId(UuidUtil.getUUID()); + jsb.setDelFlag(0); + jsbMapper.insert(jsb); + } + + @Override + public void delete(String id) { + JSB jsb = getById(id); + jsb.setDelFlag(1); + jsbMapper.updateById(jsb); + } + + @Override + public void update(JSB jsb) { + jsbMapper.updateById(jsb); + } + + @Override + public JSB getById(String id) { + return jsbMapper.selectById(id); + } + + @Override + public PageResult pageList(PageQuery query, JSB jsb) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + jsb.setDelFlag(0); + Page result = jsbMapper.selectPageList(page, jsb); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List list(JSB jsb) { + LambdaQueryWrapper wrapper = buildQueryWrapper(jsb); + return jsbMapper.selectList(wrapper); + } + + @Override + public List listByJxldh(String jxldh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(JSB::getJxldh, jxldh); + wrapper.orderByAsc(JSB::getXh); + return jsbMapper.selectList(wrapper); + } + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(JSB jsb) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jsb != null) { + if (jsb.getJsbh() != null && !jsb.getJsbh().isEmpty()) { + wrapper.like(JSB::getJsbh, jsb.getJsbh()); + } + if (jsb.getJsmc() != null && !jsb.getJsmc().isEmpty()) { + wrapper.like(JSB::getJsmc, jsb.getJsmc()); + } + if (jsb.getJxldh() != null && !jsb.getJxldh().isEmpty()) { + wrapper.eq(JSB::getJxldh, jsb.getJxldh()); + } + if (jsb.getJxcdlx() != null && !jsb.getJxcdlx().isEmpty()) { + wrapper.eq(JSB::getJxcdlx, jsb.getJxcdlx()); + } + if (jsb.getTy() != null) { + wrapper.eq(JSB::getTy, jsb.getTy()); + } + } + wrapper.orderByAsc(JSB::getXh); + return wrapper; + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassSemesterCalendarServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassSemesterCalendarServiceImpl.java new file mode 100644 index 000000000..243397fbb --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassSemesterCalendarServiceImpl.java @@ -0,0 +1,206 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.roomroot.jwgl.entity.XYDNDXQJBXXB; +import com.roomroot.jwgl.entity.XYDRWB; +import com.roomroot.jwgl.mapper.ClassSemesterCalendarMapper; +import com.roomroot.jwgl.mapper.ClassSemesterMapper; +import com.roomroot.jwgl.service.ClassSemesterCalendarService; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import jakarta.annotation.Resource; +import java.util.*; + +/** + * 学员队任务表服务实现类 + */ +@Service +public class ClassSemesterCalendarServiceImpl implements ClassSemesterCalendarService { + + @Resource + private ClassSemesterCalendarMapper classSemesterCalendarMapper; + + @Resource + private ClassSemesterMapper classSemesterMapper; + + @Override + public void update(XYDRWB xydrwb) { + classSemesterCalendarMapper.updateById(xydrwb); + } + + @Override + public XYDRWB getByBh(String bh) { + return classSemesterCalendarMapper.selectById(bh); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Map batchCopy(List sourceBhList, List targetBhList) { + List failedSourceBhs = new ArrayList<>(); + List failedTargetBhs = new ArrayList<>(); + int count = 0; + + for (String targetBh : targetBhList) { + // 根据目标学员队期表编号,获取学员队期表的学员队编号 + XYDNDXQJBXXB targetSemester = classSemesterMapper.selectById(targetBh); + if (targetSemester == null) { + failedTargetBhs.add(targetBh); + continue; + } + String newXydbh = targetSemester.getXydbh(); + + for (String sourceBh : sourceBhList) { + XYDRWB source = classSemesterCalendarMapper.selectById(sourceBh); + if (source == null) { + if (!failedSourceBhs.contains(sourceBh)) { + failedSourceBhs.add(sourceBh); + } + continue; + } + + // 复制源记录,替换编号、学员队学期编号和学员队编号 + XYDRWB newRecord = new XYDRWB(); + newRecord.setBh(UuidUtil.getUUID()); + newRecord.setNd(source.getNd()); + newRecord.setXydbh(newXydbh); + newRecord.setKbh(source.getKbh()); + newRecord.setXqdc(source.getXqdc()); + newRecord.setJybh(source.getJybh()); + newRecord.setKcxh(source.getKcxh()); + newRecord.setJsbh(source.getJsbh()); + newRecord.setRs(source.getRs()); + newRecord.setZks(source.getZks()); + newRecord.setXs(source.getXs()); + newRecord.setKlx(source.getKlx()); + newRecord.setJc(source.getJc()); + newRecord.setKsdd(source.getKsdd()); + newRecord.setKsbh(source.getKsbh()); + newRecord.setXz(source.getXz()); + newRecord.setJysdh(source.getJysdh()); + newRecord.setJysjhjybh(source.getJysjhjybh()); + newRecord.setJysjhbz(source.getJysjhbz()); + newRecord.setJhkcxh(source.getJhkcxh()); + newRecord.setXhbs(source.getXhbs()); + newRecord.setXydxqbh(targetBh); + newRecord.setBz(source.getBz()); + newRecord.setXf(source.getXf()); + newRecord.setKsks(source.getKsks()); + newRecord.setBz2(source.getBz2()); + newRecord.setCjfz(source.getCjfz()); + newRecord.setBjrxypjf(source.getBjrxypjf()); + newRecord.setLlxs(source.getLlxs()); + newRecord.setSjxs(source.getSjxs()); + newRecord.setKsksbxs(source.getKsksbxs()); + newRecord.setPdxh(source.getPdxh()); + newRecord.setPdqsz(source.getPdqsz()); + newRecord.setPdaz(source.getPdaz()); + newRecord.setPdzzksj(source.getPdzzksj()); + newRecord.setPdbz(source.getPdbz()); + newRecord.setPdtbykxxbz(source.getPdtbykxxbz()); + newRecord.setPdzdyxsfbsj(source.getPdzdyxsfbsj()); + newRecord.setPdqyzdyxs(source.getPdqyzdyxs()); + newRecord.setDelFlag(0); + + classSemesterCalendarMapper.insert(newRecord); + count++; + } + } + + Map result = new HashMap<>(); + result.put("count", count); + result.put("failedSourceBhs", failedSourceBhs); + result.put("failedTargetBhs", failedTargetBhs); + return result; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public Map batchCopyBySemester(String sourceXydxqbh, List targetBhList) { + List failedTargetBhs = new ArrayList<>(); + int count = 0; + + // 查询源学员队期表编号下的所有任务条目 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(XYDRWB::getXydxqbh, sourceXydxqbh) + .eq(XYDRWB::getDelFlag, 0); + List sourceList = classSemesterCalendarMapper.selectList(queryWrapper); + + if (CollectionUtils.isEmpty(sourceList) || CollectionUtils.isEmpty(targetBhList)) { + Map result = new HashMap<>(); + result.put("count", 0); + result.put("failedTargetBhs", failedTargetBhs); + return result; + } + + for (String targetBh : targetBhList) { + // 根据目标学员队期表编号,获取学员队期表的学员队编号 + XYDNDXQJBXXB targetSemester = classSemesterMapper.selectById(targetBh); + if (targetSemester == null) { + failedTargetBhs.add(targetBh); + continue; + } + String newXydbh = targetSemester.getXydbh(); + + for (XYDRWB source : sourceList) { + XYDRWB newRecord = new XYDRWB(); + newRecord.setBh(UuidUtil.getUUID()); + newRecord.setNd(source.getNd()); + newRecord.setXydbh(newXydbh); + newRecord.setKbh(source.getKbh()); + newRecord.setXqdc(source.getXqdc()); + newRecord.setJybh(source.getJybh()); + newRecord.setKcxh(source.getKcxh()); + newRecord.setJsbh(source.getJsbh()); + newRecord.setRs(source.getRs()); + newRecord.setZks(source.getZks()); + newRecord.setXs(source.getXs()); + newRecord.setKlx(source.getKlx()); + newRecord.setJc(source.getJc()); + newRecord.setKsdd(source.getKsdd()); + newRecord.setKsbh(source.getKsbh()); + newRecord.setXz(source.getXz()); + newRecord.setJysdh(source.getJysdh()); + newRecord.setJysjhjybh(source.getJysjhjybh()); + newRecord.setJysjhbz(source.getJysjhbz()); + newRecord.setJhkcxh(source.getJhkcxh()); + newRecord.setXhbs(source.getXhbs()); + newRecord.setXydxqbh(targetBh); + newRecord.setBz(source.getBz()); + newRecord.setXf(source.getXf()); + newRecord.setKsks(source.getKsks()); + newRecord.setBz2(source.getBz2()); + newRecord.setCjfz(source.getCjfz()); + newRecord.setBjrxypjf(source.getBjrxypjf()); + newRecord.setLlxs(source.getLlxs()); + newRecord.setSjxs(source.getSjxs()); + newRecord.setKsksbxs(source.getKsksbxs()); + newRecord.setPdxh(source.getPdxh()); + newRecord.setPdqsz(source.getPdqsz()); + newRecord.setPdaz(source.getPdaz()); + newRecord.setPdzzksj(source.getPdzzksj()); + newRecord.setPdbz(source.getPdbz()); + newRecord.setPdtbykxxbz(source.getPdtbykxxbz()); + newRecord.setPdzdyxsfbsj(source.getPdzdyxsfbsj()); + newRecord.setPdqyzdyxs(source.getPdqyzdyxs()); + newRecord.setDelFlag(0); + + classSemesterCalendarMapper.insert(newRecord); + count++; + } + } + + Map result = new HashMap<>(); + result.put("count", count); + result.put("failedTargetBhs", failedTargetBhs); + return result; + } + + @Override + public List listAllValid(XYDRWB xydrwb) { + xydrwb.setDelFlag(0); + return classSemesterCalendarMapper.selectAllValid(xydrwb); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassSemesterServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassSemesterServiceImpl.java new file mode 100644 index 000000000..0505f5682 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ClassSemesterServiceImpl.java @@ -0,0 +1,253 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDNDXQJBXXB; +import com.roomroot.jwgl.entity.XYDB; +import com.roomroot.jwgl.mapper.ClassSemesterCalendarMapper; +import com.roomroot.jwgl.mapper.ClassSemesterMapper; +import com.roomroot.jwgl.mapper.SemesterCalendarMapper; +import com.roomroot.jwgl.service.ClassSemesterService; +import com.roomroot.jwgl.service.ElectiveService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import jakarta.annotation.Resource; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 班次学期服务实现类 + */ +@Service +public class ClassSemesterServiceImpl implements ClassSemesterService { + + @Resource + private ClassSemesterMapper classSemesterMapper; + + @Resource + SemesterCalendarMapper semesterCalendarMapper; + + @Resource + ClassSemesterCalendarMapper classSemesterCalendarMapper; + + @Resource + private ElectiveService electiveService; + + @Transactional(rollbackFor = Exception.class) + @Override + public void add(XYDNDXQJBXXB xydndxqjbxxb) { + String uuid = UuidUtil.getUUID(); + xydndxqjbxxb.setBh(uuid); + xydndxqjbxxb.setDelFlag(0); + int insert = classSemesterMapper.insert(xydndxqjbxxb); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(String bh) { + XYDNDXQJBXXB xydndxqjbxxb = getByBh(bh); + xydndxqjbxxb.setDelFlag(1); + classSemesterMapper.updateById(xydndxqjbxxb); + classSemesterCalendarMapper.deleteByBcxqBh(bh); + } + + @Override + public void batchDelete(List bhList) { + for (String bh : bhList) { + XYDNDXQJBXXB xydndxqjbxxb = getByBh(bh); + if (xydndxqjbxxb != null) { + xydndxqjbxxb.setDelFlag(1); + classSemesterMapper.updateById(xydndxqjbxxb); + } + } + } + + @Override + public void update(XYDNDXQJBXXB xydndxqjbxxb) { + classSemesterMapper.updateById(xydndxqjbxxb); + } + + @Override + public XYDNDXQJBXXB getByBh(String bh) { + return classSemesterMapper.selectById(bh); + } + + @Override + public PageResult pageList(PageQuery query, XYDNDXQJBXXB xydndxqjbxxb) { + xydndxqjbxxb.setDelFlag(0); + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = classSemesterMapper.selectPageList(page, xydndxqjbxxb); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List listAllValid() { + return classSemesterMapper.selectAllValid(); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public int batchAddFromElective(LocalDateTime startTime, LocalDateTime endTime, + String nd, String xqdc, String xqkssj, String xqjssj) { + // 调用filterByDateRange获取符合条件的学员队列表 + List xydbList = electiveService.filterByDateRange(startTime, endTime); + + if (CollectionUtils.isEmpty(xydbList)) { + return 0; + } + + int count = 0; + for (XYDB xydb : xydbList) { + // 校验是否已存在:根据班次编号、年度、学期第次查询 + XYDNDXQJBXXB existing = classSemesterMapper.selectByBcAndNdAndXqdc(xydb.getXydbh(), nd, xqdc); + if (existing != null) { + // 已存在,跳过 + continue; + } + + // 创建学员队年度学期基本信息 + XYDNDXQJBXXB xydndxqjbxxb = new XYDNDXQJBXXB(); + String uuid = UuidUtil.getUUID(); + xydndxqjbxxb.setBh(uuid); + xydndxqjbxxb.setXydbh(xydb.getXydbh()); + xydndxqjbxxb.setNd(Integer.valueOf(nd)); + if("春季学期".equals(xqdc)){ + xqdc = "1"; + } else if("夏季学期".equals(xqdc)){ + xqdc = "2"; + } else if("秋季学期".equals(xqdc)){ + xqdc = "3"; + } + xydndxqjbxxb.setXhbs(0); + xydndxqjbxxb.setXqdc(Integer.valueOf(xqdc)); + xydndxqjbxxb.setKxrq(LocalDate.parse(xqkssj)); + xydndxqjbxxb.setJsrq(LocalDate.parse(xqjssj)); + xydndxqjbxxb.setYsbbh(0); + xydndxqjbxxb.setJclb("双节次"); + xydndxqjbxxb.setXtms(""); + xydndxqjbxxb.setKfjypk(0); + xydndxqjbxxb.setDelFlag(0); + + int insert = classSemesterMapper.insert(xydndxqjbxxb); + + } + return count; + } + + @Override + public int batchUpdateKfjypk(List bhList, Integer kfjypk) { + if (CollectionUtils.isEmpty(bhList)) { + return 0; + } + int count = 0; + for (String bh : bhList) { + XYDNDXQJBXXB xydndxqjbxxb = classSemesterMapper.selectById(bh); + if (xydndxqjbxxb != null) { + xydndxqjbxxb.setKfjypk(kfjypk); + classSemesterMapper.updateById(xydndxqjbxxb); + count++; + } + } + return count; + } + + @Override + public int batchUpdateXqdc(List bhList, Integer xqdc) { + if (CollectionUtils.isEmpty(bhList)) { + return 0; + } + int count = 0; + for (String bh : bhList) { + XYDNDXQJBXXB xydndxqjbxxb = classSemesterMapper.selectById(bh); + if (xydndxqjbxxb != null) { + xydndxqjbxxb.setXqdc(xqdc); + classSemesterMapper.updateById(xydndxqjbxxb); + count++; + } + } + return count; + } + + @Override + public int batchUpdateJxrwbh(List bhList, String jxrwbh) { + if (CollectionUtils.isEmpty(bhList)) { + return 0; + } + int count = 0; + for (String bh : bhList) { + XYDNDXQJBXXB xydndxqjbxxb = classSemesterMapper.selectById(bh); + if (xydndxqjbxxb != null) { + xydndxqjbxxb.setJxrwbh(jxrwbh); + classSemesterMapper.updateById(xydndxqjbxxb); + count++; + } + } + return count; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public int batchUpdateDateRange(List bhList, String xqkssj, String xqjssj) { + if (CollectionUtils.isEmpty(bhList)) { + return 0; + } + + int count = 0; + for (String bh : bhList) { + // 查询学员队年度学期基本信息 + XYDNDXQJBXXB xydndxqjbxxb = classSemesterMapper.selectById(bh); + if (xydndxqjbxxb == null) { + continue; + } + + // 更新日期范围 + xydndxqjbxxb.setKxrq(LocalDate.parse(xqkssj)); + xydndxqjbxxb.setJsrq(LocalDate.parse(xqjssj)); + classSemesterMapper.updateById(xydndxqjbxxb); + +// // 删除日期范围之外的日历记录 +// classSemesterCalendarMapper.deleteOutsideDateRange(bh, xqkssj, xqjssj); +// +// // 查询现有任务记录 +// List existingCalendar = classSemesterCalendarMapper.selectByBcxqbhAndDateRange(bh, xqkssj, xqjssj); +// +// // 查询学期校历,用于补充缺少的记录 +// String kxrqStr = xydndxqjbxxb.getKxrq() != null ? xydndxqjbxxb.getKxrq().toString() : xqkssj; +// String jsrqStr = xydndxqjbxxb.getJsrq() != null ? xydndxqjbxxb.getJsrq().toString() : xqjssj; +// List xqxlbs = semesterCalendarMapper.selectByNdAndJqsjRange(xydndxqjbxxb.getNd(), kxrqStr, jsrqStr); +// +// if (!CollectionUtils.isEmpty(xqxlbs)) { +// // 提取已有编号 +// java.util.Set existingBhs = new java.util.HashSet<>(); +// for (XYDRWB cal : existingCalendar) { +// existingBhs.add(cal.getBh()); +// } +// +// // 补充缺少的记录 +// for (XQXLB xqxlb : xqxlbs) { +// XYDRWB xydrwb = new XYDRWB(); +// String newBh = UuidUtil.getUUID(); +// if (!existingBhs.contains(newBh)) { +// xydrwb.setBh(newBh); +// xydrwb.setNd(xydndxqjbxxb.getNd()); +// xydrwb.setXydbh(xydndxqjbxxb.getXydbh()); +// xydrwb.setXqdc(xydndxqjbxxb.getXqdc()); +// xydrwb.setXydxqbh(bh); +// xydrwb.setDelFlag(0); +// classSemesterCalendarMapper.insert(xydrwb); +// } +// } +// } +// +// count++; + } + + return count; + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/CourseRunningImportServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/CourseRunningImportServiceImpl.java new file mode 100644 index 000000000..9a068ca30 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/CourseRunningImportServiceImpl.java @@ -0,0 +1,3025 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.courserunning.*; +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.mapper.*; +import com.roomroot.jwgl.vo.courserunning.*; +import com.roomroot.jwgl.mapper.*; +import com.roomroot.jwgl.service.CourseRunningImportService; +import com.roomroot.jwgl.service.DictDataService; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.BasicManagementConstants; +import com.roomroot.jwgl.utils.ExcelExportUtil; +import com.roomroot.jwgl.utils.UuidUtil; +import com.roomroot.jwgl.validator.StudentLeaveValidator; +import com.roomroot.jwgl.vo.courserunning.*; +import com.roomroot.jwgl.vo.dict.DictDataVO; +import jakarta.annotation.Resource; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.ByteArrayOutputStream; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 教学运行数据导入服务实现类。 + *

+ * 整合课程教学运行管理全部业务能力,包括:
+ * 1. 教学运行数据导入:从课程教学计划编制子系统向课程教学运行管理子系统导入数据;
+ * 2. 教学实施计划填写:对已导入的课次进行教学实施计划内容的填写、查询和提交;
+ * 3. 调课管理:教员提交调课申请、教研室审批、机关审批、申请查收与撤销等完整流程。 + *

+ */ +@Service +public class CourseRunningImportServiceImpl implements CourseRunningImportService { + + /** + * 请假类型字典类型编码:edu_leave_type(对应sys_dict_data中病假/事假/公假/调休)。 + */ + private static final String DICT_TYPE_LEAVE_TYPE = "edu_leave_type"; + + /** + * 字典数据服务,用于请假类型等字典数据的查询与翻译。 + */ + @Resource + private DictDataService dictDataService; + + /** + * 实施_课程数据访问接口,用于查询课程教学计划编制子系统中的课程数据。 + */ + @Resource + private SSKCMapper sskcMapper; + + /** + * 实施_课程表数据访问接口,用于写入课程教学运行管理子系统的课次数据。 + */ + @Resource + private SSKCBMapper sskcbMapper; + + /** + * 实施_课程学员队数据访问接口,用于查询课程关联的学员队信息。 + */ + @Resource + private SSKCXYDMapper sskcxyMapper; + + /** + * 实施_课程表学员队数据访问接口,用于写入课次关联的学员队信息。 + */ + @Resource + private SSKCBXYDMapper sskcbxyMapper; + + /** + * 实施_课程表教室数据访问接口,用于写入课次关联的教室信息。 + */ + @Resource + private SSKCBJSMapper sskcbjsMapper; + + /** + * 实施_课程表辅助教员数据访问接口,用于写入课次关联的教员信息。 + */ + @Resource + private SSKCBFZJYMapper sskcbfzjymMapper; + + /** + * 课程教学运行_操作日志 Mapper,用于记录操作日志。 + */ + @Resource + private KCJXYX_CZRZMapper kcjxyxCzrzMapper; + + /** + * 实施_调课申请 Mapper,用于调课申请的数据访问。 + */ + @Resource + private SSDKSQMapper ssdksqMapper; + + /** + * 实施_调课申请审批 Mapper,用于调课申请审批记录的数据访问。 + */ + @Resource + private SSDKSQSPMapper ssdksqspMapper; + + /** + * 调课申请_教室 Mapper,用于调课申请中教室关联的数据访问。 + */ + @Resource + private DKSQJSMapper dksqjsMapper; + + /** + * 调课申请_辅助教员 Mapper,用于调课申请中辅助教员关联的数据访问。 + */ + @Resource + private DKSQFZJYMapper dksqfzjyMapper; + + /** + * 调课申请_学员队 Mapper,用于调课申请中学员队关联的数据访问。 + */ + @Resource + private DKSQXYDMapper dksqxydMapper; + + /** + * 调课申请_保障明细 Mapper,用于调课申请中保障明细关联的数据访问。 + */ + @Resource + private DKSQBZMXMapper dksqbzmxMapper; + + /** + * 课程表_保障明细 Mapper,用于课次教学保障计划的数据访问。 + */ + @Resource + private KCBBZMXMapper kcbbzmxMapper; + + /** + * 保障提供明细 Mapper,用于保障资源库的数据访问。 + */ + @Resource + private BZTGMXMapper bztgmxMapper; + + /** + * 学员教学请假表 Mapper,用于学员正课请假申请的数据访问。 + */ + @Resource + private XYJXQJBMapper xyJXQJBMapper; + + /** + * 学员请假审批表 Mapper,用于学员请假审批记录的数据访问。 + */ + @Resource + private XYQJSPBMapper xyQJSPBMapper; + + /** + * 请假课堂表 Mapper,用于请假关联课次的数据访问。 + */ + @Resource + private QJKTBMapper qjktbMapper; + + /** + * 请假学员表 Mapper,用于请假学员关联数据的访问。 + */ + @Resource + private QJXYBMapper qjxybMapper; + + /** + * 课堂日志学员考勤表 Mapper,用于学员考勤记录的数据访问。 + */ + @Resource + private KTRZXYKQBMapper ktrzxykqbMapper; + + /** + * 学员请假校验器,集中承载 submit/audit/cancel 三类操作的参数与权限校验。 + */ + @Resource + private StudentLeaveValidator studentLeaveValidator; + + /** + * 运行分析数据访问 Mapper,用于教学运行数据统计分析。 + */ + @Resource + private OperationAnalysisMapper operationAnalysisMapper; + + /** + * 调课策略 Mapper,用于调课时间段限制策略与免审批策略的数据访问。 + */ + @Resource + private ScheduleAdjustStrategyMapper scheduleAdjustStrategyMapper; + + /** + * 教员表数据访问接口,用于审批时根据教员编号查询教研室代号。 + */ + @Resource + private JYBMapper jybMapper; + + /** + * 实施_机关_教研室数据访问接口,用于审批时根据教研室代号查询机关编号。 + */ + @Resource + private SSJGJYSMapper ssjgjysMapper; + + /** + * 按学期导入课程。 + *

+ * 根据年度和学期第次,从课程教学计划编制子系统查询实施_课程数据, + * 转换为课次级数据后写入课程教学运行管理子系统。 + *

+ * + * @param params 学期导入参数(包含年度和学期第次) + * @return 导入结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public CourseRunningImportResultVO importBySemester(SemesterImportDTO params) { + // 步骤1:参数校验,确保导入参数不为空 + if (params == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "导入参数不能为空"); + } + + // 步骤2:根据年度和学期第次查询实施_课程数据 + List courseList = sskcMapper.selectBySemester(params.getNd(), params.getXqdc()); + if (courseList == null || courseList.isEmpty()) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "未找到该学期的实施课程数据"); + } + + // 步骤3:调用通用导入方法处理课程列表 + return importCourseList(courseList, params.getNd()); + } + + /** + * 按教学班次导入课程。 + *

+ * 根据教学班次编号和年度,从课程教学计划编制子系统查询该班次关联的实施_课程数据, + * 转换为课次级数据后写入课程教学运行管理子系统。 + *

+ * + * @param params 教学班次导入参数(包含教学班次编号和年度) + * @return 导入结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public CourseRunningImportResultVO importByClasses(ClassesImportDTO params) { + // 步骤1:参数校验,确保导入参数不为空 + if (params == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "导入参数不能为空"); + } + + // 步骤2:根据教学班次编号和年度查询实施_课程数据 + List courseList = sskcMapper.selectByClassBh(params.getClassBh(), params.getNd()); + if (courseList == null || courseList.isEmpty()) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "未找到该教学班次的实施课程数据"); + } + + // 步骤3:调用通用导入方法处理课程列表 + return importCourseList(courseList, params.getNd()); + } + + /** + * 导入单门课程。 + *

+ * 根据课程编号、教学班次编号和年度,从课程教学计划编制子系统查询指定的实施_课程数据, + * 转换为课次级数据后写入课程教学运行管理子系统。 + *

+ * + * @param params 单门课程导入参数(包含课程编号、教学班次编号和年度) + * @return 导入结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public CourseRunningImportResultVO importSingleCourse(SingleCourseImportDTO params) { + // 步骤1:参数校验,确保导入参数不为空 + if (params == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "导入参数不能为空"); + } + + // 步骤2:根据课程编号、教学班次编号和年度查询实施_课程数据 + SSKC course = sskcMapper.selectByCourseBhAndClassBh(params.getCourseBh(), params.getClassBh(), params.getNd()); + if (course == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "未找到该课程的实施数据"); + } + + // 步骤3:将单门课程包装为列表,调用通用导入方法 + List courseList = new ArrayList<>(); + courseList.add(course); + + // 步骤4:调用通用导入方法处理课程列表 + return importCourseList(courseList, params.getNd()); + } + + /** + * 删除已导入课程。 + *

+ * 根据删除范围(学期/班次/单个)删除已导入到课程教学运行管理子系统的课次数据, + * 采用软删除方式,标记删除状态并清理关联数据。 + *

+ * + * @param params 删除参数(包含删除范围、课次编号列表和年度) + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void removeImportedCourses(ImportRemoveDTO params) { + // 步骤1:参数校验,确保删除参数不为空 + if (params == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "删除参数不能为空"); + } + + // 步骤2:解析删除参数 + String scope = params.getScope(); + List idList = params.getIdList(); + Integer nd = params.getNd(); + + // 步骤3:根据删除范围查询待删除的课次列表 + List lessonList; + if ("semester".equals(scope)) { + // 按学期删除:查询指定年度的所有有效课次 + lessonList = sskcbMapper.selectList(new LambdaQueryWrapper() + .eq(SSKCB::getNd, nd) + .eq(SSKCB::getSczt, 0)); + } else if ("class".equals(scope)) { + // 按班次删除:查询指定年度的所有有效课次(后续可根据班次过滤) + lessonList = sskcbMapper.selectList(new LambdaQueryWrapper() + .eq(SSKCB::getNd, nd) + .eq(SSKCB::getSczt, 0)); + } else if ("single".equals(scope) && idList != null && !idList.isEmpty()) { + // 按单个课次删除:根据课次编号列表查询 + lessonList = sskcbMapper.selectBatchIds(idList); + } else { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "无效的删除范围"); + } + + // 步骤4:如果没有待删除的课次,直接返回 + if (lessonList == null || lessonList.isEmpty()) { + return; + } + + // 步骤5:遍历课次列表,执行软删除和关联数据清理 + for (SSKCB lesson : lessonList) { + // 标记课次为已删除状态(软删除) + lesson.setSczt(1); + sskcbMapper.updateById(lesson); + + // 清理关联数据:学员队、教室、教员 + sskcbxyMapper.deleteBySskcbbh(lesson.getBh()); + sskcbjsMapper.deleteBySskcbbh(lesson.getBh()); + sskcbfzjymMapper.deleteBySskcbbh(lesson.getBh()); + + // 记录删除操作日志 + recordOperationLog(lesson.getBh(), "删除", "删除已导入的课次"); + } + } + + /** + * 导入课程列表的通用方法。 + *

+ * 将课程级数据转换为课次级数据,写入数据库,并记录操作日志。 + *

+ * + * @param courseList 待导入的实施_课程列表 + * @param nd 年度 + * @return 导入结果 + */ + private CourseRunningImportResultVO importCourseList(List courseList, Integer nd) { + // 步骤1:初始化统计变量 + int totalCount = courseList.size(); + int successCount = 0; + List failedList = new ArrayList<>(); + + // 步骤2:遍历课程列表,逐个处理导入 + for (SSKC course : courseList) { + try { + // 步骤3:将课程转换为课次列表(根据总学时计算课次数) + List lessonList = convertToLessons(course, nd); + + // 步骤4:遍历课次列表,写入数据库 + for (SSKCB lesson : lessonList) { + // 写入实施_课程表主记录 + sskcbMapper.insert(lesson); + + // 写入关联数据:学员队、教室、教员 + insertSSKCBXYD(lesson, course.getBh(), nd); + insertSSKCBJS(lesson, nd); + insertSSKCBFZJY(lesson, nd); + } + + // 步骤5:记录导入操作日志 + recordOperationLog(course.getBh(), "导入", "导入课程:" + course.getKmbh()); + + // 步骤6:更新成功计数 + successCount++; + } catch (Exception e) { + // 步骤7:记录失败的课程编号 + failedList.add(course.getBh()); + } + } + + // 步骤8:构建导入结果 + CourseRunningImportResultVO result = new CourseRunningImportResultVO(totalCount, successCount, failedList); + if (successCount == totalCount) { + result.setMessage("导入成功"); + } else if (successCount == 0) { + result.setMessage("导入失败"); + } else { + result.setMessage("部分导入成功"); + } + + // 步骤9:返回导入结果 + return result; + } + + /** + * 将课程转换为课次列表。 + *

+ * 根据课程的总学时,计算需要生成的课次数,每节课生成一条实施_课程表记录。 + * 默认每周2节课,总课次数 = 总学时 / 每周课次数。 + *

+ * + * @param course 实施_课程数据 + * @param nd 年度 + * @return 课次列表 + */ + private List convertToLessons(SSKC course, Integer nd) { + // 步骤1:初始化课次列表 + List lessonList = new ArrayList<>(); + + // 步骤2:获取总学时,默认每周2节课 + int totalHours = course.getXs() != null ? course.getXs() : 0; + int lessonsPerWeek = 2; + int weeks = totalHours / lessonsPerWeek; + + // 步骤3:遍历周次和课时,生成课次记录 + for (int week = 1; week <= weeks; week++) { + for (int lessonOfDay = 1; lessonOfDay <= lessonsPerWeek; lessonOfDay++) { + // 创建课次实体 + SSKCB lesson = new SSKCB(); + lesson.setBh(UuidUtil.getOriginalUUID()); // 生成唯一编号 + lesson.setSskcbh(course.getBh()); // 关联实施_课程编号 + lesson.setNd(nd); // 设置年度 + lesson.setJhsjap(1); // 计划时间安排标记 + lesson.setSjsjap(null); // 实际时间安排(导入时为空) + lesson.setSczt(0); // 删除状态:正常 + + // 添加到课次列表 + lessonList.add(lesson); + } + } + + // 步骤4:返回课次列表 + return lessonList; + } + + /** + * 写入实施_课程表学员队关联数据。 + *

+ * 从实施_课程学员队表中查询课程关联的学员队信息,转换后写入实施_课程表学员队表。 + *

+ * + * @param lesson 课次实体 + * @param sskcbh 实施_课程编号 + * @param nd 年度 + */ + private void insertSSKCBXYD(SSKCB lesson, String sskcbh, Integer nd) { + // 步骤1:查询实施_课程关联的学员队信息 + List sskcxyList = sskcxyMapper.selectList( + new LambdaQueryWrapper() + .eq(SSKCXYD::getSskcbh, sskcbh)); + + // 步骤2:遍历学员队列表,转换并写入实施_课程表学员队 + if (sskcxyList != null) { + for (SSKCXYD sskcxy : sskcxyList) { + SSKCBXYD sskcbxy = new SSKCBXYD(); + sskcbxy.setBh(UuidUtil.getOriginalUUID()); // 生成唯一编号 + sskcbxy.setSskcbbh(lesson.getBh()); // 关联实施_课程表编号 + sskcbxy.setXydbh(sskcxy.getXydbh()); // 学员队编号 + sskcbxy.setRs(sskcxy.getRs()); // 人数 + sskcbxy.setJc(sskcxy.getJc()); // 节次 + sskcbxy.setNd(nd); // 年度 + + // 写入数据库 + sskcbxyMapper.insert(sskcbxy); + } + } + } + + /** + * 写入实施_课程表教室关联数据。 + *

+ * 预留接口,待排课信息数据源确认后实现教室关联逻辑。 + *

+ * + * @param lesson 课次实体 + * @param nd 年度 + */ + private void insertSSKCBJS(SSKCB lesson, Integer nd) { + // TODO:待排课信息数据源确认后实现教室关联逻辑 + } + + /** + * 写入实施_课程表辅助教员关联数据。 + *

+ * 预留接口,待排课信息数据源确认后实现教员关联逻辑。 + *

+ * + * @param lesson 课次实体 + * @param nd 年度 + */ + private void insertSSKCBFZJY(SSKCB lesson, Integer nd) { + // TODO:待排课信息数据源确认后实现教员关联逻辑 + } + + /** + * 记录操作日志。 + *

+ * 将导入或删除操作记录写入实施_课程表日志表。 + *

+ * + * @param targetId 操作目标编号(课程编号或课次编号) + * @param operationType 操作类型(导入、删除) + * @param operationContent 操作内容描述 + */ + private void recordOperationLog(String targetId, String operationType, String operationContent) { + // 创建日志实体 + KCJXYX_CZRZ log = new KCJXYX_CZRZ(); + log.setBh(UuidUtil.getOriginalUUID()); // 生成唯一编号 + log.setTybh(targetId); // 目标编号 + log.setCzlx(operationType); // 操作类型 + log.setCznr(operationContent); // 操作内容 + log.setCzsj(LocalDateTime.now()); // 操作时间 + + // 写入数据库 + kcjxyxCzrzMapper.insert(log); + } + + // ==================== 教学实施计划填写 ==================== + + /** + * 日期时间格式化器,用于将LocalDateTime格式化为字符串传递给Mapper。 + */ + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + /** + * 分页查询待填写或已填写的课次列表。 + *

+ * 根据查询条件分页查询课次列表,返回课次基本信息及填写状态。 + * 查询结果关联实施_课程表和课程表,获取课程名称。 + *

+ * + * @param query 查询参数(包含分页信息和筛选条件) + * @return 分页课次列表 + */ + @Override + public PageResult queryLessonList(TeachingPlanQueryDTO query) { + // 步骤1:校验查询参数,确保分页参数有效 + if (query == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "查询参数不能为空"); + } + if (query.getPageNum() == null || query.getPageSize() == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "分页参数不能为空"); + } + + // 步骤2:构建MyBatis-Plus分页对象 + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + + // 步骤3:将日期范围转换为字符串格式,便于XML中直接拼接SQL + String rqStartStr = query.getRqStart() != null ? query.getRqStart().format(DATE_TIME_FORMATTER) : null; + String rqEndStr = query.getRqEnd() != null ? query.getRqEnd().format(DATE_TIME_FORMATTER) : null; + + // 步骤4:调用Mapper执行分页查询,关联课程表获取课程名称 + Page resultPage = sskcbMapper.selectPageListForPlan( + page, + query.getSskcbh(), + query.getNd(), + query.getJxff(), + rqStartStr, + rqEndStr, + query.getTxzt() + ); + + // 步骤5:封装分页结果并返回 + return new PageResult<>( + resultPage.getRecords(), + resultPage.getTotal(), + query.getPageNum(), + query.getPageSize() + ); + } + + /** + * 按查询条件导出课次数据为 Excel 文件。 + * + * @param query 查询参数(包含筛选条件) + * @return Excel 文件字节数组 + */ + @Override + public byte[] exportLessonListToExcel(TeachingPlanQueryDTO query) { + // 步骤1:参数校验 + if (query == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "查询参数不能为空"); + } + + // 步骤2:设置导出时为不分页查询,获取全部符合条件的数据 + query.setPageNum(1); + query.setPageSize(Integer.MAX_VALUE); + + // 步骤3:查询课次列表数据 + PageResult pageResult = queryLessonList(query); + List lessons = pageResult.getRecords(); + + // 步骤4:将数据导出为 Excel + try { + return ExcelExportUtil.export( + "课次数据", + new String[]{"实施_课程表编号", "实施_课程编号", "课程名称", "课次序号", + "年度", "上课日期", "节次", "教学内容摘要", "教学方法", "填写状态"}, + lessons, + TeachingPlanLessonVO::getSskcbbh, + TeachingPlanLessonVO::getSskcbh, + TeachingPlanLessonVO::getKcmc, + TeachingPlanLessonVO::getKcxh, + TeachingPlanLessonVO::getNd, + TeachingPlanLessonVO::getRq, + TeachingPlanLessonVO::getJc, + TeachingPlanLessonVO::getJxnr, + TeachingPlanLessonVO::getJxff, + vo -> formatTxzt(vo.getTxzt()) + ); + } catch (Exception e) { + throw new BusinessException("导出Excel失败:" + e.getMessage()); + } + } + + /** + * 格式化填写状态。 + * + * @param txzt 填写状态码 + * @return 状态描述 + */ + private String formatTxzt(Integer txzt) { + if (txzt == null) { + return ""; + } + switch (txzt) { + case 0: + return "未填写"; + case 1: + return "已填写"; + case 2: + return "已提交"; + default: + return String.valueOf(txzt); + } + } + + /** + * 查询单个课次的教学实施计划详情。 + *

+ * 根据课次编号查询该课次的完整教学实施计划信息,包括教学内容、教学要点、 + * 教学方法、日期节次、班组信息、保障需求及教员备注等。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + * @return 教学实施计划详情 + */ + @Override + public TeachingPlanDetailVO getTeachingPlanDetail(String sskcbbh) { + // 步骤1:校验课次编号参数 + if (sskcbbh == null || sskcbbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "课次编号不能为空"); + } + + // 步骤2:调用Mapper查询课次详情,关联课程表获取课程名称 + TeachingPlanDetailVO detail = sskcbMapper.selectDetailById(sskcbbh); + + // 步骤3:校验查询结果,课次不存在时抛出异常 + if (detail == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "未找到该课次的教学实施计划"); + } + + // 步骤4:返回教学实施计划详情 + return detail; + } + + /** + * 单条填写教学实施计划。 + *

+ * 对单个课次填写教学实施计划内容,保存后填写状态更新为"已填写"。 + * 校验课次是否存在、是否已提交,已提交的课次不允许修改。 + *

+ * + * @param dto 教学实施计划填写参数 + * @return 填写结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public TeachingPlanFillResultVO fillTeachingPlan(TeachingPlanFillDTO dto) { + // 步骤1:校验填写参数 + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "填写参数不能为空"); + } + + // 步骤2:查询课次实体,校验课次是否存在 + SSKCB lesson = sskcbMapper.selectById(dto.getSskcbbh()); + if (lesson == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "课次不存在"); + } + + // 步骤3:校验课次是否已删除 + if (lesson.getSczt() != null && lesson.getSczt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该课次已删除,不允许填写"); + } + + // 步骤4:校验课次是否已提交,已提交的实施计划不允许修改 + if (lesson.getJsonzd() != null && lesson.getJsonzd().contains("submitted")) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该课次已提交,不允许修改,如需修改请先撤回"); + } + + // 步骤5:将DTO中的填写内容设置到课次实体 + lesson.setJxnr(dto.getJxnr()); // 教学内容 + lesson.setJxyd(dto.getJxyd()); // 教学要点 + lesson.setJxff(dto.getJxff()); // 教学方法 + lesson.setRq(dto.getRq()); // 上课日期 + lesson.setJc(dto.getJc()); // 节次 + lesson.setBzms(dto.getBzms()); // 班组模式 + lesson.setBzhfxx(dto.getBzhfxx()); // 班组划分信息 + lesson.setYcxx(dto.getYcxx()); // 用车信息 + lesson.setJxbzbz(dto.getJxbzbz()); // 教学保障备注 + lesson.setJybz(dto.getJybz()); // 教员备注 + + // 步骤6:更新变动时间为当前时间 + lesson.setBdsj(LocalDateTime.now()); + + // 步骤7:执行数据库更新 + sskcbMapper.updateById(lesson); + + // 步骤8:记录填写操作日志 + recordOperationLog(lesson.getBh(), "填写", "填写教学实施计划:" + lesson.getBh()); + + // 步骤9:构建并返回填写结果 + TeachingPlanFillResultVO result = new TeachingPlanFillResultVO(1, 1, null); + result.setMessage("填写成功"); + return result; + } + + /** + * 批量填写教学实施计划。 + *

+ * 一次性提交多个课次的教学实施计划填写数据,逐条处理并返回整体执行结果。 + * 每条记录独立处理,失败记录不影响其他记录。 + *

+ * + * @param dto 批量教学实施计划填写参数 + * @return 批量填写结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public TeachingPlanFillResultVO batchFillTeachingPlan(TeachingPlanBatchFillDTO dto) { + // 步骤1:校验批量填写参数 + if (dto == null || dto.getPlanList() == null || dto.getPlanList().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "批量填写参数不能为空"); + } + + // 步骤2:初始化统计变量 + List planList = dto.getPlanList(); + int totalCount = planList.size(); + int successCount = 0; + List failedList = new ArrayList<>(); + + // 步骤3:遍历每条填写记录,逐个处理 + for (TeachingPlanFillDTO item : planList) { + try { + // 步骤4:调用单条填写方法处理当前记录 + fillTeachingPlan(item); + // 步骤5:单条处理成功,更新成功计数 + successCount++; + } catch (Exception e) { + // 步骤6:单条处理失败,记录失败的课次编号,继续处理下一条 + failedList.add(item.getSskcbbh()); + } + } + + // 步骤7:构建批量填写结果 + TeachingPlanFillResultVO result = new TeachingPlanFillResultVO(totalCount, successCount, failedList); + if (successCount == totalCount) { + result.setMessage("全部填写成功"); + } else if (successCount == 0) { + result.setMessage("全部填写失败"); + } else { + result.setMessage("部分填写成功,失败" + failedList.size() + "条"); + } + + // 步骤8:返回批量填写结果 + return result; + } + + /** + * 提交教学实施计划。 + *

+ * 将已填写的教学实施计划提交审核,提交后填写状态更新为"已提交"。 + * 已提交的实施计划原则上不允许再修改,如需修改需先撤回。 + * 只有已填写的课次才能提交,未填写的课次不允许直接提交。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void submitTeachingPlan(String sskcbbh) { + // 步骤1:校验课次编号参数 + if (sskcbbh == null || sskcbbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "课次编号不能为空"); + } + + // 步骤2:查询课次实体,校验课次是否存在 + SSKCB lesson = sskcbMapper.selectById(sskcbbh); + if (lesson == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "课次不存在"); + } + + // 步骤3:校验课次是否已删除 + if (lesson.getSczt() != null && lesson.getSczt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该课次已删除,不允许提交"); + } + + // 步骤4:校验课次是否已提交,避免重复提交 + if (lesson.getJsonzd() != null && lesson.getJsonzd().contains("submitted")) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该课次已提交,无需重复提交"); + } + + // 步骤5:校验课次是否已填写,未填写的课次不允许提交 + boolean isFilled = lesson.getJxnr() != null || lesson.getJxyd() != null || lesson.getJxff() != null; + if (!isFilled) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该课次尚未填写教学实施计划,不允许提交"); + } + + // 步骤6:设置提交标记到jsonzd字段,标记该课次已提交 + lesson.setJsonzd("{\"submitted\":true}"); + + // 步骤7:更新变动时间为当前时间 + lesson.setBdsj(LocalDateTime.now()); + + // 步骤8:执行数据库更新 + sskcbMapper.updateById(lesson); + + // 步骤9:记录提交操作日志 + recordOperationLog(lesson.getBh(), "提交", "提交教学实施计划:" + lesson.getBh()); + } + + // ==================== 调课管理 ==================== + + /** + * 分页查询调课申请列表。 + *

+ * 根据查询条件分页查询调课申请列表,返回申请基本信息及审批状态。 + * 查询结果关联课程表获取课程名称,关联教员表获取申请教员姓名。 + *

+ * + * @param query 查询参数(包含分页信息和筛选条件) + * @return 分页调课申请列表 + */ + @Override + public PageResult queryScheduleAdjustList(ScheduleAdjustQueryDTO query) { + // 步骤1:校验查询参数,确保分页参数有效 + if (query == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "查询参数不能为空"); + } + if (query.getPageNum() == null || query.getPageSize() == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "分页参数不能为空"); + } + + // 步骤2:构建MyBatis-Plus分页对象 + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + + // 步骤3:将日期范围转换为字符串格式,便于XML中直接拼接SQL + String cjsjStartStr = query.getCjsjStart() != null ? query.getCjsjStart().format(DATE_TIME_FORMATTER) : null; + String cjsjEndStr = query.getCjsjEnd() != null ? query.getCjsjEnd().format(DATE_TIME_FORMATTER) : null; + + // 步骤4:调用Mapper执行分页查询,关联课程表和教员表 + Page resultPage = ssdksqMapper.selectPageList( + page, + query.getSskcbbh(), + query.getSqjybh(), + query.getNd(), + query.getJyspzzt(), + query.getJyscszt(), + query.getSczt(), + cjsjStartStr, + cjsjEndStr + ); + + // 步骤5:封装分页结果并返回 + return new PageResult<>( + resultPage.getRecords(), + resultPage.getTotal(), + query.getPageNum(), + query.getPageSize() + ); + } + + /** + * 查询单个调课申请的完整详情。 + *

+ * 根据调课申请编号查询该申请的完整信息,包括申请信息、调整后的内容、 + * 教研室审批状态、机关审批记录、关联的教室、辅助教员、学员队、保障明细等。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @return 调课申请详情 + */ + @Override + public ScheduleAdjustDetailVO getScheduleAdjustDetail(String ssdksqbh) { + // 步骤1:校验调课申请编号参数 + if (ssdksqbh == null || ssdksqbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课申请编号不能为空"); + } + + // 步骤2:查询调课申请主表信息 + ScheduleAdjustDetailVO detail = ssdksqMapper.selectDetailById(ssdksqbh); + if (detail == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "未找到该调课申请"); + } + + // 步骤3:查询关联的机关审批记录列表 + detail.setAuditList(ssdksqspMapper.selectBySsdksqbh(ssdksqbh)); + + // 步骤4:查询关联的新教室列表 + detail.setRoomList(dksqjsMapper.selectByDksqbh(ssdksqbh)); + + // 步骤5:查询关联的新辅助教员列表 + detail.setTeacherList(dksqfzjyMapper.selectByDksqbh(ssdksqbh)); + + // 步骤6:查询关联的新学员队列表 + detail.setTeamList(dksqxydMapper.selectByDksqbh(ssdksqbh)); + + // 步骤7:查询关联的新保障明细列表 + detail.setSupportList(dksqbzmxMapper.selectByDksqbh(ssdksqbh)); + + // 步骤8:返回调课申请完整详情 + return detail; + } + + /** + * 提交调课申请。 + *

+ * 教员提交调课申请,包含课次编号、调课事由、新的日期节次、新的教室、 + * 辅助教员、学员队、保障明细等完整信息。 + * 同时更新课次数据,将课次的日期节次替换为调整后的新值。 + * 事务保证主表与子表数据一致性。 + *

+ * + * @param dto 调课申请参数 + * @return 调课申请编号 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String submitScheduleAdjust(ScheduleAdjustApplyDTO dto) { + // 步骤1:校验调课申请参数 + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课申请参数不能为空"); + } + if (dto.getSskcbbh() == null || dto.getSskcbbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "课次编号不能为空"); + } + if (dto.getSqjybh() == null || dto.getSqjybh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "申请教员编号不能为空"); + } + if (dto.getSy() == null || dto.getSy().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课事由不能为空"); + } + + // 步骤2:查询课次实体,校验课次是否存在 + SSKCB lesson = sskcbMapper.selectById(dto.getSskcbbh()); + if (lesson == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "课次不存在"); + } + if (lesson.getSczt() != null && lesson.getSczt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该课次已删除,不允许申请调课"); + } + + // 调课时间段策略校验 + validateScheduleAdjustTimeLimitStrategy(dto.getRq()); + + // 判断是否命中免审批策略 + boolean autoApproved = matchAutoApproveStrategy(); + + // 步骤3:构建并保存调课申请主表实体 + SSDKSQ apply = new SSDKSQ(); + String ssdksqbh = UuidUtil.getOriginalUUID(); // 生成唯一调课申请编号 + apply.setBh(ssdksqbh); + apply.setSskcbbh(dto.getSskcbbh()); // 课次编号 + apply.setSqjybh(dto.getSqjybh()); // 申请教员编号 + apply.setSy(dto.getSy()); // 调课事由 + apply.setRq(dto.getRq()); // 新日期 + apply.setJc(dto.getJc()); // 新节次 + apply.setNd(dto.getNd()); // 年度 + apply.setJxnr(dto.getJxnr()); // 教学内容 + apply.setJxyd(dto.getJxyd()); // 教学要点 + apply.setJxff(dto.getJxff()); // 教学方法 + apply.setJxbzbz(dto.getJxbzbz()); // 教学保障备注 + apply.setYcxx(dto.getYcxx()); // 用车信息 + apply.setBz(dto.getBz()); // 备注 + // 将原日期节次描述追加到备注中(ydsjap字段类型为Integer,用于存储其他业务数据) + String originalDesc = buildOriginalScheduleDesc(lesson); + if (dto.getBz() != null && !dto.getBz().trim().isEmpty()) { + apply.setBz(dto.getBz() + " | " + originalDesc); + } else { + apply.setBz(originalDesc); + } + // 命中免审批策略时在备注追加标记,便于教务人员识别 + if (autoApproved) { + apply.setBz((apply.getBz() == null ? "" : apply.getBz() + " | ") + "[免审批策略自动通过]"); + } + // 审批状态初始化:未审批、未查收、未删除 + if (autoApproved) { + // 命中免审批策略:教研室和教学系审批状态直接设为已通过,跳过三级审批 + apply.setJyspzzt(1); + apply.setJxxspzzt(1); + } else { + apply.setJyspzzt(0); + } + apply.setJyscszt(0); + apply.setSczt(0); + // 时间字段 + LocalDateTime now = LocalDateTime.now(); + apply.setCreateTime(now); + apply.setUpdateTime(now); + + // 步骤4:插入调课申请主表数据 + ssdksqMapper.insert(apply); + + // 步骤5:保存新教室关联数据 + if (dto.getRoomList() != null && !dto.getRoomList().isEmpty()) { + for (AdjustRoomDTO roomDto : dto.getRoomList()) { + DKSQJS roomEntity = new DKSQJS(); + roomEntity.setBh(UuidUtil.getOriginalUUID()); + roomEntity.setDksqbh(ssdksqbh); + roomEntity.setJsbh(roomDto.getJsbh()); + roomEntity.setBz(roomDto.getBz()); + dksqjsMapper.insert(roomEntity); + } + } + + // 步骤6:保存新辅助教员关联数据 + if (dto.getTeacherList() != null && !dto.getTeacherList().isEmpty()) { + for (AdjustTeacherDTO teacherDto : dto.getTeacherList()) { + DKSQFZJY teacherEntity = new DKSQFZJY(); + teacherEntity.setBh(UuidUtil.getOriginalUUID()); + teacherEntity.setDksqbh(ssdksqbh); + teacherEntity.setFzjybh(teacherDto.getFzjybh()); + teacherEntity.setZjy(teacherDto.getZjy()); + teacherEntity.setBz(teacherDto.getBz()); + dksqfzjyMapper.insert(teacherEntity); + } + } + + // 步骤7:保存新学员队关联数据 + if (dto.getTeamList() != null && !dto.getTeamList().isEmpty()) { + for (AdjustTeamDTO teamDto : dto.getTeamList()) { + DKSQXYD teamEntity = new DKSQXYD(); + teamEntity.setBh(UuidUtil.getOriginalUUID()); + teamEntity.setDksqbh(ssdksqbh); + teamEntity.setXydbh(teamDto.getXydbh()); + dksqxydMapper.insert(teamEntity); + } + } + + // 步骤8:保存新保障明细关联数据 + if (dto.getSupportList() != null && !dto.getSupportList().isEmpty()) { + for (AdjustSupportDTO supportDto : dto.getSupportList()) { + DKSQBZMX supportEntity = new DKSQBZMX(); + supportEntity.setBh(UuidUtil.getOriginalUUID()); + supportEntity.setSqbh(ssdksqbh); + supportEntity.setBztgmxbh(supportDto.getBztgmxbh()); + supportEntity.setSl(supportDto.getSl()); + supportEntity.setBz(supportDto.getBz()); + dksqbzmxMapper.insert(supportEntity); + } + } + + // 步骤9:更新课次数据,将日期节次替换为调整后的新值 + lesson.setRq(dto.getRq()); + lesson.setJc(dto.getJc()); + lesson.setBdsj(now); + sskcbMapper.updateById(lesson); + + // 步骤10:记录调课申请操作日志 + recordOperationLog(ssdksqbh, "申请", "提交调课申请:" + ssdksqbh); + + // 步骤11:返回调课申请编号 + return ssdksqbh; + } + + /** + * 教研室审批调课申请。 + *

+ * 教研室审批教员提交的调课申请,包含同意、发回、拒绝三种处理方式。 + * 同意时同步更新课次数据,发回时记录发回原因,拒绝时标记申请为已拒绝。 + *

+ * + * @param dto 教研室审批参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void auditByTeachingOffice(ScheduleAdjustAuditDTO dto) { + // 步骤1:校验审批参数 + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批参数不能为空"); + } + if (!"jys".equals(dto.getAuditRole())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批角色不正确,应为jys"); + } + if (dto.getSpzt() == null + || (dto.getSpzt() != 1 && dto.getSpzt() != 2 && dto.getSpzt() != 3)) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批结果不正确,应为1(同意)、2(发回)或3(拒绝)"); + } + + // 步骤2:查询调课申请实体,校验申请是否存在 + SSDKSQ apply = ssdksqMapper.selectById(dto.getSsdksqbh()); + if (apply == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在"); + } + if (apply.getSczt() != null && apply.getSczt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已撤销,不允许审批"); + } + + // 步骤3:校验申请是否已审批,避免重复审批 + if (apply.getJyspzzt() != null && apply.getJyspzzt() != 0) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已审批,无需重复审批"); + } + + // 步骤4:更新调课申请主表的教研室审批状态 + LocalDateTime now = LocalDateTime.now(); + apply.setJyspzzt(dto.getSpzt()); // 教研室审批状态 + apply.setJyspzrbh(dto.getSprbh()); // 教研室审批人编号 + apply.setJyspzsj(now); // 教研室审批时间 + apply.setFhyj(dto.getFhyj()); // 发回意见 + apply.setUpdateTime(now); // 更新时间 + ssdksqMapper.updateById(apply); + + // 步骤5:根据审批结果执行不同的后续处理 + if (dto.getSpzt() == 3) { + // 拒绝:恢复课次原数据,标记申请最终结束 + restoreLessonSchedule(apply); + } else if (dto.getSpzt() == 2) { + // 发回:发回教员修改,课次保留调整后数据,由教员重新申请 + // 此处无需修改课次数据 + } else { + // 同意:课次数据已由申请时更新,无需重复处理 + } + + // 步骤6:记录审批操作日志 + recordOperationLog(apply.getBh(), "教研室审批", "教研室审批调课申请:" + apply.getBh() + + ",审批结果:" + dto.getSpzt() + ",审批意见:" + dto.getFhyj()); + } + + /** + * 教研室查收调课申请。 + *

+ * 教研室查收调课申请,标记查收状态和查收时间。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @param csrbh 查收人编号 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void receiveByTeachingOffice(String ssdksqbh, String csrbh) { + // 步骤1:校验参数 + if (ssdksqbh == null || ssdksqbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课申请编号不能为空"); + } + if (csrbh == null || csrbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "查收人编号不能为空"); + } + + // 步骤2:查询调课申请实体 + SSDKSQ apply = ssdksqMapper.selectById(ssdksqbh); + if (apply == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在"); + } + + // 步骤3:校验是否已查收,避免重复查收 + if (apply.getJyscszt() != null && apply.getJyscszt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已查收,无需重复查收"); + } + + // 步骤4:更新查收状态和查收时间 + apply.setJyscszt(1); // 教研室查收状态:已查收 + apply.setJyscsrbh(csrbh); // 教研室查收人编号 + apply.setJyscssj(LocalDateTime.now()); // 教研室查收时间 + apply.setUpdateTime(LocalDateTime.now()); // 更新时间 + ssdksqMapper.updateById(apply); + + // 步骤5:记录查收操作日志 + recordOperationLog(apply.getBh(), "教研室查收", "教研室查收调课申请:" + apply.getBh()); + } + + /** + * 教学系审批调课申请。 + *

+ * 教学系审批教研室已同意的调课申请,包含同意、发回、拒绝三种处理方式。 + * 教学系同意后流转到机关审批,构成三级审批流程(教研室→教学系→机关)。 + *

+ * + * @param dto 教学系审批参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void auditByTeachingDepartment(ScheduleAdjustAuditDTO dto) { + // 步骤1:校验审批参数 + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批参数不能为空"); + } + if (!"jxx".equals(dto.getAuditRole())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批角色不正确,应为jxx"); + } + if (dto.getSpzt() == null + || (dto.getSpzt() != 1 && dto.getSpzt() != 2 && dto.getSpzt() != 3)) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批结果不正确,应为1(同意)、2(发回)或3(拒绝)"); + } + + // 步骤2:查询调课申请实体 + SSDKSQ apply = ssdksqMapper.selectById(dto.getSsdksqbh()); + if (apply == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在"); + } + if (apply.getSczt() != null && apply.getSczt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已撤销,不允许审批"); + } + + // 步骤3:校验教研室是否已同意,教学系审批应在教研室同意之后进行 + if (apply.getJyspzzt() == null || apply.getJyspzzt() != 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "教研室未同意或未审批,无法进行教学系审批"); + } + + // 步骤4:校验教学系是否已审批,避免重复审批 + if (apply.getJxxspzzt() != null && apply.getJxxspzzt() != 0) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请教学系已审批,无需重复审批"); + } + + // 步骤5:更新调课申请主表的教学系审批状态 + LocalDateTime now = LocalDateTime.now(); + apply.setJxxspzzt(dto.getSpzt()); // 教学系审批状态 + apply.setJxxspzrbh(dto.getSprbh()); // 教学系审批人编号 + apply.setJxxspzsj(now); // 教学系审批时间 + apply.setFhyj(dto.getFhyj()); // 发回意见 + apply.setUpdateTime(now); // 更新时间 + ssdksqMapper.updateById(apply); + + // 步骤6:根据审批结果执行不同的后续处理 + if (dto.getSpzt() == 3) { + // 拒绝:恢复课次原数据,标记申请最终结束 + restoreLessonSchedule(apply); + } else if (dto.getSpzt() == 2) { + // 发回:发回教研室,课次保留调整后数据,由教研室重新处理 + } else { + // 同意:流转到机关审批 + } + + // 步骤7:记录教学系审批操作日志 + recordOperationLog(apply.getBh(), "教学系审批", "教学系审批调课申请:" + apply.getBh() + + ",审批结果:" + dto.getSpzt() + ",审批意见:" + dto.getFhyj()); + } + + /** + * 机关审批调课申请。 + *

+ * 机关审批教研室已同意的调课申请,包含同意、发回、拒绝三种处理方式。 + * 机关全部同意后调课申请最终生效,课次按调整后的内容执行。 + *

+ * + * @param dto 机关审批参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void auditByAuthority(ScheduleAdjustAuditDTO dto) { + // 步骤1:校验审批参数 + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批参数不能为空"); + } + if (!"jg".equals(dto.getAuditRole())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批角色不正确,应为jg"); + } + if (dto.getSpzt() == null + || (dto.getSpzt() != 1 && dto.getSpzt() != 2 && dto.getSpzt() != 3)) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批结果不正确"); + } + if (dto.getJgbh() == null || dto.getJgbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "机关编号不能为空"); + } + + // 步骤2:查询调课申请实体 + SSDKSQ apply = ssdksqMapper.selectById(dto.getSsdksqbh()); + if (apply == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在"); + } + if (apply.getSczt() != null && apply.getSczt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已撤销,不允许审批"); + } + + // 步骤3:校验教研室是否已同意,机关审批应在教研室同意之后进行 + if (apply.getJyspzzt() == null || apply.getJyspzzt() != 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "教研室未同意或未审批,无法进行机关审批"); + } + + // 步骤4:校验教学系是否已同意,机关审批应在教学系同意之后进行(三级审批) + if (apply.getJxxspzzt() == null || apply.getJxxspzzt() != 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "教学系未同意或未审批,无法进行机关审批"); + } + + // 步骤5:构建并保存机关审批记录 + SSDKSQSP audit = new SSDKSQSP(); + audit.setBh(UuidUtil.getOriginalUUID()); + audit.setSsdksqbh(dto.getSsdksqbh()); + audit.setJgbh(dto.getJgbh()); + audit.setSprbh(dto.getSprbh()); + audit.setSpsj(LocalDateTime.now()); + audit.setSpzt(dto.getSpzt()); + audit.setFhyj(dto.getFhyj()); + audit.setNd(apply.getNd()); + audit.setCreateTime(LocalDateTime.now()); + audit.setUpdateTime(LocalDateTime.now()); + ssdksqspMapper.insert(audit); + + // 步骤5:根据审批结果执行不同的后续处理 + if (dto.getSpzt() == 3) { + // 拒绝:恢复课次原数据,标记申请最终结束 + restoreLessonSchedule(apply); + } else if (dto.getSpzt() == 2) { + // 发回:发回教研室或教员,课次保留调整后数据 + } else { + // 同意:课次数据已由申请时更新,无需重复处理 + } + + // 步骤6:记录机关审批操作日志 + recordOperationLog(apply.getBh(), "机关审批", "机关审批调课申请:" + apply.getBh() + + ",机关编号:" + dto.getJgbh() + + ",审批结果:" + dto.getSpzt() + ",审批意见:" + dto.getFhyj()); + } + + /** + * 机关查收调课申请。 + *

+ * 机关查收调课申请,标记查收状态和查收时间。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @param jgbh 机关编号 + * @param csrbh 查收人编号 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void receiveByAuthority(String ssdksqbh, String jgbh, String csrbh) { + // 步骤1:校验参数 + if (ssdksqbh == null || ssdksqbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课申请编号不能为空"); + } + if (jgbh == null || jgbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "机关编号不能为空"); + } + if (csrbh == null || csrbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "查收人编号不能为空"); + } + + // 步骤2:查询调课申请实体 + SSDKSQ apply = ssdksqMapper.selectById(ssdksqbh); + if (apply == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在"); + } + + // 步骤3:检查当前机关是否已查收 + LambdaQueryWrapper checkWrapper = new LambdaQueryWrapper<>(); + checkWrapper.eq(SSDKSQSP::getSsdksqbh, ssdksqbh); + checkWrapper.eq(SSDKSQSP::getJgbh, jgbh); + checkWrapper.eq(SSDKSQSP::getCszt, 1); + if (ssdksqspMapper.selectCount(checkWrapper) > 0) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "当前机关已查收该调课申请,无需重复查收"); + } + + // 步骤4:构建并保存机关查收记录 + SSDKSQSP receive = new SSDKSQSP(); + receive.setBh(UuidUtil.getOriginalUUID()); + receive.setSsdksqbh(ssdksqbh); + receive.setJgbh(jgbh); + receive.setCsrbh(csrbh); + receive.setCszt(1); + receive.setCssj(LocalDateTime.now()); + receive.setNd(apply.getNd()); + receive.setCreateTime(LocalDateTime.now()); + receive.setUpdateTime(LocalDateTime.now()); + ssdksqspMapper.insert(receive); + + // 步骤5:记录查收操作日志 + recordOperationLog(apply.getBh(), "机关查收", "机关查收调课申请:" + apply.getBh() + + ",机关编号:" + jgbh); + } + + /** + * 撤销调课申请。 + *

+ * 教员撤销自己提交的、未经过审批的调课申请。 + * 软删除调课申请,标记删除状态为1。 + * 事务保证主表软删除与课次原数据恢复的一致性。 + *

+ * + * @param ssdksqbh 调课申请编号 + * @param sqjybh 申请教员编号(用于权限校验) + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelScheduleAdjust(String ssdksqbh, String sqjybh) { + // 步骤1:校验参数 + if (ssdksqbh == null || ssdksqbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "调课申请编号不能为空"); + } + if (sqjybh == null || sqjybh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "申请教员编号不能为空"); + } + + // 步骤2:查询调课申请实体 + SSDKSQ apply = ssdksqMapper.selectById(ssdksqbh); + if (apply == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课申请不存在"); + } + + // 步骤3:校验是否已删除,避免重复撤销 + if (apply.getSczt() != null && apply.getSczt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请已撤销,无需重复撤销"); + } + + // 步骤4:校验权限,仅申请教员本人可撤销 + if (!sqjybh.equals(apply.getSqjybh())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "仅申请教员本人可撤销该调课申请"); + } + + // 步骤5:校验审批状态,已审批通过的申请不允许撤销 + if (apply.getJyspzzt() != null && apply.getJyspzzt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该调课申请教研室已同意,不允许撤销"); + } + + // 步骤6:软删除调课申请主表 + apply.setSczt(1); // 删除状态:已删除 + apply.setUpdateTime(LocalDateTime.now()); // 更新时间 + ssdksqMapper.updateById(apply); + + // 步骤7:恢复课次原数据 + restoreLessonSchedule(apply); + + // 步骤8:记录撤销操作日志 + recordOperationLog(apply.getBh(), "撤销", "撤销调课申请:" + apply.getBh()); + } + + /** + * 构建原日期节次描述。 + *

+ * 根据课次实体生成原日期节次的字符串描述,便于调课申请中保留原信息。 + *

+ * + * @param lesson 课次实体 + * @return 原日期节次描述 + */ + private String buildOriginalScheduleDesc(SSKCB lesson) { + if (lesson == null) { + return ""; + } + // 拼接原日期和原节次为可读字符串 + StringBuilder sb = new StringBuilder(); + if (lesson.getRq() != null) { + sb.append("原日期:").append(lesson.getRq().format(DATE_TIME_FORMATTER)); + } + if (lesson.getJc() != null) { + sb.append(",原节次:第").append(lesson.getJc()).append("节"); + } + return sb.toString(); + } + + /** + * 恢复课次原数据。 + *

+ * 当调课申请被拒绝或撤销时,恢复课次到申请前的原始数据。 + * 实际业务中通常需要从历史快照恢复,此处简化为清空日期节次。 + *

+ * + * @param apply 调课申请实体 + */ + private void restoreLessonSchedule(SSDKSQ apply) { + // 步骤1:查询课次实体 + SSKCB lesson = sskcbMapper.selectById(apply.getSskcbbh()); + if (lesson == null) { + return; + } + + // 步骤2:清空课次因调课而设置的日期节次(恢复原状) + lesson.setRq(null); + lesson.setJc(null); + sskcbMapper.updateById(lesson); + } + + // ==================== 教学保障管理 ==================== + + /** + * 查询教学保障提供资源列表。 + *

+ * 从保障提供明细表中查询可供选择的保障资源,支持按保障类别编号筛选, + * 仅返回未停用的保障提供明细。 + *

+ * + * @param bzlbbh 保障类别编号,为空时查询全部 + * @return 保障提供资源列表 + */ + @Override + public List querySupportResourceList(String bzlbbh) { + // 步骤1:调用Mapper查询保障提供资源列表,关联保障类别表获取类别名称 + return bztgmxMapper.selectResourceList(bzlbbh); + } + + /** + * 查询课次的教学保障计划列表。 + *

+ * 根据课次编号查询该课次已登记的教学保障计划明细,包括保障名称、单位、 + * 数量、备注、确认状态及确认信息等。 + *

+ * + * @param sskcbbh 实施_课程表编号(课次编号) + * @return 教学保障计划列表 + */ + @Override + public List queryTeachingSupportPlanList(String sskcbbh) { + // 步骤1:校验课次编号参数 + if (sskcbbh == null || sskcbbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "课次编号不能为空"); + } + + // 步骤2:调用Mapper查询课次的教学保障计划列表 + List planList = kcbbzmxMapper.selectBySskcbbh(sskcbbh); + + // 步骤3:如果查询结果为空,返回空列表而非null,便于前端处理 + if (planList == null) { + return new ArrayList<>(); + } + + // 步骤4:返回教学保障计划列表 + return planList; + } + + /** + * 登记单条教学保障计划。 + *

+ * 为指定课次登记一条教学保障需求,指定保障提供明细、数量、备注等信息。 + * 登记前校验课次是否存在,保障提供明细是否存在且未停用。 + *

+ * + * @param dto 教学保障计划登记参数 + * @return 课程表_保障明细编号 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String saveTeachingSupportPlan(TeachingSupportPlanDTO dto) { + // 步骤1:校验登记参数 + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障计划参数不能为空"); + } + if (dto.getSskcbbh() == null || dto.getSskcbbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "课次编号不能为空"); + } + if (dto.getBztgmxbh() == null || dto.getBztgmxbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障提供明细编号不能为空"); + } + if (dto.getSl() == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障数量不能为空"); + } + + // 步骤2:查询课次实体,校验课次是否存在 + SSKCB lesson = sskcbMapper.selectById(dto.getSskcbbh()); + if (lesson == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "课次不存在"); + } + if (lesson.getSczt() != null && lesson.getSczt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该课次已删除,不允许登记保障计划"); + } + + // 步骤3:查询保障提供明细,校验资源是否存在且未停用 + BZTGMX resource = bztgmxMapper.selectById(dto.getBztgmxbh()); + if (resource == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "保障提供明细不存在"); + } + if (resource.getTy() != null && resource.getTy() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该保障提供明细已停用,不允许登记"); + } + + // 步骤4:构建课程表_保障明细实体 + KCBBZMX plan = new KCBBZMX(); + String kcbbzmxbh = UuidUtil.getOriginalUUID(); // 生成唯一保障计划编号 + plan.setBh(kcbbzmxbh); + plan.setKcbbh(dto.getSskcbbh()); // 课程表编号字段存储课次编号 + plan.setBztgmxbh(dto.getBztgmxbh()); // 保障提供明细编号 + plan.setSl(dto.getSl()); // 数量 + plan.setBz(dto.getBz()); // 备注 + plan.setNd(dto.getNd()); // 年度 + // 从课次实体复制日期节次,便于后续统计和展示 + plan.setRq(lesson.getRq()); + plan.setJc(lesson.getJc()); + plan.setCreateTime(LocalDateTime.now()); + plan.setUpdateTime(LocalDateTime.now()); + + // 步骤5:插入教学保障计划数据 + kcbbzmxMapper.insert(plan); + + // 步骤6:记录操作日志 + recordOperationLog(kcbbzmxbh, "保障登记", "登记教学保障计划:" + kcbbzmxbh + + ",课次:" + dto.getSskcbbh() + + ",保障明细:" + dto.getBztgmxbh() + + ",数量:" + dto.getSl()); + + // 步骤7:返回课程表_保障明细编号 + return kcbbzmxbh; + } + + /** + * 批量登记教学保障计划。 + *

+ * 一次性为指定课次登记多条教学保障需求,事务保证批量数据一致性。 + *

+ * + * @param dto 教学保障计划批量登记参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void batchSaveTeachingSupportPlan(TeachingSupportBatchPlanDTO dto) { + // 步骤1:校验批量登记参数 + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "批量保障计划参数不能为空"); + } + if (dto.getSskcbbh() == null || dto.getSskcbbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "课次编号不能为空"); + } + if (dto.getPlanList() == null || dto.getPlanList().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障计划列表不能为空"); + } + + // 步骤2:校验课次是否存在 + SSKCB lesson = sskcbMapper.selectById(dto.getSskcbbh()); + if (lesson == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "课次不存在"); + } + if (lesson.getSczt() != null && lesson.getSczt() == 1) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该课次已删除,不允许登记保障计划"); + } + + // 步骤3:遍历每条保障计划条目,逐个登记 + for (TeachingSupportPlanItemDTO item : dto.getPlanList()) { + // 步骤4:将批量条目转换为单条登记DTO + TeachingSupportPlanDTO singleDto = new TeachingSupportPlanDTO(); + singleDto.setSskcbbh(dto.getSskcbbh()); + singleDto.setBztgmxbh(item.getBztgmxbh()); + singleDto.setNd(item.getNd()); + singleDto.setSl(item.getSl()); + singleDto.setBz(item.getBz()); + + // 步骤5:调用单条登记方法保存当前保障计划 + saveTeachingSupportPlan(singleDto); + } + + // 步骤6:记录批量登记操作日志 + recordOperationLog(dto.getSskcbbh(), "批量保障登记", "批量登记教学保障计划,课次:" + + dto.getSskcbbh() + ",共" + dto.getPlanList().size() + "条"); + } + + /** + * 修改教学保障计划。 + *

+ * 修改已登记的教学保障计划,包括调整数量和备注信息。 + * 已确认的教学保障计划不允许修改,如需修改需先取消确认。 + *

+ * + * @param dto 教学保障计划修改参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void updateTeachingSupportPlan(TeachingSupportUpdateDTO dto) { + // 步骤1:校验修改参数 + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障计划修改参数不能为空"); + } + if (dto.getKcbbzmxbh() == null || dto.getKcbbzmxbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障计划编号不能为空"); + } + if (dto.getSl() == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障数量不能为空"); + } + + // 步骤2:查询保障计划实体,校验是否存在 + KCBBZMX plan = kcbbzmxMapper.selectById(dto.getKcbbzmxbh()); + if (plan == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "教学保障计划不存在"); + } + + // 步骤3:校验是否已确认,已确认的保障计划不允许修改 + if (isSupportConfirmed(plan.getBz())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该教学保障计划已确认,不允许修改,如需修改请先取消确认"); + } + + // 步骤4:更新保障计划的数量和备注 + plan.setSl(dto.getSl()); + // 保留原有备注中的确认状态信息(如果有),并更新用户备注内容 + plan.setBz(mergeSupportRemark(plan.getBz(), dto.getBz())); + plan.setUpdateTime(LocalDateTime.now()); + + // 步骤5:执行数据库更新 + kcbbzmxMapper.updateById(plan); + + // 步骤6:记录操作日志 + recordOperationLog(plan.getBh(), "保障修改", "修改教学保障计划:" + plan.getBh() + + ",数量:" + dto.getSl()); + } + + /** + * 删除教学保障计划。 + *

+ * 删除已登记的教学保障计划。 + * 已确认的教学保障计划不允许删除,如需删除需先取消确认。 + *

+ * + * @param kcbbzmxbh 课程表_保障明细编号 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteTeachingSupportPlan(String kcbbzmxbh) { + // 步骤1:校验保障计划编号参数 + if (kcbbzmxbh == null || kcbbzmxbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障计划编号不能为空"); + } + + // 步骤2:查询保障计划实体,校验是否存在 + KCBBZMX plan = kcbbzmxMapper.selectById(kcbbzmxbh); + if (plan == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "教学保障计划不存在"); + } + + // 步骤3:校验是否已确认,已确认的保障计划不允许删除 + if (isSupportConfirmed(plan.getBz())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该教学保障计划已确认,不允许删除,如需删除请先取消确认"); + } + + // 步骤4:执行物理删除 + kcbbzmxMapper.deleteById(kcbbzmxbh); + + // 步骤5:记录操作日志 + recordOperationLog(kcbbzmxbh, "保障删除", "删除教学保障计划:" + kcbbzmxbh); + } + + /** + * 确认教学保障到位。 + *

+ * 确认某条教学保障计划已经落实到位,记录确认人、确认时间和确认备注。 + * 已确认的保障计划不允许重复确认。 + *

+ * + * @param dto 教学保障到位确认参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void confirmTeachingSupport(TeachingSupportConfirmDTO dto) { + // 步骤1:校验确认参数 + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障确认参数不能为空"); + } + if (dto.getKcbbzmxbh() == null || dto.getKcbbzmxbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障计划编号不能为空"); + } + if (dto.getQrrbh() == null || dto.getQrrbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "确认人编号不能为空"); + } + + // 步骤2:查询保障计划实体,校验是否存在 + KCBBZMX plan = kcbbzmxMapper.selectById(dto.getKcbbzmxbh()); + if (plan == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "教学保障计划不存在"); + } + + // 步骤3:校验是否已确认,避免重复确认 + if (isSupportConfirmed(plan.getBz())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该教学保障计划已确认,无需重复确认"); + } + + // 步骤4:构建确认状态JSON,追加到备注字段中 + String confirmJson = buildSupportConfirmJson(dto.getQrrbh(), dto.getQrbz()); + plan.setBz(mergeSupportRemark(plan.getBz(), confirmJson)); + plan.setUpdateTime(LocalDateTime.now()); + + // 步骤5:执行数据库更新 + kcbbzmxMapper.updateById(plan); + + // 步骤6:记录操作日志 + recordOperationLog(dto.getKcbbzmxbh(), "保障确认", "确认教学保障到位:" + dto.getKcbbzmxbh() + + ",确认人:" + dto.getQrrbh()); + } + + /** + * 批量确认教学保障到位。 + *

+ * 一次性确认多条教学保障计划已经落实到位,逐条处理并返回整体执行结果。 + * 每条记录独立处理,失败记录不影响其他记录。 + *

+ * + * @param dtoList 教学保障到位确认参数列表 + * @return 批量确认结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public TeachingSupportConfirmResultVO batchConfirmTeachingSupport(List dtoList) { + // 步骤1:校验批量确认参数 + if (dtoList == null || dtoList.isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "批量确认参数不能为空"); + } + + // 步骤2:初始化统计变量 + int totalCount = dtoList.size(); + int successCount = 0; + List failedList = new ArrayList<>(); + + // 步骤3:遍历每条确认记录,逐个处理 + for (TeachingSupportConfirmDTO dto : dtoList) { + try { + // 步骤4:调用单条确认方法处理当前记录 + confirmTeachingSupport(dto); + // 步骤5:单条处理成功,更新成功计数 + successCount++; + } catch (Exception e) { + // 步骤6:单条处理失败,记录失败的保障计划编号,继续处理下一条 + failedList.add(dto.getKcbbzmxbh()); + } + } + + // 步骤7:构建批量确认结果 + TeachingSupportConfirmResultVO result = new TeachingSupportConfirmResultVO(totalCount, successCount, failedList); + if (successCount == totalCount) { + result.setMessage("全部确认成功"); + } else if (successCount == 0) { + result.setMessage("全部确认失败"); + } else { + result.setMessage("部分确认成功,失败" + failedList.size() + "条"); + } + + // 步骤8:返回批量确认结果 + return result; + } + + /** + * 取消教学保障到位确认。 + *

+ * 取消某条教学保障计划的到位确认,恢复为未确认状态。 + * 取消操作通过清空备注中的确认状态JSON实现。 + *

+ * + * @param kcbbzmxbh 课程表_保障明细编号 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelConfirmTeachingSupport(String kcbbzmxbh) { + // 步骤1:校验保障计划编号参数 + if (kcbbzmxbh == null || kcbbzmxbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "保障计划编号不能为空"); + } + + // 步骤2:查询保障计划实体,校验是否存在 + KCBBZMX plan = kcbbzmxMapper.selectById(kcbbzmxbh); + if (plan == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "教学保障计划不存在"); + } + + // 步骤3:校验是否已确认,未确认的保障计划无需取消 + if (!isSupportConfirmed(plan.getBz())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "该教学保障计划未确认,无需取消确认"); + } + + // 步骤4:移除备注中的确认状态JSON,恢复为未确认状态 + plan.setBz(clearSupportConfirmJson(plan.getBz())); + plan.setUpdateTime(LocalDateTime.now()); + + // 步骤5:执行数据库更新 + kcbbzmxMapper.updateById(plan); + + // 步骤6:记录操作日志 + recordOperationLog(kcbbzmxbh, "取消保障确认", "取消教学保障确认:" + kcbbzmxbh); + } + + /** + * 判断教学保障计划是否已确认。 + *

+ * 通过判断备注字段中是否包含确认状态JSON来识别。 + *

+ * + * @param remark 备注字段内容 + * @return true-已确认,false-未确认 + */ + private boolean isSupportConfirmed(String remark) { + return remark != null && remark.contains("\"confirmed\":true"); + } + + /** + * 构建确认状态JSON字符串。 + *

+ * 将确认人、确认时间、确认备注封装为JSON格式,便于存储到备注字段中。 + *

+ * + * @param qrrbh 确认人编号 + * @param qrbz 确认备注 + * @return 确认状态JSON字符串 + */ + private String buildSupportConfirmJson(String qrrbh, String qrbz) { + String confirmAt = LocalDateTime.now().format(DATE_TIME_FORMATTER); + StringBuilder sb = new StringBuilder(); + sb.append("{\"confirmed\":true,"); + sb.append("\"confirmedBy\":\"").append(qrrbh).append("\","); + sb.append("\"confirmedAt\":\"").append(confirmAt).append("\","); + sb.append("\"confirmRemark\":\""); + if (qrbz != null) { + // 简单处理,将双引号转义,避免JSON格式错误 + sb.append(qrbz.replace("\"", "\\\"")); + } + sb.append("\"}"); + return sb.toString(); + } + + /** + * 合并用户备注和系统确认状态JSON。 + *

+ * 将用户输入的备注内容与系统生成的确认状态JSON合并存储到备注字段中, + * 两者通过分隔符区分,避免覆盖。 + *

+ * + * @param originalRemark 原备注内容 + * @param newRemark 新备注内容(可能是用户备注或确认状态JSON) + * @return 合并后的备注内容 + */ + private String mergeSupportRemark(String originalRemark, String newRemark) { + // 步骤1:如果原备注为空,直接返回新备注 + if (originalRemark == null || originalRemark.trim().isEmpty()) { + return newRemark; + } + + // 步骤2:如果新备注为空,保留原备注 + if (newRemark == null || newRemark.trim().isEmpty()) { + return originalRemark; + } + + // 步骤3:如果新备注是确认状态JSON,追加到原备注后面 + if (newRemark.contains("\"confirmed\":true")) { + return originalRemark + " | " + newRemark; + } + + // 步骤4:如果原备注中包含确认状态JSON,需要保留确认状态信息,仅更新用户备注部分 + if (originalRemark.contains("\"confirmed\":true")) { + int splitIndex = originalRemark.indexOf(" | {"); + if (splitIndex > 0) { + return newRemark + originalRemark.substring(splitIndex); + } + // 如果没有分隔符但包含确认JSON,说明整个备注都是确认JSON,则返回新备注+确认JSON + return newRemark + " | " + originalRemark; + } + + // 步骤5:普通备注更新,直接返回新备注 + return newRemark; + } + + /** + * 清除备注中的确认状态JSON。 + *

+ * 取消确认时,从备注字段中移除确认状态JSON,保留用户备注内容。 + *

+ * + * @param remark 备注内容 + * @return 清除确认状态后的备注内容 + */ + private String clearSupportConfirmJson(String remark) { + // 步骤1:如果备注为空,直接返回空字符串 + if (remark == null || remark.trim().isEmpty()) { + return ""; + } + + // 步骤2:找到确认状态JSON的位置 + int splitIndex = remark.indexOf(" | {"); + if (splitIndex > 0) { + // 步骤3:返回分隔符之前的用户备注部分 + return remark.substring(0, splitIndex); + } + + // 步骤4:如果整个备注都是确认状态JSON,则返回空字符串 + if (remark.contains("\"confirmed\":true")) { + return ""; + } + + // 步骤5:不包含确认状态JSON,直接返回原备注 + return remark; + } + + @Override + public List listTeachingSupportHistory(String sskcbbh) { + if (sskcbbh == null || sskcbbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "课次编号不能为空"); + } + SSKCB lesson = sskcbMapper.selectById(sskcbbh); + if (lesson == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "课次不存在"); + } + String sskcbh = lesson.getSskcbh(); + if (sskcbh == null || sskcbh.trim().isEmpty()) { + return new ArrayList<>(); + } + List historyList = kcbbzmxMapper.selectHistoryBySskcbh(sskcbh, sskcbbh); + return historyList == null ? new ArrayList<>() : historyList; + } + + // ==================== 学员正课请假 ==================== + + /** + * 分页查询学员请假申请列表。 + *

+ * 根据查询条件分页查询学员正课请假申请记录,支持多条件筛选。 + *

+ * + * @param query 查询参数(包含分页信息和筛选条件) + * @return 分页学员请假申请列表 + */ + @Override + public PageResult queryStudentLeaveList(StudentLeaveQueryDTO query) { + // 步骤1:校验查询参数 + if (query == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "查询参数不能为空"); + } + if (query.getPageNum() == null || query.getPageSize() == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "分页参数不能为空"); + } + + // 步骤2:构建MyBatis-Plus分页对象 + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + + // 步骤3:将日期范围转换为字符串格式 + String cjsjStartStr = query.getCjsjStart() != null ? query.getCjsjStart().format(DATE_TIME_FORMATTER) : null; + String cjsjEndStr = query.getCjsjEnd() != null ? query.getCjsjEnd().format(DATE_TIME_FORMATTER) : null; + + // 步骤4:调用Mapper执行分页查询 + Page resultPage = xyJXQJBMapper.selectPageList( + page, + query.getQjbh(), + query.getXybh(), + query.getXyxm(), + query.getXydbh(), + query.getQjlx(), + query.getSpzt(), + query.getNd(), + cjsjStartStr, + cjsjEndStr + ); + + // 步骤5:翻译请假类型字典值 -> 字典标签 + Map leaveTypeLabelMap = buildLeaveTypeLabelMap(); + if (resultPage.getRecords() != null) { + for (StudentLeaveApplyVO vo : resultPage.getRecords()) { + if (vo.getQjlx() != null && leaveTypeLabelMap.containsKey(vo.getQjlx())) { + vo.setQjlxLabel(leaveTypeLabelMap.get(vo.getQjlx())); + } + } + } + + // 步骤6:封装分页结果并返回 + return new PageResult<>( + resultPage.getRecords(), + resultPage.getTotal(), + query.getPageNum(), + query.getPageSize() + ); + } + + /** + * 查询学员请假申请详情。 + *

+ * 根据请假编号查询该申请的完整信息,包括学员信息、请假时间范围、 + * 审批状态、审批记录及关联课次列表。 + *

+ * + * @param qjbh 请假编号 + * @return 学员请假申请详情 + */ + @Override + public StudentLeaveDetailVO getStudentLeaveDetail(String qjbh) { + // 步骤1:校验请假编号参数 + if (qjbh == null || qjbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "请假编号不能为空"); + } + + // 步骤2:查询请假申请详情 + StudentLeaveDetailVO detail = xyJXQJBMapper.selectDetailById(qjbh); + if (detail == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "未找到该请假申请"); + } + + // 步骤3:翻译请假类型字典值 -> 字典标签 + if (detail.getQjlx() != null) { + Map leaveTypeLabelMap = buildLeaveTypeLabelMap(); + if (leaveTypeLabelMap.containsKey(detail.getQjlx())) { + detail.setQjlxLabel(leaveTypeLabelMap.get(detail.getQjlx())); + } + } + + // 步骤4:返回请假申请详情 + return detail; + } + + /** + * 学员提交请假申请。 + *

+ * 学员在线提交正课请假申请,包含请假原因、时间范围、请假类型、 + * 关联课次等信息。系统自动生成请假编号并记录申请时间。 + *

+ * + * @param dto 学员请假申请参数 + * @return 请假编号 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String submitStudentLeave(StudentLeaveApplyDTO dto) { + // 步骤1:校验申请参数(参数合法性 + 时间合法性 + 字典值 + 时间冲突) + studentLeaveValidator.validateApply(dto); + checkLeaveTimeConflict(dto.getXybh(), dto.getQssj(), dto.getJssj()); + + // 步骤2:构建学员教学请假实体 + XYJXQJB leave = new XYJXQJB(); + String qjbh = UuidUtil.getOriginalUUID(); // 生成唯一请假编号 + leave.setBh(qjbh); + leave.setMc(dto.getXyxm()); // 名称(学员姓名) + leave.setQssj(dto.getQssj()); // 起始时间 + leave.setJssj(dto.getJssj()); // 结束时间 + leave.setZt("0"); // 状态:待审批 + leave.setYy(dto.getYy()); // 原因 + leave.setBz(dto.getBz()); // 备注 + leave.setXydbh(dto.getXydbh()); // 学员队编号 + leave.setMdd(dto.getMdd()); // 目的地 + leave.setQjlx(dto.getQjlx()); // 请假类型 + leave.setKts(dto.getSskcbbhList() != null ? dto.getSskcbbhList().size() : 0); // 课堂数 + leave.setXyrs(1); // 学员人数 + LocalDateTime now = LocalDateTime.now(); + leave.setCjsj(now); + + // 步骤3:插入学员教学请假表数据 + xyJXQJBMapper.insert(leave); + + // 步骤4:保存请假学员关联记录 + QJXYB qjxyb = new QJXYB(); + qjxyb.setBh(UuidUtil.getOriginalUUID()); + qjxyb.setQjbh(qjbh); + qjxyb.setXybh(dto.getXybh()); + qjxybMapper.insert(qjxyb); + + // 步骤5:保存关联课次记录 + if (dto.getSskcbbhList() != null && !dto.getSskcbbhList().isEmpty()) { + for (String sskcbbh : dto.getSskcbbhList()) { + QJKTB qjktb = new QJKTB(); + qjktb.setBh(UuidUtil.getOriginalUUID()); + qjktb.setQjbh(qjbh); + qjktb.setSsktbh(sskcbbh); + qjktbMapper.insert(qjktb); + } + } + + // 步骤6:记录操作日志(含请假类型、时间范围、关联课次数等审计要素) + String qjlxLabelForSubmit = dto.getQjlx() != null && !dto.getQjlx().trim().isEmpty() + ? buildLeaveTypeLabelMap().getOrDefault(dto.getQjlx(), dto.getQjlx()) + : "未指定"; + int ktsForSubmit = dto.getSskcbbhList() != null ? dto.getSskcbbhList().size() : 0; + recordOperationLog(qjbh, "请假申请", + "学员提交请假申请:" + qjbh + + ",学员编号:" + dto.getXybh() + + ",请假类型:" + qjlxLabelForSubmit + + ",时间范围:" + dto.getQssj() + " 至 " + dto.getJssj() + + ",关联课次:" + ktsForSubmit + "节" + + ",原因:" + dto.getYy()); + + // 步骤7:返回请假编号 + return qjbh; + } + + /** + * 教师审批学员请假申请。 + *

+ * 教师审批学员提交的正课请假申请,包含同意、拒绝、发回修改三种处理方式。 + * 审批通过后联动更新考勤记录。 + *

+ * + * @param dto 教师审批参数 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void auditStudentLeave(StudentLeaveAuditDTO dto) { + // 步骤1:校验审批参数(参数合法性) + studentLeaveValidator.validateAudit(dto); + + // 步骤2:查询并校验请假申请实体存在 + XYJXQJB leave = xyJXQJBMapper.selectById(dto.getQjbh()); + studentLeaveValidator.assertLeaveExists(leave); + + // 步骤3:校验审批权限(教员及以上身份 + 非请假学员本人) + studentLeaveValidator.assertApprovalAuthority(dto.getSprbh(), dto.getQjbh()); + + // 步骤4:校验是否已审批,避免重复审批 + studentLeaveValidator.assertNotAudited(leave); + + // 步骤5:更新请假申请状态 + String ztStr = "0"; + if (dto.getSpzt() == 1) { + ztStr = "1"; // 已同意 + } else if (dto.getSpzt() == 2) { + ztStr = "2"; // 已拒绝 + } else { + ztStr = "3"; // 已发回 + } + leave.setZt(ztStr); + xyJXQJBMapper.updateById(leave); + + // 步骤6:保存审批记录 + XYQJSPB audit = new XYQJSPB(); + audit.setBh(UuidUtil.getOriginalUUID()); + audit.setQjbh(dto.getQjbh()); + audit.setSpzt(String.valueOf(dto.getSpzt())); + audit.setSprbh(dto.getSprbh()); + audit.setSpsj(LocalDateTime.now()); + audit.setFhyj(dto.getSpyj()); + // 自动获取审批人归属的机关编号 + String departmentId = resolveDepartmentIdByTeacher(dto.getSprbh()); + audit.setJgbh(departmentId); + xyQJSPBMapper.insert(audit); + + // 步骤7:审批通过时联动更新考勤记录 + if (dto.getSpzt() == 1) { + updateAttendanceForLeave(dto.getQjbh()); + } + + // 步骤8:记录操作日志(含请假类型、时间范围、审批结果等审计要素) + String spztLabel; + if (dto.getSpzt() == 1) { + spztLabel = "同意"; + } else if (dto.getSpzt() == 2) { + spztLabel = "拒绝"; + } else { + spztLabel = "发回修改"; + } + String qjlxLabelForAudit = leave.getQjlx() != null && !leave.getQjlx().trim().isEmpty() + ? buildLeaveTypeLabelMap().getOrDefault(leave.getQjlx(), leave.getQjlx()) + : "未指定"; + List leaveStudents = studentLeaveValidator.getLeaveStudents(dto.getQjbh()); + StringBuilder studentBhSb = new StringBuilder(); + for (QJXYB qjxyb : leaveStudents) { + if (studentBhSb.length() > 0) { + studentBhSb.append(","); + } + studentBhSb.append(qjxyb.getXybh()); + } + recordOperationLog(dto.getQjbh(), "请假审批", + "教师审批学员请假申请:" + dto.getQjbh() + + ",审批人:" + dto.getSprbh() + + ",学员编号:" + studentBhSb + + ",请假类型:" + qjlxLabelForAudit + + ",时间范围:" + leave.getQssj() + " 至 " + leave.getJssj() + + ",审批结果:" + spztLabel + + ",审批意见:" + dto.getSpyj()); + } + + /** + * 学员撤回请假申请。 + *

+ * 学员撤回自己提交的、未经过审批的请假申请。 + *

+ * + * @param qjbh 请假编号 + * @param xybh 学员编号(用于权限校验) + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void cancelStudentLeave(String qjbh, String xybh) { + // 步骤1:校验参数 + studentLeaveValidator.validateRevokeParam(qjbh, xybh); + + // 步骤2:查询并校验请假申请实体存在 + XYJXQJB leave = xyJXQJBMapper.selectById(qjbh); + studentLeaveValidator.assertLeaveExists(leave); + + // 步骤3:校验学员身份,只有提交人本人才能撤回 + studentLeaveValidator.assertRevokeOwner(qjbh, xybh); + + // 步骤4:校验是否已审批,已审批的申请不允许撤回 + studentLeaveValidator.assertNotAudited(leave); + + // 步骤5:先记录操作日志(物理删除后无法查询数据,必须先记录;含请假类型、时间范围等审计要素) + String qjlxLabelForCancel = leave.getQjlx() != null && !leave.getQjlx().trim().isEmpty() + ? buildLeaveTypeLabelMap().getOrDefault(leave.getQjlx(), leave.getQjlx()) + : "未指定"; + recordOperationLog(qjbh, "撤回请假", + "学员撤回请假申请:" + qjbh + + ",学员编号:" + xybh + + ",请假类型:" + qjlxLabelForCancel + + ",时间范围:" + leave.getQssj() + " 至 " + leave.getJssj() + + ",原申请原因:" + leave.getYy()); + + // 步骤6:物理删除关联表数据(请假学员表、请假课堂表) + qjxybMapper.delete(new LambdaQueryWrapper().eq(QJXYB::getQjbh, qjbh)); + qjktbMapper.deleteByQjbh(qjbh); + + // 步骤7:物理删除主表数据(学员教学请假表) + xyJXQJBMapper.deleteById(qjbh); + } + + /** + * 学员查询自己的请假申请列表。 + *

+ * 学员自助查询接口,只能查看自己提交的请假记录,支持分页和按审批状态筛选。 + * 返回结果中请假类型已翻译为字典标签。 + *

+ * + * @param query 查询参数(学员编号xybh必填,分页信息和审批状态可选) + * @return 分页学员请假申请列表 + */ + @Override + public PageResult queryStudentOwnLeaveList(StudentLeaveQueryDTO query) { + // 步骤1:校验查询参数 + if (query == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "查询参数不能为空"); + } + if (query.getXybh() == null || query.getXybh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "学员编号不能为空"); + } + if (query.getPageNum() == null || query.getPageSize() == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "分页参数不能为空"); + } + + // 步骤2:构建MyBatis-Plus分页对象 + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + + // 步骤3:将日期范围转换为字符串格式 + String cjsjStartStr = query.getCjsjStart() != null ? query.getCjsjStart().format(DATE_TIME_FORMATTER) : null; + String cjsjEndStr = query.getCjsjEnd() != null ? query.getCjsjEnd().format(DATE_TIME_FORMATTER) : null; + + // 步骤4:调用Mapper执行分页查询(xybh已确保为当前学员自身) + Page resultPage = xyJXQJBMapper.selectPageList( + page, + query.getQjbh(), + query.getXybh(), + null, // 学员姓名不允许学员自助查询时使用,避免模糊匹配泄露他人信息 + query.getXydbh(), + query.getQjlx(), + query.getSpzt(), + query.getNd(), + cjsjStartStr, + cjsjEndStr + ); + + // 步骤5:翻译请假类型字典值 -> 字典标签 + Map leaveTypeLabelMap = buildLeaveTypeLabelMap(); + if (resultPage.getRecords() != null) { + for (StudentLeaveApplyVO vo : resultPage.getRecords()) { + if (vo.getQjlx() != null && leaveTypeLabelMap.containsKey(vo.getQjlx())) { + vo.setQjlxLabel(leaveTypeLabelMap.get(vo.getQjlx())); + } + } + } + + // 步骤6:封装分页结果并返回 + return new PageResult<>( + resultPage.getRecords(), + resultPage.getTotal(), + query.getPageNum(), + query.getPageSize() + ); + } + + /** + * 根据请假信息更新考勤记录。 + *

+ * 审批通过后,联动更新相关课次的考勤记录,标记学员为请假状态。 + * 逻辑:通过请假学员表获取学员列表,通过请假课堂表获取课次列表, + * 再通过课次找到对应的课堂日志,最终更新课堂日志学员考勤表中的记录。 + *

+ * + * @param qjbh 请假编号 + */ + private void updateAttendanceForLeave(String qjbh) { + // 步骤1:获取请假关联的学员列表 + List students = qjxybMapper.selectList( + new LambdaQueryWrapper().eq(QJXYB::getQjbh, qjbh) + ); + if (students.isEmpty()) { + return; + } + + // 步骤2:获取请假关联的课次列表 + List classSessions = qjktbMapper.selectList( + new LambdaQueryWrapper().eq(QJKTB::getQjbh, qjbh) + ); + if (classSessions.isEmpty()) { + return; + } + + // 步骤3:遍历每个学员和课次组合,更新考勤记录 + // 收集因未找到课堂日志而跳过的课次,便于审计追溯 + List skippedSessions = new ArrayList<>(); + for (QJXYB student : students) { + for (QJKTB session : classSessions) { + // 3.1:获取课次对应的课堂日志编号 + SSKCB sskcb = sskcbMapper.selectById(session.getSsktbh()); + if (sskcb == null || sskcb.getKtbzbh() == null) { + // 记录跳过的课次,避免静默丢失考勤更新 + if (!skippedSessions.contains(session.getSsktbh())) { + skippedSessions.add(session.getSsktbh()); + } + continue; + } + String rzbh = sskcb.getKtbzbh(); + String xybh = student.getXybh(); + + // 3.2:查询是否已存在考勤记录 + KTRZXYKQB existing = ktrzxykqbMapper.selectOne( + new LambdaQueryWrapper() + .eq(KTRZXYKQB::getRzbh, rzbh) + .eq(KTRZXYKQB::getXybh, xybh) + ); + + if (existing != null) { + // 更新已有考勤记录:标记为请假 + existing.setDkqk(2); // 到课情况:2=请假 + existing.setQsqk(1); // 请示情况:1=已准假 + ktrzxykqbMapper.updateById(existing); + } else { + // 新增考勤记录 + KTRZXYKQB newRecord = new KTRZXYKQB(); + newRecord.setBh(UuidUtil.getOriginalUUID()); + newRecord.setRzbh(rzbh); + newRecord.setXybh(xybh); + newRecord.setDkqk(2); // 到课情况:2=请假 + newRecord.setQsqk(1); // 请示情况:1=已准假 + ktrzxykqbMapper.insert(newRecord); + } + } + } + + // 步骤4:如果存在跳过的课次,记录操作日志便于追溯 + if (!skippedSessions.isEmpty()) { + recordOperationLog(qjbh, "考勤联动提醒", + "请假审批通过,但以下课次未找到课堂日志,考勤未更新:" + skippedSessions); + } + } + + /** + * 构建请假类型字典值 -> 字典标签映射表。 + *

+ * 从 sys_dict_data 查询 dict_type_code=edu_leave_type 的全部字典项, + * 转换为 {dict_value: dict_label} 形式的 Map,便于查询结果翻译。 + * 示例:{"sick": "病假", "personal": "事假", "official": "公假", "compensatory": "调休"}。 + *

+ * + * @return 请假类型字典值与标签的映射表(非空) + */ + private Map buildLeaveTypeLabelMap() { + Map map = new HashMap<>(); + List dictList = dictDataService.listByType(DICT_TYPE_LEAVE_TYPE); + if (dictList != null) { + for (DictDataVO dict : dictList) { + if (dict.getDictValue() != null) { + map.put(dict.getDictValue(), dict.getDictLabel()); + } + } + } + return map; + } + + /** + * 校验学员请假时间是否与已有未完成请假申请冲突。 + *

+ * 查询该学员在请假时间段内(qssj-jssj)是否存在状态为待审批或已发回的请假申请, + * 时间冲突判定条件:新申请起始时间 < 已有申请结束时间 AND 新申请结束时间 > 已有申请起始时间。 + *

+ * + * @param xybh 学员编号 + * @param qssj 请假起始时间 + * @param jssj 请假结束时间 + */ + private void checkLeaveTimeConflict(String xybh, LocalDateTime qssj, LocalDateTime jssj) { + // 步骤1:获取该学员的所有请假编号(通过请假学员表) + List leaveStudents = qjxybMapper.selectList( + new LambdaQueryWrapper().eq(QJXYB::getXybh, xybh) + ); + if (leaveStudents.isEmpty()) { + return; + } + + // 步骤2:提取请假编号列表 + List qjbhList = new ArrayList<>(); + for (QJXYB qjxyb : leaveStudents) { + qjbhList.add(qjxyb.getQjbh()); + } + + // 步骤3:查询时间重叠且未完成的请假申请(状态为0待审批、1已同意或3已发回) + // 注:已同意(1)的请假仍需纳入冲突检测,避免同一时间段重复请假 + List conflicts = xyJXQJBMapper.selectList( + new LambdaQueryWrapper() + .in(XYJXQJB::getBh, qjbhList) + .in(XYJXQJB::getZt, "0", "1", "3") + .lt(XYJXQJB::getQssj, jssj) + .gt(XYJXQJB::getJssj, qssj) + ); + + // 步骤4:如果存在冲突,抛出异常 + if (!conflicts.isEmpty()) { + StringBuilder sb = new StringBuilder(); + for (XYJXQJB conflict : conflicts) { + sb.append("\n 请假编号:").append(conflict.getBh()) + .append(",时间:").append(conflict.getQssj()) + .append(" 至 ").append(conflict.getJssj()); + } + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, + "请假时间与已有未完成请假申请冲突:" + sb); + } + } + + // ==================== 运行分析 ==================== + + /** + * 获取教学运行概览指标。 + *

+ * 返回教学运行关键指标的汇总数据,包括整体排课完成率、学员缺勤率、 + * 场地平均使用率、教师周均课时等核心指标。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 运行概览指标 + */ + @Override + public OperationOverviewVO getOperationOverview(OperationAnalysisQueryDTO query) { + // 步骤1:构建查询参数 + String nd = query != null ? query.getNd() : null; + String xqd = query != null ? query.getXqd() : null; + + // 步骤2:查询各项指标 + Double overallCourseCompletionRate = operationAnalysisMapper.selectOverallCourseCompletionRate(nd, xqd); + Double studentAbsenceRate = operationAnalysisMapper.selectStudentAbsenceRate(nd, xqd); + Double venueUsageRate = operationAnalysisMapper.selectVenueAvgUsageRate(nd, xqd); + Double avgWeeklyHoursPerTeacher = operationAnalysisMapper.selectAvgWeeklyHoursPerTeacher(nd, xqd); + Integer totalScheduleAdjustCount = operationAnalysisMapper.selectTotalScheduleAdjustCount(nd, xqd); + Integer totalSupportPlanCount = operationAnalysisMapper.selectTotalSupportPlanCount(nd, xqd); + Integer totalLeaveCount = operationAnalysisMapper.selectTotalLeaveCount(nd, xqd); + Integer pendingAdjustCount = operationAnalysisMapper.selectPendingAdjustCount(nd, xqd); + Integer pendingLeaveCount = operationAnalysisMapper.selectPendingLeaveCount(nd, xqd); + + // 步骤3:构建概览VO + OperationOverviewVO overview = new OperationOverviewVO(); + overview.setOverallCourseCompletionRate(overallCourseCompletionRate != null ? overallCourseCompletionRate : 0.0); + overview.setStudentAbsenceRate(studentAbsenceRate != null ? studentAbsenceRate : 0.0); + overview.setVenueUsageRate(venueUsageRate != null ? venueUsageRate : 0.0); + overview.setAvgWeeklyHoursPerTeacher(avgWeeklyHoursPerTeacher != null ? avgWeeklyHoursPerTeacher : 0.0); + overview.setTotalScheduleAdjustCount(totalScheduleAdjustCount != null ? totalScheduleAdjustCount : 0); + overview.setTotalSupportPlanCount(totalSupportPlanCount != null ? totalSupportPlanCount : 0); + overview.setTotalLeaveCount(totalLeaveCount != null ? totalLeaveCount : 0); + overview.setPendingAdjustCount(pendingAdjustCount != null ? pendingAdjustCount : 0); + overview.setPendingLeaveCount(pendingLeaveCount != null ? pendingLeaveCount : 0); + + // 步骤4:返回概览指标 + return overview; + } + + /** + * 获取各教学系排课完成率对比数据。 + *

+ * 返回各教学系的排课完成率,用于柱状图展示。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 各教学系排课完成率列表 + */ + @Override + public List getCourseCompletionByDept(OperationAnalysisQueryDTO query) { + // 步骤1:构建查询参数 + String nd = query != null ? query.getNd() : null; + String xqd = query != null ? query.getXqd() : null; + + // 步骤2:查询各教学系排课完成率 + List result = operationAnalysisMapper.selectCourseCompletionByDept(nd, xqd); + + // 步骤3:返回结果 + return result != null ? result : java.util.Collections.emptyList(); + } + + /** + * 获取场地使用率分布数据。 + *

+ * 返回不同场地类型的使用率分布情况,用于饼图展示。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 场地使用率分布列表 + */ + @Override + public List getVenueUsageRate(OperationAnalysisQueryDTO query) { + // 步骤1:构建查询参数 + String nd = query != null ? query.getNd() : null; + String xqd = query != null ? query.getXqd() : null; + + // 步骤2:查询场地使用率分布 + List result = operationAnalysisMapper.selectVenueUsageRate(nd, xqd); + + // 步骤3:返回结果 + return result != null ? result : java.util.Collections.emptyList(); + } + + /** + * 获取学期调课频次趋势数据。 + *

+ * 返回学期内各时间段的调课频次变化趋势,用于折线图展示。 + *

+ * + * @param query 查询参数(包含年度、学期筛选条件) + * @return 调课频次趋势列表 + */ + @Override + public List getScheduleAdjustTrend(OperationAnalysisQueryDTO query) { + // 步骤1:构建查询参数 + String nd = query != null ? query.getNd() : null; + String xqd = query != null ? query.getXqd() : null; + + // 步骤2:查询学期调课频次趋势 + List result = operationAnalysisMapper.selectScheduleAdjustTrend(nd, xqd); + + // 步骤3:返回结果 + return result != null ? result : java.util.Collections.emptyList(); + } + + /** 导出运行分析报表为 Excel 文件(多 Sheet 工作簿) */ + @Override + public byte[] exportAnalysisToExcel(OperationAnalysisQueryDTO query) { + OperationOverviewVO overview = getOperationOverview(query); + List completionList = getCourseCompletionByDept(query); + List venueList = getVenueUsageRate(query); + List trendList = getScheduleAdjustTrend(query); + + Workbook workbook = new XSSFWorkbook(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + CellStyle headerStyle = createAnalysisHeaderStyle(workbook); + buildOverviewSheet(workbook.createSheet("运行概览"), headerStyle, overview); + buildCompletionSheet(workbook.createSheet("各系排课完成率"), headerStyle, completionList); + buildVenueSheet(workbook.createSheet("场地使用率分布"), headerStyle, venueList); + buildTrendSheet(workbook.createSheet("调课频次趋势"), headerStyle, trendList); + workbook.write(outputStream); + return outputStream.toByteArray(); + } catch (Exception e) { + throw new BusinessException(500, "导出运行分析报表失败: " + e.getMessage()); + } finally { + try { + workbook.close(); + outputStream.close(); + } catch (Exception ignored) { + } + } + } + + /** 创建运行分析报表表头样式 */ + private CellStyle createAnalysisHeaderStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER); + return style; + } + + /** 构建"运行概览"工作表(key-value 形式) */ + private void buildOverviewSheet(Sheet sheet, CellStyle headerStyle, OperationOverviewVO overview) { + String[] headers = {"指标", "数值"}; + Row headerRow = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headerStyle); + } + + if (overview == null) { + return; + } + + String[][] data = { + {"整体排课完成率(%)", formatDouble(overview.getOverallCourseCompletionRate())}, + {"学员缺勤率(%)", formatDouble(overview.getStudentAbsenceRate())}, + {"场地平均使用率(%)", formatDouble(overview.getVenueUsageRate())}, + {"教师周均课时(节)", formatDouble(overview.getAvgWeeklyHoursPerTeacher())}, + {"调课总次数", formatInt(overview.getTotalScheduleAdjustCount())}, + {"教学保障计划总数", formatInt(overview.getTotalSupportPlanCount())}, + {"学员请假总次数", formatInt(overview.getTotalLeaveCount())}, + {"待审批调课申请数", formatInt(overview.getPendingAdjustCount())}, + {"待审批请假申请数", formatInt(overview.getPendingLeaveCount())} + }; + + for (int i = 0; i < data.length; i++) { + Row row = sheet.createRow(i + 1); + row.createCell(0).setCellValue(data[i][0]); + row.createCell(1).setCellValue(data[i][1]); + } + + sheet.autoSizeColumn(0); + sheet.autoSizeColumn(1); + } + + /** 构建"各教学系排课完成率"工作表 */ + private void buildCompletionSheet(Sheet sheet, CellStyle headerStyle, + List list) { + String[] headers = {"教学系编号", "教学系名称", "排课完成率(%)", "已排课次数", "总计划课次数"}; + Row headerRow = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headerStyle); + } + + if (list == null) { + return; + } + + for (int i = 0; i < list.size(); i++) { + CourseCompletionByDeptVO item = list.get(i); + Row row = sheet.createRow(i + 1); + row.createCell(0).setCellValue(item.getJxxbh() == null ? "" : item.getJxxbh()); + row.createCell(1).setCellValue(item.getJxxmc() == null ? "" : item.getJxxmc()); + row.createCell(2).setCellValue(formatDouble(item.getCompletionRate())); + row.createCell(3).setCellValue(item.getCompletedCount() == null ? 0 : item.getCompletedCount()); + row.createCell(4).setCellValue(item.getTotalCount() == null ? 0 : item.getTotalCount()); + } + + for (int i = 0; i < headers.length; i++) { + sheet.autoSizeColumn(i); + } + } + + /** 构建"场地使用率分布"工作表 */ + private void buildVenueSheet(Sheet sheet, CellStyle headerStyle, List list) { + String[] headers = {"场地类型编号", "场地类型名称", "使用率(%)", "使用次数", "总可用次数"}; + Row headerRow = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headerStyle); + } + + if (list == null) { + return; + } + + for (int i = 0; i < list.size(); i++) { + VenueUsageRateVO item = list.get(i); + Row row = sheet.createRow(i + 1); + row.createCell(0).setCellValue(item.getCdlybh() == null ? "" : item.getCdlybh()); + row.createCell(1).setCellValue(item.getCdlymc() == null ? "" : item.getCdlymc()); + row.createCell(2).setCellValue(formatDouble(item.getUsageRate())); + row.createCell(3).setCellValue(item.getUsageCount() == null ? 0 : item.getUsageCount()); + row.createCell(4).setCellValue(item.getTotalCount() == null ? 0 : item.getTotalCount()); + } + + for (int i = 0; i < headers.length; i++) { + sheet.autoSizeColumn(i); + } + } + + /** 构建"调课频次趋势"工作表 */ + private void buildTrendSheet(Sheet sheet, CellStyle headerStyle, List list) { + String[] headers = {"时间段", "时间段名称", "调课次数"}; + Row headerRow = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headerStyle); + } + + if (list == null) { + return; + } + + for (int i = 0; i < list.size(); i++) { + ScheduleAdjustTrendVO item = list.get(i); + Row row = sheet.createRow(i + 1); + row.createCell(0).setCellValue(item.getPeriod() == null ? "" : item.getPeriod()); + row.createCell(1).setCellValue(item.getPeriodName() == null ? "" : item.getPeriodName()); + row.createCell(2).setCellValue(item.getAdjustCount() == null ? 0 : item.getAdjustCount()); + } + + for (int i = 0; i < headers.length; i++) { + sheet.autoSizeColumn(i); + } + } + + /** 格式化 Double 为字符串(null 返回空串) */ + private String formatDouble(Double value) { + return value == null ? "" : String.valueOf(value); + } + + /** 格式化 Integer 为字符串(null 返回空串) */ + private String formatInt(Integer value) { + return value == null ? "" : String.valueOf(value); + } + + // ============================================================ + // 调课策略管理实现(时间段限制策略 + 免审批策略) + // ============================================================ + + /** 策略类型常量:时间段限制 */ + private static final String STRATEGY_TIME_LIMIT = "TIME_LIMIT"; + /** 策略类型常量:免审批 */ + private static final String STRATEGY_AUTO_APPROVE = "AUTO_APPROVE"; + /** 星期中文描述 */ + private static final String[] WEEK_DAY_NAMES = {"周日", "周一", "周二", "周三", "周四", "周五", "周六"}; + + @Override + public List listScheduleAdjustStrategies(String cllx) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cllx != null && !cllx.trim().isEmpty()) { + wrapper.eq(ScheduleAdjustStrategy::getCllx, cllx); + } + wrapper.orderByDesc(ScheduleAdjustStrategy::getGxsj); + List list = scheduleAdjustStrategyMapper.selectList(wrapper); + + List result = new ArrayList<>(); + if (list == null) { + return result; + } + for (ScheduleAdjustStrategy strategy : list) { + result.add(buildStrategyVO(strategy)); + } + return result; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String saveScheduleAdjustStrategy(ScheduleAdjustStrategyDTO dto) { + if (!STRATEGY_TIME_LIMIT.equals(dto.getCllx()) && !STRATEGY_AUTO_APPROVE.equals(dto.getCllx())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, + "策略类型不正确,应为 TIME_LIMIT 或 AUTO_APPROVE"); + } + if (STRATEGY_TIME_LIMIT.equals(dto.getCllx())) { + if (dto.getJzxq() != null && (dto.getJzxq() < 0 || dto.getJzxq() > 6)) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, + "截止星期取值范围为0-6(0=周日,6=周六)"); + } + } + if (dto.getSxkssj() != null && dto.getSxjssj() != null + && dto.getSxkssj().isAfter(dto.getSxjssj())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, + "生效开始时间不能晚于生效结束时间"); + } + + LocalDateTime now = LocalDateTime.now(); + + if (dto.getClbh() == null || dto.getClbh().trim().isEmpty()) { + ScheduleAdjustStrategy strategy = new ScheduleAdjustStrategy(); + strategy.setClbh(UuidUtil.getOriginalUUID()); + strategy.setClmc(dto.getClmc()); + strategy.setCllx(dto.getCllx()); + strategy.setSfqy(dto.getSfqy() == null ? 1 : dto.getSfqy()); + strategy.setJzxq(dto.getJzxq()); + strategy.setJzjc(dto.getJzjc()); + strategy.setSxkssj(dto.getSxkssj()); + strategy.setSxjssj(dto.getSxjssj()); + strategy.setBz(dto.getBz()); + strategy.setCjsj(now); + strategy.setGxsj(now); + scheduleAdjustStrategyMapper.insert(strategy); + return strategy.getClbh(); + } else { + ScheduleAdjustStrategy existing = scheduleAdjustStrategyMapper.selectById(dto.getClbh()); + if (existing == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课策略不存在"); + } + existing.setClmc(dto.getClmc()); + existing.setCllx(dto.getCllx()); + existing.setSfqy(dto.getSfqy() == null ? 1 : dto.getSfqy()); + existing.setJzxq(dto.getJzxq()); + existing.setJzjc(dto.getJzjc()); + existing.setSxkssj(dto.getSxkssj()); + existing.setSxjssj(dto.getSxjssj()); + existing.setBz(dto.getBz()); + existing.setGxsj(now); + scheduleAdjustStrategyMapper.updateById(existing); + return existing.getClbh(); + } + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteScheduleAdjustStrategy(String clbh) { + if (clbh == null || clbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "策略编号不能为空"); + } + ScheduleAdjustStrategy existing = scheduleAdjustStrategyMapper.selectById(clbh); + if (existing == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "调课策略不存在"); + } + scheduleAdjustStrategyMapper.deleteById(clbh); + } + + /** 调课时间段策略校验:超过截止星期时不允许调整下周课程 */ + private void validateScheduleAdjustTimeLimitStrategy(LocalDateTime newDate) { + if (newDate == null) { + return; + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ScheduleAdjustStrategy::getCllx, STRATEGY_TIME_LIMIT); + wrapper.eq(ScheduleAdjustStrategy::getSfqy, 1); + List strategies = scheduleAdjustStrategyMapper.selectList(wrapper); + + if (strategies == null || strategies.isEmpty()) { + return; + } + + LocalDateTime now = LocalDateTime.now(); + int currentDayOfWeek = now.getDayOfWeek().getValue(); + boolean isFutureWeek = newDate.toLocalDate().isAfter(now.toLocalDate().plusDays(7)); + + for (ScheduleAdjustStrategy strategy : strategies) { + if (strategy.getSxkssj() != null && now.isBefore(strategy.getSxkssj())) { + continue; + } + if (strategy.getSxjssj() != null && now.isAfter(strategy.getSxjssj())) { + continue; + } + Integer deadlineDay = strategy.getJzxq(); + if (deadlineDay == null) { + continue; + } + int deadlineDayOfWeek = (deadlineDay == 0) ? 7 : deadlineDay; + if (isFutureWeek && currentDayOfWeek > deadlineDayOfWeek) { + String dayName = (deadlineDay >= 0 && deadlineDay <= 6) ? WEEK_DAY_NAMES[deadlineDay] : String.valueOf(deadlineDay); + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, + "根据调课策略[" + strategy.getClmc() + "]," + dayName + "之后不允许调整下周课程"); + } + } + } + + /** 判断当前是否命中免审批策略 */ + private boolean matchAutoApproveStrategy() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ScheduleAdjustStrategy::getCllx, STRATEGY_AUTO_APPROVE); + wrapper.eq(ScheduleAdjustStrategy::getSfqy, 1); + List strategies = scheduleAdjustStrategyMapper.selectList(wrapper); + + if (strategies == null || strategies.isEmpty()) { + return false; + } + + LocalDateTime now = LocalDateTime.now(); + for (ScheduleAdjustStrategy strategy : strategies) { + boolean inRange = true; + if (strategy.getSxkssj() != null && now.isBefore(strategy.getSxkssj())) { + inRange = false; + } + if (strategy.getSxjssj() != null && now.isAfter(strategy.getSxjssj())) { + inRange = false; + } + if (inRange) { + return true; + } + } + return false; + } + + /** 组装调课策略展示VO */ + private ScheduleAdjustStrategyVO buildStrategyVO(ScheduleAdjustStrategy strategy) { + ScheduleAdjustStrategyVO vo = new ScheduleAdjustStrategyVO(); + vo.setClbh(strategy.getClbh()); + vo.setClmc(strategy.getClmc()); + vo.setCllx(strategy.getCllx()); + vo.setCllxDesc(STRATEGY_TIME_LIMIT.equals(strategy.getCllx()) ? "时间段限制" : + (STRATEGY_AUTO_APPROVE.equals(strategy.getCllx()) ? "免审批" : strategy.getCllx())); + vo.setSfqy(strategy.getSfqy()); + vo.setSfqyDesc(strategy.getSfqy() != null && strategy.getSfqy() == 1 ? "启用" : "停用"); + vo.setJzxq(strategy.getJzxq()); + if (strategy.getJzxq() != null && strategy.getJzxq() >= 0 && strategy.getJzxq() <= 6) { + vo.setJzxqDesc(WEEK_DAY_NAMES[strategy.getJzxq()]); + } + vo.setJzjc(strategy.getJzjc()); + vo.setSxkssj(strategy.getSxkssj()); + vo.setSxjssj(strategy.getSxjssj()); + vo.setBz(strategy.getBz()); + vo.setCjsj(strategy.getCjsj()); + vo.setGxsj(strategy.getGxsj()); + return vo; + } + + /** + * 根据教员编号自动解析其归属的实施_机关编号。 + *

+ * 数据链路:教员编号 → 教员表.jysdh(教研室代号)→ 实施_机关_教研室 → 实施_机关编号。 + * 若教员未绑定教研室或教研室未绑定机关,则返回 null(保持原行为,不阻断审批)。 + *

+ * + * @param teacherCode 教员编号 + * @return 实施_机关编号(可能为 null) + */ + private String resolveDepartmentIdByTeacher(String teacherCode) { + if (teacherCode == null || teacherCode.trim().isEmpty()) { + return null; + } + // 步骤1:查询教员,获取教研室代号 + JYB teacher = jybMapper.selectById(teacherCode); + if (teacher == null || teacher.getJysdh() == null || teacher.getJysdh().trim().isEmpty()) { + return null; + } + // 步骤2:根据教研室代号查询机关编号(教员表.jysdh 对应 实施_机关_教研室.教研室编号) + List relations = ssjgjysMapper.selectResearchOfficeRelationsByResearchOfficeId(teacher.getJysdh()); + if (relations == null || relations.isEmpty()) { + return null; + } + SSJGJYS relation = relations.get(0); + if (relation.getSSJGBH() == null || relation.getSSJGBH().trim().isEmpty()) { + return null; + } + return relation.getSSJGBH(); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/CourseTeachingServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/CourseTeachingServiceImpl.java new file mode 100644 index 000000000..4c9a29160 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/CourseTeachingServiceImpl.java @@ -0,0 +1,111 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.KCBZ; +import com.roomroot.jwgl.entity.KTBZ; +import com.roomroot.jwgl.mapper.KCBZMapper; +import com.roomroot.jwgl.mapper.KTBZMapper; +import com.roomroot.jwgl.service.CourseTeachingService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.util.ArrayList; +import java.util.List; + +/** + * 课程教学管理服务实现类 + *

+ * 实现课程标准的增删改查、多版本管理及自动生成。 + * 删除逻辑:填写撤回原因(非空=已撤回),不物理删除。 + * 查询仅返回未撤回(撤回原因为空)的数据。 + *

+ */ +@Service +public class CourseTeachingServiceImpl implements CourseTeachingService { + + @Resource + private KCBZMapper kcbzMapper; + + @Resource + private KTBZMapper ktbzMapper; + + /** 填充课堂列表到课程标准 */ + private void fillKtList(KCBZ kcbz) { + if (kcbz == null) return; + List ktList = ktbzMapper.selectByKcbh(kcbz.getBh()); + kcbz.setKtList(ktList != null ? ktList : new ArrayList<>()); + } + + @Override + @Transactional + public void add(KCBZ kcbz) { + kcbz.setChyy(null); + kcbzMapper.insert(kcbz); + List ktList = kcbz.getKtList(); + if (ktList != null && !ktList.isEmpty()) { + for (KTBZ kt : ktList) { + kt.setKcbh(kcbz.getBh()); + ktbzMapper.insert(kt); + } + } + } + + @Override + @Transactional + public void delete(String bh, String chyy) { + KCBZ kcbz = new KCBZ(); + kcbz.setBh(bh); + kcbz.setChyy(chyy); + kcbzMapper.updateById(kcbz); + } + + @Override + @Transactional + public void update(KCBZ kcbz) { + kcbzMapper.updateById(kcbz); + List ktList = kcbz.getKtList(); + if (ktList != null && !ktList.isEmpty()) { + for (KTBZ kt : ktList) { + ktbzMapper.updateById(kt); + } + } + } + + @Override + public KCBZ getById(String bh) { + KCBZ kcbz = kcbzMapper.selectById(bh); + fillKtList(kcbz); + return kcbz; + } + + @Override + public PageResult pageList(PageQuery query, KCBZ kcbz) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = kcbzMapper.selectPageList(page, kcbz); + // 填充每项的课堂列表 + for (KCBZ item : result.getRecords()) { + fillKtList(item); + } + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List getVersionsByCourse(String kbh) { + return kcbzMapper.selectVersionsByCourse(kbh); + } + + @Override + @Transactional + public String generateFromCourse(String kbh) { + KCBZ kcbz = new KCBZ(); + kcbz.setBh(kbh + "-STD"); + kcbz.setKbh(kbh); + kcbz.setMc("课程标准-" + kbh); + kcbzMapper.insert(kcbz); + return kcbz.getBh(); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DepartmentManagementServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DepartmentManagementServiceImpl.java new file mode 100644 index 000000000..5ad612aef --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DepartmentManagementServiceImpl.java @@ -0,0 +1,942 @@ +package com.roomroot.jwgl.service.impl; + +import com.roomroot.jwgl.dto.department.DepartmentCreateDTO; +import com.roomroot.jwgl.dto.department.DepartmentIdDTO; +import com.roomroot.jwgl.dto.department.DepartmentQueryDTO; +import com.roomroot.jwgl.dto.department.DepartmentUpdateDTO; +import com.roomroot.jwgl.entity.SSJG; +import com.roomroot.jwgl.mapper.SSJGMapper; +import com.roomroot.jwgl.service.DepartmentManagementService; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.utils.UuidUtil; +import com.roomroot.jwgl.vo.department.DepartmentTreeVO; +import com.roomroot.jwgl.vo.department.DepartmentVO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +import java.time.LocalDateTime; +import java.util.*; + +import static com.roomroot.jwgl.utils.BasicManagementConstants.*; + +/** + * 部别管理服务实现。 + */ +@Service +public class DepartmentManagementServiceImpl implements DepartmentManagementService { + + /** 部别管理数据访问对象。 */ + @Resource + private SSJGMapper ssjgMapper; + + /** + * 新增部别。 + * + *

处理顺序:校验参数、校验上级关系、校验同级重名、组装实体、 + * 写入数据库、重新查询并返回 VO。

+ * + * @param dto 新增参数 + * @return 新增后的部别信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DepartmentVO add(DepartmentCreateDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "新增参数不能为空"); + } + + // 第二步:清理并校验数据库必填字段。 + String name = requireText(dto.getName(), "部别名称不能为空"); + String category = requireText(dto.getCategory(), "机关类别不能为空"); + + // 第三步:将空字符串形式的上级编号统一转换为 null,表示根节点。 + String parentId = normalizeText(dto.getParentId()); + + // 第四步:加载完整组织数据,用于检查上级节点和层级链路。 + List departments = selectAllDepartments(); + + // 第五步:确认上级存在、已启用,并且组织链路中不存在循环。 + validateParentChain(parentId, null, departments); + + // 第六步:同一上级下不允许出现同名部别。 + ensureSiblingNameUnique(parentId, name, null); + + /* + * 第七步:将新增 DTO 转换为数据库实体。 + * Controller 和 Service 对外只使用 DTO/VO,SSJG 仅在持久层内部流转。 + */ + SSJG department = new SSJG(); + department.setBH(UuidUtil.getOriginalUUID()); + department.setJGMC(name); + department.setJGLB(category); + department.setFLH(defaultClassificationCode(dto.getClassificationCode())); + department.setFJH(defaultLevelNumber(dto.getLevelNumber())); + department.setSJJGBH(parentId); + department.setTY(false); + department.setTYSJ(null); + department.setJC(normalizeText(dto.getAbbreviation())); + + // 第八步:执行新增,并校验数据库实际影响行数。 + if (ssjgMapper.insertDepartment(department) != 1) { + throw new BusinessException("新增部别失败"); + } + + // 第九步:重新查询数据库,返回包含上级名称等扩展字段的 VO。 + return loadDepartmentView(department.getBH()); + } + + /** + * 修改部别。 + * + *

处理顺序:校验编号和必填字段、校验新的上级关系、 + * 校验同级重名、组装更新实体、更新数据库、返回最新 VO。

+ * + * @param dto 修改参数 + * @return 修改后的部别信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DepartmentVO update(DepartmentUpdateDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "修改参数不能为空"); + } + + // 第二步:校验部别编号,并确认待修改数据真实存在。 + String id = requireId(dto.getId()); + requireDepartment(id); + + // 第三步:清理并校验数据库必填字段。 + String name = requireText(dto.getName(), "部别名称不能为空"); + String category = requireText(dto.getCategory(), "机关类别不能为空"); + + // 第四步:统一空上级编号的表达方式。 + String parentId = normalizeText(dto.getParentId()); + + // 第五步:读取完整组织结构,为移动节点校验提供数据。 + List departments = selectAllDepartments(); + + // 第六步:禁止选择自身或自身下级作为新的上级部别。 + validateParentChain(parentId, id, departments); + + // 第七步:修改时排除自身,检查同一上级下是否存在其他同名部别。 + ensureSiblingNameUnique(parentId, name, id); + + /* + * 第八步:将修改 DTO 转换为更新实体。 + * 此处不设置停用字段,避免修改基本信息时意外改变启用状态。 + */ + SSJG department = new SSJG(); + department.setBH(id); + department.setJGMC(name); + department.setJGLB(category); + department.setFLH(defaultClassificationCode(dto.getClassificationCode())); + department.setFJH(defaultLevelNumber(dto.getLevelNumber())); + department.setSJJGBH(parentId); + department.setJC(normalizeText(dto.getAbbreviation())); + + // 第九步:更新基本信息,并校验数据库实际影响行数。 + if (ssjgMapper.updateDepartment(department) != 1) { + throw new BusinessException("修改部别失败"); + } + + // 第十步:重新查询并返回修改后的完整 VO。 + return loadDepartmentView(id); + } + + /** + * 查询部别详情。 + * + * @param dto 部别编号参数 + * @return 部别详情 + */ + @Override + public DepartmentVO get(DepartmentIdDTO dto) { + // 第一步:兼容 dto 为 null 的直接调用场景,并校验编号。 + String id = requireId(dto == null ? null : dto.getId()); + + // 第二步:查询并组装包含上级名称和子节点标识的 VO。 + return loadDepartmentView(id); + } + + /** + * 查询部别平铺列表。 + * + * @param dto 查询条件 + * @return 排序后的部别列表 + */ + @Override + public List list(DepartmentQueryDTO dto) { + // 第一步:一次性读取全部部别,避免逐条查询形成 N+1 问题。 + List departments = selectAllDepartments(); + + // 第二步:建立编号索引,用于快速查找每个部别的上级名称。 + Map departmentMap = indexDepartments(departments); + + // 第三步:收集被引用为上级的编号,用于计算 hasChildren。 + Set parentIds = collectParentIds(departments); + + // 第四步:创建最终返回列表。 + List result = new ArrayList<>(); + + // 第五步:逐条应用查询条件,并将匹配实体转换为 VO。 + for (SSJG department : departments) { + if (matches(department, dto)) { + result.add(toDepartmentVO(department, departmentMap, parentIds)); + } + } + + // 第六步:按照分级号、分类号、名称依次排序。 + Collections.sort(result, new Comparator() { + @Override + public int compare(DepartmentVO left, DepartmentVO right) { + return compareDepartments(left, right); + } + }); + + // 第七步:返回平铺列表。 + return result; + } + + /** + * 查询部别树。 + * + *

查询条件命中子节点时,会同时保留该节点的所有祖先节点, + * 从而保证前端仍能展示完整层级路径。

+ * + * @param dto 查询条件 + * @return 排序后的部别树 + */ + @Override + public List tree(DepartmentQueryDTO dto) { + // 第一步:读取全部部别,并建立编号到实体的索引。 + List departments = selectAllDepartments(); + Map departmentMap = indexDepartments(departments); + + // 第二步:在构建树前检测数据库中的异常循环关系,避免无限递归。 + ensureTreeHasNoCycle(departments, departmentMap); + + // 第三步:计算所有需要展示的节点编号,包括匹配节点及其祖先。 + Set includedIds = collectIncludedTreeIds(departments, departmentMap, dto); + + // 第四步:收集全部上级编号,用于设置节点的 hasChildren 字段。 + Set parentIds = collectParentIds(departments); + + // 第五步:建立树节点索引,后续可通过编号快速挂接父子关系。 + Map nodeMap = new HashMap<>(); + + // 第六步:只为需要展示的实体创建树节点。 + for (SSJG department : departments) { + if (includedIds.contains(department.getBH())) { + DepartmentVO departmentVO = toDepartmentVO(department, departmentMap, parentIds); + nodeMap.put(department.getBH(), toTreeVO(departmentVO)); + } + } + + // 第七步:创建根节点集合。 + List roots = new ArrayList<>(); + + /* + * 第八步:逐个挂接节点。 + * 展示集合中不存在上级节点时,将当前节点视为根节点; + * 否则加入对应上级节点的 children 集合。 + */ + for (SSJG department : departments) { + DepartmentTreeVO node = nodeMap.get(department.getBH()); + if (node == null) { + continue; + } + String parentId = normalizeText(department.getSJJGBH()); + DepartmentTreeVO parent = nodeMap.get(parentId); + if (parent == null) { + roots.add(node); + } else { + parent.getChildren().add(node); + } + } + + // 第九步:递归排序根节点及所有层级的子节点。 + sortTree(roots); + + // 第十步:返回完整树形结果。 + return roots; + } + + /** + * 停用部别。 + * + *

停用前必须确认不存在启用中的直接下级部别, + * 并且不存在教研室、学员队、机关参谋或教学管理机构引用。

+ * + * @param dto 部别编号参数 + * @return 停用后的部别信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DepartmentVO disable(DepartmentIdDTO dto) { + // 第一步:校验编号并查询待停用部别。 + String id = requireId(dto == null ? null : dto.getId()); + SSJG department = requireDepartment(id); + + // 第二步:已停用时直接返回,保证接口幂等。 + if (Boolean.TRUE.equals(department.getTY())) { + return loadDepartmentView(id); + } + + // 第三步:存在启用中的直接下级部别时,不允许停用当前部别。 + if (ssjgMapper.countActiveChildren(id) > 0) { + throw new BusinessException(CONFLICT, "该部别存在启用中的下级部别,不能停用"); + } + + // 第四步:存在其他业务数据引用时,不允许停用当前部别。 + if (ssjgMapper.countDepartmentReferences(id) > 0) { + throw new BusinessException(CONFLICT, + "该部别已被人员、教研室、学员队或教学管理机构引用,不能停用"); + } + + // 第五步:将停用标志设为 true,并记录当前停用时间。 + if (ssjgMapper.updateDepartmentStatus(id, true, LocalDateTime.now()) != 1) { + throw new BusinessException("停用部别失败"); + } + + // 第六步:重新查询并返回停用后的最新状态。 + return loadDepartmentView(id); + } + + /** + * 启用部别。 + * + *

启用前会检查上级及所有祖先部别均存在且处于启用状态。

+ * + * @param dto 部别编号参数 + * @return 启用后的部别信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DepartmentVO enable(DepartmentIdDTO dto) { + // 第一步:校验编号并查询待启用部别。 + String id = requireId(dto == null ? null : dto.getId()); + SSJG department = requireDepartment(id); + + // 第二步:已启用时直接返回,保证接口幂等。 + if (!Boolean.TRUE.equals(department.getTY())) { + return loadDepartmentView(id); + } + + // 第三步:加载组织结构,检查上级链路的完整性和启用状态。 + List departments = selectAllDepartments(); + validateParentChain(normalizeText(department.getSJJGBH()), id, departments); + + // 第四步:清除停用标志和停用时间。 + if (ssjgMapper.updateDepartmentStatus(id, false, null) != 1) { + throw new BusinessException("启用部别失败"); + } + + // 第五步:重新查询并返回启用后的最新状态。 + return loadDepartmentView(id); + } + + /** + * 删除部别(逻辑删除)。 + * + *

删除前必须确认不存在任何下级部别, + * 并且不存在教研室、学员队、机关参谋或教学管理机构引用。 + * 逻辑删除后部别不再出现在列表和树形结构中。

+ * + * @param dto 部别编号参数 + * @return 被删除的部别信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DepartmentVO delete(DepartmentIdDTO dto) { + // 第一步:校验编号并查询待删除部别。 + String id = requireId(dto == null ? null : dto.getId()); + SSJG department = requireDepartment(id); + + // 第二步:删除前先构造返回 VO(逻辑删除后查不到数据)。 + List departments = selectAllDepartments(); + DepartmentVO vo = toDepartmentVO( + department, indexDepartments(departments), collectParentIds(departments)); + + // 第三步:存在任何下级部别时,不允许删除当前部别。 + // 与停用不同,删除要求所有子部别先被删除,而非仅停用。 + for (SSJG child : departments) { + if (id.equals(child.getSJJGBH())) { + throw new BusinessException(CONFLICT, "该部别存在下级部别,请先删除下级部别"); + } + } + + // 第四步:存在其他业务数据引用时,不允许删除当前部别。 + if (ssjgMapper.countDepartmentReferences(id) > 0) { + throw new BusinessException(CONFLICT, + "该部别已被人员、教研室、学员队或教学管理机构引用,不能删除"); + } + + // 第五步:执行逻辑删除,将 DEL_FLAG 置为 1。 + if (ssjgMapper.deleteDepartmentLogically(id) != 1) { + throw new BusinessException("删除部别失败"); + } + + // 第六步:返回删除前的部别信息。 + return vo; + } + + /** + * 查询部别并转换为详情 VO。 + * + * @param id 部别编号 + * @return 部别详情 VO + */ + private DepartmentVO loadDepartmentView(String id) { + // 第一步:查询目标部别,不存在时统一抛出业务异常。 + SSJG department = requireDepartment(id); + + // 第二步:读取组织数据,为上级名称和子节点标识提供上下文。 + List departments = selectAllDepartments(); + + // 第三步:将实体转换为对外返回的 VO。 + return toDepartmentVO(department, indexDepartments(departments), collectParentIds(departments)); + } + + /** + * 查询必然存在的部别。 + * + * @param id 部别编号 + * @return 部别实体 + */ + private SSJG requireDepartment(String id) { + // 根据唯一编号查询数据库。 + SSJG department = ssjgMapper.selectDepartmentById(id); + + // 未查询到数据时返回明确的 404 业务错误。 + if (department == null) { + throw new BusinessException(NOT_FOUND, "部别不存在"); + } + + // 返回已确认存在的部别实体。 + return department; + } + + /** + * 查询全部部别,并统一处理 Mapper 返回 null 的情况。 + * + * @return 非 null 的部别集合 + */ + private List selectAllDepartments() { + // 调用 Mapper 获取全部部别。 + List departments = ssjgMapper.selectAllDepartments(); + + // Mapper 返回 null 时改为空集合,减少后续判空分支。 + return departments == null ? Collections.emptyList() : departments; + } + + /** + * 校验同一上级下的部别名称唯一性。 + * + * @param parentId 上级部别编号 + * @param name 部别名称 + * @param excludeId 修改时需要排除的当前部别编号 + */ + private void ensureSiblingNameUnique(String parentId, String name, String excludeId) { + // 数据库计数大于 0 表示存在冲突记录。 + if (ssjgMapper.countSiblingName(parentId, name, excludeId) > 0) { + throw new BusinessException(CONFLICT, "同一上级部别下已存在同名部别"); + } + } + + /** + * 校验上级部别链路。 + * + *

该方法同时检查:上级存在、上级启用、不能选择自身、 + * 不能移动到自己的下级、已有组织数据不能形成循环。

+ * + * @param parentId 待选择的上级部别编号 + * @param currentId 当前部别编号;新增时为 null + * @param departments 全部部别 + */ + private void validateParentChain(String parentId, String currentId, List departments) { + // 根节点没有上级,不需要继续校验。 + if (parentId == null) { + return; + } + + // 第一层快速判断:当前节点不能直接选择自己作为上级。 + if (parentId.equals(currentId)) { + throw new BusinessException(CONFLICT, "上级部别不能选择当前部别"); + } + + // 建立编号索引,后续沿祖先链逐级查询时保持 O(1) 查找。 + Map departmentMap = indexDepartments(departments); + + // 记录已经访问的编号,用于识别数据库中的循环关系。 + Set visited = new HashSet<>(); + + // 从待选择的上级节点开始向根节点逐级检查。 + String cursor = parentId; + while (cursor != null) { + // 遇到当前节点说明新上级位于当前节点的下级链路中。 + if (cursor.equals(currentId)) { + throw new BusinessException(CONFLICT, "不能将部别移动到自己的下级部别中"); + } + + // 同一编号在当前链路中出现两次,说明已有组织数据存在循环。 + if (!visited.add(cursor)) { + throw new BusinessException("组织结构数据存在循环,请联系管理员处理"); + } + + // 根据当前编号取得上级实体。 + SSJG parent = departmentMap.get(cursor); + + // 引用了不存在的上级编号时,拒绝继续保存。 + if (parent == null) { + throw new BusinessException(NOT_FOUND, "上级部别不存在"); + } + + // 停用部别不能作为新增、移动或启用操作的有效上级。 + if (Boolean.TRUE.equals(parent.getTY())) { + throw new BusinessException(CONFLICT, "上级部别已停用,不能选择"); + } + + // 继续检查当前上级的上一级,直至到达根节点。 + cursor = normalizeText(parent.getSJJGBH()); + } + } + + /** + * 收集树形查询需要保留的部别编号。 + * + *

命中查询条件的部别需要保留,同时保留它的全部祖先节点, + * 这样筛选后的结果仍然是一棵层级完整、可以正常展示的树。

+ * + * @param departments 全部部别 + * @param departmentMap 部别编号索引 + * @param dto 查询条件 + * @return 需要出现在查询结果树中的部别编号 + */ + private Set collectIncludedTreeIds(List departments, + Map departmentMap, + DepartmentQueryDTO dto) { + // 第一步:创建结果集合,并利用 Set 自动去除重复编号。 + Set includedIds = new HashSet<>(); + + // 第二步:逐个检查部别是否满足当前查询条件。 + for (SSJG department : departments) { + // 未命中查询条件的部别不作为本次向上追溯的起点。 + if (!matches(department, dto)) { + continue; + } + + /* + * 第三步:从命中的部别开始沿上级链逐级向上追溯。 + * 当前节点和全部祖先节点都加入结果,确保筛选后的树不丢失层级路径。 + */ + String cursor = department.getBH(); + while (cursor != null && includedIds.add(cursor)) { + // 根据当前编号取得实体,再移动到它的上级部别。 + SSJG current = departmentMap.get(cursor); + cursor = current == null ? null : normalizeText(current.getSJJGBH()); + } + } + + // 第四步:返回命中节点及其祖先节点的编号集合。 + return includedIds; + } + + /** + * 检查完整部别树是否存在循环关系。 + * + *

从每个部别分别沿上级链向根节点追溯;同一条路径中再次遇到相同编号, + * 即表示组织结构无法构成合法树,需要终止查询。

+ * + * @param departments 全部部别 + * @param departmentMap 部别编号索引 + */ + private void ensureTreeHasNoCycle(List departments, Map departmentMap) { + // 第一步:以每个部别为起点,分别检查它所在的上级链路。 + for (SSJG department : departments) { + // 第二步:为当前链路创建独立的访问记录。 + Set path = new HashSet<>(); + SSJG cursor = department; + + // 第三步:沿上级链持续追溯,直至到达根节点或断开的上级节点。 + while (cursor != null) { + // 同一编号在当前链路重复出现,说明组织结构中存在循环。 + if (!path.add(cursor.getBH())) { + throw new BusinessException("组织结构数据存在循环,请联系管理员处理"); + } + + // 读取当前部别的上级编号,并将游标移动到上级部别。 + String parentId = normalizeText(cursor.getSJJGBH()); + cursor = parentId == null ? null : departmentMap.get(parentId); + } + } + } + + /** + * 按部别编号建立实体索引。 + * + * @param departments 部别集合 + * @return 部别编号与实体的映射 + */ + private Map indexDepartments(List departments) { + // 第一步:创建用于快速查询部别的编号索引。 + Map result = new HashMap<>(); + + // 第二步:将每个部别实体按唯一编号写入索引。 + for (SSJG department : departments) { + result.put(department.getBH(), department); + } + + // 第三步:返回构建完成的索引。 + return result; + } + + /** + * 收集所有被其他部别引用的上级编号。 + * + * @param departments 全部部别 + * @return 至少拥有一个下级部别的编号集合 + */ + private Set collectParentIds(List departments) { + // 第一步:创建上级编号集合,并利用 Set 自动去重。 + Set parentIds = new HashSet<>(); + + // 第二步:逐个读取部别的上级编号。 + for (SSJG department : departments) { + String parentId = normalizeText(department.getSJJGBH()); + + // 根节点没有上级编号,不加入集合。 + if (parentId != null) { + parentIds.add(parentId); + } + } + + // 第三步:返回所有实际作为上级的部别编号。 + return parentIds; + } + + /** + * 将部别实体转换为对外返回的详情 VO。 + * + * @param department 部别实体 + * @param departmentMap 部别编号索引 + * @param parentIds 实际拥有下级的部别编号集合 + * @return 部别详情 VO + */ + private DepartmentVO toDepartmentVO(SSJG department, + Map departmentMap, + Set parentIds) { + // 第一步:创建独立的返回对象,避免直接暴露数据库实体。 + DepartmentVO vo = new DepartmentVO(); + + // 第二步:复制部别自身的基础信息。 + vo.setId(department.getBH()); + vo.setName(department.getJGMC()); + vo.setCategory(department.getJGLB()); + vo.setClassificationCode(department.getFLH()); + vo.setLevelNumber(department.getFJH()); + + // 第三步:设置上级编号,并通过索引补充上级名称。 + vo.setParentId(normalizeText(department.getSJJGBH())); + SSJG parent = departmentMap.get(vo.getParentId()); + vo.setParentName(parent == null ? null : parent.getJGMC()); + + // 第四步:将实体的停用标志转换为更直观的启用状态,并保留停用时间。 + vo.setEnabled(!Boolean.TRUE.equals(department.getTY())); + vo.setDisabledAt(department.getTYSJ()); + + // 第五步:补充简称和是否拥有下级节点等展示信息。 + vo.setAbbreviation(department.getJC()); + vo.setHasChildren(parentIds.contains(department.getBH())); + + // 第六步:返回完成组装的详情 VO。 + return vo; + } + + /** + * 将部别详情 VO 转换为可挂载子节点的树形 VO。 + * + * @param source 部别详情 VO + * @return 树形部别 VO + */ + private DepartmentTreeVO toTreeVO(DepartmentVO source) { + // 第一步:创建树形返回对象,子节点集合由该类型自行初始化。 + DepartmentTreeVO target = new DepartmentTreeVO(); + + // 第二步:复制基础信息和层级信息。 + target.setId(source.getId()); + target.setName(source.getName()); + target.setCategory(source.getCategory()); + target.setClassificationCode(source.getClassificationCode()); + target.setLevelNumber(source.getLevelNumber()); + target.setParentId(source.getParentId()); + target.setParentName(source.getParentName()); + + // 第三步:复制状态信息和展示辅助字段。 + target.setEnabled(source.getEnabled()); + target.setDisabledAt(source.getDisabledAt()); + target.setAbbreviation(source.getAbbreviation()); + target.setHasChildren(source.getHasChildren()); + + // 第四步:返回尚未挂载子节点的树形 VO。 + return target; + } + + /** + * 判断部别是否满足查询条件。 + * + * @param department 待判断的部别 + * @param dto 查询条件 + * @return 满足全部条件时返回 true + */ + private boolean matches(SSJG department, DepartmentQueryDTO dto) { + // 未提供查询条件时,默认匹配全部部别。 + if (dto == null) { + return true; + } + + /* + * 第一步:处理关键字模糊查询。 + * 关键字可匹配部别编号、名称、简称和分类号,且忽略英文字母大小写。 + */ + String keyword = normalizeText(dto.getKeyword()); + if (keyword != null + && !containsIgnoreCase(department.getBH(), keyword) + && !containsIgnoreCase(department.getJGMC(), keyword) + && !containsIgnoreCase(department.getJC(), keyword) + && !containsIgnoreCase(department.getFLH(), keyword)) { + return false; + } + + // 第二步:存在部别类别条件时,按完整类别值忽略大小写匹配。 + String category = normalizeText(dto.getCategory()); + if (category != null && !category.equalsIgnoreCase(normalizeText(department.getJGLB()))) { + return false; + } + + // 第三步:存在启用状态条件时,将实体停用标志转换后进行比较。 + if (dto.getEnabled() != null) { + boolean enabled = !Boolean.TRUE.equals(department.getTY()); + return dto.getEnabled().booleanValue() == enabled; + } + + // 所有已提供的查询条件均通过时,当前部别匹配成功。 + return true; + } + + /** + * 判断源文本是否包含指定关键字,并忽略英文字母大小写。 + * + * @param source 源文本 + * @param keyword 关键字 + * @return 包含关键字时返回 true + */ + private boolean containsIgnoreCase(String source, String keyword) { + // 源文本为空时不能匹配;否则统一按 ROOT 区域规则转为小写后比较。 + return source != null + && source.toLowerCase(Locale.ROOT).contains(keyword.toLowerCase(Locale.ROOT)); + } + + /** + * 递归排序树中每一层的部别节点。 + * + * @param nodes 当前层级的树节点 + */ + private void sortTree(List nodes) { + // 第一步:按照统一的部别排序规则排列当前层级。 + Collections.sort(nodes, new Comparator() { + @Override + public int compare(DepartmentTreeVO left, DepartmentTreeVO right) { + return compareDepartments(left, right); + } + }); + + // 第二步:递归处理每个节点的子节点,保证整棵树的各层顺序一致。 + for (DepartmentTreeVO node : nodes) { + sortTree(node.getChildren()); + } + } + + /** + * 按分级号、分类号、部别名称依次比较两个部别。 + * + * @param left 左侧部别 + * @param right 右侧部别 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareDepartments(DepartmentVO left, DepartmentVO right) { + // 第一优先级:分级号较小的部别排在前面,空值排在最后。 + int levelResult = compareNullableInteger(left.getLevelNumber(), right.getLevelNumber()); + if (levelResult != 0) { + return levelResult; + } + + // 第二优先级:分级号相同时按分类号排序,空值排在最后。 + int classificationResult = compareNullableText( + left.getClassificationCode(), right.getClassificationCode()); + if (classificationResult != 0) { + return classificationResult; + } + + // 第三优先级:前两项相同时按部别名称排序。 + return compareNullableText(left.getName(), right.getName()); + } + + /** + * 比较两个允许为空的整数,空值统一排在非空值之后。 + * + * @param left 左侧整数 + * @param right 右侧整数 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareNullableInteger(Integer left, Integer right) { + // 两侧都为空时视为相等。 + if (left == null && right == null) { + return 0; + } + + // 仅左侧为空时,将左侧排在后面。 + if (left == null) { + return 1; + } + + // 仅右侧为空时,将右侧排在后面。 + if (right == null) { + return -1; + } + + // 两侧均有值时使用整数的自然顺序。 + return left.compareTo(right); + } + + /** + * 比较两个允许为空的文本,空值统一排在非空值之后。 + * + * @param left 左侧文本 + * @param right 右侧文本 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareNullableText(String left, String right) { + // 两侧都为空时视为相等。 + if (left == null && right == null) { + return 0; + } + + // 仅左侧为空时,将左侧排在后面。 + if (left == null) { + return 1; + } + + // 仅右侧为空时,将右侧排在后面。 + if (right == null) { + return -1; + } + + // 优先忽略大小写比较,保证英文字母排序不受大小写直接影响。 + int ignoreCaseResult = left.compareToIgnoreCase(right); + + // 忽略大小写后相等时再按原文本比较,使排序结果保持稳定且明确。 + return ignoreCaseResult == 0 ? left.compareTo(right) : ignoreCaseResult; + } + + /** + * 校验并规范化部别编号。 + * + * @param id 原始部别编号 + * @return 去除首尾空白后的有效编号 + */ + private String requireId(String id) { + // 第一步:去除编号首尾空白,并将空字符串统一转换为 null。 + String normalizedId = normalizeText(id); + + // 第二步:编号为空时返回参数错误。 + if (normalizedId == null) { + throw new BusinessException(BAD_REQUEST, "部别编号不能为空"); + } + + // 第三步:限制编号最大长度,避免传入不符合存储定义的值。 + if (normalizedId.length() > 36) { + throw new BusinessException(BAD_REQUEST, "部别编号格式不正确"); + } + + // 第四步:返回通过校验的规范化编号。 + return normalizedId; + } + + /** + * 校验必填文本并去除首尾空白。 + * + * @param value 原始文本 + * @param message 文本为空时使用的错误提示 + * @return 规范化后的非空文本 + */ + private String requireText(String value, String message) { + // 第一步:统一处理 null、空字符串和首尾空白。 + String normalized = normalizeText(value); + + // 第二步:规范化后为空时抛出调用方指定的参数错误。 + if (normalized == null) { + throw new BusinessException(BAD_REQUEST, message); + } + + // 第三步:返回通过必填校验的文本。 + return normalized; + } + + /** + * 规范化分类号,并为空值提供默认值。 + * + * @param value 原始分类号 + * @return 有效分类号;未填写时返回 0 + */ + private String defaultClassificationCode(String value) { + // 第一步:去除首尾空白,并将空字符串转换为 null。 + String normalized = normalizeText(value); + + // 第二步:未填写分类号时使用系统默认分类号 0。 + return normalized == null ? "0" : normalized; + } + + /** + * 校验分级号,并为空值提供默认值。 + * + * @param value 原始分级号 + * @return 有效分级号;未填写时返回 0 + */ + private Integer defaultLevelNumber(Integer value) { + // 第一步:未填写分级号时使用系统默认分级号 0。 + if (value == null) { + return 0; + } + + // 第二步:分级号不能为负数。 + if (value < 0) { + throw new BusinessException(BAD_REQUEST, "分级号不能小于0"); + } + + // 第三步:返回通过校验的分级号。 + return value; + } + + /** + * 统一规范化可选文本。 + * + * @param value 原始文本 + * @return 去除首尾空白后的文本;null 或空字符串返回 null + */ + private String normalizeText(String value) { + // 原始值为 null 时直接返回 null。 + if (value == null) { + return null; + } + + // 去除用户输入或数据库文本中的首尾空白。 + String normalized = value.trim(); + + // 去除空白后没有有效内容时统一返回 null。 + return normalized.isEmpty() ? null : normalized; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DepartmentPersonnelServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DepartmentPersonnelServiceImpl.java new file mode 100644 index 000000000..53ed93301 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DepartmentPersonnelServiceImpl.java @@ -0,0 +1,1171 @@ +package com.roomroot.jwgl.service.impl; + +import com.roomroot.jwgl.dto.departmentpersonnel.*; +import com.roomroot.jwgl.entity.SSJG; +import com.roomroot.jwgl.entity.SSJGCM; +import com.roomroot.jwgl.mapper.SSJGCMMapper; +import com.roomroot.jwgl.mapper.SSJGMapper; +import com.roomroot.jwgl.service.DepartmentPersonnelService; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.utils.ExcelExportUtil; +import com.roomroot.jwgl.utils.ExcelParseUtil; +import com.roomroot.jwgl.utils.UuidUtil; +import com.roomroot.jwgl.vo.departmentpersonnel.DepartmentPersonnelDepartmentVO; +import com.roomroot.jwgl.vo.departmentpersonnel.DepartmentPersonnelVO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +import java.util.*; + +import static com.roomroot.jwgl.utils.BasicManagementConstants.*; + +/** + * 部别人员管理服务实现。 + */ +@Service +public class DepartmentPersonnelServiceImpl implements DepartmentPersonnelService { + + @Resource + private SSJGMapper ssjgMapper; + + @Resource + private SSJGCMMapper ssjgcmMapper; + + /** + * 查询人员管理页面可选择的启用部别。 + * + *

处理顺序:读取全部部别、规范化关键字、排除停用部别、 + * 应用关键字过滤、转换为页面 VO、按照组织顺序稳定排序。

+ * + * @param dto 部别查询条件 + * @return 启用部别列表 + */ + @Override + public List listDepartments( + DepartmentPersonnelQueryDTO dto) { + // 第一步:一次性读取全部部别,并统一处理 Mapper 返回 null 的情况。 + List departments = selectAllDepartments(); + + // 第二步:规范化可选关键字,并再次执行长度校验以覆盖业务层直接调用场景。 + String keyword = optionalText( + dto == null ? null : dto.getKeyword(), + PERSONNEL_TEXT_MAX_LENGTH, + "查询关键字"); + + // 第三步:创建最终返回的部别选择项集合。 + List result = new ArrayList<>(); + + // 第四步:逐个筛选可供人员新增、调整和复职使用的启用部别。 + for (SSJG department : departments) { + // 数据集合中的空记录不能转换为页面选项。 + if (department == null) { + continue; + } + + // 停用部别不能继续接收人员,因此不出现在可选部别列表中。 + if (Boolean.TRUE.equals(department.getTY())) { + continue; + } + + // 未命中部别主要展示字段关键字时跳过当前部别。 + if (!matchesDepartment(department, keyword)) { + continue; + } + + // 将数据库部别实体转换为部别人员页面专用 VO。 + result.add(toDepartmentVO(department)); + } + + // 第五步:按照分级号、分类号和名称依次排序,保证页面顺序稳定。 + Collections.sort(result, new Comparator() { + @Override + public int compare(DepartmentPersonnelDepartmentVO left, + DepartmentPersonnelDepartmentVO right) { + return compareDepartments(left, right); + } + }); + + // 第六步:返回完成过滤和排序的启用部别列表。 + return result; + } + + /** + * 查询部别人员列表。 + * + *

处理顺序:规范化查询条件、确认指定部别存在、读取人员与部别、 + * 建立部别索引、应用多条件过滤、转换为 VO、稳定排序。

+ * + * @param dto 人员查询条件 + * @return 人员列表 + */ + @Override + public List list(DepartmentPersonnelQueryDTO dto) { + // 第一步:规范化可选部别编号,并校验数据库标识符长度。 + String departmentId = optionalIdentifier( + dto == null ? null : dto.getDepartmentId(), "所属部别编号"); + + // 第二步:指定部别编号时确认该部别真实存在,避免无效编号被误解为空列表。 + if (departmentId != null) { + requireDepartment(departmentId); + } + + // 第三步:规范化查询关键字,并再次校验数据库字段允许长度。 + String keyword = optionalText( + dto == null ? null : dto.getKeyword(), + PERSONNEL_TEXT_MAX_LENGTH, + "查询关键字"); + + // 第四步:读取全部人员和部别,并建立部别编号索引。 + List personnel = selectAllPersonnel(); + Map departmentMap = + indexDepartments(selectAllDepartments()); + + // 第五步:创建最终返回的人员 VO 集合。 + List result = new ArrayList<>(); + + // 第六步:逐条应用部别、关键字、离职状态和主官状态筛选。 + for (SSJGCM person : personnel) { + // 数据集合中的空记录不能转换为页面结果。 + if (person == null) { + continue; + } + + // 根据人员所属部别编号取得用于展示和关键字匹配的部别信息。 + SSJG department = + departmentMap.get(normalizeText(person.getSsjgbh())); + + // 未命中全部查询条件时跳过当前人员。 + if (!matchesPersonnel(person, department, dto, departmentId, keyword)) { + continue; + } + + // 将人员实体和部别实体组合为独立的页面 VO。 + result.add(toPersonnelVO(person, department)); + } + + // 第七步:按照部别、序号、姓名和编号依次排序。 + Collections.sort(result, new Comparator() { + @Override + public int compare(DepartmentPersonnelVO left, + DepartmentPersonnelVO right) { + return comparePersonnel(left, right); + } + }); + + // 第八步:返回完成过滤和排序的人员列表。 + return result; + } + + /** + * 查询部别人员详情。 + * + * @param dto 人员编号参数 + * @return 人员详情 + */ + @Override + public DepartmentPersonnelVO get(DepartmentPersonnelIdDTO dto) { + // 第一步:兼容业务层直接传入 null 的场景,并校验人员编号。 + String id = requireIdentifier( + dto == null ? null : dto.getId(), "人员编号"); + + // 第二步:查询人员并补充所属部别展示信息。 + return loadPersonnelView(id); + } + + /** + * 新增部别人员。 + * + *

处理顺序:校验参数、确认部别存在且启用、规范化人员字段、 + * 构造持久化实体、写入数据库、重新查询并返回 VO。

+ * + * @param dto 人员新增参数 + * @return 新增后的人员信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DepartmentPersonnelVO add(DepartmentPersonnelCreateDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "新增参数不能为空"); + } + + // 第二步:校验并规范化所属部别编号。 + String departmentId = + requireIdentifier(dto.getDepartmentId(), "所属部别编号"); + + // 第三步:确认目标部别真实存在并且处于启用状态。 + SSJG department = requireDepartment(departmentId); + ensureDepartmentEnabled(department); + + /* + * 第四步:将新增 DTO 转换为人员实体。 + * DTO 仅负责接收请求,SSJGCM 只在持久化和内部业务流程中流转。 + */ + SSJGCM personnel = buildNewPersonnel(dto, departmentId); + + // 第五步:执行新增,并校验数据库实际影响行数。 + if (ssjgcmMapper.insertPersonnel(personnel) != 1) { + throw new BusinessException("新增部别人员失败"); + } + + // 第六步:重新查询数据库并返回包含部别展示字段的完整 VO。 + return loadPersonnelView(personnel.getBh()); + } + + /** + * 修改部别人员。 + * + *

处理顺序:校验参数、确认人员存在、确认目标部别存在、 + * 校验部别异动目标状态、构造更新实体、更新数据库、返回最新 VO。

+ * + * @param dto 人员修改参数 + * @return 修改后的人员信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DepartmentPersonnelVO update(DepartmentPersonnelUpdateDTO dto) { + // 第一步:防止业务层被直接调用时传入 null。 + if (dto == null) { + throw new BusinessException(BAD_REQUEST, "修改参数不能为空"); + } + + // 第二步:校验人员编号,并确认待修改人员真实存在。 + String id = requireIdentifier(dto.getId(), "人员编号"); + SSJGCM existingPersonnel = requirePersonnel(id); + + // 第三步:校验调整后的所属部别编号,并确认目标部别真实存在。 + String departmentId = + requireIdentifier(dto.getDepartmentId(), "所属部别编号"); + SSJG targetDepartment = requireDepartment(departmentId); + + /* + * 第四步:人员发生跨部别异动时,目标部别必须处于启用状态。 + * 未发生部别调整时仍允许维护已有人员基本信息,避免历史异常数据无法修正。 + */ + String currentDepartmentId = + normalizeText(existingPersonnel.getSsjgbh()); + if (!departmentId.equals(currentDepartmentId)) { + ensureDepartmentEnabled(targetDepartment); + } + + /* + * 第五步:将修改 DTO 转换为更新实体。 + * 此处不设置离职状态,保证普通修改不会意外改变在职或离职状态。 + */ + SSJGCM personnel = new SSJGCM(); + personnel.setBh(id); + personnel.setSsjgbh(departmentId); + personnel.setYhxm(requirePersonnelName(dto.getName())); + personnel.setXh(optionalPersonnelText(dto.getSequenceNumber(), "人员序号")); + personnel.setZw(optionalPersonnelText(dto.getPosition(), "人员职务")); + personnel.setKh(optionalPersonnelText(dto.getCardNumber(), "人员卡号")); + personnel.setPy(optionalPersonnelText(dto.getPinyin(), "人员姓名拼音")); + personnel.setZg(toRequiredFlag(dto.getPrincipal(), "主官标志")); + + // 第六步:更新人员基本信息,并校验数据库实际影响行数。 + if (ssjgcmMapper.updatePersonnel(personnel) != 1) { + throw new BusinessException("修改部别人员失败"); + } + + // 第七步:重新查询并返回修改后的完整人员 VO。 + return loadPersonnelView(id); + } + + /** + * 将人员标记为离职。 + * + * @param dto 人员编号参数 + * @return 离职后的人员信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DepartmentPersonnelVO leave(DepartmentPersonnelIdDTO dto) { + // 第一步:校验人员编号,并确认人员真实存在。 + String id = requireIdentifier( + dto == null ? null : dto.getId(), "人员编号"); + SSJGCM personnel = requirePersonnel(id); + + // 第二步:已经离职时直接返回最新数据,保证重复请求幂等。 + if (isDeparted(personnel)) { + return loadPersonnelView(id); + } + + /* + * 第三步:仅更新离职状态,不物理删除人员。 + * 人员编号被多张业务表引用,保留记录可以避免破坏历史业务数据。 + */ + if (ssjgcmMapper.updateDepartureStatus( + id, DEPARTED_STATUS) != 1) { + throw new BusinessException( + CONFLICT, "人员状态已经发生变化,请刷新后重试"); + } + + // 第四步:重新查询并返回离职后的人员信息。 + return loadPersonnelView(id); + } + + /** + * 将人员恢复为在职。 + * + * @param dto 人员编号参数 + * @return 复职后的人员信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DepartmentPersonnelVO restore(DepartmentPersonnelIdDTO dto) { + // 第一步:校验人员编号,并确认人员真实存在。 + String id = requireIdentifier( + dto == null ? null : dto.getId(), "人员编号"); + SSJGCM personnel = requirePersonnel(id); + + // 第二步:已经在职时直接返回最新数据,保证重复请求幂等。 + if (!isDeparted(personnel)) { + return loadPersonnelView(id); + } + + // 第三步:确认人员原所属部别仍然存在。 + String departmentId = + requireIdentifier(personnel.getSsjgbh(), "所属部别编号"); + SSJG department = requireDepartment(departmentId); + + // 第四步:停用部别不能恢复在职人员,必须先调整部别或启用部别。 + ensureDepartmentEnabled(department); + + // 第五步:将离职状态恢复为在职,并防止并发状态变化被静默覆盖。 + if (ssjgcmMapper.updateDepartureStatus( + id, ACTIVE_STATUS) != 1) { + throw new BusinessException( + CONFLICT, "人员状态已经发生变化,请刷新后重试"); + } + + // 第六步:重新查询并返回复职后的人员信息。 + return loadPersonnelView(id); + } + + /** + * 批量导入部别人员。 + * + *

处理顺序:校验批次、读取并索引部别、预校验全部人员、 + * 构造全部实体、逐条写入、组装并返回导入结果。整个方法使用同一事务, + * 任意一条失败都会回滚本批次已经写入的数据。

+ * + * @param dto 批量导入参数 + * @return 成功导入的人员列表 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public List batchImport( + DepartmentPersonnelImportDTO dto) { + // 第一步:防止业务层被直接调用时传入 null 或空批次。 + if (dto == null || dto.getPersonnel() == null + || dto.getPersonnel().isEmpty()) { + throw new BusinessException(BAD_REQUEST, "导入人员列表不能为空"); + } + + // 第二步:限制单批数据量,避免一次请求占用过多内存和数据库事务资源。 + if (dto.getPersonnel().size() > BATCH_IMPORT_MAX_SIZE) { + throw new BusinessException(BAD_REQUEST, "单次最多导入1000人"); + } + + // 第三步:一次性读取并索引全部部别,避免每条导入数据重复查询数据库。 + Map departmentMap = + indexDepartments(selectAllDepartments()); + + // 第四步:创建通过预校验后待写入的人员实体集合。 + List pendingPersonnel = new ArrayList<>(); + + /* + * 第五步:在执行任何写入前预校验整批数据。 + * 这样可以尽早返回明确的行号错误,并减少无效数据库操作。 + */ + for (int index = 0; index < dto.getPersonnel().size(); index++) { + DepartmentPersonnelCreateDTO row = dto.getPersonnel().get(index); + + // 导入行为空时返回包含实际行号的参数错误。 + if (row == null) { + throw new BusinessException( + BAD_REQUEST, "第" + (index + 1) + "条导入数据不能为空"); + } + + // 校验并规范化当前行的所属部别编号。 + String departmentId; + try { + departmentId = + requireIdentifier(row.getDepartmentId(), "所属部别编号"); + } catch (BusinessException exception) { + throw importRowException(index, exception); + } + + // 当前行引用的部别不存在时返回明确行号。 + SSJG department = departmentMap.get(departmentId); + if (department == null) { + throw new BusinessException( + NOT_FOUND, + "第" + (index + 1) + "条导入数据的所属部别不存在"); + } + + // 当前行引用停用部别时禁止导入。 + if (Boolean.TRUE.equals(department.getTY())) { + throw new BusinessException( + CONFLICT, + "第" + (index + 1) + "条导入数据的所属部别已停用"); + } + + // 将当前行 DTO 转换为实体,并为字段校验错误补充导入行号。 + try { + pendingPersonnel.add(buildNewPersonnel(row, departmentId)); + } catch (BusinessException exception) { + throw importRowException(index, exception); + } + } + + // 第六步:创建最终返回的已导入人员 VO 集合。 + List importedPersonnel = new ArrayList<>(); + + // 第七步:逐条写入已经通过整批预校验的人员实体。 + for (SSJGCM personnel : pendingPersonnel) { + // 任意一条写入失败时抛出异常,由事务回滚本批次全部新增。 + if (ssjgcmMapper.insertPersonnel(personnel) != 1) { + throw new BusinessException("批量导入部别人员失败"); + } + + // 使用预先建立的部别索引组装导入结果,避免逐条重新查询。 + SSJG department = + departmentMap.get(normalizeText(personnel.getSsjgbh())); + importedPersonnel.add(toPersonnelVO(personnel, department)); + } + + // 第八步:返回本次成功导入的全部人员信息。 + return importedPersonnel; + } + + /** + * 通过Excel文件导入部别人员。 + * + *

处理顺序:解析Excel文件、校验批次、读取并索引部别、预校验全部人员、 + * 构造全部实体、逐条写入、组装并返回导入结果。整个方法使用同一事务, + * 任意一条失败都会回滚本批次已经写入的数据。

+ * + * @param inputStream Excel文件输入流 + * @param fileName 文件名 + * @return 成功导入的人员列表 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public List importFromExcel( + java.io.InputStream inputStream, String fileName) { + // 第一步:校验文件输入流和文件名。 + if (inputStream == null) { + throw new BusinessException(BAD_REQUEST, "导入文件不能为空"); + } + if (fileName == null || fileName.trim().isEmpty()) { + throw new BusinessException(BAD_REQUEST, "文件名不能为空"); + } + + // 第二步:解析Excel文件,提取人员数据。 + List personnelList; + try { + personnelList = ExcelParseUtil.parseDepartmentPersonnelExcel(inputStream, fileName); + } catch (Exception e) { + throw new BusinessException(BAD_REQUEST, "Excel文件解析失败:" + e.getMessage()); + } + + // 第三步:复用批量导入逻辑完成数据校验和写入。 + DepartmentPersonnelImportDTO importDTO = new DepartmentPersonnelImportDTO(); + importDTO.setPersonnel(personnelList); + return batchImport(importDTO); + } + + /** + * 按查询条件导出人员数据。 + * + * @param dto 导出筛选条件 + * @return 排序后的人员数据 + */ + @Override + public List export(DepartmentPersonnelQueryDTO dto) { + // 第一步:复用列表查询,保证导出与页面使用完全一致的过滤和排序规则。 + List personnel = list(dto); + + // 第二步:返回独立列表,避免调用方修改列表对象影响内部查询结果。 + return new ArrayList<>(personnel); + } + + /** + * 按查询条件导出人员数据为 Excel 文件。 + * + * @param dto 导出筛选条件 + * @return Excel 文件字节数组 + */ + @Override + public byte[] exportToExcel(DepartmentPersonnelQueryDTO dto) { + // 第一步:复用列表查询获取导出数据。 + List personnel = export(dto); + + // 第二步:将数据导出为 Excel 字节数组。 + try { + return ExcelExportUtil.export( + "部别人员数据", + new String[]{"人员编号", "所属部别编号", "所属部别名称", "所属部别简称", + "人员姓名", "人员序号", "是否离职", "人员职务", "人员卡号", + "人员姓名拼音", "是否主官"}, + personnel, + DepartmentPersonnelVO::getId, + DepartmentPersonnelVO::getDepartmentId, + DepartmentPersonnelVO::getDepartmentName, + DepartmentPersonnelVO::getDepartmentAbbreviation, + DepartmentPersonnelVO::getName, + DepartmentPersonnelVO::getSequenceNumber, + DepartmentPersonnelVO::getDeparted, + DepartmentPersonnelVO::getPosition, + DepartmentPersonnelVO::getCardNumber, + DepartmentPersonnelVO::getPinyin, + DepartmentPersonnelVO::getPrincipal + ); + } catch (Exception e) { + throw new BusinessException("导出Excel失败:" + e.getMessage()); + } + } + + /** + * 构造一条新的部别人员实体。 + * + * @param dto 人员新增参数 + * @param departmentId 已规范化的所属部别编号 + * @return 待新增人员实体 + */ + private SSJGCM buildNewPersonnel(DepartmentPersonnelCreateDTO dto, + String departmentId) { + // 第一步:创建仅在持久化流程内部使用的人员实体。 + SSJGCM personnel = new SSJGCM(); + + // 第二步:生成数据库主键,并设置已经校验的所属部别编号。 + personnel.setBh(UuidUtil.getOriginalUUID()); + personnel.setSsjgbh(departmentId); + + // 第三步:规范化姓名和全部可选人员字段。 + personnel.setYhxm(requirePersonnelName(dto.getName())); + personnel.setXh(optionalPersonnelText(dto.getSequenceNumber(), "人员序号")); + personnel.setZw(optionalPersonnelText(dto.getPosition(), "人员职务")); + personnel.setKh(optionalPersonnelText(dto.getCardNumber(), "人员卡号")); + personnel.setPy(optionalPersonnelText(dto.getPinyin(), "人员姓名拼音")); + + // 第四步:新增人员固定初始化为在职,并转换主官布尔标志。 + personnel.setLzzt(ACTIVE_STATUS); + personnel.setZg(toRequiredFlag(dto.getPrincipal(), "主官标志")); + + // 第五步:返回字段完整且可以直接写入数据库的人员实体。 + return personnel; + } + + /** + * 查询并组装人员详情 VO。 + * + * @param id 人员编号 + * @return 人员详情 VO + */ + private DepartmentPersonnelVO loadPersonnelView(String id) { + // 第一步:查询人员实体,不存在时统一返回 404。 + SSJGCM personnel = requirePersonnel(id); + + // 第二步:查询人员所属部别;历史异常数据中部别缺失时允许为 null。 + String departmentId = normalizeText(personnel.getSsjgbh()); + SSJG department = departmentId == null + ? null + : ssjgMapper.selectDepartmentById(departmentId); + + // 第三步:将人员和部别实体组合为对外返回 VO。 + return toPersonnelVO(personnel, department); + } + + /** + * 查询并确认人员存在。 + * + * @param id 人员编号 + * @return 人员实体 + */ + private SSJGCM requirePersonnel(String id) { + // 第一步:根据唯一编号查询人员。 + SSJGCM personnel = + ssjgcmMapper.selectPersonnelById(id); + + // 第二步:查询不到人员时返回资源不存在错误。 + if (personnel == null) { + throw new BusinessException(NOT_FOUND, "部别人员不存在"); + } + + // 第三步:返回已经确认存在的人员实体。 + return personnel; + } + + /** + * 查询并确认部别存在。 + * + * @param id 部别编号 + * @return 部别实体 + */ + private SSJG requireDepartment(String id) { + // 第一步:根据唯一编号查询部别。 + SSJG department = + ssjgMapper.selectDepartmentById(id); + + // 第二步:查询不到部别时返回资源不存在错误。 + if (department == null) { + throw new BusinessException(NOT_FOUND, "所属部别不存在"); + } + + // 第三步:返回已经确认存在的部别实体。 + return department; + } + + /** + * 校验部别处于启用状态。 + * + * @param department 部别实体 + */ + private void ensureDepartmentEnabled(SSJG department) { + // 部别停用时不能新增、转入或恢复在职人员。 + if (Boolean.TRUE.equals(department.getTY())) { + throw new BusinessException(CONFLICT, "所属部别已停用"); + } + } + + /** + * 查询全部部别并统一处理空返回。 + * + * @return 非 null 的部别集合 + */ + private List selectAllDepartments() { + // 第一步:调用 Mapper 读取全部部别。 + List departments = + ssjgMapper.selectAllDepartments(); + + // 第二步:Mapper 返回 null 时转换为空集合。 + return departments == null + ? Collections.emptyList() + : departments; + } + + /** + * 查询全部部别人员并统一处理空返回。 + * + * @return 非 null 的人员集合 + */ + private List selectAllPersonnel() { + // 第一步:调用 Mapper 读取全部部别人员。 + List personnel = + ssjgcmMapper.selectAllPersonnel(); + + // 第二步:Mapper 返回 null 时转换为空集合。 + return personnel == null + ? Collections.emptyList() + : personnel; + } + + /** + * 按部别编号建立实体索引。 + * + * @param departments 部别集合 + * @return 部别编号与实体的映射 + */ + private Map indexDepartments(List departments) { + // 第一步:创建用于快速查找部别的编号索引。 + Map result = new HashMap<>(); + + // 第二步:逐个读取部别并规范化编号。 + for (SSJG department : departments) { + // 空记录不能写入部别索引。 + if (department == null) { + continue; + } + + // 仅将具有有效编号的部别写入索引。 + String id = normalizeText(department.getBH()); + if (id != null) { + result.put(id, department); + } + } + + // 第三步:返回构建完成的部别索引。 + return result; + } + + /** + * 判断人员是否匹配查询条件。 + * + * @param personnel 待判断人员 + * @param department 人员所属部别;历史异常数据中允许为 null + * @param dto 原始查询条件 + * @param departmentId 已规范化的部别编号条件 + * @param keyword 已规范化的查询关键字 + * @return 满足全部条件时返回 true + */ + private boolean matchesPersonnel(SSJGCM personnel, + SSJG department, + DepartmentPersonnelQueryDTO dto, + String departmentId, + String keyword) { + // 第一步:存在部别条件时按照所属部别编号精确匹配。 + if (departmentId != null + && !departmentId.equals(normalizeText(personnel.getSsjgbh()))) { + return false; + } + + // 第二步:存在离职状态条件时,将数据库数值状态转换为布尔值后比较。 + if (dto != null && dto.getDeparted() != null + && dto.getDeparted().booleanValue() != isDeparted(personnel)) { + return false; + } + + // 第三步:存在主官状态条件时,将数据库数值标志转换为布尔值后比较。 + if (dto != null && dto.getPrincipal() != null + && dto.getPrincipal().booleanValue() != isPrincipal(personnel)) { + return false; + } + + // 第四步:未提供关键字时不限制人员文本字段。 + if (keyword == null) { + return true; + } + + /* + * 第五步:关键字覆盖人员编号、姓名、序号、职务、卡号、拼音、 + * 所属部别名称和简称,满足页面主要检索与花名册查找需求。 + */ + return containsIgnoreCase(personnel.getBh(), keyword) + || containsIgnoreCase(personnel.getYhxm(), keyword) + || containsIgnoreCase(personnel.getXh(), keyword) + || containsIgnoreCase(personnel.getZw(), keyword) + || containsIgnoreCase(personnel.getKh(), keyword) + || containsIgnoreCase(personnel.getPy(), keyword) + || department != null + && (containsIgnoreCase(department.getJGMC(), keyword) + || containsIgnoreCase(department.getJC(), keyword)); + } + + /** + * 判断部别是否匹配页面关键字。 + * + * @param department 待判断部别 + * @param keyword 已规范化的关键字 + * @return 匹配时返回 true + */ + private boolean matchesDepartment(SSJG department, String keyword) { + // 未提供关键字时默认匹配全部启用部别。 + if (keyword == null) { + return true; + } + + // 关键字可匹配部别编号、名称、简称、类别和分类号。 + return containsIgnoreCase(department.getBH(), keyword) + || containsIgnoreCase(department.getJGMC(), keyword) + || containsIgnoreCase(department.getJC(), keyword) + || containsIgnoreCase(department.getJGLB(), keyword) + || containsIgnoreCase(department.getFLH(), keyword); + } + + /** + * 将部别实体转换为页面选择项 VO。 + * + * @param department 部别实体 + * @return 部别选择项 VO + */ + private DepartmentPersonnelDepartmentVO toDepartmentVO(SSJG department) { + // 第一步:创建独立返回对象,避免对外暴露数据库实体。 + DepartmentPersonnelDepartmentVO vo = + new DepartmentPersonnelDepartmentVO(); + + // 第二步:复制部别标识和主要展示字段。 + vo.setId(department.getBH()); + vo.setName(department.getJGMC()); + vo.setAbbreviation(department.getJC()); + vo.setCategory(department.getJGLB()); + + // 第三步:复制组织排序和上级关系字段。 + vo.setClassificationCode(department.getFLH()); + vo.setLevelNumber(department.getFJH()); + vo.setParentId(normalizeText(department.getSJJGBH())); + + // 第四步:将数据库停用标志反向转换为启用状态。 + vo.setEnabled(!Boolean.TRUE.equals(department.getTY())); + + // 第五步:返回组装完成的部别选择项 VO。 + return vo; + } + + /** + * 将人员和部别实体组合为对外返回 VO。 + * + * @param personnel 人员实体 + * @param department 所属部别;历史异常数据中允许为 null + * @return 人员 VO + */ + private DepartmentPersonnelVO toPersonnelVO(SSJGCM personnel, + SSJG department) { + // 第一步:创建独立返回对象,避免对外暴露数据库实体。 + DepartmentPersonnelVO vo = new DepartmentPersonnelVO(); + + // 第二步:复制人员标识、所属部别编号和姓名。 + vo.setId(personnel.getBh()); + vo.setDepartmentId(personnel.getSsjgbh()); + vo.setName(personnel.getYhxm()); + + // 第三步:复制排序、职务、卡号和拼音等人员基本信息。 + vo.setSequenceNumber(personnel.getXh()); + vo.setPosition(personnel.getZw()); + vo.setCardNumber(personnel.getKh()); + vo.setPinyin(personnel.getPy()); + + // 第四步:将数据库数值状态转换为清晰的布尔字段。 + vo.setDeparted(isDeparted(personnel)); + vo.setPrincipal(isPrincipal(personnel)); + + // 第五步:所属部别仍存在时补充部别名称和简称。 + if (department != null) { + vo.setDepartmentName(department.getJGMC()); + vo.setDepartmentAbbreviation(department.getJC()); + } + + // 第六步:返回组装完成的人员 VO。 + return vo; + } + + /** + * 比较两个部别选择项。 + * + * @param left 左侧部别 + * @param right 右侧部别 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareDepartments(DepartmentPersonnelDepartmentVO left, + DepartmentPersonnelDepartmentVO right) { + // 第一优先级:按照分级号排序。 + int levelResult = + compareNullableInteger(left.getLevelNumber(), right.getLevelNumber()); + if (levelResult != 0) { + return levelResult; + } + + // 第二优先级:分级号相同时按照分类号排序。 + int classificationResult = compareNullableText( + left.getClassificationCode(), right.getClassificationCode()); + if (classificationResult != 0) { + return classificationResult; + } + + // 第三优先级:前两项相同时按照部别名称排序。 + return compareNullableText(left.getName(), right.getName()); + } + + /** + * 比较两个人员 VO。 + * + * @param left 左侧人员 + * @param right 右侧人员 + * @return 符合 Comparator 约定的比较结果 + */ + private int comparePersonnel(DepartmentPersonnelVO left, + DepartmentPersonnelVO right) { + // 第一优先级:按照部别名称排序,便于全量花名册按部别分组查看。 + int departmentResult = + compareNullableText(left.getDepartmentName(), right.getDepartmentName()); + if (departmentResult != 0) { + return departmentResult; + } + + // 第二优先级:同一部别内按照人员序号排序。 + int sequenceResult = + compareNullableText(left.getSequenceNumber(), right.getSequenceNumber()); + if (sequenceResult != 0) { + return sequenceResult; + } + + // 第三优先级:序号相同时按照人员姓名排序。 + int nameResult = compareNullableText(left.getName(), right.getName()); + if (nameResult != 0) { + return nameResult; + } + + // 第四优先级:前述字段相同时按照人员编号保证排序稳定。 + return compareNullableText(left.getId(), right.getId()); + } + + /** + * 比较两个允许为空的整数,空值统一排在非空值之后。 + * + * @param left 左侧整数 + * @param right 右侧整数 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareNullableInteger(Integer left, Integer right) { + // 两侧都为空时视为相等。 + if (left == null && right == null) { + return 0; + } + + // 仅左侧为空时,将左侧排在后面。 + if (left == null) { + return 1; + } + + // 仅右侧为空时,将右侧排在后面。 + if (right == null) { + return -1; + } + + // 两侧均有值时使用整数自然顺序。 + return left.compareTo(right); + } + + /** + * 比较两个允许为空的文本,空值统一排在非空值之后。 + * + * @param left 左侧文本 + * @param right 右侧文本 + * @return 符合 Comparator 约定的比较结果 + */ + private int compareNullableText(String left, String right) { + // 两侧都为空时视为相等。 + if (left == null && right == null) { + return 0; + } + + // 仅左侧为空时,将左侧排在后面。 + if (left == null) { + return 1; + } + + // 仅右侧为空时,将右侧排在后面。 + if (right == null) { + return -1; + } + + // 优先忽略英文字母大小写进行比较。 + int ignoreCaseResult = left.compareToIgnoreCase(right); + + // 忽略大小写后相等时再比较原文本,保证排序结果稳定。 + return ignoreCaseResult == 0 + ? left.compareTo(right) + : ignoreCaseResult; + } + + /** + * 判断源文本是否包含指定关键字,并忽略英文字母大小写。 + * + * @param source 源文本 + * @param keyword 查询关键字 + * @return 包含关键字时返回 true + */ + private boolean containsIgnoreCase(String source, String keyword) { + // 源文本为空时不能匹配;否则按照 ROOT 区域规则转为小写后比较。 + return source != null + && source.toLowerCase(Locale.ROOT) + .contains(keyword.toLowerCase(Locale.ROOT)); + } + + /** + * 判断人员是否处于离职状态。 + * + * @param personnel 人员实体 + * @return 离职时返回 true + */ + private boolean isDeparted(SSJGCM personnel) { + // 数据库值为 1 时视为离职,null 按数据库默认语义视为在职。 + return Integer.valueOf(DEPARTED_STATUS).equals(personnel.getLzzt()); + } + + /** + * 判断人员是否为主官。 + * + * @param personnel 人员实体 + * @return 为主官时返回 true + */ + private boolean isPrincipal(SSJGCM personnel) { + // 数据库值为 1 时视为主官,null 按非主官处理。 + return Integer.valueOf(PRINCIPAL_STATUS).equals(personnel.getZg()); + } + + /** + * 校验并规范化人员姓名。 + * + * @param name 原始人员姓名 + * @return 有效人员姓名 + */ + private String requirePersonnelName(String name) { + // 使用统一必填文本校验规则,并限制为数据库字段长度。 + return requiredText( + name, PERSONNEL_TEXT_MAX_LENGTH, "人员姓名"); + } + + /** + * 校验并规范化人员可选文本字段。 + * + * @param value 原始文本 + * @param fieldName 字段名称 + * @return 规范化后的可选文本 + */ + private String optionalPersonnelText(String value, String fieldName) { + // 使用统一可选文本校验规则,并限制为数据库字段长度。 + return optionalText( + value, PERSONNEL_TEXT_MAX_LENGTH, fieldName); + } + + /** + * 校验并规范化必填标识符。 + * + * @param id 原始标识符 + * @param fieldName 字段名称 + * @return 有效标识符 + */ + private String requireIdentifier(String id, String fieldName) { + // 第一步:去除首尾空白,并将空字符串统一转换为 null。 + String normalizedId = normalizeText(id); + + // 第二步:规范化后为空时返回参数错误。 + if (normalizedId == null) { + throw new BusinessException( + BAD_REQUEST, fieldName + "不能为空"); + } + + // 第三步:超过数据库标识符长度时返回参数错误。 + if (normalizedId.length() > IDENTIFIER_MAX_LENGTH) { + throw new BusinessException( + BAD_REQUEST, fieldName + "格式不正确"); + } + + // 第四步:返回通过校验的规范化标识符。 + return normalizedId; + } + + /** + * 校验并规范化可选标识符。 + * + * @param id 原始标识符 + * @param fieldName 字段名称 + * @return 规范化后的标识符;未填写时返回 null + */ + private String optionalIdentifier(String id, String fieldName) { + // 第一步:去除首尾空白,并将空字符串统一转换为 null。 + String normalizedId = normalizeText(id); + + // 第二步:未填写可选标识符时直接返回 null。 + if (normalizedId == null) { + return null; + } + + // 第三步:存在值时复用必填标识符长度校验。 + return requireIdentifier(normalizedId, fieldName); + } + + /** + * 校验并规范化必填文本。 + * + * @param value 原始文本 + * @param maxLength 最大长度 + * @param fieldName 字段名称 + * @return 有效文本 + */ + private String requiredText(String value, + int maxLength, + String fieldName) { + // 第一步:使用统一可选文本方法完成规范化和长度校验。 + String normalized = optionalText(value, maxLength, fieldName); + + // 第二步:规范化后为空时返回必填参数错误。 + if (normalized == null) { + throw new BusinessException( + BAD_REQUEST, fieldName + "不能为空"); + } + + // 第三步:返回通过校验的必填文本。 + return normalized; + } + + /** + * 校验并规范化可选文本。 + * + * @param value 原始文本 + * @param maxLength 最大长度 + * @param fieldName 字段名称 + * @return 规范化后的文本;未填写时返回 null + */ + private String optionalText(String value, + int maxLength, + String fieldName) { + // 第一步:去除首尾空白,并将空字符串统一转换为 null。 + String normalized = normalizeText(value); + + // 第二步:未填写可选文本时直接返回 null。 + if (normalized == null) { + return null; + } + + // 第三步:超过数据库字段长度时返回参数错误。 + if (normalized.length() > maxLength) { + throw new BusinessException( + BAD_REQUEST, + fieldName + "不能超过" + maxLength + "个字符"); + } + + // 第四步:返回通过校验的可选文本。 + return normalized; + } + + /** + * 将必填布尔值转换为数据库数值标志。 + * + * @param value 原始布尔值 + * @param fieldName 字段名称 + * @return 0 或 1 + */ + private Integer toRequiredFlag(Boolean value, String fieldName) { + // 第一步:布尔值为空时返回必填参数错误。 + if (value == null) { + throw new BusinessException( + BAD_REQUEST, fieldName + "不能为空"); + } + + // 第二步:将 true 转为 1,将 false 转为 0。 + return value.booleanValue() + ? PRINCIPAL_STATUS + : NON_PRINCIPAL_STATUS; + } + + /** + * 为批量导入字段错误补充实际行号。 + * + * @param zeroBasedIndex 从 0 开始的导入索引 + * @param exception 原始业务异常 + * @return 包含行号的业务异常 + */ + private BusinessException importRowException( + int zeroBasedIndex, BusinessException exception) { + // 第一步:保留原始业务错误码,并为提示信息增加导入行号。 + String message = "第" + (zeroBasedIndex + 1) + + "条导入数据:" + exception.getMessage(); + + // 第二步:返回可直接抛出的行级业务异常。 + return new BusinessException(exception.getCode(), message); + } + + /** + * 统一规范化可选文本。 + * + * @param value 原始文本 + * @return 去除首尾空白后的文本;null 或空字符串返回 null + */ + private String normalizeText(String value) { + // 原始值为 null 时直接返回 null。 + if (value == null) { + return null; + } + + // 去除用户输入或数据库文本中的首尾空白。 + String normalized = value.trim(); + + // 去除空白后没有有效内容时统一返回 null。 + return normalized.isEmpty() ? null : normalized; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DictDataServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DictDataServiceImpl.java new file mode 100644 index 000000000..381ca64ad --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DictDataServiceImpl.java @@ -0,0 +1,524 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.dict.DictDataBatchQueryDTO; +import com.roomroot.jwgl.dto.dict.DictDataQueryDTO; +import com.roomroot.jwgl.dto.dict.DictDataSaveDTO; +import com.roomroot.jwgl.entity.SysDictData; +import com.roomroot.jwgl.entity.SysDictType; +import com.roomroot.jwgl.mapper.DictDataMapper; +import com.roomroot.jwgl.mapper.DictTypeMapper; +import com.roomroot.jwgl.service.DictDataService; + +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.PageResultUtil; +import com.roomroot.jwgl.vo.dict.DictDataVO; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 字典数据服务实现。 + *

+ * 实现字典数据的查询(单类型、分页、批量)、保存(新增/编辑)和删除业务逻辑。 + * 保存时校验关联类型是否存在,处理默认值唯一性,保证数据一致性。 + *

+ */ +@Service +public class DictDataServiceImpl implements DictDataService { + + /** + * 字典数据数据访问接口。 + */ + @Resource + private DictDataMapper dictDataMapper; + + /** + * 字典类型数据访问接口。 + *

用于保存字典数据时校验关联的字典类型是否存在。

+ */ + @Resource + private DictTypeMapper dictTypeMapper; + + /** + * JSON序列化工具 + */ + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 按类型编码查询全部启用的字典数据。 + *

+ * 构建查询条件:按类型编码精确匹配、状态为启用(1)、按排序号升序排列。 + * 查询结果转换为 VO 列表返回,用于前端下拉框等场景。 + *

+ * + * @param dictTypeCode 字典类型编码 + * @return 启用的字典数据 VO 列表 + */ + @Override + public List listByType(String dictTypeCode) { + // 第一步:构建 Lambda 查询条件。 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 按类型编码精确匹配。 + wrapper.eq(SysDictData::getDictTypeCode, dictTypeCode); + // 仅查询状态为启用的数据。 + wrapper.eq(SysDictData::getStatus, 1); + // 按排序号升序排列,保证前端展示顺序一致。 + wrapper.orderByAsc(SysDictData::getDictSort); + + // 第二步:执行查询。 + List list = dictDataMapper.selectList(wrapper); + // 第三步:将实体列表转换为 VO 列表返回。 + return list.stream().map(this::toVO).collect(Collectors.toList()); + } + + /** + * 分页查询字典数据列表(管理页面用)。 + *

+ * 根据查询条件构建 MyBatis-Plus Lambda 查询包装器, + * 支持按字典类型编码精确匹配、关键字(模糊匹配标签或值)、状态筛选, + * 结果按排序号升序排列。分页查询完成后将实体列表转换为 VO 列表返回。 + *

+ * + * @param dto 查询条件,包含分页参数和筛选条件 + * @return 字典数据分页结果 + */ + @Override + public PageResult page(DictDataQueryDTO dto) { + // 第一步:创建 MyBatis-Plus 分页对象。 + Page page = new Page<>(dto.getPageNum(), dto.getPageSize()); + // 第二步:构建 Lambda 查询条件包装器。 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 第三步:按字典类型编码精确匹配(必填条件)。 + wrapper.eq(SysDictData::getDictTypeCode, dto.getDictTypeCode()); + // 第四步:若有关键字,同时模糊匹配字典标签和字典值。 + if (dto.getKeyword() != null && !dto.getKeyword().isEmpty()) { + wrapper.and(w -> w.like(SysDictData::getDictLabel, dto.getKeyword()) + .or() + .like(SysDictData::getDictValue, dto.getKeyword())); + } + // 第五步:若指定了状态,按状态精确匹配。 + if (dto.getStatus() != null) { + wrapper.eq(SysDictData::getStatus, dto.getStatus()); + } + // 第六步:按排序号升序排列。 + wrapper.orderByAsc(SysDictData::getDictSort); + + // 第七步:执行分页查询。 + Page result = dictDataMapper.selectPage(page, wrapper); + // 第八步:将查询结果的实体列表转换为 VO 列表。 + List records = result.getRecords().stream() + .map(this::toVO) + .collect(Collectors.toList()); + // 第九步:使用分页工具组装统一分页结果返回。 + return PageResultUtil.fromRecords( + result, + records, + dto.getPageNum(), + dto.getPageSize()); + } + + /** + * 批量查询多个类型的字典数据。 + *

+ * 遍历传入的类型编码列表,依次调用 {@link #listByType} 查询每种类型的启用数据, + * 将结果按类型编码分组放入 Map 中返回。 + *

+ * + * @param dto 批量查询参数,包含字典类型编码列表 + * @return 按类型编码分组的字典数据 VO 列表 + */ + @Override + public Map> batchList(DictDataBatchQueryDTO dto) { + // 第一步:初始化结果 Map。 + Map> result = new HashMap<>(); + // 第二步:若类型编码列表为空,直接返回空 Map。 + if (dto.getTypeCodes() == null || dto.getTypeCodes().isEmpty()) { + return result; + } + + // 第三步:遍历每个类型编码,查询该类型的字典数据并放入 Map。 + for (String typeCode : dto.getTypeCodes()) { + result.put(typeCode, listByType(typeCode)); + } + return result; + } + + /** + * 保存字典数据(新增或编辑)。 + *

+ * 根据 {@code dto.dictDataId} 判断是新增还是编辑: + *

    + *
  • 保存前校验关联的字典类型是否存在。
  • + *
  • 若 {@code isDefault} 设置为 1,先清除该类型下其他数据的默认标记,保证唯一性。
  • + *
  • 新增:构建实体后插入数据库。
  • + *
  • 编辑:查询原记录,更新字段后写入数据库。
  • + *
+ * 整个操作在事务中执行。 + *

+ * + * @param dto 保存参数 + * @return 保存后的字典数据 VO + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DictDataVO save(DictDataSaveDTO dto) { + // 第一步:校验关联的字典类型是否存在。 + SysDictType type = dictTypeMapper.selectOne( + new LambdaQueryWrapper() + .eq(SysDictType::getDictTypeCode, dto.getDictTypeCode())); + if (type == null) { + throw new BusinessException("字典类型不存在"); + } + + // 分支一:dictDataId 为空,执行新增操作。 + if (dto.getDictDataId() == null) { + // 若设置为默认值,先清除该类型下其他数据的默认标记。 + if (dto.getIsDefault() != null && dto.getIsDefault() == 1) { + clearDefault(dto.getDictTypeCode()); + } + + // 构建字典数据实体并赋值。 + SysDictData entity = new SysDictData(); + entity.setDictTypeCode(dto.getDictTypeCode()); + entity.setDictLabel(dto.getDictLabel()); + entity.setDictValue(dto.getDictValue()); + entity.setDictSort(dto.getDictSort()); + entity.setCssClass(dto.getCssClass()); + entity.setListClass(dto.getListClass()); + entity.setIsDefault(dto.getIsDefault()); + entity.setStatus(dto.getStatus()); + entity.setRemark(dto.getRemark()); + + // --- 版本控制:新增时初始化版本号为1,历史快照为空数组 --- + // 后续每次编辑都会通过 saveSnapshot 方法追加历史快照并递增版本号 + entity.setVersion(1); + entity.setPrevSnapshot("[]"); + + // --- 逻辑删除:新增时显式标记为未删除 --- + // @TableLogic 注解会使 MyBatis-Plus 自动在查询中追加 WHERE is_deleted=0 条件 + // 此处显式赋值,确保即使数据库默认值未生效也能正确标记 + entity.setIsDeleted(0); + + // 插入数据库,create_time 和 create_by 由 MyBatis-Plus 自动填充。 + dictDataMapper.insert(entity); + // 转换为 VO 返回。 + return toVO(entity); + } else { + // 分支二:dictDataId 有值,执行编辑操作。 + // 查询原记录。 + SysDictData exist = dictDataMapper.selectById(dto.getDictDataId()); + if (exist == null) { + throw new BusinessException("字典数据不存在"); + } + + // 若之前不是默认值,现在设置为默认值,则清除该类型下其他数据的默认标记。 + if (dto.getIsDefault() != null && dto.getIsDefault() == 1 + && (exist.getIsDefault() == null || exist.getIsDefault() != 1)) { + clearDefault(exist.getDictTypeCode()); + } + + // --- 版本控制逻辑:将编辑前的旧数据存入历史快照 --- + // saveSnapshot 方法会把当前 exist 对象(旧数据)转为 JSON 快照追加到 prevSnapshot 中, + // 随后 exist 的字段会被新值覆盖,版本号递增后写回数据库 + saveSnapshot(exist); + + // 更新实体字段。 + exist.setDictTypeCode(dto.getDictTypeCode()); + exist.setDictLabel(dto.getDictLabel()); + exist.setDictValue(dto.getDictValue()); + exist.setDictSort(dto.getDictSort()); + exist.setCssClass(dto.getCssClass()); + exist.setListClass(dto.getListClass()); + exist.setIsDefault(dto.getIsDefault()); + exist.setStatus(dto.getStatus()); + exist.setRemark(dto.getRemark()); + + // 版本号递增(每次编辑产生新版本,旧版本已在 saveSnapshot 中存入历史) + exist.setVersion(exist.getVersion() + 1); + + // 更新数据库,update_time 和 update_by 由 MyBatis-Plus 自动填充。 + dictDataMapper.updateById(exist); + // 转换为 VO 返回。 + return toVO(exist); + } + } + + /** + * 保存历史版本快照。 + *

+ * 将编辑前的实体对象构建为 JSON 快照,追加到 {@code prevSnapshot} 字段中。 + * 快照 JSON 结构示例: + *

+     * [
+     *   {
+     *     "version": 1,
+     *     "snapshotTime": "2026-08-01T10:00:00",
+     *     "dictLabel": "考试",
+     *     "dictValue": "1",
+     *     "dictSort": 1,
+     *     "cssClass": "danger",
+     *     "listClass": "default",
+     *     "isDefault": 1,
+     *     "status": 1,
+     *     "remark": "初始版本"
+     *   }
+     * ]
+     * 
+ * 历史版本数量上限为 20 个,超出后自动丢弃最早的版本。 + *

+ * + * @param exist 编辑前的实体对象(即当前数据库中的旧数据) + */ + private void saveSnapshot(SysDictData exist) { + try { + // 构建当前版本的快照对象,包含版本号和所有业务字段 + Map snapshot = new HashMap<>(); + // 记录当前版本号,用于后续版本回滚时定位 + snapshot.put("version", exist.getVersion()); + // 记录快照生成时间,便于版本比对 + snapshot.put("snapshotTime", LocalDateTime.now().toString()); + // 注意:此处简化处理,实际项目中可能需要从安全上下文中获取操作人ID + // snapshot.put("operatorId", SecurityUtils.getCurrentUserId()); + + // 保存业务字段(不含版本号、快照、删除标记等系统字段,仅记录业务数据) + snapshot.put("dictLabel", exist.getDictLabel()); + snapshot.put("dictValue", exist.getDictValue()); + snapshot.put("dictSort", exist.getDictSort()); + snapshot.put("cssClass", exist.getCssClass()); + snapshot.put("listClass", exist.getListClass()); + snapshot.put("isDefault", exist.getIsDefault()); + snapshot.put("status", exist.getStatus()); + snapshot.put("remark", exist.getRemark()); + + // 解析现有的快照历史(prevSnapshot 为空时初始化为空列表) + List> history = new ArrayList<>(); + if (exist.getPrevSnapshot() != null && !exist.getPrevSnapshot().isEmpty()) { + history = objectMapper.readValue(exist.getPrevSnapshot(), + new TypeReference>>() {}); + } + + // 将当前快照追加到历史列表末尾 + history.add(snapshot); + + // 限制历史版本数量(例如最多保留20个版本),超出则丢弃最早的版本 + // 保证 prevSnapshot 字段不会无限增长 + while (history.size() > 20) { + history.remove(0); + } + + // 将更新后的历史记录序列化为 JSON 并写回实体 + // 注意:此处仅修改了实体对象的 prevSnapshot 字段, + // 实际持久化由调用方(save/rollback 方法中的 updateById)完成 + exist.setPrevSnapshot(objectMapper.writeValueAsString(history)); + } catch (JsonProcessingException e) { + throw new BusinessException("保存版本快照失败: " + e.getMessage()); + } + } + + /** + * 删除字典数据。 + *

+ * 根据字典数据ID查询记录,校验数据是否存在,存在则执行逻辑删除(is_deleted置为1)。 + * 整个操作在事务中执行。 + *

+ * + * @param dictDataId 字典数据ID + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Long dictDataId) { + // 第一步:根据 ID 查询字典数据。 + SysDictData exist = dictDataMapper.selectById(dictDataId); + if (exist == null) { + throw new BusinessException("字典数据不存在"); + } + // 第二步:执行删除。 + // 由于 SysDictData 实体类已添加 @TableLogic 注解,MyBatis-Plus 会自动将 + // deleteById 转换为 UPDATE sys_dict_data SET is_deleted = 1 WHERE dict_data_id = ? + // 即逻辑删除(标记为已删除),而非物理删除,保留历史数据以便版本回溯 + dictDataMapper.deleteById(dictDataId); + } + + /** + * 清除指定类型下的默认标记。 + *

+ * 将指定字典类型下所有 {@code isDefault=1} 的数据项更新为 {@code isDefault=0}, + * 保证同一类型下只有一个默认选项。 + *

+ * + * @param dictTypeCode 字典类型编码 + */ + private void clearDefault(String dictTypeCode) { + // 构建更新实体,只设置 isDefault 字段。 + SysDictData update = new SysDictData(); + update.setIsDefault(0); + // 执行更新:按类型编码和默认标记匹配。 + dictDataMapper.update(update, + new LambdaQueryWrapper() + .eq(SysDictData::getDictTypeCode, dictTypeCode) + .eq(SysDictData::getIsDefault, 1)); + } + + /** + * 将字典数据实体转换为展示对象。 + *

+ * 提取实体中的有效字段赋值给 VO,用于对外返回统一的数据结构。 + *

+ * + * @param entity 字典数据实体 + * @return 字典数据 VO + */ + private DictDataVO toVO(SysDictData entity) { + DictDataVO vo = new DictDataVO(); + vo.setDictDataId(entity.getDictDataId()); + vo.setDictTypeCode(entity.getDictTypeCode()); + vo.setDictLabel(entity.getDictLabel()); + vo.setDictValue(entity.getDictValue()); + vo.setDictSort(entity.getDictSort()); + vo.setCssClass(entity.getCssClass()); + vo.setListClass(entity.getListClass()); + vo.setIsDefault(entity.getIsDefault()); + vo.setStatus(entity.getStatus()); + vo.setRemark(entity.getRemark()); + + // 版本控制字段 + vo.setVersion(entity.getVersion()); + vo.setPrevSnapshot(entity.getPrevSnapshot()); + + vo.setCreateTime(entity.getCreateTime()); + vo.setUpdateTime(entity.getUpdateTime()); + return vo; + } + + /** + * 获取字典数据的历史版本列表。 + *

+ * 根据字典数据ID查询其 {@code prevSnapshot} 字段中存储的所有历史快照, + * 解析为版本列表返回。每个版本快照包含版本号、快照时间和当时的业务字段值。 + * 当 {@code prevSnapshot} 为空时返回空列表。 + *

+ * + * @param dictDataId 字典数据ID + * @return 历史版本快照列表,每个元素为一个 Map,包含 version、snapshotTime 及各业务字段 + */ + @Override + public List> getHistory(Long dictDataId) { + // 根据 ID 查询字典数据(@TableLogic 自动过滤已删除记录) + SysDictData exist = dictDataMapper.selectById(dictDataId); + if (exist == null) { + throw new BusinessException("字典数据不存在"); + } + try { + // 如果 prevSnapshot 为空或空字符串,直接返回空列表 + if (exist.getPrevSnapshot() == null || exist.getPrevSnapshot().isEmpty()) { + return new ArrayList<>(); + } + // 将 prevSnapshot JSON 解析为版本列表返回 + return objectMapper.readValue(exist.getPrevSnapshot(), + new TypeReference>>() {}); + } catch (JsonProcessingException e) { + throw new BusinessException("获取历史版本失败: " + e.getMessage()); + } + } + + /** + * 回滚字典数据到指定版本。 + *

+ * 根据目标版本号从历史快照 {@code prevSnapshot} 中查找对应的快照数据, + * 将当前数据恢复到该版本的状态。回滚操作本身也会生成新的版本快照, + * 保证版本链的完整性(回滚前的当前状态也被保留)。 + *

+ *

+ * 执行步骤: + *

    + *
  1. 查询字典数据是否存在
  2. + *
  3. 从 prevSnapshot 中解析历史版本列表
  4. + *
  5. 查找目标版本号对应的快照
  6. + *
  7. 将当前状态保存为新版本快照(回滚前的状态入历史)
  8. + *
  9. 从目标快照中恢复业务字段值
  10. + *
  11. 版本号递增并更新数据库
  12. + *
+ *

+ * + * @param dictDataId 字典数据ID + * @param targetVersion 目标版本号,必须存在于历史快照中 + * @return 回滚后的字典数据详情 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DictDataVO rollback(Long dictDataId, Integer targetVersion) { + // 第一步:查询字典数据是否存在(@TableLogic 自动过滤已删除记录) + SysDictData exist = dictDataMapper.selectById(dictDataId); + if (exist == null) { + throw new BusinessException("字典数据不存在"); + } + + try { + // 第二步:从 prevSnapshot 中解析历史版本列表 + List> history = new ArrayList<>(); + if (exist.getPrevSnapshot() != null && !exist.getPrevSnapshot().isEmpty()) { + history = objectMapper.readValue(exist.getPrevSnapshot(), + new TypeReference>>() {}); + } + + // 第三步:在历史列表中查找目标版本号对应的快照 + Map targetSnapshot = null; + for (Map snapshot : history) { + if (targetVersion.equals(snapshot.get("version"))) { + targetSnapshot = snapshot; + break; + } + } + + // 如果目标版本不存在,抛出异常 + if (targetSnapshot == null) { + throw new BusinessException("目标版本不存在"); + } + + // 第四步:将当前状态保存为新版本快照 + // (回滚操作本身也是一次编辑,回滚前的状态需要保留在历史中) + saveSnapshot(exist); + + // 第五步:从目标快照中恢复业务字段值 + exist.setDictLabel((String) targetSnapshot.get("dictLabel")); + exist.setDictValue((String) targetSnapshot.get("dictValue")); + if (targetSnapshot.get("dictSort") != null) { + exist.setDictSort(((Number) targetSnapshot.get("dictSort")).intValue()); + } + exist.setCssClass((String) targetSnapshot.get("cssClass")); + exist.setListClass((String) targetSnapshot.get("listClass")); + if (targetSnapshot.get("isDefault") != null) { + exist.setIsDefault(((Number) targetSnapshot.get("isDefault")).intValue()); + } + if (targetSnapshot.get("status") != null) { + exist.setStatus(((Number) targetSnapshot.get("status")).intValue()); + } + exist.setRemark((String) targetSnapshot.get("remark")); + + // 第六步:版本号递增(回滚也算一次编辑,生成新版本) + exist.setVersion(exist.getVersion() + 1); + + // 第七步:更新数据库,update_time 和 update_by 由 MyBatis-Plus 自动填充 + dictDataMapper.updateById(exist); + + // 转换为 VO 返回 + return toVO(exist); + } catch (JsonProcessingException e) { + throw new BusinessException("回滚版本失败: " + e.getMessage()); + } + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DictTypeServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DictTypeServiceImpl.java new file mode 100644 index 000000000..d83ba15ec --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DictTypeServiceImpl.java @@ -0,0 +1,430 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.dict.DictTypeQueryDTO; +import com.roomroot.jwgl.dto.dict.DictTypeSaveDTO; +import com.roomroot.jwgl.entity.SysDictType; +import com.roomroot.jwgl.entity.SysDictData; +import com.roomroot.jwgl.mapper.DictDataMapper; +import com.roomroot.jwgl.mapper.DictTypeMapper; +import com.roomroot.jwgl.service.DictTypeService; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.PageResultUtil; +import com.roomroot.jwgl.vo.dict.DictTypeVO; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 字典类型服务实现。 + *

+ * 实现字典类型的分页查询、保存(新增/编辑)和删除业务逻辑。 + * 保存时校验编码唯一性,删除时校验是否有关联数据,保证数据完整性。 + *

+ */ +@Service +public class DictTypeServiceImpl implements DictTypeService { + + /** + * 字典类型数据访问接口。 + */ + @Resource + private DictTypeMapper dictTypeMapper; + + /** + * 字典数据数据访问接口。 + *

用于删除字典类型时校验是否有关联数据。

+ */ + @Resource + private DictDataMapper dictDataMapper; + + /** + * JSON序列化工具 + */ + private final ObjectMapper objectMapper = new ObjectMapper(); + + /** + * 分页查询字典类型列表。 + *

+ * 根据查询条件构建 MyBatis-Plus Lambda 查询包装器, + * 支持按关键字(模糊匹配类型编码或名称)、所属分组、状态筛选, + * 结果按创建时间降序排列。分页查询完成后将实体列表转换为 VO 列表返回。 + *

+ * + * @param dto 查询条件,包含分页参数和筛选条件 + * @return 字典类型分页结果 + */ + @Override + public PageResult page(DictTypeQueryDTO dto) { + // 第一步:创建 MyBatis-Plus 分页对象,传入当前页码和每页大小。 + Page page = new Page<>(dto.getPageNum(), dto.getPageSize()); + // 第二步:构建 Lambda 查询条件包装器。 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + + // 第三步:若有关键字,同时模糊匹配类型编码和类型名称。 + if (dto.getKeyword() != null && !dto.getKeyword().isEmpty()) { + wrapper.and(w -> w.like(SysDictType::getDictTypeCode, dto.getKeyword()) + .or() + .like(SysDictType::getDictTypeName, dto.getKeyword())); + } + // 第四步:若指定了所属分组,按分组精确匹配。 + if (dto.getDictGroup() != null && !dto.getDictGroup().isEmpty()) { + wrapper.eq(SysDictType::getDictGroup, dto.getDictGroup()); + } + // 第五步:若指定了状态,按状态精确匹配。 + if (dto.getStatus() != null) { + wrapper.eq(SysDictType::getStatus, dto.getStatus()); + } + // 第六步:按创建时间降序排列,新创建的类型排在前面。 + wrapper.orderByDesc(SysDictType::getCreateTime); + + // 第七步:执行分页查询。 + Page result = dictTypeMapper.selectPage(page, wrapper); + // 第八步:将查询结果的实体列表转换为 VO 列表。 + List records = result.getRecords().stream() + .map(this::toVO) + .collect(Collectors.toList()); + // 第九步:使用分页工具组装统一分页结果返回。 + return PageResultUtil.fromRecords( + result, + records, + dto.getPageNum(), + dto.getPageSize()); + } + + /** + * 保存字典类型(新增或编辑)。 + *

+ * 根据 {@code dto.dictTypeId} 判断是新增还是编辑: + *

    + *
  • 新增:校验类型编码全局唯一,构建实体后插入数据库。
  • + *
  • 编辑:查询原记录,若修改了编码则重新校验唯一性,更新字段后写入数据库。
  • + *
+ * 整个操作在事务中执行,保证数据一致性。 + *

+ * + * @param dto 保存参数 + * @return 保存后的字典类型 VO + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DictTypeVO save(DictTypeSaveDTO dto) { + // 分支一:dictTypeId 为空,执行新增操作。 + if (dto.getDictTypeId() == null) { + // 第一步:校验字典类型编码是否已存在。 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysDictType::getDictTypeCode, dto.getDictTypeCode()); + if (dictTypeMapper.selectCount(wrapper) > 0) { + throw new BusinessException("字典类型编码已存在"); + } + + // 第二步:构建字典类型实体并赋值。 + SysDictType entity = new SysDictType(); + entity.setDictTypeCode(dto.getDictTypeCode()); + entity.setDictTypeName(dto.getDictTypeName()); + entity.setDictGroup(dto.getDictGroup()); + entity.setStatus(dto.getStatus()); + entity.setRemark(dto.getRemark()); + + // --- 版本控制:新增时初始化版本号为1,历史快照为空数组 --- + // 后续每次编辑都会通过 saveSnapshot 方法追加历史快照并递增版本号 + entity.setVersion(1); + entity.setPrevSnapshot("[]"); + + // --- 逻辑删除:新增时显式标记为未删除 --- + // @TableLogic 注解会使 MyBatis-Plus 自动在查询中追加 WHERE is_deleted=0 条件 + // 此处显式赋值,确保即使数据库默认值未生效也能正确标记 + entity.setIsDeleted(0); + + // 第三步:插入数据库,create_time 和 create_by 由 MyBatis-Plus 自动填充。 + dictTypeMapper.insert(entity); + // 第四步:转换为 VO 返回。 + return toVO(entity); + } else { + // 分支二:dictTypeId 有值,执行编辑操作。 + // 第一步:根据 ID 查询原记录。 + SysDictType exist = dictTypeMapper.selectById(dto.getDictTypeId()); + if (exist == null) { + throw new BusinessException("字典类型不存在"); + } + + // 第二步:若修改了类型编码,校验新编码是否与其他记录冲突。 + if (!exist.getDictTypeCode().equals(dto.getDictTypeCode())) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysDictType::getDictTypeCode, dto.getDictTypeCode()); + if (dictTypeMapper.selectCount(wrapper) > 0) { + throw new BusinessException("字典类型编码已存在"); + } + } + + // --- 版本控制逻辑:将编辑前的旧数据存入历史快照 --- + // saveSnapshot 方法会把当前 exist 对象(旧数据)转为 JSON 快照追加到 prevSnapshot 中, + // 随后 exist 的字段会被新值覆盖,版本号递增后写回数据库 + saveSnapshot(exist); + + // 第三步:更新实体字段。 + exist.setDictTypeCode(dto.getDictTypeCode()); + exist.setDictTypeName(dto.getDictTypeName()); + exist.setDictGroup(dto.getDictGroup()); + exist.setStatus(dto.getStatus()); + exist.setRemark(dto.getRemark()); + + // 版本号递增(每次编辑产生新版本,旧版本已在 saveSnapshot 中存入历史) + exist.setVersion(exist.getVersion() + 1); + + // 第四步:更新数据库,update_time 和 update_by 由 MyBatis-Plus 自动填充。 + dictTypeMapper.updateById(exist); + // 第五步:转换为 VO 返回。 + return toVO(exist); + } + } + + /** + * 保存历史版本快照。 + *

+ * 将编辑前的实体对象构建为 JSON 快照,追加到 {@code prevSnapshot} 字段中。 + * 快照 JSON 结构示例: + *

+     * [
+     *   {
+     *     "version": 1,
+     *     "snapshotTime": "2026-08-01T10:00:00",
+     *     "dictTypeCode": "course_type",
+     *     "dictTypeName": "课程类型",
+     *     "dictGroup": "教学数据",
+     *     "status": 1,
+     *     "remark": "初始版本"
+     *   }
+     * ]
+     * 
+ * 历史版本数量上限为 20 个,超出后自动丢弃最早的版本。 + *

+ * + * @param exist 编辑前的实体对象(即当前数据库中的旧数据) + */ + private void saveSnapshot(SysDictType exist) { + try { + // 构建当前版本的快照对象,包含版本号和所有业务字段 + Map snapshot = new HashMap<>(); + // 记录当前版本号,用于后续版本回滚时定位 + snapshot.put("version", exist.getVersion()); + // 记录快照生成时间,便于版本比对 + snapshot.put("snapshotTime", LocalDateTime.now().toString()); + // 注意:此处简化处理,实际项目中可能需要从安全上下文中获取操作人ID + // snapshot.put("operatorId", SecurityUtils.getCurrentUserId()); + + // 保存业务字段(不含版本号、快照、删除标记等系统字段,仅记录业务数据) + snapshot.put("dictTypeCode", exist.getDictTypeCode()); + snapshot.put("dictTypeName", exist.getDictTypeName()); + snapshot.put("dictGroup", exist.getDictGroup()); + snapshot.put("status", exist.getStatus()); + snapshot.put("remark", exist.getRemark()); + + // 解析现有的快照历史(prevSnapshot 为空时初始化为空列表) + List> history = new ArrayList<>(); + if (exist.getPrevSnapshot() != null && !exist.getPrevSnapshot().isEmpty()) { + history = objectMapper.readValue(exist.getPrevSnapshot(), + new TypeReference>>() {}); + } + + // 将当前快照追加到历史列表末尾 + history.add(snapshot); + + // 限制历史版本数量(例如最多保留20个版本),超出则丢弃最早的版本 + // 保证 prevSnapshot 字段不会无限增长 + while (history.size() > 20) { + history.remove(0); + } + + // 将更新后的历史记录序列化为 JSON 并写回实体 + // 注意:此处仅修改了实体对象的 prevSnapshot 字段, + // 实际持久化由调用方(save/rollback 方法中的 updateById)完成 + exist.setPrevSnapshot(objectMapper.writeValueAsString(history)); + } catch (JsonProcessingException e) { + throw new BusinessException("保存版本快照失败: " + e.getMessage()); + } + } + + /** + * 删除字典类型。 + *

+ * 删除前先校验字典类型是否存在,再通过字典类型编码查询该类型下是否有关联的字典数据项。 + * 若存在数据项,抛出业务异常禁止删除;若不存在,执行逻辑删除(is_deleted置为1)。 + * 整个操作在事务中执行。 + *

+ * + * @param dictTypeId 字典类型ID + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(Long dictTypeId) { + // 第一步:根据 ID 查询字典类型。 + SysDictType exist = dictTypeMapper.selectById(dictTypeId); + if (exist == null) { + throw new BusinessException("字典类型不存在"); + } + + // 第二步:通过类型编码查询该类型下是否有关联的字典数据项。 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(SysDictData::getDictTypeCode, exist.getDictTypeCode()); + if (dictDataMapper.selectCount(wrapper) > 0) { + throw new BusinessException("该字典类型下存在数据项,无法删除"); + } + + // 第三步:执行删除。 + // 由于 SysDictType 实体类已添加 @TableLogic 注解,MyBatis-Plus 会自动将 + // deleteById 转换为 UPDATE sys_dict_type SET is_deleted = 1 WHERE dict_type_id = ? + // 即逻辑删除(标记为已删除),而非物理删除,保留历史数据以便版本回溯 + dictTypeMapper.deleteById(dictTypeId); + } + + /** + * 将字典类型实体转换为展示对象。 + *

+ * 提取实体中的有效字段赋值给 VO,用于对外返回统一的数据结构。 + *

+ * + * @param entity 字典类型实体 + * @return 字典类型 VO + */ + private DictTypeVO toVO(SysDictType entity) { + DictTypeVO vo = new DictTypeVO(); + vo.setDictTypeId(entity.getDictTypeId()); + vo.setDictTypeCode(entity.getDictTypeCode()); + vo.setDictTypeName(entity.getDictTypeName()); + vo.setDictGroup(entity.getDictGroup()); + vo.setStatus(entity.getStatus()); + vo.setRemark(entity.getRemark()); + + // 版本控制字段 + vo.setVersion(entity.getVersion()); + vo.setPrevSnapshot(entity.getPrevSnapshot()); + + vo.setCreateTime(entity.getCreateTime()); + vo.setUpdateTime(entity.getUpdateTime()); + return vo; + } + + /** + * 获取字典类型的历史版本列表。 + *

+ * 根据字典类型ID查询其 {@code prevSnapshot} 字段中存储的所有历史快照, + * 解析为版本列表返回。每个版本快照包含版本号、快照时间和当时的业务字段值。 + * 当 {@code prevSnapshot} 为空时返回空列表。 + *

+ * + * @param dictTypeId 字典类型ID + * @return 历史版本快照列表,每个元素为一个 Map,包含 version、snapshotTime 及各业务字段 + */ + @Override + public List> getHistory(Long dictTypeId) { + // 根据 ID 查询字典类型(@TableLogic 自动过滤已删除记录) + SysDictType exist = dictTypeMapper.selectById(dictTypeId); + if (exist == null) { + throw new BusinessException("字典类型不存在"); + } + try { + // 如果 prevSnapshot 为空或空字符串,直接返回空列表 + if (exist.getPrevSnapshot() == null || exist.getPrevSnapshot().isEmpty()) { + return new ArrayList<>(); + } + // 将 prevSnapshot JSON 解析为版本列表返回 + return objectMapper.readValue(exist.getPrevSnapshot(), + new TypeReference>>() {}); + } catch (JsonProcessingException e) { + throw new BusinessException("获取历史版本失败: " + e.getMessage()); + } + } + + /** + * 回滚字典类型到指定版本。 + *

+ * 根据目标版本号从历史快照 {@code prevSnapshot} 中查找对应的快照数据, + * 将当前数据恢复到该版本的状态。回滚操作本身也会生成新的版本快照, + * 保证版本链的完整性(回滚前的当前状态也被保留)。 + *

+ *

+ * 执行步骤: + *

    + *
  1. 查询字典类型是否存在
  2. + *
  3. 从 prevSnapshot 中解析历史版本列表
  4. + *
  5. 查找目标版本号对应的快照
  6. + *
  7. 将当前状态保存为新版本快照(回滚前的状态入历史)
  8. + *
  9. 从目标快照中恢复业务字段值
  10. + *
  11. 版本号递增并更新数据库
  12. + *
+ *

+ * + * @param dictTypeId 字典类型ID + * @param targetVersion 目标版本号,必须存在于历史快照中 + * @return 回滚后的字典类型详情 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public DictTypeVO rollback(Long dictTypeId, Integer targetVersion) { + // 第一步:查询字典类型是否存在(@TableLogic 自动过滤已删除记录) + SysDictType exist = dictTypeMapper.selectById(dictTypeId); + if (exist == null) { + throw new BusinessException("字典类型不存在"); + } + + try { + // 第二步:从 prevSnapshot 中解析历史版本列表 + List> history = new ArrayList<>(); + if (exist.getPrevSnapshot() != null && !exist.getPrevSnapshot().isEmpty()) { + history = objectMapper.readValue(exist.getPrevSnapshot(), + new TypeReference>>() {}); + } + + // 第三步:在历史列表中查找目标版本号对应的快照 + Map targetSnapshot = null; + for (Map snapshot : history) { + if (targetVersion.equals(snapshot.get("version"))) { + targetSnapshot = snapshot; + break; + } + } + + // 如果目标版本不存在,抛出异常 + if (targetSnapshot == null) { + throw new BusinessException("目标版本不存在"); + } + + // 第四步:将当前状态保存为新版本快照 + // (回滚操作本身也是一次编辑,回滚前的状态需要保留在历史中) + saveSnapshot(exist); + + // 第五步:从目标快照中恢复业务字段值 + exist.setDictTypeCode((String) targetSnapshot.get("dictTypeCode")); + exist.setDictTypeName((String) targetSnapshot.get("dictTypeName")); + exist.setDictGroup((String) targetSnapshot.get("dictGroup")); + if (targetSnapshot.get("status") != null) { + exist.setStatus(((Number) targetSnapshot.get("status")).intValue()); + } + exist.setRemark((String) targetSnapshot.get("remark")); + + // 第六步:版本号递增(回滚也算一次编辑,生成新版本) + exist.setVersion(exist.getVersion() + 1); + + // 第七步:更新数据库,update_time 和 update_by 由 MyBatis-Plus 自动填充 + dictTypeMapper.updateById(exist); + + // 转换为 VO 返回 + return toVO(exist); + } catch (JsonProcessingException e) { + throw new BusinessException("回滚版本失败: " + e.getMessage()); + } + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DisciplineServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DisciplineServiceImpl.java new file mode 100644 index 000000000..498388825 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/DisciplineServiceImpl.java @@ -0,0 +1,69 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XKZYXX; +import com.roomroot.jwgl.mapper.XKZYXXMapper; +import com.roomroot.jwgl.service.DisciplineService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 学科专业管理服务实现类 + *

+ * 实现学科、专业的增加、删除(逻辑删除)、更新、分页查询等功能。 + * 逻辑删除将 停用 字段置为 1(停用=0为启用,停用=1为已停用/删除)。 + *

+ */ +@Service +public class DisciplineServiceImpl implements DisciplineService { + + @Resource + private XKZYXXMapper xkzyxxMapper; + + @Override + @Transactional + public void add(XKZYXX entity) { + entity.setTy(0); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + entity.setTysj(sdf.format(new Date())); + xkzyxxMapper.insert(entity); + } + + @Override + @Transactional + public void disable(String bsh) { + XKZYXX entity = new XKZYXX(); + entity.setBsh(bsh); + entity.setTy(1); + xkzyxxMapper.updateById(entity); + } + + @Override + @Transactional + public void update(XKZYXX entity) { + xkzyxxMapper.updateById(entity); + } + + @Override + public XKZYXX getById(String bsh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XKZYXX::getBsh, bsh) + .eq(XKZYXX::getTy, 0); + return xkzyxxMapper.selectOne(wrapper); + } + + @Override + public PageResult pageList(PageQuery query, XKZYXX xkzyxx) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = xkzyxxMapper.selectPageList(page, xkzyxx); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveServiceImpl.java new file mode 100644 index 000000000..1c5d19927 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ElectiveServiceImpl.java @@ -0,0 +1,435 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.elective.AddClassDTO; +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.mapper.*; +import com.roomroot.jwgl.vo.elective.*; +import com.roomroot.jwgl.mapper.*; +import com.roomroot.jwgl.service.BuildService; +import com.roomroot.jwgl.service.ElectiveService; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.UuidUtil; +import com.roomroot.jwgl.vo.elective.*; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; + +@Service +public class ElectiveServiceImpl implements ElectiveService { + + @Resource + private XYDBMapper xydbMapper; + + @Resource + private XYDQBMapper xydqbMapper; + + @Resource + private SSKCBXYDMapper sskcbxydMapper; + + @Resource + private XYXXMapper xyxxMapper; + + @Resource + private XYKCCJMapper xykccjMapper; + + @Resource + private XYJXQJBMapper xyjxqjbMapper; + + @Resource + private BuildService buildService; + + @Resource + private ClassRoomMapper classRoomMapper; + + @Resource + private ClassSemesterMapper classSemesterMapper; + + @Override + @Transactional + public void add(AddClassDTO dto) { + XYDB entity = dto.getClazz(); + xydbMapper.insert(entity); + // 同时写入学员队期表(学号规则、注册设置) + XYDQB period = dto.getPeriod(); + if (period != null) { + period.setXydbh(entity.getXydbh()); + xydqbMapper.insert(period); + } + } + + @Override + @Transactional + public void disable(String xydbh) { + // 删除关联的学员队期记录 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XYDQB::getXydbh, xydbh); + xydqbMapper.delete(wrapper); + // 物理删除教学班次 + xydbMapper.deleteById(xydbh); + } + + @Override + @Transactional + public void update(XYDB entity) { + xydbMapper.updateById(entity); + } + + @Override + public XYDB getById(String xydbh) { + return xydbMapper.selectById(xydbh); + } + + @Override + public PageResult pageList(PageQuery query, XYDB cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = xydbMapper.selectPageList(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public PageResult pageListWithPeriod(PageQuery query, ElectiveClassVO cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = xydbMapper.selectPageListWithPeriod(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List filterByDateRange(LocalDateTime startTime, LocalDateTime endTime) { + // 将 LocalDateTime 转换为字符串格式(达梦数据库要求) + String startTimeStr = startTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + String endTimeStr = endTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")); + return xydbMapper.selectByDateRange(startTimeStr, endTimeStr); + } + + @Override + public XYDQB getPeriodByXydbh(String xydbh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XYDQB::getXydbh, xydbh); + return xydqbMapper.selectOne(wrapper); + } + + @Override + @Transactional + public void updatePeriod(XYDQB entity) { + xydqbMapper.updateById(entity); + } + + @Override + @Transactional + public void setRegistrationEnabled(String xydbh, Integer enabled) { + XYDQB entity = getPeriodByXydbh(xydbh); + if (entity != null) { + entity.setYxzc(enabled); + xydqbMapper.updateById(entity); + } + } + + @Override + public String generateNextStudentNumber(String xydbh) { + XYDB clazz = xydbMapper.selectById(xydbh); + if (clazz == null || clazz.getRxrq() == null) { + throw new RuntimeException("教学班次不存在或未设置入学日期"); + } + + // 入学日期前缀:yyyyMMdd + String prefix = clazz.getRxrq().format(DateTimeFormatter.ofPattern("yyyyMMdd")); + + // 查询该班次已生成的最大学号 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XYDQB::getXydbh, xydbh); + XYDQB period = xydqbMapper.selectOne(wrapper); + + // 取学号位数,默认3位 + int digits = (period != null && period.getXhws() != null) ? period.getXhws() : 3; + + // 查询该前缀下已有最大流水号(通过学员信息表或其他方式) + // 简单实现:从1开始,按prefix+流水号查询最大数 + String studentNumber = prefix + String.format("%0" + digits + "d", 1); + return studentNumber; + } + + // ========== 关联信息查询 ========== + + @Override + public List getClassCourses(String xydbh, Integer nd) { + return sskcbxydMapper.selectScheduleByXydbh(xydbh, nd); + } + + @Override + public List getClassSchedule(String xydbh, Integer nd) { + return sskcbxydMapper.selectScheduleByXydbh(xydbh, nd); + } + + @Override + public List getClassStudents(String xydbh) { + // 先查学员队期编号 + LambdaQueryWrapper pw = new LambdaQueryWrapper<>(); + pw.eq(XYDQB::getXydbh, xydbh); + XYDQB period = xydqbMapper.selectOne(pw); + if (period == null) return new java.util.ArrayList<>(); + // 再查学员 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XYXX::getXydqbh, period.getBh()); + return xyxxMapper.selectList(wrapper); + } + + @Override + public List getClassGrades(String xydbh, String kmbh) { + // 学员课程成绩 + 学员信息表联查 + return xykccjMapper.selectGradeByXydbh(xydbh, kmbh); + } + + @Override + public List getClassAbsences(String xydbh, Integer nd) { + return xyjxqjbMapper.selectAbsenceByXydbh(xydbh, nd); + } + + // ========== 教学班次学期信息设置 ========== + + @Override + public List listBuildings() { + return buildService.list(null); + } + + @Override + public List listRooms(String jxldh, String py) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jxldh != null && !jxldh.isEmpty()) { + wrapper.eq(JSB::getJxldh, jxldh); + } + if (py != null && !py.isEmpty()) { + wrapper.like(JSB::getPy, py); + } + wrapper.orderByAsc(JSB::getXh); + return classRoomMapper.selectList(wrapper); + } + + @Override + public SemesterInitVO getSemesterInit(String xydbh, String presetKxrq, String presetJsrq) { + XYDB clazz = xydbMapper.selectById(xydbh); + if (clazz == null) { + throw new BusinessException("教学班次不存在:" + xydbh); + } + + LocalDate rx = clazz.getRxrq() != null ? clazz.getRxrq().toLocalDate() : null; + LocalDate by = clazz.getByrq() != null ? clazz.getByrq().toLocalDate() : null; + + // 判断学制是否半年以内 + boolean halfYear = isHalfYear(rx, by); + + // 学期第次:半年以内固定第一学期;半年以上按入学日期智能推断 + Integer xqdc = halfYear ? 1 : inferXqdc(rx, by, LocalDate.now()); + // 人才培养方案总学期数(半年一学期,向上取整) + Integer totalXqdc = calcTotalXqdc(rx, by); + + // 开学/结束日期:半年以内默认入学/毕业日期;半年以上默认窗口预设日期 + LocalDate kxrq; + LocalDate jsrq; + if (halfYear) { + kxrq = rx; + jsrq = by; + } else { + kxrq = parseDate(presetKxrq); + jsrq = parseDate(presetJsrq); + } + + // 第二以上学期:默认预设上学期的专用教室 + String zyjsbh = getPrevRoom(xydbh, xqdc); + String zyjsmc = null; + if (zyjsbh != null && !zyjsbh.isEmpty()) { + JSB room = classRoomMapper.selectById(zyjsbh); + if (room != null) { + zyjsmc = room.getJsmc(); + } + } + + SemesterInitVO vo = new SemesterInitVO(); + vo.setXydbh(xydbh); + vo.setXydmc(clazz.getXydmc()); + vo.setHalfYear(halfYear); + vo.setXqdc(xqdc); + vo.setTotalXqdc(totalXqdc); + vo.setKxrq(kxrq); + vo.setJsrq(jsrq); + vo.setZyjsbh(zyjsbh); + vo.setZyjsmc(zyjsmc); + vo.setJclb(clazz.getJclb()); + return vo; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void addSemester(XYDNDXQJBXXB xydndxqjbxxb) { + if (xydndxqjbxxb == null || xydndxqjbxxb.getXydbh() == null || xydndxqjbxxb.getXydbh().isEmpty()) { + throw new BusinessException("班次编号不能为空"); + } + XYDB clazz = xydbMapper.selectById(xydndxqjbxxb.getXydbh()); + if (clazz == null) { + throw new BusinessException("教学班次不存在:" + xydndxqjbxxb.getXydbh()); + } + + LocalDate rx = clazz.getRxrq() != null ? clazz.getRxrq().toLocalDate() : null; + LocalDate by = clazz.getByrq() != null ? clazz.getByrq().toLocalDate() : null; + boolean halfYear = isHalfYear(rx, by); + + // 学期第次:半年以内只能第一学期;半年以上为空时智能推断 + Integer xqdc = xydndxqjbxxb.getXqdc(); + if (halfYear) { + if (xqdc != null && xqdc != 1) { + throw new BusinessException("学制半年以内的班次,学期第次只能设置为第一学期"); + } + xqdc = 1; + } else if (xqdc == null) { + xqdc = inferXqdc(rx, by, LocalDate.now()); + } + xydndxqjbxxb.setXqdc(xqdc); + + // 开学/结束日期:半年以内为空时默认入学/毕业日期;半年以上不允许为空(取窗口预设值) + if (xydndxqjbxxb.getKxrq() == null) { + if (halfYear) { + xydndxqjbxxb.setKxrq(rx); + } else { + throw new BusinessException("学制半年以上的班次,开学日期不能为空,请使用窗口预设开学日期"); + } + } + if (xydndxqjbxxb.getJsrq() == null) { + if (halfYear) { + xydndxqjbxxb.setJsrq(by); + } else { + throw new BusinessException("学制半年以上的班次,结束日期不能为空,请使用窗口预设结束日期"); + } + } + + // 开学/结束日期先后校验 + if (xydndxqjbxxb.getKxrq() != null && xydndxqjbxxb.getJsrq() != null + && xydndxqjbxxb.getKxrq().isAfter(xydndxqjbxxb.getJsrq())) { + throw new BusinessException("开学日期不能晚于结束日期"); + } + + // 年度为空时默认入学日期所在年度 + if (xydndxqjbxxb.getNd() == null) { + xydndxqjbxxb.setNd(rx != null ? rx.getYear() : LocalDate.now().getYear()); + } + + // 第二以上学期:专用教室为空时默认预设上学期的专用教室 + if (xqdc > 1 && (xydndxqjbxxb.getZyjsbh() == null || xydndxqjbxxb.getZyjsbh().isEmpty())) { + xydndxqjbxxb.setZyjsbh(getPrevRoom(xydndxqjbxxb.getXydbh(), xqdc)); + } + + // 重复校验:同一班次+年度+学期第次不允许重复创建 + XYDNDXQJBXXB existing = classSemesterMapper.selectByBcAndNdAndXqdc( + xydndxqjbxxb.getXydbh(), + String.valueOf(xydndxqjbxxb.getNd()), + String.valueOf(xqdc)); + if (existing != null) { + throw new BusinessException("该班次 " + xydndxqjbxxb.getNd() + " 年度第 " + xqdc + " 学期的班次学期信息已存在,请勿重复添加"); + } + + // 默认值填充 + xydndxqjbxxb.setBh(xydndxqjbxxb.getBh() == null ? UuidUtil.getUUID() : xydndxqjbxxb.getBh()); + xydndxqjbxxb.setBdsj(xydndxqjbxxb.getBdsj() == null ? LocalDateTime.now() : xydndxqjbxxb.getBdsj()); + xydndxqjbxxb.setXhbs(xydndxqjbxxb.getXhbs() == null ? 0 : xydndxqjbxxb.getXhbs()); + xydndxqjbxxb.setYsbbh(xydndxqjbxxb.getYsbbh() == null ? 0 : xydndxqjbxxb.getYsbbh()); + xydndxqjbxxb.setJclb(xydndxqjbxxb.getJclb() == null + ? (clazz.getJclb() != null ? clazz.getJclb() : "双节次") : xydndxqjbxxb.getJclb()); + xydndxqjbxxb.setKfjypk(xydndxqjbxxb.getKfjypk() == null ? 0 : xydndxqjbxxb.getKfjypk()); + xydndxqjbxxb.setXtms(xydndxqjbxxb.getXtms() == null ? "" : xydndxqjbxxb.getXtms()); + xydndxqjbxxb.setDelFlag(0); + + classSemesterMapper.insert(xydndxqjbxxb); + } + + /** + * 判断学制是否半年以内(入学日期到毕业日期不超过 183 天) + */ + private boolean isHalfYear(LocalDate rx, LocalDate by) { + if (rx == null || by == null) { + return false; + } + return ChronoUnit.DAYS.between(rx, by) <= 183; + } + + /** + * 计算人才培养方案总学期数(半年一学期,向上取整;入学日期缺失时按 1 处理) + */ + private Integer calcTotalXqdc(LocalDate rx, LocalDate by) { + if (rx == null || by == null || !by.isAfter(rx)) { + return 1; + } + long totalMonths = ChronoUnit.MONTHS.between(rx, by); + return Math.max(1, (int) ((totalMonths + 5) / 6)); + } + + /** + * 根据班次入学日期智能推断指定日期所在的学期第次(每半年为一个学期) + *

+ * 半年以内的班次返回 1;半年以上的班次按 入学日期 到 指定日期 经过的月份 + * 每 6 个月为 1 个学期递增,且不超过人才培养方案总学期数。 + *

+ */ + private Integer inferXqdc(LocalDate rx, LocalDate by, LocalDate target) { + if (rx == null) { + return 1; + } + if (target.isBefore(rx)) { + return 1; + } + // 半年以内固定第一学期 + if (by != null && isHalfYear(rx, by)) { + return 1; + } + long months = ChronoUnit.MONTHS.between(rx, target); + int xqdc = (int) (months / 6) + 1; + // 不超过总学期数 + Integer total = calcTotalXqdc(rx, by); + return Math.min(xqdc, total); + } + + /** + * 获取某班次指定学期第次的上一学期专用教室(用于第二以上学期的默认预设) + * + * @param xydbh 班次编号 + * @param xqdc 当前学期第次(<=1 时返回 null) + * @return 上学期专用教室编号,不存在返回 null + */ + private String getPrevRoom(String xydbh, Integer xqdc) { + if (xqdc == null || xqdc <= 1) { + return null; + } + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XYDNDXQJBXXB::getXydbh, xydbh) + .eq(XYDNDXQJBXXB::getXqdc, xqdc - 1) + .eq(XYDNDXQJBXXB::getDelFlag, 0) + .orderByDesc(XYDNDXQJBXXB::getNd) + .last("LIMIT 1"); + XYDNDXQJBXXB prev = classSemesterMapper.selectOne(wrapper); + return prev == null ? null : prev.getZyjsbh(); + } + + /** + * 解析 yyyy-MM-dd 格式日期字符串,为空或格式错误返回 null + */ + private LocalDate parseDate(String date) { + if (date == null || date.trim().isEmpty()) { + return null; + } + try { + return LocalDate.parse(date.trim(), DateTimeFormatter.ofPattern("yyyy-MM-dd")); + } catch (Exception e) { + return null; + } + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/FacultyServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/FacultyServiceImpl.java new file mode 100644 index 000000000..509ee427b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/FacultyServiceImpl.java @@ -0,0 +1,159 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.faculty.AddTeacherDTO; +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.mapper.*; +import com.roomroot.jwgl.mapper.*; +import com.roomroot.jwgl.service.FacultyService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.faculty.TeacherListVO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; + +/** + * 教研室与教员管理服务实现类 + *

+ * 实现教研室和教员的增删改查,以及关联属性、综合评定和课程表查询。 + *

+ */ +@Service +public class FacultyServiceImpl implements FacultyService { + + @Resource + private JYSBMapper jysbMapper; + + @Resource + private JYBMapper jybMapper; + + @Resource + private JYSXMapper jysxMapper; + + @Resource + private NDZHPDJYMapper ndzhpdyMapper; + + @Resource + private JYSCKCBMapper jysckcbMapper; + + // ==================== 教研室管理 ==================== + + @Override + @Transactional + public void addJYSB(JYSB entity) { + if (entity.getTy() == null) { + entity.setTy(false); + } + jysbMapper.insert(entity); + } + + @Override + @Transactional + public void disableJYSB(String jysdh) { + JYSB entity = new JYSB(); + entity.setJysdh(jysdh); + entity.setTy(true); + entity.setTysj(LocalDateTime.now()); + jysbMapper.updateById(entity); + } + + @Override + @Transactional + public void updateJYSB(JYSB entity) { + jysbMapper.updateById(entity); + } + + @Override + public JYSB getJYSBById(String jysdh) { + return jysbMapper.selectById(jysdh); + } + + @Override + public PageResult pageJYSB(PageQuery query, JYSB cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = jysbMapper.selectPageList(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + // ==================== 教员管理 ==================== + + @Override + @Transactional + public void addJYB(AddTeacherDTO dto) { + JYB entity = dto.getTeacher(); + //离职状态默认为0 + entity.setLzzt(0); + jybMapper.insert(entity); + // 使用前端传入的教员属性数据 + JYSX jysx = dto.getAttributes(); + if (jysx != null) { + jysx.setJybh(entity.getJybh()); + jysxMapper.insert(jysx); + } + } + + @Override + @Transactional + public void disableJYB(String jybh) { + JYB entity = new JYB(); + entity.setJybh(jybh); + entity.setLzzt(1); + jybMapper.updateById(entity); + } + + @Override + @Transactional + public void updateJYB(JYB entity) { + jybMapper.updateById(entity); + } + + @Override + public JYB getJYBById(String jybh) { + return jybMapper.selectById(jybh); + } + + @Override + public PageResult pageJYBDetail(PageQuery query, JYB cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = jybMapper.selectTeacherPageList(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + // ==================== 教员属性管理 ==================== + + @Override + public JYSX getJYSXByJYBH(String jybh) { + return jysxMapper.selectById(jybh); + } + + @Override + @Transactional + public void updateJYSX(JYSX entity) { + jysxMapper.updateById(entity); + } + + // ==================== 年度综合评定查询 ==================== + + @Override + public PageResult pageNDZHPDJY(PageQuery query, NDZHPDJY cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = ndzhpdyMapper.selectPageList(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + // ==================== 教员课程表查询 ==================== + + @Override + public PageResult pageJYSCKCB(PageQuery query, JYSCKCB cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = jysckcbMapper.selectPageList(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/KCBServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/KCBServiceImpl.java new file mode 100644 index 000000000..89e61c323 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/KCBServiceImpl.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.service.impl; + +import com.roomroot.jwgl.entity.KCB; +import com.roomroot.jwgl.mapper.KCBMapper; +import com.roomroot.jwgl.service.KCBService; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 课程表服务实现 + */ +@Service +public class KCBServiceImpl implements KCBService { + + @Resource + private KCBMapper kcbMapper; + + @Override + public void add(KCB kcb) { + if (kcb.getCjsj() == null) { + kcb.setCjsj(LocalDateTime.now()); + } + if (kcb.getBdsj() == null) { + kcb.setBdsj(LocalDateTime.now()); + } + kcb.setBh(UuidUtil.getUUID()); + kcbMapper.insert(kcb); + } + + @Override + public void delete(String kbh) { + kcbMapper.deleteById(kbh); + } + + @Override + public void batchDelete(List kbhList) { + kcbMapper.deleteBatchIds(kbhList); + } + + @Override + public void update(KCB kcb) { + kcbMapper.updateById(kcb); + } + + @Override + public KCB getByKbh(String kbh) { + return kcbMapper.selectById(kbh); + } + + @Override + public List list() { + return kcbMapper.selectList(null); + } + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/LeaveServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/LeaveServiceImpl.java new file mode 100644 index 000000000..fbe3f19d2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/LeaveServiceImpl.java @@ -0,0 +1,205 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.roomroot.jwgl.dto.LeaveDTO; +import com.roomroot.jwgl.dto.LeaveQueryDTO; +import com.roomroot.jwgl.entity.QJKTB; +import com.roomroot.jwgl.entity.QJXYB; +import com.roomroot.jwgl.entity.XYJXQJB; +import com.roomroot.jwgl.entity.XYQJSPB; +import com.roomroot.jwgl.mapper.QJKTBMapper; +import com.roomroot.jwgl.mapper.QJXYBMapper; +import com.roomroot.jwgl.mapper.XYJXQJBMapper; +import com.roomroot.jwgl.mapper.XYQJSPBMapper; +import com.roomroot.jwgl.service.LeaveService; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 请假服务实现 + */ +@Service +public class LeaveServiceImpl implements LeaveService { + + @Resource + private XYJXQJBMapper xyjxqjbMapper; + + @Resource + private QJKTBMapper qjktbMapper; + + @Resource + private QJXYBMapper qjxybMapper; + + @Resource + private XYQJSPBMapper xyqjspbMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public void add(LeaveDTO dto) { + XYJXQJB leave = dto.getLeave(); + if (leave.getCjsj() == null) { + leave.setCjsj(LocalDateTime.now()); + } + + // 设置课堂数和学员人数 + if (dto.getSsktBhList() != null) { + leave.setKts(dto.getSsktBhList().size()); + } + if (dto.getXybh() != null) { + leave.setXyrs(1); + } + + leave.setBh(UuidUtil.getUUID()); + leave.setZt("待审批"); + // 插入主表 + xyjxqjbMapper.insert(leave); + String qjbh = leave.getBh(); + + // 批量插入请假课堂表 + if (dto.getSsktBhList() != null) { + for (String ssktBh : dto.getSsktBhList()) { + QJKTB qjktb = new QJKTB(); + qjktb.setQjbh(qjbh); + qjktb.setSsktbh(ssktBh); + qjktb.setBh(UuidUtil.getUUID()); + qjktbMapper.insert(qjktb); + } + } + + // 插入请假学员表 + if (dto.getXybh() != null) { + QJXYB qjxyb = new QJXYB(); + qjxyb.setQjbh(qjbh); + qjxyb.setXybh(dto.getXybh()); + qjxyb.setBh(UuidUtil.getUUID()); + qjxybMapper.insert(qjxyb); + } + } + + @Override + public boolean update(XYJXQJB xyjxqjb) { + // 检查审批表是否有内容 + if (hasApproval(xyjxqjb.getBh())) { + return false; + } + xyjxqjbMapper.updateById(xyjxqjb); + return true; + } + + @Override + @Transactional(rollbackFor = Exception.class) + public boolean delete(String bh) { + // 检查审批表是否有内容 + if (hasApproval(bh)) { + return false; + } + // 删除主表 + xyjxqjbMapper.deleteById(bh); + // 删除请假课堂表 + LambdaQueryWrapper qjktbWrapper = new LambdaQueryWrapper<>(); + qjktbWrapper.eq(QJKTB::getQjbh, bh); + qjktbMapper.delete(qjktbWrapper); + // 删除请假学员表 + LambdaQueryWrapper qjxybWrapper = new LambdaQueryWrapper<>(); + qjxybWrapper.eq(QJXYB::getQjbh, bh); + qjxybMapper.delete(qjxybWrapper); + // 删除审批表 + LambdaQueryWrapper xyqjspbWrapper = new LambdaQueryWrapper<>(); + xyqjspbWrapper.eq(XYQJSPB::getQjbh, bh); + xyqjspbMapper.delete(xyqjspbWrapper); + return true; + } + + /** + * 检查请假编号是否存在审批记录 + */ + private boolean hasApproval(String qjbh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XYQJSPB::getQjbh, qjbh); + return xyqjspbMapper.selectCount(wrapper) > 0; + } + + @Override + public XYJXQJB getByBh(String bh) { + return xyjxqjbMapper.selectById(bh); + } + + @Override + public List list() { + return xyjxqjbMapper.selectList(null); + } + + @Override + public List list(LeaveQueryDTO queryDTO) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + // 精确匹配 + wrapper.eq(queryDTO.getBh() != null, XYJXQJB::getBh, queryDTO.getBh()); + wrapper.eq(queryDTO.getMc() != null, XYJXQJB::getMc, queryDTO.getMc()); + wrapper.eq(queryDTO.getZt() != null, XYJXQJB::getZt, queryDTO.getZt()); + wrapper.eq(queryDTO.getXydbh() != null, XYJXQJB::getXydbh, queryDTO.getXydbh()); + // 起始时间范围查询 + wrapper.ge(queryDTO.getQssjStart() != null, XYJXQJB::getQssj, queryDTO.getQssjStart()); + wrapper.le(queryDTO.getQssjEnd() != null, XYJXQJB::getQssj, queryDTO.getQssjEnd()); + // 结束时间范围查询 + wrapper.ge(queryDTO.getJssjStart() != null, XYJXQJB::getJssj, queryDTO.getJssjStart()); + wrapper.le(queryDTO.getJssjEnd() != null, XYJXQJB::getJssj, queryDTO.getJssjEnd()); + // 创建时间范围查询 + wrapper.ge(queryDTO.getCjsjStart() != null, XYJXQJB::getCjsj, queryDTO.getCjsjStart()); + wrapper.le(queryDTO.getCjsjEnd() != null, XYJXQJB::getCjsj, queryDTO.getCjsjEnd()); + return xyjxqjbMapper.selectList(wrapper); + } + + @Override + public List listClassroomBhsByQjbh(String qjbh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(QJKTB::getQjbh, qjbh); + List list = qjktbMapper.selectList(wrapper); + return list.stream().map(QJKTB::getSsktbh).collect(Collectors.toList()); + } + + @Override + public List listStudentBhsByQjbh(String qjbh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(QJXYB::getQjbh, qjbh); + List list = qjxybMapper.selectList(wrapper); + return list.stream().map(QJXYB::getXybh).collect(Collectors.toList()); + } + + // ===== 审批表 CRUD ===== + + @Override + public void addApproval(XYQJSPB xyqjspb) { + if (xyqjspb.getSpsj() == null) { + xyqjspb.setSpsj(LocalDateTime.now()); + } + xyqjspbMapper.insert(xyqjspb); + } + + @Override + public void deleteApproval(String bh) { + xyqjspbMapper.deleteById(bh); + } + + @Override + public void updateApproval(XYQJSPB xyqjspb) { + xyqjspbMapper.updateById(xyqjspb); + } + + @Override + public XYQJSPB getApprovalByBh(String bh) { + return xyqjspbMapper.selectById(bh); + } + + @Override + public List listApprovalsByQjbh(String qjbh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XYQJSPB::getQjbh, qjbh); + return xyqjspbMapper.selectList(wrapper); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/LogServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/LogServiceImpl.java new file mode 100644 index 000000000..5c2fcd2a2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/LogServiceImpl.java @@ -0,0 +1,766 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.*; +import com.roomroot.jwgl.mapper.*; +import com.roomroot.jwgl.mapper.*; +import com.roomroot.jwgl.service.LogService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.vo.log.TeacherHoursVO; +import com.roomroot.jwgl.vo.log.TeachingLogListVO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import lombok.extern.slf4j.Slf4j; + +/** + * 教学日志管理服务实现 + */ +@Slf4j +@Service +public class LogServiceImpl implements LogService { + + @Resource + private BCKTJXRZMapper bcktjxrzMapper; + @Resource + private SSKCB_RZMapper sskcbRzMapper; + @Resource + private BCKTJXRZSKJYMapper bcktjxrzskyMapper; + @Resource + private JXRZJYKSBZMapper jxrzjyksbzMapper; + @Resource + private JXRZKSBZSPBMapper jxrzksbzspbMapper; + @Resource + private JYKSBZMapper jyksbzMapper; + @Resource + private KSFBZMapper ksfbzMapper; + @Resource + private JSZWKSFBZMapper jszwksfbzMapper; + @Resource + private JYNZKSFMapper jynzksfMapper; + @Resource + private NZKSFMapper nzksfMapper; + @Resource + private KSBZXMMapper ksbzxmMapper; + @Resource + private SSKCBMapper sskcbMapper; + @Resource + private JXSSJHTJBMapper jxssjhtjbMapper; + @Resource + private KSHBZMapper kshbzMapper; + + @Resource + private KSXSFAMapper ksxsfaMapper; + + @Resource + private JZGZLMapper jzgzlMapper; + @Resource + private JXRZXYXXQKMapper jxrzxyxxqkMapper; + + @Resource + private TeacherHoursMapper teacherHoursMapper; + + // ==================== 教学日志核心 ==================== + + @Override + @Transactional + public void addTeachingLog(BCKTJXRZ entity) { + if (entity.getZt() == null) entity.setZt("0"); + bcktjxrzMapper.insert(entity); + } + + @Override + @Transactional + public void updateTeachingLog(BCKTJXRZ entity) { + bcktjxrzMapper.updateById(entity); + } + + @Override + public BCKTJXRZ getTeachingLogById(String bh) { + return bcktjxrzMapper.selectById(bh); + } + + @Override + public PageResult pageTeachingLog(PageQuery query, BCKTJXRZ cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = bcktjxrzMapper.selectPageList(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + @Override + public PageResult pageTeachingLogDetail(PageQuery query, TeachingLogListVO cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = sskcbRzMapper.selectTeachingLogList(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 教学日志(实施_课程表_日志 36字段表) ==================== + + @Override + @Transactional + public void addLog(SSKCB_RZ entity) { + fillNotNullDefaults(entity); + sskcbRzMapper.insert(entity); + } + + /** + * 为 NOT NULL 字段填充默认值 + */ + private void fillNotNullDefaults(SSKCB_RZ entity) { + if (entity.getTybh() == null) entity.setTybh(java.util.UUID.randomUUID().toString()); + if (entity.getJybh() == null) entity.setJybh(java.util.UUID.randomUUID().toString().substring(0, 8)); + if (entity.getZczt() == null) entity.setZczt(0); + if (entity.getSdrs() == null) entity.setSdrs(0); + if (entity.getJcks() == null) entity.setJcks(0); + if (entity.getAtj() == null) entity.setAtj(0); + if (entity.getRq() == null) entity.setRq(LocalDateTime.now()); + if (entity.getQsjc() == null) entity.setQsjc(0); + if (entity.getJsjc() == null) entity.setJsjc(0); + if (entity.getZt() == null) entity.setZt("系统创建"); + if (entity.getJxrzcjfs() == null) entity.setJxrzcjfs("手动"); + if (entity.getKckmbh() == null) entity.setKckmbh(java.util.UUID.randomUUID().toString().substring(0, 8)); + if (entity.getRwlb() == null) entity.setRwlb("其他"); + if (entity.getYdrs() == null) entity.setYdrs(0); + if (entity.getSksjbd() == null) entity.setSksjbd(0); + if (entity.getSkddbd() == null) entity.setSkddbd(0); + if (entity.getKcbd() == null) entity.setKcbd(0); + if (entity.getSkktbd() == null) entity.setSkktbd(0); + if (entity.getRwlbbd() == null) entity.setRwlbbd(0); + if (entity.getPxqbbd() == null) entity.setPxqbbd(0); + if (entity.getSkfsbd() == null) entity.setSkfsbd(0); + if (entity.getFzyzdjybd() == null) entity.setFzyzdjybd(0); + if (entity.getCjsj() == null) entity.setCjsj(LocalDateTime.now()); + } + + @Override + @Transactional + public void rejectLog(String bh, String thyj) { + SSKCB_RZ entity = new SSKCB_RZ(); + entity.setBh(bh); + entity.setZt("已退回"); + entity.setThyj(thyj); + entity.setXgsj(LocalDateTime.now()); + sskcbRzMapper.updateById(entity); + } + + @Override + @Transactional + public SSKCB_RZ autoCreateLog(String tybh) { + SSKCB schedule = sskcbMapper.selectById(tybh); + SSKCB_RZ log = new SSKCB_RZ(); + log.setTybh(tybh); + if (schedule != null) { + log.setRq(schedule.getRq()); + log.setQsjc(schedule.getJc()); + log.setJsjc(schedule.getJc()); + log.setJsfs(schedule.getJxff()); + log.setSkkt(schedule.getJxnr()); + } + log.setZt("系统创建"); + log.setCjsj(LocalDateTime.now()); + log.setJxrzcjfs("自动创建"); + sskcbRzMapper.insert(log); + return log; + } + + @Override + @Transactional + public void importLog(java.util.List list) { + for (SSKCB_RZ item : list) { + if (item.getZt() == null) item.setZt("系统创建"); + if (item.getCjsj() == null) item.setCjsj(LocalDateTime.now()); + sskcbRzMapper.insert(item); + } + } + + @Override + @Transactional + public void importLogFromExcel(org.springframework.web.multipart.MultipartFile file) { + try { + org.apache.poi.ss.usermodel.Workbook wb = org.apache.poi.ss.usermodel.WorkbookFactory.create(file.getInputStream()); + try { + org.apache.poi.ss.usermodel.Sheet sheet = wb.getSheetAt(0); + int lastRow = sheet.getLastRowNum(); + for (int i = 1; i <= lastRow; i++) { + org.apache.poi.ss.usermodel.Row row = sheet.getRow(i); + if (row == null) continue; + SSKCB_RZ entity = new SSKCB_RZ(); + entity.setRq(parseDate(getCellValue(row.getCell(3)))); + entity.setQsjc(toInt(getCellValue(row.getCell(4)))); + entity.setJsjc(toInt(getCellValue(row.getCell(5)))); + entity.setAtj("是".equals(getCellValue(row.getCell(6))) ? 1 : 0); + entity.setSkdd(getCellValue(row.getCell(7))); + entity.setKckmbh(getCellValue(row.getCell(8))); + entity.setSkkt(getCellValue(row.getCell(10))); + entity.setRwlb(getCellValue(row.getCell(11))); + entity.setPxqbxx(getCellValue(row.getCell(12))); + entity.setYdrs(toInt(getCellValue(row.getCell(13)))); + entity.setSdrs(toInt(getCellValue(row.getCell(14)))); + entity.setJsfs(getCellValue(row.getCell(15))); + fillNotNullDefaults(entity); + sskcbRzMapper.insert(entity); + } + } finally { + wb.close(); + } + } catch (Exception e) { + log.error("导入教学日志失败", e); + throw new RuntimeException("导入教学日志失败:" + e.getMessage()); + } + } + + private String getCellValue(org.apache.poi.ss.usermodel.Cell cell) { + if (cell == null) return ""; + switch (cell.getCellType()) { + case STRING: + return cell.getStringCellValue().trim(); + case NUMERIC: + if (org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) { + return new java.text.SimpleDateFormat("yyyy-MM-dd").format(cell.getDateCellValue()); + } + double val = cell.getNumericCellValue(); + if (val == Math.floor(val) && !Double.isInfinite(val)) { + return String.valueOf((long) val); + } + return String.valueOf(val); + default: + return ""; + } + } + + private LocalDateTime parseDate(String str) { + if (str == null || str.isEmpty()) return null; + try { + return LocalDateTime.parse(str + "T00:00:00"); + } catch (Exception e) { + } + try { + return java.time.LocalDate.parse(str).atStartOfDay(); + } catch (Exception e) { + } + return null; + } + + private Integer toInt(String str) { + if (str == null || str.isEmpty()) return null; + try { + return Integer.parseInt(str.trim()); + } catch (Exception e) { + return null; + } + } + + @Override + @Transactional + public int batchReportLogs(java.util.List bhList) { + if (bhList == null || bhList.isEmpty()) return 0; + return sskcbRzMapper.update(null, new LambdaUpdateWrapper() + .in(SSKCB_RZ::getBh, bhList) + .and(w -> w.isNull(SSKCB_RZ::getZt).or().notIn(SSKCB_RZ::getZt, "已上报", "已审核")) + .set(SSKCB_RZ::getZt, "已上报") + .set(SSKCB_RZ::getXgsj, LocalDateTime.now())); + } + + @Override + @Transactional + public int batchAuditLogs(java.util.List bhList) { + if (bhList == null || bhList.isEmpty()) return 0; + return sskcbRzMapper.update(null, new LambdaUpdateWrapper() + .in(SSKCB_RZ::getBh, bhList) + .eq(SSKCB_RZ::getZt, "已上报") + .set(SSKCB_RZ::getZt, "已审核") + .set(SSKCB_RZ::getXgsj, LocalDateTime.now())); + } + + @Override + public void exportLogExcel(TeachingLogListVO cond, jakarta.servlet.http.HttpServletResponse response) { + List list = sskcbRzMapper.selectTeachingLogList( + new Page<>(1, Integer.MAX_VALUE), cond).getRecords(); + try (org.apache.poi.xssf.usermodel.XSSFWorkbook wb = new org.apache.poi.xssf.usermodel.XSSFWorkbook()) { + org.apache.poi.ss.usermodel.Sheet sheet = wb.createSheet("教学日志"); + String[] headers = {"序号", "主讲教员标识号", "主讲教员姓名", "授课日期", "起始节次", + "结束节次", "按天记", "授课地点", "课标识号", "课名称", "授课课题", + "任务类别", "培训期班", "应到人数", "实到人数", "授课方式", "分组指导教员"}; + org.apache.poi.ss.usermodel.Row headerRow = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + headerRow.createCell(i).setCellValue(headers[i]); + } + int rowIdx = 1; + for (TeachingLogListVO v : list) { + org.apache.poi.ss.usermodel.Row row = sheet.createRow(rowIdx++); + row.createCell(0).setCellValue(rowIdx - 1); // 序号 + row.createCell(1).setCellValue(v.getJybh() != null ? v.getJybh() : ""); + row.createCell(2).setCellValue(v.getJyxm() != null ? v.getJyxm() : ""); + row.createCell(3).setCellValue(v.getRq() != null ? v.getRq().toLocalDate().toString() : ""); + row.createCell(4).setCellValue(v.getQsjc() != null ? v.getQsjc() : 0); + row.createCell(5).setCellValue(v.getJsjc() != null ? v.getJsjc() : 0); + row.createCell(6).setCellValue(v.getAtj() != null && v.getAtj() == 1 ? "是" : "否"); + row.createCell(7).setCellValue(v.getSkdd() != null ? v.getSkdd() : ""); + row.createCell(8).setCellValue(v.getKckmbh() != null ? v.getKckmbh() : ""); + row.createCell(9).setCellValue(v.getKcmc() != null ? v.getKcmc() : ""); + row.createCell(10).setCellValue(v.getSkkt() != null ? v.getSkkt() : ""); + row.createCell(11).setCellValue(v.getRwlb() != null ? v.getRwlb() : ""); + row.createCell(12).setCellValue(v.getPxqbxx() != null ? v.getPxqbxx() : ""); + row.createCell(13).setCellValue(v.getYdrs() != null ? v.getYdrs() : 0); + row.createCell(14).setCellValue(v.getSdrs() != null ? v.getSdrs() : 0); + row.createCell(15).setCellValue(v.getJsfs() != null ? v.getJsfs() : ""); + row.createCell(16).setCellValue(""); // 分组指导教员(需扩展关联查询) + } + for (int i = 0; i < headers.length; i++) sheet.autoSizeColumn(i); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment; filename=teaching_log.xlsx"); + wb.write(response.getOutputStream()); + response.getOutputStream().flush(); + } catch (Exception e) { + throw new RuntimeException("导出失败", e); + } + } + + @Override + @Transactional + public void submitApproval(String bh, String targetStatus) { + BCKTJXRZ entity = new BCKTJXRZ(); + entity.setBh(bh); + entity.setZt(targetStatus); + bcktjxrzMapper.updateById(entity); + } + + @Override + @Transactional + public void approve(String bh, String feedback) { + BCKTJXRZ entity = new BCKTJXRZ(); + entity.setBh(bh); + entity.setZt("5"); // 已审核通过 + entity.setJwbmfkyj(feedback); + bcktjxrzMapper.updateById(entity); + } + + @Override + @Transactional + public void reject(String bh, String feedback) { + BCKTJXRZ entity = new BCKTJXRZ(); + entity.setBh(bh); + entity.setZt("1"); // 退回至填写中 + entity.setJwbmfkyj(feedback); + bcktjxrzMapper.updateById(entity); + } + + @Override + @Transactional + public BCKTJXRZ autoCreateFromSchedule(String sskcbh) { + SSKCB schedule = sskcbMapper.selectById(sskcbh); + if (schedule == null) return null; + BCKTJXRZ log = new BCKTJXRZ(); + log.setSskcbh(schedule.getSskcbh()); + log.setSskcbbh(schedule.getBh()); + log.setRq(schedule.getRq()); + log.setJc(schedule.getJc()); + log.setJxff(schedule.getJxff()); + log.setNd(schedule.getNd()); + log.setZt("0"); // 未填写 + bcktjxrzMapper.insert(log); + return log; + } + + @Override + public PageResult checkLogStatusBySchedule(PageQuery query, String jybh, Integer nd) { + // 查询某教员的课表及对应教学日志状态 + Map result = new HashMap<>(); + // 此处实现对照查询逻辑 + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jybh != null) wrapper.eq(SSKCB::getSskcbh, jybh); + if (nd != null) wrapper.eq(SSKCB::getNd, nd); + Page schedulePage = sskcbMapper.selectPage(page, wrapper); + return new PageResult<>(schedulePage.getRecords(), schedulePage.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 教学日志授课教员 ==================== + + @Override + @Transactional + public void addLogTeacher(BCKTJXRZSKJY entity) { + bcktjxrzskyMapper.insert(entity); + } + + @Override + public PageResult pageLogTeacher(PageQuery query, BCKTJXRZSKJY cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getBcktjxrzbh() != null) wrapper.eq(BCKTJXRZSKJY::getBcktjxrzbh, cond.getBcktjxrzbh()); + if (cond.getFzjybh() != null) wrapper.eq(BCKTJXRZSKJY::getFzjybh, cond.getFzjybh()); + Page result = bcktjxrzskyMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 教学日志学员学习情况 ==================== + + @Override + @Transactional + public void addStudentLearningStatus(JXRZXYXXQK entity) { + jxrzxyxxqkMapper.insert(entity); + } + + @Override + public PageResult pageStudentLearningStatus(PageQuery query, String jxrzbh) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jxrzbh != null) wrapper.eq(JXRZXYXXQK::getJxrzbh, jxrzbh); + Page result = jxrzxyxxqkMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 课时核算标准 ==================== + + @Override + @Transactional + public void addCalculationStandard(KSHBZ entity) { + kshbzMapper.insert(entity); + } + + @Override + @Transactional + public void updateCalculationStandard(KSHBZ entity) { + kshbzMapper.updateById(entity); + } + + @Override + public KSHBZ getCalculationStandardById(String bh) { + return kshbzMapper.selectById(bh); + } + + @Override + public PageResult pageCalculationStandard(PageQuery query, KSHBZ cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getMc() != null) wrapper.like(KSHBZ::getMc, cond.getMc()); + Page result = kshbzMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 课时系数方案 ==================== + + @Override + @Transactional + public void addCoefficientPlan(KSXSFA entity) { + ksxsfaMapper.insert(entity); + } + + @Override + @Transactional + public void updateCoefficientPlan(KSXSFA entity) { + ksxsfaMapper.updateById(entity); + } + + @Override + public KSXSFA getCoefficientPlanById(String bh) { + return ksxsfaMapper.selectById(bh); + } + + @Override + public PageResult pageCoefficientPlan(PageQuery query, KSXSFA cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond != null && cond.getMc() != null && !cond.getMc().isEmpty()) { + wrapper.like(KSXSFA::getMc, cond.getMc()); + } + wrapper.orderByDesc(KSXSFA::getCjsj); + Page result = ksxsfaMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 课时费标准 ==================== + + @Override + @Transactional + public void addFeeStandard(KSFBZ entity) { + entity.setCjsj(LocalDateTime.now()); + ksfbzMapper.insert(entity); + } + + @Override + @Transactional + public void updateFeeStandard(KSFBZ entity) { + entity.setXgsj(LocalDateTime.now()); + ksfbzMapper.updateById(entity); + } + + @Override + public KSFBZ getFeeStandardById(String bh) { + return ksfbzMapper.selectById(bh); + } + + @Override + public PageResult pageFeeStandard(PageQuery query, KSFBZ cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getMc() != null) wrapper.like(KSFBZ::getMc, cond.getMc()); + Page result = ksfbzMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 技术职务课时费标准 ==================== + + @Override + @Transactional + public void addTechPosFeeStandard(JSZWKSFBZ entity) { + jszwksfbzMapper.insert(entity); + } + + @Override + @Transactional + public void updateTechPosFeeStandard(JSZWKSFBZ entity) { + jszwksfbzMapper.updateById(entity); + } + + @Override + public JSZWKSFBZ getTechPosFeeStandardById(String bh) { + return jszwksfbzMapper.selectById(bh); + } + + @Override + public PageResult pageTechPosFeeStandard(PageQuery query, JSZWKSFBZ cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getJszwbh() != null) wrapper.eq(JSZWKSFBZ::getJszwbh, cond.getJszwbh()); + Page result = jszwksfbzMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + + + // ==================== 课时补助项目 ==================== + + @Override + @Transactional + public void addSubsidyProject(KSBZXM entity) { + ksbzxmMapper.insert(entity); + } + + @Override + @Transactional + public void updateSubsidyProject(KSBZXM entity) { + ksbzxmMapper.updateById(entity); + } + + @Override + public KSBZXM getSubsidyProjectById(String bh) { + return ksbzxmMapper.selectById(bh); + } + + @Override + public PageResult pageSubsidyProject(PageQuery query, KSBZXM cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getMc() != null) wrapper.like(KSBZXM::getMc, cond.getMc()); + Page result = ksbzxmMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 教学日志课时补助审批表(汇总) ==================== + + @Override + @Transactional + public void addSubsidyApproval(JXRZKSBZSPB entity) { + jxrzksbzspbMapper.insert(entity); + } + + @Override + @Transactional + public void updateSubsidyApproval(JXRZKSBZSPB entity) { + jxrzksbzspbMapper.updateById(entity); + } + + @Override + public JXRZKSBZSPB getSubsidyApprovalById(String bh) { + return jxrzksbzspbMapper.selectById(bh); + } + + @Override + public PageResult pageSubsidyApproval(PageQuery query, JXRZKSBZSPB cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = jxrzksbzspbMapper.selectPageList(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + @Override + @Transactional + public void calculateSummary(String bh) { + JXRZKSBZSPB summary = jxrzksbzspbMapper.selectById(bh); + if (summary == null) return; + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(JXRZJYKSBZ::getJxrzksbzspbh, bh); + List details = jxrzjyksbzMapper.selectList(wrapper); + float totalKsl = 0, totalYzksl = 0; + BigDecimal totalAmount = BigDecimal.ZERO; + for (JXRZJYKSBZ d : details) { + if (d.getKsl() != null) totalKsl += d.getKsl(); + if (d.getSfyz() != null && d.getSfyz() == 1 && d.getKsl() != null) totalYzksl += d.getKsl(); + if (d.getKsbzje() != null) totalAmount = totalAmount.add(d.getKsbzje()); + } + summary.setZksl(totalKsl); + summary.setYzksl(totalYzksl); + summary.setZbzje(totalAmount.floatValue()); + jxrzksbzspbMapper.updateById(summary); + } + + // ==================== 教学日志教员课时补助明细 ==================== + + @Override + public PageResult pageSubsidyDetail(PageQuery query, JXRZJYKSBZ cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getJxrzksbzspbh() != null) wrapper.eq(JXRZJYKSBZ::getJxrzksbzspbh, cond.getJxrzksbzspbh()); + if (cond.getJybh() != null) wrapper.eq(JXRZJYKSBZ::getJybh, cond.getJybh()); + Page result = jxrzjyksbzMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 教员课时补助 ==================== + + @Override + @Transactional + public void addTeacherSubsidy(JYKSBZ entity) { + jyksbzMapper.insert(entity); + } + + @Override + public PageResult pageTeacherSubsidy(PageQuery query, JYKSBZ cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getJybh() != null) wrapper.eq(JYKSBZ::getJybh, cond.getJybh()); + Page result = jyksbzMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 年终课时费 ==================== + + @Override + @Transactional + public void addYearEndFee(NZKSF entity) { + nzksfMapper.insert(entity); + } + + @Override + @Transactional + public void updateYearEndFee(NZKSF entity) { + nzksfMapper.updateById(entity); + } + + @Override + public NZKSF getYearEndFeeById(String bh) { + return nzksfMapper.selectById(bh); + } + + @Override + public PageResult pageYearEndFee(PageQuery query, NZKSF cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getMc() != null) wrapper.like(NZKSF::getMc, cond.getMc()); + Page result = nzksfMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 教员年终课时费 ==================== + + @Override + public PageResult pageTeacherYearEndFee(PageQuery query, JYNZKSF cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getJybh() != null) wrapper.eq(JYNZKSF::getJybh, cond.getJybh()); + if (cond.getNzksfbh() != null) wrapper.eq(JYNZKSF::getNzksfbh, cond.getNzksfbh()); + Page result = jynzksfMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 教师工作量 ==================== + + @Override + @Transactional + public void addWorkload(JZGZL entity) { + entity.setCjsj(LocalDateTime.now()); + entity.setXgsj(LocalDateTime.now()); + jzgzlMapper.insert(entity); + } + + @Override + @Transactional + public void updateWorkload(JZGZL entity) { + entity.setXgsj(LocalDateTime.now()); + jzgzlMapper.updateById(entity); + } + + @Override + public JZGZL getWorkloadById(String bh) { + return jzgzlMapper.selectById(bh); + } + + @Override + public PageResult pageWorkload(PageQuery query, JZGZL cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getJybh() != null) wrapper.eq(JZGZL::getJybh, cond.getJybh()); + if (cond.getNd() != null) wrapper.eq(JZGZL::getNd, cond.getNd()); + if (cond.getXq() != null) wrapper.eq(JZGZL::getXq, cond.getXq()); + Page result = jzgzlMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + @Override + @Transactional + public void calcCourseWorkload(String jybh, Integer nd, String xq) { + // 统计课时量作为工作量 + LambdaQueryWrapper tw = new LambdaQueryWrapper<>(); + tw.eq(BCKTJXRZSKJY::getFzjybh, jybh).eq(BCKTJXRZSKJY::getNd, nd); + long count = bcktjxrzskyMapper.selectCount(tw); + JZGZL workload = new JZGZL(); + workload.setJybh(jybh); + workload.setNd(nd); + workload.setXq(xq); + workload.setKcjxgzl((float) count); + workload.setZgzl((float) count); + jzgzlMapper.insert(workload); + } + + @Override + public PageResult compareWorkload(PageQuery query, Integer nd, String xq) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (nd != null) wrapper.eq(JZGZL::getNd, nd); + if (xq != null) wrapper.eq(JZGZL::getXq, xq); + wrapper.orderByDesc(JZGZL::getZgzl); + Page result = jzgzlMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 教学实施计划统计 ==================== + + @Override + public PageResult pageTeachingStats(PageQuery query, JXSSJHTJB cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (cond.getRq() != null) wrapper.ge(JXSSJHTJB::getRq, cond.getRq()); + wrapper.orderByDesc(JXSSJHTJB::getRq); + Page result = jxssjhtjbMapper.selectPage(page, wrapper); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } + + // ==================== 教员课时查询管理 ==================== + + @Override + public PageResult pageTeacherHours(PageQuery query, TeacherHoursVO cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = teacherHoursMapper.selectTeacherHoursPage(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), query.getPageNum(), query.getPageSize()); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/NoticeAnnouncementServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/NoticeAnnouncementServiceImpl.java new file mode 100644 index 000000000..7efce87f0 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/NoticeAnnouncementServiceImpl.java @@ -0,0 +1,597 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementCreateDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementIdDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPriorityDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPublishDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementQueryDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientInsertDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientQueryDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementUpdateDTO; +import com.roomroot.jwgl.dto.noticeannouncement.UserNoticeAnnouncementQueryDTO; +import com.roomroot.jwgl.mapper.NoticeAnnouncementMapper; +import com.roomroot.jwgl.service.NoticeAnnouncementService; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.PageResultUtil; +import com.roomroot.jwgl.utils.UuidUtil; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementDetailVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementPublishResultVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementRecipientVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementStatisticsVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementVO; +import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementDetailVO; +import com.roomroot.jwgl.vo.noticeannouncement.UserNoticeAnnouncementVO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST; +import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.RECIPIENT_INSERT_BATCH_SIZE; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.SCOPE_ALL; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.SCOPE_ALL_TEACHERS; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_DOWN; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_DRAFT; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_PUBLISHED; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.completeDetail; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.completeStatistics; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.createRecipientRecords; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeCreate; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeNoticeId; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizePriority; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizePublish; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeQuery; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeRecipientAccountIds; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeRecipientQuery; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeUpdate; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.normalizeUserQuery; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireCurrentAccountId; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireCurrentSessionId; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireDeletableStatus; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireDownableStatus; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireEditableStatus; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requireExistingStatus; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.requirePublishableStatus; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.resolveScopeName; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.startOfDay; +import static com.roomroot.jwgl.utils.NoticeAnnouncementUtil.startOfNextDay; + +/** + * 通知公告管理服务实现。 + * + *

当前类只编排公告业务流程;DTO 复制、固定校验、状态规则、 + * 会话读取、接收人去重和统计计算统一由通知公告工具类处理。

+ */ +@Service +public class NoticeAnnouncementServiceImpl + implements NoticeAnnouncementService { + + /** + * 通知公告管理数据访问对象。 + */ + @Resource + private NoticeAnnouncementMapper noticeAnnouncementMapper; + + /** + * 分页查询后台通知公告。 + */ + @Override + @Transactional(readOnly = true) + public PageResult list( + NoticeAnnouncementQueryDTO dto) { + // 第一步:规范化并校验后台分页筛选条件。 + NoticeAnnouncementQueryDTO query = + normalizeQuery(dto); + + // 第二步:把页面日期转换为数据库时间查询边界。 + LocalDateTime publishStartTime = + startOfDay(query.getPublishStartDate()); + LocalDateTime publishEndExclusiveTime = + startOfNextDay(query.getPublishEndDate()); + + // 第三步:创建 MyBatis-Plus 分页对象。 + Page page = + new Page<>( + query.getPageNum().longValue(), + query.getPageSize().longValue()); + + // 第四步:执行公告筛选、接收阅读统计和稳定排序。 + Page selectedPage = + noticeAnnouncementMapper + .selectNoticeAnnouncementPage( + page, + query, + publishStartTime, + publishEndExclusiveTime); + + // 第五步:返回项目统一分页结构。 + return PageResultUtil.fromPage( + selectedPage, + page, + query.getPageNum(), + query.getPageSize()); + } + + /** + * 查询通知公告后台详情。 + */ + @Override + @Transactional(readOnly = true) + public NoticeAnnouncementDetailVO get( + NoticeAnnouncementIdDTO dto) { + // 第一步:规范化公告编号。 + String id = normalizeNoticeId(dto); + + // 第二步:查询并返回包含发布范围和统计信息的公告详情。 + return loadDetail(id); + } + + /** + * 新增一条拟制公告。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public NoticeAnnouncementDetailVO add( + NoticeAnnouncementCreateDTO dto) { + // 第一步:规范化并校验公告可编辑内容。 + NoticeAnnouncementCreateDTO notice = + normalizeCreate(dto); + + // 第二步:由服务端生成公告编号和创建时间。 + String id = UuidUtil.getOriginalUUID(); + LocalDateTime now = LocalDateTime.now(); + + // 第三步:固定按照拟制状态写入公告,不信任客户端状态数据。 + noticeAnnouncementMapper.insertNoticeAnnouncement( + id, + notice, + now, + STATUS_DRAFT); + + // 第四步:重新查询并返回新增后的公告详情。 + return loadDetail(id); + } + + /** + * 修改拟制或已下架公告。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public NoticeAnnouncementDetailVO update( + NoticeAnnouncementUpdateDTO dto) { + // 第一步:规范化并校验公告编号和可编辑内容。 + NoticeAnnouncementUpdateDTO notice = + normalizeUpdate(dto); + + // 第二步:锁定公告行并取得当前状态。 + String status = + noticeAnnouncementMapper + .selectNoticeStatusForUpdate( + notice.getId()); + + // 第三步:只允许修改拟制或已下架公告。 + requireEditableStatus(status); + + // 第四步:更新公告内容,不改变状态和接收人快照。 + noticeAnnouncementMapper + .updateNoticeAnnouncement(notice); + + // 第五步:返回修改后的公告详情。 + return loadDetail(notice.getId()); + } + + /** + * 发布公告并生成接收人快照。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public NoticeAnnouncementPublishResultVO publish( + NoticeAnnouncementPublishDTO dto) { + // 第一步:规范化公告编号、发布范围和指定部别编号。 + NoticeAnnouncementPublishDTO publish = + normalizePublish(dto); + + // 第二步:锁定公告行并校验拟制或已下架状态允许发布。 + String status = + noticeAnnouncementMapper + .selectNoticeStatusForUpdate( + publish.getId()); + requirePublishableStatus(status); + + /* + * 第三步:按照全校、全体教员或指定部别解析有效账户。 + * 指定部别范围会同时校验部别是否存在且处于启用状态。 + */ + List accountIds = + resolveRecipientAccountIds(publish); + + // 第四步:去除空账户和重复账户。 + List uniqueAccountIds = + normalizeRecipientAccountIds(accountIds); + if (uniqueAccountIds.isEmpty()) { + throw new ServiceException("当前发布范围内没有可接收公告的有效账户", + BAD_REQUEST); + } + + // 第五步:为每个接收账户生成独立的接收人快照记录。 + List recipients = + createRecipientRecords(uniqueAccountIds); + LocalDateTime publishTime = LocalDateTime.now(); + + /* + * 第六步:删除上一轮发布的接收人快照。 + * 拟制公告通常没有旧快照,已下架公告重新发布时会替换旧快照。 + */ + noticeAnnouncementMapper + .deleteNoticeRecipients(publish.getId()); + + // 第七步:分批写入本轮发布的全部接收人快照。 + insertRecipientSnapshots( + publish.getId(), + recipients, + publishTime); + + // 第八步:更新公告发布状态、发布时间、全体标记并重置阅读次数。 + boolean all = + SCOPE_ALL.equals(publish.getScopeType()); + noticeAnnouncementMapper.updateNoticePublishState( + publish.getId(), + STATUS_PUBLISHED, + publishTime, + all); + + // 第九步:封装并返回本次发布结果。 + NoticeAnnouncementPublishResultVO result = + new NoticeAnnouncementPublishResultVO(); + result.setId(publish.getId()); + result.setScopeType(publish.getScopeType()); + result.setScopeName( + resolveScopeName(publish.getScopeType())); + result.setRecipientCount( + Integer.valueOf(uniqueAccountIds.size())); + result.setPublishTime(publishTime); + return result; + } + + /** + * 下架已发布公告。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public NoticeAnnouncementDetailVO down( + NoticeAnnouncementIdDTO dto) { + // 第一步:规范化公告编号。 + String id = normalizeNoticeId(dto); + + // 第二步:锁定公告行并校验当前状态必须为已发布。 + String status = + noticeAnnouncementMapper + .selectNoticeStatusForUpdate(id); + requireDownableStatus(status); + + // 第三步:把公告状态修改为已下架,保留本轮接收和阅读历史。 + noticeAnnouncementMapper.updateNoticeStatus( + id, STATUS_DOWN); + + // 第四步:返回下架后的公告详情。 + return loadDetail(id); + } + + /** + * 删除拟制或已下架公告。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void delete(NoticeAnnouncementIdDTO dto) { + // 第一步:规范化公告编号。 + String id = normalizeNoticeId(dto); + + // 第二步:锁定公告行并校验当前状态允许删除。 + String status = + noticeAnnouncementMapper + .selectNoticeStatusForUpdate(id); + requireDeletableStatus(status); + + // 第三步:先删除接收人快照,避免外键引用阻止公告删除。 + noticeAnnouncementMapper + .deleteNoticeRecipients(id); + + // 第四步:删除公告主体。 + noticeAnnouncementMapper + .deleteNoticeAnnouncement(id); + } + + /** + * 调整公告优先级。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public NoticeAnnouncementDetailVO updatePriority( + NoticeAnnouncementPriorityDTO dto) { + // 第一步:规范化公告编号并校验优先级范围。 + NoticeAnnouncementPriorityDTO priority = + normalizePriority(dto); + + // 第二步:锁定公告行并确认公告存在。 + String status = + noticeAnnouncementMapper + .selectNoticeStatusForUpdate( + priority.getId()); + requireExistingStatus(status); + + // 第三步:更新优先级,设置为0时取消置顶效果。 + noticeAnnouncementMapper.updateNoticePriority( + priority.getId(), + priority.getPriority()); + + // 第四步:返回调整后的公告详情。 + return loadDetail(priority.getId()); + } + + /** + * 分页查询公告接收人和阅读明细。 + */ + @Override + @Transactional(readOnly = true) + public PageResult + recipients( + NoticeAnnouncementRecipientQueryDTO dto) { + // 第一步:规范化公告编号、分页和接收人筛选条件。 + NoticeAnnouncementRecipientQueryDTO query = + normalizeRecipientQuery(dto); + + // 第二步:确认待查询公告存在。 + loadDetail(query.getId()); + + // 第三步:创建接收人明细分页对象。 + Page page = + new Page<>( + query.getPageNum().longValue(), + query.getPageSize().longValue()); + + // 第四步:查询按账户去重后的接收和阅读明细。 + Page selectedPage = + noticeAnnouncementMapper + .selectNoticeRecipientPage( + page, query); + + // 第五步:返回项目统一分页结构。 + return PageResultUtil.fromPage( + selectedPage, + page, + query.getPageNum(), + query.getPageSize()); + } + + /** + * 查询公告接收和阅读统计。 + */ + @Override + @Transactional(readOnly = true) + public NoticeAnnouncementStatisticsVO statistics( + NoticeAnnouncementIdDTO dto) { + // 第一步:规范化公告编号。 + String id = normalizeNoticeId(dto); + + // 第二步:确认待统计公告存在。 + loadDetail(id); + + // 第三步:汇总唯一接收账户的接收和阅读人数。 + NoticeAnnouncementStatisticsVO statistics = + noticeAnnouncementMapper + .selectNoticeStatistics(id); + + // 第四步:补全未读人数和阅读率并返回。 + return completeStatistics(statistics, id); + } + + /** + * 分页查询当前登录账户可见的公告。 + */ + @Override + @Transactional(readOnly = true) + public PageResult userList( + UserNoticeAnnouncementQueryDTO dto) { + // 第一步:规范化用户公告分页和筛选条件。 + UserNoticeAnnouncementQueryDTO query = + normalizeUserQuery(dto); + + // 第二步:只根据当前服务端会话查询真实账户编号。 + String accountId = currentAccountId(); + + // 第三步:创建用户公告分页对象。 + Page page = + new Page<>( + query.getPageNum().longValue(), + query.getPageSize().longValue()); + + /* + * 第四步:只查询当前账户具有接收快照且仍为已发布状态的公告, + * 客户端不能通过提交其他账户编号越权查询。 + */ + Page selectedPage = + noticeAnnouncementMapper + .selectUserNoticePage( + page, + accountId, + query, + STATUS_PUBLISHED); + + // 第五步:返回项目统一分页结构。 + return PageResultUtil.fromPage( + selectedPage, + page, + query.getPageNum(), + query.getPageSize()); + } + + /** + * 当前登录账户打开公告并完成阅读记录。 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public UserNoticeAnnouncementDetailVO read( + NoticeAnnouncementIdDTO dto) { + // 第一步:规范化公告编号并识别当前真实账户。 + String id = normalizeNoticeId(dto); + String accountId = currentAccountId(); + + /* + * 第二步:锁定当前账户具有接收快照的已发布公告。 + * 公告不存在、已经下架或不属于当前账户时统一不返回公告内容。 + */ + String status = + noticeAnnouncementMapper + .selectUserNoticeStatusForUpdate( + id, + accountId, + STATUS_PUBLISHED); + if (status == null) { + throw new ServiceException("通知公告不存在或当前账户无权查看", + NOT_FOUND); + } + + // 第三步:首次打开时标记已读和首次阅读时间,重复打开不会覆盖时间。 + LocalDateTime readTime = LocalDateTime.now(); + noticeAnnouncementMapper.markNoticeRecipientRead( + id, + accountId, + readTime); + + // 第四步:每次有效打开都增加公告累计阅读次数。 + noticeAnnouncementMapper + .incrementNoticeReadCount(id); + + // 第五步:重新查询并返回更新后的用户公告详情。 + UserNoticeAnnouncementDetailVO detail = + noticeAnnouncementMapper + .selectUserNoticeDetail( + id, + accountId, + STATUS_PUBLISHED); + if (detail == null) { + throw new ServiceException("通知公告不存在或当前账户无权查看", + NOT_FOUND); + } + return detail; + } + + /** + * 按发布范围解析有效接收账户。 + * + * @param publish 已完成规范化的发布参数 + * @return 数据库查询到的接收账户编号 + */ + private List resolveRecipientAccountIds( + NoticeAnnouncementPublishDTO publish) { + // 第一步:全校范围查询全部有效账户。 + if (SCOPE_ALL.equals(publish.getScopeType())) { + return noticeAnnouncementMapper + .selectAllActiveAccountIds(); + } + + // 第二步:全体教员范围查询全部有效教员账户。 + if (SCOPE_ALL_TEACHERS.equals( + publish.getScopeType())) { + return noticeAnnouncementMapper + .selectAllActiveTeacherAccountIds(); + } + + /* + * 第三步:指定部别范围先确认全部部别存在且启用, + * 防止部分无效编号被静默忽略后产生错误发布范围。 + */ + List departmentIds = + publish.getDepartmentIds(); + int activeDepartmentCount = + noticeAnnouncementMapper + .countActiveDepartments( + departmentIds); + if (activeDepartmentCount + != departmentIds.size()) { + throw new ServiceException("选择的部别不存在或已经停用", + BAD_REQUEST); + } + + // 第四步:返回指定部别关联的机关人员、教员、教研室和教学班次账户。 + return noticeAnnouncementMapper + .selectActiveAccountIdsByDepartments( + departmentIds); + } + + /** + * 分批写入接收人快照。 + * + * @param noticeId 公告编号 + * @param recipients 全部接收人记录 + * @param receivedTime 统一接收时间 + */ + private void insertRecipientSnapshots( + String noticeId, + List + recipients, + LocalDateTime receivedTime) { + // 第一步:按照固定批次大小切分接收人记录。 + for (int start = 0; + start < recipients.size(); + start += RECIPIENT_INSERT_BATCH_SIZE) { + int end = Math.min( + start + RECIPIENT_INSERT_BATCH_SIZE, + recipients.size()); + + // 第二步:使用单条批量 INSERT SELECT 写入当前批次。 + noticeAnnouncementMapper.insertNoticeRecipients( + noticeId, + recipients.subList(start, end), + receivedTime); + } + } + + /** + * 根据当前服务端会话取得真实账户编号。 + * + * @return 当前登录账户编号 + */ + private String currentAccountId() { + // 第一步:只从当前 HTTP 请求读取服务端 SessionId。 + String sessionId = requireCurrentSessionId(); + + // 第二步:通过在线记录和登录信息表查询真实账户编号。 + String accountId = + noticeAnnouncementMapper + .selectAccountIdBySessionId( + sessionId); + + // 第三步:校验并返回当前会话对应的有效账户编号。 + return requireCurrentAccountId(accountId); + } + + /** + * 查询并补全公告后台详情。 + * + * @param id 公告编号 + * @return 公告后台详情 + */ + private NoticeAnnouncementDetailVO loadDetail( + String id) { + // 第一步:查询公告主体、发布范围和接收阅读统计。 + NoticeAnnouncementDetailVO detail = + noticeAnnouncementMapper + .selectNoticeAnnouncementDetail(id); + + // 第二步:公告不存在时返回明确的资源不存在错误。 + if (detail == null) { + throw new ServiceException("通知公告不存在", + NOT_FOUND); + } + + // 第三步:补全范围说明和空统计值后返回。 + return completeDetail(detail); + } + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/NotificationLogServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/NotificationLogServiceImpl.java new file mode 100644 index 000000000..977dad922 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/NotificationLogServiceImpl.java @@ -0,0 +1,244 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogExportDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogIdDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogQueryDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogRecipientQueryDTO; +import com.roomroot.jwgl.mapper.NotificationLogMapper; +import com.roomroot.jwgl.service.NotificationLogService; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.PageResultUtil; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogDetailVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogRecipientVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogStatisticsVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogVO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.roomroot.jwgl.utils.NotificationLogUtil.completeDetail; +import static com.roomroot.jwgl.utils.NotificationLogUtil.completeRecords; +import static com.roomroot.jwgl.utils.NotificationLogUtil.completeStatistics; +import static com.roomroot.jwgl.utils.NotificationLogUtil.normalizeExportQuery; +import static com.roomroot.jwgl.utils.NotificationLogUtil.normalizeId; +import static com.roomroot.jwgl.utils.NotificationLogUtil.normalizeQuery; +import static com.roomroot.jwgl.utils.NotificationLogUtil.normalizeRecipientQuery; + +/** + * 通知日志管理服务实现。 + * + *

当前类只编排通知日志业务流程,查询参数规范化和统计字段补全 + * 统一由通知日志工具类处理。

+ */ +@Service +public class NotificationLogServiceImpl + implements NotificationLogService { + + /** + * 通知日志管理数据访问对象。 + */ + @Resource + private NotificationLogMapper notificationLogMapper; + + /** + * 分页查询已经实际发送的通知日志。 + * + * @param dto 通知日志查询条件 + * @return 通知日志分页结果 + */ + @Override + @Transactional(readOnly = true) + public PageResult list( + NotificationLogQueryDTO dto) { + // 第一步:规范化并校验分页、文本、时间和发送状态条件。 + NotificationLogQueryDTO query = + normalizeQuery(dto); + + // 第二步:创建 MyBatis-Plus 通知日志分页对象。 + Page page = + new Page<>( + query.getPageNum().longValue(), + query.getPageSize().longValue()); + + // 第三步:查询已经产生接收记录的通知发送历史。 + Page selectedPage = + notificationLogMapper + .selectNotificationLogPage( + page, query); + + // 第四步:兼容 Mapper 返回 null,并确定用于读取分页总数的结果对象。 + Page resultPage = + selectedPage == null ? page : selectedPage; + + // 第五步:统一补全发送失败数、未读数、发送状态和阅读率。 + List records = + completeRecords(resultPage.getRecords()); + + // 第六步:复用项目公共分页工具返回统一分页结构。 + return PageResultUtil.fromRecords( + resultPage, + records, + query.getPageNum(), + query.getPageSize()); + } + + /** + * 根据通知编号查询单条发送日志详情。 + * + * @param dto 通知日志编号参数 + * @return 通知主体和完整发送、阅读统计 + */ + @Override + @Transactional(readOnly = true) + public NotificationLogDetailVO get( + NotificationLogIdDTO dto) { + // 第一步:规范化并校验通知日志编号。 + String id = normalizeId(dto); + + // 第二步:查询已经实际产生接收记录的通知发送日志详情。 + NotificationLogDetailVO detail = + notificationLogMapper + .selectNotificationLogDetail(id); + + // 第三步:校验日志存在并补全发送失败数、未读数和阅读率。 + return completeDetail(detail); + } + + /** + * 分页查询指定通知的接收对象和阅读明细。 + * + * @param dto 通知编号、分页和接收阅读筛选条件 + * @return 按账户去重的接收与阅读明细 + */ + @Override + @Transactional(readOnly = true) + public PageResult recipients( + NotificationLogRecipientQueryDTO dto) { + // 第一步:规范化并校验通知编号、分页和接收阅读筛选条件。 + NotificationLogRecipientQueryDTO query = + normalizeRecipientQuery(dto); + + /* + * 第二步:查询通知发送日志主体并校验存在性。 + * 即使明细筛选后为空,也能区分“没有匹配接收人”和“日志不存在”。 + */ + completeDetail( + notificationLogMapper + .selectNotificationLogDetail( + query.getId())); + + // 第三步:创建 MyBatis-Plus 接收与阅读明细分页对象。 + Page page = + new Page<>( + query.getPageNum().longValue(), + query.getPageSize().longValue()); + + // 第四步:按账户去重查询指定通知的接收和阅读明细。 + Page selectedPage = + notificationLogMapper + .selectNotificationLogRecipientPage( + page, query); + + // 第五步:兼容 Mapper 返回 null,并确定分页总数来源。 + Page resultPage = + selectedPage == null ? page : selectedPage; + + // 第六步:将异常空明细集合统一转换为空集合。 + List selectedRecords = + resultPage.getRecords(); + List records = + selectedRecords == null + ? Collections + .emptyList() + : selectedRecords; + + // 第七步:复用项目公共分页工具返回统一分页结构。 + return PageResultUtil.fromRecords( + resultPage, + records, + query.getPageNum(), + query.getPageSize()); + } + + /** + * 按当前筛选条件汇总通知发送和阅读情况。 + * + * @param dto 通知日志筛选条件 + * @return 通知数量、发送结果和阅读情况汇总 + */ + @Override + @Transactional(readOnly = true) + public NotificationLogStatisticsVO statistics( + NotificationLogExportDTO dto) { + // 第一步:将统计条件转换为与列表一致的规范化查询条件。 + NotificationLogQueryDTO query = + normalizeExportQuery(dto); + + // 第二步:在数据库中汇总符合条件的通知和接收阅读数量。 + NotificationLogStatisticsVO statistics = + notificationLogMapper + .selectNotificationLogStatistics( + query); + + // 第三步:统一补全失败数、未读数和汇总阅读率。 + return completeStatistics(statistics); + } + + /** + * 按当前筛选条件查询全部待导出的通知日志。 + * + * @param dto 通知日志导出筛选条件 + * @return 与列表筛选和排序规则一致的全部通知日志 + */ + @Override + @Transactional(readOnly = true) + public List export( + NotificationLogExportDTO dto) { + // 第一步:将导出条件转换为固定批次的列表查询参数。 + NotificationLogQueryDTO query = + normalizeExportQuery(dto); + + // 第二步:复用列表业务查询第一批通知日志。 + PageResult firstPage = + list(query); + + // 第三步:创建独立导出集合并加入第一批有效记录。 + List records = + new ArrayList<>(); + if (firstPage.getRecords() != null) { + records.addAll(firstPage.getRecords()); + } + + // 第四步:读取总页数;没有后续批次时直接返回当前结果。 + Integer pageCount = firstPage.getPages(); + if (pageCount == null + || pageCount.intValue() <= 1) { + return records; + } + + /* + * 第五步:按照固定页码依次查询剩余日志。 + * 列表接口使用发送时间和通知编号双重倒序,跨页顺序保持稳定。 + */ + for (int pageNum = 2; + pageNum <= pageCount.intValue(); + pageNum++) { + query.setPageNum(Integer.valueOf(pageNum)); + PageResult currentPage = + list(query); + + // 第六步:忽略异常空集合,按分页顺序追加有效日志。 + if (currentPage.getRecords() != null) { + records.addAll(currentPage.getRecords()); + } + } + + // 第七步:返回与内部分页结果相互独立的完整导出集合。 + return records; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/OfficeTaskBookServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/OfficeTaskBookServiceImpl.java new file mode 100644 index 000000000..0679d8584 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/OfficeTaskBookServiceImpl.java @@ -0,0 +1,80 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JYSRWS; +import com.roomroot.jwgl.mapper.OfficeTaskBookMapper; +import com.roomroot.jwgl.service.OfficeTaskBookService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 教研室任务书服务实现类 + */ +@Service +public class OfficeTaskBookServiceImpl implements OfficeTaskBookService { + + @Resource + private OfficeTaskBookMapper officeTaskBookMapper; + + @Override + public void add(JYSRWS jysrws) { + String uuid = UuidUtil.getUUID(); + jysrws.setBh(uuid); + jysrws.setDelFlag(0); + // 状态默认为未上报 + jysrws.setZt("未上报"); + officeTaskBookMapper.insert(jysrws); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(String bh) { + JYSRWS jysrws = getByBh(bh); + if (jysrws != null) { + jysrws.setDelFlag(1); + officeTaskBookMapper.updateById(jysrws); + } + } + + @Override + public void batchDelete(List bhList) { + for (String bh : bhList) { + delete(bh); + } + } + + @Override + public void update(JYSRWS jysrws) { + officeTaskBookMapper.updateById(jysrws); + } + + @Override + public JYSRWS getByBh(String bh) { + return officeTaskBookMapper.selectById(bh); + } + + @Override + public PageResult pageList(PageQuery query, JYSRWS jysrws) { + jysrws.setDelFlag(0); + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = officeTaskBookMapper.selectPageList(page, jysrws); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List listByJxrwbh(String jxrwbh) { + return officeTaskBookMapper.selectByJxrwbh(jxrwbh); + } + + @Override + public List listByJysdh(String jysdh) { + return officeTaskBookMapper.selectByJysdh(jysdh); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/OperationLogServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/OperationLogServiceImpl.java new file mode 100644 index 000000000..cf020c55c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/OperationLogServiceImpl.java @@ -0,0 +1,354 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.dto.operationlog.OperationLogExportDTO; +import com.roomroot.jwgl.dto.operationlog.OperationLogIdDTO; +import com.roomroot.jwgl.dto.operationlog.OperationLogQueryDTO; +import com.roomroot.jwgl.dto.operationlog.OperationLogRecordDTO; +import com.roomroot.jwgl.mapper.OperationLogMapper; +import com.roomroot.jwgl.service.OperationLogService; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.PageResultUtil; +import com.roomroot.jwgl.utils.UuidUtil; +import com.roomroot.jwgl.vo.operationlog.OperationLogDetailVO; +import com.roomroot.jwgl.vo.operationlog.OperationLogModuleVO; +import com.roomroot.jwgl.vo.operationlog.OperationLogVO; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST; +import static com.roomroot.jwgl.utils.BasicManagementConstants.CONTENT_KEYWORD_MAX_LENGTH; +import static com.roomroot.jwgl.utils.BasicManagementConstants.CONFLICT; +import static com.roomroot.jwgl.utils.BasicManagementConstants.IDENTIFIER_MAX_LENGTH; +import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND; +import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATION_CONTENT_MAX_LENGTH; +import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATION_LOG_EXPORT_BATCH_SIZE; +import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATION_URL_MAX_LENGTH; +import static com.roomroot.jwgl.utils.BasicManagementConstants.OPERATOR_KEYWORD_MAX_LENGTH; +import static com.roomroot.jwgl.utils.BasicManagementConstants.URL_KEYWORD_MAX_LENGTH; +import static com.roomroot.jwgl.utils.OperationLogUtil.listOperationLogModules; +import static com.roomroot.jwgl.utils.OperationLogUtil.optionalModule; +import static com.roomroot.jwgl.utils.OperationLogUtil.optionalText; +import static com.roomroot.jwgl.utils.OperationLogUtil.requiredText; +import static com.roomroot.jwgl.utils.OperationLogUtil.resolveModule; + +/** + * 操作日志管理服务实现。 + * + *

当前类统一编排分页查询、详情查询、导出和内部写入流程, + * 参数辅助处理集中维护在操作日志工具类中。

+ */ +@Service +public class OperationLogServiceImpl implements OperationLogService { + + /** + * 操作日志数据访问对象。 + */ + @Resource + private OperationLogMapper operationLogMapper; + + /** + * 分页查询操作日志。 + * + *

处理顺序:校验分页和时间范围、规范化查询条件、 + * 数据库联表筛选、识别模块名称、封装统一分页结果。

+ * + * @param dto 查询条件 + * @return 经过筛选和稳定排序的操作日志分页结果 + */ + @Override + @Transactional(readOnly = true) + public PageResult list(OperationLogQueryDTO dto) { + // 第一步:兼容业务层直接传入 null,并使用 DTO 中的默认分页参数。 + OperationLogQueryDTO query = + dto == null ? new OperationLogQueryDTO() : dto; + + // 第二步:校验页码,防止绕过控制器校验后生成无效分页语句。 + Integer pageNum = query.getPageNum(); + if (pageNum == null || pageNum.intValue() < 1) { + throw new ServiceException("页码最小为1", BAD_REQUEST); + } + + // 第三步:校验每页记录数,保持与项目其他分页接口相同的上限。 + Integer pageSize = query.getPageSize(); + if (pageSize == null + || pageSize.intValue() < 1 + || pageSize.intValue() > 100) { + throw new ServiceException("每页大小必须在1到100之间", + BAD_REQUEST); + } + + // 第四步:读取时间范围并校验开始时间不能晚于结束时间。 + LocalDateTime startTime = query.getStartTime(); + LocalDateTime endTime = query.getEndTime(); + if (startTime != null + && endTime != null + && startTime.isAfter(endTime)) { + throw new ServiceException("开始时间不能晚于结束时间", + BAD_REQUEST); + } + + // 第五步:规范化操作人关键字,空字符串按照未填写处理。 + String operatorKeyword = optionalText( + query.getOperatorKeyword(), + OPERATOR_KEYWORD_MAX_LENGTH, + "操作人关键字"); + + // 第六步:规范化并校验模块名称,只允许查询系统已识别的模块。 + String module = optionalModule(query.getModule()); + + // 第七步:规范化操作内容关键字,防止业务层直接调用时绕过长度校验。 + String contentKeyword = optionalText( + query.getContentKeyword(), + CONTENT_KEYWORD_MAX_LENGTH, + "操作内容关键字"); + + // 第八步:规范化请求地址关键字,空字符串按照未填写处理。 + String urlKeyword = optionalText( + query.getUrlKeyword(), + URL_KEYWORD_MAX_LENGTH, + "请求地址关键字"); + + // 第九步:创建分页对象,由 MyBatis-Plus 生成达梦分页和总数语句。 + Page page = + new Page<>(pageNum.longValue(), pageSize.longValue()); + + /* + * 第十步:在数据库中完成三表关联、组合筛选和稳定排序。 + * 操作人身份来源于登录记录关联账户,不接收客户端提交的账户编号。 + */ + Page selectedPage = + operationLogMapper.selectOperationLogPage( + page, + startTime, + endTime, + operatorKeyword, + module, + contentKeyword, + urlKeyword); + + // 第十一步:兼容模拟测试或异常 Mapper 返回 null 分页对象的情况。 + Page resultPage = + selectedPage == null ? page : selectedPage; + + // 第十二步:取得当前页记录,并将 null 集合统一转换为空集合。 + List selectedRecords = resultPage.getRecords(); + List records = selectedRecords == null + ? Collections.emptyList() + : selectedRecords; + + /* + * 第十三步:根据每条日志的请求地址补充模块名称。 + * 旧数据库没有模块字段,因此模块只用于页面展示和查询分类。 + */ + for (OperationLogVO record : records) { + if (record != null) { + record.setModule(resolveModule(record.getUrl())); + } + } + + // 第十四步:返回项目统一分页结构,总数由分页拦截器统计。 + return PageResultUtil.fromRecords( + resultPage, + records, + pageNum, + pageSize); + } + + /** + * 查询操作日志页面可使用的模块选项。 + * + * @return 与模块筛选白名单保持一致的模块选项 + */ + @Override + @Transactional(readOnly = true) + public List modules() { + // 调用操作日志工具类统一转换模块配置,业务层不重复维护固定选项。 + return listOperationLogModules(); + } + + /** + * 根据编号查询操作日志详情。 + * + *

处理顺序:校验并规范化编号、查询三表关联信息、 + * 校验日志是否存在、根据请求地址补充业务模块名称。

+ * + * @param dto 操作日志编号参数 + * @return 操作日志详情 + */ + @Override + @Transactional(readOnly = true) + public OperationLogDetailVO get(OperationLogIdDTO dto) { + // 第一步:防止业务层被直接调用时传入空 DTO。 + if (dto == null) { + throw new ServiceException("操作日志编号不能为空", + BAD_REQUEST); + } + + // 第二步:去除日志编号首尾空白,并将空文本视为未填写。 + String id = dto.getId() == null + ? null + : dto.getId().trim(); + if (id == null || id.isEmpty()) { + throw new ServiceException("操作日志编号不能为空", + BAD_REQUEST); + } + + /* + * 第三步:使用公共标识符长度常量校验编号。 + * 固定长度统一维护在常量工具类中,不在业务实现类重复声明。 + */ + if (id.length() > IDENTIFIER_MAX_LENGTH) { + throw new ServiceException("操作日志编号格式不正确", + BAD_REQUEST); + } + + // 第四步:根据日志编号联查操作日志、登录记录和账户信息。 + OperationLogDetailVO detail = + operationLogMapper.selectOperationLogById(id); + + // 第五步:日志不存在时返回明确的业务错误,不返回空成功结果。 + if (detail == null) { + throw new ServiceException("操作日志不存在", + NOT_FOUND); + } + + /* + * 第六步:旧操作日志表没有模块字段, + * 因此根据请求地址补充与列表接口一致的模块名称。 + */ + detail.setModule(resolveModule(detail.getUrl())); + + // 第七步:返回完成关联查询和模块补充的操作日志详情。 + return detail; + } + + /** + * 按筛选条件查询全部待导出的操作日志。 + * + *

处理顺序:转换导出条件、按照列表接口允许的最大页大小分批查询、 + * 汇总全部页面记录,并保持与页面列表完全一致的筛选和排序结果。

+ * + * @param dto 导出筛选条件 + * @return 全部符合条件的操作日志 + */ + @Override + @Transactional(readOnly = true) + public List export(OperationLogExportDTO dto) { + // 第一步:兼容业务层直接传入 null,并将其视为导出全部操作日志。 + OperationLogExportDTO export = + dto == null ? new OperationLogExportDTO() : dto; + + /* + * 第二步:创建列表查询 DTO,只复制导出允许使用的筛选条件。 + * 导出接口不接收客户端分页参数,分页过程完全由服务端控制。 + */ + OperationLogQueryDTO query = new OperationLogQueryDTO(); + query.setStartTime(export.getStartTime()); + query.setEndTime(export.getEndTime()); + query.setOperatorKeyword(export.getOperatorKeyword()); + query.setModule(export.getModule()); + query.setContentKeyword(export.getContentKeyword()); + query.setUrlKeyword(export.getUrlKeyword()); + query.setPageNum(Integer.valueOf(1)); + query.setPageSize( + Integer.valueOf(OPERATION_LOG_EXPORT_BATCH_SIZE)); + + /* + * 第三步:先查询第一页。 + * 该调用会复用已有的时间、文本、模块校验以及数据库筛选和稳定排序规则。 + */ + PageResult firstPage = list(query); + + // 第四步:创建独立导出集合,并加入第一页返回的日志记录。 + List exportedLogs = new ArrayList<>(); + if (firstPage.getRecords() != null) { + exportedLogs.addAll(firstPage.getRecords()); + } + + // 第五步:读取总页数;没有更多页面时直接返回第一页汇总结果。 + Integer pageCount = firstPage.getPages(); + if (pageCount == null || pageCount.intValue() <= 1) { + return exportedLogs; + } + + /* + * 第六步:按照固定页码依次查询剩余记录。 + * 列表查询使用操作时间和日志编号双重倒序,可以保证跨页顺序稳定。 + */ + for (int pageNum = 2; + pageNum <= pageCount.intValue(); + pageNum++) { + query.setPageNum(Integer.valueOf(pageNum)); + PageResult currentPage = list(query); + + // 第七步:忽略异常空集合,并按查询页顺序追加有效日志。 + if (currentPage.getRecords() != null) { + exportedLogs.addAll(currentPage.getRecords()); + } + } + + // 第八步:返回与内部分页结果集合相互独立的完整导出数据。 + return exportedLogs; + } + + /** + * 写入一条由服务端自动采集的操作日志。 + * + *

当前小功能只建立内部写入能力,登录记录编号由后续统一请求记录流程 + * 从服务端会话取得,不提供客户端写入接口。

+ * + * @param dto 当前登录记录编号、操作内容和请求地址 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void record(OperationLogRecordDTO dto) { + // 第一步:防止内部调用方传入空 DTO,避免产生缺少审计上下文的日志。 + if (dto == null) { + throw new ServiceException("操作日志记录参数不能为空", + BAD_REQUEST); + } + + // 第二步:校验并规范化当前请求关联的登录记录编号。 + String loginRecordId = requiredText( + dto.getLoginRecordId(), + IDENTIFIER_MAX_LENGTH, + "登录记录编号"); + + // 第三步:校验并规范化服务端生成的业务操作内容。 + String operationContent = requiredText( + dto.getOperationContent(), + OPERATION_CONTENT_MAX_LENGTH, + "操作内容"); + + // 第四步:校验并规范化当前请求的应用内访问地址。 + String url = requiredText( + dto.getUrl(), + OPERATION_URL_MAX_LENGTH, + "请求地址"); + + /* + * 第五步:由服务端生成日志编号和操作时间并写入数据库。 + * 调用方不能指定编号或时间,避免审计记录被伪造或覆盖。 + */ + int affectedRows = operationLogMapper.insertOperationLog( + UuidUtil.getOriginalUUID(), + loginRecordId, + LocalDateTime.now(), + operationContent, + url); + + // 第六步:数据库未写入唯一记录时返回明确冲突错误。 + if (affectedRows != 1) { + throw new ServiceException("操作日志记录失败", + CONFLICT); + } + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/SemesterCalendarServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/SemesterCalendarServiceImpl.java new file mode 100644 index 000000000..8af48c650 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/SemesterCalendarServiceImpl.java @@ -0,0 +1,200 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XQXLB; +import com.roomroot.jwgl.mapper.SemesterCalendarMapper; +import com.roomroot.jwgl.service.SemesterCalendarService; +import com.roomroot.jwgl.utils.DateCourseUtil; +import com.roomroot.jwgl.utils.UuidUtil; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import java.io.InputStream; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * 学期校历表服务实现类 + */ +@Service +public class SemesterCalendarServiceImpl implements SemesterCalendarService { + + @Resource + private SemesterCalendarMapper xqxlbMapper; + + @Override + public void add(String startDate,String endDateb) { + Map dateWeekAndCourseMap = DateCourseUtil.getDateWeekAndCourseMap(startDate, endDateb); + for (Map.Entry entry : dateWeekAndCourseMap.entrySet()) { + String dateStr = entry.getKey(); + Page page = new Page<>(0, 10); + XQXLB query = new XQXLB(); + //将日期分为年度和月日 + LocalDate date = LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd")); + + int nd = date.getYear(); + String md = String.format("%02d%02d", date.getMonthValue(), date.getDayOfMonth()); + query.setJqsj(md); + query.setNd(nd); + Page result = xqxlbMapper.selectPageList(page, query); + if(result.getTotal() > 0){ + continue; + } + Map map = (Map) entry.getValue(); + Object course = map.get("course"); + if (course instanceof ArrayList) { + ArrayList list = (ArrayList) course; + for (int i = 0; i < list.size(); i++) { + String courseClass = list.get(i); + XQXLB xqxlb = new XQXLB(); + //编号,用时间戳 + String bh = UuidUtil.getUUID(); + xqxlb.setBh(bh); + //假期时间 + xqxlb.setJqsj(dateStr); + //年度 + xqxlb.setNd(Integer.valueOf(nd)); + //节次 + xqxlb.setCourseClass(courseClass); + xqxlb.setKpk(true); + xqxlb.setBzxs(true); + xqxlb.setZdpk(true); + xqxlbMapper.insert(xqxlb); + } + } + + } + } + + @Override + public void delete(String bh) { + xqxlbMapper.deleteById(bh); + } + + @Override + public void update(XQXLB xqxlb) { + xqxlbMapper.updateById(xqxlb); + } + + @Override + public XQXLB getById(String bh) { + return xqxlbMapper.selectById(bh); + } + + @Override + public List listByNd(Integer nd) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XQXLB::getNd, nd); + return xqxlbMapper.selectList(wrapper); + } + + @Override + public int importExcel(MultipartFile file) { + try (InputStream is = file.getInputStream(); + Workbook workbook = file.getOriginalFilename().endsWith(".xlsx") + ? new XSSFWorkbook(is) : new HSSFWorkbook(is)) { + + Sheet sheet = workbook.getSheetAt(0); + int count = 0; + + // 从第二行开始读取数据(跳过表头) + for (int i = 1; i <= sheet.getLastRowNum(); i++) { + Row row = sheet.getRow(i); + if (row == null) continue; + + XQXLB xqxlb = new XQXLB(); + + // 编号 + xqxlb.setBh(UuidUtil.getUUID()); + + // 年度 + String nd = getCellValue(row, 0); + if (nd != null && !nd.isEmpty()) { + xqxlb.setNd(Integer.parseInt(nd)); + } + + // 假期时间 + String jqsj = getCellValue(row, 1); + xqxlb.setJqsj(jqsj); + Page page = new Page<>(0, 10); + Page result = xqxlbMapper.selectPageList(page, xqxlb); + if(result.getTotal() > 0){ + continue; + } + + // 假期名称 + xqxlb.setJqmc(getCellValue(row, 2)); + + // 加粗 + String jc = getCellValue(row, 3); + xqxlb.setJc(jc != null && "是".equals(jc)); + + // 备注 + xqxlb.setBz(getCellValue(row, 4)); + + // 可排课 + String kpk = getCellValue(row, 5); + xqxlb.setKpk("是".equals(kpk)); + + // 备注显示 + String bzxs = getCellValue(row, 6); + xqxlb.setBzxs("是".equals(bzxs)); + + // 自动排课 + String zdpk = getCellValue(row, 7); + xqxlb.setZdpk("是".equals(zdpk)); + + // 节次 + xqxlb.setCourseClass(getCellValue(row, 8)); + + xqxlbMapper.insert(xqxlb); + count++; + } + return count; + } catch (Exception e) { + throw new RuntimeException("Excel解析失败: " + e.getMessage(), e); + } + } + + /** + * 获取单元格字符串值 + */ + private String getCellValue(Row row, int index) { + Cell cell = row.getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); + if (cell == null) { + return null; + } + + CellType type = cell.getCellType(); + // 处理公式单元格 + if (type == CellType.FORMULA) { + type = cell.getCachedFormulaResultType(); + } + + switch (type) { + case NUMERIC: + double num = cell.getNumericCellValue(); + // 整数就去掉小数点后多余的 .0 + if (num == Math.floor(num)) { + return String.valueOf((long) num); + } else { + return String.valueOf(num); + } + case STRING: + return cell.getStringCellValue().trim(); + case BOOLEAN: + return String.valueOf(cell.getBooleanCellValue()); + default: + return null; + } + } + +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/SemesterServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/SemesterServiceImpl.java new file mode 100644 index 000000000..15ed26061 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/SemesterServiceImpl.java @@ -0,0 +1,91 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.roomroot.jwgl.entity.XQ; +import com.roomroot.jwgl.mapper.SemesterMapper; +import com.roomroot.jwgl.service.SemesterCalendarService; +import com.roomroot.jwgl.service.SemesterService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * 学期服务实现类 + */ +@Service +public class SemesterServiceImpl extends ServiceImpl implements SemesterService { + + // 定义格式化器,静态常量只初始化一次 + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + @Resource + private SemesterMapper semesterMapper; + + @Resource + SemesterCalendarService semesterCalendarService; + + @Override + public void add(XQ xq) { + xq.setDelFlag(0); + xq.setTkbsp(true); + int insert = semesterMapper.insert(xq); + if(insert > 0){ + LocalDateTime kxrq = xq.getKxrq(); + LocalDateTime jsrq = xq.getJsrq(); + + String startDate = localDateTimeToDateStr(kxrq); + String endDate = localDateTimeToDateStr(jsrq); + + semesterCalendarService.add(startDate, endDate); + } + } + + /** + * LocalDateTime 转为 yyyy-MM-dd 日期字符串 + * @param dateTime 日期时间对象 + * @return 格式:2026-07-22,入参null返回空串 + */ + public static String localDateTimeToDateStr(LocalDateTime dateTime) { + if (dateTime == null) { + return ""; + } + return dateTime.format(DATE_FORMATTER); + } + + @Override + public void delete(Integer nd) { + semesterMapper.deleteById(nd); + } + + @Override + public void update(XQ xq) { + semesterMapper.updateById(xq); + } + + @Override + public XQ getByNd(Integer nd) { + return semesterMapper.selectById(nd); + } + + @Override + public PageResult pageList(PageQuery query, XQ xq) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = semesterMapper.selectPageList(page, xq); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List list() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.orderByDesc(XQ::getNd); + return semesterMapper.selectList(wrapper); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/StudentRecordsServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/StudentRecordsServiceImpl.java new file mode 100644 index 000000000..838b8e7c7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/StudentRecordsServiceImpl.java @@ -0,0 +1,197 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYXX; +import com.roomroot.jwgl.entity.XYKCCJ; +import com.roomroot.jwgl.entity.XYKCGCCJ; +import com.roomroot.jwgl.mapper.XYXXMapper; +import com.roomroot.jwgl.mapper.XYKCCJMapper; +import com.roomroot.jwgl.mapper.XYKCGCCJMapper; +import com.roomroot.jwgl.service.StudentRecordsService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.List; + +/** + * 学员档案管理服务实现类 + */ +@Service +public class StudentRecordsServiceImpl implements StudentRecordsService { + + @Resource + private XYXXMapper xyxxMapper; + + @Resource + private XYKCCJMapper xykccjMapper; + + @Resource + private XYKCGCCJMapper xykgccjMapper; + + @Override + @Transactional + public void add(XYXX xyxx) { + xyxxMapper.insert(xyxx); + } + + @Override + @Transactional + public void delete(String bh) { + XYXX xyxx = new XYXX(); + xyxx.setBh(bh); + xyxx.setTxzt("退学"); + xyxxMapper.updateById(xyxx); + } + + @Override + @Transactional + public void update(XYXX xyxx) { + xyxxMapper.updateById(xyxx); + } + + @Override + public XYXX getById(String bh) { + return xyxxMapper.selectById(bh); + } + + @Override + public PageResult pageList(PageQuery query, XYXX xyxx) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = xyxxMapper.selectPageList(page, xyxx); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + @Transactional + public void retainGrade(String bh) { + XYXX xyxx = new XYXX(); + xyxx.setBh(bh); + xyxx.setLjzt("留级"); + xyxxMapper.updateById(xyxx); + } + + @Override + @Transactional + public void downgrade(String bh) { + XYXX xyxx = new XYXX(); + xyxx.setBh(bh); + xyxx.setLjzt("降级"); + xyxxMapper.updateById(xyxx); + } + + @Override + @Transactional + public void dropOut(String bh) { + XYXX xyxx = new XYXX(); + xyxx.setBh(bh); + xyxx.setTxzt("退学"); + xyxxMapper.updateById(xyxx); + } + + // ========== 学员课程成绩查询 ========== + + @Override + public List getCourseGrades(String xybh) { + return xykccjMapper.selectByStudentId(xybh); + } + + @Override + public List getCourseProcessGrades(String xybh) { + return xykgccjMapper.selectByStudentId(xybh); + } + + // ========== Excel 导出 ========== + + @Override + public void exportGradesExcel(String xybh, HttpServletResponse response) { + XYXX student = xyxxMapper.selectById(xybh); + if (student == null) { + throw new RuntimeException("学员不存在"); + } + + List grades = xykccjMapper.selectByStudentId(xybh); + + try (Workbook workbook = new XSSFWorkbook()) { + Sheet sheet = workbook.createSheet("课程成绩"); + + // 标题行样式 + Font headerFont = workbook.createFont(); + headerFont.setBold(true); + headerFont.setFontHeightInPoints((short) 12); + CellStyle headerStyle = workbook.createCellStyle(); + headerStyle.setFont(headerFont); + headerStyle.setAlignment(HorizontalAlignment.CENTER); + headerStyle.setBorderBottom(BorderStyle.THIN); + headerStyle.setBorderTop(BorderStyle.THIN); + headerStyle.setBorderLeft(BorderStyle.THIN); + headerStyle.setBorderRight(BorderStyle.THIN); + + CellStyle dataStyle = workbook.createCellStyle(); + dataStyle.setAlignment(HorizontalAlignment.CENTER); + dataStyle.setBorderBottom(BorderStyle.THIN); + dataStyle.setBorderTop(BorderStyle.THIN); + dataStyle.setBorderLeft(BorderStyle.THIN); + dataStyle.setBorderRight(BorderStyle.THIN); + + // 学员信息行 + Row infoRow = sheet.createRow(0); + infoRow.createCell(0).setCellValue("学员:" + student.getXm()); + infoRow.createCell(1).setCellValue("学号:" + student.getXh()); + + // 表头 + String[] headers = {"年度", "科目编号", "考试成绩", "平时成绩", "最终成绩", + "补考成绩", "补考次数", "考试情况", "已通过", "备注"}; + Row headerRow = sheet.createRow(2); + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headerStyle); + } + + // 数据行 + int rowIdx = 3; + for (XYKCCJ g : grades) { + Row row = sheet.createRow(rowIdx++); + row.createCell(0).setCellValue(g.getNd() != null ? g.getNd().toString() : ""); + row.createCell(1).setCellValue(g.getKmbh() != null ? g.getKmbh() : ""); + row.createCell(2).setCellValue(g.getKscj() != null ? g.getKscj().toString() : ""); + row.createCell(3).setCellValue(g.getPscj() != null ? g.getPscj().toString() : ""); + row.createCell(4).setCellValue(g.getZzcj() != null ? g.getZzcj().toString() : ""); + row.createCell(5).setCellValue(g.getBkcj() != null ? g.getBkcj().toString() : ""); + row.createCell(6).setCellValue(g.getBkcs() != null ? g.getBkcs().toString() : ""); + row.createCell(7).setCellValue(g.getKsqk() != null ? g.getKsqk() : ""); + row.createCell(8).setCellValue(g.getYtg() != null && g.getYtg() == 1 ? "是" : "否"); + row.createCell(9).setCellValue(g.getBz() != null ? g.getBz() : ""); + + for (int i = 0; i < 10; i++) { + row.getCell(i).setCellStyle(dataStyle); + } + } + + // 自动列宽 + for (int i = 0; i < headers.length; i++) { + sheet.autoSizeColumn(i); + } + + // 输出 + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", + "attachment; filename=" + URLEncoder.encode( + student.getXm() + "_课程成绩.xlsx", "UTF-8")); + workbook.write(response.getOutputStream()); + response.getOutputStream().flush(); + + } catch (IOException e) { + throw new RuntimeException("导出 Excel 失败", e); + } + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/StudentTeamOutputScheduleServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/StudentTeamOutputScheduleServiceImpl.java new file mode 100644 index 000000000..1febdee25 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/StudentTeamOutputScheduleServiceImpl.java @@ -0,0 +1,62 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDSCKCB; +import com.roomroot.jwgl.mapper.StudentTeamOutputScheduleMapper; +import com.roomroot.jwgl.service.StudentTeamOutputScheduleService; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 学员队输出课程表 Service实现 + */ +@Service +public class StudentTeamOutputScheduleServiceImpl implements StudentTeamOutputScheduleService { + + @Autowired + private StudentTeamOutputScheduleMapper xydsckcbMapper; + + @Override + public void add(XYDSCKCB xydsckcb) { + xydsckcb.setBh(UuidUtil.getUUID()); + xydsckcb.setDelFlag(0); + xydsckcbMapper.insert(xydsckcb); + } + + @Override + public void delete(String bh) { + xydsckcbMapper.deleteById(bh); + } + + @Override + public void update(XYDSCKCB xydsckcb) { + xydsckcbMapper.updateById(xydsckcb); + } + + @Override + public XYDSCKCB getByBh(String bh) { + return xydsckcbMapper.selectByBh(bh); + } + + @Override + public XYDSCKCB getByNdAndXydbh(Integer nd, String xydbh) { + return xydsckcbMapper.selectByNdAndXydbh(nd, xydbh); + } + + @Override + public Page list(int pageNum, int pageSize, XYDSCKCB xydsckcb) { + Page page = new Page<>(pageNum, pageSize); + return xydsckcbMapper.selectPageList(page, xydsckcb); + } + + @Override + public List all() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XYDSCKCB::getDelFlag, 0); + return xydsckcbMapper.selectList(wrapper); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/StudentTeamTaskServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/StudentTeamTaskServiceImpl.java new file mode 100644 index 000000000..a8a63d3af --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/StudentTeamTaskServiceImpl.java @@ -0,0 +1,239 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.XYDB; +import com.roomroot.jwgl.entity.XYDNDXQJBXXB; +import com.roomroot.jwgl.entity.XYDRWB; +import com.roomroot.jwgl.entity.ZYJXJHB; +import com.roomroot.jwgl.mapper.ClassSemesterMapper; +import com.roomroot.jwgl.mapper.StudentTeamTaskMapper; +import com.roomroot.jwgl.mapper.XYDBMapper; +import com.roomroot.jwgl.mapper.ZYJXJHBMapper; +import com.roomroot.jwgl.service.StudentTeamTaskService; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 学员队任务表 Service实现 + */ +@Service +public class StudentTeamTaskServiceImpl implements StudentTeamTaskService { + + @Autowired + private StudentTeamTaskMapper xydrwbMapper; + + @Autowired + private ClassSemesterMapper classSemesterMapper; + + @Autowired + private XYDBMapper xydbMapper; + + @Autowired + private ZYJXJHBMapper zyjxjhbMapper; + + @Override + public void add(XYDRWB xydrwb) { + xydrwb.setBh(UuidUtil.getUUID()); + xydrwb.setDelFlag(0); + xydrwbMapper.insert(xydrwb); + } + + @Override + public void delete(String bh) { + xydrwbMapper.deleteById(bh); + } + + @Override + public void update(XYDRWB xydrwb) { + xydrwbMapper.updateById(xydrwb); + } + + @Override + public XYDRWB getByBh(String bh) { + return xydrwbMapper.selectById(bh); + } + + @Override + public Page list(int pageNum, int pageSize, XYDRWB xydrwb) { + Page page = new Page<>(pageNum, pageSize); + return xydrwbMapper.selectPageList(page, xydrwb); + } + + @Override + public Page groupList(int pageNum, int pageSize, XYDRWB xydrwb) { + Page page = new Page<>(pageNum, pageSize); + return xydrwbMapper.selectPageListGroup(page, xydrwb); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public int batchPresetMerge(List bhList) { + if (CollectionUtils.isEmpty(bhList)) { + return 0; + } + + // 查询所有选中的记录 + List tasks = bhList.stream() + .map(bh -> xydrwbMapper.selectById(bh)) + .filter(task -> task != null) + .sorted(Comparator.comparing(XYDRWB::getNd, Comparator.nullsLast(Comparator.naturalOrder()))) + .collect(Collectors.toList()); + + if (tasks.isEmpty()) { + return 0; + } + + // 获取第一条记录的年度,查询该年度的最大课次序号 + Integer nd = tasks.get(0).getNd(); + Integer maxKcxh = xydrwbMapper.selectMaxKcxhByNd(nd); + int newKcxh = (maxKcxh == null ? 0 : maxKcxh) + 1; + + // 为所有记录设置统一的课次序号 + int count = 0; + for (XYDRWB task : tasks) { + task.setKcxh(newKcxh); + xydrwbMapper.updateById(task); + count++; + } + return count; + } + + @Transactional(rollbackFor = Exception.class) + @Override + public int batchPresetSplit(List bhList) { + if (CollectionUtils.isEmpty(bhList)) { + return 0; + } + + int count = 0; + for (String bh : bhList) { + XYDRWB task = xydrwbMapper.selectById(bh); + if (task != null) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(XYDRWB::getBh, bh) + .set(XYDRWB::getKcxh, null); + xydrwbMapper.update(null, wrapper); + count++; + } + } + return count; + } + + @Override + public List all() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(XYDRWB::getDelFlag, 0); + return xydrwbMapper.selectList(wrapper); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public int autoGenerateRequiredCourses(String xydbh, Integer xqdc) { + // 1. 根据学员队编号查询学员队表,获取专业代号 + XYDB xydb = xydbMapper.selectById(xydbh); + if (xydb == null || xydb.getZydh() == null) { + return 0; + } + String zydh = xydb.getZydh(); + + // 2. 根据专业代号和学期第次查询专业教学计划表(停用=0) + LambdaQueryWrapper planWrapper = new LambdaQueryWrapper<>(); + planWrapper.eq(ZYJXJHB::getZydh, zydh) + .eq(ZYJXJHB::getXqdc, xqdc) + .eq(ZYJXJHB::getTy, 0); + List planList = zyjxjhbMapper.selectList(planWrapper); + if (CollectionUtils.isEmpty(planList)) { + return 0; + } + + // 3. 根据学员队编号和学期第次查询学员队期表,获取年度和学员队学期编号 + LambdaQueryWrapper semesterWrapper = new LambdaQueryWrapper<>(); + semesterWrapper.eq(XYDNDXQJBXXB::getXydbh, xydbh) + .eq(XYDNDXQJBXXB::getXqdc, xqdc) + .eq(XYDNDXQJBXXB::getDelFlag, 0) + .orderByDesc(XYDNDXQJBXXB::getNd) + .last("LIMIT 1"); + XYDNDXQJBXXB semester = classSemesterMapper.selectOne(semesterWrapper); + if (semester == null) { + return 0; + } + Integer nd = semester.getNd(); + String xydxqbh = semester.getBh(); + + // 4. 查询当前班次已存在的课程编号集合 + LambdaQueryWrapper existWrapper = new LambdaQueryWrapper<>(); + existWrapper.eq(XYDRWB::getXydxqbh, xydxqbh) + .eq(XYDRWB::getDelFlag, 0); + List existList = xydrwbMapper.selectList(existWrapper); + List existKbhs = existList.stream() + .map(XYDRWB::getKbh) + .filter(kbh -> kbh != null) + .collect(Collectors.toList()); + + // 5. 只添加缺少的课程 + int count = 0; + for (ZYJXJHB plan : planList) { + if (existKbhs.contains(plan.getKbh())) { + continue; + } + + XYDRWB newRecord = new XYDRWB(); + newRecord.setBh(UuidUtil.getUUID()); + newRecord.setNd(nd); + newRecord.setXydbh(xydbh); + newRecord.setKbh(plan.getKbh()); + newRecord.setXqdc(xqdc); + newRecord.setKlx(plan.getKlx()); + newRecord.setXs(plan.getXs()); + newRecord.setJc(plan.getJc()); + newRecord.setZks(plan.getZks()); + newRecord.setXf(plan.getXf()); + newRecord.setKsks(plan.getKsks()); + newRecord.setCjfz(plan.getCjfz()); + newRecord.setBjrxypjf(plan.getBjrxypjf()); + newRecord.setLlxs(plan.getLlxs()); + newRecord.setSjxs(plan.getSjxs()); + newRecord.setKsksbxs(plan.getKsksbxs()); + newRecord.setJysdh(plan.getJysdh()); + newRecord.setXydxqbh(xydxqbh); + newRecord.setJhkcxh(count+1); + newRecord.setXhbs(count+1); + newRecord.setBz2(0); + newRecord.setPdtbykxxbz(1); + newRecord.setPdqyzdyxs(0); + newRecord.setDelFlag(0); + + xydrwbMapper.insert(newRecord); + count++; + } + return count; + } + + @Override + public List copy(List bhList, String newXydbh) { + List newBhList = new java.util.ArrayList<>(); + for (String bh : bhList) { + XYDRWB source = xydrwbMapper.selectById(bh); + if (source == null) { + continue; + } + + String newBh = UuidUtil.getUUID(); + source.setBh(newBh); + source.setXydbh(newXydbh); + source.setDelFlag(0); + xydrwbMapper.insert(source); + newBhList.add(newBh); + } + return newBhList; + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/SystemInitializationServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/SystemInitializationServiceImpl.java new file mode 100644 index 000000000..104a2ea9c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/SystemInitializationServiceImpl.java @@ -0,0 +1,374 @@ +package com.roomroot.jwgl.service.impl; + +import com.roomroot.jwgl.dto.systeminitialization.*; +import com.roomroot.jwgl.mapper.SystemInitializationMapper; +import com.roomroot.jwgl.service.SystemInitializationService; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.utils.UuidUtil; +import com.roomroot.jwgl.vo.systeminitialization.*; +import com.roomroot.jwgl.vo.systeminitialization.*; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + + +import java.time.LocalDateTime; + +import static com.roomroot.jwgl.utils.BasicManagementConstants.CONFLICT; +import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND; +import static com.roomroot.jwgl.utils.SystemInitializationUtil.*; + +/** + * 系统初始化管理服务实现。 + * + *

当前实现只编排系统初始化业务流程,参数校验、Excel 解析、 + * 历史数据引用检查和返回对象组装统一由工具类负责。

+ */ +@Service +public class SystemInitializationServiceImpl + implements SystemInitializationService { + + /** + * 系统初始化数据访问对象。 + */ + @Resource + private SystemInitializationMapper systemInitializationMapper; + + /** + * 查询当前生效的系统基础参数。 + * + * @return 当前系统基础参数 + */ + @Override + @Transactional(readOnly = true) + public SystemInitializationConfigurationVO + getCurrentConfiguration() { + // 第一步:按照系统版本表最大编号查询当前生效的基础参数。 + SystemInitializationConfigurationVO configuration = + systemInitializationMapper + .selectCurrentConfiguration(); + + // 第二步:系统尚未建立任何版本配置时返回明确的数据不存在异常。 + if (configuration == null) { + throw new BusinessException( + NOT_FOUND, + "当前系统尚未配置初始化参数"); + } + + // 第三步:返回已经由 Mapper 显式转换完成的系统基础参数 VO。 + return configuration; + } + + /** + * 保存新的系统基础参数版本。 + * + * @param dto 系统基础参数 + * @return 保存后立即生效的新版本 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public SystemInitializationConfigurationVO saveConfiguration( + SystemInitializationConfigurationSaveDTO dto) { + // 第一步:查询当前配置,用于判断首次初始化和执行乐观版本校验。 + SystemInitializationConfigurationVO current = + systemInitializationMapper + .selectCurrentConfiguration(); + + // 第二步:规范化页面参数并校验期望版本与当前版本一致。 + SystemInitializationConfigurationSaveDTO configuration = + prepareConfiguration(dto, current); + + // 第三步:使用同一服务端时间生成本次配置版本时间。 + LocalDateTime versionDate = LocalDateTime.now(); + + /* + * 第四步:首次配置只在版本表仍为空时写入; + * 已有配置时复制全部非页面字段并新增下一条版本记录。 + */ + int affectedRows; + if (current == null) { + affectedRows = + systemInitializationMapper + .insertInitialConfiguration( + configuration, + versionDate); + } else { + affectedRows = + systemInitializationMapper + .insertNextConfigurationVersion( + configuration, + versionDate); + } + + // 第五步:影响行数不是一条说明配置在保存期间发生并发变化。 + requireAffectedRows( + affectedRows, + 1, + "系统基础参数已变化,请刷新后重试"); + + // 第六步:重新读取并返回刚刚生效的最新配置版本。 + SystemInitializationConfigurationVO saved = + systemInitializationMapper + .selectCurrentConfiguration(); + if (saved == null) { + throw new BusinessException( + "系统基础参数保存后读取失败"); + } + return saved; + } + + /** + * 查询当前学期。 + * + * @return 当前学期 + */ + @Override + @Transactional(readOnly = true) + public SemesterInitializationVO getCurrentSemester() { + // 第一步:查询当前学期标志对应的最新学期记录。 + SemesterInitializationVO semester = + systemInitializationMapper + .selectCurrentSemester(); + + // 第二步:尚未设置当前学期时返回明确的数据不存在异常。 + if (semester == null) { + throw new BusinessException( + NOT_FOUND, + "当前系统尚未设置当前学期"); + } + + // 第三步:补充统一展示名称并返回学期参数。 + return completeSemesterName(semester); + } + + /** + * 保存指定年度和序号的学期。 + * + * @param dto 学期初始化参数 + * @return 保存后的学期 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public SemesterInitializationVO saveSemester( + SemesterInitializationSaveDTO dto) { + // 第一步:规范化并校验学期年度、序号、时间范围和默认业务标志。 + SemesterInitializationSaveDTO semester = + prepareSemester(dto); + + // 第二步:禁止当前学期时间范围与其他学期发生重叠。 + int overlapCount = + systemInitializationMapper + .countOverlappingSemesters(semester); + if (overlapCount > 0) { + throw new BusinessException( + CONFLICT, + "学期时间范围与已有学期重叠"); + } + + // 第三步:判断同一年度和序号的学期记录是否已经存在。 + SemesterInitializationVO existing = + systemInitializationMapper.selectSemesterByKey( + semester.getYear(), + semester.getSequenceNumber()); + + // 第四步:目标学期设为当前学期时先清除其他记录的当前标志。 + if (Boolean.TRUE.equals( + semester.getCurrentSemester())) { + systemInitializationMapper + .clearOtherCurrentSemesters( + semester.getYear(), + semester.getSequenceNumber()); + } + + // 第五步:根据业务键是否存在选择新增或修改,不重复创建同一学期。 + int affectedRows; + if (existing == null) { + affectedRows = + systemInitializationMapper + .insertSemester(semester); + } else { + affectedRows = + systemInitializationMapper + .updateSemester(semester); + } + + // 第六步:保存未精确影响一条记录时中止整个学期切换事务。 + requireAffectedRows( + affectedRows, + 1, + "学期参数保存失败,请刷新后重试"); + + // 第七步:重新查询并返回保存后的完整学期参数。 + SemesterInitializationVO saved = + systemInitializationMapper.selectSemesterByKey( + semester.getYear(), + semester.getSequenceNumber()); + if (saved == null) { + throw new BusinessException( + "学期参数保存后读取失败"); + } + return completeSemesterName(saved); + } + + /** + * 导入历史基础数据。 + * + * @param dto 历史数据 Excel 文件 + * @return 导入结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public SystemInitializationHistoricalImportResultVO + importHistoricalData( + SystemInitializationHistoricalImportDTO dto) { + // 第一步:解析 Excel 并完成工作表、标题、类型、长度和文件内重复校验。 + SystemInitializationHistoricalDataDTO data = + parseHistoricalData(dto); + + /* + * 第二步:一次性读取数据库已有编号, + * 在任何写入发生前完成覆盖冲突、所属部别引用和部别循环关系校验。 + */ + data = prepareHistoricalData( + data, + systemInitializationMapper.selectDepartmentIds(), + systemInitializationMapper + .selectResearchOfficeIds(), + systemInitializationMapper.selectPersonnelIds()); + + // 第三步:由服务端为每条教研室隶属关系生成独立关系编号。 + for (SystemInitializationResearchOfficeImportRowDTO row + : data.getResearchOffices()) { + row.setRelationId(UuidUtil.getOriginalUUID()); + } + + // 第四步:使用统一时间记录历史停用数据状态和最终导入完成时间。 + LocalDateTime importedAt = LocalDateTime.now(); + + // 第五步:按照父节点优先顺序批量写入历史部别。 + if (!data.getDepartments().isEmpty()) { + int departmentRows = + systemInitializationMapper + .insertHistoricalDepartments( + data.getDepartments(), + importedAt); + requireAffectedRows( + departmentRows, + data.getDepartments().size(), + "历史部别数据写入不完整"); + } + + // 第六步:批量写入教研室基础信息。 + if (!data.getResearchOffices().isEmpty()) { + int researchOfficeRows = + systemInitializationMapper + .insertHistoricalResearchOffices( + data.getResearchOffices(), + importedAt); + requireAffectedRows( + researchOfficeRows, + data.getResearchOffices().size(), + "历史教研室数据写入不完整"); + + // 第七步:在教研室写入成功后批量建立对应的部别隶属关系。 + int relationRows = + systemInitializationMapper + .insertHistoricalResearchOfficeRelations( + data.getResearchOffices()); + requireAffectedRows( + relationRows, + data.getResearchOffices().size(), + "历史教研室隶属关系写入不完整"); + } + + // 第八步:批量写入部别人员,并保存原有在职和主官状态。 + if (!data.getPersonnel().isEmpty()) { + int personnelRows = + systemInitializationMapper + .insertHistoricalPersonnel( + data.getPersonnel()); + requireAffectedRows( + personnelRows, + data.getPersonnel().size(), + "历史部别人员数据写入不完整"); + } + + // 第九步:组装并返回本次事务实际完成的四张表写入数量。 + return buildHistoricalImportResult( + data, + importedAt); + } + + /** + * 执行系统初始化预检查。 + * + * @return 预检查汇总结果 + */ + @Override + @Transactional(readOnly = true) + public SystemInitializationPrecheckVO precheck() { + // 第一步:执行最小数据库查询,验证当前连接和 SQL 执行能力。 + int databaseHealth = + systemInitializationMapper + .selectDatabaseHealth(); + + // 第二步:查询当前配置和当前学期,允许空值交给预检查项统一说明。 + SystemInitializationConfigurationVO configuration = + systemInitializationMapper + .selectCurrentConfiguration(); + SemesterInitializationVO semester = + systemInitializationMapper + .selectCurrentSemester(); + + // 第三步:统计三类初始化基础组织数据。 + int departmentCount = + systemInitializationMapper.countDepartments(); + int researchOfficeCount = + systemInitializationMapper + .countResearchOffices(); + int personnelCount = + systemInitializationMapper.countPersonnel(); + + // 第四步:统计部别层级、教研室隶属关系和人员所属部别中的失效引用。 + int brokenReferenceCount = + systemInitializationMapper + .countBrokenBaseReferences(); + + // 第五步:组装并返回包含全部检查项的统一预检查结果。 + return buildPrecheck( + databaseHealth, + configuration, + semester, + departmentCount, + researchOfficeCount, + personnelCount, + brokenReferenceCount, + LocalDateTime.now()); + } + + /** + * 完成系统初始化最终确认。 + * + * @param dto 页面预检查快照和确认标志 + * @return 初始化执行结果 + */ + @Override + @Transactional(readOnly = true) + public SystemInitializationExecutionResultVO execute( + SystemInitializationExecutionDTO dto) { + // 第一步:重新执行完整预检查,不信任页面缓存的检查结果。 + SystemInitializationPrecheckVO currentPrecheck = + precheck(); + + /* + * 第二步:校验明确确认标志、全部检查项、 + * 配置版本和当前学期均与页面预检查快照一致。 + */ + validateExecution(dto, currentPrecheck); + + // 第三步:返回最终确认使用的配置版本、学期和完成时间。 + return buildExecutionResult( + currentPrecheck, + LocalDateTime.now()); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingMaterialServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingMaterialServiceImpl.java new file mode 100644 index 000000000..9a125d458 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingMaterialServiceImpl.java @@ -0,0 +1,380 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JCXX; +import com.roomroot.jwgl.mapper.TeachingMaterialMapper; +import com.roomroot.jwgl.service.TeachingMaterialService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.DateUtils; +import com.roomroot.jwgl.utils.UuidUtil; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.core.io.ClassPathResource; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import jakarta.annotation.Resource; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigDecimal; +import java.net.URLEncoder; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * 教材信息服务实现类 + */ +@Service +public class TeachingMaterialServiceImpl implements TeachingMaterialService { + + @Resource + private TeachingMaterialMapper teachingMaterialMapper; + + @Override + public void add(JCXX jcxx) { + String uuid = UuidUtil.getUUID(); + jcxx.setId(uuid); + jcxx.setDelFlag(0); + jcxx.setCjsj(LocalDateTime.now()); + jcxx.setXgsj(LocalDateTime.now()); + String nextNum = getNextNumber(); + nextNum = getNextZeroFillCode(nextNum,4); + jcxx.setBh(nextNum); + teachingMaterialMapper.insert(jcxx); + } + + @Override + public void delete(String id) { + JCXX jcxx = getById(id); + jcxx.setDelFlag(1); + jcxx.setXgsj(LocalDateTime.now()); + teachingMaterialMapper.updateById(jcxx); + } + + @Override + public void update(JCXX jcxx) { + jcxx.setXgsj(LocalDateTime.now()); + teachingMaterialMapper.updateById(jcxx); + } + + @Override + public JCXX getByBh(String bh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(JCXX::getBh, bh); + return teachingMaterialMapper.selectOne(wrapper); + } + + @Override + public JCXX getById(String id) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(JCXX::getId, id); + return teachingMaterialMapper.selectOne(wrapper); + } + + @Override + public PageResult pageList(PageQuery query, JCXX jcxx) { + jcxx.setDelFlag(0); + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = teachingMaterialMapper.selectPageList(page, jcxx); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List list(JCXX jcxx) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jcxx != null) { + if (jcxx.getBh() != null && !jcxx.getBh().isEmpty()) { + wrapper.like(JCXX::getBh, jcxx.getBh()); + } + if (jcxx.getMc() != null && !jcxx.getMc().isEmpty()) { + wrapper.like(JCXX::getMc, jcxx.getMc()); + } + if (jcxx.getIsbn() != null && !jcxx.getIsbn().isEmpty()) { + wrapper.like(JCXX::getIsbn, jcxx.getIsbn()); + } + } + wrapper.orderByDesc(JCXX::getCjsj); + return teachingMaterialMapper.selectList(wrapper); + } + + @Override + public int importExcel(MultipartFile file) { + try (InputStream is = file.getInputStream(); + Workbook workbook = file.getOriginalFilename().endsWith(".xlsx") + ? new XSSFWorkbook(is) : new HSSFWorkbook(is)) { + + // 获取教材信息sheet + Sheet sheet = workbook.getSheet("教材信息"); + if (sheet == null) { + sheet = workbook.getSheetAt(0); + } + int count = 0; + + // 在循环外获取起始编号 + String nextNum = getNextNumber(); + + // 从第二行开始读取数据(跳过表头) + for (int i = 1; i <= sheet.getLastRowNum(); i++) { + Row row = sheet.getRow(i); + if (row == null) continue; + + //获取编号 + // 跳过空行 + String bh = getCellValue(row, 0); + + JCXX jcxx = getByBh(bh); + String flag = "0"; + if(jcxx == null){ + jcxx = new JCXX(); + jcxx.setId(UuidUtil.getUUID()); + jcxx.setDelFlag(0); + jcxx.setCjsj(LocalDateTime.now()); + // 生成编号(数字字符串) + jcxx.setBh(bh == null?String.valueOf(nextNum):bh); + nextNum = getNextZeroFillCode(nextNum,4); + flag = "1"; + } + jcxx.setXgsj(LocalDateTime.now()); + + // 教研室 + jcxx.setJysdh(getCellValue(row, 1)); + + // 教材代码 + jcxx.setJcdm(getCellValue(row, 2)); + + // 跳过空行 + String mc = getCellValue(row, 3); + if (mc == null || mc.isEmpty()) continue; + + // 名称 + jcxx.setMc(mc); + + // ISBN + jcxx.setIsbn(getCellValue(row, 4)); + + // 出版社 + jcxx.setCbs(getCellValue(row, 5)); + + // 版次 + jcxx.setBc(getCellValue(row, 6)); + + // 开本 + jcxx.setKb(getCellValue(row, 7)); + + // 作者 + //主编 + String zb = getCellValue(row, 8) == null ? "" : getCellValue(row, 8); + //副主编 + String fzb = getCellValue(row, 9) == null ? "" : getCellValue(row, 9); + //编者 + String bz = getCellValue(row, 10)==null?"":getCellValue(row, 10); + + + jcxx.setZz(zb+" "+fzb+" "+bz); + + // 定价 + String djStr = getCellValue(row, 11); + if (djStr != null && !djStr.isEmpty()) { + try { + jcxx.setDj(new BigDecimal(djStr)); + } catch (NumberFormatException e) { + jcxx.setDj(BigDecimal.ZERO); + } + } + + // 教材分类 + jcxx.setJcfl(getCellValue(row, 12)); + + // 教材类型 + jcxx.setJclx(getCellValue(row, 13)); + + // 教材编写类型 + jcxx.setJcbxlx(getCellValue(row, 14)); + + //教材编写类型 + jcxx.setJcbxlx(getCellValue(row, 15)==null?"":getCellValue(row, 15)); + //教材密级 + jcxx.setJcmj(getCellValue(row, 16)==null?"":getCellValue(row, 16)); + + //编写单位 + jcxx.setBxdw(getCellValue(row, 17)==null?"":getCellValue(row, 17)); + + //责任教研室 + jcxx.setZrjyjlxff(getCellValue(row, 18)==null?"":getCellValue(row, 18)); + //教材重点类型 + jcxx.setJczdlx(getCellValue(row, 19)==null?"":getCellValue(row, 19)); + //教材层次 + jcxx.setJccc(getCellValue(row, 20)==null?"":getCellValue(row, 20)); + //教材来源 + jcxx.setJcly(getCellValue(row, 21)==null?"":getCellValue(row, 21)); + //出版方式 + jcxx.setCbfs(getCellValue(row, 22)==null?"":getCellValue(row, 22)); + //计量单位 + jcxx.setJldw(getCellValue(row, 23)==null?"":getCellValue(row, 23)); + //出版日期 + String dateStr = getCellValue(row, 24)==null?"":getCellValue(row, 24); + LocalDateTime cbrq = DateUtils.parseAnyDate(dateStr); + jcxx.setCbrq(cbrq); + //自编教材立项日期 + jcxx.setJcmj(getCellValue(row, 25)==null?"":getCellValue(row, 25)); + //评选情况 + jcxx.setJcmj(getCellValue(row, 26)==null?"":getCellValue(row, 26)); + //颁发审定单位 + jcxx.setJcmj(getCellValue(row, 27)==null?"":getCellValue(row, 27)); + //责任教员及联系方式 + jcxx.setJcmj(getCellValue(row, 28)==null?"":getCellValue(row, 28)); + //需彩印 + jcxx.setJcmj(getCellValue(row, 29)==null?"":getCellValue(row, 29)); + //外协排版 + jcxx.setJcmj(getCellValue(row, 30)==null?"":getCellValue(row, 30)); + //备注 + jcxx.setJcmj(getCellValue(row, 31)==null?"":getCellValue(row, 31)); + //停用 + jcxx.setTy(false); + jcxx.setXcy(false); + + if("1".equals(flag)){ + teachingMaterialMapper.insert(jcxx); + }else if("0".equals(flag)){ + teachingMaterialMapper.updateById(jcxx); + } + count++; + } + return count; + } catch (Exception e) { + throw new RuntimeException("Excel解析失败: " + e.getMessage(), e); + } + } + + + @Override + public void downloadTemplate(HttpServletResponse response) { + try { + // 从resources/template目录读取模板文件 + ClassPathResource resource = new ClassPathResource("template/教材管理.xls"); + InputStream is = resource.getInputStream(); + + // 设置响应头 + response.setContentType("application/vnd.ms-excel"); + response.setHeader("Content-Disposition", "attachment;filename=" + + URLEncoder.encode("教材管理导入模板.xls", "UTF-8")); + + // 输出文件 + OutputStream os = response.getOutputStream(); + byte[] buffer = new byte[1024]; + int len; + while ((len = is.read(buffer)) != -1) { + os.write(buffer, 0, len); + } + os.flush(); + is.close(); + os.close(); + } catch (Exception e) { + throw new RuntimeException("下载模板失败: " + e.getMessage(), e); + } + } + + /** + * 获取下一个编号(四位补零) + */ + private String getNextNumber() { + String maxBh = teachingMaterialMapper.selectMaxBh(); + String nextNum = "0001"; + if (maxBh != null && !maxBh.isEmpty()) { + try { + // 去掉前导零后解析 + nextNum = maxBh; + } catch (NumberFormatException e) { + nextNum = "0001"; + } + } + return nextNum; + } + + /** + * 根据数据库最大补零编号,生成下一个编号 + * @param maxZeroCode 数据库查出的最大补零编号,为空则从00001开始 + * @param length 固定补零长度,例:5=00001 + * @return 下一个补零字符串编号 + */ + public static String getNextZeroFillCode(String maxZeroCode, int length) { + int num = 1; + // 数据库有编号则解析+1 + if (maxZeroCode != null && maxZeroCode.trim().length() > 0) { + num = Integer.parseInt(maxZeroCode.trim()) + 1; + } + // 格式化补零 + String format = "%0" + length + "d"; + return String.format(format, num); + } + + /** + * 获取单元格字符串值 + */ + private String getCellValue(Row row, int index) { + Cell cell = row.getCell(index, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK); + if (cell == null) { + return null; + } + + CellType type = cell.getCellType(); + // 处理公式单元格 + if (type == CellType.FORMULA) { + type = cell.getCachedFormulaResultType(); + } + + switch (type) { + case NUMERIC: + // 判断是否是日期格式 + if (DateUtil.isCellDateFormatted(cell)) { + java.util.Date date = cell.getDateCellValue(); + if (date != null) { + java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy/MM/dd"); + return sdf.format(date); + } + return null; + } + double num = cell.getNumericCellValue(); + // 整数就去掉小数点后多余的 .0 + if (num == Math.floor(num)) { + return String.valueOf((long) num); + } else { + return String.valueOf(num); + } + case STRING: + return cell.getStringCellValue().trim(); + case BOOLEAN: + return String.valueOf(cell.getBooleanCellValue()); + default: + return null; + } + } + + /** + * 解析日期时间字符串 + */ + private LocalDateTime parseDateTime(String dateStr) { + // 尝试多种日期格式 + String[] patterns = { + "yyyy-MM-dd HH:mm:ss", + "yyyy-MM-dd", + "yyyy/MM/dd", + "yyyy年MM月dd日" + }; + for (String pattern : patterns) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + return LocalDateTime.parse(dateStr, formatter); + } catch (Exception e) { + // 继续尝试下一个格式 + } + } + return null; + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingPlanServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingPlanServiceImpl.java new file mode 100644 index 000000000..df04c9030 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingPlanServiceImpl.java @@ -0,0 +1,107 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JXSSJH; +import com.roomroot.jwgl.mapper.TeachingPlanMapper; +import com.roomroot.jwgl.service.TeachingPlanService; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.ExcelParseUtil; +import com.roomroot.jwgl.utils.UuidUtil; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * 导入数据表-教学实施计划服务实现类 + */ +@Service +public class TeachingPlanServiceImpl implements TeachingPlanService { + + @Resource + private TeachingPlanMapper teachingPlanMapper; + + @Override + public void add(JXSSJH drsjbjxssjh) { + String uuid = UuidUtil.getUUID(); + drsjbjxssjh.setBsh(uuid); + drsjbjxssjh.setDelFlag(0); + teachingPlanMapper.insert(drsjbjxssjh); + } + + @Override + public void delete(String bsh) { + JXSSJH drsjbjxssjh = getByBsh(bsh); + drsjbjxssjh.setDelFlag(1); + teachingPlanMapper.updateById(drsjbjxssjh); + } + + @Override + public void update(JXSSJH drsjbjxssjh) { + teachingPlanMapper.updateById(drsjbjxssjh); + } + + @Override + public JXSSJH getByBsh(String bsh) { + return teachingPlanMapper.selectById(bsh); + } + + @Override + public PageResult pageList(PageQuery query, JXSSJH drsjbjxssjh) { + drsjbjxssjh.setDelFlag(0); + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = teachingPlanMapper.selectPageList(page, drsjbjxssjh); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List list(JXSSJH drsjbjxssjh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (drsjbjxssjh != null) { + if (drsjbjxssjh.getNd() != null) { + wrapper.eq(JXSSJH::getNd, drsjbjxssjh.getNd()); + } + if (drsjbjxssjh.getKcmc() != null && !drsjbjxssjh.getKcmc().isEmpty()) { + wrapper.like(JXSSJH::getKcmc, drsjbjxssjh.getKcmc()); + } + if (drsjbjxssjh.getYtgjc() != null) { + wrapper.eq(JXSSJH::getYtgjc, drsjbjxssjh.getYtgjc()); + } + } + wrapper.orderByAsc(JXSSJH::getXh); + return teachingPlanMapper.selectList(wrapper); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int importFromExcel(MultipartFile file) throws Exception { + if (file == null || file.isEmpty()) { + throw new BusinessException("导入文件不能为空"); + } + List list = ExcelParseUtil.parseJXSSJHExcel( + file.getInputStream(), file.getOriginalFilename()); + if (list.isEmpty()) { + throw new BusinessException("Excel中无有效数据"); + } + for (JXSSJH entity : list) { + // 标识号自动生成 + entity.setBsh(UuidUtil.getUUID()); + // 登录编号自动生成 + entity.setDlbh(UuidUtil.getUUID()); + // 默认值 + entity.setYtgjc(false); + entity.setYbhl(false); + entity.setYbdr(false); + entity.setYbcl(false); + entity.setDelFlag(0); + teachingPlanMapper.insert(entity); + } + return list.size(); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingStockDetailServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingStockDetailServiceImpl.java new file mode 100644 index 000000000..75dd0f124 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingStockDetailServiceImpl.java @@ -0,0 +1,106 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JCKCMXX; +import com.roomroot.jwgl.mapper.TeachingStockDetailMapper; +import com.roomroot.jwgl.service.TeachingStockDetailService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 教材库存明细项服务实现类 + */ +@Service +public class TeachingStockDetailServiceImpl implements TeachingStockDetailService { + + @Resource + private TeachingStockDetailMapper teachingStockDetailMapper; + + @Override + public void add(JCKCMXX jckcmxx) { + String uuid = UuidUtil.getUUID(); + jckcmxx.setId(uuid); + jckcmxx.setDelFlag(0); + // 生成编号(四位补零) + jckcmxx.setBh(getNextNumber()); + teachingStockDetailMapper.insert(jckcmxx); + } + + @Override + public void delete(String id) { + JCKCMXX jckcmxx = getById(id); + jckcmxx.setDelFlag(1); + teachingStockDetailMapper.updateById(jckcmxx); + } + + @Override + public void update(JCKCMXX jckcmxx) { + teachingStockDetailMapper.updateById(jckcmxx); + } + + @Override + public JCKCMXX getById(String id) { + return teachingStockDetailMapper.selectById(id); + } + + @Override + public JCKCMXX getByBh(String bh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(JCKCMXX::getBh, bh); + return teachingStockDetailMapper.selectOne(wrapper); + } + + @Override + public List getByJckcdbbh(String jckcdbbh) { + return teachingStockDetailMapper.selectByJckcdbbh(jckcdbbh); + } + + @Override + public PageResult pageList(PageQuery query, JCKCMXX jckcmxx) { + jckcmxx.setDelFlag(0); + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = teachingStockDetailMapper.selectPageList(page, jckcmxx); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List list(JCKCMXX jckcmxx) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jckcmxx != null) { + if (jckcmxx.getBh() != null && !jckcmxx.getBh().isEmpty()) { + wrapper.like(JCKCMXX::getBh, jckcmxx.getBh()); + } + if (jckcmxx.getJckcdbbh() != null && !jckcmxx.getJckcdbbh().isEmpty()) { + wrapper.eq(JCKCMXX::getJckcdbbh, jckcmxx.getJckcdbbh()); + } + if (jckcmxx.getJcxxbh() != null && !jckcmxx.getJcxxbh().isEmpty()) { + wrapper.like(JCKCMXX::getJcxxbh, jckcmxx.getJcxxbh()); + } + } + return teachingStockDetailMapper.selectList(wrapper); + } + + /** + * 获取下一个编号(四位补零) + */ + private String getNextNumber() { + String maxBh = teachingStockDetailMapper.selectMaxBh(); + int nextNum = 1; + if (maxBh != null && !maxBh.isEmpty()) { + try { + // 去掉前导零后解析 + nextNum = Integer.parseInt(maxBh) + 1; + } catch (NumberFormatException e) { + nextNum = 1; + } + } + return String.format("%04d", nextNum); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingStockServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingStockServiceImpl.java new file mode 100644 index 000000000..a72f127ee --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingStockServiceImpl.java @@ -0,0 +1,110 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.JCKCD; +import com.roomroot.jwgl.mapper.TeachingStockMapper; +import com.roomroot.jwgl.service.TeachingStockService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 教材库存单服务实现类 + */ +@Service +public class TeachingStockServiceImpl implements TeachingStockService { + + @Resource + private TeachingStockMapper teachingStockMapper; + + @Override + public void add(JCKCD jckcd) { + String uuid = UuidUtil.getUUID(); + jckcd.setId(uuid); + jckcd.setDelFlag(0); + jckcd.setCjsj(LocalDateTime.now()); + jckcd.setXgsj(LocalDateTime.now()); + // 生成编号(四位补零) + jckcd.setBh(getNextNumber()); + teachingStockMapper.insert(jckcd); + } + + @Override + public void delete(String id) { + JCKCD jckcd = getById(id); + jckcd.setDelFlag(1); + jckcd.setXgsj(LocalDateTime.now()); + teachingStockMapper.updateById(jckcd); + } + + @Override + public void update(JCKCD jckcd) { + jckcd.setXgsj(LocalDateTime.now()); + teachingStockMapper.updateById(jckcd); + } + + @Override + public JCKCD getById(String id) { + return teachingStockMapper.selectById(id); + } + + @Override + public JCKCD getByBh(String bh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(JCKCD::getBh, bh); + return teachingStockMapper.selectOne(wrapper); + } + + @Override + public PageResult pageList(PageQuery query, JCKCD jckcd) { + jckcd.setDelFlag(0); + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = teachingStockMapper.selectPageList(page, jckcd); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List list(JCKCD jckcd) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + if (jckcd != null) { + if (jckcd.getBh() != null && !jckcd.getBh().isEmpty()) { + wrapper.like(JCKCD::getBh, jckcd.getBh()); + } + if (jckcd.getMc() != null && !jckcd.getMc().isEmpty()) { + wrapper.like(JCKCD::getMc, jckcd.getMc()); + } + if (jckcd.getLb() != null && !jckcd.getLb().isEmpty()) { + wrapper.eq(JCKCD::getLb, jckcd.getLb()); + } + if (jckcd.getZt() != null && !jckcd.getZt().isEmpty()) { + wrapper.eq(JCKCD::getZt, jckcd.getZt()); + } + } + wrapper.orderByDesc(JCKCD::getCjsj); + return teachingStockMapper.selectList(wrapper); + } + + /** + * 获取下一个编号(四位补零) + */ + private String getNextNumber() { + String maxBh = teachingStockMapper.selectMaxBh(); + int nextNum = 1; + if (maxBh != null && !maxBh.isEmpty()) { + try { + // 去掉前导零后解析 + nextNum = Integer.parseInt(maxBh) + 1; + } catch (NumberFormatException e) { + nextNum = 1; + } + } + return String.format("%04d", nextNum); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingTaskServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingTaskServiceImpl.java new file mode 100644 index 000000000..816d97bce --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TeachingTaskServiceImpl.java @@ -0,0 +1,128 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.roomroot.jwgl.entity.JXRW; +import com.roomroot.jwgl.entity.JYSRWS; +import com.roomroot.jwgl.mapper.TeachingTaskMapper; +import com.roomroot.jwgl.mapper.OfficeTaskBookMapper; +import com.roomroot.jwgl.service.TeachingTaskService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.util.List; + +/** + * 教学任务服务实现类 + */ +@Service +public class TeachingTaskServiceImpl implements TeachingTaskService { + + @Resource + private TeachingTaskMapper teachingTaskMapper; + + @Resource + private OfficeTaskBookMapper officeTaskBookMapper; + + @Override + public void add(JXRW jxrw) { + String uuid = UuidUtil.getUUID(); + jxrw.setBh(uuid); + jxrw.setDelFlag(0); + // 状态默认为未发布 + jxrw.setZt("上报"); + // 自动填充创建时间 + java.time.LocalDateTime now = java.time.LocalDateTime.now(); + jxrw.setCjsj(now); // 创建时间 + teachingTaskMapper.insert(jxrw); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public void delete(String bh) { + JXRW jxrw = getByBh(bh); + if (jxrw != null) { + jxrw.setDelFlag(1); + teachingTaskMapper.updateById(jxrw); + + // 同时删除对应的教研室任务书(教学任务编号 = 教学任务编号) + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(JYSRWS::getJxrwbh, bh); + List jysrwsList = officeTaskBookMapper.selectList(wrapper); + for (JYSRWS jysrws : jysrwsList) { + jysrws.setDelFlag(1); + officeTaskBookMapper.updateById(jysrws); + } + } + } + + @Override + public void batchDelete(List bhList) { + for (String bh : bhList) { + delete(bh); + } + } + + @Override + public void update(JXRW jxrw) { + teachingTaskMapper.updateById(jxrw); + } + + @Override + public JXRW getByBh(String bh) { + return teachingTaskMapper.selectById(bh); + } + + @Override + public PageResult pageList(PageQuery query, JXRW jxrw) { + jxrw.setDelFlag(0); + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = teachingTaskMapper.selectPageList(page, jxrw); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } + + @Override + public List listAllValid() { + return teachingTaskMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(JXRW::getDelFlag, 0) + ); + } + + @Transactional(rollbackFor = Exception.class) + @Override + public int batchPublish(String bh) { + if (bh == null || bh.isEmpty()) { + return 0; + } + + JXRW jxrw = teachingTaskMapper.selectById(bh); + if (jxrw == null || jxrw.getDelFlag() != 0) { + return 0; + } + + // 更新教学任务状态为"发布" + jxrw.setZt("发布"); + teachingTaskMapper.updateById(jxrw); + + // 批量更新教研室任务书状态为"发布" + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(JYSRWS::getJxrwbh, bh) + .eq(JYSRWS::getDelFlag, 0); + List jysrwsList = officeTaskBookMapper.selectList(wrapper); + + int count = 0; + for (JYSRWS jysrws : jysrwsList) { + jysrws.setZt("发布"); + officeTaskBookMapper.updateById(jysrws); + count++; + } + + return count; + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TrainingProgramServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TrainingProgramServiceImpl.java new file mode 100644 index 000000000..678c76634 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/TrainingProgramServiceImpl.java @@ -0,0 +1,69 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.entity.ZYB; +import com.roomroot.jwgl.mapper.ZYBMapper; +import com.roomroot.jwgl.service.TrainingProgramService; +import com.roomroot.jwgl.unit.PageQuery; +import com.roomroot.jwgl.unit.PageResult; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; + +/** + * 培养方案管理服务实现类 + *

+ * 实现专业/培养方案的增加、删除(逻辑删除)、更新、分页查询等功能。 + * 逻辑删除将 停用 字段置为 true(停用=false为启用,停用=true为已停用/删除)。 + * 多版本通过培训类型(培训类型)字段区分。 + *

+ */ +@Service +public class TrainingProgramServiceImpl implements TrainingProgramService { + + @Resource + private ZYBMapper zybMapper; + + @Override + @Transactional + public void add(ZYB entity) { + entity.setTy(false); + entity.setQysj(LocalDateTime.now()); + zybMapper.insert(entity); + } + + @Override + @Transactional + public void disable(String zydh) { + ZYB entity = new ZYB(); + entity.setZydh(zydh); + entity.setTy(true); + entity.setTysj(LocalDateTime.now()); + zybMapper.updateById(entity); + } + + @Override + @Transactional + public void update(ZYB entity) { + zybMapper.updateById(entity); + } + + @Override + public ZYB getById(String zydh) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ZYB::getZydh, zydh) + .eq(ZYB::getTy, false); + return zybMapper.selectOne(wrapper); + } + + @Override + public PageResult pageList(PageQuery query, ZYB cond) { + Page page = new Page<>(query.getPageNum(), query.getPageSize()); + Page result = zybMapper.selectPageList(page, cond); + return new PageResult<>(result.getRecords(), result.getTotal(), + query.getPageNum(), query.getPageSize()); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ZYJXJHBServiceImpl.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ZYJXJHBServiceImpl.java new file mode 100644 index 000000000..2a2a49dbc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/service/impl/ZYJXJHBServiceImpl.java @@ -0,0 +1,74 @@ +package com.roomroot.jwgl.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.roomroot.jwgl.entity.ZYJXJHB; +import com.roomroot.jwgl.mapper.ZYJXJHBMapper; +import com.roomroot.jwgl.service.ZYJXJHBService; +import com.roomroot.jwgl.utils.UuidUtil; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 专业教学计划表服务实现 + */ +@Service +public class ZYJXJHBServiceImpl implements ZYJXJHBService { + + @Resource + private ZYJXJHBMapper zyjxjhbMapper; + + @Override + public void add(ZYJXJHB zyjxjhb) { + zyjxjhb.setBh(UuidUtil.getUUID()); + if (zyjxjhb.getTy() == null) { + zyjxjhb.setTy(0); + } + if (zyjxjhb.getQysj() == null) { + zyjxjhb.setQysj(LocalDateTime.now()); + } + zyjxjhbMapper.insert(zyjxjhb); + } + + @Override + public void delete(String bh) { + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); + wrapper.eq(ZYJXJHB::getBh, bh) + .set(ZYJXJHB::getTy, 1) + .set(ZYJXJHB::getTysj, LocalDateTime.now()); + zyjxjhbMapper.update(null, wrapper); + } + + @Override + public void batchDelete(List bhList) { + for (String bh : bhList) { + delete(bh); + } + } + + @Override + public void update(ZYJXJHB zyjxjhb) { + zyjxjhbMapper.updateById(zyjxjhb); + } + + @Override + public ZYJXJHB getByBh(String bh) { + return zyjxjhbMapper.selectById(bh); + } + + @Override + public List list() { + return zyjxjhbMapper.selectList(null); + } + + @Override + public List listByZydhAndTy(String zydh, Integer ty) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(ZYJXJHB::getZydh, zydh) + .eq(ZYJXJHB::getTy, ty); + return zyjxjhbMapper.selectList(wrapper); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/AccountLoginFailedException.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/AccountLoginFailedException.java new file mode 100644 index 000000000..49b92145d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/AccountLoginFailedException.java @@ -0,0 +1,36 @@ +package com.roomroot.jwgl.unit; + +import static com.roomroot.jwgl.utils.AccountManagementConstants.*; + +/** + * 表示密码校验失败且失败统计需要保留的账户登录业务异常。 + * + *

该异常由项目统一业务异常处理器转换为标准错误响应, + * 同时供登录事务通过异常类型识别不需要回滚的登录失败场景, + * 从而保留已经写入的登录失败次数和临时锁定状态。

+ */ +public final class AccountLoginFailedException extends BusinessException { + + /** + * 声明当前异常类的序列化版本,保证异常对象序列化时的版本一致性。 + */ + private static final long serialVersionUID = 1L; + + /** + * 创建不泄露账号存在状态和密码校验细节的普通登录失败异常。 + */ + public AccountLoginFailedException() { + // 使用统一的请求错误状态码和登录失败提示,避免暴露具体失败原因。 + super(BAD_REQUEST, ACCOUNT_LOGIN_FAILED_MESSAGE); + } + + /** + * 创建需要保留失败统计的指定登录失败异常。 + * + * @param message 对外返回的登录失败提示 + */ + public AccountLoginFailedException(String message) { + // 使用冲突状态码返回账户临时锁定等需要明确提示的登录失败结果。 + super(CONFLICT, message); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/BaseEntity.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/BaseEntity.java new file mode 100644 index 000000000..9427dadb6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/BaseEntity.java @@ -0,0 +1,49 @@ +package com.roomroot.jwgl.unit; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.TableField; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 实体基类 - 抽取公共字段 + *

+ * 所有数据库实体继承此类,自动获得创建时间、更新时间、创建人、更新人及逻辑删除标记等公共属性。 + * 配合 MyBatis-Plus 的自动填充机制,在插入/更新时自动设置对应字段。 + *

+ * + * @author management + */ +@Data +public class BaseEntity { + + /** + * 创建时间(自动填充,仅在插入时赋值) + */ + @TableField(fill = FieldFill.INSERT) + private LocalDateTime createTime; + + /** + * 更新时间(自动填充,插入和更新时均赋值) + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private LocalDateTime updateTime; + + /** + * 创建人(自动填充,仅在插入时赋值) + */ + @TableField(fill = FieldFill.INSERT) + private String createBy; + + /** + * 更新人(自动填充,插入和更新时均赋值) + */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateBy; + + /** + * 逻辑删除标记:0-正常,1-已删除 + */ + private Integer delFlag; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/BaseQuery.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/BaseQuery.java new file mode 100644 index 000000000..1fbc0d7ad --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/BaseQuery.java @@ -0,0 +1,36 @@ +package com.roomroot.jwgl.unit; + +import lombok.Data; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 查询基类 + *

+ * 封装列表查询的通用参数,包括关键字搜索和创建时间范围筛选。 + * 具体的业务查询DTO可继承此类以复用公共查询条件。 + *

+ * + * @author management + */ +@Data +public class BaseQuery implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 创建时间范围查询 - 起始时间 + */ + private LocalDateTime createTimeStart; + + /** + * 创建时间范围查询 - 截止时间 + */ + private LocalDateTime createTimeEnd; + + /** + * 关键字模糊搜索 + */ + private String keyword; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/BusinessException.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/BusinessException.java new file mode 100644 index 000000000..ccd0de413 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/BusinessException.java @@ -0,0 +1,59 @@ +package com.roomroot.jwgl.unit; + +/** + * 业务异常类 + *

+ * 用于业务逻辑校验失败、数据不合法、操作不允许等场景的统一异常抛出。 + * 由 {@link GlobalExceptionHandler} 全局捕获后转换为统一响应格式返回前端。 + *

+ * + * @author management + */ +public class BusinessException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + /** + * 业务错误码,默认500 + */ + private Integer code; + + /** + * 构造方法 - 使用默认错误码500 + * + * @param message 异常提示信息 + */ + public BusinessException(String message) { + super(message); + this.code = 500; + } + + /** + * 构造方法 - 指定错误码 + * + * @param code 自定义业务错误码 + * @param message 异常提示信息 + */ + public BusinessException(Integer code, String message) { + super(message); + this.code = code; + } + + /** + * 获取错误码 + * + * @return 错误码 + */ + public Integer getCode() { + return code; + } + + /** + * 设置错误码 + * + * @param code 错误码 + */ + public void setCode(Integer code) { + this.code = code; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/Constants.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/Constants.java new file mode 100644 index 000000000..9edf6ede5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/Constants.java @@ -0,0 +1,32 @@ +package com.roomroot.jwgl.unit; + +/** + * 通用系统常量 + *

+ * 定义系统全局使用的各类常量值,包括状态标记和逻辑删除标记等。 + * 所有模块统一引用此处的常量,保证系统内数值含义一致。 + *

+ * + * @author management + */ +public final class Constants { + + /** 私有构造,防止实例化 */ + private Constants() {} + + /** 通用启用状态码 */ + public static final Integer STATUS_ENABLED = 1; + + /** 通用禁用状态码 */ + public static final Integer STATUS_DISABLED = 0; + + /** 逻辑删除标记:已删除 */ + public static final Integer DEL_FLAG_DELETED = 1; + + /** 逻辑删除标记:正常(未删除) */ + public static final Integer DEL_FLAG_NORMAL = 0; + + /** 树形结构根节点的父级ID */ + public static final Long ROOT_PARENT_ID = 0L; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/PageQuery.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/PageQuery.java new file mode 100644 index 000000000..ea01a2f2a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/PageQuery.java @@ -0,0 +1,42 @@ +package com.roomroot.jwgl.unit; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import jakarta.validation.constraints.*; + +/** + * 分页查询基类 + *

+ * 继承 {@link BaseQuery},增加分页参数(页码、每页大小)。 + * 配合 MyBatis-Plus 的 {@code Page} 对象使用,实现统一分页查询。 + *

+ * + * @author management + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class PageQuery extends BaseQuery { + + /** + * 当前页码,默认第1页,最小为1 + */ + @NotNull(message = "页码不能为空") + @Min(value = 1, message = "页码最小为1") + private Integer pageNum; + + /** + * 每页记录数,默认20条,最小1条,最大100条 + */ + @NotNull(message = "每页大小不能为空") + @Min(value = 1, message = "每页最小为1条") + @Max(value = 100, message = "每页最大为100条") + private Integer pageSize; + + public PageQuery() {} + + public PageQuery(Integer pageNum, Integer pageSize) { + this.pageNum = pageNum; + this.pageSize = pageSize; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/PageResult.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/PageResult.java new file mode 100644 index 000000000..7f984263e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/PageResult.java @@ -0,0 +1,69 @@ +package com.roomroot.jwgl.unit; + +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 分页结果封装 + *

+ * 统一分页查询返回结构,包含数据列表、总记录数、当前页码、每页大小及总页数。 + * 前端可根据此结构渲染分页组件。 + *

+ * + * @param 数据泛型 + * @author management + */ +@Data +public class PageResult implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 当前页数据列表 + */ + private List records; + + /** + * 总记录数(满足查询条件的全部记录数,非当前页记录数) + */ + private Long total; + + /** + * 当前页码 + */ + private Integer pageNum; + + /** + * 每页记录数 + */ + private Integer pageSize; + + /** + * 总页数(由 total 和 pageSize 计算得出) + */ + private Integer pages; + + /** + * 无参构造方法 + */ + public PageResult() { + } + + /** + * 全参构造方法,自动计算总页数 + * + * @param records 数据列表 + * @param total 总记录数 + * @param pageNum 当前页码 + * @param pageSize 每页大小 + */ + public PageResult(List records, Long total, Integer pageNum, Integer pageSize) { + this.records = records; + this.total = total; + this.pageNum = pageNum; + this.pageSize = pageSize; + this.pages = (int) Math.ceil((double) total / pageSize); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/Result.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/Result.java new file mode 100644 index 000000000..74be699b4 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/Result.java @@ -0,0 +1,188 @@ +package com.roomroot.jwgl.unit; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 统一接口返回结果 + *

+ * 所有Controller接口返回此对象,前端根据code判断请求是否成功。 + * 泛型T为实际返回的数据类型,当无数据返回时使用 {@code Result}。 + *

+ *

状态码约定:

+ *
    + *
  • 200 - 操作成功
  • + *
  • 400 - 请求参数错误
  • + *
  • 401 - 未授权
  • + *
  • 403 - 禁止访问
  • + *
  • 404 - 资源未找到
  • + *
  • 500 - 服务器内部错误
  • + *
+ * + * @param 返回数据的泛型类型 + * @author management + */ +@Data +public class Result implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 状态码,遵循HTTP状态码语义 + */ + private Integer code; + + /** + * 提示信息,用于前端展示 + */ + private String message; + + /** + * 返回数据,可为任意类型 + */ + private T data; + + /** + * 操作成功 - 无返回数据 + * + * @param 泛型 + * @return Result对象,code=200 + */ + public static Result success() { + Result result = new Result<>(); + result.setCode(200); + result.setMessage("操作成功"); + return result; + } + + /** + * 操作成功 - 返回数据 + * + * @param data 返回的业务数据 + * @param 泛型 + * @return Result对象,code=200 + */ + public static Result success(T data) { + Result result = new Result<>(); + result.setCode(200); + result.setMessage("操作成功"); + result.setData(data); + return result; + } + + /** + * 操作成功 - 自定义消息和返回数据 + * + * @param message 自定义成功消息 + * @param data 返回的业务数据 + * @param 泛型 + * @return Result对象,code=200 + */ + public static Result success(String message, T data) { + Result result = new Result<>(); + result.setCode(200); + result.setMessage(message); + result.setData(data); + return result; + } + + + /** + * 操作失败 - 默认错误 + * + * @param 泛型 + * @return Result对象,code=500 + */ + public static Result error() { + Result result = new Result<>(); + result.setCode(500); + result.setMessage("操作失败"); + return result; + } + + /** + * 操作失败 - 自定义消息 + * + * @param message 错误提示消息 + * @param 泛型 + * @return Result对象,code=500 + */ + public static Result error(String message) { + Result result = new Result<>(); + result.setCode(500); + result.setMessage(message); + return result; + } + + /** + * 操作失败 - 自定义错误码和消息 + * + * @param code 业务错误码 + * @param message 错误提示消息 + * @param 泛型 + * @return Result对象 + */ + public static Result error(Integer code, String message) { + Result result = new Result<>(); + result.setCode(code); + result.setMessage(message); + return result; + } + + /** + * 请求参数错误 + * + * @param message 错误描述 + * @param 泛型 + * @return Result对象,code=400 + */ + public static Result badRequest(String message) { + Result result = new Result<>(); + result.setCode(400); + result.setMessage(message); + return result; + } + + /** + * 未授权/未登录 + * + * @param message 提示消息 + * @param 泛型 + * @return Result对象,code=401 + */ + public static Result unauthorized(String message) { + Result result = new Result<>(); + result.setCode(401); + result.setMessage(message); + return result; + } + + /** + * 禁止访问/权限不足 + * + * @param message 提示消息 + * @param 泛型 + * @return Result对象,code=403 + */ + public static Result forbidden(String message) { + Result result = new Result<>(); + result.setCode(403); + result.setMessage(message); + return result; + } + + /** + * 资源未找到 + * + * @param message 提示消息 + * @param 泛型 + * @return Result对象,code=404 + */ + public static Result notFound(String message) { + Result result = new Result<>(); + result.setCode(404); + result.setMessage(message); + return result; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/StatusEnum.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/StatusEnum.java new file mode 100644 index 000000000..0a64ff93a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/unit/StatusEnum.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.unit; + +/** + * 通用状态枚举 + *

+ * 定义系统中通用的启用/禁用/删除状态枚举值。 + * 通过枚举统一管理状态码与中文描述,避免代码中散布魔法数字。 + *

+ * + * @author management + */ +public enum StatusEnum { + + /** 启用状态:状态码1 */ + ENABLED(1, "启用"), + + /** 禁用状态:状态码0 */ + DISABLED(0, "禁用"), + + /** 已删除状态:状态码-1 */ + DELETED(-1, "已删除"); + + /** + * 状态码 + */ + private final Integer code; + + /** + * 状态描述 + */ + private final String desc; + + /** + * 枚举构造方法 + * + * @param code 状态码 + * @param desc 状态描述 + */ + StatusEnum(Integer code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * 获取状态码 + * + * @return 状态码 + */ + public Integer getCode() { + return code; + } + + /** + * 获取状态描述 + * + * @return 状态描述 + */ + public String getDesc() { + return desc; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/AccountLoginCaptchaUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/AccountLoginCaptchaUtil.java new file mode 100644 index 000000000..d67ccc749 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/AccountLoginCaptchaUtil.java @@ -0,0 +1,580 @@ +package com.roomroot.jwgl.utils; + +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.vo.accountmanagement.AccountLoginCaptchaVO; +import jakarta.servlet.http.HttpSession; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; +import java.util.Locale; + +import static com.roomroot.jwgl.utils.AccountManagementConstants.ACCOUNT_ID_MAX_LENGTH; +import static com.roomroot.jwgl.utils.AccountManagementConstants.BAD_REQUEST; + +/** + * 账户登录图形验证码工具类。 + * + *

统一负责验证码配置、字符生成、图片绘制、摘要计算、 + * Servlet Session 保存以及一次性消费校验。

+ */ +public final class AccountLoginCaptchaUtil { + + /** + * 登录验证码使用的字符集合。 + * + *

主动排除 0、1、I、O 等容易混淆的字符,降低用户识别错误率。

+ */ + private static final String LOGIN_CAPTCHA_CHARACTERS = + "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"; + + /** + * 单个登录验证码包含的字符数量。 + */ + private static final int LOGIN_CAPTCHA_CODE_LENGTH = 4; + + /** + * 登录验证码图片宽度。 + */ + private static final int LOGIN_CAPTCHA_IMAGE_WIDTH = 140; + + /** + * 登录验证码图片高度。 + */ + private static final int LOGIN_CAPTCHA_IMAGE_HEIGHT = 48; + + /** + * 登录验证码有效秒数。 + */ + private static final int LOGIN_CAPTCHA_EXPIRES_SECONDS = 300; + + /** + * Session 中保存验证码编号的属性名称。 + */ + private static final String LOGIN_CAPTCHA_ID_SESSION_ATTRIBUTE = + AccountLoginCaptchaUtil.class.getName() + + ".LOGIN_CAPTCHA_ID"; + + /** + * Session 中保存验证码答案摘要的属性名称。 + */ + private static final String LOGIN_CAPTCHA_HASH_SESSION_ATTRIBUTE = + AccountLoginCaptchaUtil.class.getName() + + ".LOGIN_CAPTCHA_HASH"; + + /** + * Session 中保存验证码到期时间的属性名称。 + */ + private static final String + LOGIN_CAPTCHA_EXPIRES_AT_SESSION_ATTRIBUTE = + AccountLoginCaptchaUtil.class.getName() + + ".LOGIN_CAPTCHA_EXPIRES_AT"; + + /** + * 生成验证码字符、干扰线和文字角度的安全随机数生成器。 + */ + private static final SecureRandom LOGIN_CAPTCHA_RANDOM = + new SecureRandom(); + + /** + * PNG 图片的 Base64 Data URL 前缀。 + */ + private static final String PNG_DATA_URL_PREFIX = + "data:image/png;base64,"; + + /** + * 私有化构造方法,防止验证码工具类被实例化。 + */ + private AccountLoginCaptchaUtil() { + } + + /** + * 生成验证码并保存到当前请求的 Servlet Session。 + * + * @return 验证码编号、PNG 图片数据和有效秒数 + */ + public static AccountLoginCaptchaVO generate() { + /* + * 第一步:取得当前请求的 Servlet Session。 + * 生成接口允许为匿名用户创建新 Session。 + */ + HttpSession session = getCurrentSession(true); + + // 第二步:为本次验证码生成不可预测的独立编号。 + String captchaId = UuidUtil.getOriginalUUID(); + + // 第三步:使用安全随机数生成四位易识别验证码内容。 + String captchaCode = generateCode(); + + /* + * 第四步:使用验证码编号和答案共同生成摘要。 + * Session 中不保存验证码明文。 + */ + byte[] captchaHash = hash(captchaId, captchaCode); + + /* + * 第五步:先完成图片生成。 + * 图片编码失败时不会覆盖 Session 中原有可用验证码。 + */ + String imageDataUrl = generateImageDataUrl(captchaCode); + + // 第六步:按照固定有效秒数计算绝对到期时间。 + long expiresAt = System.currentTimeMillis() + + LOGIN_CAPTCHA_EXPIRES_SECONDS * 1000L; + + /* + * 第七步:在 Session 锁内整体写入验证码校验材料。 + * 刷新验证码会覆盖旧值,并避免并发请求读取到不完整数据。 + */ + store(session, captchaId, captchaHash, expiresAt); + + // 第八步:创建独立 VO,不向页面返回验证码答案或摘要。 + AccountLoginCaptchaVO result = new AccountLoginCaptchaVO(); + + // 第九步:装配页面显示和后续登录提交所需字段。 + result.setCaptchaId(captchaId); + result.setImageDataUrl(imageDataUrl); + result.setExpiresInSeconds( + Integer.valueOf(LOGIN_CAPTCHA_EXPIRES_SECONDS)); + + // 第十步:返回验证码生成结果。 + return result; + } + + /** + * 校验并消费当前请求 Session 中的一次性验证码。 + * + *

无论验证码正确、错误或过期,读取后都会立即删除, + * 防止同一验证码被重复提交或并发重放。

+ * + * @param captchaId 登录请求提交的验证码编号 + * @param captchaCode 用户识别并输入的验证码内容 + */ + public static void validateAndConsume( + String captchaId, String captchaCode) { + // 第一步:规范化并校验验证码编号。 + String submittedCaptchaId = requiredText( + captchaId, + ACCOUNT_ID_MAX_LENGTH, + "验证码编号"); + + // 第二步:规范化验证码内容,并统一转换为大写。 + String submittedCaptchaCode = requiredText( + captchaCode, + LOGIN_CAPTCHA_CODE_LENGTH, + "验证码").toUpperCase(Locale.ROOT); + if (submittedCaptchaCode.length() + != LOGIN_CAPTCHA_CODE_LENGTH) { + throw new BusinessException( + BAD_REQUEST, "验证码必须为4个字符"); + } + + /* + * 第三步:只读取当前请求已经存在的 Session。 + * 登录校验不能在验证码缺失时创建新的空 Session。 + */ + HttpSession session = getCurrentSession(false); + if (session == null) { + throw new BusinessException( + BAD_REQUEST, "验证码已失效,请重新获取"); + } + + Object storedCaptchaId; + Object storedCaptchaHash; + Object storedExpiresAt; + try { + /* + * 第四步:在 Session 锁内读取并删除全部校验材料。 + * 读取和删除构成原子消费步骤,并发请求不能复用同一验证码。 + */ + synchronized (session) { + storedCaptchaId = + session.getAttribute( + LOGIN_CAPTCHA_ID_SESSION_ATTRIBUTE); + storedCaptchaHash = + session.getAttribute( + LOGIN_CAPTCHA_HASH_SESSION_ATTRIBUTE); + storedExpiresAt = + session.getAttribute( + LOGIN_CAPTCHA_EXPIRES_AT_SESSION_ATTRIBUTE); + + /* + * 第五步:在进行任何正确性判断前删除验证码。 + * 正确、错误、过期和编号不匹配都必须重新获取。 + */ + session.removeAttribute( + LOGIN_CAPTCHA_ID_SESSION_ATTRIBUTE); + session.removeAttribute( + LOGIN_CAPTCHA_HASH_SESSION_ATTRIBUTE); + session.removeAttribute( + LOGIN_CAPTCHA_EXPIRES_AT_SESSION_ATTRIBUTE); + } + } catch (IllegalStateException exception) { + // Session 已被并发请求作废时按验证码失效处理。 + throw new BusinessException( + BAD_REQUEST, "验证码已失效,请重新获取"); + } + + /* + * 第六步:校验 Session 属性类型和完整性。 + * 缺少任一属性都表示当前 Session 没有可用验证码。 + */ + if (!(storedCaptchaId instanceof String) + || !(storedCaptchaHash instanceof byte[]) + || !(storedExpiresAt instanceof Long)) { + throw new BusinessException( + BAD_REQUEST, "验证码已失效,请重新获取"); + } + + // 第七步:验证码编号必须与当前 Session 中的编号完全一致。 + if (!submittedCaptchaId.equals(storedCaptchaId)) { + throw new BusinessException( + BAD_REQUEST, "验证码已失效,请重新获取"); + } + + // 第八步:达到绝对到期时间后拒绝校验。 + if (System.currentTimeMillis() + >= ((Long) storedExpiresAt).longValue()) { + throw new BusinessException( + BAD_REQUEST, "验证码已过期,请重新获取"); + } + + // 第九步:使用相同编号和规范化答案计算本次提交摘要。 + byte[] submittedCaptchaHash = + hash(submittedCaptchaId, submittedCaptchaCode); + + /* + * 第十步:使用常量时间比较验证码摘要。 + * 验证码错误独立返回,不应累计账户密码失败次数。 + */ + if (!MessageDigest.isEqual( + (byte[]) storedCaptchaHash, + submittedCaptchaHash)) { + throw new BusinessException( + BAD_REQUEST, "验证码错误,请重新获取"); + } + } + + /** + * 取得当前线程绑定的 Servlet Session。 + * + * @param create 没有 Session 时是否允许创建 + * @return 当前 Session;不允许创建且当前不存在时返回 null + */ + private static HttpSession getCurrentSession(boolean create) { + // 第一步:读取 Spring 绑定到当前线程的 Servlet 请求属性。 + ServletRequestAttributes attributes = + (ServletRequestAttributes) RequestContextHolder + .getRequestAttributes(); + + /* + * 第二步:生成验证码时必须存在 HTTP 请求上下文。 + * 登录校验缺少请求上下文时按没有可用验证码 Session 处理。 + */ + if (attributes == null) { + if (create) { + throw new BusinessException( + "无法建立登录验证码会话"); + } + return null; + } + + // 第三步:按照调用场景取得已有 Session 或允许容器创建新 Session。 + return attributes.getRequest().getSession(create); + } + + /** + * 将验证码校验材料整体写入 Session。 + * + * @param session 当前验证码 Session + * @param captchaId 验证码编号 + * @param captchaHash 验证码答案摘要 + * @param expiresAt 验证码绝对到期时间 + */ + private static void store( + HttpSession session, + String captchaId, + byte[] captchaHash, + long expiresAt) { + try { + /* + * 第一步:在 Session 锁内覆盖完整验证码数据。 + * 生成和消费使用同一把锁,保证并发操作的一致性。 + */ + synchronized (session) { + session.setAttribute( + LOGIN_CAPTCHA_ID_SESSION_ATTRIBUTE, captchaId); + session.setAttribute( + LOGIN_CAPTCHA_HASH_SESSION_ATTRIBUTE, captchaHash); + session.setAttribute( + LOGIN_CAPTCHA_EXPIRES_AT_SESSION_ATTRIBUTE, + Long.valueOf(expiresAt)); + } + } catch (IllegalStateException exception) { + // 第二步:Session 被作废时拒绝返回无法继续使用的验证码。 + throw new BusinessException( + BAD_REQUEST, "登录验证码会话已失效,请重新获取"); + } + } + + /** + * 生成登录验证码字符。 + * + * @return 四位不包含易混淆字符的随机验证码 + */ + private static String generateCode() { + // 第一步:按照固定验证码长度创建字符串容器。 + StringBuilder captchaCode = + new StringBuilder(LOGIN_CAPTCHA_CODE_LENGTH); + + /* + * 第二步:每一位均使用 SecureRandom 从允许字符集合中独立选择, + * 避免使用时间戳或普通 Random 产生可预测验证码。 + */ + for (int index = 0; + index < LOGIN_CAPTCHA_CODE_LENGTH; + index++) { + int characterIndex = + LOGIN_CAPTCHA_RANDOM.nextInt( + LOGIN_CAPTCHA_CHARACTERS.length()); + captchaCode.append( + LOGIN_CAPTCHA_CHARACTERS.charAt(characterIndex)); + } + + // 第三步:返回仅在当前生成流程中短暂使用的验证码明文。 + return captchaCode.toString(); + } + + /** + * 使用验证码编号和答案生成 SHA-256 摘要。 + * + * @param captchaId 验证码编号 + * @param captchaCode 已规范为大写的验证码答案 + * @return 验证码答案摘要 + */ + private static byte[] hash( + String captchaId, String captchaCode) { + try { + // 第一步:取得 JDK 标准 SHA-256 消息摘要实现。 + MessageDigest messageDigest = + MessageDigest.getInstance("SHA-256"); + + /* + * 第二步:把验证码编号和答案共同加入摘要。 + * 不同验证码即使字符相同,也会得到不同摘要。 + */ + String captchaValue = + captchaId + ":" + captchaCode; + + // 第三步:使用固定 UTF-8 编码生成摘要字节。 + return messageDigest.digest( + captchaValue.getBytes(StandardCharsets.UTF_8)); + } catch (NoSuchAlgorithmException exception) { + /* + * Java 运行环境必须提供 SHA-256。 + * 环境异常时不能降级为保存明文验证码。 + */ + throw new BusinessException( + "登录验证码生成失败,请重试"); + } + } + + /** + * 将验证码字符绘制为 PNG Base64 Data URL。 + * + * @param captchaCode 待绘制的验证码字符 + * @return 可直接用于图片 src 属性的 Data URL + */ + private static String generateImageDataUrl( + String captchaCode) { + /* + * 第一步:创建固定尺寸 RGB 图片。 + * 固定宽高可以避免验证码刷新时引起页面布局跳动。 + */ + BufferedImage image = new BufferedImage( + LOGIN_CAPTCHA_IMAGE_WIDTH, + LOGIN_CAPTCHA_IMAGE_HEIGHT, + BufferedImage.TYPE_INT_RGB); + + // 第二步:取得二维绘图对象,并确保完成后释放图形资源。 + Graphics2D graphics = image.createGraphics(); + try { + // 第三步:开启文字和图形抗锯齿。 + graphics.setRenderingHint( + RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + graphics.setRenderingHint( + RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + // 第四步:绘制高对比度浅色背景。 + graphics.setColor(new Color(248, 249, 250)); + graphics.fillRect( + 0, 0, + LOGIN_CAPTCHA_IMAGE_WIDTH, + LOGIN_CAPTCHA_IMAGE_HEIGHT); + + /* + * 第五步:绘制六条浅色随机干扰线。 + * 干扰线保持较浅,避免影响人工识别主要字符。 + */ + for (int lineIndex = 0; + lineIndex < 6; + lineIndex++) { + graphics.setColor(new Color( + 150 + LOGIN_CAPTCHA_RANDOM.nextInt(70), + 150 + LOGIN_CAPTCHA_RANDOM.nextInt(70), + 150 + LOGIN_CAPTCHA_RANDOM.nextInt(70))); + graphics.drawLine( + LOGIN_CAPTCHA_RANDOM.nextInt( + LOGIN_CAPTCHA_IMAGE_WIDTH), + LOGIN_CAPTCHA_RANDOM.nextInt( + LOGIN_CAPTCHA_IMAGE_HEIGHT), + LOGIN_CAPTCHA_RANDOM.nextInt( + LOGIN_CAPTCHA_IMAGE_WIDTH), + LOGIN_CAPTCHA_RANDOM.nextInt( + LOGIN_CAPTCHA_IMAGE_HEIGHT)); + } + + // 第六步:使用 Java 逻辑字体,避免依赖服务器安装特定字体。 + graphics.setFont(new Font( + Font.SANS_SERIF, Font.BOLD, 28)); + FontMetrics fontMetrics = + graphics.getFontMetrics(); + + // 第七步:计算文字基线,使验证码字符垂直居中。 + int baseline = + (LOGIN_CAPTCHA_IMAGE_HEIGHT + - fontMetrics.getHeight()) / 2 + + fontMetrics.getAscent(); + + /* + * 第八步:逐个绘制验证码字符。 + * 每个字符使用独立深色和轻微随机旋转。 + */ + for (int characterIndex = 0; + characterIndex < captchaCode.length(); + characterIndex++) { + int x = 17 + characterIndex * 28; + double angle = + (LOGIN_CAPTCHA_RANDOM.nextDouble() - 0.5D) + * 0.36D; + graphics.setColor(new Color( + 20 + LOGIN_CAPTCHA_RANDOM.nextInt(90), + 20 + LOGIN_CAPTCHA_RANDOM.nextInt(90), + 20 + LOGIN_CAPTCHA_RANDOM.nextInt(90))); + + // 保存原变换,防止当前旋转影响后续字符。 + AffineTransform originalTransform = + graphics.getTransform(); + graphics.rotate( + angle, + x + 10.0D, + baseline - 10.0D); + graphics.drawString( + String.valueOf( + captchaCode.charAt(characterIndex)), + x, + baseline); + graphics.setTransform(originalTransform); + } + + /* + * 第九步:补充少量随机噪点和边框。 + * 噪点不覆盖大面积区域,兼顾抗识别能力和人工可读性。 + */ + for (int pointIndex = 0; + pointIndex < 40; + pointIndex++) { + graphics.setColor(new Color( + 100 + LOGIN_CAPTCHA_RANDOM.nextInt(120), + 100 + LOGIN_CAPTCHA_RANDOM.nextInt(120), + 100 + LOGIN_CAPTCHA_RANDOM.nextInt(120))); + graphics.fillRect( + LOGIN_CAPTCHA_RANDOM.nextInt( + LOGIN_CAPTCHA_IMAGE_WIDTH), + LOGIN_CAPTCHA_RANDOM.nextInt( + LOGIN_CAPTCHA_IMAGE_HEIGHT), + 1, + 1); + } + graphics.setColor(new Color(180, 184, 188)); + graphics.drawRect( + 0, + 0, + LOGIN_CAPTCHA_IMAGE_WIDTH - 1, + LOGIN_CAPTCHA_IMAGE_HEIGHT - 1); + } finally { + // 第十步:无论绘制是否成功,都释放 Graphics2D 资源。 + graphics.dispose(); + } + + /* + * 第十一步:把内存图片编码为 PNG,再转换为 Base64 Data URL。 + * 页面无需额外图片下载接口即可直接显示验证码。 + */ + try (ByteArrayOutputStream outputStream = + new ByteArrayOutputStream()) { + boolean encoded = + ImageIO.write(image, "png", outputStream); + if (!encoded) { + throw new BusinessException( + "登录验证码生成失败,请重试"); + } + + // 第十二步:返回包含 PNG 媒体类型前缀的完整图片数据。 + return PNG_DATA_URL_PREFIX + + Base64.getEncoder().encodeToString( + outputStream.toByteArray()); + } catch (IOException exception) { + // PNG 内存编码异常时返回统一错误。 + throw new BusinessException( + "登录验证码生成失败,请重试"); + } + } + + /** + * 校验并规范化必填验证码文本。 + * + * @param value 原始文本 + * @param maxLength 最大长度 + * @param fieldName 字段名称 + * @return 去除首尾空白后的有效文本 + */ + private static String requiredText( + String value, int maxLength, String fieldName) { + // 第一步:去除首尾空白,并把空字符串统一转换为 null。 + String normalized = + value == null ? null : value.trim(); + if (normalized != null && normalized.isEmpty()) { + normalized = null; + } + + // 第二步:规范化后为空时返回必填参数错误。 + if (normalized == null) { + throw new BusinessException( + BAD_REQUEST, fieldName + "不能为空"); + } + + // 第三步:超过允许长度时返回参数错误。 + if (normalized.length() > maxLength) { + throw new BusinessException( + BAD_REQUEST, + fieldName + "不能超过" + + maxLength + "个字符"); + } + + // 第四步:返回通过校验的文本。 + return normalized; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/AccountManagementConstants.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/AccountManagementConstants.java new file mode 100644 index 000000000..b5762fd2e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/AccountManagementConstants.java @@ -0,0 +1,228 @@ +package com.roomroot.jwgl.utils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * 账户管理常量工具类。 + */ +public final class AccountManagementConstants { + + /** + * 请求参数错误业务码。 + */ + public static final int BAD_REQUEST = 400; + + /** + * 数据不存在业务码。 + */ + public static final int NOT_FOUND = 404; + + /** + * 数据冲突业务码。 + */ + public static final int CONFLICT = 409; + + /** + * 登录账号规范化唯一索引名称。 + * + *

业务层使用该固定名称识别数据库并发写入产生的登录账号冲突。

+ */ + public static final String LOGIN_NAME_UNIQUE_INDEX = + "UK_登录信息表_账号_规范化"; + + /** + * 统一账号规范化唯一索引名称。 + * + *

业务层使用该固定名称识别数据库并发写入产生的统一账号冲突。

+ */ + public static final String UNIFIED_ACCOUNT_UNIQUE_INDEX = + "UK_登录信息表_统一账号_规范化"; + + /** + * 账户管理接口允许访问的在线角色类别。 + * + *

该名称与现有系统在线用户表和管理员页面展示的角色类别保持一致。

+ */ + public static final String ACCOUNT_ADMINISTRATOR_ROLE_CATEGORY = + "教务处超级管理员"; + + /** + * 当前 Servlet Session 中保存登录记录编号的属性名。 + * + *

操作日志使用该编号关联产生本次操作的具体登录记录。

+ */ + public static final String LOGIN_RECORD_ID_SESSION_ATTRIBUTE = + "ACCOUNT_LOGIN_RECORD_ID"; + + /** + * 账户管理接口未登录时的统一提示。 + */ + public static final String AUTHENTICATION_REQUIRED_MESSAGE = + "登录状态已失效,请重新登录"; + + /** + * 本地账号密码校验失败时的统一提示。 + * + *

账号不存在和密码错误使用同一提示,避免通过登录接口枚举有效账号。

+ */ + public static final String ACCOUNT_LOGIN_FAILED_MESSAGE = + "登录账号或密码错误"; + + /** + * 本地账户处于密码失败临时锁定期时的统一提示。 + */ + public static final String ACCOUNT_TEMPORARILY_LOCKED_MESSAGE = + "账户已临时锁定,请稍后再试"; + + /** + * 账户停用或合并后继续访问系统时的统一提示。 + */ + public static final String ACCOUNT_UNAVAILABLE_MESSAGE = + "账户已停用或已合并,请联系管理员"; + + /** + * 账户管理接口权限不足时的统一提示。 + */ + public static final String ACCOUNT_PERMISSION_DENIED_MESSAGE = + "当前账户没有账户管理权限"; + + /** + * UUID 类型账户编号最大长度。 + */ + public static final int ACCOUNT_ID_MAX_LENGTH = 36; + + /** + * 账户普通文本字段最大长度。 + */ + public static final int ACCOUNT_TEXT_MAX_LENGTH = 50; + + /** + * 联系电话最大长度。 + */ + public static final int PHONE_MAX_LENGTH = 20; + + /** + * 停用和合并原因最大长度。 + */ + public static final int REASON_MAX_LENGTH = 200; + + /** + * 自我介绍最大长度。 + */ + public static final int INTRODUCTION_MAX_LENGTH = 2000; + + /** + * 密码最小长度。 + */ + public static final int PASSWORD_MIN_LENGTH = 8; + + /** + * 密码最大长度。 + */ + public static final int PASSWORD_MAX_LENGTH = 50; + + /** + * BCrypt 能够完整处理的原始密码最大 UTF-8 字节数。 + */ + public static final int PASSWORD_MAX_BYTES = 72; + + /** + * 密码复杂度规则。 + * + *

密码必须同时包含大写字母、小写字母、数字和特殊字符, + * 并且不能包含空白字符。

+ */ + public static final String PASSWORD_COMPLEXITY_PATTERN = + "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)" + + "(?=.*[^A-Za-z\\d\\s])\\S+$"; + + /** + * 单个账户最多允许配置的角色身份数量。 + */ + public static final int ROLE_ASSIGNMENT_MAX_SIZE = 500; + + /** + * 机关人员角色编码。 + */ + public static final String ROLE_DEPARTMENT_PERSONNEL = + "DEPARTMENT_PERSONNEL"; + + /** + * 教员角色编码。 + */ + public static final String ROLE_TEACHER = "TEACHER"; + + /** + * 教研室角色编码。 + */ + public static final String ROLE_RESEARCH_OFFICE = "RESEARCH_OFFICE"; + + /** + * 教学班次角色编码。 + */ + public static final String ROLE_TEACHING_CLASS = "TEACHING_CLASS"; + + /** + * 听查课管理员角色编码。 + */ + public static final String ROLE_INSPECTION_ADMINISTRATOR = + "INSPECTION_ADMINISTRATOR"; + + /** + * 学员角色编码。 + */ + public static final String ROLE_STUDENT = "STUDENT"; + + /** + * 旧用户角色编码。 + */ + public static final String ROLE_LEGACY_USER = "LEGACY_USER"; + + /** + * 按页面展示顺序保存的角色名称映射。 + */ + public static final Map ROLE_NAMES; + + /** + * 按页面展示顺序保存的全部角色类型编码。 + */ + public static final List ROLE_TYPES; + + static { + /* + * 第一步:使用有序映射登记系统实际存在的七类身份绑定, + * 保证角色类型接口、角色详情和配置选项保持同一展示顺序。 + */ + LinkedHashMap roleNames = new LinkedHashMap<>(); + roleNames.put(ROLE_DEPARTMENT_PERSONNEL, "机关人员"); + roleNames.put(ROLE_TEACHER, "教员"); + roleNames.put(ROLE_RESEARCH_OFFICE, "教研室"); + roleNames.put(ROLE_TEACHING_CLASS, "教学班次"); + roleNames.put(ROLE_INSPECTION_ADMINISTRATOR, "听查课管理员"); + roleNames.put(ROLE_STUDENT, "学员"); + roleNames.put(ROLE_LEGACY_USER, "旧用户"); + + // 第二步:转换为只读映射,防止运行期间被其他业务代码修改。 + ROLE_NAMES = Collections.unmodifiableMap(roleNames); + + // 第三步:构造只读角色类型列表,供角色清理、迁移和页面选项统一复用。 + ROLE_TYPES = Collections.unmodifiableList(Arrays.asList( + ROLE_DEPARTMENT_PERSONNEL, + ROLE_TEACHER, + ROLE_RESEARCH_OFFICE, + ROLE_TEACHING_CLASS, + ROLE_INSPECTION_ADMINISTRATOR, + ROLE_STUDENT, + ROLE_LEGACY_USER)); + } + + /** + * 私有化构造方法,防止常量工具类被实例化。 + */ + private AccountManagementConstants() { + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/AccountManagementUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/AccountManagementUtil.java new file mode 100644 index 000000000..aa56c15b1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/AccountManagementUtil.java @@ -0,0 +1,917 @@ +package com.roomroot.jwgl.utils; + +import com.roomroot.jwgl.dto.accountmanagement.AccountCreateDTO; +import com.roomroot.jwgl.dto.accountmanagement.AccountUpdateDTO; +import com.roomroot.jwgl.entity.SSOUserAuthSession; +import com.roomroot.jwgl.entity.accountmanagement.AccountRecord; +import com.roomroot.jwgl.entity.accountmanagement.AccountRoleRecord; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.jwgl.vo.accountmanagement.AccountLoginVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountRoleVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountSsoSessionVO; +import com.roomroot.jwgl.vo.accountmanagement.AccountVO; +import org.springframework.dao.DuplicateKeyException; +import org.springframework.security.crypto.password.PasswordEncoder; + +import jakarta.servlet.http.HttpSession; +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.IntSupplier; + +import static com.roomroot.jwgl.utils.AccountManagementConstants.ACCOUNT_ID_MAX_LENGTH; +import static com.roomroot.jwgl.utils.AccountManagementConstants.ACCOUNT_TEXT_MAX_LENGTH; +import static com.roomroot.jwgl.utils.AccountManagementConstants.BAD_REQUEST; +import static com.roomroot.jwgl.utils.AccountManagementConstants.CONFLICT; +import static com.roomroot.jwgl.utils.AccountManagementConstants.INTRODUCTION_MAX_LENGTH; +import static com.roomroot.jwgl.utils.AccountManagementConstants.LOGIN_NAME_UNIQUE_INDEX; +import static com.roomroot.jwgl.utils.AccountManagementConstants.PASSWORD_COMPLEXITY_PATTERN; +import static com.roomroot.jwgl.utils.AccountManagementConstants.PASSWORD_MAX_BYTES; +import static com.roomroot.jwgl.utils.AccountManagementConstants.PASSWORD_MAX_LENGTH; +import static com.roomroot.jwgl.utils.AccountManagementConstants.PASSWORD_MIN_LENGTH; +import static com.roomroot.jwgl.utils.AccountManagementConstants.PHONE_MAX_LENGTH; +import static com.roomroot.jwgl.utils.AccountManagementConstants.ROLE_NAMES; +import static com.roomroot.jwgl.utils.AccountManagementConstants.ROLE_TYPES; +import static com.roomroot.jwgl.utils.AccountManagementConstants.UNIFIED_ACCOUNT_UNIQUE_INDEX; + +/** + * 账户管理无状态辅助工具类。 + * + *

集中处理账户参数校验、文本规范化、对象转换、角色排序、 + * SSO 身份候选整理和数据库唯一约束异常识别。该类不访问 Mapper, + * 不负责事务和账户状态变更,账户业务流程仍由 Service 实现层编排。

+ */ +public final class AccountManagementUtil { + + /** + * 构造新增账户持久化记录。 + * + * @param accountId 新账户编号 + * @param dto 账户新增参数 + * @param passwordEncoder 系统统一密码编码器 + * @return 完成规范化和默认值初始化的持久化记录 + */ + public static AccountRecord buildNewAccount( + String accountId, + AccountCreateDTO dto, + PasswordEncoder passwordEncoder) { + // 第一步:创建只在业务层和 Mapper 之间流转的持久化记录。 + AccountRecord account = new AccountRecord(); + + // 第二步:设置生成的账户编号和经过校验的必填账户资料。 + account.setId(accountId); + account.setLoginName(requiredText( + dto.getLoginName(), + ACCOUNT_TEXT_MAX_LENGTH, + "登录账号")); + account.setUserName(requiredText( + dto.getUserName(), + ACCOUNT_TEXT_MAX_LENGTH, + "用户姓名")); + + // 第三步:规范化全部可选账户资料字段。 + account.setUnifiedAccount(optionalText( + dto.getUnifiedAccount(), + ACCOUNT_TEXT_MAX_LENGTH, + "统一账号")); + account.setPhone(optionalText( + dto.getPhone(), PHONE_MAX_LENGTH, "联系电话")); + account.setIdCard(optionalText( + dto.getIdCard(), + ACCOUNT_TEXT_MAX_LENGTH, + "身份证号码")); + account.setPasswordQuestion(optionalText( + dto.getPasswordQuestion(), + ACCOUNT_TEXT_MAX_LENGTH, + "密码提示问题")); + account.setPasswordAnswer(optionalText( + dto.getPasswordAnswer(), + ACCOUNT_TEXT_MAX_LENGTH, + "密码提示答案")); + account.setIntroduction(optionalText( + dto.getIntroduction(), + INTRODUCTION_MAX_LENGTH, + "自我介绍")); + + // 第四步:校验初始密码的长度、字节数和复杂度。 + String password = requiredPassword( + dto.getPassword(), "初始密码"); + + // 第五步:只把 BCrypt 密码摘要写入内部持久化记录。 + account.setPassword(passwordEncoder.encode(password)); + + // 第六步:显式初始化登录统计,避免依赖不同数据库环境的默认值。 + // 注意:登录信息表的"上次登录时间"、"本次登录时间"、"失败次数"、"失败时间"均为 NOT NULL 约束, + // 新账户从未登录过时使用当前时间占位(上次/本次使用同一时间),失败次数为0,失败时间使用同一时间占位。 + LocalDateTime now = LocalDateTime.now(); + account.setLastLoginAt(now); + account.setCurrentLoginAt(now); + account.setFailedAttempts(Integer.valueOf(0)); + account.setFailedAt(now); + + // 第七步:显式初始化账户启用和未合并状态。 + account.setEnabled(Boolean.TRUE); + account.setDisabledReason(null); + account.setDisabledAt(null); + account.setMergedToAccountId(null); + account.setMergedAt(null); + + // 第八步:返回可直接写入数据库的账户记录。 + return account; + } + + /** + * 构造账户修改持久化记录。 + * + * @param accountId 账户编号 + * @param dto 账户修改参数 + * @return 只包含允许普通修改字段的持久化记录 + */ + public static AccountRecord buildUpdatedAccount( + String accountId, AccountUpdateDTO dto) { + // 第一步:创建独立持久化记录,避免直接修改查询得到的旧对象。 + AccountRecord account = new AccountRecord(); + + // 第二步:设置不可变账户编号和经过校验的必填资料。 + account.setId(accountId); + account.setLoginName(requiredText( + dto.getLoginName(), + ACCOUNT_TEXT_MAX_LENGTH, + "登录账号")); + account.setUserName(requiredText( + dto.getUserName(), + ACCOUNT_TEXT_MAX_LENGTH, + "用户姓名")); + + // 第三步:设置经过规范化的可选账户资料。 + account.setUnifiedAccount(optionalText( + dto.getUnifiedAccount(), + ACCOUNT_TEXT_MAX_LENGTH, + "统一账号")); + account.setPhone(optionalText( + dto.getPhone(), PHONE_MAX_LENGTH, "联系电话")); + account.setIdCard(optionalText( + dto.getIdCard(), + ACCOUNT_TEXT_MAX_LENGTH, + "身份证号码")); + account.setPasswordQuestion(optionalText( + dto.getPasswordQuestion(), + ACCOUNT_TEXT_MAX_LENGTH, + "密码提示问题")); + account.setPasswordAnswer(optionalText( + dto.getPasswordAnswer(), + ACCOUNT_TEXT_MAX_LENGTH, + "密码提示答案")); + account.setIntroduction(optionalText( + dto.getIntroduction(), + INTRODUCTION_MAX_LENGTH, + "自我介绍")); + + // 第四步:返回普通修改接口允许写入的账户记录。 + return account; + } + + /** + * 执行可能触发账户名称唯一约束的数据库写入。 + * + *

写入前查询用于尽早返回明确提示,数据库唯一索引用于阻止两个 + * 并发事务同时通过查询后写入重复账号。本方法负责统一转换最终冲突。

+ * + * @param writeOperation 新增或修改账户的数据库写入操作 + * @return 数据库实际影响行数 + */ + public static int executeAccountNameUniqueWrite( + IntSupplier writeOperation) { + try { + // 第一步:执行新增或修改账户的 Mapper 写入操作。 + return writeOperation.getAsInt(); + } catch (RuntimeException exception) { + /* + * 第二步:遍历数据库异常链,判断本次失败是否由账户名称 + * 唯一索引引起,并尽可能区分登录账号和统一账号。 + */ + String conflictMessage = + resolveAccountNameConflictMessage(exception); + + // 第三步:非账户名称唯一冲突必须保留原异常,避免掩盖其他数据库故障。 + if (conflictMessage == null) { + throw exception; + } + + // 第四步:将数据库并发冲突转换为前端可直接处理的 409 业务异常。 + throw new ServiceException(conflictMessage, CONFLICT); + } + } + + /** + * 安全作废已经存在的 Servlet 会话。 + * + * @param session 需要作废的 Servlet 会话 + */ + public static void invalidateSession(HttpSession session) { + try { + // 作废会话中的全部认证状态和业务属性。 + session.invalidate(); + } catch (IllegalStateException ignored) { + /* + * 并发请求可能已经提前作废同一个会话。 + * 该状态已经满足安全目标,因此无需再次向外抛出异常。 + */ + } + } + + /** + * 将内部角色记录转换为对外角色 VO。 + * + * @param roleRecord 内部角色持久化记录 + * @return 独立账户角色 VO + */ + public static AccountRoleVO toAccountRoleVO( + AccountRoleRecord roleRecord) { + // 第一步:创建独立 VO,避免持久化分组字段向页面暴露。 + AccountRoleVO role = new AccountRoleVO(); + + // 第二步:复制角色身份、数据范围和可用状态。 + role.setRoleType(roleRecord.getRoleType()); + role.setTargetId(roleRecord.getTargetId()); + role.setTargetName(roleRecord.getTargetName()); + role.setDataScope(roleRecord.getDataScope()); + role.setEnabled(roleRecord.getEnabled()); + + // 第三步:返回尚待统一规范化和排序的角色 VO。 + return role; + } + + /** + * 规范化角色类型、补充名称并稳定排序。 + * + * @param roles 可修改角色集合 + */ + public static void normalizeAndSortRoles( + List roles) { + // 第一步:从后向前移除空记录,并补充统一角色类型和名称。 + for (int index = roles.size() - 1; index >= 0; index--) { + AccountRoleVO role = roles.get(index); + if (role == null) { + roles.remove(index); + continue; + } + + // 角色类型统一转换为大写,兼容历史返回值格式差异。 + String roleType = normalizeRoleType(role.getRoleType()); + role.setRoleType(roleType); + role.setRoleName(ROLE_NAMES.get(roleType)); + } + + // 第二步:按照角色类型页面顺序、身份名称和身份编号稳定排序。 + Collections.sort(roles, new Comparator() { + @Override + public int compare( + AccountRoleVO left, AccountRoleVO right) { + return compareRoles(left, right); + } + }); + } + + /** + * 整理 SSO 会话中可以用于匹配本地统一账号的身份候选值。 + * + *

当前接口资料没有明确 LoginID 和 UserID 中哪一个固定对应本地统一账号, + * 因此按照 LoginID、UserID 的顺序同时保留,并忽略大小写完成去重。

+ * + * @param ssoSession 已通过存在性和有效期校验的 SSO 会话 + * @return 规范化并完成忽略大小写去重的统一账号候选值 + */ + public static List collectSsoUnifiedAccountCandidates( + SSOUserAuthSession ssoSession) { + /* + * 第一步:创建有序映射保存候选值。 + * 键用于忽略大小写去重,值保留规范化后的原始文字供 Mapper 查询。 + */ + Map candidatesByKey = + new LinkedHashMap<>(); + + // 第二步:优先加入 SSO 会话中的 LoginID 候选值。 + addSsoUnifiedAccountCandidate( + candidatesByKey, ssoSession.getLoginID()); + + // 第三步:再加入 SSO 会话中的 UserID 候选值。 + addSsoUnifiedAccountCandidate( + candidatesByKey, ssoSession.getUserID()); + + // 第四步:复制有序映射中的值,返回与内部去重结构相互独立的候选列表。 + return new ArrayList<>(candidatesByKey.values()); + } + + /** + * 将有效 SSO 会话和唯一的本地账户转换为对外返回 VO。 + * + * @param ssoSession 已通过有效期校验的 SSO 会话 + * @param account 已通过唯一性和可用状态校验的本地账户 + * @param roles 已经规范化并完成排序的本地账户角色 + * @return 不包含密码和 SSO 会话标识的独立返回 VO + */ + public static AccountSsoSessionVO toAccountSsoSessionVO( + SSOUserAuthSession ssoSession, + AccountRecord account, + List roles) { + // 第一步:创建独立 VO,避免直接向调用方暴露实体和持久化记录。 + AccountSsoSessionVO vo = new AccountSsoSessionVO(); + + /* + * 第二步:复制 SSO 会话的非敏感核验信息。 + * SessionKey 不执行赋值,避免服务端会话标识通过响应再次暴露。 + */ + vo.setAppKey(ssoSession.getAppKey()); + vo.setLoginId(ssoSession.getLoginID()); + vo.setUserId(ssoSession.getUserID()); + vo.setUserType(ssoSession.getUserType()); + vo.setIpAddress(ssoSession.getIpAddress()); + vo.setInvalidTime(ssoSession.getInvalidTime()); + vo.setCreateTime(ssoSession.getCreateTime()); + + /* + * 第三步:只复制本地账户的标识、展示信息和启用状态。 + * 密码、密码提示答案、登录失败记录及账户合并审计信息均不返回。 + */ + vo.setAccountId(account.getId()); + vo.setLoginName(account.getLoginName()); + vo.setUserName(account.getUserName()); + vo.setUnifiedAccount(account.getUnifiedAccount()); + vo.setEnabled(isAccountEnabled(account)); + + // 第四步:复制角色列表,避免调用方修改业务层内部使用的集合对象。 + vo.setRoles(new ArrayList<>(roles)); + + // 第五步:返回完成安全字段装配的 SSO 本地账户解析结果。 + return vo; + } + + /** + * 将本地账户持久化记录转换为登录结果 VO。 + * + * @param account 已通过登录校验的账户记录 + * @param roles 已经整理好的账户角色 + * @return 不包含密码和登录会话编号的登录结果 + */ + public static AccountLoginVO toAccountLoginVO( + AccountRecord account, + List roles) { + // 第一步:创建独立登录结果 VO,避免直接暴露内部账户实体。 + AccountLoginVO vo = new AccountLoginVO(); + + // 第二步:复制登录后页面识别账户所需的非敏感字段。 + vo.setAccountId(account.getId()); + vo.setLoginName(account.getLoginName()); + vo.setUserName(account.getUserName()); + vo.setUnifiedAccount(account.getUnifiedAccount()); + vo.setEnabled(isAccountEnabled(account)); + + // 第三步:复制独立角色集合,避免调用方修改业务层内部集合。 + vo.setRoles(new ArrayList<>(roles)); + + // 第四步:返回不包含密码和 Servlet Session 编号的登录结果。 + return vo; + } + + /** + * 将账户持久化记录转换为对外 VO。 + * + * @param account 账户持久化记录 + * @param roles 账户角色列表 + * @return 不包含密码和密码答案的账户 VO + */ + public static AccountVO toAccountVO( + AccountRecord account, List roles) { + // 第一步:创建独立返回对象,避免对外暴露数据库持久化记录。 + AccountVO vo = new AccountVO(); + + // 第二步:复制账户标识和主要展示资料。 + vo.setId(account.getId()); + vo.setLoginName(account.getLoginName()); + vo.setUserName(account.getUserName()); + vo.setUnifiedAccount(account.getUnifiedAccount()); + vo.setPhone(account.getPhone()); + vo.setIdCard(account.getIdCard()); + vo.setPasswordQuestion(account.getPasswordQuestion()); + vo.setIntroduction(account.getIntroduction()); + + // 第三步:复制登录时间和临时失败统计。 + vo.setLastLoginAt(account.getLastLoginAt()); + vo.setCurrentLoginAt(account.getCurrentLoginAt()); + vo.setFailedAttempts(account.getFailedAttempts()); + vo.setFailedAt(account.getFailedAt()); + + // 第四步:复制账户启停和合并审计状态。 + vo.setEnabled(isAccountEnabled(account)); + vo.setDisabledReason(account.getDisabledReason()); + vo.setDisabledAt(account.getDisabledAt()); + vo.setMergedToAccountId(account.getMergedToAccountId()); + vo.setMergedAt(account.getMergedAt()); + + /* + * 第五步:复制已经整理的角色 VO。 + * 用户密码和密码提示答案没有对应赋值步骤,确保敏感字段不会返回页面。 + */ + vo.setRoles(new ArrayList<>(roles)); + + // 第六步:返回组装完成的账户 VO。 + return vo; + } + + /** + * 判断账户当前是否启用。 + * + * @param account 账户记录 + * @return 启用时返回 true + */ + public static boolean isAccountEnabled(AccountRecord account) { + // 旧账户没有专用状态记录时按照默认启用状态处理。 + return !Boolean.FALSE.equals(account.getEnabled()); + } + + /** + * 生成用于提示的账户名称。 + * + * @param account 账户记录 + * @return 优先使用登录账号的账户展示名称 + */ + public static String displayAccountName(AccountRecord account) { + // 第一步:优先使用管理员最容易识别的登录账号。 + String displayName = normalizeText(account.getLoginName()); + if (displayName != null) { + return displayName; + } + + // 第二步:登录账号异常为空时使用账户编号兜底。 + return account.getId(); + } + + /** + * 生成角色绑定唯一键。 + * + * @param roleType 角色类型 + * @param targetId 身份编号 + * @return 不会产生普通文本拼接歧义的唯一键 + */ + public static String roleBindingKey( + String roleType, String targetId) { + // 使用不可见分隔符隔离角色类型和身份编号。 + return roleType + '\u0000' + targetId; + } + + /** + * 校验并规范化必填账户编号。 + * + * @param accountId 原始账户编号 + * @param fieldName 字段名称 + * @return 有效账户编号 + */ + public static String requireAccountId( + String accountId, String fieldName) { + // 复用必填文本校验并限制为 UUID 字段长度。 + return requiredText( + accountId, ACCOUNT_ID_MAX_LENGTH, fieldName); + } + + /** + * 校验并规范化可选账户编号。 + * + * @param accountId 原始账户编号 + * @param fieldName 字段名称 + * @return 规范化账户编号;未填写时返回 null + */ + public static String optionalAccountId( + String accountId, String fieldName) { + // 复用可选文本校验并限制为 UUID 字段长度。 + return optionalText( + accountId, ACCOUNT_ID_MAX_LENGTH, fieldName); + } + + /** + * 校验并规范化必填角色类型。 + * + * @param roleType 原始角色类型 + * @return 系统支持的标准大写角色类型 + */ + public static String requireRoleType(String roleType) { + // 第一步:去除首尾空白并统一转换为大写。 + String normalizedRoleType = normalizeRoleType(roleType); + + // 第二步:角色类型为空时返回必填参数错误。 + if (normalizedRoleType == null) { + throw new ServiceException("角色类型不能为空", + BAD_REQUEST); + } + + // 第三步:不属于系统七类角色时返回参数错误。 + if (!ROLE_NAMES.containsKey(normalizedRoleType)) { + throw new ServiceException("不支持的角色类型:" + normalizedRoleType, + BAD_REQUEST); + } + + // 第四步:返回标准角色类型。 + return normalizedRoleType; + } + + /** + * 校验并规范化可选角色类型。 + * + * @param roleType 原始角色类型 + * @return 标准角色类型;未填写时返回 null + */ + public static String optionalRoleType(String roleType) { + // 第一步:规范化角色类型。 + String normalizedRoleType = normalizeRoleType(roleType); + + // 第二步:未填写可选角色类型时直接返回 null。 + if (normalizedRoleType == null) { + return null; + } + + // 第三步:存在值时复用必填角色类型合法性校验。 + return requireRoleType(normalizedRoleType); + } + + /** + * 统一规范化角色类型。 + * + * @param roleType 原始角色类型 + * @return 去除空白并转换为大写的角色类型 + */ + public static String normalizeRoleType(String roleType) { + // 第一步:使用统一文本规则去除首尾空白和空字符串。 + String normalized = normalizeText(roleType); + + // 第二步:存在值时按照固定区域规则转换为大写。 + return normalized == null + ? null + : normalized.toUpperCase(Locale.ROOT); + } + + /** + * 校验登录用原始密码。 + * + *

登录密码不执行 trim,也不重复执行注册时的复杂度校验; + * 只校验必填和长度,最终由 PasswordEncoder.matches 完成摘要比较。

+ * + * @param password 原始登录密码 + * @return 保持原样的原始登录密码 + */ + public static String requiredLoginPassword(String password) { + // 复用旧密码校验规则,并保留登录接口原有字段提示。 + return requiredExistingPassword(password, "登录密码"); + } + + /** + * 校验用于身份确认的已有密码。 + * + *

已有密码不执行 trim,也不要求符合当前新密码复杂度规则, + * 只校验必填和长度,最终由 PasswordEncoder.matches 比较摘要。

+ * + * @param password 原始已有密码 + * @param fieldName 字段名称 + * @return 保持原样的已有密码 + */ + public static String requiredExistingPassword( + String password, String fieldName) { + // 第一步:拒绝 null、空字符串和只包含空白字符的已有密码。 + if (password == null || password.trim().isEmpty()) { + throw new ServiceException(fieldName + "不能为空", + BAD_REQUEST); + } + + // 第二步:校验已有密码长度,保持与账户密码字段的输入边界一致。 + if (password.length() < PASSWORD_MIN_LENGTH + || password.length() > PASSWORD_MAX_LENGTH) { + throw new ServiceException(fieldName + "长度必须在8到50个字符之间", + BAD_REQUEST); + } + + // 第三步:返回未被 trim 或改写的原始密码,交给 PasswordEncoder 校验。 + return password; + } + + /** + * 校验必填原始密码。 + * + *

密码不执行 trim,避免静默改变用户实际输入; + * 空白字符由复杂度规则统一拒绝。

+ * + * @param password 原始密码 + * @param fieldName 字段名称 + * @return 通过完整校验的原始密码 + */ + public static String requiredPassword( + String password, String fieldName) { + // 第一步:拒绝 null、空字符串和只包含空白字符的密码。 + if (password == null || password.trim().isEmpty()) { + throw new ServiceException(fieldName + "不能为空", + BAD_REQUEST); + } + + // 第二步:校验密码字符长度,防止过短密码和超长请求。 + if (password.length() < PASSWORD_MIN_LENGTH + || password.length() > PASSWORD_MAX_LENGTH) { + throw new ServiceException(fieldName + "长度必须在8到50个字符之间", + BAD_REQUEST); + } + + /* + * 第三步:限制 UTF-8 字节数。 + * BCrypt 最多完整处理 72 字节,提前拒绝可以避免不同密码因截断产生相同摘要。 + */ + if (password.getBytes(StandardCharsets.UTF_8).length + > PASSWORD_MAX_BYTES) { + throw new ServiceException(fieldName + "的UTF-8编码不能超过72字节", + BAD_REQUEST); + } + + /* + * 第四步:校验密码复杂度。 + * 密码必须同时包含大写字母、小写字母、数字和特殊字符, + * 并且不能包含空格、换行或制表符等空白字符。 + */ + if (!password.matches(PASSWORD_COMPLEXITY_PATTERN)) { + throw new ServiceException(fieldName + + "必须包含大写字母、小写字母、数字和特殊字符," + + "且不能包含空白字符", + BAD_REQUEST); + } + + // 第五步:返回保持原样的密码,交由 PasswordEncoder 立即编码。 + return password; + } + + /** + * 校验并规范化必填文本。 + * + * @param value 原始文本 + * @param maxLength 最大长度 + * @param fieldName 字段名称 + * @return 有效文本 + */ + public static String requiredText( + String value, int maxLength, String fieldName) { + // 第一步:使用统一可选文本规则完成规范化和长度校验。 + String normalized = + optionalText(value, maxLength, fieldName); + + // 第二步:规范化后为空时返回必填参数错误。 + if (normalized == null) { + throw new ServiceException(fieldName + "不能为空", + BAD_REQUEST); + } + + // 第三步:返回通过校验的必填文本。 + return normalized; + } + + /** + * 校验并规范化可选文本。 + * + * @param value 原始文本 + * @param maxLength 最大长度 + * @param fieldName 字段名称 + * @return 规范化文本;未填写时返回 null + */ + public static String optionalText( + String value, int maxLength, String fieldName) { + // 第一步:去除首尾空白,并将空字符串统一转换为 null。 + String normalized = normalizeText(value); + + // 第二步:未填写可选文本时直接返回 null。 + if (normalized == null) { + return null; + } + + // 第三步:超过数据库字段长度时返回参数错误。 + if (normalized.length() > maxLength) { + throw new ServiceException(fieldName + "不能超过" + maxLength + "个字符", + BAD_REQUEST); + } + + // 第四步:返回通过校验的可选文本。 + return normalized; + } + + /** + * 统一规范化文本。 + * + * @param value 原始文本 + * @return 去除首尾空白后的文本;无有效内容时返回 null + */ + public static String normalizeText(String value) { + // 原始值为 null 时直接返回 null。 + if (value == null) { + return null; + } + + // 去除用户输入或数据库文本中的首尾空白。 + String normalized = value.trim(); + + // 去除空白后没有有效内容时统一返回 null。 + return normalized.isEmpty() ? null : normalized; + } + + /** + * 判断源文本是否包含关键字,并忽略英文字母大小写。 + * + * @param source 源文本 + * @param keyword 查询关键字 + * @return 包含时返回 true + */ + public static boolean containsIgnoreCase( + String source, String keyword) { + // 源文本为空时不能匹配,否则按照固定区域规则转为小写后比较。 + return source != null + && source.toLowerCase(Locale.ROOT) + .contains(keyword.toLowerCase(Locale.ROOT)); + } + + /** + * 从异常链中解析账户名称唯一冲突提示。 + * + * @param exception 数据库写入抛出的运行时异常 + * @return 唯一冲突提示;不是账户名称唯一冲突时返回 null + */ + private static String resolveAccountNameConflictMessage( + RuntimeException exception) { + // 第一步:记录异常链中是否出现通用唯一约束冲突标志。 + boolean uniqueConstraintViolation = false; + + // 第二步:创建已访问异常集合,防止异常原因链异常循环。 + Set visitedCauses = new HashSet<>(); + + // 第三步:从最外层数据库访问异常开始逐层检查原因。 + Throwable currentCause = exception; + while (currentCause != null + && visitedCauses.add(currentCause)) { + // 第四步:优先根据固定索引名称返回准确的字段冲突提示。 + String exceptionMessage = currentCause.getMessage(); + if (exceptionMessage != null) { + String normalizedMessage = + exceptionMessage.toUpperCase(Locale.ROOT); + if (normalizedMessage.contains( + UNIFIED_ACCOUNT_UNIQUE_INDEX + .toUpperCase(Locale.ROOT))) { + return "统一账号已存在"; + } + if (normalizedMessage.contains( + LOGIN_NAME_UNIQUE_INDEX + .toUpperCase(Locale.ROOT))) { + return "登录账号已存在"; + } + } + + // 第五步:Spring 已转换为重复键异常时记录通用唯一冲突。 + if (currentCause instanceof DuplicateKeyException) { + uniqueConstraintViolation = true; + } + + /* + * 第六步:兼容未被 Spring 精确转换的达梦 SQLException。 + * 仅通过达梦唯一约束特有错误码判断,不使用 SQLSTATE 23 前缀, + * 因为该前缀也覆盖 NOT NULL、外键等非唯一约束,会导致误判。 + */ + if (currentCause instanceof SQLException) { + SQLException sqlException = + (SQLException) currentCause; + int errorCode = sqlException.getErrorCode(); + if (errorCode == -6602 + || errorCode == -6625 + || errorCode == -6612) { + uniqueConstraintViolation = true; + } + } + + // 第七步:继续检查当前异常包装的下一层原始原因。 + currentCause = currentCause.getCause(); + } + + /* + * 第八步:无法从异常文本区分具体索引但确认属于唯一约束冲突时, + * 返回同时覆盖登录账号和统一账号的通用提示。 + */ + return uniqueConstraintViolation + ? "登录账号或统一账号已存在" + : null; + } + + /** + * 向 SSO 统一账号候选映射中加入一个有效身份值。 + * + * @param candidatesByKey 忽略大小写的候选值有序映射 + * @param candidate SSO 会话中的原始身份值 + */ + private static void addSsoUnifiedAccountCandidate( + Map candidatesByKey, + String candidate) { + // 第一步:去除候选值首尾空白,并把空内容统一转换为 null。 + String normalizedCandidate = normalizeText(candidate); + + // 第二步:无有效内容的身份值不能用于查询本地统一账号。 + if (normalizedCandidate == null) { + return; + } + + // 第三步:使用固定区域规则生成忽略英文字母大小写的去重键。 + String candidateKey = + normalizedCandidate.toLowerCase(Locale.ROOT); + + // 第四步:同一身份值重复出现时保留最先加入的规范化结果。 + candidatesByKey.putIfAbsent( + candidateKey, normalizedCandidate); + } + + /** + * 比较两个角色 VO。 + * + * @param left 左侧角色 + * @param right 右侧角色 + * @return 符合 Comparator 约定的比较结果 + */ + private static int compareRoles( + AccountRoleVO left, AccountRoleVO right) { + // 第一优先级:按照系统定义的角色类型页面顺序排序。 + int roleTypeResult = Integer.compare( + roleTypeOrder(left.getRoleType()), + roleTypeOrder(right.getRoleType())); + if (roleTypeResult != 0) { + return roleTypeResult; + } + + // 第二优先级:同一角色类型内按照身份名称排序。 + int targetNameResult = compareNullableText( + left.getTargetName(), right.getTargetName()); + if (targetNameResult != 0) { + return targetNameResult; + } + + // 第三优先级:身份名称相同时按照身份编号保证结果稳定。 + return compareNullableText( + left.getTargetId(), right.getTargetId()); + } + + /** + * 取得角色类型的固定页面顺序。 + * + * @param roleType 角色类型 + * @return 角色顺序;未知类型排在最后 + */ + private static int roleTypeOrder(String roleType) { + // 第一步:查询角色类型在固定列表中的位置。 + int index = ROLE_TYPES.indexOf(roleType); + + // 第二步:未知历史类型统一排在全部已知类型之后。 + return index < 0 ? ROLE_TYPES.size() : index; + } + + /** + * 比较两个允许为空的文本。 + * + * @param left 左侧文本 + * @param right 右侧文本 + * @return 符合 Comparator 约定的比较结果 + */ + private static int compareNullableText( + String left, String right) { + // 两侧都为空时视为相等。 + if (left == null && right == null) { + return 0; + } + + // 仅左侧为空时将左侧排在后面。 + if (left == null) { + return 1; + } + + // 仅右侧为空时将右侧排在后面。 + if (right == null) { + return -1; + } + + // 优先忽略英文字母大小写比较。 + int ignoreCaseResult = left.compareToIgnoreCase(right); + + // 忽略大小写后相等时再比较原文本,保证排序结果稳定。 + return ignoreCaseResult == 0 + ? left.compareTo(right) + : ignoreCaseResult; + } + + /** + * 私有化构造方法,防止无状态工具类被实例化。 + */ + private AccountManagementUtil() { + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/BasicManagementConstants.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/BasicManagementConstants.java new file mode 100644 index 000000000..0e54d6795 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/BasicManagementConstants.java @@ -0,0 +1,136 @@ +package com.roomroot.jwgl.utils; + +/** + * 基础管理功能公共常量工具类。 + * + *

集中维护部别管理、隶属关系设置和部别人员管理使用的业务码、 + * 字段长度、批量处理上限及数据库状态值,避免业务实现类直接声明固定数值。

+ */ +public final class BasicManagementConstants { + + /** + * 请求参数错误业务码。 + */ + public static final int BAD_REQUEST = 400; + + /** + * 数据不存在业务码。 + */ + public static final int NOT_FOUND = 404; + + /** + * 无权限业务码。 + */ + public static final int FORBIDDEN = 403; + + /** + * 数据冲突业务码。 + */ + public static final int CONFLICT = 409; + + /** + * UUID 等通用标识符的最大长度。 + */ + public static final int IDENTIFIER_MAX_LENGTH = 36; + + /** + * 教研室编号的最大长度。 + */ + public static final int RESEARCH_OFFICE_ID_MAX_LENGTH = 50; + + /** + * 教学班次编号的最大长度。 + */ + public static final int TEACHING_CLASS_ID_MAX_LENGTH = 50; + + /** + * 人员文本字段最大长度。 + */ + public static final int PERSONNEL_TEXT_MAX_LENGTH = 50; + + /** + * 操作人查询关键字的最大长度。 + */ + public static final int OPERATOR_KEYWORD_MAX_LENGTH = 50; + + /** + * 操作日志模块名称的最大长度。 + */ + public static final int MODULE_NAME_MAX_LENGTH = 20; + + /** + * 操作内容查询关键字的最大长度。 + */ + public static final int CONTENT_KEYWORD_MAX_LENGTH = 100; + + /** + * 请求地址查询关键字的最大长度。 + */ + public static final int URL_KEYWORD_MAX_LENGTH = 200; + + /** + * 操作日志写入内容的最大长度。 + */ + public static final int OPERATION_CONTENT_MAX_LENGTH = 2000; + + /** + * 操作日志写入请求地址的最大长度。 + */ + public static final int OPERATION_URL_MAX_LENGTH = 200; + + /** + * 当前请求中暂存操作日志登录记录编号的属性名。 + * + *

统一日志拦截器在 Session 作废前保存编号, + * 供请求完成后的自动记录流程继续使用。

+ */ + public static final String + OPERATION_LOG_LOGIN_RECORD_ID_REQUEST_ATTRIBUTE = + "OPERATION_LOG_LOGIN_RECORD_ID"; + + /** + * 当前请求是否发生业务处理失败的属性名。 + * + *

全局异常处理器负责写入该标记, + * 操作日志拦截器在请求完成阶段据此补充失败操作日志。

+ */ + public static final String + OPERATION_LOG_FAILED_REQUEST_ATTRIBUTE = + "OPERATION_LOG_FAILED"; + + /** + * 操作日志导出时单次分页查询的最大记录数。 + */ + public static final int OPERATION_LOG_EXPORT_BATCH_SIZE = 100; + + /** + * 单次批量导入最大人数。 + */ + public static final int BATCH_IMPORT_MAX_SIZE = 1000; + + /** + * 数据库存储的在职状态。 + */ + public static final int ACTIVE_STATUS = 0; + + /** + * 数据库存储的离职状态。 + */ + public static final int DEPARTED_STATUS = 1; + + /** + * 数据库存储的非主官状态。 + */ + public static final int NON_PRINCIPAL_STATUS = 0; + + /** + * 数据库存储的主官状态。 + */ + public static final int PRINCIPAL_STATUS = 1; + + /** + * 私有化构造方法,防止常量工具类被实例化。 + */ + private BasicManagementConstants() { + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/DateCourseUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/DateCourseUtil.java new file mode 100644 index 000000000..ff75adf5c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/DateCourseUtil.java @@ -0,0 +1,105 @@ +package com.roomroot.jwgl.utils; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DateCourseUtil { + + private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + /** + * 核心方法:传入起止日期,返回日期->星期+12课时分段 + * @param startDate 开始日期 yyyy-MM-dd + * @param endDate 结束日期 yyyy-MM-dd + * @return key:日期字符串 value:星期+课时列表 + */ + public static Map getDateWeekAndCourseMap(String startDate, String endDate) { + Map resultMap = new HashMap<>(); + + // 解析起止日期 + LocalDate start = LocalDate.parse(startDate, FORMATTER); + LocalDate end = LocalDate.parse(endDate, FORMATTER); + + LocalDate currentDate = start; + // 循环遍历区间所有日期(包含首尾) + while (!currentDate.isAfter(end)) { + Map> result = new HashMap<>(); + Map innerMap = new HashMap<>(); + + // 1. 设置中文星期 + innerMap.put("week", getCnWeek(currentDate.getDayOfWeek())); + // 2. 生成当日12课时,两两拼接 + List courseSegList = build12CourseSegment(); + innerMap.put("course", courseSegList); + + // 日期作为key存入map + String dateStr = currentDate.format(FORMATTER); + resultMap.put(dateStr, innerMap); + + // 日期向后+1天 + currentDate = currentDate.plusDays(1); + } + return resultMap; + } + + /** + * 构建固定12课时,每两节合并一段 + * @return [1-2,3-4,5-6,7-8,9-10,11-12] + */ + private static List build12CourseSegment() { + List list = new ArrayList<>(); + // 步长2,从1开始 + for (int i = 1; i <= 12; i += 2) { + String seg = i + "-" + (i + 1); + list.add(seg); + } + return list; + } + + /** + * DayOfWeek 转为中文星期 + */ + private static String getCnWeek(DayOfWeek dayOfWeek) { + String week; + switch (dayOfWeek) { + case MONDAY: + week = "星期一"; + break; + case TUESDAY: + week = "星期二"; + break; + case WEDNESDAY: + week = "星期三"; + break; + case THURSDAY: + week = "星期四"; + break; + case FRIDAY: + week = "星期五"; + break; + case SATURDAY: + week = "星期六"; + break; + case SUNDAY: + week = "星期日"; + break; + default: + throw new IllegalArgumentException("无效星期"); + } + return week; + } + + // 测试main方法 + public static void main(String[] args) { + Map map = getDateWeekAndCourseMap("2026-07-01", "2026-08-03"); + // 遍历打印查看结果 + for (Map.Entry entry : map.entrySet()) { + System.out.println(""); + } + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/DateUtils.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/DateUtils.java new file mode 100644 index 000000000..ab1528c99 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/DateUtils.java @@ -0,0 +1,141 @@ +package com.roomroot.jwgl.utils; + +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; + +/** + * JDK1.8 专用日期工具类,自动解析多种常见日期字符串 + * 支持:横杠/斜杠/点分隔、纯数字紧凑格式、10/13位时间戳 + */ +public class DateUtils { + + // 所有支持的日期格式,可自行扩展 + private static final List DATE_PATTERNS; + + static { + DATE_PATTERNS = new ArrayList<>(); + DATE_PATTERNS.add("yyyy-MM-dd HH:mm:ss"); + DATE_PATTERNS.add("yyyy-MM-dd HH:mm"); + DATE_PATTERNS.add("yyyy-MM-dd"); + DATE_PATTERNS.add("yyyy/MM/dd HH:mm:ss"); + DATE_PATTERNS.add("yyyy/MM/dd"); + DATE_PATTERNS.add("yyyy.MM.dd"); + DATE_PATTERNS.add("yyyyMMddHHmmss"); + DATE_PATTERNS.add("yyyyMMdd"); + } + + /** + * 自动识别多种日期字符串,转为 LocalDateTime + * @param dateStr 日期文本 + * @return 解析成功返回LocalDateTime,失败/空返回null + */ + public static LocalDateTime parseAnyDate(String dateStr) { + if (dateStr == null || dateStr.trim().length() == 0) { + return null; + } + String source = dateStr.trim(); + + // 1. 判断是否为时间戳 10位秒 / 13位毫秒 + if (source.matches("^\\d{10,13}$")) { + long ts = Long.parseLong(source); + Instant instant; + if (source.length() == 10) { + instant = Instant.ofEpochSecond(ts); + } else { + instant = Instant.ofEpochMilli(ts); + } + return LocalDateTime.ofInstant(instant, ZoneId.systemDefault()); + } + + // 2. 遍历所有格式尝试解析 + for (String pattern : DATE_PATTERNS) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + if (pattern.contains("HH")) { + // 带时分秒 + return LocalDateTime.parse(source, formatter); + } else { + // 仅日期,补 00:00:00 + LocalDate localDate = LocalDate.parse(source, formatter); + return LocalDateTime.of(localDate, LocalTime.MIN); + } + } catch (DateTimeParseException e) { + // 当前格式不匹配,继续循环 + continue; + } + } + // 全部格式匹配失败 + return null; + } + + /** + * LocalDateTime 格式化输出 + * @param dateTime 时间对象 + * @param pattern 自定义格式,传null默认 yyyy-MM-dd HH:mm:ss + * @return 格式化字符串 + */ + public static String formatDate(LocalDateTime dateTime, String pattern) { + if (dateTime == null) { + return ""; + } + String fmt = pattern; + if (fmt == null || fmt.trim().length() == 0) { + fmt = "yyyy-MM-dd HH:mm:ss"; + } + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(fmt); + return dateTime.format(formatter); + } + + /** + * 默认格式输出 yyyy-MM-dd HH:mm:ss + */ + public static String formatDate(LocalDateTime dateTime) { + return formatDate(dateTime, null); + } + + // ========== 兼容旧 java.util.Date 转换方法(JDK8通用) ========== + /** + * LocalDateTime 转 java.util.Date + */ + public static java.util.Date localDateTimeToDate(LocalDateTime localDateTime) { + if (localDateTime == null) { + return null; + } + Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); + return java.util.Date.from(instant); + } + + /** + * java.util.Date 转 LocalDateTime + */ + public static LocalDateTime dateToLocalDateTime(java.util.Date date) { + if (date == null) { + return null; + } + return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()); + } + + // 测试主方法 + public static void main(String[] args) { + String[] testArr = { + "2026-07-21", + "2026/07/21 15:20:30", + "2026.07.21", + "20260721123000", + "1784604600", + "1784604600000", + "" + }; + for (String s : testArr) { + LocalDateTime dt = parseAnyDate(s); + System.out.println("原始值:" + s + " 解析结果:" + formatDate(dt)); + } + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/ExcelExportUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/ExcelExportUtil.java new file mode 100644 index 000000000..acfa44778 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/ExcelExportUtil.java @@ -0,0 +1,107 @@ +package com.roomroot.jwgl.utils; + +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.io.ByteArrayOutputStream; +import java.util.List; +import java.util.function.Function; + +/** + * Excel 导出工具类。 + */ +public class ExcelExportUtil { + + private ExcelExportUtil() { + } + + /** + * 导出数据为 Excel 字节数组。 + * + * @param sheetName 工作表名称 + * @param headers 表头数组 + * @param data 数据列表 + * @param getters 每列对应的数据取值函数数组 + * @param 数据类型 + * @return Excel 文件字节数组 + * @throws Exception 导出异常 + */ + @SafeVarargs + public static byte[] export(String sheetName, String[] headers, + List data, Function... getters) throws Exception { + Workbook workbook = new XSSFWorkbook(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + Sheet sheet = workbook.createSheet(sheetName); + + // 创建表头样式 + CellStyle headerStyle = createHeaderStyle(workbook); + + // 写入表头 + Row headerRow = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headerStyle); + } + + // 写入数据行 + for (int rowIndex = 0; rowIndex < data.size(); rowIndex++) { + Row row = sheet.createRow(rowIndex + 1); + T item = data.get(rowIndex); + for (int colIndex = 0; colIndex < getters.length; colIndex++) { + Cell cell = row.createCell(colIndex); + Object value = getters[colIndex].apply(item); + setCellValue(cell, value); + } + } + + // 自动调整列宽 + for (int i = 0; i < headers.length; i++) { + sheet.autoSizeColumn(i); + // 增加一些额外宽度,避免中文显示不完整 + int currentWidth = sheet.getColumnWidth(i); + sheet.setColumnWidth(i, currentWidth + 512); + } + + workbook.write(outputStream); + return outputStream.toByteArray(); + } finally { + workbook.close(); + outputStream.close(); + } + } + + /** + * 创建表头样式。 + */ + private static CellStyle createHeaderStyle(Workbook workbook) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER); + return style; + } + + /** + * 设置单元格值。 + */ + private static void setCellValue(Cell cell, Object value) { + if (value == null) { + cell.setCellValue(""); + } else if (value instanceof Number) { + cell.setCellValue(((Number) value).doubleValue()); + } else if (value instanceof Boolean) { + cell.setCellValue((Boolean) value); + } else { + cell.setCellValue(value.toString()); + } + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/ExcelParseUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/ExcelParseUtil.java new file mode 100644 index 000000000..cfdd1a9b7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/ExcelParseUtil.java @@ -0,0 +1,338 @@ +package com.roomroot.jwgl.utils; + +import com.roomroot.jwgl.dto.courserunning.ClassesImportDTO; +import com.roomroot.jwgl.dto.courserunning.SemesterImportDTO; +import com.roomroot.jwgl.dto.courserunning.SingleCourseImportDTO; +import com.roomroot.jwgl.dto.departmentpersonnel.DepartmentPersonnelCreateDTO; +import com.roomroot.jwgl.entity.JXSSJH; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; + +import java.io.InputStream; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; + +public class ExcelParseUtil { + + private ExcelParseUtil() { + } + + public static List parseDepartmentPersonnelExcel(InputStream inputStream, String fileName) throws Exception { + List result = new ArrayList<>(); + + Workbook workbook = createWorkbook(inputStream, fileName); + try { + Sheet sheet = workbook.getSheetAt(0); + int firstDataRow = 1; + int lastRowNum = sheet.getLastRowNum(); + + for (int rowNum = firstDataRow; rowNum <= lastRowNum; rowNum++) { + Row row = sheet.getRow(rowNum); + if (row == null) { + continue; + } + + DepartmentPersonnelCreateDTO dto = parsePersonnelRow(row, rowNum + 1); + if (dto != null && dto.getDepartmentId() != null && dto.getName() != null) { + result.add(dto); + } + } + } finally { + workbook.close(); + } + + return result; + } + + /** + * 解析整学期课程导入 Excel 文件。 + * + * @param inputStream Excel 文件输入流 + * @param fileName 文件名 + * @return 学期导入参数列表 + * @throws Exception 解析异常 + */ + public static List parseCourseSemesterImportExcel( + InputStream inputStream, String fileName) throws Exception { + List result = new ArrayList<>(); + + Workbook workbook = createWorkbook(inputStream, fileName); + try { + Sheet sheet = workbook.getSheetAt(0); + int firstDataRow = 1; + int lastRowNum = sheet.getLastRowNum(); + + for (int rowNum = firstDataRow; rowNum <= lastRowNum; rowNum++) { + Row row = sheet.getRow(rowNum); + if (row == null) { + continue; + } + + SemesterImportDTO dto = new SemesterImportDTO(); + dto.setNd(parseIntCell(row.getCell(0))); + dto.setXqdc(parseIntCell(row.getCell(1))); + + if (dto.getNd() != null && dto.getXqdc() != null) { + result.add(dto); + } + } + } finally { + workbook.close(); + } + + return result; + } + + /** + * 解析教学班次课程导入 Excel 文件。 + * + * @param inputStream Excel 文件输入流 + * @param fileName 文件名 + * @return 教学班次导入参数列表 + * @throws Exception 解析异常 + */ + public static List parseCourseClassesImportExcel( + InputStream inputStream, String fileName) throws Exception { + List result = new ArrayList<>(); + + Workbook workbook = createWorkbook(inputStream, fileName); + try { + Sheet sheet = workbook.getSheetAt(0); + int firstDataRow = 1; + int lastRowNum = sheet.getLastRowNum(); + + for (int rowNum = firstDataRow; rowNum <= lastRowNum; rowNum++) { + Row row = sheet.getRow(rowNum); + if (row == null) { + continue; + } + + ClassesImportDTO dto = new ClassesImportDTO(); + dto.setClassBh(getCellStringValue(row.getCell(0))); + dto.setNd(parseIntCell(row.getCell(1))); + + if (dto.getClassBh() != null && dto.getNd() != null) { + result.add(dto); + } + } + } finally { + workbook.close(); + } + + return result; + } + + /** + * 解析单门课程导入 Excel 文件。 + * + * @param inputStream Excel 文件输入流 + * @param fileName 文件名 + * @return 单门课程导入参数列表 + * @throws Exception 解析异常 + */ + public static List parseCourseSingleImportExcel( + InputStream inputStream, String fileName) throws Exception { + List result = new ArrayList<>(); + + Workbook workbook = createWorkbook(inputStream, fileName); + try { + Sheet sheet = workbook.getSheetAt(0); + int firstDataRow = 1; + int lastRowNum = sheet.getLastRowNum(); + + for (int rowNum = firstDataRow; rowNum <= lastRowNum; rowNum++) { + Row row = sheet.getRow(rowNum); + if (row == null) { + continue; + } + + SingleCourseImportDTO dto = new SingleCourseImportDTO(); + dto.setCourseBh(getCellStringValue(row.getCell(0))); + dto.setClassBh(getCellStringValue(row.getCell(1))); + dto.setNd(parseIntCell(row.getCell(2))); + + if (dto.getCourseBh() != null && dto.getClassBh() != null && dto.getNd() != null) { + result.add(dto); + } + } + } finally { + workbook.close(); + } + + return result; + } + + private static Workbook createWorkbook(InputStream inputStream, String fileName) throws Exception { + String name = fileName == null ? "" : fileName.toLowerCase(); + if (!name.endsWith(".xlsx") && !name.endsWith(".xls")) { + throw new IllegalArgumentException("不支持的文件格式,仅支持.xlsx和.xls格式"); + } + return WorkbookFactory.create(inputStream); + } + + private static DepartmentPersonnelCreateDTO parsePersonnelRow(Row row, int rowIndex) { + DepartmentPersonnelCreateDTO dto = new DepartmentPersonnelCreateDTO(); + + dto.setDepartmentId(getCellStringValue(row.getCell(0))); + dto.setName(getCellStringValue(row.getCell(1))); + dto.setSequenceNumber(getCellStringValue(row.getCell(2))); + dto.setPosition(getCellStringValue(row.getCell(3))); + dto.setCardNumber(getCellStringValue(row.getCell(4))); + dto.setPinyin(getCellStringValue(row.getCell(5))); + dto.setPrincipal(parseBooleanValue(row.getCell(6))); + + return dto; + } + + /** + * 解析单元格整数值。 + * + * @param cell 单元格 + * @return 整数值,无法解析时返回 null + */ + private static Integer parseIntCell(Cell cell) { + String value = getCellStringValue(cell); + if (value == null || value.isEmpty()) { + return null; + } + try { + return Integer.valueOf(value); + } catch (NumberFormatException e) { + return null; + } + } + + private static String getCellStringValue(Cell cell) { + if (cell == null) { + return null; + } + + switch (cell.getCellType()) { + case STRING: + String value = cell.getStringCellValue(); + return value != null ? value.trim() : null; + case NUMERIC: + double numericValue = cell.getNumericCellValue(); + if (numericValue == Math.floor(numericValue)) { + return String.valueOf((long) numericValue); + } + return String.valueOf(numericValue); + case BOOLEAN: + return String.valueOf(cell.getBooleanCellValue()); + case FORMULA: + try { + return cell.getStringCellValue(); + } catch (Exception e) { + return String.valueOf(cell.getNumericCellValue()); + } + default: + return null; + } + } + + /** + * 解析教学实施计划导入 Excel 文件。 + * Excel 表头顺序:序号、年度、原序号、日期、原节次、节次、课程名称、班次、 + * 责任单位、授课教员、教学场地、教学内容、教学方法、教员备注、教务备注、检查结果、课程表编号 + * + * @param inputStream Excel 文件输入流 + * @param fileName 文件名 + * @return 教学实施计划实体列表(标识号和登录编号待外部填充) + * @throws Exception 解析异常 + */ + public static List parseJXSSJHExcel(InputStream inputStream, String fileName) throws Exception { + List result = new ArrayList<>(); + + Workbook workbook = createWorkbook(inputStream, fileName); + try { + Sheet sheet = workbook.getSheetAt(0); + int firstDataRow = 1; + int lastRowNum = sheet.getLastRowNum(); + + for (int rowNum = firstDataRow; rowNum <= lastRowNum; rowNum++) { + Row row = sheet.getRow(rowNum); + if (row == null) { + continue; + } + + JXSSJH entity = new JXSSJH(); + entity.setXh(parseIntCell(row.getCell(0))); + entity.setNd(parseIntCell(row.getCell(1))); + entity.setYxh(getCellStringValue(row.getCell(2))); + entity.setRq(parseDateTimeCell(row.getCell(3))); + entity.setYjc(getCellStringValue(row.getCell(4))); + entity.setJc(parseIntCell(row.getCell(5))); + entity.setKcmc(getCellStringValue(row.getCell(6))); + entity.setBc(getCellStringValue(row.getCell(7))); + entity.setZrdw(getCellStringValue(row.getCell(8))); + entity.setSkjy(getCellStringValue(row.getCell(9))); + entity.setJxcd(getCellStringValue(row.getCell(10))); + entity.setJxnr(getCellStringValue(row.getCell(11))); + entity.setJxff(getCellStringValue(row.getCell(12))); + entity.setJybz(getCellStringValue(row.getCell(13))); + entity.setJwbz(getCellStringValue(row.getCell(14))); + entity.setJcjg(getCellStringValue(row.getCell(15))); + entity.setKcbbh(getCellStringValue(row.getCell(16))); + + // 至少有课程名称才视为有效行 + if (entity.getKcmc() != null && !entity.getKcmc().isEmpty()) { + result.add(entity); + } + } + } finally { + workbook.close(); + } + + return result; + } + + /** + * 解析日期时间单元格,支持多种格式。 + */ + private static LocalDateTime parseDateTimeCell(Cell cell) { + if (cell == null) { + return null; + } + // 优先尝试POI日期类型 + if (cell.getCellType() == CellType.NUMERIC && org.apache.poi.ss.usermodel.DateUtil.isCellDateFormatted(cell)) { + return cell.getLocalDateTimeCellValue(); + } + // 字符串格式解析 + String value = getCellStringValue(cell); + if (value == null || value.isEmpty()) { + return null; + } + // 尝试多种常见格式 + String[] patterns = { + "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM-dd", + "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM/dd" + }; + for (String pattern : patterns) { + try { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern); + if (pattern.contains("HH")) { + return LocalDateTime.parse(value, formatter); + } else { + return LocalDate.parse(value, formatter).atStartOfDay(); + } + } catch (Exception ignored) { + } + } + return null; + } + + private static Boolean parseBooleanValue(Cell cell) { + String value = getCellStringValue(cell); + if (value == null || value.isEmpty()) { + return false; + } + return "是".equals(value) || "true".equalsIgnoreCase(value) || "1".equals(value) || "√".equals(value); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NoticeAnnouncementConstants.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NoticeAnnouncementConstants.java new file mode 100644 index 000000000..d67e413e8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NoticeAnnouncementConstants.java @@ -0,0 +1,131 @@ +package com.roomroot.jwgl.utils; + +/** + * 通知公告管理常量工具类。 + * + *

集中维护通知公告分页、文本字段和状态流转需要使用的固定值, + * 避免后续业务实现类重复声明相同数字和文本。

+ */ +public final class NoticeAnnouncementConstants { + + /** + * 通知公告分页允许的最大单页记录数。 + */ + public static final int MAX_PAGE_SIZE = 100; + + /** + * 公告编号的最大长度。 + */ + public static final int NOTICE_ID_MAX_LENGTH = 50; + + /** + * 公告标题的最大长度。 + */ + public static final int TITLE_MAX_LENGTH = 100; + + /** + * 公告正文允许的最大长度。 + * + *

数据库字段为大文本类型,该限制用于防止单次请求提交异常大的正文。

+ */ + public static final int CONTENT_MAX_LENGTH = 100000; + + /** + * 公告类别的最大长度。 + */ + public static final int CATEGORY_MAX_LENGTH = 50; + + /** + * 公告状态的最大长度。 + */ + public static final int STATUS_MAX_LENGTH = 50; + + /** + * 公告备注的最大长度。 + */ + public static final int REMARK_MAX_LENGTH = 500; + + /** + * 接收人查询关键字的最大长度。 + */ + public static final int RECIPIENT_KEYWORD_MAX_LENGTH = 100; + + /** + * 发布范围类型的最大长度。 + */ + public static final int SCOPE_TYPE_MAX_LENGTH = 20; + + /** + * 单次指定发布允许选择的最大部别数量。 + */ + public static final int MAX_DEPARTMENT_SCOPE_SIZE = 200; + + /** + * 公告优先级允许的最大值。 + */ + public static final int PRIORITY_MAX_VALUE = 9999; + + /** + * 接收人快照单次批量写入的最大记录数。 + */ + public static final int RECIPIENT_INSERT_BATCH_SIZE = 200; + + /** + * 公告默认类别。 + */ + public static final String DEFAULT_CATEGORY = "公告"; + + /** + * 公告拟制状态。 + */ + public static final String STATUS_DRAFT = "拟制"; + + /** + * 公告已发布状态。 + */ + public static final String STATUS_PUBLISHED = "已发布"; + + /** + * 公告已下架状态。 + */ + public static final String STATUS_DOWN = "已下架"; + + /** + * 面向全校有效账户发布。 + */ + public static final String SCOPE_ALL = "ALL"; + + /** + * 面向全部有效教员账户发布。 + */ + public static final String SCOPE_ALL_TEACHERS = + "ALL_TEACHERS"; + + /** + * 面向指定部别关联账户发布。 + */ + public static final String SCOPE_DEPARTMENTS = + "DEPARTMENTS"; + + /** + * 数据库只能还原为定向发布时使用的范围类型。 + */ + public static final String SCOPE_TARGETED = "TARGETED"; + + /** + * 未登录业务错误码。 + */ + public static final int UNAUTHORIZED = 401; + + /** + * 当前会话未登录时的统一提示。 + */ + public static final String AUTHENTICATION_REQUIRED_MESSAGE = + "请先登录后再操作"; + + /** + * 私有化构造方法,防止常量工具类被实例化。 + */ + private NoticeAnnouncementConstants() { + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NoticeAnnouncementUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NoticeAnnouncementUtil.java new file mode 100644 index 000000000..a12f5de98 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NoticeAnnouncementUtil.java @@ -0,0 +1,947 @@ +package com.roomroot.jwgl.utils; + +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementCreateDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementIdDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPriorityDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementPublishDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementQueryDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientInsertDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementRecipientQueryDTO; +import com.roomroot.jwgl.dto.noticeannouncement.NoticeAnnouncementUpdateDTO; +import com.roomroot.jwgl.dto.noticeannouncement.UserNoticeAnnouncementQueryDTO; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementDetailVO; +import com.roomroot.jwgl.vo.noticeannouncement.NoticeAnnouncementStatisticsVO; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.time.DateTimeException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST; +import static com.roomroot.jwgl.utils.BasicManagementConstants.CONFLICT; +import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.AUTHENTICATION_REQUIRED_MESSAGE; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.CATEGORY_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.CONTENT_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.MAX_DEPARTMENT_SCOPE_SIZE; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.MAX_PAGE_SIZE; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.NOTICE_ID_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.PRIORITY_MAX_VALUE; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.RECIPIENT_KEYWORD_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.REMARK_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.SCOPE_ALL; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.SCOPE_ALL_TEACHERS; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.SCOPE_DEPARTMENTS; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.SCOPE_TARGETED; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_DOWN; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_DRAFT; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.STATUS_PUBLISHED; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.TITLE_MAX_LENGTH; +import static com.roomroot.jwgl.utils.NoticeAnnouncementConstants.UNAUTHORIZED; + +/** + * 通知公告管理无状态辅助工具类。 + * + *

集中处理 DTO 复制、文本规范化、状态流转校验、发布范围校验、 + * 会话识别、接收人快照组装和统计结果补全,使业务实现类只保留流程编排。

+ */ +public final class NoticeAnnouncementUtil { + + /** + * 复制、规范化并校验通知公告后台分页查询参数。 + * + * @param dto 页面提交的查询参数 + * @return 可直接用于业务查询的独立参数对象 + */ + public static NoticeAnnouncementQueryDTO normalizeQuery( + NoticeAnnouncementQueryDTO dto) { + // 第一步:兼容业务层直接传入 null,并使用 DTO 中定义的默认分页参数。 + NoticeAnnouncementQueryDTO source = + dto == null ? new NoticeAnnouncementQueryDTO() : dto; + + // 第二步:校验分页、优先级和发布日期区间。 + validatePage(source.getPageNum(), source.getPageSize()); + validatePriorityRange( + source.getMinPriority(), + source.getMaxPriority()); + validatePublishDateRange( + source.getPublishStartDate(), + source.getPublishEndDate()); + + // 第三步:创建独立查询对象,避免修改调用方持有的原始 DTO。 + NoticeAnnouncementQueryDTO query = + new NoticeAnnouncementQueryDTO(); + + // 第四步:复制已经校验通过的分页、数值和日期条件。 + query.setPageNum(source.getPageNum()); + query.setPageSize(source.getPageSize()); + query.setMinPriority(source.getMinPriority()); + query.setMaxPriority(source.getMaxPriority()); + query.setPublishStartDate(source.getPublishStartDate()); + query.setPublishEndDate(source.getPublishEndDate()); + + // 第五步:规范化文本查询条件。 + query.setTitle(optionalText( + source.getTitle(), + TITLE_MAX_LENGTH, + "公告标题")); + query.setCategory(optionalText( + source.getCategory(), + CATEGORY_MAX_LENGTH, + "公告类别")); + query.setStatus(normalizeOptionalStatus( + source.getStatus())); + + // 第六步:返回完成校验和规范化的独立查询对象。 + return query; + } + + /** + * 复制、规范化并校验新增公告参数。 + * + * @param dto 页面提交的新增参数 + * @return 可直接用于数据库写入的独立参数对象 + */ + public static NoticeAnnouncementCreateDTO normalizeCreate( + NoticeAnnouncementCreateDTO dto) { + // 第一步:拒绝绕过 Controller 后传入的空新增参数。 + if (dto == null) { + throw new ServiceException("公告新增参数不能为空", + BAD_REQUEST); + } + + // 第二步:校验优先级范围。 + validatePriority(dto.getPriority()); + + // 第三步:创建独立参数对象并规范化全部可编辑字段。 + NoticeAnnouncementCreateDTO normalized = + new NoticeAnnouncementCreateDTO(); + copyEditableFields(dto, normalized); + + // 第四步:返回可安全用于写入的公告内容。 + return normalized; + } + + /** + * 复制、规范化并校验修改公告参数。 + * + * @param dto 页面提交的修改参数 + * @return 可直接用于数据库更新的独立参数对象 + */ + public static NoticeAnnouncementUpdateDTO normalizeUpdate( + NoticeAnnouncementUpdateDTO dto) { + // 第一步:拒绝绕过 Controller 后传入的空修改参数。 + if (dto == null) { + throw new ServiceException("公告修改参数不能为空", + BAD_REQUEST); + } + + // 第二步:复用新增参数规则校验和规范化可编辑内容。 + NoticeAnnouncementCreateDTO content = + normalizeCreate(dto); + + // 第三步:创建独立修改对象并规范化公告编号。 + NoticeAnnouncementUpdateDTO normalized = + new NoticeAnnouncementUpdateDTO(); + normalized.setId(normalizeNoticeId(dto.getId())); + + // 第四步:复制已经规范化的公告内容。 + normalized.setTitle(content.getTitle()); + normalized.setContent(content.getContent()); + normalized.setCategory(content.getCategory()); + normalized.setPriority(content.getPriority()); + normalized.setRemark(content.getRemark()); + + // 第五步:返回可安全用于更新的修改参数。 + return normalized; + } + + /** + * 规范化公告编号 DTO。 + * + * @param dto 页面提交的公告编号参数 + * @return 去除首尾空白后的公告编号 + */ + public static String normalizeNoticeId( + NoticeAnnouncementIdDTO dto) { + // 第一步:空 DTO 按照缺少公告编号处理。 + String id = dto == null ? null : dto.getId(); + + // 第二步:复用公告编号文本规则完成规范化。 + return normalizeNoticeId(id); + } + + /** + * 复制、规范化并校验公告发布参数。 + * + * @param dto 页面提交的发布参数 + * @return 可直接用于业务发布流程的独立参数对象 + */ + public static NoticeAnnouncementPublishDTO normalizePublish( + NoticeAnnouncementPublishDTO dto) { + // 第一步:拒绝绕过 Controller 后传入的空发布参数。 + if (dto == null) { + throw new ServiceException("公告发布参数不能为空", + BAD_REQUEST); + } + + // 第二步:规范化公告编号和发布范围类型。 + String id = normalizeNoticeId(dto.getId()); + String scopeType = requiredText( + dto.getScopeType(), + NoticeAnnouncementConstants.SCOPE_TYPE_MAX_LENGTH, + "发布范围").toUpperCase(Locale.ROOT); + + // 第三步:校验发布范围类型是否属于系统支持范围。 + if (!SCOPE_ALL.equals(scopeType) + && !SCOPE_ALL_TEACHERS.equals(scopeType) + && !SCOPE_DEPARTMENTS.equals(scopeType)) { + throw new ServiceException("发布范围必须为ALL、ALL_TEACHERS或DEPARTMENTS", + BAD_REQUEST); + } + + // 第四步:规范化并去重指定部别编号。 + List departmentIds = + normalizeDepartmentIds(dto.getDepartmentIds()); + + /* + * 第五步:指定部别发布必须选择至少一个部别; + * 全校和全体教员发布不能携带无效的部别范围。 + */ + if (SCOPE_DEPARTMENTS.equals(scopeType) + && departmentIds.isEmpty()) { + throw new ServiceException("指定部别发布至少选择一个部别", + BAD_REQUEST); + } + if (!SCOPE_DEPARTMENTS.equals(scopeType) + && !departmentIds.isEmpty()) { + throw new ServiceException("当前发布范围不能同时提交部别编号", + BAD_REQUEST); + } + + // 第六步:创建并返回独立的发布参数对象。 + NoticeAnnouncementPublishDTO normalized = + new NoticeAnnouncementPublishDTO(); + normalized.setId(id); + normalized.setScopeType(scopeType); + normalized.setDepartmentIds(departmentIds); + return normalized; + } + + /** + * 复制、规范化并校验公告优先级调整参数。 + * + * @param dto 页面提交的优先级参数 + * @return 可直接用于更新的独立参数对象 + */ + public static NoticeAnnouncementPriorityDTO normalizePriority( + NoticeAnnouncementPriorityDTO dto) { + // 第一步:拒绝绕过 Controller 后传入的空优先级参数。 + if (dto == null) { + throw new ServiceException("公告优先级参数不能为空", + BAD_REQUEST); + } + + // 第二步:校验优先级是否处于允许范围。 + validatePriority(dto.getPriority()); + + // 第三步:创建并返回独立的优先级参数对象。 + NoticeAnnouncementPriorityDTO normalized = + new NoticeAnnouncementPriorityDTO(); + normalized.setId(normalizeNoticeId(dto.getId())); + normalized.setPriority(dto.getPriority()); + return normalized; + } + + /** + * 复制、规范化并校验接收人分页查询参数。 + * + * @param dto 页面提交的接收人查询参数 + * @return 可直接用于数据库查询的独立参数对象 + */ + public static NoticeAnnouncementRecipientQueryDTO + normalizeRecipientQuery( + NoticeAnnouncementRecipientQueryDTO dto) { + // 第一步:拒绝缺少公告编号的空查询参数。 + if (dto == null) { + throw new ServiceException("接收人查询参数不能为空", + BAD_REQUEST); + } + + // 第二步:校验分页参数。 + validatePage(dto.getPageNum(), dto.getPageSize()); + + // 第三步:创建独立查询对象并复制规范化后的条件。 + NoticeAnnouncementRecipientQueryDTO normalized = + new NoticeAnnouncementRecipientQueryDTO(); + normalized.setId(normalizeNoticeId(dto.getId())); + normalized.setPageNum(dto.getPageNum()); + normalized.setPageSize(dto.getPageSize()); + normalized.setKeyword(optionalText( + dto.getKeyword(), + RECIPIENT_KEYWORD_MAX_LENGTH, + "接收人关键字")); + normalized.setRead(dto.getRead()); + + // 第四步:返回完成规范化的接收人查询参数。 + return normalized; + } + + /** + * 复制、规范化并校验当前用户公告分页参数。 + * + * @param dto 页面提交的用户公告查询参数 + * @return 可直接用于数据库查询的独立参数对象 + */ + public static UserNoticeAnnouncementQueryDTO + normalizeUserQuery( + UserNoticeAnnouncementQueryDTO dto) { + // 第一步:兼容业务层传入 null,并使用默认分页参数。 + UserNoticeAnnouncementQueryDTO source = + dto == null + ? new UserNoticeAnnouncementQueryDTO() + : dto; + + // 第二步:校验分页参数。 + validatePage(source.getPageNum(), source.getPageSize()); + + // 第三步:创建独立查询对象并规范化全部筛选条件。 + UserNoticeAnnouncementQueryDTO normalized = + new UserNoticeAnnouncementQueryDTO(); + normalized.setPageNum(source.getPageNum()); + normalized.setPageSize(source.getPageSize()); + normalized.setTitle(optionalText( + source.getTitle(), + TITLE_MAX_LENGTH, + "公告标题")); + normalized.setCategory(optionalText( + source.getCategory(), + CATEGORY_MAX_LENGTH, + "公告类别")); + normalized.setUnreadOnly( + Boolean.TRUE.equals(source.getUnreadOnly())); + + // 第四步:返回完成规范化的用户公告查询参数。 + return normalized; + } + + /** + * 从当前请求取得已存在的服务端会话编号。 + * + * @return 当前 Servlet Session 编号 + */ + public static String requireCurrentSessionId() { + // 第一步:读取当前线程绑定的 HTTP 请求上下文。 + ServletRequestAttributes attributes = + (ServletRequestAttributes) RequestContextHolder + .getRequestAttributes(); + if (attributes == null) { + throw new ServiceException(AUTHENTICATION_REQUIRED_MESSAGE, + UNAUTHORIZED); + } + + // 第二步:取得已有 Servlet Session,不为匿名请求创建新会话。 + HttpServletRequest request = attributes.getRequest(); + HttpSession session = request.getSession(false); + if (session == null) { + throw new ServiceException(AUTHENTICATION_REQUIRED_MESSAGE, + UNAUTHORIZED); + } + + // 第三步:返回只由服务端生成和维护的会话编号。 + return session.getId(); + } + + /** + * 校验并规范化数据库根据当前会话查询到的账户编号。 + * + * @param accountId 当前会话对应的账户编号 + * @return 去除首尾空白后的真实账户编号 + */ + public static String requireCurrentAccountId( + String accountId) { + // 第一步:会话不存在对应账户时按照未登录处理。 + if (accountId == null + || accountId.trim().isEmpty()) { + throw new ServiceException(AUTHENTICATION_REQUIRED_MESSAGE, + UNAUTHORIZED); + } + + // 第二步:返回规范化后的账户编号供用户公告查询复用。 + return accountId.trim(); + } + + /** + * 去除空账户编号并按照原始顺序去重。 + * + * @param accountIds 数据库解析出的接收账户编号 + * @return 可用于生成接收人快照的唯一账户编号集合 + */ + public static List normalizeRecipientAccountIds( + List accountIds) { + // 第一步:空查询结果统一转换为空集合。 + if (accountIds == null || accountIds.isEmpty()) { + return Collections.emptyList(); + } + + // 第二步:使用有序集合去除重复账户,并保留数据库返回顺序。 + Set uniqueAccountIds = new LinkedHashSet<>(); + for (String accountId : accountIds) { + // 第三步:忽略数据库异常返回的空账户编号。 + if (accountId != null + && !accountId.trim().isEmpty()) { + uniqueAccountIds.add(accountId.trim()); + } + } + + // 第四步:返回独立列表,避免调用方修改内部去重集合。 + return new ArrayList<>(uniqueAccountIds); + } + + /** + * 为接收账户生成通告用户信息批量写入参数。 + * + * @param accountIds 已完成去重的接收账户编号 + * @return 包含服务端记录编号的接收人快照 + */ + public static List + createRecipientRecords( + List accountIds) { + // 第一步:没有接收账户时返回稳定的空集合。 + if (accountIds == null || accountIds.isEmpty()) { + return Collections.emptyList(); + } + + // 第二步:为每个接收账户生成独立的通告用户信息记录编号。 + List records = + new ArrayList<>(accountIds.size()); + for (String accountId : accountIds) { + NoticeAnnouncementRecipientInsertDTO record = + new NoticeAnnouncementRecipientInsertDTO(); + record.setId(UuidUtil.getOriginalUUID()); + record.setAccountId(accountId); + records.add(record); + } + + // 第三步:返回可供 Mapper 分批写入的接收人快照。 + return records; + } + + /** + * 补全公告详情中的发布范围和统计默认值。 + * + * @param detail 数据库查询到的公告详情 + * @return 可直接返回页面的公告详情 + */ + public static NoticeAnnouncementDetailVO completeDetail( + NoticeAnnouncementDetailVO detail) { + // 第一步:空详情直接返回,由业务层决定资源不存在提示。 + if (detail == null) { + return null; + } + + // 第二步:根据现有“全体”字段还原全校或定向发布类型。 + if (Boolean.TRUE.equals(detail.getAll())) { + detail.setScopeType(SCOPE_ALL); + detail.setScopeName("全校"); + } else { + detail.setScopeType(SCOPE_TARGETED); + detail.setScopeName("定向发布"); + } + + // 第三步:将数据库空统计值统一补为0。 + if (detail.getRecipientCount() == null) { + detail.setRecipientCount(Long.valueOf(0L)); + } + if (detail.getReaderCount() == null) { + detail.setReaderCount(Long.valueOf(0L)); + } + if (detail.getReadCount() == null) { + detail.setReadCount(Integer.valueOf(0)); + } + + // 第四步:返回完成展示字段补全的公告详情。 + return detail; + } + + /** + * 补全公告阅读统计中的未读人数和阅读率。 + * + * @param statistics 数据库汇总结果 + * @param noticeId 公告编号 + * @return 可直接返回页面的统计结果 + */ + public static NoticeAnnouncementStatisticsVO + completeStatistics( + NoticeAnnouncementStatisticsVO statistics, + String noticeId) { + // 第一步:兼容没有接收人记录时数据库返回空对象的情况。 + NoticeAnnouncementStatisticsVO result = + statistics == null + ? new NoticeAnnouncementStatisticsVO() + : statistics; + + // 第二步:补充公告编号和空统计值。 + result.setId(noticeId); + long recipientCount = number(result.getRecipientCount()); + long receivedCount = number(result.getReceivedCount()); + long readerCount = number(result.getReaderCount()); + result.setRecipientCount(Long.valueOf(recipientCount)); + result.setReceivedCount(Long.valueOf(receivedCount)); + result.setReaderCount(Long.valueOf(readerCount)); + + // 第三步:根据接收人数和已读人数计算未读人数。 + long unreadCount = + Math.max(0L, recipientCount - readerCount); + result.setUnreadCount(Long.valueOf(unreadCount)); + + // 第四步:没有接收人时阅读率为0,避免除零异常。 + if (recipientCount == 0L) { + result.setReadRate( + BigDecimal.ZERO.setScale( + 2, RoundingMode.HALF_UP)); + return result; + } + + // 第五步:阅读率按照百分比计算并保留两位小数。 + BigDecimal readRate = + BigDecimal.valueOf(readerCount) + .multiply(BigDecimal.valueOf(100L)) + .divide( + BigDecimal.valueOf(recipientCount), + 2, + RoundingMode.HALF_UP); + result.setReadRate(readRate); + return result; + } + + /** + * 返回发布范围中文名称。 + * + * @param scopeType 已完成规范化的发布范围类型 + * @return 发布范围中文名称 + */ + public static String resolveScopeName( + String scopeType) { + // 第一步:识别全校发布范围。 + if (SCOPE_ALL.equals(scopeType)) { + return "全校"; + } + + // 第二步:识别全体教员发布范围。 + if (SCOPE_ALL_TEACHERS.equals(scopeType)) { + return "全体教员"; + } + + // 第三步:其余已通过校验的范围为指定部别。 + return "指定部别"; + } + + /** + * 校验公告状态是否允许修改。 + * + * @param status 当前公告状态 + */ + public static void requireEditableStatus( + String status) { + // 第一步:公告不存在时返回明确的资源不存在提示。 + requireExistingStatus(status); + + // 第二步:只有拟制或已下架公告允许修改。 + if (!STATUS_DRAFT.equals(status) + && !STATUS_DOWN.equals(status)) { + throw new ServiceException("已发布公告不能修改,请先下架", + CONFLICT); + } + } + + /** + * 校验公告状态是否允许发布。 + * + * @param status 当前公告状态 + */ + public static void requirePublishableStatus( + String status) { + // 第一步:公告不存在时返回明确的资源不存在提示。 + requireExistingStatus(status); + + // 第二步:只有拟制或已下架公告允许发布或重新发布。 + if (!STATUS_DRAFT.equals(status) + && !STATUS_DOWN.equals(status)) { + throw new ServiceException("公告已经发布,请勿重复发布", + CONFLICT); + } + } + + /** + * 校验公告状态是否允许下架。 + * + * @param status 当前公告状态 + */ + public static void requireDownableStatus( + String status) { + // 第一步:公告不存在时返回明确的资源不存在提示。 + requireExistingStatus(status); + + // 第二步:只有已发布公告允许下架。 + if (!STATUS_PUBLISHED.equals(status)) { + throw new ServiceException("只有已发布公告可以下架", + CONFLICT); + } + } + + /** + * 校验公告状态是否允许删除。 + * + * @param status 当前公告状态 + */ + public static void requireDeletableStatus( + String status) { + // 第一步:公告不存在时返回明确的资源不存在提示。 + requireExistingStatus(status); + + // 第二步:已发布公告必须先下架,不能直接删除。 + if (STATUS_PUBLISHED.equals(status)) { + throw new ServiceException("已发布公告不能删除,请先下架", + CONFLICT); + } + + // 第三步:只允许删除拟制或已下架公告。 + if (!STATUS_DRAFT.equals(status) + && !STATUS_DOWN.equals(status)) { + throw new ServiceException("当前公告状态不允许删除", + CONFLICT); + } + } + + /** + * 校验公告是否存在。 + * + * @param status 数据库查询到的公告状态 + */ + public static void requireExistingStatus( + String status) { + // 状态为空表示公告编号不存在。 + if (status == null || status.trim().isEmpty()) { + throw new ServiceException("通知公告不存在", + NOT_FOUND); + } + } + + /** + * 将查询开始日期转换为当天零点。 + * + * @param date 查询开始日期 + * @return 当天零点;日期为空时返回 null + */ + public static LocalDateTime startOfDay(LocalDate date) { + // 未填写开始日期时不生成数据库开始时间条件。 + return date == null ? null : date.atStartOfDay(); + } + + /** + * 将查询结束日期转换为下一天零点,供数据库使用小于条件包含完整结束日期。 + * + * @param date 查询结束日期 + * @return 下一天零点;日期为空时返回 null + */ + public static LocalDateTime startOfNextDay(LocalDate date) { + // 第一步:未填写结束日期时不生成数据库结束时间条件。 + if (date == null) { + return null; + } + + try { + // 第二步:使用下一天零点作为排他上界,完整包含结束日期内的全部时刻。 + return date.plusDays(1L).atStartOfDay(); + } catch (DateTimeException exception) { + // 第三步:无法生成下一天时返回明确的请求参数错误。 + throw new ServiceException("发布结束日期超出系统支持范围", + BAD_REQUEST); + } + } + + /** + * 复制并规范化公告可编辑字段。 + * + * @param source 原始公告内容 + * @param target 独立公告内容对象 + */ + private static void copyEditableFields( + NoticeAnnouncementCreateDTO source, + NoticeAnnouncementCreateDTO target) { + // 第一步:公告标题和正文为必填文本。 + target.setTitle(requiredText( + source.getTitle(), + TITLE_MAX_LENGTH, + "公告标题")); + target.setContent(requiredText( + source.getContent(), + CONTENT_MAX_LENGTH, + "公告内容")); + + // 第二步:公告类别为必填文本,优先级使用已经校验通过的数值。 + target.setCategory(requiredText( + source.getCategory(), + CATEGORY_MAX_LENGTH, + "公告类别")); + target.setPriority(source.getPriority()); + + // 第三步:公告备注为空白时统一转换为 null。 + target.setRemark(optionalText( + source.getRemark(), + REMARK_MAX_LENGTH, + "公告备注")); + } + + /** + * 规范化公告编号文本。 + * + * @param id 原始公告编号 + * @return 规范化后的公告编号 + */ + private static String normalizeNoticeId( + String id) { + // 公告编号属于必填文本,统一执行非空和长度校验。 + return requiredText( + id, + NOTICE_ID_MAX_LENGTH, + "公告编号"); + } + + /** + * 规范化指定部别编号集合。 + * + * @param departmentIds 原始部别编号集合 + * @return 去除空值和重复值后的部别编号集合 + */ + private static List normalizeDepartmentIds( + List departmentIds) { + // 第一步:未提交部别编号时返回稳定的空集合。 + if (departmentIds == null + || departmentIds.isEmpty()) { + return Collections.emptyList(); + } + + // 第二步:限制单次发布范围大小,避免生成异常大的查询条件。 + if (departmentIds.size() + > MAX_DEPARTMENT_SCOPE_SIZE) { + throw new ServiceException("单次最多选择200个部别", + BAD_REQUEST); + } + + // 第三步:规范化每个部别编号并按照提交顺序去重。 + Set uniqueIds = new LinkedHashSet<>(); + for (String departmentId : departmentIds) { + uniqueIds.add(requiredText( + departmentId, + NOTICE_ID_MAX_LENGTH, + "部别编号")); + } + + // 第四步:返回独立的部别编号列表。 + return new ArrayList<>(uniqueIds); + } + + /** + * 规范化可选公告状态并校验状态白名单。 + * + * @param status 原始状态 + * @return 规范化后的状态;未填写时返回 null + */ + private static String normalizeOptionalStatus( + String status) { + // 第一步:规范化可选状态文本。 + String normalized = optionalText( + status, + STATUS_MAX_LENGTH, + "公告状态"); + if (normalized == null) { + return null; + } + + // 第二步:只允许查询通知公告业务定义的三种状态。 + if (!STATUS_DRAFT.equals(normalized) + && !STATUS_PUBLISHED.equals(normalized) + && !STATUS_DOWN.equals(normalized)) { + throw new ServiceException("不支持的公告状态", + BAD_REQUEST); + } + + // 第三步:返回通过白名单校验的状态。 + return normalized; + } + + /** + * 校验分页参数。 + * + * @param pageNum 当前页码 + * @param pageSize 每页记录数 + */ + private static void validatePage( + Integer pageNum, Integer pageSize) { + // 第一步:页码必须从1开始。 + if (pageNum == null || pageNum.intValue() < 1) { + throw new ServiceException("页码最小为1", + BAD_REQUEST); + } + + // 第二步:每页记录数必须处于系统允许范围内。 + if (pageSize == null + || pageSize.intValue() < 1 + || pageSize.intValue() > MAX_PAGE_SIZE) { + throw new ServiceException("每页大小必须在1到100之间", + BAD_REQUEST); + } + } + + /** + * 校验优先级查询区间。 + * + * @param minPriority 最低优先级 + * @param maxPriority 最高优先级 + */ + private static void validatePriorityRange( + Integer minPriority, Integer maxPriority) { + // 第一步:分别校验两个可选优先级边界。 + if (minPriority != null) { + validatePriority(minPriority); + } + if (maxPriority != null) { + validatePriority(maxPriority); + } + + // 第二步:同时填写上下限时,最低优先级不能大于最高优先级。 + if (minPriority != null + && maxPriority != null + && minPriority.intValue() + > maxPriority.intValue()) { + throw new ServiceException("最低优先级不能大于最高优先级", + BAD_REQUEST); + } + } + + /** + * 校验单个公告优先级。 + * + * @param priority 公告优先级 + */ + private static void validatePriority( + Integer priority) { + // 优先级必须存在且处于系统允许范围。 + if (priority == null + || priority.intValue() < 0 + || priority.intValue() + > PRIORITY_MAX_VALUE) { + throw new ServiceException("公告优先级必须在0到9999之间", + BAD_REQUEST); + } + } + + /** + * 校验发布日期查询区间。 + * + * @param startDate 发布开始日期 + * @param endDate 发布结束日期 + */ + private static void validatePublishDateRange( + LocalDate startDate, LocalDate endDate) { + // 同时填写开始日期和结束日期时,开始日期不能晚于结束日期。 + if (startDate != null + && endDate != null + && startDate.isAfter(endDate)) { + throw new ServiceException("发布开始日期不能晚于发布结束日期", + BAD_REQUEST); + } + } + + /** + * 规范化必填文本。 + * + * @param value 原始文本 + * @param maxLength 最大允许长度 + * @param fieldName 字段名称 + * @return 去除首尾空白后的非空文本 + */ + private static String requiredText( + String value, int maxLength, String fieldName) { + // 第一步:复用可选文本规则完成空白和长度处理。 + String normalized = + optionalText(value, maxLength, fieldName); + + // 第二步:空文本不满足必填字段要求。 + if (normalized == null) { + throw new ServiceException(fieldName + "不能为空", + BAD_REQUEST); + } + + // 第三步:返回通过非空校验的文本。 + return normalized; + } + + /** + * 规范化可选文本。 + * + * @param value 原始文本 + * @param maxLength 最大允许长度 + * @param fieldName 字段名称 + * @return 去除首尾空白后的文本;空文本返回 null + */ + private static String optionalText( + String value, int maxLength, String fieldName) { + // 第一步:未填写文本时直接返回 null。 + if (value == null) { + return null; + } + + // 第二步:去除文本首尾空白。 + String normalized = value.trim(); + + // 第三步:只包含空白的文本按照未填写处理。 + if (normalized.isEmpty()) { + return null; + } + + // 第四步:校验规范化后的文本长度。 + if (normalized.length() > maxLength) { + throw new ServiceException(fieldName + "不能超过" + + maxLength + "个字符", + BAD_REQUEST); + } + + // 第五步:返回规范化文本。 + return normalized; + } + + /** + * 将可空 Long 转换为非负基础数值。 + * + * @param value 数据库统计值 + * @return 非负 long 数值 + */ + private static long number(Long value) { + // 空值或异常负值统一按照0处理。 + return value == null + ? 0L + : Math.max(0L, value.longValue()); + } + + /** + * 私有化构造方法,防止无状态工具类被实例化。 + */ + private NoticeAnnouncementUtil() { + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NotificationLogConstants.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NotificationLogConstants.java new file mode 100644 index 000000000..b63fc1c71 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NotificationLogConstants.java @@ -0,0 +1,74 @@ +package com.roomroot.jwgl.utils; + +/** + * 通知日志管理常量工具类。 + * + *

集中维护通知日志分页、文本和发送状态使用的固定值, + * 避免业务实现类重复声明业务常量。

+ */ +public final class NotificationLogConstants { + + /** + * 通知日志分页允许的最大单页记录数。 + */ + public static final int MAX_PAGE_SIZE = 100; + + /** + * 通知标题的最大长度。 + */ + public static final int TITLE_MAX_LENGTH = 100; + + /** + * 通知类别的最大长度。 + */ + public static final int CATEGORY_MAX_LENGTH = 50; + + /** + * 通知发送状态的最大长度。 + */ + public static final int SEND_STATUS_MAX_LENGTH = 20; + + /** + * 通知日志业务编号的最大长度。 + */ + public static final int NOTIFICATION_ID_MAX_LENGTH = 50; + + /** + * 接收人查询关键字的最大长度。 + */ + public static final int RECIPIENT_KEYWORD_MAX_LENGTH = 100; + + /** + * 通知日志导出时每批查询的记录数。 + */ + public static final int EXPORT_BATCH_SIZE = MAX_PAGE_SIZE; + + /** + * 全部接收对象均接收成功时使用的状态。 + */ + public static final String SEND_STATUS_SUCCESS = + "发送成功"; + + /** + * 部分接收对象接收成功时使用的状态。 + */ + public static final String SEND_STATUS_PARTIAL = + "部分成功"; + + /** + * 没有接收对象接收成功时使用的状态。 + */ + public static final String SEND_STATUS_FAILED = + "发送失败"; + + /** + * 阅读率保留的小数位数。 + */ + public static final int READ_RATE_SCALE = 2; + + /** + * 私有化构造方法,防止常量工具类被实例化。 + */ + private NotificationLogConstants() { + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NotificationLogUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NotificationLogUtil.java new file mode 100644 index 000000000..06ad82a74 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/NotificationLogUtil.java @@ -0,0 +1,920 @@ +package com.roomroot.jwgl.utils; + +import com.roomroot.jwgl.dto.notificationlog.NotificationLogExportDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogIdDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogQueryDTO; +import com.roomroot.jwgl.dto.notificationlog.NotificationLogRecipientQueryDTO; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogDetailVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogStatisticsVO; +import com.roomroot.jwgl.vo.notificationlog.NotificationLogVO; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletResponse; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.net.URLEncoder; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static com.roomroot.jwgl.utils.BasicManagementConstants.BAD_REQUEST; +import static com.roomroot.jwgl.utils.BasicManagementConstants.NOT_FOUND; +import static com.roomroot.jwgl.utils.NotificationLogConstants.*; + +/** + * 通知日志管理无状态辅助工具类。 + * + *

集中处理查询 DTO 复制、文本规范化、时间范围校验和统计字段补全, + * 使通知日志业务实现类只保留查询流程编排。

+ */ +public final class NotificationLogUtil { + + /** + * 通知日志 Excel 文件中的固定列标题。 + */ + private static final String[] NOTIFICATION_LOG_EXPORT_HEADERS = { + "通知编号", + "发送时间", + "通知标题", + "通知类别", + "公告状态", + "发送状态", + "接收对象数", + "成功接收数", + "发送失败数", + "已读人数", + "未读人数", + "阅读率", + "累计打开次数" + }; + + /** + * 通知日志 Excel 文件中各列的显示宽度。 + */ + private static final int[] NOTIFICATION_LOG_EXPORT_COLUMN_WIDTHS = { + 38, 20, 32, 18, 16, 16, 14, + 14, 14, 14, 14, 14, 16 + }; + + /** + * Excel 单个工作表除标题外允许写入的最大数据行数。 + */ + private static final int EXCEL_MAX_DATA_ROW_COUNT = 1048575; + + /** + * 通知日志导出文件名使用的时间格式。 + */ + private static final DateTimeFormatter + NOTIFICATION_LOG_EXPORT_FILE_TIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + + /** + * 通知日志导出单元格使用的时间格式。 + */ + private static final DateTimeFormatter + NOTIFICATION_LOG_EXPORT_CELL_TIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + /** + * 工具类不允许创建实例。 + */ + private NotificationLogUtil() { + } + + /** + * 复制、规范化并校验通知发送历史分页查询参数。 + * + * @param dto 页面提交的查询参数 + * @return 可直接用于数据库查询的独立参数对象 + */ + public static NotificationLogQueryDTO normalizeQuery( + NotificationLogQueryDTO dto) { + // 第一步:兼容业务层直接传入 null,并使用 DTO 默认分页参数。 + NotificationLogQueryDTO source = + dto == null ? new NotificationLogQueryDTO() : dto; + + // 第二步:校验分页参数和发送时间范围。 + validatePage(source.getPageNum(), source.getPageSize()); + validateTimeRange( + source.getSendStartTime(), + source.getSendEndTime()); + + // 第三步:创建独立查询对象,避免修改调用方持有的原始 DTO。 + NotificationLogQueryDTO query = + new NotificationLogQueryDTO(); + + // 第四步:复制已经校验通过的分页和时间条件。 + query.setPageNum(source.getPageNum()); + query.setPageSize(source.getPageSize()); + query.setSendStartTime(source.getSendStartTime()); + query.setSendEndTime(source.getSendEndTime()); + + // 第五步:规范化标题、类别和发送状态筛选条件。 + query.setTitle(optionalText( + source.getTitle(), + TITLE_MAX_LENGTH, + "通知标题")); + query.setCategory(optionalText( + source.getCategory(), + CATEGORY_MAX_LENGTH, + "通知类别")); + query.setSendStatus(normalizeSendStatus( + source.getSendStatus())); + + // 第六步:返回完成校验和规范化的独立查询对象。 + return query; + } + + /** + * 规范化并校验通知日志编号参数。 + * + * @param dto 页面提交的通知日志编号 + * @return 去除首尾空白后的有效通知编号 + */ + public static String normalizeId( + NotificationLogIdDTO dto) { + // 第一步:业务层被直接调用时,拒绝空编号参数对象。 + if (dto == null) { + throw new BusinessException( + BAD_REQUEST, "通知日志编号不能为空"); + } + + // 第二步:复用必填文本规则完成去空白和长度校验。 + return requiredText( + dto.getId(), + NOTIFICATION_ID_MAX_LENGTH, + "通知日志编号"); + } + + /** + * 复制、规范化并校验接收对象及阅读明细分页查询参数。 + * + * @param dto 页面提交的明细查询参数 + * @return 可直接用于数据库查询的独立参数对象 + */ + public static NotificationLogRecipientQueryDTO + normalizeRecipientQuery( + NotificationLogRecipientQueryDTO dto) { + // 第一步:业务层被直接调用时,拒绝空查询参数对象。 + if (dto == null) { + throw new BusinessException( + BAD_REQUEST, "接收明细查询参数不能为空"); + } + + // 第二步:校验通知编号和分页参数。 + String id = requiredText( + dto.getId(), + NOTIFICATION_ID_MAX_LENGTH, + "通知日志编号"); + validatePage(dto.getPageNum(), dto.getPageSize()); + + /* + * 第三步:已阅读必然表示已经接收, + * 因此拒绝“接收失败且已读”的无效组合条件。 + */ + if (Boolean.FALSE.equals(dto.getReceived()) + && Boolean.TRUE.equals(dto.getRead())) { + throw new BusinessException( + BAD_REQUEST, + "接收失败的对象不能筛选为已阅读"); + } + + // 第四步:创建独立查询对象,避免修改控制器接收的原始 DTO。 + NotificationLogRecipientQueryDTO query = + new NotificationLogRecipientQueryDTO(); + + // 第五步:写入已经完成校验和规范化的查询条件。 + query.setId(id); + query.setPageNum(dto.getPageNum()); + query.setPageSize(dto.getPageSize()); + query.setKeyword(optionalText( + dto.getKeyword(), + RECIPIENT_KEYWORD_MAX_LENGTH, + "接收人关键字")); + query.setReceived(dto.getReceived()); + query.setRead(dto.getRead()); + + // 第六步:返回仅供本次数据库查询使用的独立参数对象。 + return query; + } + + /** + * 将导出或统计条件转换为统一通知日志查询参数。 + * + * @param dto 页面提交的导出筛选条件 + * @return 使用固定分页参数且完成规范化的查询对象 + */ + public static NotificationLogQueryDTO normalizeExportQuery( + NotificationLogExportDTO dto) { + // 第一步:兼容业务层直接传入 null,将其视为未设置筛选条件。 + NotificationLogExportDTO source = + dto == null ? new NotificationLogExportDTO() : dto; + + // 第二步:创建列表查询对象,只复制导出允许使用的业务筛选条件。 + NotificationLogQueryDTO query = + new NotificationLogQueryDTO(); + query.setTitle(source.getTitle()); + query.setCategory(source.getCategory()); + query.setSendStartTime(source.getSendStartTime()); + query.setSendEndTime(source.getSendEndTime()); + query.setSendStatus(source.getSendStatus()); + + /* + * 第三步:导出和统计不接收客户端分页参数, + * 此处使用列表接口允许的固定最大批次。 + */ + query.setPageNum(Integer.valueOf(1)); + query.setPageSize(Integer.valueOf( + NotificationLogConstants.EXPORT_BATCH_SIZE)); + + // 第四步:复用列表参数规则完成文本、时间和发送状态校验。 + return normalizeQuery(query); + } + + /** + * 补全当前页全部通知日志的发送和阅读统计字段。 + * + * @param records Mapper 返回的当前页日志 + * @return 字段完整且不包含 null 元素的通知日志列表 + */ + public static List completeRecords( + List records) { + // 第一步:数据库未返回记录时统一使用不可变空集合。 + if (records == null || records.isEmpty()) { + return Collections.emptyList(); + } + + // 第二步:逐条补全数量、发送状态和阅读率。 + List completed = + new ArrayList<>(records.size()); + for (NotificationLogVO record : records) { + if (record == null) { + continue; + } + completeRecord(record); + completed.add(record); + } + + // 第三步:返回完成业务统计补全的当前页记录。 + return completed; + } + + /** + * 校验通知发送日志存在并补全详情统计字段。 + * + * @param detail Mapper 返回的通知日志详情 + * @return 完成发送和阅读统计补全的详情 + */ + public static NotificationLogDetailVO completeDetail( + NotificationLogDetailVO detail) { + // 第一步:没有查询到实际发送记录时返回明确的不存在错误。 + if (detail == null) { + throw new BusinessException( + NOT_FOUND, "通知发送日志不存在"); + } + + // 第二步:复用列表单条记录规则补全发送失败数、未读数和阅读率。 + completeRecord(detail); + + // 第三步:返回字段完整的通知发送日志详情。 + return detail; + } + + /** + * 补全通知日志汇总统计中的派生数量和阅读率。 + * + * @param statistics Mapper 返回的基础汇总结果 + * @return 不包含空数量且派生字段完整的汇总统计 + */ + public static NotificationLogStatisticsVO completeStatistics( + NotificationLogStatisticsVO statistics) { + // 第一步:数据库没有返回统计对象时创建全零结果。 + NotificationLogStatisticsVO result = + statistics == null + ? new NotificationLogStatisticsVO() + : statistics; + + // 第二步:把通知数量和三类发送结果统一转换为非负数。 + result.setNotificationCount(Long.valueOf(nonNegative( + result.getNotificationCount()))); + result.setSuccessNotificationCount(Long.valueOf(nonNegative( + result.getSuccessNotificationCount()))); + result.setPartialNotificationCount(Long.valueOf(nonNegative( + result.getPartialNotificationCount()))); + result.setFailedNotificationCount(Long.valueOf(nonNegative( + result.getFailedNotificationCount()))); + + // 第三步:规范化接收对象、成功接收和已阅读总数。 + long recipientCount = + nonNegative(result.getRecipientCount()); + long receivedCount = + Math.min( + recipientCount, + nonNegative(result.getReceivedCount())); + long readerCount = + Math.min( + recipientCount, + nonNegative(result.getReaderCount())); + result.setRecipientCount(Long.valueOf(recipientCount)); + result.setReceivedCount(Long.valueOf(receivedCount)); + result.setReaderCount(Long.valueOf(readerCount)); + + // 第四步:根据总人数计算发送失败人数和未阅读人数。 + result.setFailedCount(Long.valueOf( + recipientCount - receivedCount)); + result.setUnreadCount(Long.valueOf( + recipientCount - readerCount)); + + // 第五步:根据全部接收对象和阅读人数计算汇总阅读率。 + result.setReadRate(calculateReadRate( + recipientCount, readerCount)); + + // 第六步:返回完成全部统计补全的结果。 + return result; + } + + /** + * 将通知日志列表生成 Excel 文件并写入 HTTP 响应。 + * + * @param response 当前 HTTP 响应 + * @param records 待导出的通知日志 + */ + public static void writeNotificationLogExcel( + HttpServletResponse response, + List records) { + // 第一步:没有响应对象时无法向客户端输出导出文件。 + if (response == null) { + throw new BusinessException( + "通知日志导出响应不能为空"); + } + + // 第二步:空数据集合统一转换为空列表,仍允许下载只有标题的文件。 + List exportRecords = + records == null + ? Collections.emptyList() + : records; + + // 第三步:提前校验 Excel 单表最大数据行数。 + if (exportRecords.size() > EXCEL_MAX_DATA_ROW_COUNT) { + throw new BusinessException( + "导出数据超过Excel单表最大行数"); + } + + /* + * 第四步:先在内存中完整生成文件。 + * 生成失败时 HTTP 响应尚未写入,可以继续返回统一异常结果。 + */ + byte[] fileContent = + createNotificationLogWorkbook(exportRecords); + + // 第五步:使用服务端当前时间生成唯一下载文件名。 + String fileName = + "通知日志_" + + LocalDateTime.now().format( + NOTIFICATION_LOG_EXPORT_FILE_TIME_FORMATTER) + + ".xlsx"; + + try { + // 第六步:按照 UTF-8 编码中文文件名并规范化空格编码。 + String encodedFileName = + URLEncoder.encode(fileName, "UTF-8") + .replace("+", "%20"); + + // 第七步:设置 xlsx 响应类型、下载文件名和文件长度。 + response.setCharacterEncoding("UTF-8"); + response.setContentType( + "application/vnd.openxmlformats-officedocument" + + ".spreadsheetml.sheet"); + response.setHeader( + "Content-Disposition", + "attachment; filename*=UTF-8''" + + encodedFileName); + response.setContentLength(fileContent.length); + + // 第八步:将完整文件内容写入响应并立即刷新。 + ServletOutputStream outputStream = + response.getOutputStream(); + outputStream.write(fileContent); + outputStream.flush(); + } catch (IOException exception) { + // 第九步:保留响应写入失败的原始异常原因。 + throw new IllegalStateException( + "通知日志导出文件写入失败", + exception); + } + } + + /** + * 在内存中创建通知日志 Excel 工作簿。 + * + * @param records 待导出的通知日志 + * @return 完整的 xlsx 文件字节 + */ + private static byte[] createNotificationLogWorkbook( + List records) { + /* + * 第一步:创建 xlsx 工作簿和内存输出流。 + * try-with-resources 保证生成成功或失败后都能释放资源。 + */ + try (Workbook workbook = new XSSFWorkbook(); + ByteArrayOutputStream outputStream = + new ByteArrayOutputStream()) { + // 第二步:创建通知日志工作表并冻结标题行。 + Sheet sheet = workbook.createSheet("通知日志"); + sheet.createFreezePane(0, 1); + + // 第三步:创建并复用标题和正文单元格样式。 + CellStyle headerStyle = + createNotificationLogHeaderStyle(workbook); + CellStyle bodyStyle = + createNotificationLogBodyStyle(workbook); + + // 第四步:按照固定列顺序写入标题行。 + Row headerRow = sheet.createRow(0); + for (int columnIndex = 0; + columnIndex + < NOTIFICATION_LOG_EXPORT_HEADERS.length; + columnIndex++) { + Cell cell = headerRow.createCell(columnIndex); + cell.setCellValue( + NOTIFICATION_LOG_EXPORT_HEADERS[columnIndex]); + cell.setCellStyle(headerStyle); + } + + // 第五步:按照业务层返回顺序逐行写入通知日志。 + int rowIndex = 1; + for (NotificationLogVO record : records) { + // 忽略异常空记录,避免单条无效对象中断全部导出。 + if (record == null) { + continue; + } + Row row = sheet.createRow(rowIndex++); + writeNotificationLogRow(row, record, bodyStyle); + } + + // 第六步:按照预设字符宽度设置全部导出列。 + for (int columnIndex = 0; + columnIndex + < NOTIFICATION_LOG_EXPORT_COLUMN_WIDTHS.length; + columnIndex++) { + sheet.setColumnWidth( + columnIndex, + NOTIFICATION_LOG_EXPORT_COLUMN_WIDTHS[columnIndex] + * 256); + } + + // 第七步:为标题行增加自动筛选,方便下载后继续筛选。 + sheet.setAutoFilter( + new CellRangeAddress( + 0, + 0, + 0, + NOTIFICATION_LOG_EXPORT_HEADERS.length - 1)); + + // 第八步:将工作簿完整写入内存并返回独立字节数组。 + workbook.write(outputStream); + return outputStream.toByteArray(); + } catch (IOException exception) { + // 第九步:工作簿生成失败时保留原始异常原因。 + throw new IllegalStateException( + "通知日志Excel文件生成失败", + exception); + } + } + + /** + * 将一条通知日志写入 Excel 正文行。 + * + * @param row 当前 Excel 行 + * @param record 当前通知日志 + * @param bodyStyle 正文单元格样式 + */ + private static void writeNotificationLogRow( + Row row, + NotificationLogVO record, + CellStyle bodyStyle) { + // 第一步:写入通知自身的编号、发送时间、标题和类别。 + setNotificationLogCell( + row, 0, record.getId(), bodyStyle); + setNotificationLogCell( + row, + 1, + formatNotificationLogTime(record.getSendTime()), + bodyStyle); + setNotificationLogCell( + row, 2, record.getTitle(), bodyStyle); + setNotificationLogCell( + row, 3, record.getCategory(), bodyStyle); + + // 第二步:写入公告当前状态和本次综合发送状态。 + setNotificationLogCell( + row, 4, record.getNoticeStatus(), bodyStyle); + setNotificationLogCell( + row, 5, record.getSendStatus(), bodyStyle); + + // 第三步:写入接收对象、成功接收和发送失败数量。 + setNotificationLogCell( + row, 6, record.getRecipientCount(), bodyStyle); + setNotificationLogCell( + row, 7, record.getReceivedCount(), bodyStyle); + setNotificationLogCell( + row, 8, record.getFailedCount(), bodyStyle); + + // 第四步:写入已读、未读、阅读率和累计打开次数。 + setNotificationLogCell( + row, 9, record.getReaderCount(), bodyStyle); + setNotificationLogCell( + row, 10, record.getUnreadCount(), bodyStyle); + setNotificationLogCell( + row, + 11, + record.getReadRate() == null + ? null + : record.getReadRate().toPlainString() + "%", + bodyStyle); + setNotificationLogCell( + row, 12, record.getReadCount(), bodyStyle); + } + + /** + * 创建通知日志 Excel 标题样式。 + * + * @param workbook 当前工作簿 + * @return 可复用的标题样式 + */ + private static CellStyle createNotificationLogHeaderStyle( + Workbook workbook) { + // 第一步:创建加粗标题字体。 + Font font = workbook.createFont(); + font.setBold(true); + + // 第二步:创建居中、浅灰背景和细边框的标题样式。 + CellStyle style = workbook.createCellStyle(); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor( + IndexedColors.GREY_25_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + setNotificationLogCellBorders(style); + + // 第三步:返回供全部标题单元格复用的样式。 + return style; + } + + /** + * 创建通知日志 Excel 正文样式。 + * + * @param workbook 当前工作簿 + * @return 可复用的正文样式 + */ + private static CellStyle createNotificationLogBodyStyle( + Workbook workbook) { + // 第一步:创建垂直居中且允许长文本换行的正文样式。 + CellStyle style = workbook.createCellStyle(); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setWrapText(true); + + // 第二步:为正文单元格设置统一细边框。 + setNotificationLogCellBorders(style); + + // 第三步:返回供全部正文单元格复用的样式。 + return style; + } + + /** + * 为通知日志 Excel 样式设置统一细边框。 + * + * @param style 待设置的单元格样式 + */ + private static void setNotificationLogCellBorders( + CellStyle style) { + // 统一设置上下左右细边框,保证导出表格边界清晰。 + style.setBorderTop(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + } + + /** + * 写入经过安全处理的 Excel 文本单元格。 + * + * @param row 当前 Excel 行 + * @param columnIndex 当前列下标 + * @param value 原始单元格值 + * @param style 正文样式 + */ + private static void setNotificationLogCell( + Row row, + int columnIndex, + Object value, + CellStyle style) { + // 第一步:创建目标单元格。 + Cell cell = row.createCell(columnIndex); + + // 第二步:空值写为空文本,其余值先转换为字符串。 + String text = value == null ? "" : String.valueOf(value); + + // 第三步:写入经过公式注入防护的安全文本。 + cell.setCellValue(toSafeExcelText(text)); + + // 第四步:应用统一正文样式。 + cell.setCellStyle(style); + } + + /** + * 将可能触发 Excel 公式解析的文本转换为普通文本。 + * + * @param value 原始文本 + * @return 可安全写入 Excel 的文本 + */ + private static String toSafeExcelText( + String value) { + // 第一步:空文本无需进行公式起始字符判断。 + if (value == null || value.isEmpty()) { + return ""; + } + + // 第二步:识别 Excel 可能作为公式解析的起始字符。 + char firstCharacter = value.charAt(0); + if (firstCharacter == '=' + || firstCharacter == '+' + || firstCharacter == '-' + || firstCharacter == '@' + || firstCharacter == '\t' + || firstCharacter == '\r' + || firstCharacter == '\n') { + // 第三步:增加单引号前缀,强制 Excel 按普通文本显示。 + return "'" + value; + } + + // 第四步:普通文本保持原值。 + return value; + } + + /** + * 将通知发送时间格式化为 Excel 展示文本。 + * + * @param value 原始发送时间 + * @return 固定格式时间;空值返回空文本 + */ + private static String formatNotificationLogTime( + LocalDateTime value) { + // 空时间统一写为空文本。 + return value == null + ? "" + : value.format( + NOTIFICATION_LOG_EXPORT_CELL_TIME_FORMATTER); + } + + /** + * 校验分页参数。 + * + * @param pageNum 当前页码 + * @param pageSize 每页记录数 + */ + private static void validatePage( + Integer pageNum, + Integer pageSize) { + // 第一步:页码必须从1开始。 + if (pageNum == null || pageNum.intValue() < 1) { + throw new BusinessException( + BAD_REQUEST, "页码最小为1"); + } + + // 第二步:每页记录数必须处于系统允许范围。 + if (pageSize == null + || pageSize.intValue() < 1 + || pageSize.intValue() > MAX_PAGE_SIZE) { + throw new BusinessException( + BAD_REQUEST, "每页大小必须在1到100之间"); + } + } + + /** + * 校验发送时间范围。 + * + * @param startTime 发送开始时间 + * @param endTime 发送结束时间 + */ + private static void validateTimeRange( + LocalDateTime startTime, + LocalDateTime endTime) { + // 开始时间和结束时间同时存在时,开始时间不能晚于结束时间。 + if (startTime != null + && endTime != null + && startTime.isAfter(endTime)) { + throw new BusinessException( + BAD_REQUEST, + "发送开始时间不能晚于发送结束时间"); + } + } + + /** + * 规范化可选文本条件。 + * + * @param value 原始文本 + * @param maxLength 最大长度 + * @param fieldName 字段名称 + * @return 去除首尾空白后的文本;空文本返回 null + */ + private static String optionalText( + String value, + int maxLength, + String fieldName) { + // 第一步:null 文本表示不启用当前筛选条件。 + if (value == null) { + return null; + } + + // 第二步:去除首尾空白,空字符串同样表示不启用筛选。 + String normalized = value.trim(); + if (normalized.isEmpty()) { + return null; + } + + // 第三步:校验规范化后的真实文本长度。 + if (normalized.length() > maxLength) { + throw new BusinessException( + BAD_REQUEST, + fieldName + "不能超过" + + maxLength + "个字符"); + } + + // 第四步:返回可直接用于查询的规范化文本。 + return normalized; + } + + /** + * 规范化必填文本并校验最大长度。 + * + * @param value 原始文本 + * @param maxLength 最大长度 + * @param fieldName 字段名称 + * @return 去除首尾空白后的有效文本 + */ + private static String requiredText( + String value, + int maxLength, + String fieldName) { + // 第一步:复用可选文本规则完成去空白和长度校验。 + String normalized = + optionalText(value, maxLength, fieldName); + + // 第二步:规范化后没有有效内容时返回必填参数错误。 + if (normalized == null) { + throw new BusinessException( + BAD_REQUEST, fieldName + "不能为空"); + } + + // 第三步:返回可用于数据库查询的有效文本。 + return normalized; + } + + /** + * 规范化并校验发送状态。 + * + * @param sendStatus 页面提交的发送状态 + * @return 规范化后的发送状态;未选择时返回 null + */ + private static String normalizeSendStatus( + String sendStatus) { + // 第一步:去除无效空白并识别未选择状态的情况。 + String normalized = optionalText( + sendStatus, + NotificationLogConstants.SEND_STATUS_MAX_LENGTH, + "发送状态"); + if (normalized == null) { + return null; + } + + // 第二步:只允许查询通知日志定义的三种发送状态。 + if (!SEND_STATUS_SUCCESS.equals(normalized) + && !SEND_STATUS_PARTIAL.equals(normalized) + && !SEND_STATUS_FAILED.equals(normalized)) { + throw new BusinessException( + BAD_REQUEST, + "发送状态必须为发送成功、部分成功或发送失败"); + } + + // 第三步:返回通过白名单校验的发送状态。 + return normalized; + } + + /** + * 补全一条通知日志的统计结果。 + * + * @param record 待补全的通知日志 + */ + private static void completeRecord( + NotificationLogVO record) { + // 第一步:把数据库可能返回的空数量统一转换为非负数。 + long recipientCount = + nonNegative(record.getRecipientCount()); + long receivedCount = + Math.min( + recipientCount, + nonNegative(record.getReceivedCount())); + long readerCount = + Math.min( + recipientCount, + nonNegative(record.getReaderCount())); + + // 第二步:写回规范化后的基础统计数量。 + record.setRecipientCount( + Long.valueOf(recipientCount)); + record.setReceivedCount( + Long.valueOf(receivedCount)); + record.setReaderCount( + Long.valueOf(readerCount)); + + // 第三步:根据接收和阅读人数计算失败人数及未读人数。 + record.setFailedCount( + Long.valueOf( + recipientCount - receivedCount)); + record.setUnreadCount( + Long.valueOf( + recipientCount - readerCount)); + + // 第四步:根据接收情况确定本次通知的综合发送状态。 + record.setSendStatus(resolveSendStatus( + recipientCount, + receivedCount)); + + // 第五步:按照接收对象总数计算阅读率,避免除零异常。 + record.setReadRate(calculateReadRate( + recipientCount, + readerCount)); + } + + /** + * 根据接收总数和成功接收数确定发送状态。 + * + * @param recipientCount 接收对象总数 + * @param receivedCount 成功接收人数 + * @return 发送成功、部分成功或发送失败 + */ + private static String resolveSendStatus( + long recipientCount, + long receivedCount) { + // 没有任何有效接收或成功接收记录时视为发送失败。 + if (recipientCount == 0L || receivedCount == 0L) { + return SEND_STATUS_FAILED; + } + + // 全部接收对象均接收成功时视为发送成功。 + if (receivedCount >= recipientCount) { + return SEND_STATUS_SUCCESS; + } + + // 其余情况表示只有部分接收对象接收成功。 + return SEND_STATUS_PARTIAL; + } + + /** + * 计算阅读率百分比。 + * + * @param recipientCount 接收对象总数 + * @param readerCount 已阅读人数 + * @return 保留两位小数的阅读率 + */ + private static BigDecimal calculateReadRate( + long recipientCount, + long readerCount) { + // 没有接收对象时阅读率固定为0。 + if (recipientCount == 0L) { + return BigDecimal.ZERO.setScale( + READ_RATE_SCALE, + RoundingMode.HALF_UP); + } + + // 使用高精度十进制计算百分比并统一保留两位小数。 + return BigDecimal.valueOf(readerCount) + .multiply(BigDecimal.valueOf(100L)) + .divide( + BigDecimal.valueOf(recipientCount), + READ_RATE_SCALE, + RoundingMode.HALF_UP); + } + + /** + * 把可能为空或小于零的数据库数量转换为非负数。 + * + * @param value 数据库返回数量 + * @return 非负数量 + */ + private static long nonNegative(Long value) { + // 空值和负值统一按照0处理。 + return value == null || value.longValue() < 0L + ? 0L + : value.longValue(); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/OperationLogUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/OperationLogUtil.java new file mode 100644 index 000000000..b1754f314 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/OperationLogUtil.java @@ -0,0 +1,1197 @@ +package com.roomroot.jwgl.utils; + +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.vo.operationlog.OperationLogModuleVO; +import com.roomroot.jwgl.vo.operationlog.OperationLogVO; +import jakarta.servlet.ServletOutputStream; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.web.method.HandlerMethod; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.*; + +import static com.roomroot.jwgl.utils.AccountManagementConstants.LOGIN_RECORD_ID_SESSION_ATTRIBUTE; +import static com.roomroot.jwgl.utils.BasicManagementConstants.*; + +/** + * 操作日志无状态辅助工具类。 + * + *

集中处理操作日志文本规范化、模块校验、请求地址模块识别, + * 以及当前登录记录编号的安全读取, + * 业务实现类只保留查询、写入和结果装配流程。

+ */ +public final class OperationLogUtil { + + /** + * 操作日志 Excel 文件中的列标题。 + */ + private static final String[] OPERATION_LOG_EXPORT_HEADERS = { + "日志编号", + "操作时间", + "业务模块", + "操作内容", + "请求地址", + "操作账户编号", + "登录账号", + "操作人姓名", + "统一账号", + "登录记录编号", + "登录时间", + "IP地址", + "内网IP", + "事务ID" + }; + + /** + * 操作日志 Excel 文件中每一列的显示宽度。 + */ + private static final int[] OPERATION_LOG_EXPORT_COLUMN_WIDTHS = { + 38, 20, 18, 32, 38, 38, 20, + 18, 20, 38, 20, 18, 18, 38 + }; + + /** + * Excel 单个工作表在标题行之外允许写入的最大数据行数。 + */ + private static final int EXCEL_MAX_DATA_ROW_COUNT = 1048575; + + /** + * 操作日志导出文件名使用的时间格式。 + */ + private static final DateTimeFormatter + OPERATION_LOG_EXPORT_FILE_TIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + + /** + * 操作日志导出单元格使用的时间格式。 + */ + private static final DateTimeFormatter + OPERATION_LOG_EXPORT_CELL_TIME_FORMATTER = + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + /** + * 当前项目全部 Controller 对应的操作日志模块名称。 + * + *

模块筛选参数只允许使用该集合中的名称,避免传入无法转换为 + * 数据库 URL 查询条件的任意文本。

+ */ + private static final List OPERATION_LOG_MODULES = + Collections.unmodifiableList( + Arrays.asList( + "部别管理", + "隶属关系设置", + "部别人员管理", + "字典设置", + "账户管理", + "操作日志管理", + "通知公告管理", + "培养方案管理", + "学科专业管理", + "教研室与教员管理", + "课程教学管理", + "学员档案管理", + "教学楼管理", + "教室管理", + "教学场地历管理", + "教室输出课程表管理", + "学期校历管理", + "教材管理", + "教材库存管理", + "其他")); + + /** + * 操作日志模块名称校验集合。 + * + *

校验集合由有序模块列表统一生成,避免模块查询选项与 + * 查询参数白名单分别维护后出现不一致。

+ */ + private static final Set SUPPORTED_MODULES = + Collections.unmodifiableSet( + new HashSet<>(OPERATION_LOG_MODULES)); + + /** + * 查询操作日志管理页面可使用的模块选项。 + * + * @return 按系统业务顺序排列的模块选项 + */ + public static List + listOperationLogModules() { + // 第一步:创建独立结果集合,防止调用方修改工具类内部模块配置。 + List modules = new ArrayList<>(); + + // 第二步:按照固定业务顺序将每个模块名称转换为页面展示 VO。 + for (String moduleName : OPERATION_LOG_MODULES) { + OperationLogModuleVO module = + new OperationLogModuleVO(); + module.setName(moduleName); + modules.add(module); + } + + // 第三步:返回本次调用生成的模块选项集合。 + return modules; + } + + /** + * 将操作日志数据生成 Excel 文件并写入 HTTP 响应。 + * + *

该方法只负责文件格式和响应输出,不承担日志筛选或业务查询; + * 导出数据必须先由操作日志业务层按照统一规则查询完成。

+ * + * @param response 当前 HTTP 响应 + * @param records 待导出的操作日志数据 + */ + public static void writeOperationLogExcel( + HttpServletResponse response, + List records) { + // 第一步:没有响应对象时无法向客户端输出导出文件。 + if (response == null) { + throw new BusinessException( + "操作日志导出响应不能为空"); + } + + // 第二步:将空数据集合统一转换为空列表,仍允许导出只有标题的文件。 + List exportRecords = + records == null + ? Collections.emptyList() + : records; + + /* + * 第三步:提前校验 Excel 单表行数边界, + * 避免生成过程中超过格式限制后才得到不完整文件。 + */ + if (exportRecords.size() > EXCEL_MAX_DATA_ROW_COUNT) { + throw new BusinessException( + "导出数据超过Excel单表最大行数"); + } + + /* + * 第四步:先在内存中完整生成工作簿内容。 + * 生成失败时尚未修改 HTTP 响应,仍可由全局异常处理器返回统一错误。 + */ + byte[] fileContent = + createOperationLogWorkbook(exportRecords); + + /* + * 第五步:使用服务端当前时间生成不会覆盖历史下载文件的名称。 + * 文件名只包含固定中文和数字时间,不接收客户端输入。 + */ + String fileName = + "操作日志_" + + LocalDateTime.now().format( + OPERATION_LOG_EXPORT_FILE_TIME_FORMATTER) + + ".xlsx"; + + try { + /* + * 第六步:按照 UTF-8 编码文件名,并将空格编码规范化为 %20, + * 保证不同浏览器能够正确显示中文下载名称。 + */ + String encodedFileName = + URLEncoder.encode(fileName, "UTF-8") + .replace("+", "%20"); + + // 第七步:设置标准 xlsx 响应类型和附件下载响应头。 + response.setCharacterEncoding("UTF-8"); + response.setContentType( + "application/vnd.openxmlformats-officedocument" + + ".spreadsheetml.sheet"); + response.setHeader( + "Content-Disposition", + "attachment; filename*=UTF-8''" + + encodedFileName); + response.setContentLength(fileContent.length); + + // 第八步:将已经完整生成的文件内容写入响应并立即刷新。 + ServletOutputStream outputStream = + response.getOutputStream(); + outputStream.write(fileContent); + outputStream.flush(); + } catch (IOException exception) { + /* + * 第九步:响应输出失败时抛出带原因的系统异常。 + * 全局异常处理器负责统一提示并标记本次导出操作失败。 + */ + throw new IllegalStateException( + "操作日志导出文件写入失败", + exception); + } + } + + /** + * 在内存中创建操作日志 Excel 工作簿。 + * + * @param records 待导出的操作日志数据 + * @return 完整的 xlsx 文件字节 + */ + private static byte[] createOperationLogWorkbook( + List records) { + /* + * 第一步:使用 xlsx 工作簿和内存输出流生成完整文件。 + * try-with-resources 保证生成成功或失败后都能释放 POI 资源。 + */ + try (Workbook workbook = new XSSFWorkbook(); + ByteArrayOutputStream outputStream = + new ByteArrayOutputStream()) { + // 第二步:创建操作日志工作表,并冻结首行方便浏览大量数据。 + Sheet sheet = workbook.createSheet("操作日志"); + sheet.createFreezePane(0, 1); + + // 第三步:创建并复用标题、正文样式,避免为每个单元格重复生成样式对象。 + CellStyle headerStyle = + createOperationLogHeaderStyle(workbook); + CellStyle bodyStyle = + createOperationLogBodyStyle(workbook); + + // 第四步:创建固定标题行。 + Row headerRow = sheet.createRow(0); + for (int columnIndex = 0; + columnIndex < OPERATION_LOG_EXPORT_HEADERS.length; + columnIndex++) { + Cell cell = headerRow.createCell(columnIndex); + cell.setCellValue( + OPERATION_LOG_EXPORT_HEADERS[columnIndex]); + cell.setCellStyle(headerStyle); + } + + // 第五步:按照业务层返回顺序逐行写入完整操作日志字段。 + int rowIndex = 1; + for (OperationLogVO record : records) { + // 忽略异常空记录,避免单条无效对象导致整个导出失败。 + if (record == null) { + continue; + } + + Row row = sheet.createRow(rowIndex++); + writeOperationLogRow(row, record, bodyStyle); + } + + // 第六步:设置固定列宽,保证编号、时间、内容和地址具有可读空间。 + for (int columnIndex = 0; + columnIndex + < OPERATION_LOG_EXPORT_COLUMN_WIDTHS.length; + columnIndex++) { + sheet.setColumnWidth( + columnIndex, + OPERATION_LOG_EXPORT_COLUMN_WIDTHS[columnIndex] + * 256); + } + + // 第七步:为标题行增加自动筛选,方便下载后继续筛选日志。 + sheet.setAutoFilter( + new CellRangeAddress( + 0, + 0, + 0, + OPERATION_LOG_EXPORT_HEADERS.length - 1)); + + // 第八步:将工作簿完整写入内存并返回独立字节数组。 + workbook.write(outputStream); + return outputStream.toByteArray(); + } catch (IOException exception) { + // 第九步:工作簿生成失败时保留原始异常原因,交由统一异常处理器记录。 + throw new IllegalStateException( + "操作日志Excel文件生成失败", + exception); + } + } + + /** + * 将一条操作日志写入 Excel 正文行。 + * + * @param row 当前 Excel 行 + * @param record 当前操作日志 + * @param bodyStyle 正文单元格样式 + */ + private static void writeOperationLogRow( + Row row, + OperationLogVO record, + CellStyle bodyStyle) { + // 第一步:写入操作日志自身的编号、时间、模块、内容和请求地址。 + setOperationLogCell(row, 0, record.getId(), bodyStyle); + setOperationLogCell( + row, + 1, + formatOperationLogTime(record.getOperationAt()), + bodyStyle); + setOperationLogCell(row, 2, record.getModule(), bodyStyle); + setOperationLogCell( + row, 3, record.getOperationContent(), bodyStyle); + setOperationLogCell(row, 4, record.getUrl(), bodyStyle); + + // 第二步:写入操作账户的编号、登录账号、姓名和统一账号。 + setOperationLogCell( + row, 5, record.getAccountId(), bodyStyle); + setOperationLogCell( + row, 6, record.getLoginName(), bodyStyle); + setOperationLogCell( + row, 7, record.getOperatorName(), bodyStyle); + setOperationLogCell( + row, 8, record.getUnifiedAccount(), bodyStyle); + + // 第三步:写入当前操作所属登录记录及其登录环境信息。 + setOperationLogCell( + row, 9, record.getLoginRecordId(), bodyStyle); + setOperationLogCell( + row, + 10, + formatOperationLogTime(record.getLoginAt()), + bodyStyle); + setOperationLogCell( + row, 11, record.getIpAddress(), bodyStyle); + setOperationLogCell( + row, 12, record.getIntranetIpAddress(), bodyStyle); + setOperationLogCell( + row, 13, record.getTransactionId(), bodyStyle); + } + + /** + * 创建操作日志 Excel 标题单元格样式。 + * + * @param workbook 当前工作簿 + * @return 可复用的标题样式 + */ + private static CellStyle createOperationLogHeaderStyle( + Workbook workbook) { + // 第一步:创建加粗标题字体。 + Font font = workbook.createFont(); + font.setBold(true); + + // 第二步:创建居中、浅灰背景和细边框的标题样式。 + CellStyle style = workbook.createCellStyle(); + style.setFont(font); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setFillForegroundColor( + IndexedColors.GREY_25_PERCENT.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + setOperationLogCellBorders(style); + + // 第三步:返回供全部标题单元格复用的样式。 + return style; + } + + /** + * 创建操作日志 Excel 正文单元格样式。 + * + * @param workbook 当前工作簿 + * @return 可复用的正文样式 + */ + private static CellStyle createOperationLogBodyStyle( + Workbook workbook) { + // 第一步:创建垂直居中且允许长文本换行的正文样式。 + CellStyle style = workbook.createCellStyle(); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setWrapText(true); + + // 第二步:为正文单元格设置统一细边框。 + setOperationLogCellBorders(style); + + // 第三步:返回供全部正文单元格复用的样式。 + return style; + } + + /** + * 为单元格样式设置统一细边框。 + * + * @param style 待设置的单元格样式 + */ + private static void setOperationLogCellBorders( + CellStyle style) { + // 统一设置上下左右细边框,保证导出表格边界清晰。 + style.setBorderTop(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + } + + /** + * 写入经过安全处理的 Excel 文本单元格。 + * + * @param row 当前 Excel 行 + * @param columnIndex 当前列下标 + * @param value 原始文本 + * @param style 正文样式 + */ + private static void setOperationLogCell( + Row row, + int columnIndex, + String value, + CellStyle style) { + // 第一步:创建目标单元格。 + Cell cell = row.createCell(columnIndex); + + /* + * 第二步:写入安全文本。 + * 对公式起始字符增加文本前缀,避免账号、姓名或地址触发 Excel 公式执行。 + */ + cell.setCellValue(toSafeExcelText(value)); + + // 第三步:应用统一正文样式。 + cell.setCellStyle(style); + } + + /** + * 将可能触发 Excel 公式解析的内容转换为普通文本。 + * + * @param value 原始文本 + * @return 可安全写入 Excel 的文本 + */ + private static String toSafeExcelText(String value) { + // 第一步:空值统一写为空文本,避免出现字符串“null”。 + if (value == null || value.isEmpty()) { + return ""; + } + + // 第二步:识别 Excel 可能作为公式解析的起始字符。 + char firstCharacter = value.charAt(0); + if (firstCharacter == '=' + || firstCharacter == '+' + || firstCharacter == '-' + || firstCharacter == '@' + || firstCharacter == '\t' + || firstCharacter == '\r' + || firstCharacter == '\n') { + // 第三步:增加单引号前缀,强制 Excel 将原内容作为普通文本显示。 + return "'" + value; + } + + // 第四步:普通文本保持原值。 + return value; + } + + /** + * 将操作日志时间转换为统一导出文本。 + * + * @param value 原始时间 + * @return yyyy-MM-dd HH:mm:ss 格式文本;空时间返回空字符串 + */ + private static String formatOperationLogTime( + LocalDateTime value) { + // 第一步:数据库时间为空时导出为空单元格。 + if (value == null) { + return ""; + } + + // 第二步:按照统一秒级时间格式输出。 + return value.format( + OPERATION_LOG_EXPORT_CELL_TIME_FORMATTER); + } + + /** + * 判断当前 Controller 请求是否属于需要记录的关键业务操作。 + * + *

新增、修改、删除、登录、退出、导入和其他业务提交通常使用 + * POST、PUT、PATCH 或 DELETE,因此统一记录;普通 GET 查询不记录, + * 仅保留具有审计价值的数据导出操作,避免列表、详情和选项查询产生噪声。

+ * + * @param request 当前 HTTP 请求 + * @param handlerMethod 当前 Controller 方法 + * @return 属于关键业务操作时返回 true,否则返回 false + */ + public static boolean shouldRecordOperation( + HttpServletRequest request, + HandlerMethod handlerMethod) { + /* + * 第一步:请求或 Controller 方法为空时无法确认业务动作, + * 按照非关键操作处理,避免辅助日志流程影响原请求。 + */ + if (request == null || handlerMethod == null) { + return false; + } + + // 第二步:读取 HTTP 方法;没有有效方法时不生成操作日志。 + String httpMethod = request.getMethod(); + if (httpMethod == null + || httpMethod.trim().isEmpty()) { + return false; + } + + /* + * 第三步:记录全部会改变业务状态或提交业务处理的请求。 + * 项目现有新增、修改、删除、登录、退出、导入等接口均包含在该范围内。 + */ + if ("POST".equalsIgnoreCase(httpMethod) + || "PUT".equalsIgnoreCase(httpMethod) + || "PATCH".equalsIgnoreCase(httpMethod) + || "DELETE".equalsIgnoreCase(httpMethod)) { + return true; + } + + /* + * 第四步:普通 GET、OPTIONS 以及其他只读或协议请求不记录。 + * GET 仅在后续规则明确识别为导出时才属于关键操作。 + */ + if (!"GET".equalsIgnoreCase(httpMethod)) { + return false; + } + + // 第五步:优先按照应用内地址最后一个路径段识别标准导出接口。 + String actionSegment = + resolveLastPathSegment( + resolveApplicationUrl(request)); + if ("export".equals(actionSegment)) { + return true; + } + + /* + * 第六步:兼容路径未直接使用 export、但 Controller 方法以 export + * 开头的现有或后续导出接口,不读取方法参数和客户端提交内容。 + */ + String handlerMethodName = + handlerMethod.getMethod().getName() + .trim() + .toLowerCase(Locale.ROOT); + return "export".equals(handlerMethodName) + || handlerMethodName.startsWith("export"); + } + + /** + * 标记当前请求的业务处理已经失败。 + * + *

该标记只保存在当前请求范围内,不保存异常信息, + * 供操作日志拦截器在请求完成阶段判断是否记录失败日志。

+ * + * @param request 当前 HTTP 请求 + */ + public static void markOperationFailed( + HttpServletRequest request) { + // 第一步:没有当前请求时不能保存请求范围内的失败标记。 + if (request == null) { + return; + } + + // 第二步:使用固定布尔值标记当前请求已经进入统一异常处理流程。 + request.setAttribute( + OPERATION_LOG_FAILED_REQUEST_ATTRIBUTE, + Boolean.TRUE); + } + + /** + * 判断当前请求是否已经被统一异常处理器标记为失败。 + * + * @param request 当前 HTTP 请求 + * @return 已明确标记失败时返回 true,否则返回 false + */ + public static boolean isOperationFailed( + HttpServletRequest request) { + // 第一步:没有当前请求时不能取得失败标记。 + if (request == null) { + return false; + } + + // 第二步:只接受工具类写入的 Boolean.TRUE,拒绝其他异常属性值。 + return Boolean.TRUE.equals( + request.getAttribute( + OPERATION_LOG_FAILED_REQUEST_ATTRIBUTE)); + } + + /** + * 从当前请求已经存在的 Servlet Session 中安全读取登录记录编号。 + * + *

该方法不会创建新 Session;没有有效登录上下文时返回 null, + * 供后续统一日志采集流程决定是否跳过当前请求。

+ * + * @param request 当前 HTTP 请求 + * @return 规范化后的登录记录编号;无法安全取得时返回 null + */ + public static String resolveCurrentLoginRecordId( + HttpServletRequest request) { + // 第一步:没有当前 HTTP 请求时不能取得对应登录上下文。 + if (request == null) { + return null; + } + + /* + * 第二步:只读取已经存在的 Servlet Session。 + * 日志辅助流程不能为匿名请求创建没有业务意义的新 Session。 + */ + HttpSession session; + try { + session = request.getSession(false); + } catch (IllegalStateException exception) { + // 请求或其关联会话已经失效时,按照没有登录上下文处理。 + return null; + } + if (session == null) { + return null; + } + + /* + * 第三步:读取登录成功时由服务端保存的登录记录编号。 + * 并发退出登录可能已经作废 Session,因此捕获失效状态并安全返回。 + */ + Object loginRecordId; + try { + loginRecordId = + session.getAttribute( + LOGIN_RECORD_ID_SESSION_ATTRIBUTE); + } catch (IllegalStateException exception) { + return null; + } + + // 第四步:只接受登录流程写入的字符串属性,拒绝异常类型。 + if (!(loginRecordId instanceof String)) { + return null; + } + + // 第五步:去除编号首尾空白,并拒绝空内容。 + String normalizedLoginRecordId = + ((String) loginRecordId).trim(); + if (normalizedLoginRecordId.isEmpty()) { + return null; + } + + // 第六步:拒绝超过数据库标识符字段长度的异常 Session 数据。 + if (normalizedLoginRecordId.length() + > IDENTIFIER_MAX_LENGTH) { + return null; + } + + // 第七步:返回可供内部操作日志 DTO 使用的登录记录编号。 + return normalizedLoginRecordId; + } + + /** + * 从当前请求属性中安全读取 Controller 执行前暂存的登录记录编号。 + * + *

该方法主要用于退出登录场景。退出接口作废 Session 后, + * 日志记录流程仍可使用请求范围内提前保存的登录记录编号。

+ * + * @param request 当前 HTTP 请求 + * @return 规范化后的登录记录编号;请求属性不存在或不合法时返回 null + */ + public static String resolveRequestLoginRecordId( + HttpServletRequest request) { + // 第一步:没有当前 HTTP 请求时不能读取请求范围内的暂存数据。 + if (request == null) { + return null; + } + + // 第二步:读取拦截器在 Controller 执行前保存的登录记录编号。 + Object loginRecordId = request.getAttribute( + OPERATION_LOG_LOGIN_RECORD_ID_REQUEST_ATTRIBUTE); + + // 第三步:只接受统一采集流程写入的字符串属性。 + if (!(loginRecordId instanceof String)) { + return null; + } + + // 第四步:去除编号首尾空白,并拒绝空内容。 + String normalizedLoginRecordId = + ((String) loginRecordId).trim(); + if (normalizedLoginRecordId.isEmpty()) { + return null; + } + + // 第五步:拒绝超过数据库标识符字段长度的异常请求属性。 + if (normalizedLoginRecordId.length() + > IDENTIFIER_MAX_LENGTH) { + return null; + } + + // 第六步:返回可供内部操作日志 DTO 使用的登录记录编号。 + return normalizedLoginRecordId; + } + + /** + * 取得不包含部署上下文和查询参数的应用内请求地址。 + * + * @param request 当前 HTTP 请求 + * @return 可直接写入操作日志的应用内请求地址 + */ + public static String resolveApplicationUrl( + HttpServletRequest request) { + // 第一步:没有当前请求时使用根地址,保证内部日志地址始终有效。 + if (request == null) { + return "/"; + } + + /* + * 第二步:只读取请求 URI。 + * getRequestURI 不包含查询参数,因此不会把筛选条件或敏感参数写入日志。 + */ + String requestUri = request.getRequestURI(); + if (requestUri == null || requestUri.trim().isEmpty()) { + return "/"; + } + + // 第三步:去除应用部署上下文,只保留系统内部路由地址。 + String applicationUrl = requestUri.trim(); + String contextPath = request.getContextPath(); + if (contextPath != null + && !contextPath.isEmpty() + && applicationUrl.startsWith(contextPath)) { + applicationUrl = + applicationUrl.substring(contextPath.length()); + } + + // 第四步:上下文之后没有具体路径时,统一使用根地址。 + if (applicationUrl.isEmpty()) { + applicationUrl = "/"; + } + + // 第五步:补充缺失的路径分隔符,保持日志地址格式一致。 + if (!applicationUrl.startsWith("/")) { + applicationUrl = "/" + applicationUrl; + } + + /* + * 第六步:应用内地址超过数据库字段长度时按字段边界截取。 + * 日志辅助流程不能因为异常长地址影响原业务请求正常完成。 + */ + if (applicationUrl.length() > OPERATION_URL_MAX_LENGTH) { + applicationUrl = + applicationUrl.substring(0, OPERATION_URL_MAX_LENGTH); + } + + // 第七步:返回不包含域名、端口、部署上下文和查询参数的应用内地址。 + return applicationUrl; + } + + /** + * 根据请求地址、HTTP 方法和 Controller 方法生成操作内容。 + * + *

操作内容只描述业务模块和动作,不读取请求参数或请求体, + * 避免把密码、验证码等敏感数据写入操作日志。

+ * + * @param request 当前 HTTP 请求 + * @param handlerMethod 当前 Controller 方法 + * @return 可直接写入操作日志的中文操作内容 + */ + public static String resolveOperationContent( + HttpServletRequest request, + HandlerMethod handlerMethod) { + // 第一步:取得与日志 URL 字段完全一致的应用内请求地址。 + String applicationUrl = resolveApplicationUrl(request); + + // 第二步:根据应用内地址识别当前操作所属业务模块。 + String module = resolveModule(applicationUrl); + + // 第三步:提取请求地址最后一个路径段,作为优先动作识别依据。 + String actionSegment = + resolveLastPathSegment(applicationUrl); + + /* + * 第四步:地址动作无法识别时,再使用 Controller 方法名称补充判断。 + * 这里只读取方法名称,不读取方法参数和请求数据。 + */ + String handlerMethodName = handlerMethod == null + ? null + : handlerMethod.getMethod().getName(); + + // 第五步:取得当前 HTTP 方法,为未识别的业务动作提供稳定兜底。 + String httpMethod = request == null + ? null + : request.getMethod(); + + // 第六步:按照地址动作、方法名称和 HTTP 方法的顺序识别中文动作。 + String action = resolveAction( + actionSegment, handlerMethodName, httpMethod); + + // 第七步:组合模块和动作,形成简洁且可检索的操作内容。 + return module + ":" + action; + } + + /** + * 提取应用内地址最后一个非空路径段。 + * + * @param applicationUrl 应用内请求地址 + * @return 小写路径动作;没有有效路径段时返回空字符串 + */ + private static String resolveLastPathSegment( + String applicationUrl) { + // 第一步:空地址或根地址没有可用于识别的动作路径段。 + if (applicationUrl == null + || applicationUrl.trim().isEmpty() + || "/".equals(applicationUrl.trim())) { + return ""; + } + + // 第二步:去除地址末尾的路径分隔符,避免提取到空路径段。 + String normalizedUrl = + applicationUrl.trim().toLowerCase(Locale.ROOT); + while (normalizedUrl.endsWith("/") + && normalizedUrl.length() > 1) { + normalizedUrl = + normalizedUrl.substring( + 0, normalizedUrl.length() - 1); + } + + // 第三步:截取最后一个路径分隔符之后的动作名称。 + int separatorIndex = normalizedUrl.lastIndexOf('/'); + if (separatorIndex < 0 + || separatorIndex == normalizedUrl.length() - 1) { + return normalizedUrl; + } + + // 第四步:返回统一转换为小写的最后一个路径段。 + return normalizedUrl.substring(separatorIndex + 1); + } + + /** + * 按照请求动作、Controller 方法名称和 HTTP 方法识别中文动作。 + * + * @param actionSegment 请求地址最后一个路径段 + * @param handlerMethodName Controller 方法名称 + * @param httpMethod HTTP 方法 + * @return 中文操作动作 + */ + private static String resolveAction( + String actionSegment, + String handlerMethodName, + String httpMethod) { + // 第一步:优先按照项目现有接口地址中的明确动作名称进行识别。 + String action = resolveKnownAction(actionSegment); + if (action != null) { + return action; + } + + /* + * 第二步:地址没有明确动作时,尝试使用 Controller 方法名称识别。 + * 方法名称只作为兼容非标准地址的辅助依据。 + */ + action = resolveKnownAction(handlerMethodName); + if (action != null) { + return action; + } + + // 第三步:仍无法识别时,根据 HTTP 方法生成不会暴露业务参数的兜底描述。 + if ("GET".equalsIgnoreCase(httpMethod)) { + return "查询"; + } + if ("DELETE".equalsIgnoreCase(httpMethod)) { + return "删除"; + } + if ("PUT".equalsIgnoreCase(httpMethod) + || "PATCH".equalsIgnoreCase(httpMethod)) { + return "修改"; + } + + // 第四步:POST 和其他自定义方法统一描述为执行业务操作。 + return "执行业务操作"; + } + + /** + * 将项目现有接口动作名称转换为中文操作描述。 + * + * @param actionName 地址动作或 Controller 方法名称 + * @return 已识别的中文动作;无法识别时返回 null + */ + private static String resolveKnownAction(String actionName) { + // 第一步:没有可识别名称时交给后续规则继续处理。 + if (actionName == null || actionName.trim().isEmpty()) { + return null; + } + + // 第二步:统一小写并移除方法名称中的分隔符,兼容路径和 Java 命名方式。 + String normalizedAction = actionName.trim() + .toLowerCase(Locale.ROOT) + .replace("-", "") + .replace("_", ""); + + // 第三步:识别新增、修改、删除及状态变更动作。 + if ("add".equals(normalizedAction) + || normalizedAction.startsWith("add")) { + return "新增"; + } + if ("update".equals(normalizedAction) + || normalizedAction.startsWith("update")) { + return "修改"; + } + if ("delete".equals(normalizedAction) + || "remove".equals(normalizedAction) + || normalizedAction.startsWith("delete") + || normalizedAction.startsWith("remove")) { + return "删除"; + } + if ("disable".equals(normalizedAction)) { + return "停用"; + } + if ("enable".equals(normalizedAction)) { + return "启用"; + } + if ("leave".equals(normalizedAction)) { + return "离岗"; + } + if ("restore".equals(normalizedAction)) { + return "恢复在岗"; + } + + // 第四步:识别列表、详情、树形数据和可选数据查询动作。 + if ("list".equals(normalizedAction) + || "all".equals(normalizedAction) + || "tree".equals(normalizedAction) + || "available".equals(normalizedAction) + || "departments".equals(normalizedAction) + || "roles".equals(normalizedAction) + || "roletypes".equals(normalizedAction) + || "roleoptions".equals(normalizedAction) + || "versions".equals(normalizedAction) + || normalizedAction.startsWith("list")) { + return "查询列表"; + } + if ("get".equals(normalizedAction) + || normalizedAction.startsWith("get")) { + return "查询详情"; + } + + // 第五步:识别导入、导出、模板及自动生成动作。 + if ("import".equals(normalizedAction) + || "batchimport".equals(normalizedAction)) { + return "导入"; + } + if ("export".equals(normalizedAction)) { + return "导出"; + } + if ("template".equals(normalizedAction)) { + return "下载导入模板"; + } + if ("generate".equals(normalizedAction)) { + return "生成"; + } + + // 第六步:识别账户登录、退出和密码维护动作。 + if ("login".equals(normalizedAction)) { + return "登录"; + } + if ("logout".equals(normalizedAction)) { + return "退出登录"; + } + if ("logincaptcha".equals(normalizedAction)) { + return "获取登录验证码"; + } + if ("logininformation".equals(normalizedAction)) { + return "查询登录信息"; + } + if ("resolvessosession".equals(normalizedAction)) { + return "解析单点登录会话"; + } + if ("resetpassword".equals(normalizedAction)) { + return "重置密码"; + } + if ("changepassword".equals(normalizedAction)) { + return "修改密码"; + } + if ("configureroles".equals(normalizedAction)) { + return "配置角色"; + } + if ("merge".equals(normalizedAction)) { + return "合并账户"; + } + + // 第七步:识别学籍异动等项目中已经存在的专用业务动作。 + if ("retaingrade".equals(normalizedAction)) { + return "留级"; + } + if ("downgrade".equals(normalizedAction)) { + return "降级"; + } + if ("dropout".equals(normalizedAction)) { + return "退学"; + } + + // 第八步:当前名称无法识别时返回 null,由 HTTP 方法规则兜底。 + return null; + } + + /** + * 规范化必填文本并校验最大长度。 + * + * @param value 原始文本 + * @param maxLength 最大长度 + * @param fieldName 字段名称 + * @return 通过校验的规范化文本 + */ + public static String requiredText( + String value, int maxLength, String fieldName) { + // 第一步:复用可选文本规则完成去除首尾空白和长度校验。 + String normalized = + optionalText(value, maxLength, fieldName); + + // 第二步:规范化后没有有效内容时返回必填参数错误。 + if (normalized == null) { + throw new BusinessException( + BAD_REQUEST, fieldName + "不能为空"); + } + + // 第三步:返回可供业务流程和数据库写入使用的有效文本。 + return normalized; + } + + /** + * 规范化可选文本并校验最大长度。 + * + * @param value 原始文本 + * @param maxLength 最大长度 + * @param fieldName 字段名称 + * @return 去除首尾空白后的文本;没有有效内容时返回 null + */ + public static String optionalText( + String value, int maxLength, String fieldName) { + // 第一步:未填写文本时直接返回 null,表示不应用对应查询条件。 + if (value == null) { + return null; + } + + // 第二步:去除首尾空白,避免无意义空格影响查询和日志写入。 + String normalized = value.trim(); + if (normalized.isEmpty()) { + return null; + } + + // 第三步:校验规范化后的长度,防止超过接口或数据库字段边界。 + if (normalized.length() > maxLength) { + throw new BusinessException( + BAD_REQUEST, + fieldName + "不能超过" + maxLength + "个字符"); + } + + // 第四步:返回通过校验的规范化文本。 + return normalized; + } + + /** + * 规范化并校验可选模块名称。 + * + * @param value 原始模块名称 + * @return 系统支持的模块名称;未填写时返回 null + */ + public static String optionalModule(String value) { + // 第一步:复用可选文本处理,完成空值和长度校验。 + String module = optionalText( + value, MODULE_NAME_MAX_LENGTH, "模块名称"); + if (module == null) { + return null; + } + + /* + * 第二步:只接收能够映射到当前项目 Controller 地址的模块名称。 + * “其他”用于查询无法归入现有地址规则的历史日志。 + */ + if (SUPPORTED_MODULES.contains(module)) { + return module; + } + + // 第三步:拒绝无法转换为地址筛选条件的模块名称。 + throw new BusinessException( + BAD_REQUEST, "不支持的模块名称"); + } + + /** + * 根据请求地址识别操作日志所属模块。 + * + * @param url 请求地址 + * @return 模块名称 + */ + public static String resolveModule(String url) { + // 第一步:空地址无法识别业务模块,统一归入其他。 + if (url == null || url.trim().isEmpty()) { + return "其他"; + } + + // 第二步:统一转换为小写,避免地址英文字母大小写影响识别。 + String normalizedUrl = + url.trim().toLowerCase(Locale.ROOT); + + // 第三步:优先匹配更具体的部别人员管理地址。 + if (normalizedUrl.contains( + "/department-personnel/")) { + return "部别人员管理"; + } + + // 第四步:匹配教研室和教学班次两类隶属关系地址。 + if (normalizedUrl.contains( + "/research-office-affiliation/") + || normalizedUrl.contains( + "/teaching-class-affiliation/")) { + return "隶属关系设置"; + } + + // 第五步:依次识别其余已经完成的基础性系管理模块。 + if (normalizedUrl.contains("/department/")) { + return "部别管理"; + } + if (normalizedUrl.contains("/dictionary-setting/")) { + return "字典设置"; + } + if (normalizedUrl.contains("/account-management/")) { + return "账户管理"; + } + if (normalizedUrl.contains("/operation-log/")) { + return "操作日志管理"; + } + if (normalizedUrl.contains("/notice-announcement/")) { + return "通知公告管理"; + } + + /* + * 第六步:识别培养方案、学科专业、教研室与教员、 + * 课程教学以及学员档案等教学业务模块。 + */ + if (normalizedUrl.contains("/training/")) { + return "培养方案管理"; + } + if (normalizedUrl.contains("/discipline/")) { + return "学科专业管理"; + } + if (normalizedUrl.contains("/faculty/")) { + return "教研室与教员管理"; + } + if (normalizedUrl.contains("/course-teaching/")) { + return "课程教学管理"; + } + if (normalizedUrl.contains("/student-records/")) { + return "学员档案管理"; + } + + /* + * 第七步:优先识别教室体系中的具体子模块。 + * “/classroom/jxcdl/”必须先于通用“/classroom/”判断, + * 否则教学场地历会被错误归入教室管理。 + */ + if (normalizedUrl.contains("/classroom/jxcdl/")) { + return "教学场地历管理"; + } + if (normalizedUrl.contains("/classroomlesson/")) { + return "教室输出课程表管理"; + } + if (normalizedUrl.contains("/build/")) { + return "教学楼管理"; + } + if (normalizedUrl.contains("/classroom/")) { + return "教室管理"; + } + + /* + * 第八步:识别学期校历、教材信息和教材库存模块。 + * 教材库存单与其明细项使用同一路径前缀,统一归入教材库存管理。 + */ + if (normalizedUrl.contains("/xqxlb/")) { + return "学期校历管理"; + } + if (normalizedUrl.contains("/teachingmaterial/")) { + return "教材管理"; + } + if (normalizedUrl.contains("/teachingstock/")) { + return "教材库存管理"; + } + + // 第九步:不能匹配当前项目 Controller 地址规则的日志统一归入其他。 + return "其他"; + } + + /** + * 私有化构造方法,防止无状态工具类被实例化。 + */ + private OperationLogUtil() { + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/PageResultUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/PageResultUtil.java new file mode 100644 index 000000000..5336e8c82 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/PageResultUtil.java @@ -0,0 +1,90 @@ +package com.roomroot.jwgl.utils; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.jwgl.unit.PageResult; + +import java.util.Collections; +import java.util.List; + +/** + * 项目统一分页结果转换工具类。 + * + *

集中处理 Mapper 返回空分页对象、分页记录集合为 null 以及 + * MyBatis-Plus 分页对象转换为项目 {@link PageResult} 的通用逻辑。

+ */ +public final class PageResultUtil { + + /** + * 工具类不允许创建实例。 + */ + private PageResultUtil() { + } + + /** + * 将 Mapper 返回的 MyBatis-Plus 分页对象转换为项目统一分页结果。 + * + * @param selectedPage Mapper 返回的分页对象 + * @param defaultPage Mapper 返回 null 时使用的默认分页对象 + * @param pageNum 当前页码 + * @param pageSize 每页记录数 + * @param 分页记录类型 + * @return 项目统一分页结果 + */ + public static PageResult fromPage( + Page selectedPage, + Page defaultPage, + Integer pageNum, + Integer pageSize) { + // 第一步:优先使用 Mapper 返回结果,为 null 时回退到调用方创建的分页对象。 + Page resultPage = + selectedPage == null ? defaultPage : selectedPage; + + // 第二步:读取分页记录,空分页对象或空记录集合统一按照空集合处理。 + List records = resultPage == null + ? Collections.emptyList() + : resultPage.getRecords(); + + // 第三步:使用通用记录组装方法返回项目统一分页结构。 + return fromRecords( + resultPage, + records, + pageNum, + pageSize); + } + + /** + * 使用已经完成业务转换的记录集合组装项目统一分页结果。 + * + *

适用于 Mapper 分页记录还需要补充字段或转换为 VO 的场景, + * 分页总数继续取自原始 MyBatis-Plus 分页对象。

+ * + * @param resultPage 提供分页总数的 MyBatis-Plus 分页对象 + * @param records 已经完成业务处理的当前页记录 + * @param pageNum 当前页码 + * @param pageSize 每页记录数 + * @param 对外返回的分页记录类型 + * @return 项目统一分页结果 + */ + public static PageResult fromRecords( + Page resultPage, + List records, + Integer pageNum, + Integer pageSize) { + // 第一步:将 null 记录集合统一转换为不可变空集合,保证返回结构稳定。 + List safeRecords = records == null + ? Collections.emptyList() + : records; + + // 第二步:分页对象为空时使用零条记录,避免公共转换过程产生空指针。 + Long total = resultPage == null + ? Long.valueOf(0L) + : Long.valueOf(resultPage.getTotal()); + + // 第三步:创建并返回项目统一分页结果。 + return new PageResult<>( + safeRecords, + total, + pageNum, + pageSize); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/SupportConfirmUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/SupportConfirmUtil.java new file mode 100644 index 000000000..94465771d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/SupportConfirmUtil.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.utils; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class SupportConfirmUtil { + + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + private SupportConfirmUtil() { + } + + public static boolean isConfirmed(String remark) { + return remark != null && remark.contains("\"confirmed\":true"); + } + + public static String buildConfirmJson(String qrrbh, String qrbz) { + String confirmAt = LocalDateTime.now().format(DATE_TIME_FORMATTER); + StringBuilder sb = new StringBuilder(); + sb.append("{\"confirmed\":true,"); + sb.append("\"confirmedBy\":\"").append(qrrbh).append("\","); + sb.append("\"confirmedAt\":\"").append(confirmAt).append("\","); + sb.append("\"confirmRemark\":\""); + if (qrbz != null) { + sb.append(qrbz.replace("\"", "\\\"")); + } + sb.append("\"}"); + return sb.toString(); + } + + public static String mergeRemark(String originalRemark, String newRemark) { + if (originalRemark == null || originalRemark.trim().isEmpty()) { + return newRemark; + } + + if (newRemark == null || newRemark.trim().isEmpty()) { + return originalRemark; + } + + if (newRemark.contains("\"confirmed\":true")) { + return originalRemark + " | " + newRemark; + } + + if (originalRemark.contains("\"confirmed\":true")) { + int splitIndex = originalRemark.indexOf(" | {"); + if (splitIndex > 0) { + return newRemark + originalRemark.substring(splitIndex); + } + return newRemark + " | " + originalRemark; + } + + return newRemark; + } + + public static String clearConfirmJson(String remark) { + if (remark == null || remark.trim().isEmpty()) { + return ""; + } + + int splitIndex = remark.indexOf(" | {"); + if (splitIndex > 0) { + return remark.substring(0, splitIndex); + } + + if (remark.contains("\"confirmed\":true")) { + return ""; + } + + return remark; + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/SystemInitializationUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/SystemInitializationUtil.java new file mode 100644 index 000000000..355da2ac3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/SystemInitializationUtil.java @@ -0,0 +1,1647 @@ +package com.roomroot.jwgl.utils; + +import com.roomroot.jwgl.dto.systeminitialization.*; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.vo.systeminitialization.*; +import com.roomroot.jwgl.vo.systeminitialization.*; +import org.apache.poi.ss.usermodel.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.time.LocalDateTime; +import java.util.*; + +import static com.roomroot.jwgl.utils.BasicManagementConstants.*; + +/** + * 系统初始化参数校验、Excel 解析和展示字段组装工具类。 + * + *

工具类只处理可复用的纯数据逻辑,不访问数据库, + * 使系统初始化 Impl 保持为清晰的业务流程编排。

+ */ +public final class SystemInitializationUtil { + + /** 历史初始化 Excel 最大文件大小,单位为字节。 */ + private static final long MAX_IMPORT_FILE_SIZE = 20L * 1024L * 1024L; + + /** 单次历史初始化最多允许处理的数据行数。 */ + private static final int MAX_IMPORT_ROW_COUNT = 5000; + + /** 部别工作表名称。 */ + private static final String DEPARTMENT_SHEET = "部别"; + + /** 教研室工作表名称。 */ + private static final String RESEARCH_OFFICE_SHEET = "教研室"; + + /** 部别人员工作表名称。 */ + private static final String PERSONNEL_SHEET = "部别人员"; + + /** Excel 文本格式化器。 */ + private static final DataFormatter DATA_FORMATTER = + new DataFormatter(Locale.CHINA); + + /** + * 规范化并校验待保存的系统基础参数。 + * + * @param dto 原始保存参数 + * @param current 当前生效配置;首次初始化时为 null + * @return 可以直接交给 Mapper 保存的参数 + */ + public static SystemInitializationConfigurationSaveDTO + prepareConfiguration( + SystemInitializationConfigurationSaveDTO dto, + SystemInitializationConfigurationVO current) { + // 第一步:阻止业务层直接调用时传入空保存参数。 + if (dto == null) { + throw new BusinessException( + BAD_REQUEST, + "系统基础参数不能为空"); + } + + /* + * 第二步:已有配置时必须校验页面期望版本。 + * 这样可以识别其他操作人已经保存的新版本,避免旧页面覆盖新数据。 + */ + if (current != null) { + if (dto.getExpectedVersionId() == null) { + throw new BusinessException( + BAD_REQUEST, + "期望版本编号不能为空"); + } + if (!dto.getExpectedVersionId() + .equals(current.getVersionId())) { + throw new BusinessException( + CONFLICT, + "系统基础参数已经更新,请刷新后重试"); + } + } + + // 第三步:首次初始化时不允许传入没有实际对应记录的期望版本编号。 + if (current == null && dto.getExpectedVersionId() != null) { + throw new BusinessException( + CONFLICT, + "系统尚无初始化配置,请刷新后重试"); + } + + // 第四步:规范化全部必填文本并执行长度校验。 + dto.setUniversityName( + requireText(dto.getUniversityName(), + "院校名称", 200)); + dto.setUniversityCode( + requireText(dto.getUniversityCode(), + "院校代码", 100)); + dto.setDefaultSystemName( + requireText(dto.getDefaultSystemName(), + "缺省系统名称", 200)); + dto.setSystemMode( + requireText(dto.getSystemMode(), + "系统运行模式", 50)); + dto.setSemesterNameStyle( + requireText(dto.getSemesterNameStyle(), + "学期名称样式", 200)); + dto.setPeriodCategory( + requireText(dto.getPeriodCategory(), + "节次类别", 100)); + dto.setExternalFileStorageRootPath( + requireText(dto.getExternalFileStorageRootPath(), + "外部文件存储根路径", 1000)); + dto.setLocalFileAccessPath( + requireText(dto.getLocalFileAccessPath(), + "本站文件访问路径", 1000)); + + // 第五步:学员队名称规则必须由页面明确选择。 + if (dto.getStudentTeamFullNameIncludesGrade() == null) { + throw new BusinessException( + BAD_REQUEST, + "学员队全名称含年级标志不能为空"); + } + + /* + * 第六步:版本值为空时沿用当前值。 + * 首次初始化仍未提供版本值时使用固定初始版本,保证数据库字段可识别。 + */ + String versionValue = + optionalText(dto.getVersionValue(), 100, "配置版本值"); + if (versionValue == null && current != null) { + versionValue = current.getVersionValue(); + } + dto.setVersionValue( + versionValue == null ? "1.0" : versionValue); + + // 第七步:返回完成原地规范化的保存参数。 + return dto; + } + + /** + * 规范化并校验学期初始化参数。 + * + * @param dto 原始学期参数 + * @return 可以直接保存的学期参数 + */ + public static SemesterInitializationSaveDTO prepareSemester( + SemesterInitializationSaveDTO dto) { + // 第一步:阻止业务层直接调用时传入空学期参数。 + if (dto == null) { + throw new BusinessException( + BAD_REQUEST, + "学期初始化参数不能为空"); + } + + // 第二步:校验年度位于系统允许维护的范围内。 + if (dto.getYear() == null + || dto.getYear().intValue() < 2000 + || dto.getYear().intValue() > 2100) { + throw new BusinessException( + BAD_REQUEST, + "学期年度必须在2000至2100之间"); + } + + // 第三步:校验同一年度内的学期序号。 + if (dto.getSequenceNumber() == null + || dto.getSequenceNumber().intValue() < 1 + || dto.getSequenceNumber().intValue() > 10) { + throw new BusinessException( + BAD_REQUEST, + "学期序号必须在1至10之间"); + } + + // 第四步:开学和结束时间均为学期初始化必填参数。 + if (dto.getStartAt() == null || dto.getEndAt() == null) { + throw new BusinessException( + BAD_REQUEST, + "开学时间和结束时间不能为空"); + } + + // 第五步:结束时间必须严格晚于开学时间。 + if (!dto.getEndAt().isAfter(dto.getStartAt())) { + throw new BusinessException( + BAD_REQUEST, + "学期结束时间必须晚于开学时间"); + } + + // 第六步:补全由非 Controller 调用时可能遗漏的布尔默认值。 + dto.setCurrentSemester(defaultBoolean( + dto.getCurrentSemester(), true)); + dto.setAdjustmentWithoutApproval(defaultBoolean( + dto.getAdjustmentWithoutApproval(), false)); + dto.setAdjustmentForbidden(defaultBoolean( + dto.getAdjustmentForbidden(), false)); + dto.setFinalGradeAsPass(defaultBoolean( + dto.getFinalGradeAsPass(), false)); + dto.setLeadershipReviewRequired(defaultBoolean( + dto.getLeadershipReviewRequired(), false)); + dto.setTeacherAdjustmentWithoutApproval(defaultBoolean( + dto.getTeacherAdjustmentWithoutApproval(), false)); + dto.setTimeAdjustmentWithoutApproval(defaultBoolean( + dto.getTimeAdjustmentWithoutApproval(), false)); + dto.setSupportAdjustmentWithoutApproval(defaultBoolean( + dto.getSupportAdjustmentWithoutApproval(), false)); + dto.setVenueAdjustmentWithoutApproval(defaultBoolean( + dto.getVenueAdjustmentWithoutApproval(), false)); + + // 第七步:校验锁定周数,并为空值提供零周默认值。 + Integer lockedWeeks = dto.getLockedWeeks(); + if (lockedWeeks == null) { + lockedWeeks = Integer.valueOf(0); + } + if (lockedWeeks.intValue() < 0 + || lockedWeeks.intValue() > 100) { + throw new BusinessException( + BAD_REQUEST, + "锁定周数必须在0至100之间"); + } + dto.setLockedWeeks(lockedWeeks); + + // 第八步:规范化学期的两个可选文本字段。 + dto.setHourCoefficientSchemeId( + optionalText( + dto.getHourCoefficientSchemeId(), + 100, + "课时系数方案编号")); + dto.setLockMeasurementUnit( + optionalText( + dto.getLockMeasurementUnit(), + 50, + "锁定计量单位")); + + // 第九步:返回完成规范化的学期初始化参数。 + return dto; + } + + /** + * 解析系统初始化历史基础数据 Excel。 + * + * @param dto 上传文件参数 + * @return 已规范化的三类历史基础数据 + */ + public static SystemInitializationHistoricalDataDTO + parseHistoricalData( + SystemInitializationHistoricalImportDTO dto) { + // 第一步:校验请求和上传文件对象。 + MultipartFile file = dto == null ? null : dto.getFile(); + if (file == null || file.isEmpty()) { + throw new BusinessException( + BAD_REQUEST, + "历史数据文件不能为空"); + } + + // 第二步:限制上传文件大小,避免初始化接口占用过多服务端内存。 + if (file.getSize() > MAX_IMPORT_FILE_SIZE) { + throw new BusinessException( + BAD_REQUEST, + "历史数据文件不能超过20MB"); + } + + // 第三步:只允许项目已经引入解析能力的 xls 和 xlsx 文件。 + String fileName = normalizeFileName( + file.getOriginalFilename()); + String lowerName = fileName.toLowerCase(Locale.ROOT); + if (!lowerName.endsWith(".xls") + && !lowerName.endsWith(".xlsx")) { + throw new BusinessException( + BAD_REQUEST, + "历史数据文件必须为xls或xlsx格式"); + } + + /* + * 第四步:使用 Apache POI 自动识别工作簿格式。 + * try-with-resources 保证上传流和工作簿在成功或失败后均能释放。 + */ + try (InputStream inputStream = file.getInputStream(); + Workbook workbook = + WorkbookFactory.create(inputStream)) { + // 第五步:创建本次解析结果并保留安全处理后的原文件名。 + SystemInitializationHistoricalDataDTO result = + new SystemInitializationHistoricalDataDTO(); + result.setFileName(fileName); + + // 第六步:分别解析存在的部别、教研室和部别人员工作表。 + result.setDepartments(parseDepartments( + workbook.getSheet(DEPARTMENT_SHEET))); + result.setResearchOffices(parseResearchOffices( + workbook.getSheet(RESEARCH_OFFICE_SHEET))); + result.setPersonnel(parsePersonnel( + workbook.getSheet(PERSONNEL_SHEET))); + + // 第七步:至少需要提供一类可导入的历史基础数据。 + int totalRows = result.getDepartments().size() + + result.getResearchOffices().size() + + result.getPersonnel().size(); + if (totalRows == 0) { + throw new BusinessException( + BAD_REQUEST, + "历史数据文件中没有可导入的数据"); + } + + // 第八步:限制整份工作簿的总数据行数。 + if (totalRows > MAX_IMPORT_ROW_COUNT) { + throw new BusinessException( + BAD_REQUEST, + "单次最多导入5000条历史数据"); + } + + // 第九步:返回已完成格式、必填项和文件内重复校验的数据。 + return result; + } catch (BusinessException exception) { + // 第十步:业务校验异常直接保留原状态码和明确提示。 + throw exception; + } catch (IOException exception) { + // 第十一步:读取上传流失败时转换为统一业务异常。 + throw new BusinessException( + BAD_REQUEST, + "历史数据文件读取失败"); + } catch (RuntimeException exception) { + // 第十二步:损坏、加密或无法识别的工作簿统一返回格式错误。 + throw new BusinessException( + BAD_REQUEST, + "历史数据Excel格式不正确"); + } + } + + /** + * 按父子关系对待导入部别排序。 + * + * @param rows 文件中的部别 + * @param existingIds 数据库已有部别编号 + * @return 保证上级先于下级的部别顺序 + */ + public static List + sortDepartmentsByHierarchy( + List rows, + Set existingIds) { + // 第一步:将空集合统一转换为不可变空列表。 + if (rows == null || rows.isEmpty()) { + return Collections.emptyList(); + } + + // 第二步:建立文件内部别编号索引,用于快速检查上级引用。 + Map + pending = new LinkedHashMap<>(); + for (SystemInitializationDepartmentImportRowDTO row : rows) { + pending.put(row.getId(), row); + } + + // 第三步:把数据库已有部别作为已经可以被引用的上级节点。 + Set available = new HashSet<>(); + if (existingIds != null) { + available.addAll(existingIds); + } + + // 第四步:提前识别既不在数据库也不在本文件中的上级编号。 + for (SystemInitializationDepartmentImportRowDTO row : rows) { + String parentId = row.getParentId(); + if (parentId != null + && !available.contains(parentId) + && !pending.containsKey(parentId)) { + throw new BusinessException( + NOT_FOUND, + "部别“" + row.getName() + + "”引用的上级部别不存在"); + } + } + + // 第五步:循环选择上级已经可用的部别,形成父节点优先顺序。 + List sorted = + new ArrayList<>(); + while (!pending.isEmpty()) { + boolean progressed = false; + List resolvedIds = new ArrayList<>(); + + // 当前轮只处理根节点或上级已经处理完成的节点。 + for (Map.Entry + entry : pending.entrySet()) { + String parentId = entry.getValue().getParentId(); + if (parentId == null || available.contains(parentId)) { + sorted.add(entry.getValue()); + available.add(entry.getKey()); + resolvedIds.add(entry.getKey()); + progressed = true; + } + } + + // 删除当前轮已经完成排序的部别。 + for (String resolvedId : resolvedIds) { + pending.remove(resolvedId); + } + + // 一轮没有任何进展说明文件内部别上级关系存在循环。 + if (!progressed) { + throw new BusinessException( + CONFLICT, + "历史部别数据存在循环隶属关系"); + } + } + + // 第六步:返回保证上级先写入的部别列表。 + return sorted; + } + + /** + * 为学期 VO 补充页面展示名称。 + * + * @param semester 原始学期 VO + * @return 已补充名称的学期 VO + */ + public static SemesterInitializationVO completeSemesterName( + SemesterInitializationVO semester) { + // 第一步:空学期直接返回,便于查询当前学期时处理未初始化状态。 + if (semester == null) { + return null; + } + + // 第二步:使用年度和序号生成稳定且不依赖数据库表达式的学期名称。 + semester.setSemesterName( + semester.getYear() + + "年第" + + semester.getSequenceNumber() + + "学期"); + + // 第三步:返回补充展示字段后的学期信息。 + return semester; + } + + /** + * 创建统一的初始化预检查项。 + * + * @param code 检查项代码 + * @param name 检查项名称 + * @param passed 是否通过 + * @param message 结果说明 + * @return 单项预检查结果 + */ + public static SystemInitializationCheckItemVO checkItem( + String code, + String name, + boolean passed, + String message) { + // 第一步:创建独立检查项返回对象。 + SystemInitializationCheckItemVO item = + new SystemInitializationCheckItemVO(); + + // 第二步:填写检查项标识、名称、结果和说明。 + item.setCode(code); + item.setName(name); + item.setPassed(Boolean.valueOf(passed)); + item.setMessage(message); + + // 第三步:返回完整检查项。 + return item; + } + + /** + * 判断全部预检查项是否通过。 + * + * @param items 预检查项 + * @return 全部通过时返回 true + */ + public static boolean allChecksPassed( + List items) { + // 第一步:逐项检查结果,任一项未通过时立即返回 false。 + for (SystemInitializationCheckItemVO item : items) { + if (!Boolean.TRUE.equals(item.getPassed())) { + return false; + } + } + + // 第二步:全部检查项均通过。 + return true; + } + + /** + * 使用数据库现有编号对整份历史基础数据执行写入前校验。 + * + * @param data 已完成 Excel 格式解析的数据 + * @param existingDepartmentIds 数据库已有部别编号 + * @param existingResearchOfficeIds 数据库已有教研室编号 + * @param existingPersonnelIds 数据库已有人员编号 + * @return 已完成冲突、引用校验和部别层级排序的数据 + */ + public static SystemInitializationHistoricalDataDTO + prepareHistoricalData( + SystemInitializationHistoricalDataDTO data, + List existingDepartmentIds, + List existingResearchOfficeIds, + List existingPersonnelIds) { + // 第一步:防止业务层绕过 Excel 解析直接传入空历史数据。 + if (data == null) { + throw new BusinessException( + BAD_REQUEST, + "历史基础数据不能为空"); + } + + // 第二步:把数据库查询结果转换为支持常量时间查找的编号集合。 + Set departmentIds = + toIdentifierSet(existingDepartmentIds); + Set researchOfficeIds = + toIdentifierSet(existingResearchOfficeIds); + Set personnelIds = + toIdentifierSet(existingPersonnelIds); + + // 第三步:禁止导入文件中的部别编号覆盖数据库已有部别。 + requireNoExistingIdentifiers( + data.getDepartments(), + departmentIds, + "部别", + new IdentifierReader< + SystemInitializationDepartmentImportRowDTO>() { + @Override + public String read( + SystemInitializationDepartmentImportRowDTO row) { + return row.getId(); + } + }); + + // 第四步:按照上级部别先于下级部别的顺序重排待导入部别。 + List + sortedDepartments = + sortDepartmentsByHierarchy( + data.getDepartments(), + departmentIds); + data.setDepartments(sortedDepartments); + + // 第五步:把文件内待导入部别加入可引用编号集合。 + for (SystemInitializationDepartmentImportRowDTO row + : sortedDepartments) { + departmentIds.add(row.getId()); + } + + // 第六步:禁止导入文件中的教研室编号覆盖数据库已有教研室。 + requireNoExistingIdentifiers( + data.getResearchOffices(), + researchOfficeIds, + "教研室", + new IdentifierReader< + SystemInitializationResearchOfficeImportRowDTO>() { + @Override + public String read( + SystemInitializationResearchOfficeImportRowDTO + row) { + return row.getId(); + } + }); + + // 第七步:逐条校验教研室所属部别已经存在或包含在本次导入文件中。 + for (SystemInitializationResearchOfficeImportRowDTO row + : data.getResearchOffices()) { + if (!departmentIds.contains(row.getDepartmentId())) { + throw new BusinessException( + NOT_FOUND, + "教研室“" + row.getName() + + "”所属部别不存在"); + } + } + + // 第八步:禁止导入文件中的人员编号覆盖数据库已有部别人员。 + requireNoExistingIdentifiers( + data.getPersonnel(), + personnelIds, + "部别人员", + new IdentifierReader< + SystemInitializationPersonnelImportRowDTO>() { + @Override + public String read( + SystemInitializationPersonnelImportRowDTO row) { + return row.getId(); + } + }); + + // 第九步:逐条校验部别人员所属部别已经存在或包含在本次导入文件中。 + for (SystemInitializationPersonnelImportRowDTO row + : data.getPersonnel()) { + if (!departmentIds.contains(row.getDepartmentId())) { + throw new BusinessException( + NOT_FOUND, + "部别人员“" + row.getName() + + "”所属部别不存在"); + } + } + + // 第十步:返回可以在同一事务中直接写入的数据。 + return data; + } + + /** + * 组装完整的系统初始化预检查结果。 + * + * @param databaseHealth 数据库健康检查固定返回值 + * @param configuration 当前配置 + * @param semester 当前学期 + * @param departmentCount 部别数量 + * @param researchOfficeCount 教研室数量 + * @param personnelCount 部别人员数量 + * @param brokenReferenceCount 失效引用数量 + * @param checkedAt 检查时间 + * @return 初始化预检查汇总 + */ + public static SystemInitializationPrecheckVO buildPrecheck( + int databaseHealth, + SystemInitializationConfigurationVO configuration, + SemesterInitializationVO semester, + int departmentCount, + int researchOfficeCount, + int personnelCount, + int brokenReferenceCount, + LocalDateTime checkedAt) { + // 第一步:创建预检查结果并记录前端最终确认所需的配置和学期快照。 + SystemInitializationPrecheckVO result = + new SystemInitializationPrecheckVO(); + result.setConfigurationVersionId( + configuration == null + ? null + : configuration.getVersionId()); + result.setCurrentSemester( + completeSemesterName(semester)); + + // 第二步:记录三类基础组织数据数量,便于页面定位未初始化数据。 + result.setDepartmentCount( + Integer.valueOf(departmentCount)); + result.setResearchOfficeCount( + Integer.valueOf(researchOfficeCount)); + result.setPersonnelCount( + Integer.valueOf(personnelCount)); + + // 第三步:验证数据库连接和最小 SQL 执行能力。 + result.getItems().add(checkItem( + "DATABASE", + "数据库连接", + databaseHealth == 1, + databaseHealth == 1 + ? "数据库连接正常" + : "数据库健康检查未通过")); + + // 第四步:验证当前配置存在且页面必需字段完整。 + boolean configurationReady = + isConfigurationComplete(configuration); + result.getItems().add(checkItem( + "CONFIGURATION", + "系统基础参数", + configurationReady, + configurationReady + ? "系统基础参数已配置" + : "系统基础参数未配置或必需字段不完整")); + + // 第五步:验证系统已经设置唯一可识别的当前学期。 + boolean semesterReady = semester != null; + result.getItems().add(checkItem( + "CURRENT_SEMESTER", + "当前学期", + semesterReady, + semesterReady + ? "当前学期已设置" + : "当前学期尚未设置")); + + // 第六步:验证部别、教研室和部别人员三类基础组织数据均已建立。 + boolean organizationReady = + departmentCount > 0 + && researchOfficeCount > 0 + && personnelCount > 0; + result.getItems().add(checkItem( + "ORGANIZATION_DATA", + "基础组织数据", + organizationReady, + "部别" + departmentCount + + "条,教研室" + researchOfficeCount + + "条,部别人员" + personnelCount + "条")); + + // 第七步:验证基础组织数据之间不存在悬空或失效引用。 + boolean referencesReady = brokenReferenceCount == 0; + result.getItems().add(checkItem( + "REFERENCE_INTEGRITY", + "基础数据引用", + referencesReady, + referencesReady + ? "基础数据引用完整" + : "发现" + brokenReferenceCount + + "条失效引用")); + + // 第八步:汇总全部检查项并记录本次检查时间。 + result.setReady(Boolean.valueOf( + allChecksPassed(result.getItems()))); + result.setCheckedAt(checkedAt); + + // 第九步:返回完整预检查结果。 + return result; + } + + /** + * 校验系统初始化最终执行确认参数。 + * + * @param dto 页面提交的确认参数 + * @param precheck 服务端重新生成的预检查结果 + */ + public static void validateExecution( + SystemInitializationExecutionDTO dto, + SystemInitializationPrecheckVO precheck) { + // 第一步:防止非 Controller 调用绕过请求对象非空校验。 + if (dto == null) { + throw new BusinessException( + BAD_REQUEST, + "系统初始化确认参数不能为空"); + } + + // 第二步:最终执行必须由操作人明确确认,不能仅依赖页面默认值。 + if (!Boolean.TRUE.equals(dto.getConfirmed())) { + throw new BusinessException( + BAD_REQUEST, + "请确认后再执行系统初始化"); + } + + /* + * 第三步:业务层可能被非 Controller 入口调用, + * 因此需要再次校验页面预检查快照中的三个关键字段。 + */ + if (dto.getExpectedConfigurationVersionId() == null + || dto.getSemesterYear() == null + || dto.getSemesterSequenceNumber() == null) { + throw new BusinessException( + BAD_REQUEST, + "系统初始化预检查快照不能为空"); + } + + // 第四步:服务端重新预检查未全部通过时禁止完成初始化。 + if (precheck == null + || !Boolean.TRUE.equals(precheck.getReady())) { + throw new BusinessException( + CONFLICT, + "系统初始化预检查未通过"); + } + + // 第五步:确认页面预检查后系统基础参数版本没有发生变化。 + if (!dto.getExpectedConfigurationVersionId().equals( + precheck.getConfigurationVersionId())) { + throw new BusinessException( + CONFLICT, + "系统基础参数已变化,请重新预检查"); + } + + // 第六步:确认页面预检查后当前学期没有发生切换。 + SemesterInitializationVO semester = + precheck.getCurrentSemester(); + if (semester == null + || !dto.getSemesterYear().equals( + semester.getYear()) + || !dto.getSemesterSequenceNumber().equals( + semester.getSequenceNumber())) { + throw new BusinessException( + CONFLICT, + "当前学期已变化,请重新预检查"); + } + } + + /** + * 组装历史基础数据导入结果。 + * + * @param data 实际写入的数据 + * @param importedAt 导入完成时间 + * @return 导入数量汇总 + */ + public static SystemInitializationHistoricalImportResultVO + buildHistoricalImportResult( + SystemInitializationHistoricalDataDTO data, + LocalDateTime importedAt) { + // 第一步:创建历史数据导入返回对象。 + SystemInitializationHistoricalImportResultVO result = + new SystemInitializationHistoricalImportResultVO(); + + // 第二步:写入文件名称和三类业务数据数量。 + result.setFileName(data.getFileName()); + result.setDepartmentCount(Integer.valueOf( + data.getDepartments().size())); + result.setResearchOfficeCount(Integer.valueOf( + data.getResearchOffices().size())); + result.setResearchOfficeAffiliationCount(Integer.valueOf( + data.getResearchOffices().size())); + result.setPersonnelCount(Integer.valueOf( + data.getPersonnel().size())); + + // 第三步:汇总四张表的实际新增记录总数。 + result.setTotalCount(Integer.valueOf( + data.getDepartments().size() + + data.getResearchOffices().size() * 2 + + data.getPersonnel().size())); + + // 第四步:记录统一事务完成时间并返回。 + result.setImportedAt(importedAt); + return result; + } + + /** + * 组装系统初始化最终执行结果。 + * + * @param precheck 已通过的服务端预检查结果 + * @param completedAt 完成时间 + * @return 初始化执行结果 + */ + public static SystemInitializationExecutionResultVO + buildExecutionResult( + SystemInitializationPrecheckVO precheck, + LocalDateTime completedAt) { + // 第一步:创建最终执行结果。 + SystemInitializationExecutionResultVO result = + new SystemInitializationExecutionResultVO(); + + // 第二步:记录最终确认使用的配置版本和当前学期。 + result.setCompleted(Boolean.TRUE); + result.setConfigurationVersionId( + precheck.getConfigurationVersionId()); + result.setSemesterName( + precheck.getCurrentSemester().getSemesterName()); + + // 第三步:记录完成时间和明确的执行结果说明。 + result.setCompletedAt(completedAt); + result.setMessage( + "系统基础参数、当前学期和基础组织数据检查通过"); + + // 第四步:返回初始化确认结果。 + return result; + } + + /** + * 校验系统初始化数据库写入影响行数。 + * + * @param actual 实际影响行数 + * @param expected 期望影响行数 + * @param message 影响行数不一致时的业务提示 + */ + public static void requireAffectedRows( + int actual, + int expected, + String message) { + // 实际影响行数与完整业务数据数量不一致时中止事务。 + if (actual != expected) { + throw new BusinessException(CONFLICT, message); + } + } + + /** + * 解析部别工作表。 + */ + private static List + parseDepartments(Sheet sheet) { + // 第一步:工作表不存在时表示本次不导入部别。 + if (sheet == null) { + return Collections.emptyList(); + } + + // 第二步:读取标题行并校验部别必需列。 + Map headers = readHeaders(sheet); + requireHeaders( + sheet, + headers, + "编号", + "名称", + "类别"); + + // 第三步:创建结果和文件内编号去重集合。 + List result = + new ArrayList<>(); + Set ids = new HashSet<>(); + + // 第四步:逐行读取非空部别数据。 + for (int rowIndex = sheet.getFirstRowNum() + 1; + rowIndex <= sheet.getLastRowNum(); + rowIndex++) { + Row row = sheet.getRow(rowIndex); + if (isEmptyRow(row)) { + continue; + } + + // 第五步:规范化当前部别的必填和可选字段。 + SystemInitializationDepartmentImportRowDTO item = + new SystemInitializationDepartmentImportRowDTO(); + item.setId(requireImportText( + read(row, headers, "编号"), + sheet.getSheetName(), + rowIndex, + "编号", + 36)); + item.setName(requireImportText( + read(row, headers, "名称"), + sheet.getSheetName(), + rowIndex, + "名称", + 50)); + item.setCategory(requireImportText( + read(row, headers, "类别"), + sheet.getSheetName(), + rowIndex, + "类别", + 50)); + item.setClassificationCode(defaultText( + optionalImportText( + read(row, headers, "分类号"), + sheet.getSheetName(), + rowIndex, + "分类号", + 50), + "0")); + item.setLevelNumber(parseInteger( + read(row, headers, "分级号"), + sheet.getSheetName(), + rowIndex, + "分级号", + 0, + Integer.MAX_VALUE, + Integer.valueOf(0))); + item.setParentId(optionalImportText( + read(row, headers, "上级编号"), + sheet.getSheetName(), + rowIndex, + "上级编号", + 36)); + item.setAbbreviation(optionalImportText( + read(row, headers, "简称"), + sheet.getSheetName(), + rowIndex, + "简称", + 500)); + item.setDisabled(parseBoolean( + read(row, headers, "停用"), + sheet.getSheetName(), + rowIndex, + "停用", + false)); + + // 第六步:文件内出现重复部别编号时立即返回具体行错误。 + if (!ids.add(item.getId())) { + throw importCellException( + sheet.getSheetName(), + rowIndex, + "编号", + "存在重复部别编号"); + } + + // 第七步:当前部别不能直接引用自身作为上级。 + if (item.getId().equals(item.getParentId())) { + throw importCellException( + sheet.getSheetName(), + rowIndex, + "上级编号", + "上级部别不能是当前部别"); + } + + // 第八步:加入完成规范化的部别导入集合。 + result.add(item); + } + + // 第九步:返回全部部别导入行。 + return result; + } + + /** + * 解析教研室工作表。 + */ + private static List + parseResearchOffices(Sheet sheet) { + // 第一步:工作表不存在时表示本次不导入教研室。 + if (sheet == null) { + return Collections.emptyList(); + } + + // 第二步:读取标题行并校验教研室必需列。 + Map headers = readHeaders(sheet); + requireHeaders( + sheet, + headers, + "教研室编号", + "名称", + "所属部别编号"); + + // 第三步:创建结果和文件内编号去重集合。 + List result = + new ArrayList<>(); + Set ids = new HashSet<>(); + + // 第四步:逐行读取非空教研室数据。 + for (int rowIndex = sheet.getFirstRowNum() + 1; + rowIndex <= sheet.getLastRowNum(); + rowIndex++) { + Row row = sheet.getRow(rowIndex); + if (isEmptyRow(row)) { + continue; + } + + // 第五步:规范化当前教研室的全部字段。 + SystemInitializationResearchOfficeImportRowDTO item = + new SystemInitializationResearchOfficeImportRowDTO(); + item.setId(requireImportText( + read(row, headers, "教研室编号"), + sheet.getSheetName(), + rowIndex, + "教研室编号", + 50)); + item.setName(requireImportText( + read(row, headers, "名称"), + sheet.getSheetName(), + rowIndex, + "名称", + 100)); + item.setDepartmentId(requireImportText( + read(row, headers, "所属部别编号"), + sheet.getSheetName(), + rowIndex, + "所属部别编号", + 36)); + item.setAbbreviation(optionalImportText( + read(row, headers, "简称"), + sheet.getSheetName(), + rowIndex, + "简称", + 100)); + item.setSequenceNumber(optionalImportText( + read(row, headers, "序号"), + sheet.getSheetName(), + rowIndex, + "序号", + 50)); + item.setRemark(optionalImportText( + read(row, headers, "备注"), + sheet.getSheetName(), + rowIndex, + "备注", + 500)); + item.setVirtual(parseBoolean( + read(row, headers, "虚拟"), + sheet.getSheetName(), + rowIndex, + "虚拟", + false)); + item.setOrganNature(parseBoolean( + read(row, headers, "机关性质"), + sheet.getSheetName(), + rowIndex, + "机关性质", + false)); + item.setDisabled(parseBoolean( + read(row, headers, "停用"), + sheet.getSheetName(), + rowIndex, + "停用", + false)); + item.setDepartmentText(optionalImportText( + read(row, headers, "部系"), + sheet.getSheetName(), + rowIndex, + "部系", + 100)); + + // 第六步:文件内教研室编号必须保持唯一。 + if (!ids.add(item.getId())) { + throw importCellException( + sheet.getSheetName(), + rowIndex, + "教研室编号", + "存在重复教研室编号"); + } + + // 第七步:加入完成规范化的教研室导入集合。 + result.add(item); + } + + // 第八步:返回全部教研室导入行。 + return result; + } + + /** + * 解析部别人员工作表。 + */ + private static List + parsePersonnel(Sheet sheet) { + // 第一步:工作表不存在时表示本次不导入部别人员。 + if (sheet == null) { + return Collections.emptyList(); + } + + // 第二步:读取标题行并校验人员必需列。 + Map headers = readHeaders(sheet); + requireHeaders( + sheet, + headers, + "人员编号", + "所属部别编号", + "姓名"); + + // 第三步:创建结果和文件内编号去重集合。 + List result = + new ArrayList<>(); + Set ids = new HashSet<>(); + + // 第四步:逐行读取非空人员数据。 + for (int rowIndex = sheet.getFirstRowNum() + 1; + rowIndex <= sheet.getLastRowNum(); + rowIndex++) { + Row row = sheet.getRow(rowIndex); + if (isEmptyRow(row)) { + continue; + } + + // 第五步:规范化当前人员的全部字段。 + SystemInitializationPersonnelImportRowDTO item = + new SystemInitializationPersonnelImportRowDTO(); + item.setId(requireImportText( + read(row, headers, "人员编号"), + sheet.getSheetName(), + rowIndex, + "人员编号", + 36)); + item.setDepartmentId(requireImportText( + read(row, headers, "所属部别编号"), + sheet.getSheetName(), + rowIndex, + "所属部别编号", + 36)); + item.setName(requireImportText( + read(row, headers, "姓名"), + sheet.getSheetName(), + rowIndex, + "姓名", + 50)); + item.setSequenceNumber(optionalImportText( + read(row, headers, "序号"), + sheet.getSheetName(), + rowIndex, + "序号", + 50)); + item.setPosition(optionalImportText( + read(row, headers, "职务"), + sheet.getSheetName(), + rowIndex, + "职务", + 50)); + item.setCardNumber(optionalImportText( + read(row, headers, "卡号"), + sheet.getSheetName(), + rowIndex, + "卡号", + 50)); + item.setPinyin(optionalImportText( + read(row, headers, "拼音"), + sheet.getSheetName(), + rowIndex, + "拼音", + 50)); + item.setPrincipal(parseBoolean( + read(row, headers, "主官"), + sheet.getSheetName(), + rowIndex, + "主官", + false)); + item.setDeparted(parseBoolean( + read(row, headers, "离职"), + sheet.getSheetName(), + rowIndex, + "离职", + false)); + + // 第六步:文件内人员编号必须保持唯一。 + if (!ids.add(item.getId())) { + throw importCellException( + sheet.getSheetName(), + rowIndex, + "人员编号", + "存在重复人员编号"); + } + + // 第七步:加入完成规范化的人员导入集合。 + result.add(item); + } + + // 第八步:返回全部人员导入行。 + return result; + } + + /** + * 读取工作表标题行。 + */ + private static Map readHeaders(Sheet sheet) { + // 第一步:读取工作表第一行作为标题行。 + Row headerRow = sheet.getRow(sheet.getFirstRowNum()); + if (headerRow == null) { + throw new BusinessException( + BAD_REQUEST, + "工作表“" + sheet.getSheetName() + + "”缺少标题行"); + } + + // 第二步:按标题文本建立列下标索引。 + Map headers = new HashMap<>(); + for (Cell cell : headerRow) { + String header = normalizeText( + DATA_FORMATTER.formatCellValue(cell)); + if (header != null) { + headers.put(header, Integer.valueOf( + cell.getColumnIndex())); + } + } + + // 第三步:返回标题索引。 + return headers; + } + + /** + * 校验工作表包含指定必需标题。 + */ + private static void requireHeaders( + Sheet sheet, + Map headers, + String... requiredHeaders) { + // 逐个检查必需标题,发现缺失时返回工作表和列名。 + for (String requiredHeader : requiredHeaders) { + if (!headers.containsKey(requiredHeader)) { + throw new BusinessException( + BAD_REQUEST, + "工作表“" + sheet.getSheetName() + + "”缺少“" + requiredHeader + + "”列"); + } + } + } + + /** + * 读取指定标题对应的单元格文本。 + */ + private static String read( + Row row, + Map headers, + String header) { + // 第一步:可选标题不存在时直接返回 null。 + Integer columnIndex = headers.get(header); + if (columnIndex == null || row == null) { + return null; + } + + // 第二步:读取目标单元格;空单元格统一返回 null。 + Cell cell = row.getCell( + columnIndex.intValue(), + Row.MissingCellPolicy.RETURN_BLANK_AS_NULL); + if (cell == null) { + return null; + } + + // 第三步:历史导入不执行不可信公式,要求上传文件使用固定值。 + if (cell.getCellType() == CellType.FORMULA) { + throw new BusinessException( + BAD_REQUEST, + "工作表“" + row.getSheet().getSheetName() + + "”第" + (row.getRowNum() + 1) + + "行“" + header + + "”不能使用公式"); + } + + // 第四步:使用统一格式化器读取数字、日期、布尔和文本显示值。 + return normalizeText( + DATA_FORMATTER.formatCellValue(cell)); + } + + /** + * 判断 Excel 行是否没有任何有效内容。 + */ + private static boolean isEmptyRow(Row row) { + // 行对象不存在时直接视为空行。 + if (row == null) { + return true; + } + + // 任一单元格存在有效显示文本时,该行不是空行。 + for (Cell cell : row) { + if (normalizeText( + DATA_FORMATTER.formatCellValue(cell)) != null) { + return false; + } + } + + // 全部单元格均为空。 + return true; + } + + /** + * 解析 Excel 整数字段。 + */ + private static Integer parseInteger( + String value, + String sheetName, + int rowIndex, + String fieldName, + int min, + int max, + Integer defaultValue) { + // 第一步:空值使用调用方提供的默认值。 + if (value == null) { + return defaultValue; + } + + // 第二步:将 Excel 显示文本转换为整数。 + try { + int parsed = Integer.parseInt(value); + if (parsed < min || parsed > max) { + throw importCellException( + sheetName, + rowIndex, + fieldName, + "数值超出允许范围"); + } + return Integer.valueOf(parsed); + } catch (NumberFormatException exception) { + // 第三步:非整数文本返回具体工作表、行号和字段。 + throw importCellException( + sheetName, + rowIndex, + fieldName, + "必须为整数"); + } + } + + /** + * 解析 Excel 布尔字段。 + */ + private static Boolean parseBoolean( + String value, + String sheetName, + int rowIndex, + String fieldName, + boolean defaultValue) { + // 第一步:空值使用当前字段定义的默认值。 + if (value == null) { + return Boolean.valueOf(defaultValue); + } + + // 第二步:兼容中文、英文和数字形式的真值。 + String normalized = value.toLowerCase(Locale.ROOT); + if ("是".equals(normalized) + || "true".equals(normalized) + || "1".equals(normalized) + || "启用".equals(normalized)) { + return Boolean.TRUE; + } + + // 第三步:兼容中文、英文和数字形式的假值。 + if ("否".equals(normalized) + || "false".equals(normalized) + || "0".equals(normalized) + || "停用".equals(normalized)) { + return Boolean.FALSE; + } + + // 第四步:无法识别的文本返回具体字段错误。 + throw importCellException( + sheetName, + rowIndex, + fieldName, + "只能填写是/否、true/false或1/0"); + } + + /** + * 校验 Excel 必填文本。 + */ + private static String requireImportText( + String value, + String sheetName, + int rowIndex, + String fieldName, + int maxLength) { + // 第一步:规范化文本并检查必填内容。 + String normalized = normalizeText(value); + if (normalized == null) { + throw importCellException( + sheetName, + rowIndex, + fieldName, + "不能为空"); + } + + // 第二步:检查数据库字段允许的最大长度。 + if (normalized.length() > maxLength) { + throw importCellException( + sheetName, + rowIndex, + fieldName, + "不能超过" + maxLength + "个字符"); + } + + // 第三步:返回通过校验的文本。 + return normalized; + } + + /** + * 校验 Excel 可选文本。 + */ + private static String optionalImportText( + String value, + String sheetName, + int rowIndex, + String fieldName, + int maxLength) { + // 第一步:规范化可选文本。 + String normalized = normalizeText(value); + + // 第二步:非空文本仍需执行最大长度校验。 + if (normalized != null + && normalized.length() > maxLength) { + throw importCellException( + sheetName, + rowIndex, + fieldName, + "不能超过" + maxLength + "个字符"); + } + + // 第三步:返回规范化后的可选文本。 + return normalized; + } + + /** + * 创建包含 Excel 单元格位置的业务异常。 + */ + private static BusinessException importCellException( + String sheetName, + int rowIndex, + String fieldName, + String message) { + // 将零下标行号转换为 Excel 页面使用的一下标行号。 + return new BusinessException( + BAD_REQUEST, + "工作表“" + sheetName + + "”第" + (rowIndex + 1) + + "行“" + fieldName + + "”" + message); + } + + /** + * 校验普通必填文本。 + */ + private static String requireText( + String value, + String fieldName, + int maxLength) { + // 第一步:规范化输入文本。 + String normalized = normalizeText(value); + + // 第二步:没有有效内容时返回参数错误。 + if (normalized == null) { + throw new BusinessException( + BAD_REQUEST, + fieldName + "不能为空"); + } + + // 第三步:校验最大长度。 + if (normalized.length() > maxLength) { + throw new BusinessException( + BAD_REQUEST, + fieldName + "不能超过" + + maxLength + "个字符"); + } + + // 第四步:返回规范化文本。 + return normalized; + } + + /** + * 校验普通可选文本。 + */ + private static String optionalText( + String value, + int maxLength, + String fieldName) { + // 第一步:规范化可选文本。 + String normalized = normalizeText(value); + + // 第二步:非空值执行最大长度校验。 + if (normalized != null + && normalized.length() > maxLength) { + throw new BusinessException( + BAD_REQUEST, + fieldName + "不能超过" + + maxLength + "个字符"); + } + + // 第三步:返回规范化结果。 + return normalized; + } + + /** + * 规范化上传文件名。 + */ + private static String normalizeFileName(String fileName) { + // 第一步:剥离浏览器可能上传的客户端路径,仅保留文件名。 + String normalized = normalizeText(fileName); + if (normalized == null) { + throw new BusinessException( + BAD_REQUEST, + "历史数据文件名称不能为空"); + } + normalized = normalized.replace('\\', '/'); + int lastSeparator = normalized.lastIndexOf('/'); + if (lastSeparator >= 0) { + normalized = normalized.substring(lastSeparator + 1); + } + + // 第二步:限制文件名长度,避免日志和响应中出现异常超长文本。 + if (normalized.length() > 255) { + throw new BusinessException( + BAD_REQUEST, + "历史数据文件名称不能超过255个字符"); + } + + // 第三步:返回安全文件名。 + return normalized; + } + + /** + * 规范化可选文本。 + */ + private static String normalizeText(String value) { + // 原始值为空时直接返回 null。 + if (value == null) { + return null; + } + + // 去除首尾空白,并把空字符串统一转换为 null。 + String normalized = value.trim(); + return normalized.isEmpty() ? null : normalized; + } + + /** + * 为空布尔值提供默认值。 + */ + private static Boolean defaultBoolean( + Boolean value, + boolean defaultValue) { + // 有明确值时保留原值,否则返回调用方指定的默认值。 + return value == null + ? Boolean.valueOf(defaultValue) + : value; + } + + /** + * 为空文本提供默认值。 + */ + private static String defaultText( + String value, + String defaultValue) { + // 有有效文本时保留原值,否则使用默认值。 + return value == null ? defaultValue : value; + } + + /** + * 判断当前系统基础参数是否满足初始化必需字段要求。 + */ + private static boolean isConfigurationComplete( + SystemInitializationConfigurationVO configuration) { + // 第一步:配置记录和版本编号均必须存在。 + if (configuration == null + || configuration.getVersionId() == null) { + return false; + } + + // 第二步:检查全部页面必需文本字段具有有效内容。 + if (normalizeText(configuration.getUniversityName()) == null + || normalizeText(configuration.getUniversityCode()) == null + || normalizeText( + configuration.getDefaultSystemName()) == null + || normalizeText(configuration.getSystemMode()) == null + || normalizeText( + configuration.getSemesterNameStyle()) == null + || normalizeText(configuration.getPeriodCategory()) == null + || normalizeText( + configuration + .getExternalFileStorageRootPath()) == null + || normalizeText( + configuration.getLocalFileAccessPath()) == null) { + return false; + } + + // 第三步:学员队名称规则必须具有明确布尔值。 + return configuration + .getStudentTeamFullNameIncludesGrade() != null; + } + + /** + * 将数据库编号列表转换为去空值集合。 + */ + private static Set toIdentifierSet( + List identifiers) { + // 第一步:创建独立集合,避免修改 Mapper 返回的原始列表。 + Set result = new HashSet<>(); + + // 第二步:空列表直接返回空集合。 + if (identifiers == null) { + return result; + } + + // 第三步:只保留具有有效内容的编号。 + for (String identifier : identifiers) { + String normalized = normalizeText(identifier); + if (normalized != null) { + result.add(normalized); + } + } + + // 第四步:返回用于常量时间查找的编号集合。 + return result; + } + + /** + * 校验待导入编号未出现在数据库现有编号集合中。 + */ + private static void requireNoExistingIdentifiers( + List rows, + Set existingIds, + String dataName, + IdentifierReader identifierReader) { + // 第一步:逐条读取待导入业务编号。 + for (T row : rows) { + String identifier = identifierReader.read(row); + + // 第二步:发现数据库已存在相同编号时立即中止整份文件导入。 + if (existingIds.contains(identifier)) { + throw new BusinessException( + CONFLICT, + dataName + "编号“" + identifier + + "”已存在"); + } + } + } + + /** + * 历史导入行编号读取器。 + * + * @param 历史导入行类型 + */ + private interface IdentifierReader { + + /** + * 读取一行数据的唯一编号。 + * + * @param row 历史导入行 + * @return 唯一编号 + */ + String read(T row); + } + + /** + * 私有构造方法,防止工具类被实例化。 + */ + private SystemInitializationUtil() { + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/UuidUtil.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/UuidUtil.java new file mode 100644 index 000000000..b36195a01 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/utils/UuidUtil.java @@ -0,0 +1,40 @@ +package com.roomroot.jwgl.utils; + +import java.util.UUID; + +/** + * UUID生成工具类 + */ +public class UuidUtil { + + /** + * 原生UUID 带横线 36位 + * 示例:550e8400-e29b-41d4-a716-446655440000 + */ + public static String getOriginalUUID() { + return UUID.randomUUID().toString(); + } + + /** + * 去掉横线,小写 32位(最常用) + * 示例:550e8400e29b41d4a716446655440000 + */ + public static String getUUID() { + return UUID.randomUUID().toString().replace("-", ""); + } + + /** + * 去掉横线,大写 32位 + */ + public static String getUpperUUID() { + return UUID.randomUUID().toString().replace("-", "").toUpperCase(); + } + + /** + * 自定义前缀 + UUID + * @param prefix 前缀 + */ + public static String getUuidWithPrefix(String prefix) { + return prefix + getUUID(); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/validator/StudentLeaveValidator.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/validator/StudentLeaveValidator.java new file mode 100644 index 000000000..e686533b8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/validator/StudentLeaveValidator.java @@ -0,0 +1,254 @@ +package com.roomroot.jwgl.validator; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.roomroot.jwgl.dto.courserunning.StudentLeaveApplyDTO; +import com.roomroot.jwgl.dto.courserunning.StudentLeaveAuditDTO; +import com.roomroot.jwgl.entity.JYB; +import com.roomroot.jwgl.entity.QJXYB; +import com.roomroot.jwgl.entity.SSJGCM; +import com.roomroot.jwgl.entity.XYJXQJB; +import com.roomroot.jwgl.mapper.JYBMapper; +import com.roomroot.jwgl.mapper.QJXYBMapper; +import com.roomroot.jwgl.mapper.SSJGCMMapper; +import com.roomroot.jwgl.service.DictDataService; +import com.roomroot.jwgl.unit.BusinessException; +import com.roomroot.jwgl.utils.BasicManagementConstants; +import com.roomroot.jwgl.vo.dict.DictDataVO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; + +/** + * 学员正课请假参数与权限校验器。 + *

+ * 集中承载学员请假申请、审批、撤回三类操作的参数合法性、业务前置状态和审批权限校验, + * 使 Service 层只保留核心业务逻辑。校验失败统一抛出 {@link BusinessException}。 + *

+ *

+ * 审批权限判定遵循标书"教员及以上"语义:审批人编号在 教员表(JYB) 或 实施_机关参谋表(SSJGCM) + * 中任一表存在即放行,覆盖普通教员和机关管理人员两类自然人身份。 + *

+ */ +@Component +public class StudentLeaveValidator { + + /** 请假类型字典类型编码,与 sys_dict_data 中 dict_type_code 对应。 */ + private static final String DICT_TYPE_LEAVE_TYPE = "edu_leave_type"; + + @Resource + private JYBMapper jybMapper; + + @Resource + private SSJGCMMapper ssjgcmMapper; + + @Resource + private QJXYBMapper qjxybMapper; + + @Resource + private DictDataService dictDataService; + + // ==================== 申请参数校验 ==================== + + /** + * 校验学员请假申请参数。 + *

+ * 包含非空校验、时间合法性校验、请假类型字典值有效性校验,并执行时间冲突检测。 + *

+ * + * @param dto 请假申请参数 + */ + public void validateApply(StudentLeaveApplyDTO dto) { + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "请假申请参数不能为空"); + } + if (dto.getXybh() == null || dto.getXybh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "学员编号不能为空"); + } + if (dto.getXydbh() == null || dto.getXydbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "学员队编号不能为空"); + } + if (dto.getYy() == null || dto.getYy().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "请假原因不能为空"); + } + if (dto.getQssj() == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "开始时间不能为空"); + } + if (dto.getJssj() == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "结束时间不能为空"); + } + // 开始时间必须早于结束时间 + if (!dto.getQssj().isBefore(dto.getJssj())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "开始时间必须早于结束时间"); + } + // 不允许补请假 + if (dto.getQssj().isBefore(LocalDateTime.now())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "请假开始时间不能早于当前时间,不允许补请假"); + } + // 请假类型字典值有效性 + if (dto.getQjlx() != null && !dto.getQjlx().trim().isEmpty()) { + Map leaveTypeLabelMap = buildLeaveTypeLabelMap(); + if (!leaveTypeLabelMap.containsKey(dto.getQjlx())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, + "请假类型值无效,有效值:" + leaveTypeLabelMap.keySet()); + } + } + } + + // ==================== 审批校验 ==================== + + /** + * 校验审批参数及审批人权限。 + *

+ * 依次校验:参数非空 → 审批结果合法 → 审批人教员及以上身份 → 审批人非请假学员本人。 + *

+ * + * @param dto 审批参数 + */ + public void validateAudit(StudentLeaveAuditDTO dto) { + if (dto == null) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批参数不能为空"); + } + if (dto.getQjbh() == null || dto.getQjbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "请假编号不能为空"); + } + if (dto.getSpzt() == null + || (dto.getSpzt() != 1 && dto.getSpzt() != 2 && dto.getSpzt() != 3)) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, + "审批结果不正确,应为1(同意)、2(拒绝)或3(发回修改)"); + } + if (dto.getSprbh() == null || dto.getSprbh().trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "审批人编号不能为空"); + } + } + + /** + * 断言审批人具备"教员及以上"审批权限。 + *

+ * 审批人编号在 教员表(JYB) 或 实施_机关参谋表(SSJGCM) 中任一表存在即通过; + * 同时校验审批人不能是本次请假的学员本人。 + *

+ * + * @param sprbh 审批人编号 + * @param qjbh 请假编号(用于排除学员本人) + */ + public void assertApprovalAuthority(String sprbh, String qjbh) { + // 1. 教员及以上身份:JYB ∪ SSJGCM 任一命中即放行 + long teacherCount = jybMapper.selectCount( + new LambdaQueryWrapper().eq(JYB::getJybh, sprbh) + ); + if (teacherCount == 0) { + long deptPersonnelCount = ssjgcmMapper.selectCount( + new LambdaQueryWrapper().eq(SSJGCM::getBh, sprbh) + ); + if (deptPersonnelCount == 0) { + throw new BusinessException(BasicManagementConstants.FORBIDDEN, + "审批人不是教员或机关人员,无权审批学员请假申请"); + } + } + // 2. 审批人不能是请假学员本人 + List leaveStudents = qjxybMapper.selectList( + new LambdaQueryWrapper().eq(QJXYB::getQjbh, qjbh) + ); + for (QJXYB qjxyb : leaveStudents) { + if (sprbh.equals(qjxyb.getXybh())) { + throw new BusinessException(BasicManagementConstants.FORBIDDEN, + "审批人不能是请假学员本人,无权审批"); + } + } + } + + /** + * 返回请假关联学员列表(供审批日志使用,避免重复查询)。 + * + * @param qjbh 请假编号 + * @return 关联学员列表 + */ + public List getLeaveStudents(String qjbh) { + return qjxybMapper.selectList( + new LambdaQueryWrapper().eq(QJXYB::getQjbh, qjbh) + ); + } + + // ==================== 撤回校验 ==================== + + /** + * 校验撤回参数及学员身份。 + * + * @param qjbh 请假编号 + * @param xybh 学员编号 + */ + public void validateRevokeParam(String qjbh, String xybh) { + if (qjbh == null || qjbh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "请假编号不能为空"); + } + if (xybh == null || xybh.trim().isEmpty()) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, "学员编号不能为空"); + } + } + + /** + * 断言学员是请假申请的提交人。 + * + * @param qjbh 请假编号 + * @param xybh 学员编号 + */ + public void assertRevokeOwner(String qjbh, String xybh) { + long count = qjxybMapper.selectCount( + new LambdaQueryWrapper() + .eq(QJXYB::getQjbh, qjbh) + .eq(QJXYB::getXybh, xybh) + ); + if (count == 0) { + throw new BusinessException(BasicManagementConstants.FORBIDDEN, + "无权撤回该请假申请,您不是该申请的提交人"); + } + } + + // ==================== 通用状态校验 ==================== + + /** + * 断言请假申请存在。 + * + * @param leave 请假实体(已查询结果) + */ + public void assertLeaveExists(XYJXQJB leave) { + if (leave == null) { + throw new BusinessException(BasicManagementConstants.NOT_FOUND, "请假申请不存在"); + } + } + + /** + * 断言请假申请未审批(用于撤回和重复审批防护)。 + * + * @param leave 请假实体 + */ + public void assertNotAudited(XYJXQJB leave) { + if (!"0".equals(leave.getZt())) { + throw new BusinessException(BasicManagementConstants.BAD_REQUEST, + "该请假申请已审批,无需重复审批/不允许撤回"); + } + } + + // ==================== 内部辅助 ==================== + + /** + * 构建请假类型字典值 → 标签映射表。 + * + * @return 字典值与标签映射(非空) + */ + private Map buildLeaveTypeLabelMap() { + Map map = new java.util.HashMap<>(); + List dictList = dictDataService.listByType(DICT_TYPE_LEAVE_TYPE); + if (dictList != null) { + for (DictDataVO dict : dictList) { + if (dict.getDictValue() != null) { + map.put(dict.getDictValue(), dict.getDictLabel()); + } + } + } + return map; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginCaptchaVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginCaptchaVO.java new file mode 100644 index 000000000..103d56294 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginCaptchaVO.java @@ -0,0 +1,28 @@ +package com.roomroot.jwgl.vo.accountmanagement; + +import lombok.Data; + +/** + * 本地账户登录验证码生成结果。 + * + *

验证码答案不会返回页面,只返回验证码编号、PNG 图片数据和有效期; + * 页面登录时需要同时提交验证码编号和用户识别出的验证码内容。

+ */ +@Data +public class AccountLoginCaptchaVO { + + /** + * 当前验证码编号。 + */ + private String captchaId; + + /** + * 可直接赋给图片 src 属性的 PNG Base64 Data URL。 + */ + private String imageDataUrl; + + /** + * 验证码剩余有效秒数。 + */ + private Integer expiresInSeconds; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginHistoryVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginHistoryVO.java new file mode 100644 index 000000000..eaf94630c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginHistoryVO.java @@ -0,0 +1,37 @@ +package com.roomroot.jwgl.vo.accountmanagement; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 当前账户的一条成功登录历史记录。 + */ +@Data +public class AccountLoginHistoryVO { + + /** + * 登录记录编号。 + */ + private String id; + + /** + * 登录成功时间。 + */ + private LocalDateTime loginAt; + + /** + * 登录请求来源 IP 地址。 + */ + private String ipAddress; + + /** + * 客户端内网 IP;服务端无法可靠取得时为空。 + */ + private String intranetIpAddress; + + /** + * 本次登录过程的事务标识。 + */ + private String transactionId; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginInformationVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginInformationVO.java new file mode 100644 index 000000000..6d6182297 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginInformationVO.java @@ -0,0 +1,43 @@ +package com.roomroot.jwgl.vo.accountmanagement; + +import com.roomroot.jwgl.unit.PageResult; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 当前账户的登录信息和成功登录历史。 + */ +@Data +public class AccountLoginInformationVO { + + /** + * 当前登录信息编号。 + */ + private String accountId; + + /** + * 当前登录账号。 + */ + private String loginName; + + /** + * 当前用户姓名。 + */ + private String userName; + + /** + * 上次登录成功时间。 + */ + private LocalDateTime lastLoginAt; + + /** + * 本次登录成功时间。 + */ + private LocalDateTime currentLoginAt; + + /** + * 当前账户的成功登录历史分页结果。 + */ + private PageResult loginHistory; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginVO.java new file mode 100644 index 000000000..8f20821e2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountLoginVO.java @@ -0,0 +1,46 @@ +package com.roomroot.jwgl.vo.accountmanagement; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 本地账户密码登录结果。 + * + *

该对象只返回后续页面识别账户和角色所需的信息, + * 不返回密码、密码提示答案、登录失败统计或 Servlet Session 编号。

+ */ +@Data +public class AccountLoginVO { + + /** + * 登录信息编号。 + */ + private String accountId; + + /** + * 登录账号。 + */ + private String loginName; + + /** + * 用户姓名。 + */ + private String userName; + + /** + * 统一身份账号。 + */ + private String unifiedAccount; + + /** + * 账户是否启用。 + */ + private Boolean enabled; + + /** + * 账户已绑定的角色和数据范围。 + */ + private List roles = new ArrayList<>(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountMergeResultVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountMergeResultVO.java new file mode 100644 index 000000000..3d2525947 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountMergeResultVO.java @@ -0,0 +1,20 @@ +package com.roomroot.jwgl.vo.accountmanagement; + +import lombok.Data; + +/** + * 账户合并结果。 + */ +@Data +public class AccountMergeResultVO { + + /** + * 已停用并保留审计信息的源账户。 + */ + private AccountVO sourceAccount; + + /** + * 已接收源账户角色和数据范围的目标账户。 + */ + private AccountVO targetAccount; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountRoleOptionVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountRoleOptionVO.java new file mode 100644 index 000000000..fac1868eb --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountRoleOptionVO.java @@ -0,0 +1,50 @@ +package com.roomroot.jwgl.vo.accountmanagement; + +import lombok.Data; + +/** + * 账户角色配置可选身份。 + */ +@Data +public class AccountRoleOptionVO { + + /** + * 角色类型编码。 + */ + private String roleType; + + /** + * 角色类型名称。 + */ + private String roleName; + + /** + * 可绑定身份编号。 + */ + private String targetId; + + /** + * 可绑定身份名称。 + */ + private String targetName; + + /** + * 身份的数据范围说明。 + */ + private String dataScope; + + /** + * 身份当前是否可用于新配置。 + */ + private Boolean enabled; + + /** + * 当前绑定的账户编号;未绑定时为空。 + */ + private String boundAccountId; + + /** + * 当前绑定的登录账号;未绑定时为空。 + */ + private String boundLoginName; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountRoleTypeVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountRoleTypeVO.java new file mode 100644 index 000000000..f61ef2b12 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountRoleTypeVO.java @@ -0,0 +1,20 @@ +package com.roomroot.jwgl.vo.accountmanagement; + +import lombok.Data; + +/** + * 账户角色类型选项。 + */ +@Data +public class AccountRoleTypeVO { + + /** + * 角色类型编码。 + */ + private String code; + + /** + * 角色类型名称。 + */ + private String name; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountRoleVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountRoleVO.java new file mode 100644 index 000000000..712fdb0cc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountRoleVO.java @@ -0,0 +1,40 @@ +package com.roomroot.jwgl.vo.accountmanagement; + +import lombok.Data; + +/** + * 账户已绑定角色和数据范围。 + */ +@Data +public class AccountRoleVO { + + /** + * 角色类型编码。 + */ + private String roleType; + + /** + * 角色类型名称。 + */ + private String roleName; + + /** + * 角色对应身份编号。 + */ + private String targetId; + + /** + * 角色对应身份名称。 + */ + private String targetName; + + /** + * 身份所属部别、教研室、班次等数据范围说明。 + */ + private String dataScope; + + /** + * 角色身份当前是否仍处于可用状态。 + */ + private Boolean enabled; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountSsoSessionVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountSsoSessionVO.java new file mode 100644 index 000000000..912900e25 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountSsoSessionVO.java @@ -0,0 +1,82 @@ +package com.roomroot.jwgl.vo.accountmanagement; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 统一身份认证会话对应的本地账户信息。 + * + *

该对象不返回 SSO 会话标识、密码和密码提示答案, + * 只向账户管理页面提供后续核验所需的非敏感信息。

+ */ +@Data +public class AccountSsoSessionVO { + + /** + * SSO 会话所属应用标识。 + */ + private String appKey; + + /** + * SSO 会话中的登录标识。 + */ + private String loginId; + + /** + * SSO 会话中的用户标识。 + */ + private String userId; + + /** + * SSO 会话中的用户类型。 + */ + private String userType; + + /** + * SSO 会话记录的来源 IP。 + */ + private String ipAddress; + + /** + * SSO 会话失效时间。 + */ + private LocalDateTime invalidTime; + + /** + * SSO 会话创建时间。 + */ + private LocalDateTime createTime; + + /** + * 唯一解析到的本地登录信息编号。 + */ + private String accountId; + + /** + * 本地登录账号。 + */ + private String loginName; + + /** + * 本地用户姓名。 + */ + private String userName; + + /** + * 本地统一身份账号。 + */ + private String unifiedAccount; + + /** + * 本地账户是否启用。 + */ + private Boolean enabled; + + /** + * 本地账户已经绑定的全部角色和数据范围。 + */ + private List roles = new ArrayList<>(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountVO.java new file mode 100644 index 000000000..bd3dcc122 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/accountmanagement/AccountVO.java @@ -0,0 +1,104 @@ +package com.roomroot.jwgl.vo.accountmanagement; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 账户列表和详情展示数据。 + */ +@Data +public class AccountVO { + + /** + * 登录信息编号。 + */ + private String id; + + /** + * 登录账号。 + */ + private String loginName; + + /** + * 用户姓名。 + */ + private String userName; + + /** + * 统一身份账号。 + */ + private String unifiedAccount; + + /** + * 联系电话。 + */ + private String phone; + + /** + * 身份证号码。 + */ + private String idCard; + + /** + * 密码提示问题;密码和提示答案不向页面返回。 + */ + private String passwordQuestion; + + /** + * 自我介绍。 + */ + private String introduction; + + /** + * 上次登录时间。 + */ + private LocalDateTime lastLoginAt; + + /** + * 本次登录时间。 + */ + private LocalDateTime currentLoginAt; + + /** + * 连续登录失败次数。 + */ + private Integer failedAttempts; + + /** + * 最近一次登录失败时间。 + */ + private LocalDateTime failedAt; + + /** + * 账户是否启用。 + */ + private Boolean enabled; + + /** + * 停用原因。 + */ + private String disabledReason; + + /** + * 停用时间。 + */ + private LocalDateTime disabledAt; + + /** + * 合并后的目标账户编号;未发生合并时为空。 + */ + private String mergedToAccountId; + + /** + * 账户合并时间。 + */ + private LocalDateTime mergedAt; + + /** + * 账户已绑定的全部角色和数据范围。 + */ + private List roles = new ArrayList<>(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/AffiliationDepartmentVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/AffiliationDepartmentVO.java new file mode 100644 index 000000000..9136e8a53 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/AffiliationDepartmentVO.java @@ -0,0 +1,45 @@ +package com.roomroot.jwgl.vo.affiliationsetting; + +import lombok.Data; + +/** + * 隶属关系设置页面共享的可选部别信息。 + */ +@Data +public class AffiliationDepartmentVO { + + /** + * 部别唯一编号。 + */ + private String id; + + /** + * 部别名称。 + */ + private String name; + + /** + * 部别简称。 + */ + private String abbreviation; + + /** + * 机关类别。 + */ + private String category; + + /** + * 分类号。 + */ + private String classificationCode; + + /** + * 分级号。 + */ + private Integer levelNumber; + + /** + * 上级部别编号;根节点为 null。 + */ + private String parentId; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/PersonnelAffiliationVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/PersonnelAffiliationVO.java new file mode 100644 index 000000000..f3e2bee53 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/PersonnelAffiliationVO.java @@ -0,0 +1,31 @@ +package com.roomroot.jwgl.vo.affiliationsetting; + +import lombok.Data; + +/** + * 人员隶属关系展示VO。 + * + *

统一封装教员/学员/机关参谋三类人员的当前归属信息, + * 供前端列表展示使用。

+ */ +@Data +public class PersonnelAffiliationVO { + + /** 人员类型:TEACHER / STUDENT / DEPARTMENT_PERSONNEL */ + private String personnelType; + + /** 人员编号 */ + private String personnelId; + + /** 人员姓名 */ + private String personnelName; + + /** 当前归属编号(教研室代号 / 学员队期编号 / 实施_机关编号) */ + private String currentDepartmentId; + + /** 当前归属名称(教研室名称 / 学员队期名称 / 机关名称) */ + private String currentDepartmentName; + + /** 上一个归属编号(异动前归属,未异动过则为null) */ + private String previousDepartmentId; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/ResearchOfficeAffiliationVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/ResearchOfficeAffiliationVO.java new file mode 100644 index 000000000..f8b36a3c5 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/ResearchOfficeAffiliationVO.java @@ -0,0 +1,77 @@ +package com.roomroot.jwgl.vo.affiliationsetting; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 已建立的教研室隶属关系展示信息。 + */ +@Data +public class ResearchOfficeAffiliationVO { + + /** + * 隶属关系唯一编号。 + */ + private String id; + + /** + * 所属部别编号。 + */ + private String departmentId; + + /** + * 所属部别名称。 + */ + private String departmentName; + + /** + * 教研室标识号。 + */ + private String researchOfficeId; + + /** + * 教研室名称。 + */ + private String researchOfficeName; + + /** + * 教研室简称。 + */ + private String abbreviation; + + /** + * 教研室所属系别。 + */ + private String department; + + /** + * 是否启用,由数据库“停用”字段反向转换得到。 + */ + private Boolean enabled; + + /** + * 停用时间。 + */ + private LocalDateTime disabledAt; + + /** + * 是否为虚拟教研室。 + */ + private Boolean virtual; + + /** + * 教研室备注。 + */ + private String remark; + + /** + * 教研室显示序号。 + */ + private String sequenceNumber; + + /** + * 是否具有机关性质。 + */ + private Boolean organNature; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/ResearchOfficeVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/ResearchOfficeVO.java new file mode 100644 index 000000000..a1a7ac51b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/ResearchOfficeVO.java @@ -0,0 +1,62 @@ +package com.roomroot.jwgl.vo.affiliationsetting; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教研室基础展示信息。 + */ +@Data +public class ResearchOfficeVO { + + /** + * 教研室标识号。 + */ + private String id; + + /** + * 教研室名称。 + */ + private String name; + + /** + * 教研室简称。 + */ + private String abbreviation; + + /** + * 教研室所属系别。 + */ + private String department; + + /** + * 是否启用,由数据库“停用”字段反向转换得到。 + */ + private Boolean enabled; + + /** + * 停用时间。 + */ + private LocalDateTime disabledAt; + + /** + * 是否为虚拟教研室。 + */ + private Boolean virtual; + + /** + * 教研室备注。 + */ + private String remark; + + /** + * 教研室显示序号。 + */ + private String sequenceNumber; + + /** + * 是否具有机关性质。 + */ + private Boolean organNature; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/TeachingClassAffiliationVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/TeachingClassAffiliationVO.java new file mode 100644 index 000000000..daf58d14b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/TeachingClassAffiliationVO.java @@ -0,0 +1,125 @@ +package com.roomroot.jwgl.vo.affiliationsetting; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教学班次隶属关系及教学班次基础展示信息。 + * + *

查询未隶属教学班次时,部别编号和部别名称为 null; + * 查询已隶属教学班次时,这两个字段保存当前关系对应的部别信息。

+ */ +@Data +public class TeachingClassAffiliationVO { + + /** + * 所属部别编号。 + */ + private String departmentId; + + /** + * 所属部别名称。 + */ + private String departmentName; + + /** + * 教学班次编号。 + */ + private String teachingClassId; + + /** + * 教学班次名称。 + */ + private String teachingClassName; + + /** + * 教学班次简称。 + */ + private String abbreviation; + + /** + * 教学班次人数。 + */ + private Integer studentCount; + + /** + * 年级。 + */ + private String grade; + + /** + * 专业代号。 + */ + private String majorCode; + + /** + * 专业教室编号。 + */ + private String classroomId; + + /** + * 备注。 + */ + private String remark; + + /** + * 在校状态。 + */ + private String status; + + /** + * 显示序号。 + */ + private String sequenceNumber; + + /** + * 任务类别。 + */ + private String taskCategory; + + /** + * 虚实类型。 + */ + private Integer virtualType; + + /** + * 入学日期。 + */ + private LocalDateTime admissionDate; + + /** + * 毕业日期。 + */ + private LocalDateTime graduationDate; + + /** + * 教学班次类型。 + */ + private String teachingClassType; + + /** + * 培训任务标识号。 + */ + private String trainingTaskId; + + /** + * 节次类别。 + */ + private String sessionCategory; + + /** + * 系统模式。 + */ + private String systemMode; + + /** + * 所属单位。 + */ + private String organization; + + /** + * 是否为主体培训任务。 + */ + private Integer primaryTrainingTask; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/TransferHistoryVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/TransferHistoryVO.java new file mode 100644 index 000000000..155c5b9b1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/affiliationsetting/TransferHistoryVO.java @@ -0,0 +1,25 @@ +package com.roomroot.jwgl.vo.affiliationsetting; + +import lombok.Data; + +/** + * 人员异动历史展示VO。 + * + *

直接返回异动历史JSON原文,由前端解析展示。 + * JSON数组格式:[{from,to,time,reason}]

+ */ +@Data +public class TransferHistoryVO { + + /** 人员类型 */ + private String personnelType; + + /** 人员编号 */ + private String personnelId; + + /** 人员姓名 */ + private String personnelName; + + /** 异动历史JSON原文(可能为空) */ + private String transferHistory; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustRoomVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustRoomVO.java new file mode 100644 index 000000000..1a999f680 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustRoomVO.java @@ -0,0 +1,33 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +/** + * 调课申请-新教室展示数据。 + *

+ * 用于展示调课申请中调整后的教室信息。 + *

+ */ +@Data +public class AdjustRoomVO { + + /** + * 记录编号。 + */ + private String bh; + + /** + * 教室编号。 + */ + private String jsbh; + + /** + * 教室名称(关联教室表获取)。 + */ + private String jsmc; + + /** + * 备注。 + */ + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustSupportVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustSupportVO.java new file mode 100644 index 000000000..3317e5121 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustSupportVO.java @@ -0,0 +1,38 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +/** + * 调课申请-新保障明细展示数据。 + *

+ * 用于展示调课申请中调整后的教学保障提供明细。 + *

+ */ +@Data +public class AdjustSupportVO { + + /** + * 记录编号。 + */ + private String bh; + + /** + * 保障提供明细编号。 + */ + private String bztgmxbh; + + /** + * 保障提供明细名称(关联保障提供明细表获取)。 + */ + private String bztgmxmc; + + /** + * 数量。 + */ + private Integer sl; + + /** + * 备注。 + */ + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustTeacherVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustTeacherVO.java new file mode 100644 index 000000000..ba45c4bb7 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustTeacherVO.java @@ -0,0 +1,38 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +/** + * 调课申请-新辅助教员展示数据。 + *

+ * 用于展示调课申请中调整后的辅助教员信息。 + *

+ */ +@Data +public class AdjustTeacherVO { + + /** + * 记录编号。 + */ + private String bh; + + /** + * 辅助教员编号。 + */ + private String fzjybh; + + /** + * 辅助教员姓名(关联教员表获取)。 + */ + private String fzjyxm; + + /** + * 是否主教员:1-是,0-否。 + */ + private Integer zjy; + + /** + * 备注。 + */ + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustTeamVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustTeamVO.java new file mode 100644 index 000000000..3257cb3a3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/AdjustTeamVO.java @@ -0,0 +1,28 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +/** + * 调课申请-新学员队展示数据。 + *

+ * 用于展示调课申请中调整后的学员队信息。 + *

+ */ +@Data +public class AdjustTeamVO { + + /** + * 记录编号。 + */ + private String bh; + + /** + * 学员队编号。 + */ + private String xydbh; + + /** + * 学员队名称(关联学员队表获取)。 + */ + private String xydmc; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/CourseCompletionByDeptVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/CourseCompletionByDeptVO.java new file mode 100644 index 000000000..1f19ee7b3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/CourseCompletionByDeptVO.java @@ -0,0 +1,38 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +/** + * 各教学系排课完成率对比数据。 + *

+ * 用于柱状图展示各教学系的排课完成情况。 + *

+ */ +@Data +public class CourseCompletionByDeptVO { + + /** + * 教学系编号。 + */ + private String jxxbh; + + /** + * 教学系名称。 + */ + private String jxxmc; + + /** + * 排课完成率(百分比)。 + */ + private Double completionRate; + + /** + * 已排课次数。 + */ + private Integer completedCount; + + /** + * 总计划课次数。 + */ + private Integer totalCount; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/CourseRunningImportResultVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/CourseRunningImportResultVO.java new file mode 100644 index 000000000..8fc6b2e66 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/CourseRunningImportResultVO.java @@ -0,0 +1,65 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 教学运行数据导入结果。 + *

+ * 用于返回导入操作的执行结果,包含总数、成功数、失败数和失败列表。 + *

+ */ +@Data +public class CourseRunningImportResultVO { + + /** + * 待导入课程总数。 + */ + private Integer totalCount; + + /** + * 成功导入课程数。 + */ + private Integer successCount; + + /** + * 导入失败课程数。 + */ + private Integer failCount; + + /** + * 导入失败的课程编号列表。 + */ + private List failList = new ArrayList<>(); + + /** + * 导入结果提示信息。 + */ + private String message; + + /** + * 默认构造函数,初始化各字段为默认值。 + */ + public CourseRunningImportResultVO() { + this.totalCount = 0; + this.successCount = 0; + this.failCount = 0; + this.failList = new ArrayList<>(); + } + + /** + * 构造函数,根据总数、成功数和失败列表初始化。 + * + * @param totalCount 待导入课程总数 + * @param successCount 成功导入课程数 + * @param failList 导入失败的课程编号列表 + */ + public CourseRunningImportResultVO(Integer totalCount, Integer successCount, List failList) { + this.totalCount = totalCount; + this.successCount = successCount; + this.failCount = totalCount - successCount; + this.failList = failList != null ? failList : new ArrayList<>(); + } +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/OperationOverviewVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/OperationOverviewVO.java new file mode 100644 index 000000000..32564c1a1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/OperationOverviewVO.java @@ -0,0 +1,58 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +/** + * 运行分析概览指标。 + *

+ * 展示教学运行关键指标的汇总数据,包括排课完成率、学员缺勤率、场地使用率、教师周均课时等。 + *

+ */ +@Data +public class OperationOverviewVO { + + /** + * 整体排课完成率(百分比)。 + */ + private Double overallCourseCompletionRate; + + /** + * 学员缺勤率(百分比)。 + */ + private Double studentAbsenceRate; + + /** + * 场地平均使用率(百分比)。 + */ + private Double venueUsageRate; + + /** + * 教师周均课时(节)。 + */ + private Double avgWeeklyHoursPerTeacher; + + /** + * 当前学期调课总次数。 + */ + private Integer totalScheduleAdjustCount; + + /** + * 当前学期教学保障计划总数。 + */ + private Integer totalSupportPlanCount; + + /** + * 当前学期学员请假总次数。 + */ + private Integer totalLeaveCount; + + /** + * 当前学期待审批调课申请数。 + */ + private Integer pendingAdjustCount; + + /** + * 当前学期待审批请假申请数。 + */ + private Integer pendingLeaveCount; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustApplyVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustApplyVO.java new file mode 100644 index 000000000..2aa18ea18 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustApplyVO.java @@ -0,0 +1,96 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 调课申请列表项展示数据。 + *

+ * 用于展示调课申请的基本信息,包括课次信息、申请教员、申请时间、 + * 调课事由、审批状态和查收状态等关键信息。 + *

+ */ +@Data +public class ScheduleAdjustApplyVO { + + /** + * 调课申请编号。 + */ + private String ssdksqbh; + + /** + * 实施_课程表编号。 + */ + private String sskcbbh; + + /** + * 课程名称,关联课程表获取。 + */ + private String kcmc; + + /** + * 申请教员编号。 + */ + private String sqjybh; + + /** + * 申请教员姓名,关联教员表获取。 + */ + private String sqjyxm; + + /** + * 调课事由。 + */ + private String sy; + + /** + * 原日期节次描述(保存到备注字段中)。 + */ + private String ydsjap; + + /** + * 调课后新日期。 + */ + private LocalDateTime rq; + + /** + * 调课后新节次。 + */ + private Integer jc; + + /** + * 年度。 + */ + private Integer nd; + + /** + * 创建时间。 + */ + private LocalDateTime cjsj; + + /** + * 教研室审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝。 + */ + private Integer jyspzzt; + + /** + * 教研室查收状态:0-未查收,1-已查收。 + */ + private Integer jyscszt; + + /** + * 机关审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝。 + */ + private Integer jgspzt; + + /** + * 发回意见。 + */ + private String fhyj; + + /** + * 删除状态:0-正常,1-已删除。 + */ + private Integer sczt; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustAuditVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustAuditVO.java new file mode 100644 index 000000000..5c19db213 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustAuditVO.java @@ -0,0 +1,81 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 调课申请审批记录展示数据。 + *

+ * 用于展示调课申请中机关审批环节的审批记录,包括机关编号、审批时间、 + * 审批状态、审批人、查收状态、发回意见等信息。 + *

+ */ +@Data +public class ScheduleAdjustAuditVO { + + /** + * 审批记录编号。 + */ + private String bh; + + /** + * 调课申请编号。 + */ + private String ssdksqbh; + + /** + * 机关编号。 + */ + private String jgbh; + + /** + * 机关名称(关联机构表获取)。 + */ + private String jgmc; + + /** + * 审批时间。 + */ + private LocalDateTime spsj; + + /** + * 审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝。 + */ + private Integer spzt; + + /** + * 审批人编号。 + */ + private String sprbh; + + /** + * 审批人姓名(关联教员表获取)。 + */ + private String sprxm; + + /** + * 查收状态:0-未查收,1-已查收。 + */ + private Integer cszt; + + /** + * 查收时间。 + */ + private LocalDateTime cssj; + + /** + * 查收人编号。 + */ + private String csrbh; + + /** + * 发回意见。 + */ + private String fhyj; + + /** + * 年度。 + */ + private Integer nd; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustDetailVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustDetailVO.java new file mode 100644 index 000000000..e2cfec1cc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustDetailVO.java @@ -0,0 +1,158 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 调课申请详情展示数据。 + *

+ * 用于展示调课申请的完整信息,包括课次信息、申请教员、调整后的日期节次、 + * 教学内容变更、新的教室列表、辅助教员列表、学员队列表、保障明细列表, + * 以及教研室和机关的审批记录。 + *

+ */ +@Data +public class ScheduleAdjustDetailVO { + + /** + * 调课申请编号。 + */ + private String ssdksqbh; + + /** + * 实施_课程表编号。 + */ + private String sskcbbh; + + /** + * 课程名称。 + */ + private String kcmc; + + /** + * 申请教员编号。 + */ + private String sqjybh; + + /** + * 申请教员姓名。 + */ + private String sqjyxm; + + /** + * 调课事由。 + */ + private String sy; + + /** + * 原日期节次描述(保存到备注字段中)。 + */ + private String ydsjap; + + /** + * 调课后新日期。 + */ + private LocalDateTime rq; + + /** + * 调课后新节次。 + */ + private Integer jc; + + /** + * 年度。 + */ + private Integer nd; + + /** + * 调课后教学内容。 + */ + private String jxnr; + + /** + * 调课后教学要点。 + */ + private String jxyd; + + /** + * 调课后教学方法。 + */ + private String jxff; + + /** + * 教学保障备注。 + */ + private String jxbzbz; + + /** + * 用车信息。 + */ + private String ycxx; + + /** + * 备注。 + */ + private String bz; + + /** + * 创建时间。 + */ + private LocalDateTime cjsj; + + /** + * 教研室审批状态:0-未审批,1-已同意,2-已发回,3-已拒绝。 + */ + private Integer jyspzzt; + + /** + * 教研室审批时间。 + */ + private LocalDateTime jyspzsj; + + /** + * 教研室审批人编号。 + */ + private String jyspzrbh; + + /** + * 教研室查收状态:0-未查收,1-已查收。 + */ + private Integer jyscszt; + + /** + * 教研室查收时间。 + */ + private LocalDateTime jyscssj; + + /** + * 教研室查收人编号。 + */ + private String jyscsrbh; + + /** + * 机关审批列表,记录所有机关审批结果。 + */ + private List auditList; + + /** + * 新教室列表。 + */ + private List roomList; + + /** + * 新辅助教员列表。 + */ + private List teacherList; + + /** + * 新学员队列表。 + */ + private List teamList; + + /** + * 新保障明细列表。 + */ + private List supportList; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustStrategyVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustStrategyVO.java new file mode 100644 index 000000000..d035090d1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustStrategyVO.java @@ -0,0 +1,54 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 调课策略展示VO。 + */ +@Data +public class ScheduleAdjustStrategyVO { + + /** 策略编号 */ + private String clbh; + + /** 策略名称 */ + private String clmc; + + /** 策略类型:TIME_LIMIT=时间段限制,AUTO_APPROVE=免审批 */ + private String cllx; + + /** 策略类型描述 */ + private String cllxDesc; + + /** 是否启用:0=停用,1=启用 */ + private Integer sfqy; + + /** 是否启用描述 */ + private String sfqyDesc; + + /** 截止星期:0-6 */ + private Integer jzxq; + + /** 截止星期描述 */ + private String jzxqDesc; + + /** 截止节次 */ + private Integer jzjc; + + /** 生效开始时间 */ + private LocalDateTime sxkssj; + + /** 生效结束时间 */ + private LocalDateTime sxjssj; + + /** 备注 */ + private String bz; + + /** 创建时间 */ + private LocalDateTime cjsj; + + /** 更新时间 */ + private LocalDateTime gxsj; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustTrendVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustTrendVO.java new file mode 100644 index 000000000..26b481b51 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/ScheduleAdjustTrendVO.java @@ -0,0 +1,28 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +/** + * 学期调课频次趋势数据。 + *

+ * 用于折线图展示学期内各时间段的调课频次变化趋势。 + *

+ */ +@Data +public class ScheduleAdjustTrendVO { + + /** + * 时间段标识(如周次、月份)。 + */ + private String period; + + /** + * 时间段名称(如"第1周"、"1月")。 + */ + private String periodName; + + /** + * 调课次数。 + */ + private Integer adjustCount; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/StudentLeaveApplyVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/StudentLeaveApplyVO.java new file mode 100644 index 000000000..65806b253 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/StudentLeaveApplyVO.java @@ -0,0 +1,81 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学员请假申请列表VO。 + *

+ * 用于展示学员正课请假申请的基本信息。 + *

+ */ +@Data +public class StudentLeaveApplyVO { + + /** + * 请假编号。 + */ + private String qjbh; + + /** + * 学员编号。 + */ + private String xybh; + + /** + * 学员姓名。 + */ + private String xyxm; + + /** + * 学员队编号。 + */ + private String xydbh; + + /** + * 学员队名称。 + */ + private String xydmc; + + /** + * 请假原因。 + */ + private String yy; + + /** + * 请假类型(字典值:sick/personal/official/compensatory)。 + */ + private String qjlx; + + /** + * 请假类型名称(字典标签:病假/事假/公假/调休)。 + */ + private String qjlxLabel; + + /** + * 开始时间。 + */ + private LocalDateTime qssj; + + /** + * 结束时间。 + */ + private LocalDateTime jssj; + + /** + * 审批状态:0-待审批,1-已同意,2-已拒绝,3-已发回。 + */ + private Integer spzt; + + /** + * 审批状态名称。 + */ + private String spztmc; + + /** + * 创建时间。 + */ + private LocalDateTime cjsj; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/StudentLeaveDetailVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/StudentLeaveDetailVO.java new file mode 100644 index 000000000..ceeacc702 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/StudentLeaveDetailVO.java @@ -0,0 +1,143 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 学员请假申请详情VO。 + *

+ * 用于展示学员正课请假申请的完整信息,包括关联课次和审批记录。 + *

+ */ +@Data +public class StudentLeaveDetailVO { + + /** + * 请假编号。 + */ + private String qjbh; + + /** + * 学员编号。 + */ + private String xybh; + + /** + * 学员姓名。 + */ + private String xyxm; + + /** + * 学员队编号。 + */ + private String xydbh; + + /** + * 学员队名称。 + */ + private String xydmc; + + /** + * 请假原因。 + */ + private String yy; + + /** + * 请假类型(字典值:sick/personal/official/compensatory)。 + */ + private String qjlx; + + /** + * 请假类型名称(字典标签:病假/事假/公假/调休)。 + */ + private String qjlxLabel; + + /** + * 开始时间。 + */ + private LocalDateTime qssj; + + /** + * 结束时间。 + */ + private LocalDateTime jssj; + + /** + * 目的地。 + */ + private String mdd; + + /** + * 备注。 + */ + private String bz; + + /** + * 审批状态:0-待审批,1-已同意,2-已拒绝,3-已发回。 + */ + private Integer spzt; + + /** + * 审批状态名称。 + */ + private String spztmc; + + /** + * 审批人编号。 + */ + private String sprbh; + + /** + * 审批人姓名。 + */ + private String sprxm; + + /** + * 审批时间。 + */ + private LocalDateTime spsj; + + /** + * 审批意见。 + */ + private String spyj; + + /** + * 创建时间。 + */ + private LocalDateTime cjsj; + + /** + * 关联课次列表。 + */ + private List lessonList; +} + +/** + * 请假关联课次VO。 + */ +@Data +class LeaveLessonVO { + + /** + * 课次编号。 + */ + private String sskcbbh; + + /** + * 课程名称。 + */ + private String kcmc; + + /** + * 上课日期。 + */ + private LocalDateTime rq; + + /** + * 节次。 + */ + private Integer jc; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingPlanDetailVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingPlanDetailVO.java new file mode 100644 index 000000000..b27126704 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingPlanDetailVO.java @@ -0,0 +1,106 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教学实施计划详情展示数据。 + *

+ * 用于展示单个课次的完整教学实施计划信息,包括教学内容、教学要点、 + * 教学方法、日期节次、班组信息、保障需求及教员备注等全部填写内容。 + *

+ */ +@Data +public class TeachingPlanDetailVO { + + /** + * 实施_课程表编号,课次的唯一标识。 + */ + private String sskcbbh; + + /** + * 实施_课程编号,关联上级课程数据。 + */ + private String sskcbh; + + /** + * 课程名称。 + */ + private String kcmc; + + /** + * 课次序号。 + */ + private Integer kcxh; + + /** + * 年度。 + */ + private Integer nd; + + /** + * 教学内容,本节课的具体教学主题或知识点。 + */ + private String jxnr; + + /** + * 教学要点,本节课的重点和难点。 + */ + private String jxyd; + + /** + * 教学方法,如讲授、讨论、实操、案例教学等。 + */ + private String jxff; + + /** + * 上课日期。 + */ + private LocalDateTime rq; + + /** + * 节次。 + */ + private Integer jc; + + /** + * 班组模式,本节课的班组组织形式。 + */ + private String bzms; + + /** + * 班组划分信息,各班组的人员构成或分组情况。 + */ + private String bzhfxx; + + /** + * 用车信息,本节课需要的车辆保障需求。 + */ + private String ycxx; + + /** + * 教学保障备注,其他教学保障需求说明。 + */ + private String jxbzbz; + + /** + * 教员备注,授课教员的补充说明。 + */ + private String jybz; + + /** + * 填写状态:0-未填写,1-已填写,2-已提交。 + */ + private Integer txzt; + + /** + * 创建时间。 + */ + private LocalDateTime cjsj; + + /** + * 变动时间,最后一次修改时间。 + */ + private LocalDateTime bdsj; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingPlanFillResultVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingPlanFillResultVO.java new file mode 100644 index 000000000..a7aee0435 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingPlanFillResultVO.java @@ -0,0 +1,65 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 教学实施计划填写结果展示数据。 + *

+ * 用于返回批量填写操作的结果统计,包含总课次数、成功数、失败数及失败原因列表。 + *

+ */ +@Data +public class TeachingPlanFillResultVO { + + /** + * 待填写课次总数。 + */ + private Integer totalCount; + + /** + * 成功填写课次数。 + */ + private Integer successCount; + + /** + * 填写失败课次数。 + */ + private Integer failCount; + + /** + * 填写失败的课次编号列表。 + */ + private List failList; + + /** + * 填写结果提示信息。 + */ + private String message; + + /** + * 默认构造函数,初始化各字段为默认值。 + */ + public TeachingPlanFillResultVO() { + this.totalCount = 0; + this.successCount = 0; + this.failCount = 0; + this.failList = new ArrayList<>(); + } + + /** + * 构造函数,根据总数、成功数和失败列表初始化。 + * + * @param totalCount 待填写课次总数 + * @param successCount 成功填写课次数 + * @param failList 填写失败的课次编号列表 + */ + public TeachingPlanFillResultVO(Integer totalCount, Integer successCount, List failList) { + this.totalCount = totalCount; + this.successCount = successCount; + this.failCount = totalCount - successCount; + this.failList = failList != null ? failList : new ArrayList<>(); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingPlanLessonVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingPlanLessonVO.java new file mode 100644 index 000000000..803111049 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingPlanLessonVO.java @@ -0,0 +1,76 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教学实施计划课次列表项展示数据。 + *

+ * 用于展示课次的基本信息及填写状态,包括课程名称、课次序号、 + * 上课日期节次、填写状态等关键信息,方便教员快速定位待填写的课次。 + *

+ */ +@Data +public class TeachingPlanLessonVO { + + /** + * 实施_课程表编号,课次的唯一标识。 + */ + private String sskcbbh; + + /** + * 实施_课程编号,关联上级课程数据。 + */ + private String sskcbh; + + /** + * 课程名称(课编号对应的课程名称)。 + */ + private String kcmc; + + /** + * 课次序号,本节课在整门课程中的第几次课。 + */ + private Integer kcxh; + + /** + * 年度,课次所属的教学年度。 + */ + private Integer nd; + + /** + * 上课日期,本节课的计划上课日期。 + */ + private LocalDateTime rq; + + /** + * 节次,本节课在当天的节次序号。 + */ + private Integer jc; + + /** + * 教学内容摘要,已填写时展示内容前段摘要。 + */ + private String jxnr; + + /** + * 教学方法,已填写时展示使用的教学方法。 + */ + private String jxff; + + /** + * 填写状态:0-未填写,1-已填写,2-已提交。 + */ + private Integer txzt; + + /** + * 创建时间,课次数据的创建时间。 + */ + private LocalDateTime cjsj; + + /** + * 变动时间,课次数据最后一次修改时间。 + */ + private LocalDateTime bdsj; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportConfirmResultVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportConfirmResultVO.java new file mode 100644 index 000000000..5bfa4f669 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportConfirmResultVO.java @@ -0,0 +1,65 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * 教学保障批量确认结果展示数据。 + *

+ * 用于返回批量确认操作的结果统计,包含总记录数、成功数、失败数及失败原因列表。 + *

+ */ +@Data +public class TeachingSupportConfirmResultVO { + + /** + * 待确认记录总数。 + */ + private Integer totalCount; + + /** + * 成功确认记录数。 + */ + private Integer successCount; + + /** + * 确认失败记录数。 + */ + private Integer failCount; + + /** + * 确认失败的保障计划编号列表。 + */ + private List failList; + + /** + * 确认结果提示信息。 + */ + private String message; + + /** + * 默认构造函数,初始化各字段为默认值。 + */ + public TeachingSupportConfirmResultVO() { + this.totalCount = 0; + this.successCount = 0; + this.failCount = 0; + this.failList = new ArrayList<>(); + } + + /** + * 构造函数,根据总数、成功数和失败列表初始化。 + * + * @param totalCount 待确认记录总数 + * @param successCount 成功确认记录数 + * @param failList 确认失败的保障计划编号列表 + */ + public TeachingSupportConfirmResultVO(Integer totalCount, Integer successCount, List failList) { + this.totalCount = totalCount; + this.successCount = successCount; + this.failCount = totalCount - successCount; + this.failList = failList != null ? failList : new ArrayList<>(); + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportHistoryVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportHistoryVO.java new file mode 100644 index 000000000..cb8fdf5af --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportHistoryVO.java @@ -0,0 +1,61 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教学保障历史联想展示数据。 + *

+ * 用于在教师登记保障物资时,基于同一实施课程的历史登记记录联想推荐常用保障物资, + * 避免重复录入。按保障提供明细编号聚合,统计历史使用次数、最近使用时间和推荐数量。 + *

+ */ +@Data +public class TeachingSupportHistoryVO { + + /** + * 保障提供明细编号。 + */ + private String bztgmxbh; + + /** + * 保障提供明细名称。 + */ + private String bztgmxmc; + + /** + * 单位。 + */ + private String dw; + + /** + * 保障类别编号。 + */ + private String bzlbbh; + + /** + * 保障类别名称。 + */ + private String zzlmc; + + /** + * 历史使用次数(同一实施课程历史登记该保障物资的课次数)。 + */ + private Integer syscs; + + /** + * 最近一次历史登记时间。 + */ + private LocalDateTime zjsj; + + /** + * 历史登记的平均数量(用于推荐数量参考)。 + */ + private Integer pjsl; + + /** + * 历史最近一次登记数量(用于推荐数量参考)。 + */ + private Integer zjsl; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportPlanVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportPlanVO.java new file mode 100644 index 000000000..c37aa6cfe --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportPlanVO.java @@ -0,0 +1,96 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教学保障计划列表项展示数据。 + *

+ * 用于展示某课次的教学保障计划明细,包括保障提供明细名称、单位、数量、 + * 备注、确认状态及确认信息等。 + *

+ */ +@Data +public class TeachingSupportPlanVO { + + /** + * 课程表_保障明细编号,保障计划的唯一标识。 + */ + private String kcbbzmxbh; + + /** + * 实施_课程表编号(课次编号)。 + */ + private String sskcbbh; + + /** + * 保障提供明细编号。 + */ + private String bztgmxbh; + + /** + * 保障提供明细名称,关联保障提供明细表获取。 + */ + private String bztgmxmc; + + /** + * 单位,保障提供明细的计量单位,关联保障提供明细表获取。 + */ + private String dw; + + /** + * 保障类别名称,关联保障类别表获取。 + */ + private String zzlmc; + + /** + * 年度。 + */ + private Integer nd; + + /** + * 数量,本课次需要该保障提供的数量。 + */ + private Integer sl; + + /** + * 备注,保障计划相关说明。 + */ + private String bz; + + /** + * 确认状态:0-未确认,1-已确认。 + */ + private Integer qrzt; + + /** + * 确认人编号。 + */ + private String qrrbh; + + /** + * 确认人姓名,关联教员表获取。 + */ + private String qrrxm; + + /** + * 确认时间。 + */ + private LocalDateTime qrsj; + + /** + * 确认备注。 + */ + private String qrbz; + + /** + * 创建时间。 + */ + private LocalDateTime cjsj; + + /** + * 更新时间。 + */ + private LocalDateTime updateTime; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportResourceVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportResourceVO.java new file mode 100644 index 000000000..7f9e543aa --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/TeachingSupportResourceVO.java @@ -0,0 +1,48 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +/** + * 保障提供资源(保障资源库)展示数据。 + *

+ * 用于展示可供选择的保障提供明细,支持按类别筛选。 + *

+ */ +@Data +public class TeachingSupportResourceVO { + + /** + * 保障提供明细编号。 + */ + private String bztgmxbh; + + /** + * 保障提供明细名称。 + */ + private String bztgmxmc; + + /** + * 单位。 + */ + private String dw; + + /** + * 保障类别编号。 + */ + private String bzlbbh; + + /** + * 保障类别名称。 + */ + private String zzlmc; + + /** + * 停用状态:0-启用,1-停用。 + */ + private Integer ty; + + /** + * 备注。 + */ + private String bz; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/VenueUsageRateVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/VenueUsageRateVO.java new file mode 100644 index 000000000..9927d31e8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/courserunning/VenueUsageRateVO.java @@ -0,0 +1,38 @@ +package com.roomroot.jwgl.vo.courserunning; + +import lombok.Data; + +/** + * 场地使用率分布数据。 + *

+ * 用于饼图展示不同场地类型的使用率分布情况。 + *

+ */ +@Data +public class VenueUsageRateVO { + + /** + * 场地类型编号。 + */ + private String cdlybh; + + /** + * 场地类型名称。 + */ + private String cdlymc; + + /** + * 使用率(百分比)。 + */ + private Double usageRate; + + /** + * 使用次数。 + */ + private Integer usageCount; + + /** + * 总可用次数。 + */ + private Integer totalCount; +} \ No newline at end of file diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/department/DepartmentTreeVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/department/DepartmentTreeVO.java new file mode 100644 index 000000000..2cfe2f301 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/department/DepartmentTreeVO.java @@ -0,0 +1,20 @@ +package com.roomroot.jwgl.vo.department; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.ArrayList; +import java.util.List; + +/** + * 部别树节点。 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class DepartmentTreeVO extends DepartmentVO { + + /** + * 当前部别的直接下级节点。 + */ + private List children = new ArrayList<>(); +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/department/DepartmentVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/department/DepartmentVO.java new file mode 100644 index 000000000..3ca7fcf0c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/department/DepartmentVO.java @@ -0,0 +1,67 @@ +package com.roomroot.jwgl.vo.department; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 部别详情及列表数据。 + */ +@Data +public class DepartmentVO { + + /** + * 部别唯一编号。 + */ + private String id; + + /** + * 部别名称。 + */ + private String name; + + /** + * 机关类别。 + */ + private String category; + + /** + * 分类号。 + */ + private String classificationCode; + + /** + * 分级号。 + */ + private Integer levelNumber; + + /** + * 上级部别编号;根节点为 null。 + */ + private String parentId; + + /** + * 上级部别名称;根节点或上级数据缺失时为 null。 + */ + private String parentName; + + /** + * 是否启用;由数据库“停用”字段反向转换得到。 + */ + private Boolean enabled; + + /** + * 停用时间;启用状态通常为 null。 + */ + private LocalDateTime disabledAt; + + /** + * 部别简称。 + */ + private String abbreviation; + + /** + * 是否存在直接或间接查询数据中的子节点。 + */ + private Boolean hasChildren; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/departmentpersonnel/DepartmentPersonnelDepartmentVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/departmentpersonnel/DepartmentPersonnelDepartmentVO.java new file mode 100644 index 000000000..3ed68d38f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/departmentpersonnel/DepartmentPersonnelDepartmentVO.java @@ -0,0 +1,50 @@ +package com.roomroot.jwgl.vo.departmentpersonnel; + +import lombok.Data; + +/** + * 部别人员管理页面使用的部别选择项。 + */ +@Data +public class DepartmentPersonnelDepartmentVO { + + /** + * 部别唯一编号。 + */ + private String id; + + /** + * 部别名称。 + */ + private String name; + + /** + * 部别简称。 + */ + private String abbreviation; + + /** + * 机关类别。 + */ + private String category; + + /** + * 分类号,用于部别排序。 + */ + private String classificationCode; + + /** + * 分级号,用于部别排序。 + */ + private Integer levelNumber; + + /** + * 上级部别编号;根节点为 null。 + */ + private String parentId; + + /** + * 是否启用。 + */ + private Boolean enabled; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/departmentpersonnel/DepartmentPersonnelVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/departmentpersonnel/DepartmentPersonnelVO.java new file mode 100644 index 000000000..b093f9a09 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/departmentpersonnel/DepartmentPersonnelVO.java @@ -0,0 +1,65 @@ +package com.roomroot.jwgl.vo.departmentpersonnel; + +import lombok.Data; + +/** + * 部别人员详情、列表、导入和导出数据。 + */ +@Data +public class DepartmentPersonnelVO { + + /** + * 人员唯一编号。 + */ + private String id; + + /** + * 所属部别编号。 + */ + private String departmentId; + + /** + * 所属部别名称。 + */ + private String departmentName; + + /** + * 所属部别简称。 + */ + private String departmentAbbreviation; + + /** + * 人员姓名。 + */ + private String name; + + /** + * 人员序号。 + */ + private String sequenceNumber; + + /** + * 是否离职。 + */ + private Boolean departed; + + /** + * 人员职务。 + */ + private String position; + + /** + * 人员卡号。 + */ + private String cardNumber; + + /** + * 人员姓名拼音。 + */ + private String pinyin; + + /** + * 是否为主官。 + */ + private Boolean principal; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/dict/DictDataVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/dict/DictDataVO.java new file mode 100644 index 000000000..1d2efc31d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/dict/DictDataVO.java @@ -0,0 +1,94 @@ +package com.roomroot.jwgl.vo.dict; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 字典数据展示数据。 + *

+ * 用于字典数据列表、详情以及前端下拉框的数据返回, + * 包含字典项的标签、值、排序、状态、样式等完整信息。 + *

+ */ +@Data +public class DictDataVO { + + /** + * 字典数据ID(主键,自增)。 + */ + private Long dictDataId; + + /** + * 字典类型编码。 + *

关联字典类型,标识该数据项属于哪个字典类型。

+ */ + private String dictTypeCode; + + /** + * 字典标签(显示用)。 + *

前端下拉框、单选、多选组件中展示的文本。

+ */ + private String dictLabel; + + /** + * 字典值(存储用)。 + *

实际存入数据库或用于业务逻辑判断的值。

+ */ + private String dictValue; + + /** + * 排序号。 + *

控制数据项在前端下拉框中的展示顺序,数值越小越靠前。

+ */ + private Integer dictSort; + + /** + * 标签样式(success/warning/info/danger)。 + *

用于前端标签组件的颜色样式。

+ */ + private String cssClass; + + /** + * 表格回显样式。 + *

用于前端表格中该字典项的展示样式。

+ */ + private String listClass; + + /** + * 是否默认(0否 1是)。 + *

同一类型下只有一个默认选项,前端下拉框默认选中此项。

+ */ + private Integer isDefault; + + /** + * 状态(0停用 1正常)。 + *

停用后该选项在业务界面不可选。

+ */ + private Integer status; + + /** + * 备注。 + */ + private String remark; + + /** + * 版本号(用于版本控制,初始为1,每次编辑+1) + */ + private Integer version; + + /** + * 历史版本快照(JSON格式,仅详情接口返回) + */ + private String prevSnapshot; + + /** + * 创建时间。 + */ + private LocalDateTime createTime; + + /** + * 更新时间。 + */ + private LocalDateTime updateTime; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/dict/DictTypeVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/dict/DictTypeVO.java new file mode 100644 index 000000000..d58755dc3 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/dict/DictTypeVO.java @@ -0,0 +1,69 @@ +package com.roomroot.jwgl.vo.dict; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 字典类型展示数据。 + *

+ * 用于字典类型列表和详情的数据返回,包含类型的基本信息和状态, + * 以及创建和更新时间,便于前端展示和管理。 + *

+ */ +@Data +public class DictTypeVO { + + /** + * 字典类型ID(主键,自增)。 + */ + private Long dictTypeId; + + /** + * 字典类型编码(全局唯一,程序引用键)。 + *

业务代码通过此编码引用字典类型,编码一旦确定不建议修改。

+ */ + private String dictTypeCode; + + /** + * 字典类型名称(用于页面展示)。 + */ + private String dictTypeName; + + /** + * 所属分组(基础数据/教学数据/学籍数据等)。 + *

用于对字典类型进行分类管理和展示。

+ */ + private String dictGroup; + + /** + * 状态(0停用 1正常)。 + *

停用后该类型下的数据项在业务下拉框中不可见。

+ */ + private Integer status; + + /** + * 备注。 + */ + private String remark; + + /** + * 版本号(用于版本控制,初始为1,每次编辑+1) + */ + private Integer version; + + /** + * 历史版本快照(JSON格式,仅详情接口返回) + */ + private String prevSnapshot; + + /** + * 创建时间。 + */ + private LocalDateTime createTime; + + /** + * 更新时间。 + */ + private LocalDateTime updateTime; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/ClassScheduleVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/ClassScheduleVO.java new file mode 100644 index 000000000..29606fa6e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/ClassScheduleVO.java @@ -0,0 +1,23 @@ +package com.roomroot.jwgl.vo.elective; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 班次课程表 VO(实施_课程表 + 课表名称) + */ +@Data +public class ClassScheduleVO { + + private String bh; + private String sskcbh; + private String kckmbh; + private String kcmc; // 课程名称 + private LocalDateTime rq; // 日期 + private Integer jc; // 节次 + private String jxnr; // 教学内容 + private String jxff; // 教学方法 + private Integer nd; // 年度 + private String xydbh; // 学员队编号 +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/ElectiveClassVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/ElectiveClassVO.java new file mode 100644 index 000000000..68c55d9d2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/ElectiveClassVO.java @@ -0,0 +1,96 @@ +package com.roomroot.jwgl.vo.elective; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教学班次列表展示 VO + *

+ * 三表联查(学员队表 + 学员队期表 + 专业表),展示教学班次完整信息。 + *

+ */ +@Data +public class ElectiveClassVO { + + /** 学员队编号 */ + private String xydbh; + + /** 名称(班次名称) */ + private String xydmc; + + /** 计划人数 */ + private Integer xydrs; + + /** 年级 */ + private String nj; + + /** 专业代号 */ + private String zydh; + + /** 任务类别(规划内、其他) */ + private String rwlb; + + /** 入学日期 */ + private LocalDateTime rxrq; + + /** 毕业日期 */ + private LocalDateTime byrq; + + /** 学员队类型(教学班/管理班/选修班) */ + private String xydlx; + + /** 在校状态 */ + private String zxzt; + + /** 虚实类型 */ + private Integer xslx; + + /** 培训任务标识号(培训任务) */ + private String pxrwbsh; + + /** 节次类别 */ + private String jclb; + + /** 所属单位 */ + private String ssdw; + + /** 主体培训任务 */ + private Integer ztpxrw; + + /** 简称 */ + private String jc; + + /** 培训类型(关联专业表) */ + private String pxlx; + + /** 培训层次(关联专业表) */ + private String pxcc; + + /** 培训类型2(关联专业表) */ + private String pxlx2; + + /** 学员学号前缀 */ + private String xhqz; + + /** 学员学号自动累加位数 */ + private Integer xhws; + + /** 是否允许学员自行注册(0-关闭 1-开启) */ + private Integer yxzc; + + // ====== 查询条件字段(以下字段仅用于筛选,不回显) ====== + + /** 学期(查询条件) */ + private String xq; + + /** 入学日期(起) - 范围查询条件 */ + private LocalDateTime rxrqStart; + /** 入学日期(止) - 范围查询条件 */ + private LocalDateTime rxrqEnd; + + /** 毕业日期(起) - 范围查询条件 */ + private LocalDateTime byrqStart; + /** 毕业日期(止) - 范围查询条件 */ + private LocalDateTime byrqEnd; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/SemesterInitVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/SemesterInitVO.java new file mode 100644 index 000000000..0ef13e495 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/SemesterInitVO.java @@ -0,0 +1,71 @@ +package com.roomroot.jwgl.vo.elective; + +import lombok.Data; + +import java.time.LocalDate; + +/** + * 教学班次学期信息设置窗口 - 初始化默认值 + *

+ * 打开【教学班次学期信息设置】窗口时返回的默认数据: + *

    + *
  • 学期第次:学制半年以内固定为第一学期;半年以上按入学日期智能推断,可手动修改
  • + *
  • 开学/结束日期:半年以内默认入学/毕业日期;半年以上默认窗口预设日期
  • + *
  • 专用教室:第二以上学期默认预设上学期的专用教室
  • + *
+ *

+ */ +@Data +public class SemesterInitVO { + + /** + * 班次编号(学员队编号) + */ + private String xydbh; + + /** + * 班次名称 + */ + private String xydmc; + + /** + * 是否学制半年以内(true-半年以内,学期第次只能为第一学期) + */ + private Boolean halfYear; + + /** + * 默认/推断的学期第次 + */ + private Integer xqdc; + + /** + * 人才培养方案总学期数(按学制推算,半年一学期) + */ + private Integer totalXqdc; + + /** + * 默认开学日期 + */ + private LocalDate kxrq; + + /** + * 默认结束日期 + */ + private LocalDate jsrq; + + /** + * 预设专用教室编号(第二以上学期取上学期的专用教室) + */ + private String zyjsbh; + + /** + * 预设专用教室名称 + */ + private String zyjsmc; + + /** + * 节次类别(带出班次默认值) + */ + private String jclb; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/StudentAbsenceVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/StudentAbsenceVO.java new file mode 100644 index 000000000..4dc8de882 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/StudentAbsenceVO.java @@ -0,0 +1,22 @@ +package com.roomroot.jwgl.vo.elective; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学员缺勤 VO(请假记录 + 请假学员) + */ +@Data +public class StudentAbsenceVO { + + private String qjbh; // 请假编号 + private String mc; // 请假名称 + private LocalDateTime qssj; // 起始时间 + private LocalDateTime jssj; // 结束时间 + private String yy; // 原因 + private String xybh; // 学员编号 + private String xm; // 学员姓名 + private String xh; // 学号 + private Integer kts; // 课堂数 +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/StudentGradeVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/StudentGradeVO.java new file mode 100644 index 000000000..344e39f5b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/elective/StudentGradeVO.java @@ -0,0 +1,22 @@ +package com.roomroot.jwgl.vo.elective; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 学员成绩 VO + */ +@Data +public class StudentGradeVO { + + private String xybh; // 学员编号 + private String xm; // 学员姓名 + private String xh; // 学号 + private String kmbh; // 科目编号 + private BigDecimal kscj; // 考试成绩 + private BigDecimal pscj; // 平时成绩 + private BigDecimal zzcj; // 最终成绩 + private String ksqk; // 考试情况 + private Integer ytg; // 已通过 +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/faculty/TeacherListVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/faculty/TeacherListVO.java new file mode 100644 index 000000000..c1e61016a --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/faculty/TeacherListVO.java @@ -0,0 +1,49 @@ +package com.roomroot.jwgl.vo.faculty; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教员列表展示 VO + *

双表联查(教员表 LEFT JOIN 教员属性 LEFT JOIN 教研室表)

+ */ +@Data +public class TeacherListVO { + + /** 序号 */ + private String xh; + + /** 教员工号 */ + private String jybh; + + /** 教研室名称 */ + private String jysmc; + + /** 教员姓名 */ + private String jyxm; + + /** 性别(0-女 1-男) */ + private Integer xb; + + /** 出生日期 */ + private LocalDateTime cssj; + + /** 职称 */ + private String zc; + + /** 学历 */ + private String xl; + + /** 学位 */ + private String xw; + + /** 进校日期(入伍工作时间) */ + private LocalDateTime rwgzsj; + + /** 联系方式(手机号) */ + private String sjh; + + /** 是否实教教员(虚实类型) */ + private Integer xslx; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/log/TeacherHoursVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/log/TeacherHoursVO.java new file mode 100644 index 000000000..1187b80dd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/log/TeacherHoursVO.java @@ -0,0 +1,102 @@ +package com.roomroot.jwgl.vo.log; + +import lombok.Data; + +/** + * 教员课时查询管理 - 列表展示与查询条件 + *

+ * 数据来源:学期教员信息表(xqj) JOIN 教员表(jy) JOIN 教研室表(jys)。 + * 该 VO 既用于列表展示字段,也用于查询条件;课时量类查询条件均为数字范围(xx课时 至 xx课时)。 + *

+ */ +@Data +public class TeacherHoursVO { + + // ======================== 列表展示字段 ======================== + + /** 教研室名称(教研室表.教研室名称) */ + private String jysmc; + + /** 教员姓名(教员表.教员姓名) */ + private String jyxm; + + /** 教员类别(教员表.教员类别) */ + private String jylb; + + /** 当前职称(教员表.职称) */ + private String dqzc; + + /** 学期职称(学期教员信息表.职称) */ + private String xqzc; + + /** 当前专业技术职务等级(教员表.专业技术职务等级) */ + private String dqzyjszwdj; + + /** 学期专业技术职务等级(学期教员信息表.专业技术职务等级) */ + private String xqzyjszwdj; + + /** 主讲课时(学期教员信息表.课程主讲课时) */ + private Float zjks; + + /** 辅讲课时(学期教员信息表.课程辅讲课时) */ + private Float fjks; + + /** 课程总课时(学期教员信息表.课程总课时) */ + private Float kczks; + + /** 课程折算总课时(学期教员信息表.课程折算总课时) */ + private Float kczszks; + + /** 项目补助课时(学期教员信息表.项目补助课时) */ + private Float xmbzks; + + /** 总课时(学期教员信息表.折算总课时) */ + private Float zszks; + + // ======================== 查询条件字段 ======================== + + /** 教员编号(精确匹配,可选) */ + private String jybh; + + /** 教研室(教研室代号,精确匹配,可选) */ + private String jysdh; + + /** 部系名称(模糊匹配,可选) */ + private String bx; + + /** 年度/学期(学期教员信息表.年度,精确匹配,可选) */ + private Integer nd; + + // ---- 课时量数字范围(xx课时 至 xx课时),均为可选 ---- + + /** 主讲课时下限 */ + private Float zjksMin; + /** 主讲课时上限 */ + private Float zjksMax; + + /** 辅讲课时下限 */ + private Float fjksMin; + /** 辅讲课时上限 */ + private Float fjksMax; + + /** 课程总课时下限 */ + private Float kczksMin; + /** 课程总课时上限 */ + private Float kczksMax; + + /** 课程折算总课时下限 */ + private Float kczszksMin; + /** 课程折算总课时上限 */ + private Float kczszksMax; + + /** 项目补助课时下限 */ + private Float xmbzksMin; + /** 项目补助课时上限 */ + private Float xmbzksMax; + + /** 总课时下限 */ + private Float zszksMin; + /** 总课时上限 */ + private Float zszksMax; + +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/log/TeachingLogListVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/log/TeachingLogListVO.java new file mode 100644 index 000000000..a227e737c --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/log/TeachingLogListVO.java @@ -0,0 +1,69 @@ +package com.roomroot.jwgl.vo.log; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 教学日志列表展示 VO + *

基于实施_课程表_日志(36字段版),联合教员表、课表获取展示信息。

+ */ +@Data +public class TeachingLogListVO { + + // ====== 基础字段 ====== + private String bh; + private String tybh; + private String jybh; + private Integer zczt; + private String rzjl; + private String bz; + private LocalDateTime cjsj; + private LocalDateTime xgsj; + private String ztpj; + private String jsfs; + private Integer sdrs; + private Integer jcks; + private Integer atj; + private LocalDateTime rq; + private Integer qsjc; + private Integer jsjc; + private String zt; + private LocalDateTime jyshzsj; + private String skkt; + private String jxrzcjfs; + private String skdd; + private String kckmbh; + private String rwlb; + private Integer ydrs; + private String pxqbxx; + private String ckldhzj; + private Integer sksjbd; + private Integer skddbd; + private Integer kcbd; + private Integer skktbd; + private Integer rwlbbd; + private Integer pxqbbd; + private Integer skfsbd; + private Integer fzyzdjybd; + private String gljgbh; + private String thyj; + + // ====== 联查展示字段 ====== + private String jyxm; // 教员姓名(关联教员表) + private String kcmc; // 课程名称(关联课表) + private String jysmc; // 教研室名称(关联教研室表) + + // ====== 查询条件字段(仅用于筛选) ====== + private LocalDateTime ksrq; + private LocalDateTime jsrq; + private String jys; + private String skjy; + private String gdqk; + private String gdxxqk; + + /** 改动情况列表(多选IN查询) */ + private java.util.List gdqkList; + /** 改动详细情况列表(多选IN查询) */ + private java.util.List gdxxqkList; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/login/LoginResultVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/login/LoginResultVO.java new file mode 100644 index 000000000..c0449ee91 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/login/LoginResultVO.java @@ -0,0 +1,41 @@ +package com.roomroot.jwgl.vo.login; + +import lombok.Data; + +import java.util.List; + +/** + * 登录响应结果 + */ +@Data +public class LoginResultVO { + + /** 用户编号(登录信息表编号) */ + private String userId; + + /** 用户姓名 */ + private String userName; + + /** 登录账号 */ + private String loginName; + + /** 用户类型: ADMIN-行政 TEACHER-教员 STUDENT-学员 MANAGER-管理员 */ + private String userType; + + /** 单位编号 */ + private String orgId; + + /** 单位名称 */ + private String orgName; + + /** 角色列表 */ + private List roles; + + @Data + public static class RoleInfo { + private String roleType; + private String roleName; + private String targetId; + private String targetName; + } +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementDetailVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementDetailVO.java new file mode 100644 index 000000000..5b95b1f02 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementDetailVO.java @@ -0,0 +1,87 @@ +package com.roomroot.jwgl.vo.noticeannouncement; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知公告后台详情展示数据。 + */ +@Data +public class NoticeAnnouncementDetailVO { + + /** + * 公告编号。 + */ + private String id; + + /** + * 公告标题。 + */ + private String title; + + /** + * 公告正文。 + */ + private String content; + + /** + * 公告发布时间;拟制公告暂存创建时间。 + */ + private LocalDateTime publishTime; + + /** + * 公告创建时间。 + */ + private LocalDateTime createTime; + + /** + * 公告优先级。 + */ + private Integer priority; + + /** + * 公告累计打开次数。 + */ + private Integer readCount; + + /** + * 公告当前状态。 + */ + private String status; + + /** + * 公告类别。 + */ + private String category; + + /** + * 公告备注。 + */ + private String remark; + + /** + * 是否面向全校账户发布。 + */ + private Boolean all; + + /** + * 可从现有数据库字段还原的发布范围类型。 + */ + private String scopeType; + + /** + * 发布范围中文说明。 + */ + private String scopeName; + + /** + * 已接收账户人数。 + */ + private Long recipientCount; + + /** + * 已阅读账户人数。 + */ + private Long readerCount; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementPublishResultVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementPublishResultVO.java new file mode 100644 index 000000000..b165b3204 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementPublishResultVO.java @@ -0,0 +1,37 @@ +package com.roomroot.jwgl.vo.noticeannouncement; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知公告发布结果。 + */ +@Data +public class NoticeAnnouncementPublishResultVO { + + /** + * 已发布公告编号。 + */ + private String id; + + /** + * 本次发布范围类型。 + */ + private String scopeType; + + /** + * 本次发布范围中文说明。 + */ + private String scopeName; + + /** + * 本次生成的去重接收账户人数。 + */ + private Integer recipientCount; + + /** + * 本次发布时间。 + */ + private LocalDateTime publishTime; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementRecipientVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementRecipientVO.java new file mode 100644 index 000000000..f5180833b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementRecipientVO.java @@ -0,0 +1,52 @@ +package com.roomroot.jwgl.vo.noticeannouncement; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知公告接收人明细。 + */ +@Data +public class NoticeAnnouncementRecipientVO { + + /** + * 通告用户信息记录编号。 + */ + private String id; + + /** + * 接收账户编号。 + */ + private String accountId; + + /** + * 接收账户登录名。 + */ + private String loginName; + + /** + * 接收人姓名。 + */ + private String userName; + + /** + * 是否已经接收。 + */ + private Boolean received; + + /** + * 接收时间。 + */ + private LocalDateTime receivedTime; + + /** + * 是否已经阅读。 + */ + private Boolean read; + + /** + * 首次阅读时间。 + */ + private LocalDateTime readTime; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementStatisticsVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementStatisticsVO.java new file mode 100644 index 000000000..7f02def77 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementStatisticsVO.java @@ -0,0 +1,42 @@ +package com.roomroot.jwgl.vo.noticeannouncement; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 通知公告接收和阅读统计。 + */ +@Data +public class NoticeAnnouncementStatisticsVO { + + /** + * 公告编号。 + */ + private String id; + + /** + * 接收账户总数。 + */ + private Long recipientCount; + + /** + * 已接收账户数。 + */ + private Long receivedCount; + + /** + * 已阅读账户数。 + */ + private Long readerCount; + + /** + * 未阅读账户数。 + */ + private Long unreadCount; + + /** + * 阅读率百分比,保留两位小数。 + */ + private BigDecimal readRate; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementVO.java new file mode 100644 index 000000000..7887910fe --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/NoticeAnnouncementVO.java @@ -0,0 +1,65 @@ +package com.roomroot.jwgl.vo.noticeannouncement; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知公告分页列表展示数据。 + * + *

公告主体字段来自通告表,接收人数和阅读人数由通告用户信息表 + * 按公告编号汇总后返回,不向页面暴露旧实体的公共持久化字段。

+ */ +@Data +public class NoticeAnnouncementVO { + + /** + * 公告编号。 + */ + private String id; + + /** + * 公告发布时间。 + */ + private LocalDateTime publishTime; + + /** + * 公告标题。 + */ + private String title; + + /** + * 公告当前状态。 + */ + private String status; + + /** + * 公告类别。 + */ + private String category; + + /** + * 公告优先级。 + */ + private Integer priority; + + /** + * 公告被有效打开的累计次数。 + */ + private Integer readCount; + + /** + * 已接收该公告的账户人数。 + */ + private Long recipientCount; + + /** + * 已阅读该公告的账户人数。 + */ + private Long readerCount; + + /** + * 公告备注。 + */ + private String remark; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/UserNoticeAnnouncementDetailVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/UserNoticeAnnouncementDetailVO.java new file mode 100644 index 000000000..210a8892b --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/UserNoticeAnnouncementDetailVO.java @@ -0,0 +1,62 @@ +package com.roomroot.jwgl.vo.noticeannouncement; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 当前登录用户可查看的通知公告详情。 + */ +@Data +public class UserNoticeAnnouncementDetailVO { + + /** + * 公告编号。 + */ + private String id; + + /** + * 公告标题。 + */ + private String title; + + /** + * 公告正文。 + */ + private String content; + + /** + * 公告发布时间。 + */ + private LocalDateTime publishTime; + + /** + * 公告类别。 + */ + private String category; + + /** + * 公告优先级。 + */ + private Integer priority; + + /** + * 公告备注。 + */ + private String remark; + + /** + * 公告累计打开次数。 + */ + private Integer readCount; + + /** + * 当前账户是否已经阅读。 + */ + private Boolean read; + + /** + * 当前账户首次阅读时间。 + */ + private LocalDateTime readTime; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/UserNoticeAnnouncementVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/UserNoticeAnnouncementVO.java new file mode 100644 index 000000000..429972b37 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/noticeannouncement/UserNoticeAnnouncementVO.java @@ -0,0 +1,47 @@ +package com.roomroot.jwgl.vo.noticeannouncement; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 当前登录用户的通知公告列表展示数据。 + */ +@Data +public class UserNoticeAnnouncementVO { + + /** + * 公告编号。 + */ + private String id; + + /** + * 公告标题。 + */ + private String title; + + /** + * 公告类别。 + */ + private String category; + + /** + * 公告优先级。 + */ + private Integer priority; + + /** + * 公告发布时间。 + */ + private LocalDateTime publishTime; + + /** + * 当前账户是否已经阅读。 + */ + private Boolean read; + + /** + * 当前账户首次阅读时间。 + */ + private LocalDateTime readTime; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogDetailVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogDetailVO.java new file mode 100644 index 000000000..05b75d79d --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogDetailVO.java @@ -0,0 +1,42 @@ +package com.roomroot.jwgl.vo.notificationlog; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDateTime; + +/** + * 通知发送日志详情展示数据。 + * + *

继承列表中的发送和阅读统计字段, + * 并补充通知正文、创建信息和发布范围等详情字段。

+ */ +@Data +@EqualsAndHashCode(callSuper = true) +public class NotificationLogDetailVO extends NotificationLogVO { + + /** + * 通知正文。 + */ + private String content; + + /** + * 通知创建时间。 + */ + private LocalDateTime createTime; + + /** + * 通知优先级。 + */ + private Integer priority; + + /** + * 通知备注。 + */ + private String remark; + + /** + * 是否面向全体账户发送。 + */ + private Boolean all; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogRecipientVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogRecipientVO.java new file mode 100644 index 000000000..797e238f1 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogRecipientVO.java @@ -0,0 +1,55 @@ +package com.roomroot.jwgl.vo.notificationlog; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 通知日志接收对象及阅读明细。 + * + *

每个通知和账户只返回一条汇总记录, + * 兼容数据库中可能存在的历史重复接收数据。

+ */ +@Data +public class NotificationLogRecipientVO { + + /** + * 通告用户信息代表记录编号。 + */ + private String id; + + /** + * 接收账户编号。 + */ + private String accountId; + + /** + * 接收账户登录账号。 + */ + private String loginName; + + /** + * 接收人姓名。 + */ + private String userName; + + /** + * 是否已经成功接收。 + */ + private Boolean received; + + /** + * 首次成功接收时间。 + */ + private LocalDateTime receivedTime; + + /** + * 是否已经阅读。 + */ + private Boolean read; + + /** + * 首次阅读时间。 + */ + private LocalDateTime readTime; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogStatisticsVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogStatisticsVO.java new file mode 100644 index 000000000..5fc67f8f8 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogStatisticsVO.java @@ -0,0 +1,65 @@ +package com.roomroot.jwgl.vo.notificationlog; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 通知日志发送与阅读汇总统计。 + * + *

统计范围与页面提交的通知日志筛选条件保持一致, + * 数量来自数据库聚合,派生数量和阅读率由工具类统一补全。

+ */ +@Data +public class NotificationLogStatisticsVO { + + /** + * 符合条件的通知日志数量。 + */ + private Long notificationCount; + + /** + * 发送成功的通知数量。 + */ + private Long successNotificationCount; + + /** + * 部分成功的通知数量。 + */ + private Long partialNotificationCount; + + /** + * 发送失败的通知数量。 + */ + private Long failedNotificationCount; + + /** + * 全部通知的接收对象总数。 + */ + private Long recipientCount; + + /** + * 已成功接收总人数。 + */ + private Long receivedCount; + + /** + * 发送失败总人数。 + */ + private Long failedCount; + + /** + * 已阅读总人数。 + */ + private Long readerCount; + + /** + * 未阅读总人数。 + */ + private Long unreadCount; + + /** + * 汇总阅读率百分比,保留两位小数。 + */ + private BigDecimal readRate; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogVO.java new file mode 100644 index 000000000..a6238d377 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/notificationlog/NotificationLogVO.java @@ -0,0 +1,81 @@ +package com.roomroot.jwgl.vo.notificationlog; + +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 通知日志发送历史分页展示数据。 + * + *

通知主体来自通告表,发送、接收和阅读数据来自通告用户信息表, + * 不向页面暴露数据库实体的公共持久化字段。

+ */ +@Data +public class NotificationLogVO { + + /** + * 通知编号。 + */ + private String id; + + /** + * 本次通知发送时间。 + */ + private LocalDateTime sendTime; + + /** + * 通知标题。 + */ + private String title; + + /** + * 通知类别。 + */ + private String category; + + /** + * 通知公告当前状态。 + */ + private String noticeStatus; + + /** + * 通知被有效打开的累计次数。 + */ + private Integer readCount; + + /** + * 本次通知接收对象总数。 + */ + private Long recipientCount; + + /** + * 已成功接收人数。 + */ + private Long receivedCount; + + /** + * 发送失败人数。 + */ + private Long failedCount; + + /** + * 已阅读人数。 + */ + private Long readerCount; + + /** + * 未阅读人数。 + */ + private Long unreadCount; + + /** + * 阅读率百分比,保留两位小数。 + */ + private BigDecimal readRate; + + /** + * 本次通知综合发送状态。 + */ + private String sendStatus; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/operationlog/OperationLogDetailVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/operationlog/OperationLogDetailVO.java new file mode 100644 index 000000000..519b7632e --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/operationlog/OperationLogDetailVO.java @@ -0,0 +1,11 @@ +package com.roomroot.jwgl.vo.operationlog; + +/** + * 操作日志详情展示数据。 + * + *

详情接口当前展示的数据全部来自操作日志、登录记录和登录信息三表, + * 因此继承列表 VO 的公共审计字段,避免重复声明相同属性。 + * 后续出现仅详情页面使用的字段时继续维护在当前 VO 中。

+ */ +public class OperationLogDetailVO extends OperationLogVO { +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/operationlog/OperationLogModuleVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/operationlog/OperationLogModuleVO.java new file mode 100644 index 000000000..29c9ded49 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/operationlog/OperationLogModuleVO.java @@ -0,0 +1,18 @@ +package com.roomroot.jwgl.vo.operationlog; + +import lombok.Data; + +/** + * 操作日志模块选项展示数据。 + * + *

用于向页面返回后端实际支持的模块筛选名称, + * 避免前端重复维护模块白名单。

+ */ +@Data +public class OperationLogModuleVO { + + /** + * 模块名称。 + */ + private String name; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/operationlog/OperationLogVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/operationlog/OperationLogVO.java new file mode 100644 index 000000000..c920f7320 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/operationlog/OperationLogVO.java @@ -0,0 +1,85 @@ +package com.roomroot.jwgl.vo.operationlog; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 操作日志分页列表展示数据。 + * + *

操作日志、登录记录和登录账户信息通过 Mapper 联表查询, + * 当前 VO 只返回审计页面需要的非敏感字段。

+ */ +@Data +public class OperationLogVO { + + /** + * 操作日志编号。 + */ + private String id; + + /** + * 本次操作关联的登录记录编号。 + */ + private String loginRecordId; + + /** + * 操作发生时间。 + */ + private LocalDateTime operationAt; + + /** + * 根据请求地址识别出的业务模块名称。 + */ + private String module; + + /** + * 操作内容。 + */ + private String operationContent; + + /** + * 操作对应的请求地址。 + */ + private String url; + + /** + * 操作账户编号。 + */ + private String accountId; + + /** + * 登录账号。 + */ + private String loginName; + + /** + * 操作人姓名。 + */ + private String operatorName; + + /** + * 统一身份账号。 + */ + private String unifiedAccount; + + /** + * 本次会话的登录时间。 + */ + private LocalDateTime loginAt; + + /** + * 登录请求来源 IP 地址。 + */ + private String ipAddress; + + /** + * 客户端内网 IP;数据库未记录时为空。 + */ + private String intranetIpAddress; + + /** + * 本次登录过程的事务标识。 + */ + private String transactionId; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SemesterInitializationVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SemesterInitializationVO.java new file mode 100644 index 000000000..a2ce72a1f --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SemesterInitializationVO.java @@ -0,0 +1,66 @@ +package com.roomroot.jwgl.vo.systeminitialization; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 学期初始化参数展示数据。 + */ +@Data +public class SemesterInitializationVO { + + /** 学期所属年度。 */ + private Integer year; + + /** 同一年度内的学期序号。 */ + private Integer sequenceNumber; + + /** 根据年度和序号生成的学期名称。 */ + private String semesterName; + + /** 是否为当前学期。 */ + private Boolean currentSemester; + + /** 开学时间。 */ + private LocalDateTime startAt; + + /** 学期结束时间。 */ + private LocalDateTime endAt; + + /** 调课是否不需要审批。 */ + private Boolean adjustmentWithoutApproval; + + /** 课时系数方案编号。 */ + private String hourCoefficientSchemeId; + + /** 是否禁止调课。 */ + private Boolean adjustmentForbidden; + + /** 终结成绩是否定为及格。 */ + private Boolean finalGradeAsPass; + + /** 调课是否需要教务领导审核。 */ + private Boolean leadershipReviewRequired; + + /** 教员调整是否不需要审批。 */ + private Boolean teacherAdjustmentWithoutApproval; + + /** 时间调整是否不需要审批。 */ + private Boolean timeAdjustmentWithoutApproval; + + /** 保障调整是否不需要审批。 */ + private Boolean supportAdjustmentWithoutApproval; + + /** 场地调整是否不需要审批。 */ + private Boolean venueAdjustmentWithoutApproval; + + /** 锁定周数。 */ + private Integer lockedWeeks; + + /** 锁定计量单位。 */ + private String lockMeasurementUnit; + + /** 课时统计时间。 */ + private LocalDateTime teachingHourStatisticsAt; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationCheckItemVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationCheckItemVO.java new file mode 100644 index 000000000..75b3161fc --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationCheckItemVO.java @@ -0,0 +1,22 @@ +package com.roomroot.jwgl.vo.systeminitialization; + +import lombok.Data; + +/** + * 系统初始化单项预检查结果。 + */ +@Data +public class SystemInitializationCheckItemVO { + + /** 检查项代码。 */ + private String code; + + /** 检查项名称。 */ + private String name; + + /** 是否通过。 */ + private Boolean passed; + + /** 检查结果说明。 */ + private String message; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationConfigurationVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationConfigurationVO.java new file mode 100644 index 000000000..60e8f8af2 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationConfigurationVO.java @@ -0,0 +1,75 @@ +package com.roomroot.jwgl.vo.systeminitialization; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 当前生效的系统初始化基础参数。 + * + *

只包含系统初始化页面允许展示的基础参数, + * 不包含院校校验码等敏感数据库字段。

+ */ +@Data +public class SystemInitializationConfigurationVO { + + /** + * 当前配置版本编号。 + */ + private Integer versionId; + + /** + * 当前配置版本时间。 + */ + private LocalDateTime versionDate; + + /** + * 当前配置版本值。 + */ + private String versionValue; + + /** + * 院校名称。 + */ + private String universityName; + + /** + * 院校代码。 + */ + private String universityCode; + + /** + * 缺省系统名称。 + */ + private String defaultSystemName; + + /** + * 系统运行模式。 + */ + private String systemMode; + + /** + * 学期名称显示样式。 + */ + private String semesterNameStyle; + + /** + * 节次类别。 + */ + private String periodCategory; + + /** + * 学员队全名称是否包含年级。 + */ + private Boolean studentTeamFullNameIncludesGrade; + + /** + * 外部文件存储根路径。 + */ + private String externalFileStorageRootPath; + + /** + * 本站文件访问路径。 + */ + private String localFileAccessPath; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationExecutionResultVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationExecutionResultVO.java new file mode 100644 index 000000000..f3359cdf6 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationExecutionResultVO.java @@ -0,0 +1,27 @@ +package com.roomroot.jwgl.vo.systeminitialization; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 系统初始化最终执行结果。 + */ +@Data +public class SystemInitializationExecutionResultVO { + + /** 初始化是否成功完成。 */ + private Boolean completed; + + /** 完成初始化时使用的配置版本编号。 */ + private Integer configurationVersionId; + + /** 完成初始化时使用的学期名称。 */ + private String semesterName; + + /** 初始化完成时间。 */ + private LocalDateTime completedAt; + + /** 初始化结果说明。 */ + private String message; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationHistoricalImportResultVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationHistoricalImportResultVO.java new file mode 100644 index 000000000..8cf698e43 --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationHistoricalImportResultVO.java @@ -0,0 +1,33 @@ +package com.roomroot.jwgl.vo.systeminitialization; + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * 系统初始化历史基础数据导入结果。 + */ +@Data +public class SystemInitializationHistoricalImportResultVO { + + /** 原始上传文件名称。 */ + private String fileName; + + /** 成功导入部别数量。 */ + private Integer departmentCount; + + /** 成功导入教研室数量。 */ + private Integer researchOfficeCount; + + /** 成功建立教研室隶属关系数量。 */ + private Integer researchOfficeAffiliationCount; + + /** 成功导入部别人员数量。 */ + private Integer personnelCount; + + /** 本次成功写入总数。 */ + private Integer totalCount; + + /** 导入完成时间。 */ + private LocalDateTime importedAt; +} diff --git a/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationPrecheckVO.java b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationPrecheckVO.java new file mode 100644 index 000000000..3a009f0fd --- /dev/null +++ b/backend/roomroot-jwgl/src/main/java/com/roomroot/jwgl/vo/systeminitialization/SystemInitializationPrecheckVO.java @@ -0,0 +1,39 @@ +package com.roomroot.jwgl.vo.systeminitialization; + +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * 系统初始化预检查汇总结果。 + */ +@Data +public class SystemInitializationPrecheckVO { + + /** 当前配置版本编号。 */ + private Integer configurationVersionId; + + /** 当前学期。 */ + private SemesterInitializationVO currentSemester; + + /** 当前部别数量。 */ + private Integer departmentCount; + + /** 当前教研室数量。 */ + private Integer researchOfficeCount; + + /** 当前部别人员数量。 */ + private Integer personnelCount; + + /** 所有必需检查项是否均已通过。 */ + private Boolean ready; + + /** 各项检查结果。 */ + private List items = + new ArrayList<>(); + + /** 本次检查时间。 */ + private LocalDateTime checkedAt; +} diff --git a/backend/roomroot-quartz/pom.xml b/backend/roomroot-quartz/pom.xml new file mode 100644 index 000000000..0296b488e --- /dev/null +++ b/backend/roomroot-quartz/pom.xml @@ -0,0 +1,34 @@ + + + + roomroot + com.roomroot + 3.9.2 + + 4.0.0 + + roomroot-quartz + + + quartz定时任务 + + + + + + + org.springframework.boot + spring-boot-starter-quartz + + + + + com.roomroot + roomroot-common + + + + + \ No newline at end of file diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/config/ScheduleConfig.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/config/ScheduleConfig.java new file mode 100644 index 000000000..b081eb8f7 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/config/ScheduleConfig.java @@ -0,0 +1,57 @@ +//package com.roomroot.quartz.config; +// +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import org.springframework.scheduling.quartz.SchedulerFactoryBean; +//import javax.sql.DataSource; +//import java.util.Properties; +// +///** +// * 定时任务配置(单机部署建议删除此类和qrtz数据库表,默认走内存会最高效) +// * +// * @author roomroot +// */ +//@Configuration +//public class ScheduleConfig +//{ +// @Bean +// public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) +// { +// SchedulerFactoryBean factory = new SchedulerFactoryBean(); +// factory.setDataSource(dataSource); +// +// // quartz参数 +// Properties prop = new Properties(); +// prop.put("org.quartz.scheduler.instanceName", "roomrootScheduler"); +// prop.put("org.quartz.scheduler.instanceId", "AUTO"); +// // 线程池配置 +// prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool"); +// prop.put("org.quartz.threadPool.threadCount", "20"); +// prop.put("org.quartz.threadPool.threadPriority", "5"); +// // JobStore配置 +// prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore"); +// // 集群配置 +// prop.put("org.quartz.jobStore.isClustered", "true"); +// prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000"); +// prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "10"); +// prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true"); +// +// // sqlserver 启用 +// // prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?"); +// prop.put("org.quartz.jobStore.misfireThreshold", "12000"); +// prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_"); +// factory.setQuartzProperties(prop); +// +// factory.setSchedulerName("roomrootScheduler"); +// // 延时启动 +// factory.setStartupDelay(1); +// factory.setApplicationContextSchedulerContextKey("applicationContextKey"); +// // 可选,QuartzScheduler +// // 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 +// factory.setOverwriteExistingJobs(true); +// // 设置自动启动,默认为true +// factory.setAutoStartup(true); +// +// return factory; +// } +//} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/controller/SysJobController.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/controller/SysJobController.java new file mode 100644 index 000000000..4d0c828c0 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/controller/SysJobController.java @@ -0,0 +1,185 @@ +package com.roomroot.quartz.controller; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.exception.job.TaskException; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.poi.ExcelUtil; +import com.roomroot.quartz.domain.SysJob; +import com.roomroot.quartz.service.ISysJobService; +import com.roomroot.quartz.util.CronUtils; +import com.roomroot.quartz.util.ScheduleUtils; + +/** + * 调度任务信息操作处理 + * + * @author roomroot + */ +@RestController +@RequestMapping("/monitor/job") +public class SysJobController extends BaseController +{ + @Autowired + private ISysJobService jobService; + + /** + * 查询定时任务列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJob sysJob) + { + startPage(); + List list = jobService.selectJobList(sysJob); + return getDataTable(list); + } + + /** + * 导出定时任务列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "定时任务", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysJob sysJob) + { + List list = jobService.selectJobList(sysJob); + ExcelUtil util = new ExcelUtil(SysJob.class); + util.exportExcel(response, list, "定时任务"); + } + + /** + * 获取定时任务详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobId}") + public AjaxResult getInfo(@PathVariable("jobId") Long jobId) + { + return success(jobService.selectJobById(jobId)); + } + + /** + * 新增定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:add')") + @Log(title = "定时任务", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@RequestBody SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("新增任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("新增任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setCreateBy(getUsername()); + return toAjax(jobService.insertJob(job)); + } + + /** + * 修改定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:edit')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@RequestBody SysJob job) throws SchedulerException, TaskException + { + if (!CronUtils.isValid(job.getCronExpression())) + { + return error("修改任务'" + job.getJobName() + "'失败,Cron表达式不正确"); + } + else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'rmi'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'ldap(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[] { Constants.HTTP, Constants.HTTPS })) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不允许'http(s)'调用"); + } + else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串存在违规"); + } + else if (!ScheduleUtils.whiteList(job.getInvokeTarget())) + { + return error("修改任务'" + job.getJobName() + "'失败,目标字符串不在白名单内"); + } + job.setUpdateBy(getUsername()); + return toAjax(jobService.updateJob(job)); + } + + /** + * 定时任务状态修改 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/changeStatus") + public AjaxResult changeStatus(@RequestBody SysJob job) throws SchedulerException + { + SysJob newJob = jobService.selectJobById(job.getJobId()); + newJob.setStatus(job.getStatus()); + return toAjax(jobService.changeStatus(newJob)); + } + + /** + * 定时任务立即执行一次 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:changeStatus')") + @Log(title = "定时任务", businessType = BusinessType.UPDATE) + @PutMapping("/run") + public AjaxResult run(@RequestBody SysJob job) throws SchedulerException + { + boolean result = jobService.run(job); + return result ? success() : error("任务不存在或已过期!"); + } + + /** + * 删除定时任务 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobIds}") + public AjaxResult remove(@PathVariable Long[] jobIds) throws SchedulerException + { + jobService.deleteJobByIds(jobIds); + return success(); + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/controller/SysJobLogController.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/controller/SysJobLogController.java new file mode 100644 index 000000000..89efaee38 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/controller/SysJobLogController.java @@ -0,0 +1,92 @@ +package com.roomroot.quartz.controller; + +import java.util.List; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.roomroot.common.annotation.Log; +import com.roomroot.common.core.controller.BaseController; +import com.roomroot.common.core.domain.AjaxResult; +import com.roomroot.common.core.page.TableDataInfo; +import com.roomroot.common.enums.BusinessType; +import com.roomroot.common.utils.poi.ExcelUtil; +import com.roomroot.quartz.domain.SysJobLog; +import com.roomroot.quartz.service.ISysJobLogService; + +/** + * 调度日志操作处理 + * + * @author roomroot + */ +@RestController +@RequestMapping("/monitor/jobLog") +public class SysJobLogController extends BaseController +{ + @Autowired + private ISysJobLogService jobLogService; + + /** + * 查询定时任务调度日志列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:list')") + @GetMapping("/list") + public TableDataInfo list(SysJobLog sysJobLog) + { + startPage(); + List list = jobLogService.selectJobLogList(sysJobLog); + return getDataTable(list); + } + + /** + * 导出定时任务调度日志列表 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:export')") + @Log(title = "任务调度日志", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, SysJobLog sysJobLog) + { + List list = jobLogService.selectJobLogList(sysJobLog); + ExcelUtil util = new ExcelUtil(SysJobLog.class); + util.exportExcel(response, list, "调度日志"); + } + + /** + * 根据调度编号获取详细信息 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:query')") + @GetMapping(value = "/{jobLogId}") + public AjaxResult getInfo(@PathVariable Long jobLogId) + { + return success(jobLogService.selectJobLogById(jobLogId)); + } + + + /** + * 删除定时任务调度日志 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "定时任务调度日志", businessType = BusinessType.DELETE) + @DeleteMapping("/{jobLogIds}") + public AjaxResult remove(@PathVariable Long[] jobLogIds) + { + return toAjax(jobLogService.deleteJobLogByIds(jobLogIds)); + } + + /** + * 清空定时任务调度日志 + */ + @PreAuthorize("@ss.hasPermi('monitor:job:remove')") + @Log(title = "调度日志", businessType = BusinessType.CLEAN) + @DeleteMapping("/clean") + public AjaxResult clean() + { + jobLogService.cleanJobLog(); + return success(); + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/domain/SysJob.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/domain/SysJob.java new file mode 100644 index 000000000..541e06433 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/domain/SysJob.java @@ -0,0 +1,173 @@ +package com.roomroot.quartz.domain; + +import java.util.Date; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.annotation.Excel.ColumnType; +import com.roomroot.common.constant.ScheduleConstants; +import com.roomroot.common.core.domain.BaseEntity; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.quartz.util.CronUtils; + +/** + * 定时任务调度表 sys_job + * + * @author roomroot + */ +@TableName("sys_job") +public class SysJob extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 任务ID */ + @Excel(name = "任务序号", cellType = ColumnType.NUMERIC) + private Long jobId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** cron执行表达式 */ + @Excel(name = "执行表达式 ") + private String cronExpression; + + /** cron计划策略 */ + @Excel(name = "计划策略 ", readConverterExp = "0=默认,1=立即触发执行,2=触发一次执行,3=不触发立即执行") + private String misfirePolicy = ScheduleConstants.MISFIRE_DEFAULT; + + /** 是否并发执行(0允许 1禁止) */ + @Excel(name = "并发执行", readConverterExp = "0=允许,1=禁止") + private String concurrent; + + /** 任务状态(0正常 1暂停) */ + @Excel(name = "任务状态", readConverterExp = "0=正常,1=暂停") + private String status; + + public Long getJobId() + { + return jobId; + } + + public void setJobId(Long jobId) + { + this.jobId = jobId; + } + + @NotBlank(message = "任务名称不能为空") + @Size(min = 0, max = 64, message = "任务名称不能超过64个字符") + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + @NotBlank(message = "调用目标字符串不能为空") + @Size(min = 0, max = 500, message = "调用目标字符串长度不能超过500个字符") + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + @NotBlank(message = "Cron执行表达式不能为空") + @Size(min = 0, max = 255, message = "Cron执行表达式不能超过255个字符") + public String getCronExpression() + { + return cronExpression; + } + + public void setCronExpression(String cronExpression) + { + this.cronExpression = cronExpression; + } + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public Date getNextValidTime() + { + if (StringUtils.isNotEmpty(cronExpression)) + { + return CronUtils.getNextExecution(cronExpression); + } + return null; + } + + public String getMisfirePolicy() + { + return misfirePolicy; + } + + public void setMisfirePolicy(String misfirePolicy) + { + this.misfirePolicy = misfirePolicy; + } + + public String getConcurrent() + { + return concurrent; + } + + public void setConcurrent(String concurrent) + { + this.concurrent = concurrent; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobId", getJobId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("cronExpression", getCronExpression()) + .append("nextValidTime", getNextValidTime()) + .append("misfirePolicy", getMisfirePolicy()) + .append("concurrent", getConcurrent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/domain/SysJobLog.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/domain/SysJobLog.java new file mode 100644 index 000000000..77cc5b6ee --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/domain/SysJobLog.java @@ -0,0 +1,160 @@ +package com.roomroot.quartz.domain; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * 定时任务调度日志表 sys_job_log + * + * @author roomroot + */ +@TableName("sys_job_log") +public class SysJobLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @Excel(name = "日志序号") + private Long jobLogId; + + /** 任务名称 */ + @Excel(name = "任务名称") + private String jobName; + + /** 任务组名 */ + @Excel(name = "任务组名") + private String jobGroup; + + /** 调用目标字符串 */ + @Excel(name = "调用目标字符串") + private String invokeTarget; + + /** 日志信息 */ + @Excel(name = "日志信息") + private String jobMessage; + + /** 执行状态(0正常 1失败) */ + @Excel(name = "执行状态", readConverterExp = "0=正常,1=失败") + private String status; + + /** 异常信息 */ + @Excel(name = "异常信息") + private String exceptionInfo; + + /** 开始时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startTime; + + /** 结束时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endTime; + + public Long getJobLogId() + { + return jobLogId; + } + + public void setJobLogId(Long jobLogId) + { + this.jobLogId = jobLogId; + } + + public String getJobName() + { + return jobName; + } + + public void setJobName(String jobName) + { + this.jobName = jobName; + } + + public String getJobGroup() + { + return jobGroup; + } + + public void setJobGroup(String jobGroup) + { + this.jobGroup = jobGroup; + } + + public String getInvokeTarget() + { + return invokeTarget; + } + + public void setInvokeTarget(String invokeTarget) + { + this.invokeTarget = invokeTarget; + } + + public String getJobMessage() + { + return jobMessage; + } + + public void setJobMessage(String jobMessage) + { + this.jobMessage = jobMessage; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getExceptionInfo() + { + return exceptionInfo; + } + + public void setExceptionInfo(String exceptionInfo) + { + this.exceptionInfo = exceptionInfo; + } + + public Date getStartTime() + { + return startTime; + } + + public void setStartTime(Date startTime) + { + this.startTime = startTime; + } + + public Date getEndTime() + { + return endTime; + } + + public void setEndTime(Date endTime) + { + this.endTime = endTime; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("jobLogId", getJobLogId()) + .append("jobName", getJobName()) + .append("jobGroup", getJobGroup()) + .append("jobMessage", getJobMessage()) + .append("status", getStatus()) + .append("exceptionInfo", getExceptionInfo()) + .append("startTime", getStartTime()) + .append("stopTime", getEndTime()) + .toString(); + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/mapper/SysJobLogMapper.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/mapper/SysJobLogMapper.java new file mode 100644 index 000000000..11ffe8726 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/mapper/SysJobLogMapper.java @@ -0,0 +1,65 @@ +package com.roomroot.quartz.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.quartz.domain.SysJobLog; + +/** + * 调度任务日志信息 数据层 + * + * @author roomroot + */ +public interface SysJobLogMapper extends BaseMapper +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 查询所有调度任务日志 + * + * @return 调度任务日志列表 + */ + public List selectJobLogAll(); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + * @return 结果 + */ + public int insertJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/mapper/SysJobMapper.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/mapper/SysJobMapper.java new file mode 100644 index 000000000..452f91faa --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/mapper/SysJobMapper.java @@ -0,0 +1,68 @@ +package com.roomroot.quartz.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.quartz.domain.SysJob; + +/** + * 调度任务信息 数据层 + * + * @author roomroot + */ +public interface SysJobMapper extends BaseMapper +{ + /** + * 查询调度任务日志集合 + * + * @param job 调度信息 + * @return 操作日志集合 + */ + public List selectJobList(SysJob job); + + /** + * 查询所有调度任务 + * + * @return 调度任务列表 + */ + public List selectJobAll(); + + /** + * 通过调度ID查询调度任务信息 + * + * @param jobId 调度ID + * @return 角色对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 通过调度ID删除调度任务信息 + * + * @param jobId 调度ID + * @return 结果 + */ + public int deleteJobById(Long jobId); + + /** + * 批量删除调度任务信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteJobByIds(Long[] ids); + + /** + * 修改调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int updateJob(SysJob job); + + /** + * 新增调度任务信息 + * + * @param job 调度任务信息 + * @return 结果 + */ + public int insertJob(SysJob job); +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/ISysJobLogService.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/ISysJobLogService.java new file mode 100644 index 000000000..e66523d83 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/ISysJobLogService.java @@ -0,0 +1,56 @@ +package com.roomroot.quartz.service; + +import java.util.List; +import com.roomroot.quartz.domain.SysJobLog; + +/** + * 定时任务调度日志信息信息 服务层 + * + * @author roomroot + */ +public interface ISysJobLogService +{ + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + public List selectJobLogList(SysJobLog jobLog); + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + public SysJobLog selectJobLogById(Long jobLogId); + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + public void addJobLog(SysJobLog jobLog); + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的日志ID + * @return 结果 + */ + public int deleteJobLogByIds(Long[] logIds); + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + * @return 结果 + */ + public int deleteJobLogById(Long jobId); + + /** + * 清空任务日志 + */ + public void cleanJobLog(); +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/ISysJobService.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/ISysJobService.java new file mode 100644 index 000000000..6b4abea86 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/ISysJobService.java @@ -0,0 +1,102 @@ +package com.roomroot.quartz.service; + +import java.util.List; +import org.quartz.SchedulerException; +import com.roomroot.common.exception.job.TaskException; +import com.roomroot.quartz.domain.SysJob; + +/** + * 定时任务调度信息信息 服务层 + * + * @author roomroot + */ +public interface ISysJobService +{ + /** + * 获取quartz调度器的计划任务 + * + * @param job 调度信息 + * @return 调度任务集合 + */ + public List selectJobList(SysJob job); + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + public SysJob selectJobById(Long jobId); + + /** + * 暂停任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int pauseJob(SysJob job) throws SchedulerException; + + /** + * 恢复任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int resumeJob(SysJob job) throws SchedulerException; + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + * @return 结果 + */ + public int deleteJob(SysJob job) throws SchedulerException; + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + public void deleteJobByIds(Long[] jobIds) throws SchedulerException; + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + * @return 结果 + */ + public int changeStatus(SysJob job) throws SchedulerException; + + /** + * 立即运行任务 + * + * @param job 调度信息 + * @return 结果 + */ + public boolean run(SysJob job) throws SchedulerException; + + /** + * 新增任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int insertJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 更新任务 + * + * @param job 调度信息 + * @return 结果 + */ + public int updateJob(SysJob job) throws SchedulerException, TaskException; + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + public boolean checkCronExpressionIsValid(String cronExpression); +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/impl/SysJobLogServiceImpl.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/impl/SysJobLogServiceImpl.java new file mode 100644 index 000000000..18ea5d56c --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/impl/SysJobLogServiceImpl.java @@ -0,0 +1,87 @@ +package com.roomroot.quartz.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.roomroot.quartz.domain.SysJobLog; +import com.roomroot.quartz.mapper.SysJobLogMapper; +import com.roomroot.quartz.service.ISysJobLogService; + +/** + * 定时任务调度日志信息 服务层 + * + * @author roomroot + */ +@Service +public class SysJobLogServiceImpl implements ISysJobLogService +{ + @Autowired + private SysJobLogMapper jobLogMapper; + + /** + * 获取quartz调度器日志的计划任务 + * + * @param jobLog 调度日志信息 + * @return 调度任务日志集合 + */ + @Override + public List selectJobLogList(SysJobLog jobLog) + { + return jobLogMapper.selectJobLogList(jobLog); + } + + /** + * 通过调度任务日志ID查询调度信息 + * + * @param jobLogId 调度任务日志ID + * @return 调度任务日志对象信息 + */ + @Override + public SysJobLog selectJobLogById(Long jobLogId) + { + return jobLogMapper.selectJobLogById(jobLogId); + } + + /** + * 新增任务日志 + * + * @param jobLog 调度日志信息 + */ + @Override + public void addJobLog(SysJobLog jobLog) + { + jobLogMapper.insertJobLog(jobLog); + } + + /** + * 批量删除调度日志信息 + * + * @param logIds 需要删除的数据ID + * @return 结果 + */ + @Override + public int deleteJobLogByIds(Long[] logIds) + { + return jobLogMapper.deleteJobLogByIds(logIds); + } + + /** + * 删除任务日志 + * + * @param jobId 调度日志ID + */ + @Override + public int deleteJobLogById(Long jobId) + { + return jobLogMapper.deleteJobLogById(jobId); + } + + /** + * 清空任务日志 + */ + @Override + public void cleanJobLog() + { + jobLogMapper.cleanJobLog(); + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/impl/SysJobServiceImpl.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/impl/SysJobServiceImpl.java new file mode 100644 index 000000000..a4d447cf5 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/service/impl/SysJobServiceImpl.java @@ -0,0 +1,261 @@ +package com.roomroot.quartz.service.impl; + +import java.util.List; +import jakarta.annotation.PostConstruct; +import org.quartz.JobDataMap; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.roomroot.common.constant.ScheduleConstants; +import com.roomroot.common.exception.job.TaskException; +import com.roomroot.quartz.domain.SysJob; +import com.roomroot.quartz.mapper.SysJobMapper; +import com.roomroot.quartz.service.ISysJobService; +import com.roomroot.quartz.util.CronUtils; +import com.roomroot.quartz.util.ScheduleUtils; + +/** + * 定时任务调度信息 服务层 + * + * @author roomroot + */ +@Service +public class SysJobServiceImpl implements ISysJobService +{ + @Autowired + private Scheduler scheduler; + + @Autowired + private SysJobMapper jobMapper; + + /** + * 项目启动时,初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理(注:不能手动修改数据库ID和任务组名,否则会导致脏数据) + */ + @PostConstruct + public void init() throws SchedulerException, TaskException + { + scheduler.clear(); + List jobList = jobMapper.selectJobAll(); + for (SysJob job : jobList) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + } + + /** + * 获取quartz调度器的计划任务列表 + * + * @param job 调度信息 + * @return + */ + @Override + public List selectJobList(SysJob job) + { + return jobMapper.selectJobList(job); + } + + /** + * 通过调度任务ID查询调度信息 + * + * @param jobId 调度任务ID + * @return 调度任务对象信息 + */ + @Override + public SysJob selectJobById(Long jobId) + { + return jobMapper.selectJobById(jobId); + } + + /** + * 暂停任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int pauseJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 恢复任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int resumeJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + job.setStatus(ScheduleConstants.Status.NORMAL.getValue()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 删除任务后,所对应的trigger也将被删除 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteJob(SysJob job) throws SchedulerException + { + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + int rows = jobMapper.deleteJobById(jobId); + if (rows > 0) + { + scheduler.deleteJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + return rows; + } + + /** + * 批量删除调度信息 + * + * @param jobIds 需要删除的任务ID + * @return 结果 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteJobByIds(Long[] jobIds) throws SchedulerException + { + for (Long jobId : jobIds) + { + SysJob job = jobMapper.selectJobById(jobId); + deleteJob(job); + } + } + + /** + * 任务调度状态修改 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int changeStatus(SysJob job) throws SchedulerException + { + int rows = 0; + String status = job.getStatus(); + if (ScheduleConstants.Status.NORMAL.getValue().equals(status)) + { + rows = resumeJob(job); + } + else if (ScheduleConstants.Status.PAUSE.getValue().equals(status)) + { + rows = pauseJob(job); + } + return rows; + } + + /** + * 立即运行任务 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public boolean run(SysJob job) throws SchedulerException + { + boolean result = false; + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + SysJob properties = selectJobById(job.getJobId()); + // 参数 + JobDataMap dataMap = new JobDataMap(); + dataMap.put(ScheduleConstants.TASK_PROPERTIES, properties); + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + result = true; + scheduler.triggerJob(jobKey, dataMap); + } + return result; + } + + /** + * 新增任务 + * + * @param job 调度信息 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int insertJob(SysJob job) throws SchedulerException, TaskException + { + job.setStatus(ScheduleConstants.Status.PAUSE.getValue()); + int rows = jobMapper.insertJob(job); + if (rows > 0) + { + ScheduleUtils.createScheduleJob(scheduler, job); + } + return rows; + } + + /** + * 更新任务的时间表达式 + * + * @param job 调度信息 + */ + @Override + @Transactional(rollbackFor = Exception.class) + public int updateJob(SysJob job) throws SchedulerException, TaskException + { + SysJob properties = selectJobById(job.getJobId()); + int rows = jobMapper.updateJob(job); + if (rows > 0) + { + updateSchedulerJob(job, properties.getJobGroup()); + } + return rows; + } + + /** + * 更新任务 + * + * @param job 任务对象 + * @param jobGroup 任务组名 + */ + public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException + { + Long jobId = job.getJobId(); + // 判断是否存在 + JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup); + if (scheduler.checkExists(jobKey)) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(jobKey); + } + ScheduleUtils.createScheduleJob(scheduler, job); + } + + /** + * 校验cron表达式是否有效 + * + * @param cronExpression 表达式 + * @return 结果 + */ + @Override + public boolean checkCronExpressionIsValid(String cronExpression) + { + return CronUtils.isValid(cronExpression); + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/task/RyTask.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/task/RyTask.java new file mode 100644 index 000000000..1afc4e721 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/task/RyTask.java @@ -0,0 +1,28 @@ +package com.roomroot.quartz.task; + +import org.springframework.stereotype.Component; +import com.roomroot.common.utils.StringUtils; + +/** + * 定时任务调度测试 + * + * @author roomroot + */ +@Component("ryTask") +public class RyTask +{ + public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) + { + System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i)); + } + + public void ryParams(String params) + { + System.out.println("执行有参方法:" + params); + } + + public void ryNoParams() + { + System.out.println("执行无参方法"); + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/AbstractQuartzJob.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/AbstractQuartzJob.java new file mode 100644 index 000000000..06dea80e9 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/AbstractQuartzJob.java @@ -0,0 +1,106 @@ +package com.roomroot.quartz.util; + +import java.util.Date; +import org.quartz.Job; +import org.quartz.JobExecutionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.constant.ScheduleConstants; +import com.roomroot.common.utils.ExceptionUtil; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.bean.BeanUtils; +import com.roomroot.common.utils.spring.SpringUtils; +import com.roomroot.quartz.domain.SysJob; +import com.roomroot.quartz.domain.SysJobLog; +import com.roomroot.quartz.service.ISysJobLogService; + +/** + * 抽象quartz调用 + * + * @author roomroot + */ +public abstract class AbstractQuartzJob implements Job +{ + private static final Logger log = LoggerFactory.getLogger(AbstractQuartzJob.class); + + /** + * 线程本地变量 + */ + private static ThreadLocal threadLocal = new ThreadLocal<>(); + + @Override + public void execute(JobExecutionContext context) + { + SysJob sysJob = new SysJob(); + BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES)); + try + { + before(context, sysJob); + if (sysJob != null) + { + doExecute(context, sysJob); + } + after(context, sysJob, null); + } + catch (Exception e) + { + log.error("任务执行异常 - :", e); + after(context, sysJob, e); + } + } + + /** + * 执行前 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void before(JobExecutionContext context, SysJob sysJob) + { + threadLocal.set(new Date()); + } + + /** + * 执行后 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + */ + protected void after(JobExecutionContext context, SysJob sysJob, Exception e) + { + Date startTime = threadLocal.get(); + threadLocal.remove(); + + final SysJobLog sysJobLog = new SysJobLog(); + sysJobLog.setJobName(sysJob.getJobName()); + sysJobLog.setJobGroup(sysJob.getJobGroup()); + sysJobLog.setInvokeTarget(sysJob.getInvokeTarget()); + sysJobLog.setStartTime(startTime); + sysJobLog.setEndTime(new Date()); + long runMs = sysJobLog.getEndTime().getTime() - sysJobLog.getStartTime().getTime(); + sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒"); + if (e != null) + { + sysJobLog.setStatus(Constants.FAIL); + String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000); + sysJobLog.setExceptionInfo(errorMsg); + } + else + { + sysJobLog.setStatus(Constants.SUCCESS); + } + + // 写入数据库当中 + SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog); + } + + /** + * 执行方法,由子类重载 + * + * @param context 工作执行上下文对象 + * @param sysJob 系统计划任务 + * @throws Exception 执行过程中的异常 + */ + protected abstract void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception; +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/CronUtils.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/CronUtils.java new file mode 100644 index 000000000..784e9eda1 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/CronUtils.java @@ -0,0 +1,63 @@ +package com.roomroot.quartz.util; + +import java.text.ParseException; +import java.util.Date; +import org.quartz.CronExpression; + +/** + * cron表达式工具类 + * + * @author roomroot + * + */ +public class CronUtils +{ + /** + * 返回一个布尔值代表一个给定的Cron表达式的有效性 + * + * @param cronExpression Cron表达式 + * @return boolean 表达式是否有效 + */ + public static boolean isValid(String cronExpression) + { + return CronExpression.isValidExpression(cronExpression); + } + + /** + * 返回一个字符串值,表示该消息无效Cron表达式给出有效性 + * + * @param cronExpression Cron表达式 + * @return String 无效时返回表达式错误描述,如果有效返回null + */ + public static String getInvalidMessage(String cronExpression) + { + try + { + new CronExpression(cronExpression); + return null; + } + catch (ParseException pe) + { + return pe.getMessage(); + } + } + + /** + * 返回下一个执行时间根据给定的Cron表达式 + * + * @param cronExpression Cron表达式 + * @return Date 下次Cron表达式执行时间 + */ + public static Date getNextExecution(String cronExpression) + { + try + { + CronExpression cron = new CronExpression(cronExpression); + return cron.getNextValidTimeAfter(new Date(System.currentTimeMillis())); + } + catch (ParseException e) + { + throw new IllegalArgumentException(e.getMessage()); + } + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/JobInvokeUtil.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/JobInvokeUtil.java new file mode 100644 index 000000000..23f3ea66b --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/JobInvokeUtil.java @@ -0,0 +1,182 @@ +package com.roomroot.quartz.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.LinkedList; +import java.util.List; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.spring.SpringUtils; +import com.roomroot.quartz.domain.SysJob; + +/** + * 任务执行工具 + * + * @author roomroot + */ +public class JobInvokeUtil +{ + /** + * 执行方法 + * + * @param sysJob 系统任务 + */ + public static void invokeMethod(SysJob sysJob) throws Exception + { + String invokeTarget = sysJob.getInvokeTarget(); + String beanName = getBeanName(invokeTarget); + String methodName = getMethodName(invokeTarget); + List methodParams = getMethodParams(invokeTarget); + + if (!isValidClassName(beanName)) + { + Object bean = SpringUtils.getBean(beanName); + invokeMethod(bean, methodName, methodParams); + } + else + { + Object bean = Class.forName(beanName).getDeclaredConstructor().newInstance(); + invokeMethod(bean, methodName, methodParams); + } + } + + /** + * 调用任务方法 + * + * @param bean 目标对象 + * @param methodName 方法名称 + * @param methodParams 方法参数 + */ + private static void invokeMethod(Object bean, String methodName, List methodParams) + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, + InvocationTargetException + { + if (StringUtils.isNotNull(methodParams) && methodParams.size() > 0) + { + Method method = bean.getClass().getMethod(methodName, getMethodParamsType(methodParams)); + method.invoke(bean, getMethodParamsValue(methodParams)); + } + else + { + Method method = bean.getClass().getMethod(methodName); + method.invoke(bean); + } + } + + /** + * 校验是否为为class包名 + * + * @param invokeTarget 名称 + * @return true是 false否 + */ + public static boolean isValidClassName(String invokeTarget) + { + return StringUtils.countMatches(invokeTarget, ".") > 1; + } + + /** + * 获取bean名称 + * + * @param invokeTarget 目标字符串 + * @return bean名称 + */ + public static String getBeanName(String invokeTarget) + { + String beanName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringBeforeLast(beanName, "."); + } + + /** + * 获取bean方法 + * + * @param invokeTarget 目标字符串 + * @return method方法 + */ + public static String getMethodName(String invokeTarget) + { + String methodName = StringUtils.substringBefore(invokeTarget, "("); + return StringUtils.substringAfterLast(methodName, "."); + } + + /** + * 获取method方法参数相关列表 + * + * @param invokeTarget 目标字符串 + * @return method方法相关参数列表 + */ + public static List getMethodParams(String invokeTarget) + { + String methodStr = StringUtils.substringBetweenLast(invokeTarget, "(", ")"); + if (StringUtils.isEmpty(methodStr)) + { + return null; + } + String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)"); + List classs = new LinkedList<>(); + for (int i = 0; i < methodParams.length; i++) + { + String str = StringUtils.trimToEmpty(methodParams[i]); + // String字符串类型,以'或"开头 + if (StringUtils.startsWithAny(str, "'", "\"")) + { + classs.add(new Object[] { StringUtils.substring(str, 1, str.length() - 1), String.class }); + } + // boolean布尔类型,等于true或者false + else if ("true".equalsIgnoreCase(str) || "false".equalsIgnoreCase(str)) + { + classs.add(new Object[] { Boolean.valueOf(str), Boolean.class }); + } + // long长整形,以L结尾 + else if (StringUtils.endsWith(str, "L")) + { + classs.add(new Object[] { Long.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Long.class }); + } + // double浮点类型,以D结尾 + else if (StringUtils.endsWith(str, "D")) + { + classs.add(new Object[] { Double.valueOf(StringUtils.substring(str, 0, str.length() - 1)), Double.class }); + } + // 其他类型归类为整形 + else + { + classs.add(new Object[] { Integer.valueOf(str), Integer.class }); + } + } + return classs; + } + + /** + * 获取参数类型 + * + * @param methodParams 参数相关列表 + * @return 参数类型列表 + */ + public static Class[] getMethodParamsType(List methodParams) + { + Class[] classs = new Class[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Class) os[1]; + index++; + } + return classs; + } + + /** + * 获取参数值 + * + * @param methodParams 参数相关列表 + * @return 参数值列表 + */ + public static Object[] getMethodParamsValue(List methodParams) + { + Object[] classs = new Object[methodParams.size()]; + int index = 0; + for (Object[] os : methodParams) + { + classs[index] = (Object) os[0]; + index++; + } + return classs; + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/QuartzDisallowConcurrentExecution.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/QuartzDisallowConcurrentExecution.java new file mode 100644 index 000000000..2943a5c16 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/QuartzDisallowConcurrentExecution.java @@ -0,0 +1,21 @@ +package com.roomroot.quartz.util; + +import org.quartz.DisallowConcurrentExecution; +import org.quartz.JobExecutionContext; +import com.roomroot.quartz.domain.SysJob; + +/** + * 定时任务处理(禁止并发执行) + * + * @author roomroot + * + */ +@DisallowConcurrentExecution +public class QuartzDisallowConcurrentExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/QuartzJobExecution.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/QuartzJobExecution.java new file mode 100644 index 000000000..5f6183ce9 --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/QuartzJobExecution.java @@ -0,0 +1,19 @@ +package com.roomroot.quartz.util; + +import org.quartz.JobExecutionContext; +import com.roomroot.quartz.domain.SysJob; + +/** + * 定时任务处理(允许并发执行) + * + * @author roomroot + * + */ +public class QuartzJobExecution extends AbstractQuartzJob +{ + @Override + protected void doExecute(JobExecutionContext context, SysJob sysJob) throws Exception + { + JobInvokeUtil.invokeMethod(sysJob); + } +} diff --git a/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/ScheduleUtils.java b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/ScheduleUtils.java new file mode 100644 index 000000000..cbb69c57d --- /dev/null +++ b/backend/roomroot-quartz/src/main/java/com/roomroot/quartz/util/ScheduleUtils.java @@ -0,0 +1,141 @@ +package com.roomroot.quartz.util; + +import org.quartz.CronScheduleBuilder; +import org.quartz.CronTrigger; +import org.quartz.Job; +import org.quartz.JobBuilder; +import org.quartz.JobDetail; +import org.quartz.JobKey; +import org.quartz.Scheduler; +import org.quartz.SchedulerException; +import org.quartz.TriggerBuilder; +import org.quartz.TriggerKey; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.constant.ScheduleConstants; +import com.roomroot.common.exception.job.TaskException; +import com.roomroot.common.exception.job.TaskException.Code; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.spring.SpringUtils; +import com.roomroot.quartz.domain.SysJob; + +/** + * 定时任务工具类 + * + * @author roomroot + * + */ +public class ScheduleUtils +{ + /** + * 得到quartz任务类 + * + * @param sysJob 执行计划 + * @return 具体执行任务类 + */ + private static Class getQuartzJobClass(SysJob sysJob) + { + boolean isConcurrent = "0".equals(sysJob.getConcurrent()); + return isConcurrent ? QuartzJobExecution.class : QuartzDisallowConcurrentExecution.class; + } + + /** + * 构建任务触发对象 + */ + public static TriggerKey getTriggerKey(Long jobId, String jobGroup) + { + return TriggerKey.triggerKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 构建任务键对象 + */ + public static JobKey getJobKey(Long jobId, String jobGroup) + { + return JobKey.jobKey(ScheduleConstants.TASK_CLASS_NAME + jobId, jobGroup); + } + + /** + * 创建定时任务 + */ + public static void createScheduleJob(Scheduler scheduler, SysJob job) throws SchedulerException, TaskException + { + Class jobClass = getQuartzJobClass(job); + // 构建job信息 + Long jobId = job.getJobId(); + String jobGroup = job.getJobGroup(); + JobDetail jobDetail = JobBuilder.newJob(jobClass).withIdentity(getJobKey(jobId, jobGroup)).build(); + + // 表达式调度构建器 + CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(job.getCronExpression()); + cronScheduleBuilder = handleCronScheduleMisfirePolicy(job, cronScheduleBuilder); + + // 按新的cronExpression表达式构建一个新的trigger + CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(getTriggerKey(jobId, jobGroup)) + .withSchedule(cronScheduleBuilder).build(); + + // 放入参数,运行时的方法可以获取 + jobDetail.getJobDataMap().put(ScheduleConstants.TASK_PROPERTIES, job); + + // 判断是否存在 + if (scheduler.checkExists(getJobKey(jobId, jobGroup))) + { + // 防止创建时存在数据问题 先移除,然后在执行创建操作 + scheduler.deleteJob(getJobKey(jobId, jobGroup)); + } + + // 判断任务是否过期 + if (StringUtils.isNotNull(CronUtils.getNextExecution(job.getCronExpression()))) + { + // 执行调度任务 + scheduler.scheduleJob(jobDetail, trigger); + } + + // 暂停任务 + if (job.getStatus().equals(ScheduleConstants.Status.PAUSE.getValue())) + { + scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup)); + } + } + + /** + * 设置定时任务策略 + */ + public static CronScheduleBuilder handleCronScheduleMisfirePolicy(SysJob job, CronScheduleBuilder cb) + throws TaskException + { + switch (job.getMisfirePolicy()) + { + case ScheduleConstants.MISFIRE_DEFAULT: + return cb; + case ScheduleConstants.MISFIRE_IGNORE_MISFIRES: + return cb.withMisfireHandlingInstructionIgnoreMisfires(); + case ScheduleConstants.MISFIRE_FIRE_AND_PROCEED: + return cb.withMisfireHandlingInstructionFireAndProceed(); + case ScheduleConstants.MISFIRE_DO_NOTHING: + return cb.withMisfireHandlingInstructionDoNothing(); + default: + throw new TaskException("The task misfire policy '" + job.getMisfirePolicy() + + "' cannot be used in cron schedule tasks", Code.CONFIG_ERROR); + } + } + + /** + * 检查包名是否为白名单配置 + * + * @param invokeTarget 目标字符串 + * @return 结果 + */ + public static boolean whiteList(String invokeTarget) + { + String packageName = StringUtils.substringBefore(invokeTarget, "("); + int count = StringUtils.countMatches(packageName, "."); + if (count > 1) + { + return StringUtils.startsWithAny(invokeTarget, Constants.JOB_WHITELIST_STR); + } + Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]); + String beanPackageName = obj.getClass().getPackage().getName(); + return StringUtils.startsWithAny(beanPackageName, Constants.JOB_WHITELIST_STR) + && !StringUtils.startsWithAny(beanPackageName, Constants.JOB_ERROR_STR); + } +} diff --git a/backend/roomroot-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml b/backend/roomroot-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml new file mode 100644 index 000000000..b06fd2acd --- /dev/null +++ b/backend/roomroot-quartz/src/main/resources/mapper/quartz/SysJobLogMapper.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + select job_log_id, job_name, job_group, invoke_target, job_message, status, exception_info, start_time, end_time, create_time + from sys_job_log + + + + + + + + + + delete from sys_job_log where job_log_id = #{jobLogId} + + + + delete from sys_job_log where job_log_id in + + #{jobLogId} + + + + + truncate table sys_job_log + + + + insert into sys_job_log( + job_log_id, + job_name, + job_group, + invoke_target, + job_message, + status, + exception_info, + start_time, + end_time, + create_time + )values( + #{jobLogId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{jobMessage}, + #{status}, + #{exceptionInfo}, + #{startTime}, + #{endTime}, + sysdate() + ) + + + \ No newline at end of file diff --git a/backend/roomroot-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml b/backend/roomroot-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml new file mode 100644 index 000000000..9b4423bb7 --- /dev/null +++ b/backend/roomroot-quartz/src/main/resources/mapper/quartz/SysJobMapper.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + select job_id, job_name, job_group, invoke_target, cron_expression, misfire_policy, concurrent, status, create_by, create_time, update_by, update_time, remark + from sys_job + + + + + + + + + + delete from sys_job where job_id = #{jobId} + + + + delete from sys_job where job_id in + + #{jobId} + + + + + update sys_job + + job_name = #{jobName}, + job_group = #{jobGroup}, + invoke_target = #{invokeTarget}, + cron_expression = #{cronExpression}, + misfire_policy = #{misfirePolicy}, + concurrent = #{concurrent}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where job_id = #{jobId} + + + + insert into sys_job( + job_id, + job_name, + job_group, + invoke_target, + cron_expression, + misfire_policy, + concurrent, + status, + remark, + create_by, + create_time + )values( + #{jobId}, + #{jobName}, + #{jobGroup}, + #{invokeTarget}, + #{cronExpression}, + #{misfirePolicy}, + #{concurrent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/backend/roomroot-system/pom.xml b/backend/roomroot-system/pom.xml new file mode 100644 index 000000000..15a320dc2 --- /dev/null +++ b/backend/roomroot-system/pom.xml @@ -0,0 +1,34 @@ + + + + roomroot + com.roomroot + 3.9.2 + + 4.0.0 + + roomroot-system + + + system系统模块 + + + + + + + com.roomroot + roomroot-common + + + + + com.baomidou + mybatis-plus-spring-boot3-starter + + + + + \ No newline at end of file diff --git a/backend/roomroot-system/roomroot-system.iml b/backend/roomroot-system/roomroot-system.iml new file mode 100644 index 000000000..abe781f78 --- /dev/null +++ b/backend/roomroot-system/roomroot-system.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysCache.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysCache.java new file mode 100644 index 000000000..169a53127 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysCache.java @@ -0,0 +1,81 @@ +package com.roomroot.system.domain; + +import com.roomroot.common.utils.StringUtils; + +/** + * 缓存信息 + * + * @author roomroot + */ +public class SysCache +{ + /** 缓存名称 */ + private String cacheName = ""; + + /** 缓存键名 */ + private String cacheKey = ""; + + /** 缓存内容 */ + private String cacheValue = ""; + + /** 备注 */ + private String remark = ""; + + public SysCache() + { + + } + + public SysCache(String cacheName, String remark) + { + this.cacheName = cacheName; + this.remark = remark; + } + + public SysCache(String cacheName, String cacheKey, String cacheValue) + { + this.cacheName = StringUtils.replace(cacheName, ":", ""); + this.cacheKey = StringUtils.replace(cacheKey, cacheName, ""); + this.cacheValue = cacheValue; + } + + public String getCacheName() + { + return cacheName; + } + + public void setCacheName(String cacheName) + { + this.cacheName = cacheName; + } + + public String getCacheKey() + { + return cacheKey; + } + + public void setCacheKey(String cacheKey) + { + this.cacheKey = cacheKey; + } + + public String getCacheValue() + { + return cacheValue; + } + + public void setCacheValue(String cacheValue) + { + this.cacheValue = cacheValue; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysConfig.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysConfig.java new file mode 100644 index 000000000..9d4f156de --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysConfig.java @@ -0,0 +1,113 @@ +package com.roomroot.system.domain; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.annotation.Excel.ColumnType; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * 参数配置表 sys_config + * + * @author roomroot + */ +@TableName("sys_config") +public class SysConfig extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 参数主键 */ + @Excel(name = "参数主键", cellType = ColumnType.NUMERIC) + private Long configId; + + /** 参数名称 */ + @Excel(name = "参数名称") + private String configName; + + /** 参数键名 */ + @Excel(name = "参数键名") + private String configKey; + + /** 参数键值 */ + @Excel(name = "参数键值") + private String configValue; + + /** 系统内置(Y是 N否) */ + @Excel(name = "系统内置", readConverterExp = "Y=是,N=否") + private String configType; + + public Long getConfigId() + { + return configId; + } + + public void setConfigId(Long configId) + { + this.configId = configId; + } + + @NotBlank(message = "参数名称不能为空") + @Size(min = 0, max = 100, message = "参数名称不能超过100个字符") + public String getConfigName() + { + return configName; + } + + public void setConfigName(String configName) + { + this.configName = configName; + } + + @NotBlank(message = "参数键名长度不能为空") + @Size(min = 0, max = 100, message = "参数键名长度不能超过100个字符") + public String getConfigKey() + { + return configKey; + } + + public void setConfigKey(String configKey) + { + this.configKey = configKey; + } + + @NotBlank(message = "参数键值不能为空") + @Size(min = 0, max = 500, message = "参数键值长度不能超过500个字符") + public String getConfigValue() + { + return configValue; + } + + public void setConfigValue(String configValue) + { + this.configValue = configValue; + } + + public String getConfigType() + { + return configType; + } + + public void setConfigType(String configType) + { + this.configType = configType; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("configId", getConfigId()) + .append("configName", getConfigName()) + .append("configKey", getConfigKey()) + .append("configValue", getConfigValue()) + .append("configType", getConfigType()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysLogininfor.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysLogininfor.java new file mode 100644 index 000000000..3bc13944f --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysLogininfor.java @@ -0,0 +1,149 @@ +package com.roomroot.system.domain; + +import java.util.Date; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.annotation.Excel.ColumnType; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * 系统访问记录表 sys_logininfor + * + * @author roomroot + */ +@TableName("sys_logininfor") +public class SysLogininfor extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** ID */ + @TableId(type = IdType.AUTO) + @Excel(name = "序号", cellType = ColumnType.NUMERIC) + private Long infoId; + + /** 用户账号 */ + @Excel(name = "用户账号") + private String userName; + + /** 登录状态 0成功 1失败 */ + @Excel(name = "登录状态", readConverterExp = "0=成功,1=失败") + private String status; + + /** 登录IP地址 */ + @Excel(name = "登录地址") + private String ipaddr; + + /** 登录地点 */ + @Excel(name = "登录地点") + private String loginLocation; + + /** 浏览器类型 */ + @Excel(name = "浏览器") + private String browser; + + /** 操作系统 */ + @Excel(name = "操作系统") + private String os; + + /** 提示消息 */ + @Excel(name = "提示消息") + private String msg; + + /** 访问时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "访问时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date loginTime; + + public Long getInfoId() + { + return infoId; + } + + public void setInfoId(Long infoId) + { + this.infoId = infoId; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public String getMsg() + { + return msg; + } + + public void setMsg(String msg) + { + this.msg = msg; + } + + public Date getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Date loginTime) + { + this.loginTime = loginTime; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysNotice.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysNotice.java new file mode 100644 index 000000000..aebb52e81 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysNotice.java @@ -0,0 +1,119 @@ +package com.roomroot.system.domain; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.roomroot.common.core.domain.BaseEntity; +import com.roomroot.common.xss.Xss; + +/** + * 通知公告表 sys_notice + * + * @author roomroot + */ +@TableName("sys_notice") +public class SysNotice extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 公告ID */ + private Long noticeId; + + /** 公告标题 */ + private String noticeTitle; + + /** 公告类型(1通知 2公告) */ + private String noticeType; + + /** 公告内容 */ + private String noticeContent; + + /** 公告状态(0正常 1关闭) */ + private String status; + + /** 是否已读 */ + @JsonProperty("isRead") + private boolean isRead; + + public Long getNoticeId() + { + return noticeId; + } + + public void setNoticeId(Long noticeId) + { + this.noticeId = noticeId; + } + + public void setNoticeTitle(String noticeTitle) + { + this.noticeTitle = noticeTitle; + } + + @Xss(message = "公告标题不能包含脚本字符") + @NotBlank(message = "公告标题不能为空") + @Size(min = 0, max = 50, message = "公告标题不能超过50个字符") + public String getNoticeTitle() + { + return noticeTitle; + } + + public void setNoticeType(String noticeType) + { + this.noticeType = noticeType; + } + + public String getNoticeType() + { + return noticeType; + } + + public void setNoticeContent(String noticeContent) + { + this.noticeContent = noticeContent; + } + + public String getNoticeContent() + { + return noticeContent; + } + + public void setStatus(String status) + { + this.status = status; + } + + public String getStatus() + { + return status; + } + + public boolean getIsRead() + { + return isRead; + } + + public void setIsRead(boolean isRead) + { + this.isRead = isRead; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("noticeId", getNoticeId()) + .append("noticeTitle", getNoticeTitle()) + .append("noticeType", getNoticeType()) + .append("noticeContent", getNoticeContent()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysNoticeRead.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysNoticeRead.java new file mode 100644 index 000000000..96ddcdcff --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysNoticeRead.java @@ -0,0 +1,78 @@ +package com.roomroot.system.domain; + +import java.util.Date; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; + +/** + * 公告已读记录表 sys_notice_read + * + * @author roomroot + */ +@TableName("sys_notice_read") +public class SysNoticeRead +{ + /** 主键 */ + private Long readId; + + /** 公告ID */ + private Long noticeId; + + /** 用户ID */ + private Long userId; + + /** 阅读时间 */ + private Date readTime; + + public Long getReadId() + { + return readId; + } + + public void setReadId(Long readId) + { + this.readId = readId; + } + + public Long getNoticeId() + { + return noticeId; + } + + public void setNoticeId(Long noticeId) + { + this.noticeId = noticeId; + } + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Date getReadTime() + { + return readTime; + } + + public void setReadTime(Date readTime) + { + this.readTime = readTime; + } + + @Override + public String toString() + { + return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE) + .append("readId", getReadId()) + .append("noticeId", getNoticeId()) + .append("userId", getUserId()) + .append("readTime", getReadTime()) + .toString(); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysOperLog.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysOperLog.java new file mode 100644 index 000000000..b94f5f9d2 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysOperLog.java @@ -0,0 +1,271 @@ +package com.roomroot.system.domain; + +import java.util.Date; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.annotation.Excel.ColumnType; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * 操作日志记录表 oper_log + * + * @author roomroot + */ +@TableName("sys_oper_log") +public class SysOperLog extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 日志主键 */ + @Excel(name = "操作序号", cellType = ColumnType.NUMERIC) + private Long operId; + + /** 操作模块 */ + @Excel(name = "操作模块") + private String title; + + /** 业务类型(0其它 1新增 2修改 3删除) */ + @Excel(name = "业务类型", readConverterExp = "0=其它,1=新增,2=修改,3=删除,4=授权,5=导出,6=导入,7=强退,8=生成代码,9=清空数据") + private Integer businessType; + + /** 业务类型数组 */ + private Integer[] businessTypes; + + /** 请求方法 */ + @Excel(name = "请求方法") + private String method; + + /** 请求方式 */ + @Excel(name = "请求方式") + private String requestMethod; + + /** 操作类别(0其它 1后台用户 2手机端用户) */ + @Excel(name = "操作类别", readConverterExp = "0=其它,1=后台用户,2=手机端用户") + private Integer operatorType; + + /** 操作人员 */ + @Excel(name = "操作人员") + private String operName; + + /** 部门名称 */ + @Excel(name = "部门名称") + private String deptName; + + /** 请求url */ + @Excel(name = "请求地址") + private String operUrl; + + /** 操作地址 */ + @Excel(name = "操作地址") + private String operIp; + + /** 操作地点 */ + @Excel(name = "操作地点") + private String operLocation; + + /** 请求参数 */ + @Excel(name = "请求参数") + private String operParam; + + /** 返回参数 */ + @Excel(name = "返回参数") + private String jsonResult; + + /** 操作状态(0正常 1异常) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=异常") + private Integer status; + + /** 错误消息 */ + @Excel(name = "错误消息") + private String errorMsg; + + /** 操作时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "操作时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date operTime; + + /** 消耗时间 */ + @Excel(name = "消耗时间", suffix = "毫秒") + private Long costTime; + + public Long getOperId() + { + return operId; + } + + public void setOperId(Long operId) + { + this.operId = operId; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public Integer getBusinessType() + { + return businessType; + } + + public void setBusinessType(Integer businessType) + { + this.businessType = businessType; + } + + public Integer[] getBusinessTypes() + { + return businessTypes; + } + + public void setBusinessTypes(Integer[] businessTypes) + { + this.businessTypes = businessTypes; + } + + public String getMethod() + { + return method; + } + + public void setMethod(String method) + { + this.method = method; + } + + public String getRequestMethod() + { + return requestMethod; + } + + public void setRequestMethod(String requestMethod) + { + this.requestMethod = requestMethod; + } + + public Integer getOperatorType() + { + return operatorType; + } + + public void setOperatorType(Integer operatorType) + { + this.operatorType = operatorType; + } + + public String getOperName() + { + return operName; + } + + public void setOperName(String operName) + { + this.operName = operName; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getOperUrl() + { + return operUrl; + } + + public void setOperUrl(String operUrl) + { + this.operUrl = operUrl; + } + + public String getOperIp() + { + return operIp; + } + + public void setOperIp(String operIp) + { + this.operIp = operIp; + } + + public String getOperLocation() + { + return operLocation; + } + + public void setOperLocation(String operLocation) + { + this.operLocation = operLocation; + } + + public String getOperParam() + { + return operParam; + } + + public void setOperParam(String operParam) + { + this.operParam = operParam; + } + + public String getJsonResult() + { + return jsonResult; + } + + public void setJsonResult(String jsonResult) + { + this.jsonResult = jsonResult; + } + + public Integer getStatus() + { + return status; + } + + public void setStatus(Integer status) + { + this.status = status; + } + + public String getErrorMsg() + { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) + { + this.errorMsg = errorMsg; + } + + public Date getOperTime() + { + return operTime; + } + + public void setOperTime(Date operTime) + { + this.operTime = operTime; + } + + public Long getCostTime() + { + return costTime; + } + + public void setCostTime(Long costTime) + { + this.costTime = costTime; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysPost.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysPost.java new file mode 100644 index 000000000..d5e6769d3 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysPost.java @@ -0,0 +1,126 @@ +package com.roomroot.system.domain; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; +import com.roomroot.common.annotation.Excel; +import com.roomroot.common.annotation.Excel.ColumnType; +import com.roomroot.common.core.domain.BaseEntity; + +/** + * 岗位表 sys_post + * + * @author roomroot + */ +@TableName("sys_post") +public class SysPost extends BaseEntity +{ + private static final long serialVersionUID = 1L; + + /** 岗位序号 */ + @Excel(name = "岗位序号", cellType = ColumnType.NUMERIC) + private Long postId; + + /** 岗位编码 */ + @Excel(name = "岗位编码") + private String postCode; + + /** 岗位名称 */ + @Excel(name = "岗位名称") + private String postName; + + /** 岗位排序 */ + @Excel(name = "岗位排序") + private Integer postSort; + + /** 状态(0正常 1停用) */ + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + private String status; + + /** 用户是否存在此岗位标识 默认不存在 */ + private boolean flag = false; + + public Long getPostId() + { + return postId; + } + + public void setPostId(Long postId) + { + this.postId = postId; + } + + @NotBlank(message = "岗位编码不能为空") + @Size(min = 0, max = 64, message = "岗位编码长度不能超过64个字符") + public String getPostCode() + { + return postCode; + } + + public void setPostCode(String postCode) + { + this.postCode = postCode; + } + + @NotBlank(message = "岗位名称不能为空") + @Size(min = 0, max = 50, message = "岗位名称长度不能超过50个字符") + public String getPostName() + { + return postName; + } + + public void setPostName(String postName) + { + this.postName = postName; + } + + @NotNull(message = "显示顺序不能为空") + public Integer getPostSort() + { + return postSort; + } + + public void setPostSort(Integer postSort) + { + this.postSort = postSort; + } + + public String getStatus() + { + return status; + } + + public void setStatus(String status) + { + this.status = status; + } + + public boolean isFlag() + { + return flag; + } + + public void setFlag(boolean flag) + { + this.flag = flag; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("postId", getPostId()) + .append("postCode", getPostCode()) + .append("postName", getPostName()) + .append("postSort", getPostSort()) + .append("status", getStatus()) + .append("createBy", getCreateBy()) + .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) + .append("updateTime", getUpdateTime()) + .append("remark", getRemark()) + .toString(); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysRoleDept.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysRoleDept.java new file mode 100644 index 000000000..6667e0adb --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysRoleDept.java @@ -0,0 +1,48 @@ +package com.roomroot.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; + +/** + * 角色和部门关联 sys_role_dept + * + * @author roomroot + */ +@TableName("sys_role_dept") +public class SysRoleDept +{ + /** 角色ID */ + private Long roleId; + + /** 部门ID */ + private Long deptId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getDeptId() + { + return deptId; + } + + public void setDeptId(Long deptId) + { + this.deptId = deptId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("deptId", getDeptId()) + .toString(); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysRoleMenu.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysRoleMenu.java new file mode 100644 index 000000000..16b935442 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysRoleMenu.java @@ -0,0 +1,48 @@ +package com.roomroot.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; + +/** + * 角色和菜单关联 sys_role_menu + * + * @author roomroot + */ +@TableName("sys_role_menu") +public class SysRoleMenu +{ + /** 角色ID */ + private Long roleId; + + /** 菜单ID */ + private Long menuId; + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + public Long getMenuId() + { + return menuId; + } + + public void setMenuId(Long menuId) + { + this.menuId = menuId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("roleId", getRoleId()) + .append("menuId", getMenuId()) + .toString(); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysUserOnline.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysUserOnline.java new file mode 100644 index 000000000..6d60e7018 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysUserOnline.java @@ -0,0 +1,113 @@ +package com.roomroot.system.domain; + +/** + * 当前在线会话 + * + * @author roomroot + */ +public class SysUserOnline +{ + /** 会话编号 */ + private String tokenId; + + /** 部门名称 */ + private String deptName; + + /** 用户名称 */ + private String userName; + + /** 登录IP地址 */ + private String ipaddr; + + /** 登录地址 */ + private String loginLocation; + + /** 浏览器类型 */ + private String browser; + + /** 操作系统 */ + private String os; + + /** 登录时间 */ + private Long loginTime; + + public String getTokenId() + { + return tokenId; + } + + public void setTokenId(String tokenId) + { + this.tokenId = tokenId; + } + + public String getDeptName() + { + return deptName; + } + + public void setDeptName(String deptName) + { + this.deptName = deptName; + } + + public String getUserName() + { + return userName; + } + + public void setUserName(String userName) + { + this.userName = userName; + } + + public String getIpaddr() + { + return ipaddr; + } + + public void setIpaddr(String ipaddr) + { + this.ipaddr = ipaddr; + } + + public String getLoginLocation() + { + return loginLocation; + } + + public void setLoginLocation(String loginLocation) + { + this.loginLocation = loginLocation; + } + + public String getBrowser() + { + return browser; + } + + public void setBrowser(String browser) + { + this.browser = browser; + } + + public String getOs() + { + return os; + } + + public void setOs(String os) + { + this.os = os; + } + + public Long getLoginTime() + { + return loginTime; + } + + public void setLoginTime(Long loginTime) + { + this.loginTime = loginTime; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysUserPost.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysUserPost.java new file mode 100644 index 000000000..a0dc238b9 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysUserPost.java @@ -0,0 +1,48 @@ +package com.roomroot.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; + +/** + * 用户和岗位关联 sys_user_post + * + * @author roomroot + */ +@TableName("sys_user_post") +public class SysUserPost +{ + /** 用户ID */ + private Long userId; + + /** 岗位ID */ + private Long postId; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getPostId() + { + return postId; + } + + public void setPostId(Long postId) + { + this.postId = postId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("postId", getPostId()) + .toString(); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysUserRole.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysUserRole.java new file mode 100644 index 000000000..69187156e --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/SysUserRole.java @@ -0,0 +1,48 @@ +package com.roomroot.system.domain; + +import org.apache.commons.lang3.builder.ToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import com.baomidou.mybatisplus.annotation.TableName; + +/** + * 用户和角色关联 sys_user_role + * + * @author roomroot + */ +@TableName("sys_user_role") +public class SysUserRole +{ + /** 用户ID */ + private Long userId; + + /** 角色ID */ + private Long roleId; + + public Long getUserId() + { + return userId; + } + + public void setUserId(Long userId) + { + this.userId = userId; + } + + public Long getRoleId() + { + return roleId; + } + + public void setRoleId(Long roleId) + { + this.roleId = roleId; + } + + @Override + public String toString() { + return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) + .append("userId", getUserId()) + .append("roleId", getRoleId()) + .toString(); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/vo/MetaVo.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/vo/MetaVo.java new file mode 100644 index 000000000..2354bb735 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/vo/MetaVo.java @@ -0,0 +1,106 @@ +package com.roomroot.system.domain.vo; + +import com.roomroot.common.utils.StringUtils; + +/** + * 路由显示信息 + * + * @author roomroot + */ +public class MetaVo +{ + /** + * 设置该路由在侧边栏和面包屑中展示的名字 + */ + private String title; + + /** + * 设置该路由的图标,对应路径src/assets/icons/svg + */ + private String icon; + + /** + * 设置为true,则不会被 缓存 + */ + private boolean noCache; + + /** + * 内链地址(http(s)://开头) + */ + private String link; + + public MetaVo() + { + } + + public MetaVo(String title, String icon) + { + this.title = title; + this.icon = icon; + } + + public MetaVo(String title, String icon, boolean noCache) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + } + + public MetaVo(String title, String icon, String link) + { + this.title = title; + this.icon = icon; + this.link = link; + } + + public MetaVo(String title, String icon, boolean noCache, String link) + { + this.title = title; + this.icon = icon; + this.noCache = noCache; + if (StringUtils.ishttp(link)) + { + this.link = link; + } + } + + public boolean isNoCache() + { + return noCache; + } + + public void setNoCache(boolean noCache) + { + this.noCache = noCache; + } + + public String getTitle() + { + return title; + } + + public void setTitle(String title) + { + this.title = title; + } + + public String getIcon() + { + return icon; + } + + public void setIcon(String icon) + { + this.icon = icon; + } + + public String getLink() + { + return link; + } + + public void setLink(String link) + { + this.link = link; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/domain/vo/RouterVo.java b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/vo/RouterVo.java new file mode 100644 index 000000000..0ad77aa8e --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/domain/vo/RouterVo.java @@ -0,0 +1,148 @@ +package com.roomroot.system.domain.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import java.util.List; + +/** + * 路由配置信息 + * + * @author roomroot + */ +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class RouterVo +{ + /** + * 路由名字 + */ + private String name; + + /** + * 路由地址 + */ + private String path; + + /** + * 是否隐藏路由,当设置 true 的时候该路由不会再侧边栏出现 + */ + private boolean hidden; + + /** + * 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + */ + private String redirect; + + /** + * 组件地址 + */ + private String component; + + /** + * 路由参数:如 {"id": 1, "name": "ry"} + */ + private String query; + + /** + * 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + */ + private Boolean alwaysShow; + + /** + * 其他元素 + */ + private MetaVo meta; + + /** + * 子路由 + */ + private List children; + + public String getName() + { + return name; + } + + public void setName(String name) + { + this.name = name; + } + + public String getPath() + { + return path; + } + + public void setPath(String path) + { + this.path = path; + } + + public boolean getHidden() + { + return hidden; + } + + public void setHidden(boolean hidden) + { + this.hidden = hidden; + } + + public String getRedirect() + { + return redirect; + } + + public void setRedirect(String redirect) + { + this.redirect = redirect; + } + + public String getComponent() + { + return component; + } + + public void setComponent(String component) + { + this.component = component; + } + + public String getQuery() + { + return query; + } + + public void setQuery(String query) + { + this.query = query; + } + + public Boolean getAlwaysShow() + { + return alwaysShow; + } + + public void setAlwaysShow(Boolean alwaysShow) + { + this.alwaysShow = alwaysShow; + } + + public MetaVo getMeta() + { + return meta; + } + + public void setMeta(MetaVo meta) + { + this.meta = meta; + } + + public List getChildren() + { + return children; + } + + public void setChildren(List children) + { + this.children = children; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysConfigMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysConfigMapper.java new file mode 100644 index 000000000..6a9fca30f --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysConfigMapper.java @@ -0,0 +1,77 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.system.domain.SysConfig; + +/** + * 参数配置 数据层 + * + * @author roomroot + */ +public interface SysConfigMapper extends BaseMapper +{ + /** + * 查询参数配置信息 + * + * @param config 参数配置信息 + * @return 参数配置信息 + */ + public SysConfig selectConfig(SysConfig config); + + /** + * 通过ID查询配置 + * + * @param configId 参数ID + * @return 参数配置信息 + */ + public SysConfig selectConfigById(Long configId); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List selectConfigList(SysConfig config); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数配置信息 + */ + public SysConfig checkConfigKeyUnique(String configKey); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 删除参数配置 + * + * @param configId 参数ID + * @return 结果 + */ + public int deleteConfigById(Long configId); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + * @return 结果 + */ + public int deleteConfigByIds(Long[] configIds); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysDeptMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysDeptMapper.java new file mode 100644 index 000000000..fb05cf328 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysDeptMapper.java @@ -0,0 +1,126 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.common.core.domain.entity.SysDept; + +/** + * 部门管理 数据层 + * + * @author roomroot + */ +public interface SysDeptMapper extends BaseMapper +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @param deptCheckStrictly 部门树选择项是否关联显示 + * @return 选中部门列表 + */ + public List selectDeptListByRoleId(@Param("roleId") Long roleId, @Param("deptCheckStrictly") boolean deptCheckStrictly); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门 + * + * @param deptId 部门ID + * @return 部门列表 + */ + public List selectChildrenDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public int hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 + */ + public int checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param deptName 部门名称 + * @param parentId 父部门ID + * @return 结果 + */ + public SysDept checkDeptNameUnique(@Param("deptName") String deptName, @Param("parentId") Long parentId); + + /** + * 新增部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 修改所在部门正常状态 + * + * @param deptIds 部门ID组 + */ + public void updateDeptStatusNormal(Long[] deptIds); + + /** + * 修改子元素关系 + * + * @param depts 子元素 + * @return 结果 + */ + public int updateDeptChildren(@Param("depts") List depts); + + /** + * 保存部门排序 + * + * @param dept 部门信息 + */ + public void updateDeptSort(SysDept dept); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysDictDataMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysDictDataMapper.java new file mode 100644 index 000000000..a2d5ef097 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysDictDataMapper.java @@ -0,0 +1,96 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.common.core.domain.entity.SysDictData; + +/** + * 字典表 数据层 + * + * @author roomroot + */ +public interface SysDictDataMapper extends BaseMapper +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List selectDictDataByType(String dictType); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String selectDictLabel(@Param("dictType") String dictType, @Param("dictValue") String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + public SysDictData selectDictDataById(Long dictCode); + + /** + * 查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据 + */ + public int countDictDataByType(String dictType); + + /** + * 通过字典ID删除字典数据信息 + * + * @param dictCode 字典数据ID + * @return 结果 + */ + public int deleteDictDataById(Long dictCode); + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + * @return 结果 + */ + public int deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); + + /** + * 同步修改字典类型 + * + * @param oldDictType 旧字典类型 + * @param newDictType 新旧字典类型 + * @return 结果 + */ + public int updateDictDataType(@Param("oldDictType") String oldDictType, @Param("newDictType") String newDictType); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysDictTypeMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysDictTypeMapper.java new file mode 100644 index 000000000..55a5292fd --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysDictTypeMapper.java @@ -0,0 +1,84 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.common.core.domain.entity.SysDictType; + +/** + * 字典表 数据层 + * + * @author roomroot + */ +public interface SysDictTypeMapper extends BaseMapper +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 通过字典ID删除字典信息 + * + * @param dictId 字典ID + * @return 结果 + */ + public int deleteDictTypeById(Long dictId); + + /** + * 批量删除字典类型信息 + * + * @param dictIds 需要删除的字典ID + * @return 结果 + */ + public int deleteDictTypeByIds(Long[] dictIds); + + /** + * 新增字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public SysDictType checkDictTypeUnique(String dictType); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysLogininforMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysLogininforMapper.java new file mode 100644 index 000000000..de575db70 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysLogininforMapper.java @@ -0,0 +1,52 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import java.util.Map; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 数据层 + * + * @author roomroot + */ +public interface SysLogininforMapper extends BaseMapper +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + * + * @return 结果 + */ + public int cleanLogininfor(); + + /** + * 根据用户名查询登录统计信息 + * + * @param userName 用户名 + * @return 登录统计信息(上次登录时间、登录次数、最后登录IP) + */ + public Map selectLoginStatsByUserName(String userName); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysMenuMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysMenuMapper.java new file mode 100644 index 000000000..a1e3bfb7f --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysMenuMapper.java @@ -0,0 +1,142 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.common.core.domain.entity.SysMenu; + +/** + * 菜单表 数据层 + * + * @author roomroot + */ +public interface SysMenuMapper extends BaseMapper +{ + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu); + + /** + * 根据用户所有权限 + * + * @return 权限列表 + */ + public List selectMenuPerms(); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + public List selectMenuListByUserId(SysMenu menu); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public List selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public List selectMenuPermsByUserId(Long userId); + + /** + * 根据用户ID查询菜单 + * + * @return 菜单列表 + */ + public List selectMenuTreeAll(); + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @param menuCheckStrictly 菜单树选择项是否关联显示 + * @return 选中菜单列表 + */ + public List selectMenuListByRoleId(@Param("roleId") Long roleId, @Param("menuCheckStrictly") boolean menuCheckStrictly); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int hasChildByMenuId(Long menuId); + + /** + * 新增菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 保存菜单排序 + * + * @param menu 菜单信息 + */ + public void updateMenuSort(SysMenu menu); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menuName 菜单名称 + * @param parentId 父菜单ID + * @return 结果 + */ + public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId); + + /** + * 根据路由路径或名称查询菜单信息(用于唯一性校验) + * + * @param path 路由地址 + * @param routeName 路由名称 + * @return 匹配的菜单列表 + */ + public List selectMenusByPathOrRouteName(@Param("path") String path, @Param("routeName") String routeName); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysNoticeMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysNoticeMapper.java new file mode 100644 index 000000000..db78ff8aa --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysNoticeMapper.java @@ -0,0 +1,61 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.system.domain.SysNotice; + +/** + * 通知公告表 数据层 + * + * @author roomroot + */ +public interface SysNoticeMapper extends BaseMapper +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 批量删除公告 + * + * @param noticeId 公告ID + * @return 结果 + */ + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysNoticeReadMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysNoticeReadMapper.java new file mode 100644 index 000000000..57c3e9ec6 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysNoticeReadMapper.java @@ -0,0 +1,76 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import java.util.Map; +import org.apache.ibatis.annotations.Param; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.system.domain.SysNotice; +import com.roomroot.system.domain.SysNoticeRead; + +/** + * 公告已读记录 数据层 + * + * @author roomroot + */ +public interface SysNoticeReadMapper extends BaseMapper +{ + /** + * 新增已读记录(忽略重复) + * + * @param noticeRead 已读记录 + * @return 结果 + */ + public int insertNoticeRead(SysNoticeRead noticeRead); + + /** + * 查询某用户未读公告数量 + * + * @param userId 用户ID + * @return 未读数量 + */ + public int selectUnreadCount(@Param("userId") Long userId); + + /** + * 查询某用户是否已读某公告 + * + * @param noticeId 公告ID + * @param userId 用户ID + * @return 已读记录数(0未读 1已读) + */ + public int selectIsRead(@Param("noticeId") Long noticeId, @Param("userId") Long userId); + + /** + * 批量标记已读 + * + * @param userId 用户ID + * @param noticeIds 公告ID数组 + * @return 结果 + */ + public int insertNoticeReadBatch(@Param("userId") Long userId, @Param("noticeIds") Long[] noticeIds); + + /** + * 查询带已读状态的公告列表(SQL层限制条数,一次查询完成) + * + * @param userId 用户ID + * @param limit 最多返回条数 + * @return 带 isRead 标记的公告列表 + */ + public List selectNoticeListWithReadStatus(@Param("userId") Long userId, @Param("limit") int limit); + + /** + * 查询已阅读某公告的用户列表 + * + * @param noticeId 公告ID + * @param searchValue 搜索值 + * @return 已读用户列表 + */ + public List> selectReadUsersByNoticeId(@Param("noticeId") Long noticeId, @Param("searchValue") String searchValue); + + /** + * 公告删除时清理对应已读记录 + * + * @param noticeIds 公告ID数组 + * @return 结果 + */ + public int deleteByNoticeIds(@Param("noticeIds") Long[] noticeIds); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysOperLogMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysOperLogMapper.java new file mode 100644 index 000000000..a8187d72d --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysOperLogMapper.java @@ -0,0 +1,49 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.system.domain.SysOperLog; + +/** + * 操作日志 数据层 + * + * @author roomroot + */ +public interface SysOperLogMapper extends BaseMapper +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysPostMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysPostMapper.java new file mode 100644 index 000000000..d4e50c3a9 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysPostMapper.java @@ -0,0 +1,100 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.system.domain.SysPost; + +/** + * 岗位信息 数据层 + * + * @author roomroot + */ +public interface SysPostMapper extends BaseMapper +{ + /** + * 查询岗位数据集合 + * + * @param post 岗位信息 + * @return 岗位数据集合 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public List selectPostsByUserName(String userName); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 修改岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); + + /** + * 新增岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 校验岗位名称 + * + * @param postName 岗位名称 + * @return 结果 + */ + public SysPost checkPostNameUnique(String postName); + + /** + * 校验岗位编码 + * + * @param postCode 岗位编码 + * @return 结果 + */ + public SysPost checkPostCodeUnique(String postCode); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysRoleDeptMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysRoleDeptMapper.java new file mode 100644 index 000000000..5e0acf14e --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysRoleDeptMapper.java @@ -0,0 +1,45 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.system.domain.SysRoleDept; + +/** + * 角色与部门关联表 数据层 + * + * @author roomroot + */ +public interface SysRoleDeptMapper extends BaseMapper +{ + /** + * 通过角色ID删除角色和部门关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleDeptByRoleId(Long roleId); + + /** + * 批量删除角色部门关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleDept(Long[] ids); + + /** + * 查询部门使用数量 + * + * @param deptId 部门ID + * @return 结果 + */ + public int selectCountRoleDeptByDeptId(Long deptId); + + /** + * 批量新增角色部门信息 + * + * @param roleDeptList 角色部门列表 + * @return 结果 + */ + public int batchRoleDept(List roleDeptList); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysRoleMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysRoleMapper.java new file mode 100644 index 000000000..58867ab69 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysRoleMapper.java @@ -0,0 +1,108 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.common.core.domain.entity.SysRole; + +/** + * 角色表 数据层 + * + * @author roomroot + */ +public interface SysRoleMapper extends BaseMapper +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 根据用户ID查询角色 + * + * @param userName 用户名 + * @return 角色列表 + */ + public List selectRolesByUserName(String userName); + + /** + * 校验角色名称是否唯一 + * + * @param roleName 角色名称 + * @return 角色信息 + */ + public SysRole checkRoleNameUnique(String roleName); + + /** + * 校验角色权限是否唯一 + * + * @param roleKey 角色权限 + * @return 角色信息 + */ + public SysRole checkRoleKeyUnique(String roleKey); + + /** + * 修改角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 新增角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(Long[] roleIds); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysRoleMenuMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysRoleMenuMapper.java new file mode 100644 index 000000000..27c03902c --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysRoleMenuMapper.java @@ -0,0 +1,45 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.system.domain.SysRoleMenu; + +/** + * 角色与菜单关联表 数据层 + * + * @author roomroot + */ +public interface SysRoleMenuMapper extends BaseMapper +{ + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int checkMenuExistRole(Long menuId); + + /** + * 通过角色ID删除角色和菜单关联 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleMenuByRoleId(Long roleId); + + /** + * 批量删除角色菜单关联信息 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteRoleMenu(Long[] ids); + + /** + * 批量新增角色菜单信息 + * + * @param roleMenuList 角色菜单列表 + * @return 结果 + */ + public int batchRoleMenu(List roleMenuList); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysUserMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysUserMapper.java new file mode 100644 index 000000000..ecad40ce6 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysUserMapper.java @@ -0,0 +1,148 @@ +package com.roomroot.system.mapper; + +import java.util.Date; +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.common.core.domain.entity.SysUser; + +/** + * 用户表 数据层 + * + * @author roomroot + */ +public interface SysUserMapper extends BaseMapper +{ + /** + * 根据条件分页查询用户列表 + * + * @param sysUser 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser sysUser); + + /** + * 根据条件分页查询已配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 修改用户头像 + * + * @param userId 用户ID + * @param avatar 头像地址 + * @return 结果 + */ + public int updateUserAvatar(@Param("userId") Long userId, @Param("avatar") String avatar); + + /** + * 修改用户状态 + * + * @param userId 用户ID + * @param status 状态 + * @return 结果 + */ + public int updateUserStatus(@Param("userId") Long userId, @Param("status") String status); + + /** + * 更新用户登录信息(IP和登录时间) + * + * @param userId 用户ID + * @param loginIp 登录IP地址 + * @param loginDate 登录时间 + * @return 结果 + */ + public int updateLoginInfo(@Param("userId") Long userId, @Param("loginIp") String loginIp, @Param("loginDate") Date loginDate); + + /** + * 重置用户密码 + * + * @param userId 用户ID + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(@Param("userId") Long userId, @Param("password") String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(Long[] userIds); + + /** + * 校验用户名称是否唯一 + * + * @param userName 用户名称 + * @return 结果 + */ + public SysUser checkUserNameUnique(String userName); + + /** + * 校验手机号码是否唯一 + * + * @param phonenumber 手机号码 + * @return 结果 + */ + public SysUser checkPhoneUnique(String phonenumber); + + /** + * 校验email是否唯一 + * + * @param email 用户邮箱 + * @return 结果 + */ + public SysUser checkEmailUnique(String email); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysUserPostMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysUserPostMapper.java new file mode 100644 index 000000000..15b3a0657 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysUserPostMapper.java @@ -0,0 +1,45 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.system.domain.SysUserPost; + +/** + * 用户与岗位关联表 数据层 + * + * @author roomroot + */ +public interface SysUserPostMapper extends BaseMapper +{ + /** + * 通过用户ID删除用户和岗位关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserPostByUserId(Long userId); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 批量删除用户和岗位关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserPost(Long[] ids); + + /** + * 批量新增用户岗位信息 + * + * @param userPostList 用户岗位列表 + * @return 结果 + */ + public int batchUserPost(List userPostList); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysUserRoleMapper.java b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysUserRoleMapper.java new file mode 100644 index 000000000..57e88c73e --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/mapper/SysUserRoleMapper.java @@ -0,0 +1,63 @@ +package com.roomroot.system.mapper; + +import java.util.List; +import org.apache.ibatis.annotations.Param; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.roomroot.system.domain.SysUserRole; + +/** + * 用户与角色关联表 数据层 + * + * @author roomroot + */ +public interface SysUserRoleMapper extends BaseMapper +{ + /** + * 通过用户ID删除用户和角色关联 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserRoleByUserId(Long userId); + + /** + * 批量删除用户和角色关联 + * + * @param ids 需要删除的数据ID + * @return 结果 + */ + public int deleteUserRole(Long[] ids); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 批量新增用户角色信息 + * + * @param userRoleList 用户角色列表 + * @return 结果 + */ + public int batchUserRole(List userRoleList); + + /** + * 删除用户和角色关联信息 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteUserRoleInfo(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysConfigService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysConfigService.java new file mode 100644 index 000000000..0b256b7dc --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysConfigService.java @@ -0,0 +1,89 @@ +package com.roomroot.system.service; + +import java.util.List; +import com.roomroot.system.domain.SysConfig; + +/** + * 参数配置 服务层 + * + * @author roomroot + */ +public interface ISysConfigService +{ + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + public SysConfig selectConfigById(Long configId); + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数键名 + * @return 参数键值 + */ + public String selectConfigByKey(String configKey); + + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + public boolean selectCaptchaEnabled(); + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + public List selectConfigList(SysConfig config); + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int insertConfig(SysConfig config); + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + public int updateConfig(SysConfig config); + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + public void deleteConfigByIds(Long[] configIds); + + /** + * 加载参数缓存数据 + */ + public void loadingConfigCache(); + + /** + * 清空参数缓存数据 + */ + public void clearConfigCache(); + + /** + * 重置参数缓存数据 + */ + public void resetConfigCache(); + + /** + * 校验参数键名是否唯一 + * + * @param config 参数信息 + * @return 结果 + */ + public boolean checkConfigKeyUnique(SysConfig config); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysDeptService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysDeptService.java new file mode 100644 index 000000000..af3a279b5 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysDeptService.java @@ -0,0 +1,132 @@ +package com.roomroot.system.service; + +import java.util.List; +import com.roomroot.common.core.domain.TreeSelect; +import com.roomroot.common.core.domain.entity.SysDept; + +/** + * 部门管理 服务层 + * + * @author roomroot + */ +public interface ISysDeptService +{ + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + public List selectDeptList(SysDept dept); + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + public List selectDeptTreeList(SysDept dept); + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + public List buildDeptTree(List depts); + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + public List buildDeptTreeSelect(List depts); + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + public List selectDeptListByRoleId(Long roleId); + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + public SysDept selectDeptById(Long deptId); + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + public int selectNormalChildrenDeptById(Long deptId); + + /** + * 是否存在部门子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + public boolean hasChildByDeptId(Long deptId); + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkDeptExistUser(Long deptId); + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + public boolean checkDeptNameUnique(SysDept dept); + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + public void checkDeptDataScope(Long deptId); + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int insertDept(SysDept dept); + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + public int updateDept(SysDept dept); + + /** + * 保存部门排序 + * + * @param deptIds 部门ID数组 + * @param orderNums 排序数组 + */ + public void updateDeptSort(String[] deptIds, String[] orderNums); + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + public int deleteDeptById(Long deptId); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysDictDataService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysDictDataService.java new file mode 100644 index 000000000..823b636d6 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysDictDataService.java @@ -0,0 +1,60 @@ +package com.roomroot.system.service; + +import java.util.List; +import com.roomroot.common.core.domain.entity.SysDictData; + +/** + * 字典 业务层 + * + * @author roomroot + */ +public interface ISysDictDataService +{ + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + public List selectDictDataList(SysDictData dictData); + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + public String selectDictLabel(String dictType, String dictValue); + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + public SysDictData selectDictDataById(Long dictCode); + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + */ + public void deleteDictDataByIds(Long[] dictCodes); + + /** + * 新增保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int insertDictData(SysDictData dictData); + + /** + * 修改保存字典数据信息 + * + * @param dictData 字典数据信息 + * @return 结果 + */ + public int updateDictData(SysDictData dictData); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysDictTypeService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysDictTypeService.java new file mode 100644 index 000000000..893b816a5 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysDictTypeService.java @@ -0,0 +1,98 @@ +package com.roomroot.system.service; + +import java.util.List; +import com.roomroot.common.core.domain.entity.SysDictData; +import com.roomroot.common.core.domain.entity.SysDictType; + +/** + * 字典 业务层 + * + * @author roomroot + */ +public interface ISysDictTypeService +{ + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + public List selectDictTypeList(SysDictType dictType); + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + public List selectDictTypeAll(); + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + public List selectDictDataByType(String dictType); + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + public SysDictType selectDictTypeById(Long dictId); + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + public SysDictType selectDictTypeByType(String dictType); + + /** + * 批量删除字典信息 + * + * @param dictIds 需要删除的字典ID + */ + public void deleteDictTypeByIds(Long[] dictIds); + + /** + * 加载字典缓存数据 + */ + public void loadingDictCache(); + + /** + * 清空字典缓存数据 + */ + public void clearDictCache(); + + /** + * 重置字典缓存数据 + */ + public void resetDictCache(); + + /** + * 新增保存字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int insertDictType(SysDictType dictType); + + /** + * 修改保存字典类型信息 + * + * @param dictType 字典类型信息 + * @return 结果 + */ + public int updateDictType(SysDictType dictType); + + /** + * 校验字典类型称是否唯一 + * + * @param dictType 字典类型 + * @return 结果 + */ + public boolean checkDictTypeUnique(SysDictType dictType); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysLogininforService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysLogininforService.java new file mode 100644 index 000000000..4b791d183 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysLogininforService.java @@ -0,0 +1,49 @@ +package com.roomroot.system.service; + +import java.util.List; +import java.util.Map; +import com.roomroot.system.domain.SysLogininfor; + +/** + * 系统访问日志情况信息 服务层 + * + * @author roomroot + */ +public interface ISysLogininforService +{ + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + public void insertLogininfor(SysLogininfor logininfor); + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + public List selectLogininforList(SysLogininfor logininfor); + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + public int deleteLogininforByIds(Long[] infoIds); + + /** + * 清空系统登录日志 + */ + public void cleanLogininfor(); + + /** + * 根据用户名查询登录统计信息 + * + * @param userName 用户名 + * @return 登录统计信息(上次登录时间、登录次数、最后登录IP) + */ + public Map selectLoginStatsByUserName(String userName); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysMenuService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysMenuService.java new file mode 100644 index 000000000..aa0946217 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysMenuService.java @@ -0,0 +1,160 @@ +package com.roomroot.system.service; + +import java.util.List; +import java.util.Set; +import com.roomroot.common.core.domain.TreeSelect; +import com.roomroot.common.core.domain.entity.SysMenu; +import com.roomroot.system.domain.vo.RouterVo; + +/** + * 菜单 业务层 + * + * @author roomroot + */ +public interface ISysMenuService +{ + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(Long userId); + + /** + * 根据用户查询系统菜单列表 + * + * @param menu 菜单信息 + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuList(SysMenu menu, Long userId); + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectMenuPermsByUserId(Long userId); + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + public Set selectMenuPermsByRoleId(Long roleId); + + /** + * 根据用户ID查询菜单树信息 + * + * @param userId 用户ID + * @return 菜单列表 + */ + public List selectMenuTreeByUserId(Long userId); + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + public List selectMenuListByRoleId(Long roleId); + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + public List buildMenus(List menus); + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + public List buildMenuTree(List menus); + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + public List buildMenuTreeSelect(List menus); + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + public SysMenu selectMenuById(Long menuId); + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean hasChildByMenuId(Long menuId); + + /** + * 查询菜单是否存在角色 + * + * @param menuId 菜单ID + * @return 结果 true 存在 false 不存在 + */ + public boolean checkMenuExistRole(Long menuId); + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int insertMenu(SysMenu menu); + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + public int updateMenu(SysMenu menu); + + /** + * 保存菜单排序 + * + * @param menuIds 菜单ID + * @param orderNums 排序ID + */ + public void updateMenuSort(String[] menuIds, String[] orderNums); + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + public int deleteMenuById(Long menuId); + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean checkMenuNameUnique(SysMenu menu); + + /** + * 校验路由组合是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean checkRouteConfigUnique(SysMenu menu); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysNoticeReadService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysNoticeReadService.java new file mode 100644 index 000000000..91ab86484 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysNoticeReadService.java @@ -0,0 +1,62 @@ +package com.roomroot.system.service; + +import java.util.List; +import java.util.Map; +import com.roomroot.system.domain.SysNotice; + +/** + * 公告已读记录 服务层 + * + * @author roomroot + */ +public interface ISysNoticeReadService +{ + /** + * 标记已读(幂等,重复调用不报错) + * + * @param noticeId 公告ID + * @param userId 用户ID + */ + public void markRead(Long noticeId, Long userId); + + /** + * 查询某用户未读公告数量 + * + * @param userId 用户ID + * @return 未读数量 + */ + public int selectUnreadCount(Long userId); + + /** + * 查询公告列表并标记当前用户已读状态(用于首页展示) + * + * @param userId 用户ID + * @param limit 最多返回条数 + * @return 带 isRead 标记的公告列表 + */ + public List selectNoticeListWithReadStatus(Long userId, int limit); + + /** + * 批量标记已读 + * + * @param userId 用户ID + * @param noticeIds 公告ID数组 + */ + public void markReadBatch(Long userId, Long[] noticeIds); + + /** + * 查询已阅读某公告的用户列表 + * + * @param noticeId 公告ID + * @param searchValue 搜索值 + * @return 已读用户列表 + */ + public List> selectReadUsersByNoticeId(Long noticeId, String searchValue); + + /** + * 删除公告时清理对应已读记录 + * + * @param noticeIds 公告ID数组 + */ + public void deleteByNoticeIds(Long[] noticeIds); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysNoticeService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysNoticeService.java new file mode 100644 index 000000000..4bda14d1e --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysNoticeService.java @@ -0,0 +1,60 @@ +package com.roomroot.system.service; + +import java.util.List; +import com.roomroot.system.domain.SysNotice; + +/** + * 公告 服务层 + * + * @author roomroot + */ +public interface ISysNoticeService +{ + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + public SysNotice selectNoticeById(Long noticeId); + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + public List selectNoticeList(SysNotice notice); + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int insertNotice(SysNotice notice); + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + public int updateNotice(SysNotice notice); + + /** + * 删除公告信息 + * + * @param noticeId 公告ID + * @return 结果 + */ + public int deleteNoticeById(Long noticeId); + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + public int deleteNoticeByIds(Long[] noticeIds); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysOperLogService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysOperLogService.java new file mode 100644 index 000000000..c6f537b1d --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysOperLogService.java @@ -0,0 +1,48 @@ +package com.roomroot.system.service; + +import java.util.List; +import com.roomroot.system.domain.SysOperLog; + +/** + * 操作日志 服务层 + * + * @author roomroot + */ +public interface ISysOperLogService +{ + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + public void insertOperlog(SysOperLog operLog); + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + public List selectOperLogList(SysOperLog operLog); + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + public int deleteOperLogByIds(Long[] operIds); + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + public SysOperLog selectOperLogById(Long operId); + + /** + * 清空操作日志 + */ + public void cleanOperLog(); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysPostService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysPostService.java new file mode 100644 index 000000000..5a1fd2e58 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysPostService.java @@ -0,0 +1,99 @@ +package com.roomroot.system.service; + +import java.util.List; +import com.roomroot.system.domain.SysPost; + +/** + * 岗位信息 服务层 + * + * @author roomroot + */ +public interface ISysPostService +{ + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位列表 + */ + public List selectPostList(SysPost post); + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + public List selectPostAll(); + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + public SysPost selectPostById(Long postId); + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + public List selectPostListByUserId(Long userId); + + /** + * 校验岗位名称 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostNameUnique(SysPost post); + + /** + * 校验岗位编码 + * + * @param post 岗位信息 + * @return 结果 + */ + public boolean checkPostCodeUnique(SysPost post); + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + public int countUserPostById(Long postId); + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + public int deletePostById(Long postId); + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + public int deletePostByIds(Long[] postIds); + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int insertPost(SysPost post); + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + public int updatePost(SysPost post); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysRoleService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysRoleService.java new file mode 100644 index 000000000..5fc640363 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysRoleService.java @@ -0,0 +1,173 @@ +package com.roomroot.system.service; + +import java.util.List; +import java.util.Set; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.system.domain.SysUserRole; + +/** + * 角色业务层 + * + * @author roomroot + */ +public interface ISysRoleService +{ + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + public List selectRoleList(SysRole role); + + /** + * 根据用户ID查询角色列表 + * + * @param userId 用户ID + * @return 角色列表 + */ + public List selectRolesByUserId(Long userId); + + /** + * 根据用户ID查询角色权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + public Set selectRolePermissionByUserId(Long userId); + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + public List selectRoleAll(); + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + public List selectRoleListByUserId(Long userId); + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + public SysRole selectRoleById(Long roleId); + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleNameUnique(SysRole role); + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + public boolean checkRoleKeyUnique(SysRole role); + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + public void checkRoleAllowed(SysRole role); + + /** + * 校验角色是否有数据权限 + * + * @param roleIds 角色id + */ + public void checkRoleDataScope(Long... roleIds); + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + public int countUserRoleByRoleId(Long roleId); + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int insertRole(SysRole role); + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRole(SysRole role); + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + public int updateRoleStatus(SysRole role); + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + public int authDataScope(SysRole role); + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + public int deleteRoleById(Long roleId); + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + public int deleteRoleByIds(Long[] roleIds); + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + public int deleteAuthUser(SysUserRole userRole); + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + public int deleteAuthUsers(Long roleId, Long[] userIds); + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要删除的用户数据ID + * @return 结果 + */ + public int insertAuthUsers(Long roleId, Long[] userIds); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysUserOnlineService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysUserOnlineService.java new file mode 100644 index 000000000..c2cfc35e3 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysUserOnlineService.java @@ -0,0 +1,48 @@ +package com.roomroot.system.service; + +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.system.domain.SysUserOnline; + +/** + * 在线用户 服务层 + * + * @author roomroot + */ +public interface ISysUserOnlineService +{ + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user); + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user); + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user); + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + public SysUserOnline loginUserToUserOnline(LoginUser user); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysUserService.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysUserService.java new file mode 100644 index 000000000..5ee61b1a2 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/ISysUserService.java @@ -0,0 +1,217 @@ +package com.roomroot.system.service; + +import java.util.Date; +import java.util.List; +import com.roomroot.common.core.domain.entity.SysUser; + +/** + * 用户 业务层 + * + * @author roomroot + */ +public interface ISysUserService +{ + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUserList(SysUser user); + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectAllocatedList(SysUser user); + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + public List selectUnallocatedList(SysUser user); + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + public SysUser selectUserByUserName(String userName); + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + public SysUser selectUserById(Long userId); + + /** + * 根据用户ID查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserRoleGroup(String userName); + + /** + * 根据用户ID查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + public String selectUserPostGroup(String userName); + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkUserNameUnique(SysUser user); + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkPhoneUnique(SysUser user); + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean checkEmailUnique(SysUser user); + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + public void checkUserAllowed(SysUser user); + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + public void checkUserDataScope(Long userId); + + /** + * 新增用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int insertUser(SysUser user); + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public boolean registerUser(SysUser user); + + /** + * 修改用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUser(SysUser user); + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserAuth(Long userId, Long[] roleIds); + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserStatus(SysUser user); + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + public int updateUserProfile(SysUser user); + + /** + * 修改用户头像 + * + * @param userId 用户ID + * @param avatar 头像地址 + * @return 结果 + */ + public boolean updateUserAvatar(Long userId, String avatar); + + /** + * 更新用户登录信息(IP和登录时间) + * + * @param userId 用户ID + * @param loginIp 登录IP地址 + * @param loginDate 登录时间 + * @return 结果 + */ + public void updateLoginInfo(Long userId, String loginIp, Date loginDate); + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + public int resetPwd(SysUser user); + + /** + * 重置用户密码 + * + * @param userId 用户ID + * @param password 密码 + * @return 结果 + */ + public int resetUserPwd(Long userId, String password); + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + public int deleteUserById(Long userId); + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + public int deleteUserByIds(Long[] userIds); + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + public String importUser(List userList, Boolean isUpdateSupport, String operName); +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysConfigServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysConfigServiceImpl.java new file mode 100644 index 000000000..652c3f980 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysConfigServiceImpl.java @@ -0,0 +1,229 @@ +package com.roomroot.system.service.impl; + +import java.util.Collection; +import java.util.List; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.roomroot.common.constant.CacheConstants; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.redis.RedisCache; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.domain.SysConfig; +import com.roomroot.system.mapper.SysConfigMapper; +import com.roomroot.system.service.ISysConfigService; + +/** + * 参数配置 服务层实现 + * + * @author roomroot + */ +@Service +public class SysConfigServiceImpl implements ISysConfigService +{ + @Autowired + private SysConfigMapper configMapper; + + @Autowired + private RedisCache redisCache; + + /** + * 项目启动时,初始化参数到缓存 + */ + @PostConstruct + public void init() + { + loadingConfigCache(); + } + + /** + * 查询参数配置信息 + * + * @param configId 参数配置ID + * @return 参数配置信息 + */ + @Override + public SysConfig selectConfigById(Long configId) + { + SysConfig config = new SysConfig(); + config.setConfigId(configId); + return configMapper.selectConfig(config); + } + + /** + * 根据键名查询参数配置信息 + * + * @param configKey 参数key + * @return 参数键值 + */ + @Override + public String selectConfigByKey(String configKey) + { + String configValue = Convert.toStr(redisCache.getCacheObject(getCacheKey(configKey))); + if (StringUtils.isNotEmpty(configValue)) + { + return configValue; + } + SysConfig config = new SysConfig(); + config.setConfigKey(configKey); + SysConfig retConfig = configMapper.selectConfig(config); + if (StringUtils.isNotNull(retConfig)) + { + redisCache.setCacheObject(getCacheKey(configKey), retConfig.getConfigValue()); + return retConfig.getConfigValue(); + } + return StringUtils.EMPTY; + } + + /** + * 获取验证码开关 + * + * @return true开启,false关闭 + */ + @Override + public boolean selectCaptchaEnabled() + { + String captchaEnabled = selectConfigByKey("sys.account.captchaEnabled"); + if (StringUtils.isEmpty(captchaEnabled)) + { + return true; + } + return Convert.toBool(captchaEnabled); + } + + /** + * 查询参数配置列表 + * + * @param config 参数配置信息 + * @return 参数配置集合 + */ + @Override + public List selectConfigList(SysConfig config) + { + return configMapper.selectConfigList(config); + } + + /** + * 新增参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int insertConfig(SysConfig config) + { + int row = configMapper.insertConfig(config); + if (row > 0) + { + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + return row; + } + + /** + * 修改参数配置 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public int updateConfig(SysConfig config) + { + SysConfig temp = configMapper.selectConfigById(config.getConfigId()); + if (!StringUtils.equals(temp.getConfigKey(), config.getConfigKey())) + { + redisCache.deleteObject(getCacheKey(temp.getConfigKey())); + } + + int row = configMapper.updateConfig(config); + if (row > 0) + { + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + return row; + } + + /** + * 批量删除参数信息 + * + * @param configIds 需要删除的参数ID + */ + @Override + public void deleteConfigByIds(Long[] configIds) + { + for (Long configId : configIds) + { + SysConfig config = selectConfigById(configId); + if (StringUtils.equals(UserConstants.YES, config.getConfigType())) + { + throw new ServiceException(String.format("内置参数【%1$s】不能删除 ", config.getConfigKey())); + } + configMapper.deleteConfigById(configId); + redisCache.deleteObject(getCacheKey(config.getConfigKey())); + } + } + + /** + * 加载参数缓存数据 + */ + @Override + public void loadingConfigCache() + { + List configsList = configMapper.selectConfigList(new SysConfig()); + for (SysConfig config : configsList) + { + redisCache.setCacheObject(getCacheKey(config.getConfigKey()), config.getConfigValue()); + } + } + + /** + * 清空参数缓存数据 + */ + @Override + public void clearConfigCache() + { + Collection keys = redisCache.keys(CacheConstants.SYS_CONFIG_KEY + "*"); + redisCache.deleteObject(keys); + } + + /** + * 重置参数缓存数据 + */ + @Override + public void resetConfigCache() + { + clearConfigCache(); + loadingConfigCache(); + } + + /** + * 校验参数键名是否唯一 + * + * @param config 参数配置信息 + * @return 结果 + */ + @Override + public boolean checkConfigKeyUnique(SysConfig config) + { + Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId(); + SysConfig info = configMapper.checkConfigKeyUnique(config.getConfigKey()); + if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 设置cache key + * + * @param configKey 参数键 + * @return 缓存键key + */ + private String getCacheKey(String configKey) + { + return CacheConstants.SYS_CONFIG_KEY + configKey; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysDeptServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysDeptServiceImpl.java new file mode 100644 index 000000000..f09fa1fed --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysDeptServiceImpl.java @@ -0,0 +1,364 @@ +package com.roomroot.system.service.impl; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.roomroot.common.annotation.DataScope; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.TreeSelect; +import com.roomroot.common.core.domain.entity.SysDept; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.spring.SpringUtils; +import com.roomroot.system.mapper.SysDeptMapper; +import com.roomroot.system.mapper.SysRoleMapper; +import com.roomroot.system.service.ISysDeptService; + +/** + * 部门管理 服务实现 + * + * @author roomroot + */ +@Service +public class SysDeptServiceImpl implements ISysDeptService +{ + @Autowired + private SysDeptMapper deptMapper; + + @Autowired + private SysRoleMapper roleMapper; + + /** + * 查询部门管理数据 + * + * @param dept 部门信息 + * @return 部门信息集合 + */ + @Override + @DataScope(deptAlias = "d") + public List selectDeptList(SysDept dept) + { + return deptMapper.selectDeptList(dept); + } + + /** + * 查询部门树结构信息 + * + * @param dept 部门信息 + * @return 部门树信息集合 + */ + @Override + public List selectDeptTreeList(SysDept dept) + { + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + return buildDeptTreeSelect(depts); + } + + /** + * 构建前端所需要树结构 + * + * @param depts 部门列表 + * @return 树结构列表 + */ + @Override + public List buildDeptTree(List depts) + { + List returnList = new ArrayList(); + List tempList = depts.stream().map(SysDept::getDeptId).collect(Collectors.toList()); + for (SysDept dept : depts) + { + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(dept.getParentId())) + { + recursionFn(depts, dept); + returnList.add(dept); + } + } + if (returnList.isEmpty()) + { + returnList = depts; + } + return returnList; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param depts 部门列表 + * @return 下拉树结构列表 + */ + @Override + public List buildDeptTreeSelect(List depts) + { + List deptTrees = buildDeptTree(depts); + return deptTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据角色ID查询部门树信息 + * + * @param roleId 角色ID + * @return 选中部门列表 + */ + @Override + public List selectDeptListByRoleId(Long roleId) + { + SysRole role = roleMapper.selectRoleById(roleId); + return deptMapper.selectDeptListByRoleId(roleId, role.isDeptCheckStrictly()); + } + + /** + * 根据部门ID查询信息 + * + * @param deptId 部门ID + * @return 部门信息 + */ + @Override + public SysDept selectDeptById(Long deptId) + { + return deptMapper.selectDeptById(deptId); + } + + /** + * 根据ID查询所有子部门(正常状态) + * + * @param deptId 部门ID + * @return 子部门数 + */ + @Override + public int selectNormalChildrenDeptById(Long deptId) + { + return deptMapper.selectNormalChildrenDeptById(deptId); + } + + /** + * 是否存在子节点 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public boolean hasChildByDeptId(Long deptId) + { + int result = deptMapper.hasChildByDeptId(deptId); + return result > 0; + } + + /** + * 查询部门是否存在用户 + * + * @param deptId 部门ID + * @return 结果 true 存在 false 不存在 + */ + @Override + public boolean checkDeptExistUser(Long deptId) + { + int result = deptMapper.checkDeptExistUser(deptId); + return result > 0; + } + + /** + * 校验部门名称是否唯一 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public boolean checkDeptNameUnique(SysDept dept) + { + Long deptId = StringUtils.isNull(dept.getDeptId()) ? -1L : dept.getDeptId(); + SysDept info = deptMapper.checkDeptNameUnique(dept.getDeptName(), dept.getParentId()); + if (StringUtils.isNotNull(info) && info.getDeptId().longValue() != deptId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验部门是否有数据权限 + * + * @param deptId 部门id + */ + @Override + public void checkDeptDataScope(Long deptId) + { + if (!SecurityUtils.isAdmin() && StringUtils.isNotNull(deptId)) + { + SysDept dept = new SysDept(); + dept.setDeptId(deptId); + List depts = SpringUtils.getAopProxy(this).selectDeptList(dept); + if (StringUtils.isEmpty(depts)) + { + throw new ServiceException("没有权限访问部门数据!"); + } + } + } + + /** + * 新增保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public int insertDept(SysDept dept) + { + SysDept info = deptMapper.selectDeptById(dept.getParentId()); + // 如果父节点不为正常状态,则不允许新增子节点 + if (!UserConstants.DEPT_NORMAL.equals(info.getStatus())) + { + throw new ServiceException("部门停用,不允许新增"); + } + dept.setAncestors(info.getAncestors() + "," + dept.getParentId()); + return deptMapper.insertDept(dept); + } + + /** + * 修改保存部门信息 + * + * @param dept 部门信息 + * @return 结果 + */ + @Override + public int updateDept(SysDept dept) + { + SysDept newParentDept = deptMapper.selectDeptById(dept.getParentId()); + SysDept oldDept = deptMapper.selectDeptById(dept.getDeptId()); + if (StringUtils.isNotNull(newParentDept) && StringUtils.isNotNull(oldDept)) + { + String newAncestors = newParentDept.getAncestors() + "," + newParentDept.getDeptId(); + String oldAncestors = oldDept.getAncestors(); + dept.setAncestors(newAncestors); + updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors); + } + int result = deptMapper.updateDept(dept); + if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors()) + && !StringUtils.equals("0", dept.getAncestors())) + { + // 如果该部门是启用状态,则启用该部门的所有上级部门 + updateParentDeptStatusNormal(dept); + } + return result; + } + + /** + * 修改该部门的父级部门状态 + * + * @param dept 当前部门 + */ + private void updateParentDeptStatusNormal(SysDept dept) + { + String ancestors = dept.getAncestors(); + Long[] deptIds = Convert.toLongArray(ancestors); + deptMapper.updateDeptStatusNormal(deptIds); + } + + /** + * 修改子元素关系 + * + * @param deptId 被修改的部门ID + * @param newAncestors 新的父ID集合 + * @param oldAncestors 旧的父ID集合 + */ + public void updateDeptChildren(Long deptId, String newAncestors, String oldAncestors) + { + List children = deptMapper.selectChildrenDeptById(deptId); + for (SysDept child : children) + { + child.setAncestors(child.getAncestors().replaceFirst(oldAncestors, newAncestors)); + } + if (children.size() > 0) + { + deptMapper.updateDeptChildren(children); + } + } + + /** + * 保存部门排序 + * + * @param deptIds 部门ID数组 + * @param orderNums 排序数组 + */ + @Override + @Transactional + public void updateDeptSort(String[] deptIds, String[] orderNums) + { + try + { + for (int i = 0; i < deptIds.length; i++) + { + SysDept dept = new SysDept(); + dept.setDeptId(Convert.toLong(deptIds[i])); + dept.setOrderNum(Convert.toInt(orderNums[i])); + deptMapper.updateDeptSort(dept); + } + } + catch (Exception e) + { + throw new ServiceException("保存排序异常,请联系管理员"); + } + } + + /** + * 删除部门管理信息 + * + * @param deptId 部门ID + * @return 结果 + */ + @Override + public int deleteDeptById(Long deptId) + { + return deptMapper.deleteDeptById(deptId); + } + + /** + * 递归列表 + */ + private void recursionFn(List list, SysDept t) + { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysDept tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysDept t) + { + List tlist = new ArrayList(); + Iterator it = list.iterator(); + while (it.hasNext()) + { + SysDept n = (SysDept) it.next(); + if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getDeptId().longValue()) + { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysDept t) + { + return getChildList(list, t).size() > 0; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysDictDataServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysDictDataServiceImpl.java new file mode 100644 index 000000000..c84570670 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysDictDataServiceImpl.java @@ -0,0 +1,111 @@ +package com.roomroot.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.roomroot.common.core.domain.entity.SysDictData; +import com.roomroot.common.utils.DictUtils; +import com.roomroot.system.mapper.SysDictDataMapper; +import com.roomroot.system.service.ISysDictDataService; + +/** + * 字典 业务层处理 + * + * @author roomroot + */ +@Service +public class SysDictDataServiceImpl implements ISysDictDataService +{ + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 根据条件分页查询字典数据 + * + * @param dictData 字典数据信息 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataList(SysDictData dictData) + { + return dictDataMapper.selectDictDataList(dictData); + } + + /** + * 根据字典类型和字典键值查询字典数据信息 + * + * @param dictType 字典类型 + * @param dictValue 字典键值 + * @return 字典标签 + */ + @Override + public String selectDictLabel(String dictType, String dictValue) + { + return dictDataMapper.selectDictLabel(dictType, dictValue); + } + + /** + * 根据字典数据ID查询信息 + * + * @param dictCode 字典数据ID + * @return 字典数据 + */ + @Override + public SysDictData selectDictDataById(Long dictCode) + { + return dictDataMapper.selectDictDataById(dictCode); + } + + /** + * 批量删除字典数据信息 + * + * @param dictCodes 需要删除的字典数据ID + */ + @Override + public void deleteDictDataByIds(Long[] dictCodes) + { + for (Long dictCode : dictCodes) + { + SysDictData data = selectDictDataById(dictCode); + dictDataMapper.deleteDictDataById(dictCode); + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + } + + /** + * 新增保存字典数据信息 + * + * @param data 字典数据信息 + * @return 结果 + */ + @Override + public int insertDictData(SysDictData data) + { + int row = dictDataMapper.insertDictData(data); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } + + /** + * 修改保存字典数据信息 + * + * @param data 字典数据信息 + * @return 结果 + */ + @Override + public int updateDictData(SysDictData data) + { + int row = dictDataMapper.updateDictData(data); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(data.getDictType()); + DictUtils.setDictCache(data.getDictType(), dictDatas); + } + return row; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysDictTypeServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysDictTypeServiceImpl.java new file mode 100644 index 000000000..bbbc249bf --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysDictTypeServiceImpl.java @@ -0,0 +1,223 @@ +package com.roomroot.system.service.impl; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import jakarta.annotation.PostConstruct; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.entity.SysDictData; +import com.roomroot.common.core.domain.entity.SysDictType; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.DictUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.mapper.SysDictDataMapper; +import com.roomroot.system.mapper.SysDictTypeMapper; +import com.roomroot.system.service.ISysDictTypeService; + +/** + * 字典 业务层处理 + * + * @author roomroot + */ +@Service +public class SysDictTypeServiceImpl implements ISysDictTypeService +{ + @Autowired + private SysDictTypeMapper dictTypeMapper; + + @Autowired + private SysDictDataMapper dictDataMapper; + + /** + * 项目启动时,初始化字典到缓存 + */ + @PostConstruct + public void init() + { + loadingDictCache(); + } + + /** + * 根据条件分页查询字典类型 + * + * @param dictType 字典类型信息 + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeList(SysDictType dictType) + { + return dictTypeMapper.selectDictTypeList(dictType); + } + + /** + * 根据所有字典类型 + * + * @return 字典类型集合信息 + */ + @Override + public List selectDictTypeAll() + { + return dictTypeMapper.selectDictTypeAll(); + } + + /** + * 根据字典类型查询字典数据 + * + * @param dictType 字典类型 + * @return 字典数据集合信息 + */ + @Override + public List selectDictDataByType(String dictType) + { + List dictDatas = DictUtils.getDictCache(dictType); + if (StringUtils.isNotEmpty(dictDatas)) + { + return dictDatas; + } + dictDatas = dictDataMapper.selectDictDataByType(dictType); + if (StringUtils.isNotEmpty(dictDatas)) + { + DictUtils.setDictCache(dictType, dictDatas); + return dictDatas; + } + return null; + } + + /** + * 根据字典类型ID查询信息 + * + * @param dictId 字典类型ID + * @return 字典类型 + */ + @Override + public SysDictType selectDictTypeById(Long dictId) + { + return dictTypeMapper.selectDictTypeById(dictId); + } + + /** + * 根据字典类型查询信息 + * + * @param dictType 字典类型 + * @return 字典类型 + */ + @Override + public SysDictType selectDictTypeByType(String dictType) + { + return dictTypeMapper.selectDictTypeByType(dictType); + } + + /** + * 批量删除字典类型信息 + * + * @param dictIds 需要删除的字典ID + */ + @Override + public void deleteDictTypeByIds(Long[] dictIds) + { + for (Long dictId : dictIds) + { + SysDictType dictType = selectDictTypeById(dictId); + if (dictDataMapper.countDictDataByType(dictType.getDictType()) > 0) + { + throw new ServiceException(String.format("%1$s已分配,不能删除", dictType.getDictName())); + } + dictTypeMapper.deleteDictTypeById(dictId); + DictUtils.removeDictCache(dictType.getDictType()); + } + } + + /** + * 加载字典缓存数据 + */ + @Override + public void loadingDictCache() + { + SysDictData dictData = new SysDictData(); + dictData.setStatus("0"); + Map> dictDataMap = dictDataMapper.selectDictDataList(dictData).stream().collect(Collectors.groupingBy(SysDictData::getDictType)); + for (Map.Entry> entry : dictDataMap.entrySet()) + { + DictUtils.setDictCache(entry.getKey(), entry.getValue().stream().sorted(Comparator.comparing(SysDictData::getDictSort)).collect(Collectors.toList())); + } + } + + /** + * 清空字典缓存数据 + */ + @Override + public void clearDictCache() + { + DictUtils.clearDictCache(); + } + + /** + * 重置字典缓存数据 + */ + @Override + public void resetDictCache() + { + clearDictCache(); + loadingDictCache(); + } + + /** + * 新增保存字典类型信息 + * + * @param dict 字典类型信息 + * @return 结果 + */ + @Override + public int insertDictType(SysDictType dict) + { + int row = dictTypeMapper.insertDictType(dict); + if (row > 0) + { + DictUtils.setDictCache(dict.getDictType(), null); + } + return row; + } + + /** + * 修改保存字典类型信息 + * + * @param dict 字典类型信息 + * @return 结果 + */ + @Override + @Transactional + public int updateDictType(SysDictType dict) + { + SysDictType oldDict = dictTypeMapper.selectDictTypeById(dict.getDictId()); + dictDataMapper.updateDictDataType(oldDict.getDictType(), dict.getDictType()); + int row = dictTypeMapper.updateDictType(dict); + if (row > 0) + { + List dictDatas = dictDataMapper.selectDictDataByType(dict.getDictType()); + DictUtils.setDictCache(dict.getDictType(), dictDatas); + } + return row; + } + + /** + * 校验字典类型称是否唯一 + * + * @param dict 字典类型 + * @return 结果 + */ + @Override + public boolean checkDictTypeUnique(SysDictType dict) + { + Long dictId = StringUtils.isNull(dict.getDictId()) ? -1L : dict.getDictId(); + SysDictType dictType = dictTypeMapper.checkDictTypeUnique(dict.getDictType()); + if (StringUtils.isNotNull(dictType) && dictType.getDictId().longValue() != dictId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysLogininforServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysLogininforServiceImpl.java new file mode 100644 index 000000000..75c796dd0 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysLogininforServiceImpl.java @@ -0,0 +1,94 @@ +package com.roomroot.system.service.impl; + +import java.util.List; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.common.utils.PageUtils; +import com.roomroot.system.domain.SysLogininfor; +import com.roomroot.system.mapper.SysLogininforMapper; +import com.roomroot.system.service.ISysLogininforService; + +/** + * 系统访问日志情况信息 服务层处理 + * + * @author roomroot + */ +@Service +public class SysLogininforServiceImpl implements ISysLogininforService +{ + + @Autowired + private SysLogininforMapper logininforMapper; + + /** + * 新增系统登录日志 + * + * @param logininfor 访问日志对象 + */ + @Override + public void insertLogininfor(SysLogininfor logininfor) + { + logininforMapper.insertLogininfor(logininfor); + } + + /** + * 查询系统登录日志集合 + * + * @param logininfor 访问日志对象 + * @return 登录记录集合 + */ + @Override + public List selectLogininforList(SysLogininfor logininfor) + { + Page page = PageUtils.getPage(); + if (page != null) + { + List allList = logininforMapper.selectLogininforList(logininfor); + ((Page) page).setTotal(allList.size()); + int start = (int) ((page.getCurrent() - 1) * page.getSize()); + int end = Math.min(start + (int) page.getSize(), allList.size()); + if (start < end) + { + return allList.subList(start, end); + } + return List.of(); + } + return logininforMapper.selectLogininforList(logininfor); + } + + /** + * 批量删除系统登录日志 + * + * @param infoIds 需要删除的登录日志ID + * @return 结果 + */ + @Override + public int deleteLogininforByIds(Long[] infoIds) + { + return logininforMapper.deleteLogininforByIds(infoIds); + } + + /** + * 清空系统登录日志 + */ + @Override + public void cleanLogininfor() + { + logininforMapper.cleanLogininfor(); + } + + /** + * 根据用户名查询登录统计信息 + * + * @param userName 用户名 + * @return 登录统计信息(上次登录时间、登录次数、最后登录IP) + */ + @Override + public Map selectLoginStatsByUserName(String userName) + { + return logininforMapper.selectLoginStatsByUserName(userName); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysMenuServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysMenuServiceImpl.java new file mode 100644 index 000000000..611a8f186 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysMenuServiceImpl.java @@ -0,0 +1,618 @@ +package com.roomroot.system.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.roomroot.common.constant.Constants; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.TreeSelect; +import com.roomroot.common.core.domain.entity.SysMenu; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.common.core.text.Convert; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.domain.vo.MetaVo; +import com.roomroot.system.domain.vo.RouterVo; +import com.roomroot.system.mapper.SysMenuMapper; +import com.roomroot.system.mapper.SysRoleMapper; +import com.roomroot.system.mapper.SysRoleMenuMapper; +import com.roomroot.system.service.ISysMenuService; + +/** + * 菜单 业务层处理 + * + * @author roomroot + */ +@Service +public class SysMenuServiceImpl implements ISysMenuService +{ + private static final Logger log = LoggerFactory.getLogger(SysMenuServiceImpl.class); + + public static final String PREMISSION_STRING = "perms[\"{0}\"]"; + + public static final Long MENU_ROOT_ID = 0L; + + @Autowired + private SysMenuMapper menuMapper; + + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + /** + * 根据用户查询系统菜单列表 + * + * @param userId 用户ID + * @return 菜单列表 + */ + @Override + public List selectMenuList(Long userId) + { + return selectMenuList(new SysMenu(), userId); + } + + /** + * 查询系统菜单列表 + * + * @param menu 菜单信息 + * @return 菜单列表 + */ + @Override + public List selectMenuList(SysMenu menu, Long userId) + { + List menuList = null; + // 管理员显示所有菜单信息 + if (SecurityUtils.isAdmin(userId)) + { + menuList = menuMapper.selectMenuList(menu); + } + else + { + menu.getParams().put("userId", userId); + menuList = menuMapper.selectMenuListByUserId(menu); + } + return menuList; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectMenuPermsByUserId(Long userId) + { + List perms = menuMapper.selectMenuPermsByUserId(userId); + Set permsSet = new HashSet<>(); + for (String perm : perms) + { + if (StringUtils.isNotEmpty(perm)) + { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据角色ID查询权限 + * + * @param roleId 角色ID + * @return 权限列表 + */ + @Override + public Set selectMenuPermsByRoleId(Long roleId) + { + List perms = menuMapper.selectMenuPermsByRoleId(roleId); + Set permsSet = new HashSet<>(); + for (String perm : perms) + { + if (StringUtils.isNotEmpty(perm)) + { + permsSet.addAll(Arrays.asList(perm.trim().split(","))); + } + } + return permsSet; + } + + /** + * 根据用户ID查询菜单 + * + * @param userId 用户名称 + * @return 菜单列表 + */ + @Override + public List selectMenuTreeByUserId(Long userId) + { + List menus = null; + if (SecurityUtils.isAdmin(userId)) + { + menus = menuMapper.selectMenuTreeAll(); + } + else + { + menus = menuMapper.selectMenuTreeByUserId(userId); + } + return getChildPerms(menus, MENU_ROOT_ID); + } + + /** + * 根据角色ID查询菜单树信息 + * + * @param roleId 角色ID + * @return 选中菜单列表 + */ + @Override + public List selectMenuListByRoleId(Long roleId) + { + SysRole role = roleMapper.selectRoleById(roleId); + return menuMapper.selectMenuListByRoleId(roleId, role.isMenuCheckStrictly()); + } + + /** + * 构建前端路由所需要的菜单 + * + * @param menus 菜单列表 + * @return 路由列表 + */ + @Override + public List buildMenus(List menus) + { + List routers = new LinkedList(); + for (SysMenu menu : menus) + { + RouterVo router = new RouterVo(); + router.setHidden("1".equals(menu.getVisible())); + router.setName(getRouteName(menu)); + router.setPath(getRouterPath(menu)); + router.setComponent(getComponent(menu)); + router.setQuery(menu.getQuery()); + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + List cMenus = menu.getChildren(); + if (StringUtils.isNotEmpty(cMenus) && UserConstants.TYPE_DIR.equals(menu.getMenuType())) + { + router.setAlwaysShow(true); + router.setRedirect("noRedirect"); + router.setChildren(buildMenus(cMenus)); + } + else if (isMenuFrame(menu)) + { + router.setMeta(null); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + children.setPath(menu.getPath()); + children.setComponent(menu.getComponent()); + children.setName(getRouteName(menu.getRouteName(), menu.getPath())); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath())); + children.setQuery(menu.getQuery()); + childrenList.add(children); + router.setChildren(childrenList); + } + else if (menu.getParentId().intValue() == MENU_ROOT_ID && isInnerLink(menu)) + { + router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon())); + router.setPath("/"); + List childrenList = new ArrayList(); + RouterVo children = new RouterVo(); + String routerPath = innerLinkReplaceEach(menu.getPath()); + children.setPath(routerPath); + children.setComponent(UserConstants.INNER_LINK); + children.setName(getRouteName(menu.getRouteName(), routerPath)); + children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath())); + childrenList.add(children); + router.setChildren(childrenList); + } + routers.add(router); + } + return routers; + } + + /** + * 构建前端所需要树结构 + * + * @param menus 菜单列表 + * @return 树结构列表 + */ + @Override + public List buildMenuTree(List menus) + { + List returnList = new ArrayList(); + List tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList()); + for (Iterator iterator = menus.iterator(); iterator.hasNext();) + { + SysMenu menu = (SysMenu) iterator.next(); + // 如果是顶级节点, 遍历该父节点的所有子节点 + if (!tempList.contains(menu.getParentId())) + { + recursionFn(menus, menu); + returnList.add(menu); + } + } + if (returnList.isEmpty()) + { + returnList = menus; + } + return returnList; + } + + /** + * 构建前端所需要下拉树结构 + * + * @param menus 菜单列表 + * @return 下拉树结构列表 + */ + @Override + public List buildMenuTreeSelect(List menus) + { + List menuTrees = buildMenuTree(menus); + return menuTrees.stream().map(TreeSelect::new).collect(Collectors.toList()); + } + + /** + * 根据菜单ID查询信息 + * + * @param menuId 菜单ID + * @return 菜单信息 + */ + @Override + public SysMenu selectMenuById(Long menuId) + { + return menuMapper.selectMenuById(menuId); + } + + /** + * 是否存在菜单子节点 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean hasChildByMenuId(Long menuId) + { + int result = menuMapper.hasChildByMenuId(menuId); + return result > 0; + } + + /** + * 查询菜单使用数量 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public boolean checkMenuExistRole(Long menuId) + { + int result = roleMenuMapper.checkMenuExistRole(menuId); + return result > 0; + } + + /** + * 新增保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public int insertMenu(SysMenu menu) + { + return menuMapper.insertMenu(menu); + } + + /** + * 修改保存菜单信息 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public int updateMenu(SysMenu menu) + { + return menuMapper.updateMenu(menu); + } + + /** + * 保存菜单排序 + * + * @param menuIds 菜单ID + * @param orderNums 排序ID + */ + @Override + @Transactional + public void updateMenuSort(String[] menuIds, String[] orderNums) + { + try + { + for (int i = 0; i < menuIds.length; i++) + { + SysMenu menu = new SysMenu(); + menu.setMenuId(Convert.toLong(menuIds[i])); + menu.setOrderNum(Convert.toInt(orderNums[i])); + menuMapper.updateMenuSort(menu); + } + } + catch (Exception e) + { + throw new ServiceException("保存排序异常,请联系管理员"); + } + } + + /** + * 删除菜单管理信息 + * + * @param menuId 菜单ID + * @return 结果 + */ + @Override + public int deleteMenuById(Long menuId) + { + return menuMapper.deleteMenuById(menuId); + } + + /** + * 校验菜单名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public boolean checkMenuNameUnique(SysMenu menu) + { + Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId(); + SysMenu info = menuMapper.checkMenuNameUnique(menu.getMenuName(), menu.getParentId()); + if (StringUtils.isNotNull(info) && info.getMenuId().longValue() != menuId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验路由名称是否唯一 + * + * @param menu 菜单信息 + * @return 结果 + */ + @Override + public boolean checkRouteConfigUnique(SysMenu menu) + { + Long menuId = StringUtils.isNull(menu.getMenuId()) ? -1L : menu.getMenuId(); + Long parentId = menu.getParentId(); + String path = menu.getPath(); + String routeName = StringUtils.isEmpty(menu.getRouteName()) ? path : menu.getRouteName(); + List sysMenuList = menuMapper.selectMenusByPathOrRouteName(path, routeName); + for (SysMenu sysMenu : sysMenuList) + { + if (sysMenu.getMenuId().longValue() != menuId.longValue()) + { + Long dbParentId = sysMenu.getParentId(); + String dbPath = sysMenu.getPath(); + String dbRouteName = StringUtils.isEmpty(sysMenu.getRouteName()) ? dbPath : sysMenu.getRouteName(); + if (StringUtils.equalsAnyIgnoreCase(path, dbPath) && parentId.longValue() == dbParentId.longValue()) + { + log.warn("[同级路由冲突] 同级下已存在相同路由路径 '{}',冲突菜单:{}", dbPath, sysMenu.getMenuName()); + return UserConstants.NOT_UNIQUE; + } + else if (StringUtils.equalsAnyIgnoreCase(path, dbPath) && parentId.longValue() == MENU_ROOT_ID) + { + log.warn("[根目录路由冲突] 根目录下路由 '{}' 必须唯一,已被菜单 '{}' 占用", path, sysMenu.getMenuName()); + return UserConstants.NOT_UNIQUE; + } + else if (StringUtils.equalsAnyIgnoreCase(routeName, dbRouteName)) + { + log.warn("[路由名称冲突] 路由名称 '{}' 需全局唯一,已被菜单 '{}' 使用", routeName, sysMenu.getMenuName()); + return UserConstants.NOT_UNIQUE; + } + } + } + return UserConstants.UNIQUE; + } + + /** + * 获取路由名称 + * + * @param menu 菜单信息 + * @return 路由名称 + */ + public String getRouteName(SysMenu menu) + { + // 非外链并且是一级目录(类型为目录) + if (isMenuFrame(menu)) + { + return StringUtils.EMPTY; + } + return getRouteName(menu.getRouteName(), menu.getPath()); + } + + /** + * 获取路由名称,如没有配置路由名称则取路由地址 + * + * @param name 路由名称 + * @param path 路由地址 + * @return 路由名称(驼峰格式) + */ + public String getRouteName(String name, String path) + { + String routerName = StringUtils.isNotEmpty(name) ? name : path; + return StringUtils.capitalize(routerName); + } + + /** + * 获取路由地址 + * + * @param menu 菜单信息 + * @return 路由地址 + */ + public String getRouterPath(SysMenu menu) + { + String routerPath = menu.getPath(); + // 内链打开外网方式 + if (menu.getParentId().intValue() != MENU_ROOT_ID && isInnerLink(menu)) + { + routerPath = innerLinkReplaceEach(routerPath); + } + // 非外链并且是一级目录(类型为目录) + if (MENU_ROOT_ID == menu.getParentId().intValue() && UserConstants.TYPE_DIR.equals(menu.getMenuType()) + && UserConstants.NO_FRAME.equals(menu.getIsFrame())) + { + routerPath = "/" + menu.getPath(); + } + // 非外链并且是一级目录(类型为菜单) + else if (isMenuFrame(menu)) + { + routerPath = "/"; + } + return routerPath; + } + + /** + * 获取组件信息 + * + * @param menu 菜单信息 + * @return 组件信息 + */ + public String getComponent(SysMenu menu) + { + String component = UserConstants.LAYOUT; + if (StringUtils.isNotEmpty(menu.getComponent()) && !isMenuFrame(menu)) + { + component = menu.getComponent(); + } + else if (StringUtils.isEmpty(menu.getComponent()) && menu.getParentId().intValue() != MENU_ROOT_ID && isInnerLink(menu)) + { + component = UserConstants.INNER_LINK; + } + else if (StringUtils.isEmpty(menu.getComponent()) && isParentView(menu)) + { + component = UserConstants.PARENT_VIEW; + } + return component; + } + + /** + * 是否为菜单内部跳转 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isMenuFrame(SysMenu menu) + { + return menu.getParentId().intValue() == MENU_ROOT_ID && UserConstants.TYPE_MENU.equals(menu.getMenuType()) + && menu.getIsFrame().equals(UserConstants.NO_FRAME); + } + + /** + * 是否为parent_view组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isParentView(SysMenu menu) + { + return menu.getParentId().intValue() != MENU_ROOT_ID && UserConstants.TYPE_DIR.equals(menu.getMenuType()); + } + + /** + * 是否为内链组件 + * + * @param menu 菜单信息 + * @return 结果 + */ + public boolean isInnerLink(SysMenu menu) + { + return menu.getIsFrame().equals(UserConstants.NO_FRAME) && StringUtils.ishttp(menu.getPath()); + } + + /** + * 根据父节点的ID获取所有子节点 + * + * @param list 分类表 + * @param parentId 传入的父节点ID + * @return String + */ + public List getChildPerms(List list, long parentId) + { + List returnList = new ArrayList(); + for (Iterator iterator = list.iterator(); iterator.hasNext();) + { + SysMenu t = (SysMenu) iterator.next(); + // 一、根据传入的某个父节点ID,遍历该父节点的所有子节点 + if (t.getParentId() == parentId) + { + recursionFn(list, t); + returnList.add(t); + } + } + return returnList; + } + + /** + * 递归列表 + * + * @param list 分类表 + * @param t 子节点 + */ + private void recursionFn(List list, SysMenu t) + { + // 得到子节点列表 + List childList = getChildList(list, t); + t.setChildren(childList); + for (SysMenu tChild : childList) + { + if (hasChild(list, tChild)) + { + recursionFn(list, tChild); + } + } + } + + /** + * 得到子节点列表 + */ + private List getChildList(List list, SysMenu t) + { + List tlist = new ArrayList(); + Iterator it = list.iterator(); + while (it.hasNext()) + { + SysMenu n = (SysMenu) it.next(); + if (n.getParentId().longValue() == t.getMenuId().longValue()) + { + tlist.add(n); + } + } + return tlist; + } + + /** + * 判断是否有子节点 + */ + private boolean hasChild(List list, SysMenu t) + { + return getChildList(list, t).size() > 0; + } + + /** + * 内链域名特殊字符替换 + * + * @return 替换后的内链域名 + */ + public String innerLinkReplaceEach(String path) + { + return StringUtils.replaceEach(path, new String[] { Constants.HTTP, Constants.HTTPS, Constants.WWW, ".", ":" }, + new String[] { "", "", "", "/", "/" }); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysNoticeReadServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysNoticeReadServiceImpl.java new file mode 100644 index 000000000..4ced2d6fc --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysNoticeReadServiceImpl.java @@ -0,0 +1,83 @@ +package com.roomroot.system.service.impl; + +import java.util.List; +import java.util.Map; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.roomroot.system.domain.SysNotice; +import com.roomroot.system.domain.SysNoticeRead; +import com.roomroot.system.mapper.SysNoticeReadMapper; +import com.roomroot.system.service.ISysNoticeReadService; + +/** + * 公告已读记录 服务层实现 + * + * @author roomroot + */ +@Service +public class SysNoticeReadServiceImpl implements ISysNoticeReadService +{ + @Autowired + private SysNoticeReadMapper noticeReadMapper; + + /** + * 标记已读 + */ + @Override + public void markRead(Long noticeId, Long userId) + { + SysNoticeRead record = new SysNoticeRead(); + record.setNoticeId(noticeId); + record.setUserId(userId); + noticeReadMapper.insertNoticeRead(record); + } + + /** + * 查询某用户未读公告数量 + */ + @Override + public int selectUnreadCount(Long userId) + { + return noticeReadMapper.selectUnreadCount(userId); + } + + /** + * 查询公告列表并标记当前用户已读状态 + */ + @Override + public List selectNoticeListWithReadStatus(Long userId, int limit) + { + return noticeReadMapper.selectNoticeListWithReadStatus(userId, limit); + } + + /** + * 批量标记已读 + */ + @Override + public void markReadBatch(Long userId, Long[] noticeIds) + { + if (noticeIds == null || noticeIds.length == 0) + { + return; + } + noticeReadMapper.insertNoticeReadBatch(userId, noticeIds); + } + + /** + * 查询已阅读某公告的用户列表 + */ + @Override + public List> selectReadUsersByNoticeId(Long noticeId, String searchValue) + { + return noticeReadMapper.selectReadUsersByNoticeId(noticeId, searchValue); + } + + /** + * 删除公告时清理对应已读记录 + */ + @Override + public void deleteByNoticeIds(Long[] noticeIds) + { + noticeReadMapper.deleteByNoticeIds(noticeIds); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysNoticeServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysNoticeServiceImpl.java new file mode 100644 index 000000000..3c61af595 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysNoticeServiceImpl.java @@ -0,0 +1,102 @@ +package com.roomroot.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.roomroot.common.utils.PageUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.roomroot.system.domain.SysNotice; +import com.roomroot.system.mapper.SysNoticeMapper; +import com.roomroot.system.service.ISysNoticeService; + +@Service +public class SysNoticeServiceImpl implements ISysNoticeService +{ + @Autowired + private SysNoticeMapper noticeMapper; + + /** + * 查询公告信息 + * + * @param noticeId 公告ID + * @return 公告信息 + */ + @Override + public SysNotice selectNoticeById(Long noticeId) + { + return noticeMapper.selectNoticeById(noticeId); + } + + /** + * 查询公告列表 + * + * @param notice 公告信息 + * @return 公告集合 + */ + @Override + public List selectNoticeList(SysNotice notice) + { + Page page = PageUtils.getPage(); + if (page != null) + { + List allList = noticeMapper.selectNoticeList(notice); + ((Page) page).setTotal(allList.size()); + int start = (int) ((page.getCurrent() - 1) * page.getSize()); + int end = Math.min(start + (int) page.getSize(), allList.size()); + if (start < end) + { + return allList.subList(start, end); + } + return List.of(); + } + return noticeMapper.selectNoticeList(notice); + } + + /** + * 新增公告 + * + * @param notice 公告信息 + * @return 结果 + */ + @Override + public int insertNotice(SysNotice notice) + { + return noticeMapper.insertNotice(notice); + } + + /** + * 修改公告 + * + * @param notice 公告信息 + * @return 结果 + */ + @Override + public int updateNotice(SysNotice notice) + { + return noticeMapper.updateNotice(notice); + } + + /** + * 删除公告对象 + * + * @param noticeId 公告ID + * @return 结果 + */ + @Override + public int deleteNoticeById(Long noticeId) + { + return noticeMapper.deleteNoticeById(noticeId); + } + + /** + * 批量删除公告信息 + * + * @param noticeIds 需要删除的公告ID + * @return 结果 + */ + @Override + public int deleteNoticeByIds(Long[] noticeIds) + { + return noticeMapper.deleteNoticeByIds(noticeIds); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysOperLogServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysOperLogServiceImpl.java new file mode 100644 index 000000000..a1399ead9 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysOperLogServiceImpl.java @@ -0,0 +1,76 @@ +package com.roomroot.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.roomroot.system.domain.SysOperLog; +import com.roomroot.system.mapper.SysOperLogMapper; +import com.roomroot.system.service.ISysOperLogService; + +/** + * 操作日志 服务层处理 + * + * @author roomroot + */ +@Service +public class SysOperLogServiceImpl implements ISysOperLogService +{ + @Autowired + private SysOperLogMapper operLogMapper; + + /** + * 新增操作日志 + * + * @param operLog 操作日志对象 + */ + @Override + public void insertOperlog(SysOperLog operLog) + { + operLogMapper.insertOperlog(operLog); + } + + /** + * 查询系统操作日志集合 + * + * @param operLog 操作日志对象 + * @return 操作日志集合 + */ + @Override + public List selectOperLogList(SysOperLog operLog) + { + return operLogMapper.selectOperLogList(operLog); + } + + /** + * 批量删除系统操作日志 + * + * @param operIds 需要删除的操作日志ID + * @return 结果 + */ + @Override + public int deleteOperLogByIds(Long[] operIds) + { + return operLogMapper.deleteOperLogByIds(operIds); + } + + /** + * 查询操作日志详细 + * + * @param operId 操作ID + * @return 操作日志对象 + */ + @Override + public SysOperLog selectOperLogById(Long operId) + { + return operLogMapper.selectOperLogById(operId); + } + + /** + * 清空操作日志 + */ + @Override + public void cleanOperLog() + { + operLogMapper.cleanOperLog(); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysPostServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysPostServiceImpl.java new file mode 100644 index 000000000..9a5447fde --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysPostServiceImpl.java @@ -0,0 +1,178 @@ +package com.roomroot.system.service.impl; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.domain.SysPost; +import com.roomroot.system.mapper.SysPostMapper; +import com.roomroot.system.mapper.SysUserPostMapper; +import com.roomroot.system.service.ISysPostService; + +/** + * 岗位信息 服务层处理 + * + * @author roomroot + */ +@Service +public class SysPostServiceImpl implements ISysPostService +{ + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + /** + * 查询岗位信息集合 + * + * @param post 岗位信息 + * @return 岗位信息集合 + */ + @Override + public List selectPostList(SysPost post) + { + return postMapper.selectPostList(post); + } + + /** + * 查询所有岗位 + * + * @return 岗位列表 + */ + @Override + public List selectPostAll() + { + return postMapper.selectPostAll(); + } + + /** + * 通过岗位ID查询岗位信息 + * + * @param postId 岗位ID + * @return 角色对象信息 + */ + @Override + public SysPost selectPostById(Long postId) + { + return postMapper.selectPostById(postId); + } + + /** + * 根据用户ID获取岗位选择框列表 + * + * @param userId 用户ID + * @return 选中岗位ID列表 + */ + @Override + public List selectPostListByUserId(Long userId) + { + return postMapper.selectPostListByUserId(userId); + } + + /** + * 校验岗位名称是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostNameUnique(SysPost post) + { + Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); + SysPost info = postMapper.checkPostNameUnique(post.getPostName()); + if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验岗位编码是否唯一 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public boolean checkPostCodeUnique(SysPost post) + { + Long postId = StringUtils.isNull(post.getPostId()) ? -1L : post.getPostId(); + SysPost info = postMapper.checkPostCodeUnique(post.getPostCode()); + if (StringUtils.isNotNull(info) && info.getPostId().longValue() != postId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 通过岗位ID查询岗位使用数量 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int countUserPostById(Long postId) + { + return userPostMapper.countUserPostById(postId); + } + + /** + * 删除岗位信息 + * + * @param postId 岗位ID + * @return 结果 + */ + @Override + public int deletePostById(Long postId) + { + return postMapper.deletePostById(postId); + } + + /** + * 批量删除岗位信息 + * + * @param postIds 需要删除的岗位ID + * @return 结果 + */ + @Override + public int deletePostByIds(Long[] postIds) + { + for (Long postId : postIds) + { + SysPost post = selectPostById(postId); + if (countUserPostById(postId) > 0) + { + throw new ServiceException(String.format("%1$s已分配,不能删除", post.getPostName())); + } + } + return postMapper.deletePostByIds(postIds); + } + + /** + * 新增保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public int insertPost(SysPost post) + { + return postMapper.insertPost(post); + } + + /** + * 修改保存岗位信息 + * + * @param post 岗位信息 + * @return 结果 + */ + @Override + public int updatePost(SysPost post) + { + return postMapper.updatePost(post); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysRoleServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysRoleServiceImpl.java new file mode 100644 index 000000000..3b7b63f58 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysRoleServiceImpl.java @@ -0,0 +1,426 @@ +package com.roomroot.system.service.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.roomroot.common.annotation.DataScope; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.spring.SpringUtils; +import com.roomroot.system.domain.SysRoleDept; +import com.roomroot.system.domain.SysRoleMenu; +import com.roomroot.system.domain.SysUserRole; +import com.roomroot.system.mapper.SysRoleDeptMapper; +import com.roomroot.system.mapper.SysRoleMapper; +import com.roomroot.system.mapper.SysRoleMenuMapper; +import com.roomroot.system.mapper.SysUserRoleMapper; +import com.roomroot.system.service.ISysRoleService; + +/** + * 角色 业务层处理 + * + * @author roomroot + */ +@Service +public class SysRoleServiceImpl implements ISysRoleService +{ + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysRoleMenuMapper roleMenuMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysRoleDeptMapper roleDeptMapper; + + /** + * 根据条件分页查询角色数据 + * + * @param role 角色信息 + * @return 角色数据集合信息 + */ + @Override + @DataScope(deptAlias = "d") + public List selectRoleList(SysRole role) + { + return roleMapper.selectRoleList(role); + } + + /** + * 根据用户ID查询角色 + * + * @param userId 用户ID + * @return 角色列表 + */ + @Override + public List selectRolesByUserId(Long userId) + { + List userRoles = roleMapper.selectRolePermissionByUserId(userId); + List roles = selectRoleAll(); + for (SysRole role : roles) + { + for (SysRole userRole : userRoles) + { + if (role.getRoleId().longValue() == userRole.getRoleId().longValue()) + { + role.setFlag(true); + break; + } + } + } + return roles; + } + + /** + * 根据用户ID查询权限 + * + * @param userId 用户ID + * @return 权限列表 + */ + @Override + public Set selectRolePermissionByUserId(Long userId) + { + List perms = roleMapper.selectRolePermissionByUserId(userId); + Set permsSet = new HashSet<>(); + for (SysRole perm : perms) + { + if (StringUtils.isNotNull(perm)) + { + permsSet.addAll(Arrays.asList(perm.getRoleKey().trim().split(","))); + } + } + return permsSet; + } + + /** + * 查询所有角色 + * + * @return 角色列表 + */ + @Override + public List selectRoleAll() + { + return SpringUtils.getAopProxy(this).selectRoleList(new SysRole()); + } + + /** + * 根据用户ID获取角色选择框列表 + * + * @param userId 用户ID + * @return 选中角色ID列表 + */ + @Override + public List selectRoleListByUserId(Long userId) + { + return roleMapper.selectRoleListByUserId(userId); + } + + /** + * 通过角色ID查询角色 + * + * @param roleId 角色ID + * @return 角色对象信息 + */ + @Override + public SysRole selectRoleById(Long roleId) + { + return roleMapper.selectRoleById(roleId); + } + + /** + * 校验角色名称是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleNameUnique(SysRole role) + { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRole info = roleMapper.checkRoleNameUnique(role.getRoleName()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色权限是否唯一 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public boolean checkRoleKeyUnique(SysRole role) + { + Long roleId = StringUtils.isNull(role.getRoleId()) ? -1L : role.getRoleId(); + SysRole info = roleMapper.checkRoleKeyUnique(role.getRoleKey()); + if (StringUtils.isNotNull(info) && info.getRoleId().longValue() != roleId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验角色是否允许操作 + * + * @param role 角色信息 + */ + @Override + public void checkRoleAllowed(SysRole role) + { + if (StringUtils.isNotNull(role.getRoleId()) && role.isAdmin()) + { + throw new ServiceException("不允许操作超级管理员角色"); + } + } + + /** + * 校验角色是否有数据权限 + * + * @param roleIds 角色id + */ + @Override + public void checkRoleDataScope(Long... roleIds) + { + if (!SecurityUtils.isAdmin()) + { + for (Long roleId : roleIds) + { + SysRole role = new SysRole(); + role.setRoleId(roleId); + List roles = SpringUtils.getAopProxy(this).selectRoleList(role); + if (StringUtils.isEmpty(roles)) + { + throw new ServiceException("没有权限访问角色数据!"); + } + } + } + } + + /** + * 通过角色ID查询角色使用数量 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + public int countUserRoleByRoleId(Long roleId) + { + return userRoleMapper.countUserRoleByRoleId(roleId); + } + + /** + * 新增保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int insertRole(SysRole role) + { + // 新增角色信息 + roleMapper.insertRole(role); + return insertRoleMenu(role); + } + + /** + * 修改保存角色信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int updateRole(SysRole role) + { + // 修改角色信息 + roleMapper.updateRole(role); + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(role.getRoleId()); + return insertRoleMenu(role); + } + + /** + * 修改角色状态 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + public int updateRoleStatus(SysRole role) + { + return roleMapper.updateRole(role); + } + + /** + * 修改数据权限信息 + * + * @param role 角色信息 + * @return 结果 + */ + @Override + @Transactional + public int authDataScope(SysRole role) + { + // 修改角色信息 + roleMapper.updateRole(role); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(role.getRoleId()); + // 新增角色和部门信息(数据权限) + return insertRoleDept(role); + } + + /** + * 新增角色菜单信息 + * + * @param role 角色对象 + */ + public int insertRoleMenu(SysRole role) + { + int rows = 1; + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long menuId : role.getMenuIds()) + { + SysRoleMenu rm = new SysRoleMenu(); + rm.setRoleId(role.getRoleId()); + rm.setMenuId(menuId); + list.add(rm); + } + if (list.size() > 0) + { + rows = roleMenuMapper.batchRoleMenu(list); + } + return rows; + } + + /** + * 新增角色部门信息(数据权限) + * + * @param role 角色对象 + */ + public int insertRoleDept(SysRole role) + { + int rows = 1; + // 新增角色与部门(数据权限)管理 + List list = new ArrayList(); + for (Long deptId : role.getDeptIds()) + { + SysRoleDept rd = new SysRoleDept(); + rd.setRoleId(role.getRoleId()); + rd.setDeptId(deptId); + list.add(rd); + } + if (list.size() > 0) + { + rows = roleDeptMapper.batchRoleDept(list); + } + return rows; + } + + /** + * 通过角色ID删除角色 + * + * @param roleId 角色ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRoleById(Long roleId) + { + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenuByRoleId(roleId); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDeptByRoleId(roleId); + return roleMapper.deleteRoleById(roleId); + } + + /** + * 批量删除角色信息 + * + * @param roleIds 需要删除的角色ID + * @return 结果 + */ + @Override + @Transactional + public int deleteRoleByIds(Long[] roleIds) + { + for (Long roleId : roleIds) + { + checkRoleAllowed(new SysRole(roleId)); + checkRoleDataScope(roleId); + SysRole role = selectRoleById(roleId); + if (countUserRoleByRoleId(roleId) > 0) + { + throw new ServiceException(String.format("%1$s已分配,不能删除", role.getRoleName())); + } + } + // 删除角色与菜单关联 + roleMenuMapper.deleteRoleMenu(roleIds); + // 删除角色与部门关联 + roleDeptMapper.deleteRoleDept(roleIds); + return roleMapper.deleteRoleByIds(roleIds); + } + + /** + * 取消授权用户角色 + * + * @param userRole 用户和角色关联信息 + * @return 结果 + */ + @Override + public int deleteAuthUser(SysUserRole userRole) + { + return userRoleMapper.deleteUserRoleInfo(userRole); + } + + /** + * 批量取消授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要取消授权的用户数据ID + * @return 结果 + */ + @Override + public int deleteAuthUsers(Long roleId, Long[] userIds) + { + return userRoleMapper.deleteUserRoleInfos(roleId, userIds); + } + + /** + * 批量选择授权用户角色 + * + * @param roleId 角色ID + * @param userIds 需要授权的用户数据ID + * @return 结果 + */ + @Override + public int insertAuthUsers(Long roleId, Long[] userIds) + { + // 新增用户与角色管理 + List list = new ArrayList(); + for (Long userId : userIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + return userRoleMapper.batchUserRole(list); + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysUserOnlineServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysUserOnlineServiceImpl.java new file mode 100644 index 000000000..ce7c9eb86 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysUserOnlineServiceImpl.java @@ -0,0 +1,96 @@ +package com.roomroot.system.service.impl; + +import org.springframework.stereotype.Service; +import com.roomroot.common.core.domain.model.LoginUser; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.system.domain.SysUserOnline; +import com.roomroot.system.service.ISysUserOnlineService; + +/** + * 在线用户 服务层处理 + * + * @author roomroot + */ +@Service +public class SysUserOnlineServiceImpl implements ISysUserOnlineService +{ + /** + * 通过登录地址查询信息 + * + * @param ipaddr 登录地址 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByIpaddr(String ipaddr, LoginUser user) + { + if (StringUtils.equals(ipaddr, user.getIpaddr())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 通过用户名称查询信息 + * + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByUserName(String userName, LoginUser user) + { + if (StringUtils.equals(userName, user.getUsername())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 通过登录地址/用户名称查询信息 + * + * @param ipaddr 登录地址 + * @param userName 用户名称 + * @param user 用户信息 + * @return 在线用户信息 + */ + @Override + public SysUserOnline selectOnlineByInfo(String ipaddr, String userName, LoginUser user) + { + if (StringUtils.equals(ipaddr, user.getIpaddr()) && StringUtils.equals(userName, user.getUsername())) + { + return loginUserToUserOnline(user); + } + return null; + } + + /** + * 设置在线用户信息 + * + * @param user 用户信息 + * @return 在线用户 + */ + @Override + public SysUserOnline loginUserToUserOnline(LoginUser user) + { + if (StringUtils.isNull(user) || StringUtils.isNull(user.getUser())) + { + return null; + } + SysUserOnline sysUserOnline = new SysUserOnline(); + sysUserOnline.setTokenId(user.getToken()); + sysUserOnline.setUserName(user.getUsername()); + sysUserOnline.setIpaddr(user.getIpaddr()); + sysUserOnline.setLoginLocation(user.getLoginLocation()); + sysUserOnline.setBrowser(user.getBrowser()); + sysUserOnline.setOs(user.getOs()); + sysUserOnline.setLoginTime(user.getLoginTime()); + if (StringUtils.isNotNull(user.getUser().getDept())) + { + sysUserOnline.setDeptName(user.getUser().getDept().getDeptName()); + } + return sysUserOnline; + } +} diff --git a/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysUserServiceImpl.java b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysUserServiceImpl.java new file mode 100644 index 000000000..d9695e981 --- /dev/null +++ b/backend/roomroot-system/src/main/java/com/roomroot/system/service/impl/SysUserServiceImpl.java @@ -0,0 +1,565 @@ +package com.roomroot.system.service.impl; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; +import jakarta.validation.Validator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import com.roomroot.common.annotation.DataScope; +import com.roomroot.common.constant.UserConstants; +import com.roomroot.common.core.domain.entity.SysRole; +import com.roomroot.common.core.domain.entity.SysUser; +import com.roomroot.common.exception.ServiceException; +import com.roomroot.common.utils.SecurityUtils; +import com.roomroot.common.utils.StringUtils; +import com.roomroot.common.utils.bean.BeanValidators; +import com.roomroot.common.utils.spring.SpringUtils; +import com.roomroot.system.domain.SysPost; +import com.roomroot.system.domain.SysUserPost; +import com.roomroot.system.domain.SysUserRole; +import com.roomroot.system.mapper.SysPostMapper; +import com.roomroot.system.mapper.SysRoleMapper; +import com.roomroot.system.mapper.SysUserMapper; +import com.roomroot.system.mapper.SysUserPostMapper; +import com.roomroot.system.mapper.SysUserRoleMapper; +import com.roomroot.system.service.ISysConfigService; +import com.roomroot.system.service.ISysDeptService; +import com.roomroot.system.service.ISysUserService; + +/** + * 用户 业务层处理 + * + * @author roomroot + */ +@Service +public class SysUserServiceImpl implements ISysUserService +{ + private static final Logger log = LoggerFactory.getLogger(SysUserServiceImpl.class); + + @Autowired + private SysUserMapper userMapper; + + @Autowired + private SysRoleMapper roleMapper; + + @Autowired + private SysPostMapper postMapper; + + @Autowired + private SysUserRoleMapper userRoleMapper; + + @Autowired + private SysUserPostMapper userPostMapper; + + @Autowired + private ISysConfigService configService; + + @Autowired + private ISysDeptService deptService; + + @Autowired + protected Validator validator; + + /** + * 根据条件分页查询用户列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUserList(SysUser user) + { + return userMapper.selectUserList(user); + } + + /** + * 根据条件分页查询已分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectAllocatedList(SysUser user) + { + return userMapper.selectAllocatedList(user); + } + + /** + * 根据条件分页查询未分配用户角色列表 + * + * @param user 用户信息 + * @return 用户信息集合信息 + */ + @Override + @DataScope(deptAlias = "d", userAlias = "u") + public List selectUnallocatedList(SysUser user) + { + return userMapper.selectUnallocatedList(user); + } + + /** + * 通过用户名查询用户 + * + * @param userName 用户名 + * @return 用户对象信息 + */ + @Override + public SysUser selectUserByUserName(String userName) + { + return userMapper.selectUserByUserName(userName); + } + + /** + * 通过用户ID查询用户 + * + * @param userId 用户ID + * @return 用户对象信息 + */ + @Override + public SysUser selectUserById(Long userId) + { + return userMapper.selectUserById(userId); + } + + /** + * 查询用户所属角色组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserRoleGroup(String userName) + { + List list = roleMapper.selectRolesByUserName(userName); + if (CollectionUtils.isEmpty(list)) + { + return StringUtils.EMPTY; + } + return list.stream().map(SysRole::getRoleName).collect(Collectors.joining(",")); + } + + /** + * 查询用户所属岗位组 + * + * @param userName 用户名 + * @return 结果 + */ + @Override + public String selectUserPostGroup(String userName) + { + List list = postMapper.selectPostsByUserName(userName); + if (CollectionUtils.isEmpty(list)) + { + return StringUtils.EMPTY; + } + return list.stream().map(SysPost::getPostName).collect(Collectors.joining(",")); + } + + /** + * 校验用户名称是否唯一 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean checkUserNameUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkUserNameUnique(user.getUserName()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验手机号码是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public boolean checkPhoneUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkPhoneUnique(user.getPhonenumber()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验email是否唯一 + * + * @param user 用户信息 + * @return + */ + @Override + public boolean checkEmailUnique(SysUser user) + { + Long userId = StringUtils.isNull(user.getUserId()) ? -1L : user.getUserId(); + SysUser info = userMapper.checkEmailUnique(user.getEmail()); + if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue()) + { + return UserConstants.NOT_UNIQUE; + } + return UserConstants.UNIQUE; + } + + /** + * 校验用户是否允许操作 + * + * @param user 用户信息 + */ + @Override + public void checkUserAllowed(SysUser user) + { + if (StringUtils.isNotNull(user.getUserId()) && user.isAdmin()) + { + throw new ServiceException("不允许操作超级管理员用户"); + } + } + + /** + * 校验用户是否有数据权限 + * + * @param userId 用户id + */ + @Override + public void checkUserDataScope(Long userId) + { + if (!SecurityUtils.isAdmin()) + { + SysUser user = new SysUser(); + user.setUserId(userId); + List users = SpringUtils.getAopProxy(this).selectUserList(user); + if (StringUtils.isEmpty(users)) + { + throw new ServiceException("没有权限访问用户数据!"); + } + } + } + + /** + * 新增保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int insertUser(SysUser user) + { + // 新增用户信息 + int rows = userMapper.insertUser(user); + // 新增用户岗位关联 + insertUserPost(user); + // 新增用户与角色管理 + insertUserRole(user); + return rows; + } + + /** + * 注册用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public boolean registerUser(SysUser user) + { + return userMapper.insertUser(user) > 0; + } + + /** + * 修改保存用户信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + @Transactional + public int updateUser(SysUser user) + { + Long userId = user.getUserId(); + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 新增用户与角色管理 + insertUserRole(user); + // 删除用户与岗位关联 + userPostMapper.deleteUserPostByUserId(userId); + // 新增用户与岗位管理 + insertUserPost(user); + return userMapper.updateUser(user); + } + + /** + * 用户授权角色 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + @Override + @Transactional + public void insertUserAuth(Long userId, Long[] roleIds) + { + userRoleMapper.deleteUserRoleByUserId(userId); + insertUserRole(userId, roleIds); + } + + /** + * 修改用户状态 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserStatus(SysUser user) + { + return userMapper.updateUserStatus(user.getUserId(), user.getStatus()); + } + + /** + * 修改用户基本信息 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int updateUserProfile(SysUser user) + { + return userMapper.updateUser(user); + } + + /** + * 修改用户头像 + * + * @param userId 用户ID + * @param avatar 头像地址 + * @return 结果 + */ + @Override + public boolean updateUserAvatar(Long userId, String avatar) + { + return userMapper.updateUserAvatar(userId, avatar) > 0; + } + + /** + * 更新用户登录信息(IP和登录时间) + * + * @param userId 用户ID + * @param loginIp 登录IP地址 + * @param loginDate 登录时间 + * @return 结果 + */ + public void updateLoginInfo(Long userId, String loginIp, Date loginDate) + { + userMapper.updateLoginInfo(userId, loginIp, loginDate); + } + + /** + * 重置用户密码 + * + * @param user 用户信息 + * @return 结果 + */ + @Override + public int resetPwd(SysUser user) + { + return userMapper.resetUserPwd(user.getUserId(), user.getPassword()); + } + + /** + * 重置用户密码 + * + * @param userId 用户ID + * @param password 密码 + * @return 结果 + */ + @Override + public int resetUserPwd(Long userId, String password) + { + return userMapper.resetUserPwd(userId, password); + } + + /** + * 新增用户角色信息 + * + * @param user 用户对象 + */ + public void insertUserRole(SysUser user) + { + this.insertUserRole(user.getUserId(), user.getRoleIds()); + } + + /** + * 新增用户岗位信息 + * + * @param user 用户对象 + */ + public void insertUserPost(SysUser user) + { + Long[] posts = user.getPostIds(); + if (StringUtils.isNotEmpty(posts)) + { + // 新增用户与岗位管理 + List list = new ArrayList(posts.length); + for (Long postId : posts) + { + SysUserPost up = new SysUserPost(); + up.setUserId(user.getUserId()); + up.setPostId(postId); + list.add(up); + } + userPostMapper.batchUserPost(list); + } + } + + /** + * 新增用户角色信息 + * + * @param userId 用户ID + * @param roleIds 角色组 + */ + public void insertUserRole(Long userId, Long[] roleIds) + { + if (StringUtils.isNotEmpty(roleIds)) + { + // 新增用户与角色管理 + List list = new ArrayList(roleIds.length); + for (Long roleId : roleIds) + { + SysUserRole ur = new SysUserRole(); + ur.setUserId(userId); + ur.setRoleId(roleId); + list.add(ur); + } + userRoleMapper.batchUserRole(list); + } + } + + /** + * 通过用户ID删除用户 + * + * @param userId 用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserById(Long userId) + { + // 删除用户与角色关联 + userRoleMapper.deleteUserRoleByUserId(userId); + // 删除用户与岗位表 + userPostMapper.deleteUserPostByUserId(userId); + return userMapper.deleteUserById(userId); + } + + /** + * 批量删除用户信息 + * + * @param userIds 需要删除的用户ID + * @return 结果 + */ + @Override + @Transactional + public int deleteUserByIds(Long[] userIds) + { + for (Long userId : userIds) + { + checkUserAllowed(new SysUser(userId)); + checkUserDataScope(userId); + } + // 删除用户与角色关联 + userRoleMapper.deleteUserRole(userIds); + // 删除用户与岗位关联 + userPostMapper.deleteUserPost(userIds); + return userMapper.deleteUserByIds(userIds); + } + + /** + * 导入用户数据 + * + * @param userList 用户数据列表 + * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 + * @param operName 操作用户 + * @return 结果 + */ + @Override + public String importUser(List userList, Boolean isUpdateSupport, String operName) + { + if (StringUtils.isNull(userList) || userList.size() == 0) + { + throw new ServiceException("导入用户数据不能为空!"); + } + int successNum = 0; + int failureNum = 0; + StringBuilder successMsg = new StringBuilder(); + StringBuilder failureMsg = new StringBuilder(); + for (SysUser user : userList) + { + try + { + // 验证是否存在这个用户 + SysUser u = userMapper.selectUserByUserName(user.getUserName()); + if (StringUtils.isNull(u)) + { + BeanValidators.validateWithException(validator, user); + deptService.checkDeptDataScope(user.getDeptId()); + String password = configService.selectConfigByKey("sys.user.initPassword"); + user.setPassword(SecurityUtils.encryptPassword(password)); + user.setCreateBy(operName); + userMapper.insertUser(user); + successNum++; + successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 导入成功"); + } + else if (isUpdateSupport) + { + BeanValidators.validateWithException(validator, user); + checkUserAllowed(u); + checkUserDataScope(u.getUserId()); + deptService.checkDeptDataScope(user.getDeptId()); + user.setUserId(u.getUserId()); + user.setDeptId(u.getDeptId()); + user.setUpdateBy(operName); + userMapper.updateUser(user); + successNum++; + successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 更新成功"); + } + else + { + failureNum++; + failureMsg.append("
" + failureNum + "、账号 " + user.getUserName() + " 已存在"); + } + } + catch (Exception e) + { + failureNum++; + String msg = "
" + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; + failureMsg.append(msg + e.getMessage()); + log.error(msg, e); + } + } + if (failureNum > 0) + { + failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); + throw new ServiceException(failureMsg.toString()); + } + else + { + successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); + } + return successMsg.toString(); + } +} diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysConfigMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysConfigMapper.xml new file mode 100644 index 000000000..427cb8cc3 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysConfigMapper.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + select config_id, config_name, config_key, config_value, config_type, create_by, create_time, update_by, update_time, remark + from sys_config + + + + + + + and config_id = #{configId} + + + and config_key = #{configKey} + + + + + + + + + + + + + + insert into sys_config ( + config_name, + config_key, + config_value, + config_type, + create_by, + remark, + create_time + )values( + #{configName}, + #{configKey}, + #{configValue}, + #{configType}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_config + + config_name = #{configName}, + config_key = #{configKey}, + config_value = #{configValue}, + config_type = #{configType}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where config_id = #{configId} + + + + delete from sys_config where config_id = #{configId} + + + + delete from sys_config where config_id in + + #{configId} + + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysDeptMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysDeptMapper.xml new file mode 100644 index 000000000..d1d2178bc --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysDeptMapper.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time + from sys_dept d + + + + + + + + + + + + + + + + + + + + insert into sys_dept( + dept_id, + parent_id, + dept_name, + ancestors, + order_num, + leader, + phone, + email, + status, + create_by, + create_time + )values( + #{deptId}, + #{parentId}, + #{deptName}, + #{ancestors}, + #{orderNum}, + #{leader}, + #{phone}, + #{email}, + #{status}, + #{createBy}, + sysdate() + ) + + + + update sys_dept + + parent_id = #{parentId}, + dept_name = #{deptName}, + ancestors = #{ancestors}, + order_num = #{orderNum}, + leader = #{leader}, + phone = #{phone}, + email = #{email}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where dept_id = #{deptId} + + + + update sys_dept set ancestors = + + when #{item.deptId} then #{item.ancestors} + + where dept_id in + + #{item.deptId} + + + + + update sys_dept set status = '0' where dept_id in + + #{deptId} + + + + + update sys_dept set order_num = #{orderNum} where dept_id = #{deptId} + + + + update sys_dept set del_flag = '2' where dept_id = #{deptId} + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysDictDataMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysDictDataMapper.xml new file mode 100644 index 000000000..509cf3a17 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysDictDataMapper.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + select dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark + from sys_dict_data + + + + + + + + + + + + + + delete from sys_dict_data where dict_code = #{dictCode} + + + + delete from sys_dict_data where dict_code in + + #{dictCode} + + + + + update sys_dict_data + + dict_sort = #{dictSort}, + dict_label = #{dictLabel}, + dict_value = #{dictValue}, + dict_type = #{dictType}, + css_class = #{cssClass}, + list_class = #{listClass}, + is_default = #{isDefault}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_code = #{dictCode} + + + + update sys_dict_data set dict_type = #{newDictType} where dict_type = #{oldDictType} + + + + insert into sys_dict_data( + dict_sort, + dict_label, + dict_value, + dict_type, + css_class, + list_class, + is_default, + status, + remark, + create_by, + create_time + )values( + #{dictSort}, + #{dictLabel}, + #{dictValue}, + #{dictType}, + #{cssClass}, + #{listClass}, + #{isDefault}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysDictTypeMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysDictTypeMapper.xml new file mode 100644 index 000000000..c7b223200 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysDictTypeMapper.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + select dict_id, dict_name, dict_type, status, create_by, create_time, remark + from sys_dict_type + + + + + + + + + + + + + + delete from sys_dict_type where dict_id = #{dictId} + + + + delete from sys_dict_type where dict_id in + + #{dictId} + + + + + update sys_dict_type + + dict_name = #{dictName}, + dict_type = #{dictType}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where dict_id = #{dictId} + + + + insert into sys_dict_type( + dict_name, + dict_type, + status, + remark, + create_by, + create_time + )values( + #{dictName}, + #{dictType}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysLogininforMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysLogininforMapper.xml new file mode 100644 index 000000000..ab2182f32 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysLogininforMapper.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + insert into sys_logininfor (user_name, status, ipaddr, login_location, browser, os, msg, login_time) + values (#{userName}, #{status}, #{ipaddr}, #{loginLocation}, #{browser}, #{os}, #{msg}, sysdate()) + + + + + + delete from sys_logininfor where info_id in + + #{infoId} + + + + + truncate table sys_logininfor + + + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysMenuMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysMenuMapper.xml new file mode 100644 index 000000000..f80e41007 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysMenuMapper.xml @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select menu_id, menu_name, parent_id, order_num, path, component, query, route_name, is_frame, is_cache, menu_type, visible, status, ifnull(perms,'') as perms, icon, create_time + from sys_menu + + + + + + + + + + + + + + + + + + + + + + + + + + + + update sys_menu + + menu_name = #{menuName}, + parent_id = #{parentId}, + order_num = #{orderNum}, + path = #{path}, + component = #{component}, + query = #{query}, + route_name = #{routeName}, + is_frame = #{isFrame}, + is_cache = #{isCache}, + menu_type = #{menuType}, + visible = #{visible}, + status = #{status}, + perms = #{perms}, + icon = #{icon}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where menu_id = #{menuId} + + + + insert into sys_menu( + menu_id, + parent_id, + menu_name, + order_num, + path, + component, + query, + route_name, + is_frame, + is_cache, + menu_type, + visible, + status, + perms, + icon, + remark, + create_by, + create_time + )values( + #{menuId}, + #{parentId}, + #{menuName}, + #{orderNum}, + #{path}, + #{component}, + #{query}, + #{routeName}, + #{isFrame}, + #{isCache}, + #{menuType}, + #{visible}, + #{status}, + #{perms}, + #{icon}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_menu set order_num = #{orderNum} where menu_id = #{menuId} + + + + delete from sys_menu where menu_id = #{menuId} + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysNoticeMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysNoticeMapper.xml new file mode 100644 index 000000000..ef97bc124 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysNoticeMapper.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + select notice_id, notice_title, notice_type, TO_CHAR(notice_content) AS notice_content, status, create_by, create_time, update_by, update_time, remark + from sys_notice + + + + + + + + insert into sys_notice ( + notice_title, + notice_type, + notice_content, + status, + remark, + create_by, + create_time + )values( + #{noticeTitle}, + #{noticeType}, + #{noticeContent}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_notice + + notice_title = #{noticeTitle}, + notice_type = #{noticeType}, + notice_content = #{noticeContent}, + status = #{status}, + update_by = #{updateBy}, + update_time = sysdate() + + where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id = #{noticeId} + + + + delete from sys_notice where notice_id in + + #{noticeId} + + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml new file mode 100644 index 000000000..40b2840a1 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysNoticeReadMapper.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + INSERT INTO sys_notice_read (notice_id, user_id, read_time) + SELECT #{noticeId}, #{userId}, SYSDATE + FROM DUAL + WHERE NOT EXISTS ( + SELECT 1 FROM sys_notice_read + WHERE notice_id = #{noticeId} AND user_id = #{userId} + ); + + + + + + + + + + + + + + insert ignore into sys_notice_read (notice_id, user_id, read_time) + values + + (#{noticeId}, #{userId}, sysdate()) + + + + + + delete from sys_notice_read where notice_id in + + #{noticeId} + + + + + + + diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysOperLogMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysOperLogMapper.xml new file mode 100644 index 000000000..2e863407e --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysOperLogMapper.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + select oper_id, title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, oper_time, cost_time + from sys_oper_log + + + + insert into sys_oper_log(title, business_type, method, request_method, operator_type, oper_name, dept_name, oper_url, oper_ip, oper_location, oper_param, json_result, status, error_msg, cost_time, oper_time) + values (#{title}, #{businessType}, #{method}, #{requestMethod}, #{operatorType}, #{operName}, #{deptName}, #{operUrl}, #{operIp}, #{operLocation}, #{operParam}, #{jsonResult}, #{status}, #{errorMsg}, #{costTime}, sysdate()) + + + + + + delete from sys_oper_log where oper_id in + + #{operId} + + + + + + + truncate table sys_oper_log + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysPostMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysPostMapper.xml new file mode 100644 index 000000000..c386f3ba4 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysPostMapper.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + select post_id, post_code, post_name, post_sort, status, create_by, create_time, remark + from sys_post + + + + + + + + + + + + + + + + + + update sys_post + + post_code = #{postCode}, + post_name = #{postName}, + post_sort = #{postSort}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where post_id = #{postId} + + + + insert into sys_post( + post_id, + post_code, + post_name, + post_sort, + status, + remark, + create_by, + create_time + )values( + #{postId}, + #{postCode}, + #{postName}, + #{postSort}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + delete from sys_post where post_id = #{postId} + + + + delete from sys_post where post_id in + + #{postId} + + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml new file mode 100644 index 000000000..084aa70dc --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysRoleDeptMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_role_dept where role_id=#{roleId} + + + + + + delete from sys_role_dept where role_id in + + #{roleId} + + + + + insert into sys_role_dept(role_id, dept_id) values + + (#{item.roleId},#{item.deptId}) + + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysRoleMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysRoleMapper.xml new file mode 100644 index 000000000..d1a28dd78 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysRoleMapper.xml @@ -0,0 +1,152 @@ + + + + + + + + + + + + + + + + + + + + + + + select distinct r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.menu_check_strictly, r.dept_check_strictly, + r.status, r.del_flag, r.create_time, r.remark + from sys_role r + left join sys_user_role ur on ur.role_id = r.role_id + left join sys_user u on u.user_id = ur.user_id + left join sys_dept d on u.dept_id = d.dept_id + + + + + + + + + + + + + + + + + + + + insert into sys_role( + role_id, + role_name, + role_key, + role_sort, + data_scope, + menu_check_strictly, + dept_check_strictly, + status, + remark, + create_by, + create_time + )values( + #{roleId}, + #{roleName}, + #{roleKey}, + #{roleSort}, + #{dataScope}, + #{menuCheckStrictly}, + #{deptCheckStrictly}, + #{status}, + #{remark}, + #{createBy}, + sysdate() + ) + + + + update sys_role + + role_name = #{roleName}, + role_key = #{roleKey}, + role_sort = #{roleSort}, + data_scope = #{dataScope}, + menu_check_strictly = #{menuCheckStrictly}, + dept_check_strictly = #{deptCheckStrictly}, + status = #{status}, + remark = #{remark}, + update_by = #{updateBy}, + update_time = sysdate() + + where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id = #{roleId} + + + + update sys_role set del_flag = '2' where role_id in + + #{roleId} + + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml new file mode 100644 index 000000000..1026d52d9 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysRoleMenuMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + delete from sys_role_menu where role_id=#{roleId} + + + + delete from sys_role_menu where role_id in + + #{roleId} + + + + + insert into sys_role_menu(role_id, menu_id) values + + (#{item.roleId},#{item.menuId}) + + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysUserMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysUserMapper.xml new file mode 100644 index 000000000..e080761f0 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysUserMapper.xml @@ -0,0 +1,227 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.avatar, u.phonenumber, u.password, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.pwd_update_date, u.create_by, u.create_time, u.update_by, u.update_time, u.remark, + d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.status as dept_status, + r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status + from sys_user u + left join sys_dept d on u.dept_id = d.dept_id + left join sys_user_role ur on u.user_id = ur.user_id + left join sys_role r on r.role_id = ur.role_id + + + + + + + + + + + + + + + + + + + + insert into sys_user( + user_id, + dept_id, + user_name, + nick_name, + email, + avatar, + phonenumber, + sex, + password, + status, + pwd_update_date, + create_by, + remark, + create_time + )values( + #{userId}, + #{deptId}, + #{userName}, + #{nickName}, + #{email}, + #{avatar}, + #{phonenumber}, + #{sex}, + #{password}, + #{status}, + #{pwdUpdateDate}, + #{createBy}, + #{remark}, + sysdate() + ) + + + + update sys_user + + dept_id = #{deptId}, + nick_name = #{nickName}, + email = #{email}, + phonenumber = #{phonenumber}, + sex = #{sex}, + avatar = #{avatar}, + password = #{password}, + status = #{status}, + login_ip = #{loginIp}, + login_date = #{loginDate}, + update_by = #{updateBy}, + remark = #{remark}, + update_time = sysdate() + + where user_id = #{userId} + + + + update sys_user set status = #{status}, update_time = sysdate() where user_id = #{userId} + + + + update sys_user set avatar = #{avatar}, update_time = sysdate() where user_id = #{userId} + + + + update sys_user set login_ip = #{loginIp}, login_date = #{loginDate} where user_id = #{userId} + + + + update sys_user set pwd_update_date = sysdate(), password = #{password}, update_time = sysdate() where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id = #{userId} + + + + update sys_user set del_flag = '2' where user_id in + + #{userId} + + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysUserPostMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysUserPostMapper.xml new file mode 100644 index 000000000..b43288640 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysUserPostMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + delete from sys_user_post where user_id=#{userId} + + + + + + delete from sys_user_post where user_id in + + #{userId} + + + + + insert into sys_user_post(user_id, post_id) values + + (#{item.userId},#{item.postId}) + + + + \ No newline at end of file diff --git a/backend/roomroot-system/src/main/resources/mapper/system/SysUserRoleMapper.xml b/backend/roomroot-system/src/main/resources/mapper/system/SysUserRoleMapper.xml new file mode 100644 index 000000000..aff265540 --- /dev/null +++ b/backend/roomroot-system/src/main/resources/mapper/system/SysUserRoleMapper.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + delete from sys_user_role where user_id=#{userId} + + + + + + delete from sys_user_role where user_id in + + #{userId} + + + + + insert into sys_user_role(user_id, role_id) values + + (#{item.userId},#{item.roleId}) + + + + + delete from sys_user_role where user_id=#{userId} and role_id=#{roleId} + + + + delete from sys_user_role where role_id=#{roleId} and user_id in + + #{userId} + + + \ No newline at end of file diff --git a/backend/sql/jwgl.sql b/backend/sql/jwgl.sql new file mode 100644 index 000000000..e69de29bb diff --git a/backend/sql/quartz.sql b/backend/sql/quartz.sql new file mode 100644 index 000000000..1e6afd018 --- /dev/null +++ b/backend/sql/quartz.sql @@ -0,0 +1,174 @@ +DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS; +DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE; +DROP TABLE IF EXISTS QRTZ_LOCKS; +DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_TRIGGERS; +DROP TABLE IF EXISTS QRTZ_JOB_DETAILS; +DROP TABLE IF EXISTS QRTZ_CALENDARS; + +-- ---------------------------- +-- 1、存储每一个已配置的 jobDetail 的详细信息 +-- ---------------------------- +create table QRTZ_JOB_DETAILS ( + sched_name varchar(120) not null comment '调度名称', + job_name varchar(200) not null comment '任务名称', + job_group varchar(200) not null comment '任务组名', + description varchar(250) null comment '相关介绍', + job_class_name varchar(250) not null comment '执行任务类名称', + is_durable varchar(1) not null comment '是否持久化', + is_nonconcurrent varchar(1) not null comment '是否并发', + is_update_data varchar(1) not null comment '是否更新数据', + requests_recovery varchar(1) not null comment '是否接受恢复执行', + job_data blob null comment '存放持久化job对象', + primary key (sched_name, job_name, job_group) +) engine=innodb comment = '任务详细信息表'; + +-- ---------------------------- +-- 2、 存储已配置的 Trigger 的信息 +-- ---------------------------- +create table QRTZ_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment '触发器的名字', + trigger_group varchar(200) not null comment '触发器所属组的名字', + job_name varchar(200) not null comment 'qrtz_job_details表job_name的外键', + job_group varchar(200) not null comment 'qrtz_job_details表job_group的外键', + description varchar(250) null comment '相关介绍', + next_fire_time bigint(13) null comment '上一次触发时间(毫秒)', + prev_fire_time bigint(13) null comment '下一次触发时间(默认为-1表示不触发)', + priority integer null comment '优先级', + trigger_state varchar(16) not null comment '触发器状态', + trigger_type varchar(8) not null comment '触发器的类型', + start_time bigint(13) not null comment '开始时间', + end_time bigint(13) null comment '结束时间', + calendar_name varchar(200) null comment '日程表名称', + misfire_instr smallint(2) null comment '补偿执行的策略', + job_data blob null comment '存放持久化job对象', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, job_name, job_group) references QRTZ_JOB_DETAILS(sched_name, job_name, job_group) +) engine=innodb comment = '触发器详细信息表'; + +-- ---------------------------- +-- 3、 存储简单的 Trigger,包括重复次数,间隔,以及已触发的次数 +-- ---------------------------- +create table QRTZ_SIMPLE_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + repeat_count bigint(7) not null comment '重复的次数统计', + repeat_interval bigint(12) not null comment '重复的间隔时间', + times_triggered bigint(10) not null comment '已经触发的次数', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = '简单触发器的信息表'; + +-- ---------------------------- +-- 4、 存储 Cron Trigger,包括 Cron 表达式和时区信息 +-- ---------------------------- +create table QRTZ_CRON_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + cron_expression varchar(200) not null comment 'cron表达式', + time_zone_id varchar(80) comment '时区', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = 'Cron类型的触发器表'; + +-- ---------------------------- +-- 5、 Trigger 作为 Blob 类型存储(用于 Quartz 用户用 JDBC 创建他们自己定制的 Trigger 类型,JobStore 并不知道如何存储实例的时候) +-- ---------------------------- +create table QRTZ_BLOB_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + blob_data blob null comment '存放持久化Trigger对象', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = 'Blob类型的触发器表'; + +-- ---------------------------- +-- 6、 以 Blob 类型存储存放日历信息, quartz可配置一个日历来指定一个时间范围 +-- ---------------------------- +create table QRTZ_CALENDARS ( + sched_name varchar(120) not null comment '调度名称', + calendar_name varchar(200) not null comment '日历名称', + calendar blob not null comment '存放持久化calendar对象', + primary key (sched_name, calendar_name) +) engine=innodb comment = '日历信息表'; + +-- ---------------------------- +-- 7、 存储已暂停的 Trigger 组的信息 +-- ---------------------------- +create table QRTZ_PAUSED_TRIGGER_GRPS ( + sched_name varchar(120) not null comment '调度名称', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + primary key (sched_name, trigger_group) +) engine=innodb comment = '暂停的触发器表'; + +-- ---------------------------- +-- 8、 存储与已触发的 Trigger 相关的状态信息,以及相联 Job 的执行信息 +-- ---------------------------- +create table QRTZ_FIRED_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + entry_id varchar(95) not null comment '调度器实例id', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + instance_name varchar(200) not null comment '调度器实例名', + fired_time bigint(13) not null comment '触发的时间', + sched_time bigint(13) not null comment '定时器制定的时间', + priority integer not null comment '优先级', + state varchar(16) not null comment '状态', + job_name varchar(200) null comment '任务名称', + job_group varchar(200) null comment '任务组名', + is_nonconcurrent varchar(1) null comment '是否并发', + requests_recovery varchar(1) null comment '是否接受恢复执行', + primary key (sched_name, entry_id) +) engine=innodb comment = '已触发的触发器表'; + +-- ---------------------------- +-- 9、 存储少量的有关 Scheduler 的状态信息,假如是用于集群中,可以看到其他的 Scheduler 实例 +-- ---------------------------- +create table QRTZ_SCHEDULER_STATE ( + sched_name varchar(120) not null comment '调度名称', + instance_name varchar(200) not null comment '实例名称', + last_checkin_time bigint(13) not null comment '上次检查时间', + checkin_interval bigint(13) not null comment '检查间隔时间', + primary key (sched_name, instance_name) +) engine=innodb comment = '调度器状态表'; + +-- ---------------------------- +-- 10、 存储程序的悲观锁的信息(假如使用了悲观锁) +-- ---------------------------- +create table QRTZ_LOCKS ( + sched_name varchar(120) not null comment '调度名称', + lock_name varchar(40) not null comment '悲观锁名称', + primary key (sched_name, lock_name) +) engine=innodb comment = '存储的悲观锁信息表'; + +-- ---------------------------- +-- 11、 Quartz集群实现同步机制的行锁表 +-- ---------------------------- +create table QRTZ_SIMPROP_TRIGGERS ( + sched_name varchar(120) not null comment '调度名称', + trigger_name varchar(200) not null comment 'qrtz_triggers表trigger_name的外键', + trigger_group varchar(200) not null comment 'qrtz_triggers表trigger_group的外键', + str_prop_1 varchar(512) null comment 'String类型的trigger的第一个参数', + str_prop_2 varchar(512) null comment 'String类型的trigger的第二个参数', + str_prop_3 varchar(512) null comment 'String类型的trigger的第三个参数', + int_prop_1 int null comment 'int类型的trigger的第一个参数', + int_prop_2 int null comment 'int类型的trigger的第二个参数', + long_prop_1 bigint null comment 'long类型的trigger的第一个参数', + long_prop_2 bigint null comment 'long类型的trigger的第二个参数', + dec_prop_1 numeric(13,4) null comment 'decimal类型的trigger的第一个参数', + dec_prop_2 numeric(13,4) null comment 'decimal类型的trigger的第二个参数', + bool_prop_1 varchar(1) null comment 'Boolean类型的trigger的第一个参数', + bool_prop_2 varchar(1) null comment 'Boolean类型的trigger的第二个参数', + primary key (sched_name, trigger_name, trigger_group), + foreign key (sched_name, trigger_name, trigger_group) references QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group) +) engine=innodb comment = '同步机制的行锁表'; + +commit; \ No newline at end of file diff --git a/backend/sql/ry_20260417.sql b/backend/sql/ry_20260417.sql new file mode 100644 index 000000000..86a9b9ae1 --- /dev/null +++ b/backend/sql/ry_20260417.sql @@ -0,0 +1,723 @@ +-- ---------------------------- +-- 1、部门表 +-- ---------------------------- +drop table if exists sys_dept; +create table sys_dept ( + dept_id bigint(20) not null auto_increment comment '部门id', + parent_id bigint(20) default 0 comment '父部门id', + ancestors varchar(50) default '' comment '祖级列表', + dept_name varchar(30) default '' comment '部门名称', + order_num int(4) default 0 comment '显示顺序', + leader varchar(20) default null comment '负责人', + phone varchar(11) default null comment '联系电话', + email varchar(50) default null comment '邮箱', + status char(1) default '0' comment '部门状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (dept_id) +) engine=innodb auto_increment=200 comment = '部门表'; + +-- ---------------------------- +-- 初始化-部门表数据 +-- ---------------------------- +insert into sys_dept values(100, 0, '0', '风影随行科技', 0, '风影随行', '15888888888', 'fysx@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(101, 100, '0,100', '深圳总公司', 1, '风影随行', '15888888888', 'fysx@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(102, 100, '0,100', '长沙分公司', 2, '风影随行', '15888888888', 'fysx@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(103, 101, '0,100,101', '研发部门', 1, '风影随行', '15888888888', 'fysx@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(104, 101, '0,100,101', '市场部门', 2, '风影随行', '15888888888', 'fysx@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(105, 101, '0,100,101', '测试部门', 3, '风影随行', '15888888888', 'fysx@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(106, 101, '0,100,101', '财务部门', 4, '风影随行', '15888888888', 'fysx@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(107, 101, '0,100,101', '运维部门', 5, '风影随行', '15888888888', 'fysx@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(108, 102, '0,100,102', '市场部门', 1, '风影随行', '15888888888', 'fysx@qq.com', '0', '0', 'admin', sysdate(), '', null); +insert into sys_dept values(109, 102, '0,100,102', '财务部门', 2, '风影随行', '15888888888', 'fysx@qq.com', '0', '0', 'admin', sysdate(), '', null); + + +-- ---------------------------- +-- 2、用户信息表 +-- ---------------------------- +drop table if exists sys_user; +create table sys_user ( + user_id bigint(20) not null auto_increment comment '用户ID', + dept_id bigint(20) default null comment '部门ID', + user_name varchar(30) not null comment '用户账号', + nick_name varchar(30) not null comment '用户昵称', + user_type varchar(2) default '00' comment '用户类型(00系统用户)', + email varchar(50) default '' comment '用户邮箱', + phonenumber varchar(11) default '' comment '手机号码', + sex char(1) default '0' comment '用户性别(0男 1女 2未知)', + avatar varchar(100) default '' comment '头像地址', + password varchar(100) default '' comment '密码', + status char(1) default '0' comment '账号状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + login_ip varchar(128) default '' comment '最后登录IP', + login_date datetime comment '最后登录时间', + pwd_update_date datetime comment '密码最后更新时间', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (user_id) +) engine=innodb auto_increment=100 comment = '用户信息表'; + +-- ---------------------------- +-- 初始化-用户信息表数据 +-- ---------------------------- +insert into sys_user values(1, 103, 'admin', '风影随行', '00', 'ry@163.com', '15888888888', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '管理员'); +insert into sys_user values(2, 105, 'ry', '风影随行', '00', 'fysx@qq.com ', '15666666666', '1', '', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE8ByOhJIrdAu2', '0', '0', '127.0.0.1', sysdate(), sysdate(), 'admin', sysdate(), '', null, '测试员'); + + +-- ---------------------------- +-- 3、岗位信息表 +-- ---------------------------- +drop table if exists sys_post; +create table sys_post +( + post_id bigint(20) not null auto_increment comment '岗位ID', + post_code varchar(64) not null comment '岗位编码', + post_name varchar(50) not null comment '岗位名称', + post_sort int(4) not null comment '显示顺序', + status char(1) not null comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (post_id) +) engine=innodb comment = '岗位信息表'; + +-- ---------------------------- +-- 初始化-岗位信息表数据 +-- ---------------------------- +insert into sys_post values(1, 'ceo', '董事长', 1, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(2, 'se', '项目经理', 2, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(3, 'hr', '人力资源', 3, '0', 'admin', sysdate(), '', null, ''); +insert into sys_post values(4, 'user', '普通员工', 4, '0', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 4、角色信息表 +-- ---------------------------- +drop table if exists sys_role; +create table sys_role ( + role_id bigint(20) not null auto_increment comment '角色ID', + role_name varchar(30) not null comment '角色名称', + role_key varchar(100) not null comment '角色权限字符串', + role_sort int(4) not null comment '显示顺序', + data_scope char(1) default '1' comment '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + menu_check_strictly tinyint(1) default 1 comment '菜单树选择项是否关联显示', + dept_check_strictly tinyint(1) default 1 comment '部门树选择项是否关联显示', + status char(1) not null comment '角色状态(0正常 1停用)', + del_flag char(1) default '0' comment '删除标志(0代表存在 2代表删除)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (role_id) +) engine=innodb auto_increment=100 comment = '角色信息表'; + +-- ---------------------------- +-- 初始化-角色信息表数据 +-- ---------------------------- +insert into sys_role values('1', '超级管理员', 'admin', 1, 1, 1, 1, '0', '0', 'admin', sysdate(), '', null, '超级管理员'); +insert into sys_role values('2', '普通角色', 'common', 2, 2, 1, 1, '0', '0', 'admin', sysdate(), '', null, '普通角色'); + + +-- ---------------------------- +-- 5、菜单权限表 +-- ---------------------------- +drop table if exists sys_menu; +create table sys_menu ( + menu_id bigint(20) not null auto_increment comment '菜单ID', + menu_name varchar(50) not null comment '菜单名称', + parent_id bigint(20) default 0 comment '父菜单ID', + order_num int(4) default 0 comment '显示顺序', + path varchar(200) default '' comment '路由地址', + component varchar(255) default null comment '组件路径', + query varchar(255) default null comment '路由参数', + route_name varchar(50) default '' comment '路由名称', + is_frame int(1) default 1 comment '是否为外链(0是 1否)', + is_cache int(1) default 0 comment '是否缓存(0缓存 1不缓存)', + menu_type char(1) default '' comment '菜单类型(M目录 C菜单 F按钮)', + visible char(1) default 0 comment '菜单状态(0显示 1隐藏)', + status char(1) default 0 comment '菜单状态(0正常 1停用)', + perms varchar(100) default null comment '权限标识', + icon varchar(100) default '#' comment '菜单图标', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注', + primary key (menu_id) +) engine=innodb auto_increment=2000 comment = '菜单权限表'; + +-- ---------------------------- +-- 初始化-菜单信息表数据 +-- ---------------------------- +-- 一级菜单 +insert into sys_menu values('1', '系统管理', '0', '1', 'system', null, '', '', 1, 0, 'M', '0', '0', '', 'system', 'admin', sysdate(), '', null, '系统管理目录'); +insert into sys_menu values('2', '系统监控', '0', '2', 'monitor', null, '', '', 1, 0, 'M', '0', '0', '', 'monitor', 'admin', sysdate(), '', null, '系统监控目录'); +insert into sys_menu values('3', '系统工具', '0', '3', 'tool', null, '', '', 1, 0, 'M', '0', '0', '', 'tool', 'admin', sysdate(), '', null, '系统工具目录'); +insert into sys_menu values('4', '风影随行官网', '0', '4', 'http://ruoyi.vip', null, '', '', 0, 0, 'M', '0', '0', '', 'guide', 'admin', sysdate(), '', null, '风影随行官网地址'); +-- 二级菜单 +insert into sys_menu values('100', '用户管理', '1', '1', 'user', 'system/user/index', '', '', 1, 0, 'C', '0', '0', 'system:user:list', 'user', 'admin', sysdate(), '', null, '用户管理菜单'); +insert into sys_menu values('101', '角色管理', '1', '2', 'role', 'system/role/index', '', '', 1, 0, 'C', '0', '0', 'system:role:list', 'peoples', 'admin', sysdate(), '', null, '角色管理菜单'); +insert into sys_menu values('102', '菜单管理', '1', '3', 'menu', 'system/menu/index', '', '', 1, 0, 'C', '0', '0', 'system:menu:list', 'tree-table', 'admin', sysdate(), '', null, '菜单管理菜单'); +insert into sys_menu values('103', '部门管理', '1', '4', 'dept', 'system/dept/index', '', '', 1, 0, 'C', '0', '0', 'system:dept:list', 'tree', 'admin', sysdate(), '', null, '部门管理菜单'); +insert into sys_menu values('104', '岗位管理', '1', '5', 'post', 'system/post/index', '', '', 1, 0, 'C', '0', '0', 'system:post:list', 'post', 'admin', sysdate(), '', null, '岗位管理菜单'); +insert into sys_menu values('105', '字典管理', '1', '6', 'dict', 'system/dict/index', '', '', 1, 0, 'C', '0', '0', 'system:dict:list', 'dict', 'admin', sysdate(), '', null, '字典管理菜单'); +insert into sys_menu values('106', '参数设置', '1', '7', 'config', 'system/config/index', '', '', 1, 0, 'C', '0', '0', 'system:config:list', 'edit', 'admin', sysdate(), '', null, '参数设置菜单'); +insert into sys_menu values('107', '通知公告', '1', '8', 'notice', 'system/notice/index', '', '', 1, 0, 'C', '0', '0', 'system:notice:list', 'message', 'admin', sysdate(), '', null, '通知公告菜单'); +insert into sys_menu values('108', '日志管理', '1', '9', 'log', '', '', '', 1, 0, 'M', '0', '0', '', 'log', 'admin', sysdate(), '', null, '日志管理菜单'); +insert into sys_menu values('109', '在线用户', '2', '1', 'online', 'monitor/online/index', '', '', 1, 0, 'C', '0', '0', 'monitor:online:list', 'online', 'admin', sysdate(), '', null, '在线用户菜单'); +insert into sys_menu values('110', '定时任务', '2', '2', 'job', 'monitor/job/index', '', '', 1, 0, 'C', '0', '0', 'monitor:job:list', 'job', 'admin', sysdate(), '', null, '定时任务菜单'); +insert into sys_menu values('111', '数据监控', '2', '3', 'druid', 'monitor/druid/index', '', '', 1, 0, 'C', '0', '0', 'monitor:druid:list', 'druid', 'admin', sysdate(), '', null, '数据监控菜单'); +insert into sys_menu values('112', '服务监控', '2', '4', 'server', 'monitor/server/index', '', '', 1, 0, 'C', '0', '0', 'monitor:server:list', 'server', 'admin', sysdate(), '', null, '服务监控菜单'); +insert into sys_menu values('113', '缓存监控', '2', '5', 'cache', 'monitor/cache/index', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis', 'admin', sysdate(), '', null, '缓存监控菜单'); +insert into sys_menu values('114', '缓存列表', '2', '6', 'cacheList', 'monitor/cache/list', '', '', 1, 0, 'C', '0', '0', 'monitor:cache:list', 'redis-list', 'admin', sysdate(), '', null, '缓存列表菜单'); +insert into sys_menu values('115', '表单构建', '3', '1', 'build', 'tool/build/index', '', '', 1, 0, 'C', '0', '0', 'tool:build:list', 'build', 'admin', sysdate(), '', null, '表单构建菜单'); +insert into sys_menu values('116', '代码生成', '3', '2', 'gen', 'tool/gen/index', '', '', 1, 0, 'C', '0', '0', 'tool:gen:list', 'code', 'admin', sysdate(), '', null, '代码生成菜单'); +insert into sys_menu values('117', '系统接口', '3', '3', 'swagger', 'tool/swagger/index', '', '', 1, 0, 'C', '0', '0', 'tool:swagger:list', 'swagger', 'admin', sysdate(), '', null, '系统接口菜单'); +-- 三级菜单 +insert into sys_menu values('500', '操作日志', '108', '1', 'operlog', 'monitor/operlog/index', '', '', 1, 0, 'C', '0', '0', 'monitor:operlog:list', 'form', 'admin', sysdate(), '', null, '操作日志菜单'); +insert into sys_menu values('501', '登录日志', '108', '2', 'logininfor', 'monitor/logininfor/index', '', '', 1, 0, 'C', '0', '0', 'monitor:logininfor:list', 'logininfor', 'admin', sysdate(), '', null, '登录日志菜单'); +-- 用户管理按钮 +insert into sys_menu values('1000', '用户查询', '100', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1001', '用户新增', '100', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1002', '用户修改', '100', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1003', '用户删除', '100', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1004', '用户导出', '100', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1005', '用户导入', '100', '6', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1006', '重置密码', '100', '7', '', '', '', '', 1, 0, 'F', '0', '0', 'system:user:resetPwd', '#', 'admin', sysdate(), '', null, ''); +-- 角色管理按钮 +insert into sys_menu values('1007', '角色查询', '101', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1008', '角色新增', '101', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1009', '角色修改', '101', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1010', '角色删除', '101', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1011', '角色导出', '101', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:role:export', '#', 'admin', sysdate(), '', null, ''); +-- 菜单管理按钮 +insert into sys_menu values('1012', '菜单查询', '102', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1013', '菜单新增', '102', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1014', '菜单修改', '102', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1015', '菜单删除', '102', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:menu:remove', '#', 'admin', sysdate(), '', null, ''); +-- 部门管理按钮 +insert into sys_menu values('1016', '部门查询', '103', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1017', '部门新增', '103', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1018', '部门修改', '103', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1019', '部门删除', '103', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:dept:remove', '#', 'admin', sysdate(), '', null, ''); +-- 岗位管理按钮 +insert into sys_menu values('1020', '岗位查询', '104', '1', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1021', '岗位新增', '104', '2', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1022', '岗位修改', '104', '3', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1023', '岗位删除', '104', '4', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1024', '岗位导出', '104', '5', '', '', '', '', 1, 0, 'F', '0', '0', 'system:post:export', '#', 'admin', sysdate(), '', null, ''); +-- 字典管理按钮 +insert into sys_menu values('1025', '字典查询', '105', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1026', '字典新增', '105', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1027', '字典修改', '105', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1028', '字典删除', '105', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1029', '字典导出', '105', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:dict:export', '#', 'admin', sysdate(), '', null, ''); +-- 参数设置按钮 +insert into sys_menu values('1030', '参数查询', '106', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1031', '参数新增', '106', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1032', '参数修改', '106', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1033', '参数删除', '106', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1034', '参数导出', '106', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:config:export', '#', 'admin', sysdate(), '', null, ''); +-- 通知公告按钮 +insert into sys_menu values('1035', '公告查询', '107', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1036', '公告新增', '107', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1037', '公告修改', '107', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1038', '公告删除', '107', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'system:notice:remove', '#', 'admin', sysdate(), '', null, ''); +-- 操作日志按钮 +insert into sys_menu values('1039', '操作查询', '500', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1040', '操作删除', '500', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1041', '日志导出', '500', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:operlog:export', '#', 'admin', sysdate(), '', null, ''); +-- 登录日志按钮 +insert into sys_menu values('1042', '登录查询', '501', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1043', '登录删除', '501', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1044', '日志导出', '501', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:export', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1045', '账户解锁', '501', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:logininfor:unlock', '#', 'admin', sysdate(), '', null, ''); +-- 在线用户按钮 +insert into sys_menu values('1046', '在线查询', '109', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1047', '批量强退', '109', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:batchLogout', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1048', '单条强退', '109', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:online:forceLogout', '#', 'admin', sysdate(), '', null, ''); +-- 定时任务按钮 +insert into sys_menu values('1049', '任务查询', '110', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1050', '任务新增', '110', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:add', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1051', '任务修改', '110', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1052', '任务删除', '110', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1053', '状态修改', '110', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:changeStatus', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1054', '任务导出', '110', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'monitor:job:export', '#', 'admin', sysdate(), '', null, ''); +-- 代码生成按钮 +insert into sys_menu values('1055', '生成查询', '116', '1', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:query', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1056', '生成修改', '116', '2', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:edit', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1057', '生成删除', '116', '3', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:remove', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1058', '导入代码', '116', '4', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:import', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1059', '预览代码', '116', '5', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:preview', '#', 'admin', sysdate(), '', null, ''); +insert into sys_menu values('1060', '生成代码', '116', '6', '#', '', '', '', 1, 0, 'F', '0', '0', 'tool:gen:code', '#', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 6、用户和角色关联表 用户N-1角色 +-- ---------------------------- +drop table if exists sys_user_role; +create table sys_user_role ( + user_id bigint(20) not null comment '用户ID', + role_id bigint(20) not null comment '角色ID', + primary key(user_id, role_id) +) engine=innodb comment = '用户和角色关联表'; + +-- ---------------------------- +-- 初始化-用户和角色关联表数据 +-- ---------------------------- +insert into sys_user_role values ('1', '1'); +insert into sys_user_role values ('2', '2'); + + +-- ---------------------------- +-- 7、角色和菜单关联表 角色1-N菜单 +-- ---------------------------- +drop table if exists sys_role_menu; +create table sys_role_menu ( + role_id bigint(20) not null comment '角色ID', + menu_id bigint(20) not null comment '菜单ID', + primary key(role_id, menu_id) +) engine=innodb comment = '角色和菜单关联表'; + +-- ---------------------------- +-- 初始化-角色和菜单关联表数据 +-- ---------------------------- +insert into sys_role_menu values ('2', '1'); +insert into sys_role_menu values ('2', '2'); +insert into sys_role_menu values ('2', '3'); +insert into sys_role_menu values ('2', '4'); +insert into sys_role_menu values ('2', '100'); +insert into sys_role_menu values ('2', '101'); +insert into sys_role_menu values ('2', '102'); +insert into sys_role_menu values ('2', '103'); +insert into sys_role_menu values ('2', '104'); +insert into sys_role_menu values ('2', '105'); +insert into sys_role_menu values ('2', '106'); +insert into sys_role_menu values ('2', '107'); +insert into sys_role_menu values ('2', '108'); +insert into sys_role_menu values ('2', '109'); +insert into sys_role_menu values ('2', '110'); +insert into sys_role_menu values ('2', '111'); +insert into sys_role_menu values ('2', '112'); +insert into sys_role_menu values ('2', '113'); +insert into sys_role_menu values ('2', '114'); +insert into sys_role_menu values ('2', '115'); +insert into sys_role_menu values ('2', '116'); +insert into sys_role_menu values ('2', '117'); +insert into sys_role_menu values ('2', '500'); +insert into sys_role_menu values ('2', '501'); +insert into sys_role_menu values ('2', '1000'); +insert into sys_role_menu values ('2', '1001'); +insert into sys_role_menu values ('2', '1002'); +insert into sys_role_menu values ('2', '1003'); +insert into sys_role_menu values ('2', '1004'); +insert into sys_role_menu values ('2', '1005'); +insert into sys_role_menu values ('2', '1006'); +insert into sys_role_menu values ('2', '1007'); +insert into sys_role_menu values ('2', '1008'); +insert into sys_role_menu values ('2', '1009'); +insert into sys_role_menu values ('2', '1010'); +insert into sys_role_menu values ('2', '1011'); +insert into sys_role_menu values ('2', '1012'); +insert into sys_role_menu values ('2', '1013'); +insert into sys_role_menu values ('2', '1014'); +insert into sys_role_menu values ('2', '1015'); +insert into sys_role_menu values ('2', '1016'); +insert into sys_role_menu values ('2', '1017'); +insert into sys_role_menu values ('2', '1018'); +insert into sys_role_menu values ('2', '1019'); +insert into sys_role_menu values ('2', '1020'); +insert into sys_role_menu values ('2', '1021'); +insert into sys_role_menu values ('2', '1022'); +insert into sys_role_menu values ('2', '1023'); +insert into sys_role_menu values ('2', '1024'); +insert into sys_role_menu values ('2', '1025'); +insert into sys_role_menu values ('2', '1026'); +insert into sys_role_menu values ('2', '1027'); +insert into sys_role_menu values ('2', '1028'); +insert into sys_role_menu values ('2', '1029'); +insert into sys_role_menu values ('2', '1030'); +insert into sys_role_menu values ('2', '1031'); +insert into sys_role_menu values ('2', '1032'); +insert into sys_role_menu values ('2', '1033'); +insert into sys_role_menu values ('2', '1034'); +insert into sys_role_menu values ('2', '1035'); +insert into sys_role_menu values ('2', '1036'); +insert into sys_role_menu values ('2', '1037'); +insert into sys_role_menu values ('2', '1038'); +insert into sys_role_menu values ('2', '1039'); +insert into sys_role_menu values ('2', '1040'); +insert into sys_role_menu values ('2', '1041'); +insert into sys_role_menu values ('2', '1042'); +insert into sys_role_menu values ('2', '1043'); +insert into sys_role_menu values ('2', '1044'); +insert into sys_role_menu values ('2', '1045'); +insert into sys_role_menu values ('2', '1046'); +insert into sys_role_menu values ('2', '1047'); +insert into sys_role_menu values ('2', '1048'); +insert into sys_role_menu values ('2', '1049'); +insert into sys_role_menu values ('2', '1050'); +insert into sys_role_menu values ('2', '1051'); +insert into sys_role_menu values ('2', '1052'); +insert into sys_role_menu values ('2', '1053'); +insert into sys_role_menu values ('2', '1054'); +insert into sys_role_menu values ('2', '1055'); +insert into sys_role_menu values ('2', '1056'); +insert into sys_role_menu values ('2', '1057'); +insert into sys_role_menu values ('2', '1058'); +insert into sys_role_menu values ('2', '1059'); +insert into sys_role_menu values ('2', '1060'); + +-- ---------------------------- +-- 8、角色和部门关联表 角色1-N部门 +-- ---------------------------- +drop table if exists sys_role_dept; +create table sys_role_dept ( + role_id bigint(20) not null comment '角色ID', + dept_id bigint(20) not null comment '部门ID', + primary key(role_id, dept_id) +) engine=innodb comment = '角色和部门关联表'; + +-- ---------------------------- +-- 初始化-角色和部门关联表数据 +-- ---------------------------- +insert into sys_role_dept values ('2', '100'); +insert into sys_role_dept values ('2', '101'); +insert into sys_role_dept values ('2', '105'); + + +-- ---------------------------- +-- 9、用户与岗位关联表 用户1-N岗位 +-- ---------------------------- +drop table if exists sys_user_post; +create table sys_user_post +( + user_id bigint(20) not null comment '用户ID', + post_id bigint(20) not null comment '岗位ID', + primary key (user_id, post_id) +) engine=innodb comment = '用户与岗位关联表'; + +-- ---------------------------- +-- 初始化-用户与岗位关联表数据 +-- ---------------------------- +insert into sys_user_post values ('1', '1'); +insert into sys_user_post values ('2', '2'); + + +-- ---------------------------- +-- 10、操作日志记录 +-- ---------------------------- +drop table if exists sys_oper_log; +create table sys_oper_log ( + oper_id bigint(20) not null auto_increment comment '日志主键', + title varchar(50) default '' comment '模块标题', + business_type int(2) default 0 comment '业务类型(0其它 1新增 2修改 3删除)', + method varchar(200) default '' comment '方法名称', + request_method varchar(10) default '' comment '请求方式', + operator_type int(1) default 0 comment '操作类别(0其它 1后台用户 2手机端用户)', + oper_name varchar(50) default '' comment '操作人员', + dept_name varchar(50) default '' comment '部门名称', + oper_url varchar(255) default '' comment '请求URL', + oper_ip varchar(128) default '' comment '主机地址', + oper_location varchar(255) default '' comment '操作地点', + oper_param varchar(2000) default '' comment '请求参数', + json_result varchar(2000) default '' comment '返回参数', + status int(1) default 0 comment '操作状态(0正常 1异常)', + error_msg varchar(2000) default '' comment '错误消息', + oper_time datetime comment '操作时间', + cost_time bigint(20) default 0 comment '消耗时间', + primary key (oper_id), + key idx_sys_oper_log_bt (business_type), + key idx_sys_oper_log_s (status), + key idx_sys_oper_log_ot (oper_time) +) engine=innodb auto_increment=100 comment = '操作日志记录'; + + +-- ---------------------------- +-- 11、字典类型表 +-- ---------------------------- +drop table if exists sys_dict_type; +create table sys_dict_type +( + dict_id bigint(20) not null auto_increment comment '字典主键', + dict_name varchar(100) default '' comment '字典名称', + dict_type varchar(100) default '' comment '字典类型', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_id), + unique (dict_type) +) engine=innodb auto_increment=100 comment = '字典类型表'; + +insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', sysdate(), '', null, '用户性别列表'); +insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', sysdate(), '', null, '菜单状态列表'); +insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', sysdate(), '', null, '系统开关列表'); +insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', sysdate(), '', null, '任务状态列表'); +insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', sysdate(), '', null, '任务分组列表'); +insert into sys_dict_type values(6, '系统是否', 'sys_yes_no', '0', 'admin', sysdate(), '', null, '系统是否列表'); +insert into sys_dict_type values(7, '通知类型', 'sys_notice_type', '0', 'admin', sysdate(), '', null, '通知类型列表'); +insert into sys_dict_type values(8, '通知状态', 'sys_notice_status', '0', 'admin', sysdate(), '', null, '通知状态列表'); +insert into sys_dict_type values(9, '操作类型', 'sys_oper_type', '0', 'admin', sysdate(), '', null, '操作类型列表'); +insert into sys_dict_type values(10, '系统状态', 'sys_common_status', '0', 'admin', sysdate(), '', null, '登录状态列表'); + + +-- ---------------------------- +-- 12、字典数据表 +-- ---------------------------- +drop table if exists sys_dict_data; +create table sys_dict_data +( + dict_code bigint(20) not null auto_increment comment '字典编码', + dict_sort int(4) default 0 comment '字典排序', + dict_label varchar(100) default '' comment '字典标签', + dict_value varchar(100) default '' comment '字典键值', + dict_type varchar(100) default '' comment '字典类型', + css_class varchar(100) default null comment '样式属性(其他样式扩展)', + list_class varchar(100) default null comment '表格回显样式', + is_default char(1) default 'N' comment '是否默认(Y是 N否)', + status char(1) default '0' comment '状态(0正常 1停用)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (dict_code) +) engine=innodb auto_increment=100 comment = '字典数据表'; + +insert into sys_dict_data values(1, 1, '男', '0', 'sys_user_sex', '', '', 'Y', '0', 'admin', sysdate(), '', null, '性别男'); +insert into sys_dict_data values(2, 2, '女', '1', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别女'); +insert into sys_dict_data values(3, 3, '未知', '2', 'sys_user_sex', '', '', 'N', '0', 'admin', sysdate(), '', null, '性别未知'); +insert into sys_dict_data values(4, 1, '显示', '0', 'sys_show_hide', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '显示菜单'); +insert into sys_dict_data values(5, 2, '隐藏', '1', 'sys_show_hide', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '隐藏菜单'); +insert into sys_dict_data values(6, 1, '正常', '0', 'sys_normal_disable', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(7, 2, '停用', '1', 'sys_normal_disable', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(8, 1, '正常', '0', 'sys_job_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(9, 2, '暂停', '1', 'sys_job_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); +insert into sys_dict_data values(10, 1, '默认', 'DEFAULT', 'sys_job_group', '', '', 'Y', '0', 'admin', sysdate(), '', null, '默认分组'); +insert into sys_dict_data values(11, 2, '系统', 'SYSTEM', 'sys_job_group', '', '', 'N', '0', 'admin', sysdate(), '', null, '系统分组'); +insert into sys_dict_data values(12, 1, '是', 'Y', 'sys_yes_no', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '系统默认是'); +insert into sys_dict_data values(13, 2, '否', 'N', 'sys_yes_no', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '系统默认否'); +insert into sys_dict_data values(14, 1, '通知', '1', 'sys_notice_type', '', 'warning', 'Y', '0', 'admin', sysdate(), '', null, '通知'); +insert into sys_dict_data values(15, 2, '公告', '2', 'sys_notice_type', '', 'success', 'N', '0', 'admin', sysdate(), '', null, '公告'); +insert into sys_dict_data values(16, 1, '正常', '0', 'sys_notice_status', '', 'primary', 'Y', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(17, 2, '关闭', '1', 'sys_notice_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '关闭状态'); +insert into sys_dict_data values(18, 99, '其他', '0', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '其他操作'); +insert into sys_dict_data values(19, 1, '新增', '1', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '新增操作'); +insert into sys_dict_data values(20, 2, '修改', '2', 'sys_oper_type', '', 'info', 'N', '0', 'admin', sysdate(), '', null, '修改操作'); +insert into sys_dict_data values(21, 3, '删除', '3', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '删除操作'); +insert into sys_dict_data values(22, 4, '授权', '4', 'sys_oper_type', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '授权操作'); +insert into sys_dict_data values(23, 5, '导出', '5', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导出操作'); +insert into sys_dict_data values(24, 6, '导入', '6', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '导入操作'); +insert into sys_dict_data values(25, 7, '强退', '7', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '强退操作'); +insert into sys_dict_data values(26, 8, '生成代码', '8', 'sys_oper_type', '', 'warning', 'N', '0', 'admin', sysdate(), '', null, '生成操作'); +insert into sys_dict_data values(27, 9, '清空数据', '9', 'sys_oper_type', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '清空操作'); +insert into sys_dict_data values(28, 1, '成功', '0', 'sys_common_status', '', 'primary', 'N', '0', 'admin', sysdate(), '', null, '正常状态'); +insert into sys_dict_data values(29, 2, '失败', '1', 'sys_common_status', '', 'danger', 'N', '0', 'admin', sysdate(), '', null, '停用状态'); + + +-- ---------------------------- +-- 13、参数配置表 +-- ---------------------------- +drop table if exists sys_config; +create table sys_config ( + config_id int(5) not null auto_increment comment '参数主键', + config_name varchar(100) default '' comment '参数名称', + config_key varchar(100) default '' comment '参数键名', + config_value varchar(500) default '' comment '参数键值', + config_type char(1) default 'N' comment '系统内置(Y是 N否)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (config_id) +) engine=innodb auto_increment=100 comment = '参数配置表'; + +insert into sys_config values(1, '主框架页-默认皮肤样式名称', 'sys.index.skinName', 'skin-blue', 'Y', 'admin', sysdate(), '', null, '蓝色 skin-blue、绿色 skin-green、紫色 skin-purple、红色 skin-red、黄色 skin-yellow' ); +insert into sys_config values(2, '用户管理-账号初始密码', 'sys.user.initPassword', '123456', 'Y', 'admin', sysdate(), '', null, '初始化密码 123456' ); +insert into sys_config values(3, '主框架页-侧边栏主题', 'sys.index.sideTheme', 'theme-dark', 'Y', 'admin', sysdate(), '', null, '深色主题theme-dark,浅色主题theme-light' ); +insert into sys_config values(4, '账号自助-验证码开关', 'sys.account.captchaEnabled', 'true', 'Y', 'admin', sysdate(), '', null, '是否开启验证码功能(true开启,false关闭)'); +insert into sys_config values(5, '账号自助-是否开启用户注册功能', 'sys.account.registerUser', 'false', 'Y', 'admin', sysdate(), '', null, '是否开启注册用户功能(true开启,false关闭)'); +insert into sys_config values(6, '用户登录-黑名单列表', 'sys.login.blackIPList', '', 'Y', 'admin', sysdate(), '', null, '设置登录IP黑名单限制,多个匹配项以;分隔,支持匹配(*通配、网段)'); +insert into sys_config values(7, '用户管理-初始密码修改策略', 'sys.account.initPasswordModify', '1', 'Y', 'admin', sysdate(), '', null, '0:初始密码修改策略关闭,没有任何提示,1:提醒用户,如果未修改初始密码,则在登录时就会提醒修改密码对话框'); +insert into sys_config values(8, '用户管理-账号密码更新周期', 'sys.account.passwordValidateDays', '0', 'Y', 'admin', sysdate(), '', null, '密码更新周期(填写数字,数据初始化值为0不限制,若修改必须为大于0小于365的正整数),如果超过这个周期登录系统时,则在登录时就会提醒修改密码对话框'); +insert into sys_config values(9, '用户管理-密码字符范围', 'sys.account.chrtype', '0', 'Y', 'admin', sysdate(), '', null, '默认任意字符范围,0任意(密码可以输入任意字符),1数字(密码只能为0-9数字),2英文字母(密码只能为a-z和A-Z字母),3字母和数字(密码必须包含字母,数字),4字母数字和特殊字符(目前支持的特殊字符包括:~!@#$%^&*()-=_+)'); + + +-- ---------------------------- +-- 14、系统访问记录 +-- ---------------------------- +drop table if exists sys_logininfor; +create table sys_logininfor ( + info_id bigint(20) not null auto_increment comment '访问ID', + user_name varchar(50) default '' comment '用户账号', + ipaddr varchar(128) default '' comment '登录IP地址', + login_location varchar(255) default '' comment '登录地点', + browser varchar(50) default '' comment '浏览器类型', + os varchar(50) default '' comment '操作系统', + status char(1) default '0' comment '登录状态(0成功 1失败)', + msg varchar(255) default '' comment '提示消息', + login_time datetime comment '访问时间', + primary key (info_id), + key idx_sys_logininfor_s (status), + key idx_sys_logininfor_lt (login_time) +) engine=innodb auto_increment=100 comment = '系统访问记录'; + + +-- ---------------------------- +-- 15、定时任务调度表 +-- ---------------------------- +drop table if exists sys_job; +create table sys_job ( + job_id bigint(20) not null auto_increment comment '任务ID', + job_name varchar(64) default '' comment '任务名称', + job_group varchar(64) default 'DEFAULT' comment '任务组名', + invoke_target varchar(500) not null comment '调用目标字符串', + cron_expression varchar(255) default '' comment 'cron执行表达式', + misfire_policy varchar(20) default '3' comment '计划执行错误策略(1立即执行 2执行一次 3放弃执行)', + concurrent char(1) default '1' comment '是否并发执行(0允许 1禁止)', + status char(1) default '0' comment '状态(0正常 1暂停)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default '' comment '备注信息', + primary key (job_id, job_name, job_group) +) engine=innodb auto_increment=100 comment = '定时任务调度表'; + +insert into sys_job values(1, '系统默认(无参)', 'DEFAULT', 'ryTask.ryNoParams', '0/10 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(2, '系统默认(有参)', 'DEFAULT', 'ryTask.ryParams(\'ry\')', '0/15 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); +insert into sys_job values(3, '系统默认(多参)', 'DEFAULT', 'ryTask.ryMultipleParams(\'ry\', true, 2000L, 316.50D, 100)', '0/20 * * * * ?', '3', '1', '1', 'admin', sysdate(), '', null, ''); + + +-- ---------------------------- +-- 16、定时任务调度日志表 +-- ---------------------------- +drop table if exists sys_job_log; +create table sys_job_log ( + job_log_id bigint(20) not null auto_increment comment '任务日志ID', + job_name varchar(64) not null comment '任务名称', + job_group varchar(64) not null comment '任务组名', + invoke_target varchar(500) not null comment '调用目标字符串', + job_message varchar(500) comment '日志信息', + status char(1) default '0' comment '执行状态(0正常 1失败)', + exception_info varchar(2000) default '' comment '异常信息', + start_time datetime comment '执行开始时间', + end_time datetime comment '执行结束时间', + create_time datetime comment '创建时间', + primary key (job_log_id) +) engine=innodb comment = '定时任务调度日志表'; + + +-- ---------------------------- +-- 17、通知公告表 +-- ---------------------------- +drop table if exists sys_notice; +create table sys_notice ( + notice_id int(4) not null auto_increment comment '公告ID', + notice_title varchar(50) not null comment '公告标题', + notice_type char(1) not null comment '公告类型(1通知 2公告)', + notice_content longblob default null comment '公告内容', + status char(1) default '0' comment '公告状态(0正常 1关闭)', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(255) default null comment '备注', + primary key (notice_id) +) engine=innodb auto_increment=10 comment = '通知公告表'; + +-- ---------------------------- +-- 初始化-公告信息表数据 +-- ---------------------------- +insert into sys_notice values('1', '温馨提醒:2018-07-01 风影随行新版本发布啦', '2', '新版本内容', '0', 'admin', sysdate(), '', null, '管理员'); +insert into sys_notice values('2', '维护通知:2018-07-01 风影随行系统凌晨维护', '1', '维护内容', '0', 'admin', sysdate(), '', null, '管理员'); +insert into sys_notice values('3', '风影随行开源框架介绍', '1', '

项目介绍

RuoYi开源项目是为企业用户定制的后台脚手架框架,为企业打造的一站式解决方案,降低企业开发成本,提升开发效率。主要包括用户管理、角色管理、部门管理、菜单管理、参数管理、字典管理、岗位管理、定时任务服务监控、登录日志、操作日志、代码生成等功能。其中,还支持多数据源、数据权限、国际化、Redis缓存、Docker部署、滑动验证码、第三方认证登录、分布式事务、分布式文件存储、分库分表处理等技术特点。


官网及演示

风影随行官网地址: http://ruoyi.vip

风影随行文档地址: http://doc.ruoyi.vip

演示地址【不分离版】: http://demo.ruoyi.vip

演示地址【分离版本】: http://vue.ruoyi.vip

演示地址【微服务版】: http://cloud.ruoyi.vip

演示地址【移动端版】: http://h5.ruoyi.vip


', '0', 'admin', sysdate(), '', null, '管理员'); + + +-- ---------------------------- +-- 18、公告已读记录表 +-- ---------------------------- +drop table if exists sys_notice_read; +create table sys_notice_read ( + read_id bigint(20) not null auto_increment comment '已读主键', + notice_id int(4) not null comment '公告id', + user_id bigint(20) not null comment '用户id', + read_time datetime not null comment '阅读时间', + primary key (read_id), + unique key uk_user_notice (user_id, notice_id) comment '同一用户同一公告只记录一次' +) engine=innodb auto_increment=1 comment='公告已读记录表'; + + +-- ---------------------------- +-- 19、代码生成业务表 +-- ---------------------------- +drop table if exists gen_table; +create table gen_table ( + table_id bigint(20) not null auto_increment comment '编号', + table_name varchar(200) default '' comment '表名称', + table_comment varchar(500) default '' comment '表描述', + sub_table_name varchar(64) default null comment '关联子表的表名', + sub_table_fk_name varchar(64) default null comment '子表关联的外键名', + class_name varchar(100) default '' comment '实体类名称', + tpl_category varchar(200) default 'crud' comment '使用的模板(crud单表操作 tree树表操作)', + tpl_web_type varchar(30) default '' comment '前端模板类型(element-ui模版 element-plus模版)', + package_name varchar(100) comment '生成包路径', + module_name varchar(30) comment '生成模块名', + business_name varchar(30) comment '生成业务名', + function_name varchar(50) comment '生成功能名', + function_author varchar(50) comment '生成功能作者', + form_col_num int(1) default 1 comment '表单布局(单列 双列 三列)', + gen_type char(1) default '0' comment '生成代码方式(0zip压缩包 1自定义路径)', + gen_path varchar(200) default '/' comment '生成路径(不填默认项目路径)', + options varchar(1000) comment '其它生成选项', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + remark varchar(500) default null comment '备注', + primary key (table_id) +) engine=innodb auto_increment=1 comment = '代码生成业务表'; + + +-- ---------------------------- +-- 20、代码生成业务表字段 +-- ---------------------------- +drop table if exists gen_table_column; +create table gen_table_column ( + column_id bigint(20) not null auto_increment comment '编号', + table_id bigint(20) comment '归属表编号', + column_name varchar(200) comment '列名称', + column_comment varchar(500) comment '列描述', + column_type varchar(100) comment '列类型', + java_type varchar(500) comment 'JAVA类型', + java_field varchar(200) comment 'JAVA字段名', + is_pk char(1) comment '是否主键(1是)', + is_increment char(1) comment '是否自增(1是)', + is_required char(1) comment '是否必填(1是)', + is_insert char(1) comment '是否为插入字段(1是)', + is_edit char(1) comment '是否编辑字段(1是)', + is_list char(1) comment '是否列表字段(1是)', + is_query char(1) comment '是否查询字段(1是)', + query_type varchar(200) default 'EQ' comment '查询方式(等于、不等于、大于、小于、范围)', + html_type varchar(200) comment '显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)', + dict_type varchar(200) default '' comment '字典类型', + sort int comment '排序', + create_by varchar(64) default '' comment '创建者', + create_time datetime comment '创建时间', + update_by varchar(64) default '' comment '更新者', + update_time datetime comment '更新时间', + primary key (column_id) +) engine=innodb auto_increment=1 comment = '代码生成业务表字段'; \ No newline at end of file