diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f728db3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,130 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + - master + - work + +jobs: + upgrade-readiness-precheck: + runs-on: ubuntu-latest + env: + MAVEN_SETTINGS_FILE: .mvn/settings-mirror.xml + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + cache: maven + + - name: Upgrade readiness precheck + run: ./scripts/upgrade-precheck.sh --phase boot3 --strict --report build/upgrade-precheck-report.txt + + - name: Upload upgrade precheck report + if: always() + uses: actions/upload-artifact@v4 + with: + name: upgrade-precheck-report + path: build/upgrade-precheck-report.txt + + - name: Build upgrade execution checklist + if: always() + run: ./scripts/upgrade-build-checklist.sh build/upgrade-precheck-report.txt build/upgrade-execution-checklist.md config/upgrade-owners.map --fail-on-unmapped + + - name: Validate upgrade execution checklist format + if: always() + run: | + grep -q "Phase 0 - Recommended Migration Batches" build/upgrade-execution-checklist.md + grep -q "owner:" build/upgrade-execution-checklist.md + grep -q "unmapped owners:" build/upgrade-execution-checklist.md + + - name: Upload upgrade execution checklist + if: always() + uses: actions/upload-artifact@v4 + with: + name: upgrade-execution-checklist + path: build/upgrade-execution-checklist.md + + - name: Build boot3 jakarta migration plan + if: always() + run: ./scripts/boot3-migration-plan.sh build/boot3-jakarta-migration-plan.md + + - name: Upload boot3 migration plan + if: always() + uses: actions/upload-artifact@v4 + with: + name: boot3-jakarta-migration-plan + path: build/boot3-jakarta-migration-plan.md + + - name: Build boot3 jakarta task board + if: always() + run: ./scripts/boot3-task-board.sh build/boot3-jakarta-task-board.md config/upgrade-owners.map + + - name: Upload boot3 task board + if: always() + uses: actions/upload-artifact@v4 + with: + name: boot3-jakarta-task-board + path: build/boot3-jakarta-task-board.md + + - name: Publish upgrade precheck summary + if: always() + run: | + echo "## Upgrade readiness precheck" >> "$GITHUB_STEP_SUMMARY" + if [ -f build/upgrade-precheck-report.txt ]; then + while IFS='=' read -r key value; do + echo "- \`${key}\`: ${value}" >> "$GITHUB_STEP_SUMMARY" + done < build/upgrade-precheck-report.txt + else + echo "- report not found" >> "$GITHUB_STEP_SUMMARY" + fi + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "## Upgrade execution checklist artifact" >> "$GITHUB_STEP_SUMMARY" + echo "- artifact: \`upgrade-execution-checklist\`" >> "$GITHUB_STEP_SUMMARY" + echo "- artifact: \`boot3-jakarta-migration-plan\`" >> "$GITHUB_STEP_SUMMARY" + echo "- artifact: \`boot3-jakarta-task-board\`" >> "$GITHUB_STEP_SUMMARY" + + unit-and-validation-tests: + needs: upgrade-readiness-precheck + runs-on: ubuntu-latest + env: + MAVEN_SETTINGS_FILE: .mvn/settings-mirror.xml + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + cache: maven + + - name: Smoke gate + run: ./scripts/test-gate.sh smoke + + full-regression: + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' + env: + MAVEN_SETTINGS_FILE: .mvn/settings-mirror.xml + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup JDK 17 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + cache: maven + + - name: Full test suite + run: ./scripts/test-gate.sh full diff --git a/.mvn/settings-mirror.xml b/.mvn/settings-mirror.xml new file mode 100644 index 0000000..2d6a54a --- /dev/null +++ b/.mvn/settings-mirror.xml @@ -0,0 +1,12 @@ + + + + aliyun-public + aliyun-public + https://maven.aliyun.com/repository/public + * + + + diff --git a/README.md b/README.md index 7be780c..a60c797 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,98 @@ -#SYTRAVEL -
-本系统由于技术水平有限,不能达到完全满意。如果有什么问题或意见都可以提出交流 -
- -5.1.1系统主页模块 23
-5.1.2旅游项目模块 24
-5.1.3旅游团队模块 25
-5.1.4产品分类模块 27
-5.1.5旅游产品模块 28
-5.1.6角色管理模块 29
-5.1.7信息监控模块 30
-5.1.8日志管理模块 30
-5.1.9系统用户模块 31
+# SYTRAVEL + +SYTRAVEL 是一个旅游业务管理系统后端项目,当前升级分支已切换到 **Spring Boot 3.3.5 + Java 17 + JPA**(当前阶段目标为 Boot 3 稳定化与全量回归封板)。 +项目已完成一轮“可维护性 + 安全性”重构:请求 DTO 化、统一返回结构、全局参数校验异常处理、密码 BCrypt 迁移与权限守卫等。 + +--- + +## 主要功能模块 + +- 系统主页模块 +- 旅游项目模块 +- 旅游团队模块 +- 产品分类模块 +- 旅游产品模块 +- 角色管理模块 +- 信息监控模块 +- 日志管理模块 +- 系统用户模块 + +--- + +## 近期重构内容(已落地) + +- Controller 入参由动态 JSON 迁移为强类型 DTO(`@Valid` + JSR-303) +- 统一 API 返回模型:`AjaxResult.success/failed/validationError/internalError` +- 全局异常处理:`GlobalExceptionHandler` +- 密码安全升级:Base64 -> BCrypt,支持登录时旧密码自动升级 +- 操作日志/返回构造统一:`ResultBuilder` + `OperationResultSupport` +- 权限守卫:`PermissionGuard`(管理员关键写操作) +- 补充单元测试与 MockMvc 参数校验测试 + +详细计划见: + +- `docs/refactor-plan.md` +- `docs/platform-upgrade-assessment.md` +- `docs/test-runbook.md` +- `docs/boot3-batch-freeze.md` + +--- + +## 本地运行 + +### 1) 环境要求 + +- JDK 17(当前升级分支基线) +- Maven 3.6+ +- MySQL(按项目配置准备库与账号) + +### 2) 启动 + +```bash +mvn spring-boot:run +``` + +或: + +```bash +./mvnw spring-boot:run +``` + +--- + +## 测试 + +```bash +mvn test +``` + +> 说明:在部分受限网络环境下,可能出现 Maven 访问中央仓库 403(父 POM 无法下载),需要可访问 Maven 仓库的网络或私有镜像仓库。 + +可选:如果你有公司 Maven 镜像,可先生成 settings 并导出环境变量,再执行测试: + +```bash +./scripts/create-maven-settings.sh <你的可访问仓库地址> .mvn/settings-mirror.xml +export MAVEN_SETTINGS_FILE=$PWD/.mvn/settings-mirror.xml +./scripts/test-gate.sh smoke +``` + +仓库已内置 `.mvn/settings-mirror.xml`(阿里云公共镜像);若你的网络可访问该地址可直接使用,不可访问时再替换为公司私有镜像。 + +CI 已配置在 `.github/workflows/ci.yml`,默认执行单元测试、控制器参数校验测试,并在主分支执行全量回归。 + +推荐使用统一测试入口脚本: + +```bash +./scripts/test-gate.sh smoke +``` + +--- + +## 下一步 + +- 平台升级实施:Boot 1.5 -> 2.7 -> 3.x(两跳迁移) +- 运行 `./scripts/boot3-migration-plan.sh` 生成 Jakarta 迁移批次与热点文件清单 +- 运行 `./scripts/boot3-task-board.sh` 生成按 owner 分配的迁移任务板 +- 执行 Task 1 冻结校验:`./scripts/boot3-freeze-guard.sh origin/main HEAD` +- 持续补齐核心业务回归测试与鉴权覆盖 +- 前端静态资源与接口层进一步治理 diff --git a/config/upgrade-owners.map b/config/upgrade-owners.map new file mode 100644 index 0000000..97b6a83 --- /dev/null +++ b/config/upgrade-owners.map @@ -0,0 +1,8 @@ +# glob-pattern=owner +src/main/java/com/sy/travel/service/*=backend-core +src/main/java/com/sy/travel/rest/*=backend-api +src/main/java/com/sy/travel/common/*=backend-platform +src/main/java/com/sy/travel/dto/*=backend-api +src/main/java/com/sy/travel/entity/*=backend-core +src/main/java/com/sy/travel/handler/*=backend-platform +src/test/java/*=qa-automation diff --git a/docs/boot3-batch-freeze.md b/docs/boot3-batch-freeze.md new file mode 100644 index 0000000..092b3a1 --- /dev/null +++ b/docs/boot3-batch-freeze.md @@ -0,0 +1,66 @@ +# Boot 3 Jakarta 迁移批次冻结(Task 1) + +> 更新时间:2026-04-02 +> 目标:冻结 Boot 3 迁移批次范围,统一验收门槛,避免后续范围漂移。 + +## 1) 冻结范围 + +### Batch A(优先) +- 范围:`javax.validation.*`、`javax.annotation.*` +- 典型目录:`src/main/java/com/sy/travel/dto/*`、`src/main/java/com/sy/travel/rest/*`、`src/main/java/com/sy/travel/handler/*` +- 输出:参数校验链路 jakarta 化,控制器校验行为保持一致 + +### Batch B +- 范围:`javax.persistence.*` +- 典型目录:`src/main/java/com/sy/travel/entity/*` +- 输出:实体映射 jakarta 化,JPA 读写行为不变 + +### Batch C +- 范围:`javax.servlet.*`、`javax.websocket.*` +- 典型目录:`src/main/java/com/sy/travel/service/SYWebsocketService.java`、相关协议层类 +- 输出:协议层 jakarta 化,WebSocket 生命周期行为不变 + +## 2) 冻结验收门槛 + +- Batch A 完成后:`smoke` 通过 +- Batch B 完成后:`smoke + validation` 通过 +- Batch C 完成后:`full` 通过 + +> 统一执行入口:`./scripts/test-gate.sh [smoke|validation|full]` + +## 3) 执行依据(自动产物) + +- `build/boot3-jakarta-migration-plan.md`(范围与热点) +- `build/boot3-jakarta-task-board.md`(owner 分配) + +## 4) 变更控制规则 + +1. 本文档冻结后,新增/变更文件只能进入对应批次,不跨批插入。 +2. 若发现跨批强依赖,必须在 PR 说明中给出原因与回滚策略。 +3. 每个批次结束时更新一次“完成状态”与“剩余风险”。 + +## 5) 当前进度状态(滚动) + +- Batch A:已完成(`dto/rest/handler` 的 `javax.validation.*` -> `jakarta.validation.*` import 切换) +- Batch B:已完成(`entity` 的 `javax.persistence.*` -> `jakarta.persistence.*` import 切换) +- Batch C:已完成(`websocket` 的 `javax.websocket.*` -> `jakarta.websocket.*` import 切换) +- Task 5:阻塞中(Boot Parent 已切换到 3.x;本次执行 `./scripts/test-gate.sh full` 因 Maven 镜像网络不可达失败,需人工在可联网环境封板) + - 备注:后续 **非手动** 流程可忽略该任务,待人工网络窗口再执行。 + +## 6) Task 1 执行指令(新增) + +为避免“批次范围冻结”只停留在文档,仓库新增了自动检查脚本: + +```bash +# 检查某个提交范围是否只改动当前批次允许目录 +./scripts/boot3-freeze-guard.sh A origin/main HEAD +./scripts/boot3-freeze-guard.sh B origin/main HEAD +./scripts/boot3-freeze-guard.sh C origin/main HEAD +``` + +说明: +- A 仅允许改动 `dto/rest/handler` +- B 仅允许改动 `entity` +- C 仅允许改动 `SYWebsocketService.java` +- 通过后再执行对应验收门槛(`test-gate.sh`) + diff --git a/docs/platform-upgrade-assessment.md b/docs/platform-upgrade-assessment.md new file mode 100644 index 0000000..bdcfd75 --- /dev/null +++ b/docs/platform-upgrade-assessment.md @@ -0,0 +1,79 @@ +# 平台升级评估(Spring Boot / JDK) + +> 更新时间:2026-04-02 +> 目标:完成“平台升级”阶段的技术评估,给出可执行迁移路径与风险清单。 + +## 1. 当前基线 + +- 主线:Spring Boot `1.5.9.RELEASE` + JDK `8` +- 升级分支(进行中):Spring Boot `3.3.5` + JDK `17` +- Spring Data JPA + 传统 Servlet API + WebSocket +- 前后端耦合较深,接口历史兼容要求较高 + +## 2. 升级目标 + +- 当前目标:Spring Boot `3.3.x` + JDK `17` 稳定化与回归封板 +- 下一目标:持续清理兼容性边角并准备后续版本升级 + +## 3. 推荐路线(两跳迁移) + +1. **第一跳(推荐先做)** + - JDK 8 -> JDK 17 + - Spring Boot 1.5.x -> 2.7.x + - 处理 Spring Security/Spring MVC/Spring Data 的配置与 API 兼容问题 +2. **第二跳(稳定后)** + - Spring Boot 2.7.x -> 3.x + - `javax.*` -> `jakarta.*` 全量改造 + - 校验并回归 WebSocket、文件上传、JPA 相关能力 + +## 4. 主要风险点 + +- `javax` 到 `jakarta` 命名空间迁移导致编译错误(Boot 3 阶段) +- 旧版第三方依赖与 JDK17 兼容性问题 +- 历史接口返回结构/异常处理兼容风险 +- 前端脚本对错误码和字段名的隐式依赖 + +## 5. 验收建议 + +- 先锁定接口回归集(登录、用户、项目、团队、产品、角色、分类) +- 升级分支强制执行单测 + MockMvc 校验 +- 每一跳都做灰度发布与回滚预案 + +## 6. 结论 + +- “平台升级”阶段**评估任务已完成**,建议按“两跳迁移”执行。 +- 下一里程碑建议从 **Boot 1.5 -> 2.7 + JDK17** 开始实施。 + +## 7. 当前就绪度(2026-04-02) + +### 已完成的前置清障 + +- 接口入参 DTO 化和主要校验链路已落地(降低升级过程中的输入不确定性) +- 全局异常响应已经统一到结构化 `AjaxResult`(400/500 行为更稳定,便于升级后回归对比) +- 错误信息回传已做脱敏处理(避免在升级调试阶段暴露内部异常细节) + +### 下一步(升级执行前) + +1. 在当前升级分支上完成 Boot 3 + JDK17 的编译、测试与配置收敛 +2. 以登录/用户/项目/团队/产品/角色/分类回归集作为强制验收门槛 +3. 完成 full 回归并产出封板报告(风险、回滚方案、上线窗口) + +## 8. Boot 3 迁移执行化(新增) + +- 已新增脚本:`scripts/boot3-migration-plan.sh` +- 用途:基于仓库中 `javax.*` import 自动生成 Boot 3 / Jakarta 迁移计划与热点文件清单 +- 已新增脚本:`scripts/boot3-task-board.sh` +- 用途:按迁移批次把 `javax.*` 文件自动映射到 owner(来自 `config/upgrade-owners.map`),生成可执行任务板 +- 本地执行示例: + +```bash +./scripts/boot3-migration-plan.sh build/boot3-jakarta-migration-plan.md +./scripts/boot3-task-board.sh build/boot3-jakarta-task-board.md config/upgrade-owners.map +``` + +- CI 中已上传 artifact:`boot3-jakarta-migration-plan`、`boot3-jakarta-task-board`(便于按批次推进下一阶段改造) + +## 9. 迁移批次冻结状态(Task 1) + +- 已完成批次范围冻结:见 `docs/boot3-batch-freeze.md` +- Batch A/B/C 的范围与验收门槛已固化,后续迭代按冻结规则推进 diff --git a/docs/refactor-plan.md b/docs/refactor-plan.md new file mode 100644 index 0000000..ae9e445 --- /dev/null +++ b/docs/refactor-plan.md @@ -0,0 +1,154 @@ +# SYTRAVEL 重构路线图(分阶段) + +> 目标:在不中断业务功能的前提下,把项目从“可运行”升级为“可维护、可测试、可演进”。 + +## 0. 现状快照 + +- 后端主线曾基于 Spring Boot 1.5.9 + Java 8,升级分支已开始迁移到 Spring Boot 2.7.18 + Java 17。 +- Controller/Service 层存在大量重复样板代码(分页、参数校验、日志结果组装)。 +- 密码处理为 Base64 编码(非加密),存在明显安全风险。 +- 前端为传统 jQuery + 多版本脚本混用,静态资源结构分散。 +- 自动化测试非常薄弱(当前仅见基础启动测试)。 + +## 1. 重构原则 + +1. **先建立安全网,再动核心逻辑**:优先补测试与观测能力。 +2. **小步快跑,保持可回滚**:每个阶段可独立上线。 +3. **功能不变优先**:先做结构重组,再做行为变更。 +4. **风险前置**:安全问题(密码、鉴权、输入校验)优先。 + +## 2. 阶段计划 + +## 阶段 A:基线与可观测(1~2 周) + +- 建立“重构基线”: + - 固定本地开发环境(JDK/Maven/MySQL 版本、初始化脚本)。 + - 增加健康检查与关键日志字段。 +- 测试补齐(最少可用): + - 为核心 Service(用户、项目、团队、产品)补充单元测试。 + - 为 REST 接口增加 MockMvc 集成测试(覆盖增删改查主流程)。 +- 输出: + - 测试可执行文档。 + - 第一版覆盖率报告(可先设 20% 目标)。 + +## 阶段 B:后端分层与公共能力抽取(2~3 周) + +- 引入 DTO + 校验: + - 请求对象从 `JSON` 动态取值改为强类型 DTO。 + - 使用参数校验注解替代手写判空。 +- 统一异常与返回: + - 引入全局异常处理(`@ControllerAdvice`)。 + - 统一错误码、错误消息、日志追踪 ID。 +- 抽取可复用模块: + - 分页查询模板。 + - 操作日志记录模板(替代每个 Service 手动拼装结果)。 +- 输出: + - 至少一个模块(建议 user)完成“端到端新结构示范”。 + +## 阶段 C:安全整改(并行推进,1~2 周) + +- 密码体系升级: + - 由 Base64 改为 BCrypt/Argon2。 + - 登录验证改为哈希比对。 + - 增加“渐进迁移策略”(老密码首次登录后自动升级)。 +- 基础安全: + - 补充接口鉴权与角色控制。 + - 统一输入过滤,避免注入/越权。 +- 输出: + - 安全基线清单 + 验收用例。 + +## 阶段 D:数据层与领域模型治理(2 周) + +- Repository 命名与查询规范统一。 +- 领域对象瘦身:实体不承载表现层逻辑(如 `toMap`)。 +- 引入 MapStruct/手写 Mapper(Entity ↔ DTO)。 +- 关键表加索引与唯一约束复核。 + +## 阶段 E:前端治理(2~4 周) + +- 静态资源整理: + - 清理重复/多版本 jQuery 与历史插件。 + - 按模块归档 JS/CSS。 +- 前端接口层统一: + - 封装请求方法、统一错误提示。 + - 去除页面脚本中的重复逻辑。 +- 视情况选择: + - 轻量化继续维护 jQuery 架构;或逐步迁移到 Vue/React(按业务价值评估)。 + +## 阶段 F:平台升级(3~6 周,独立里程碑) + +- Spring Boot 1.5.x 升级到 2.7.x(再评估到 3.x)。 +- JDK 8 升级到 17 LTS。 +- 依赖升级并修复兼容性问题。 +- 完成后再启用更高版本框架特性(如新版安全配置)。 + +## 3. 每一轮迭代的执行模板 + +每个模块都按以下顺序推进: + +1. 画出现状调用链(Rest → Service → Repository)。 +2. 写回归测试,锁定当前行为。 +3. 引入 DTO + 参数校验。 +4. 抽离公共逻辑(日志、分页、异常处理)。 +5. 保持接口兼容并灰度验证。 +6. 合并后监控 1~2 天再进行下一模块。 + +## 4. 第一批优先重构清单(建议按这个顺序) + +1. **用户模块(SYUserRest/SYUserService)**:风险最高(密码、权限、输入)。 +2. **登录模块(SYLoginRest/SYLoginService)**:与安全改造联动。 +3. **项目/团队/产品模块**:模板化改造,提取共性。 +4. **日志模块**:统一审计模型与查询能力。 + +## 5. 验收标准(DoD) + +- 功能回归:核心接口回归通过率 100%。 +- 测试覆盖:核心业务包覆盖率达到阶段目标(20%→40%→60%)。 +- 安全:不再存在明文/可逆密码存储。 +- 可维护性:单个 Service 类平均行数明显下降,重复代码减少。 +- 文档:模块设计说明与接口说明同步更新。 + +## 6. 我们下一步怎么做 + +建议从 **阶段 A + 用户模块** 开始,第一迭代只做三件事: + +1. 为用户模块补单测与接口测试。 +2. 把 `JSON` 入参改为 `UserCreateRequest/UserUpdateRequest`。 +3. 落地全局异常处理和统一错误响应。 + +完成后你确认效果,我们再进入第二迭代(密码与鉴权改造)。 + +## 7. 当前重构进度(滚动更新) + +> 更新时间:2026-04-02 + +### 已完成 + +- [x] 用户模块 REST 入参 DTO 化(新增 `UserCreateRequest` / `UserUpdateRequest` / `UserAdminPwdUpdateRequest`) +- [x] 用户模块 `@Valid` 参数校验接入 +- [x] 全局参数校验异常处理(`GlobalExceptionHandler`) +- [x] `AjaxResult` 工厂方法落地(`success` / `failed` / `validationError` / `internalError`) +- [x] 用户模块服务层移除动态 JSON 入参依赖(改为 DTO) +- [x] 多个 Service/Rest 返回构造统一迁移到 `AjaxResult` 工厂方法 +- [x] 用户模块基础单元/接口测试补齐(`SYUserServiceTest` / `SYUserRestValidationTest`) +- [x] 参数校验一致性增强:删除接口 `operator` 非空约束覆盖用户/项目/团队/产品/角色/分类模块 +- [x] 全局异常处理增强:`ConstraintViolationException` / `MissingServletRequestParameterException` / `MethodArgumentTypeMismatchException` 统一返回结构化 400 +- [x] 500 错误信息脱敏:异常详情不再回传客户端,统一返回通用内部错误消息(避免泄露内部实现细节) + +### 进行中 + +- [x] 其余模块(项目/团队/产品/分类/角色)的 DTO 化与参数校验改造 +- [x] 项目模块已落地 DTO 化入口:`/sy/project/add` + `/sy/project/update`(`ProjectCreateRequest` / `ProjectUpdateRequest` + `@Valid`) +- [x] 团队模块已落地 DTO 化入口:`/sy/team/add` + `/sy/team/update`(`TeamCreateRequest` / `TeamUpdateRequest` + `@Valid`) +- [x] 产品模块已落地 DTO 化入口:`/sy/product/add` + `/sy/product/update`(`ProductCreateRequest` / `ProductUpdateRequest` + `@Valid`) +- [x] 分类模块已落地 DTO 化入口:`/sy/classes/add` + `/sy/classes/update`(`ClassesCreateRequest` / `ClassesUpdateRequest` + `@Valid`) +- [x] 角色模块已落地 DTO 化入口:`/sy/role/add` + `/sy/role/update`(`RoleCreateRequest` / `RoleUpdateRequest` + `@Valid`) +- [x] Service 层重复模板(日志记录+结果返回)抽取公共组件 + +### 待开始 + +- [x] 密码存储从 Base64 迁移到 BCrypt(登录成功自动升级旧密码) +- [x] 鉴权与角色控制体系梳理(已收敛为用户/项目/团队/产品/角色/分类关键写操作管理员权限校验) +- [x] 平台升级评估完成(见 `docs/platform-upgrade-assessment.md`,建议两跳迁移:1.5->2.7->3.x) +- [x] Spring Boot 2.7 迁移分支起步(parent 升级到 2.7.18,Java 基线升级到 11) +- [x] 升级执行前置清单落地(依赖扫描、JDK17 兼容性预检;2026-04-02 已执行 `upgrade-precheck.sh --phase boot3 --strict` 并生成 `build/upgrade-precheck-report.txt` / `build/upgrade-execution-checklist.md`) diff --git a/docs/test-runbook.md b/docs/test-runbook.md new file mode 100644 index 0000000..3ecc2f0 --- /dev/null +++ b/docs/test-runbook.md @@ -0,0 +1,184 @@ +# 测试执行手册(Test Runbook) + +> 适用仓库:`SYTRAVEL` +> 更新时间:2026-04-02 + +## 1. 目标 + +- 统一本项目的测试执行方式 +- 明确最小可回归测试集 +- 记录受限网络场景(Maven 403)下的处理建议 + +## 2. 测试分层 + +### 2.1 单元测试(Service/Common) + +建议优先执行: + +- `PasswordSupportTest` +- `SYLoginServiceTest` +- `SYUserServiceTest` +- `PermissionGuardTest` +- `SYWebsocketServiceTest` +- `GlobalExceptionHandlerTest` + +命令示例: + +```bash +mvn -q -Dtest=PasswordSupportTest,SYLoginServiceTest,SYUserServiceTest,PermissionGuardTest,SYWebsocketServiceTest,GlobalExceptionHandlerTest test +``` + +统一脚本: + +```bash +./scripts/test-gate.sh unit +``` + +### 2.2 控制器参数校验测试(MockMvc) + +建议执行: + +- `SYUserRestValidationTest` +- `SYLoginRestValidationTest` +- `SYProjectRestValidationTest` +- `SYTeamRestValidationTest` +- `SYProductRestValidationTest` +- `SYRoleRestValidationTest` +- `SYClassesRestValidationTest` +- `SYLoggerRestValidationTest` + +命令示例: + +```bash +mvn -q -Dtest=SYLoginRestValidationTest,SYUserRestValidationTest,SYProjectRestValidationTest,SYTeamRestValidationTest,SYProductRestValidationTest,SYRoleRestValidationTest,SYClassesRestValidationTest,SYLoggerRestValidationTest test +``` + +统一脚本: + +```bash +./scripts/test-gate.sh validation +``` + +## 3. 全量回归 + +```bash +mvn test +``` + +统一脚本: + +```bash +./scripts/test-gate.sh full +``` + +## 4. 常见问题 + +### 4.1 Maven 仓库 403(父 POM 无法下载) + +现象: + +- `spring-boot-starter-parent:1.5.9.RELEASE` 下载失败 +- `spring-boot-starter-parent:2.7.18` 下载失败 +- `spring-boot-starter-parent:3.3.5` 下载失败 +- Maven 输出 `status code: 403` + +建议: + +1. 切换到可访问 Maven Central 的网络环境执行测试 +2. 配置公司私有 Maven 镜像后再执行 +3. 在 CI 中预热依赖缓存,避免临时网络波动影响 + +补充:`scripts/test-gate.sh` 在检测到 `status code: 403` 时会输出明确的环境提示,便于快速定位为仓库访问问题而非业务代码失败。 + +### 4.2 403 场景的可落地解决方案(建议优先做) + +如果你希望“后续能真实跑起来验证”,请先完成以下一次性配置: + +```bash +./scripts/create-maven-settings.sh <你的可访问仓库地址> .mvn/settings-mirror.xml +export MAVEN_SETTINGS_FILE=$PWD/.mvn/settings-mirror.xml +``` + +然后用同一套 settings 执行: + +```bash +./scripts/upgrade-precheck.sh --phase boot3 --strict --report build/upgrade-precheck-report.txt +./scripts/test-gate.sh smoke +``` + +说明: + +- `MAVEN_SETTINGS_FILE` 已被 `upgrade-precheck.sh` 与 `test-gate.sh` 支持。 +- 仓库默认提供 `.mvn/settings-mirror.xml`,镜像地址为 `https://maven.aliyun.com/repository/public`。 +- `<你的可访问仓库地址>` 更建议填写公司 Nexus/Artifactory 的 Maven 代理地址(最稳妥)。 +- 若你已经有 `~/.m2/settings.xml`,也可直接 `export MAVEN_SETTINGS_FILE=~/.m2/settings.xml`。 + +## 5. CI 建议 + +- PR 最低门禁: + - 升级前预检(6) + - 单元测试(2.1) + - 控制器校验测试(2.2) +- 主分支门禁: + - `mvn test` 全量 + +仓库已提供参考实现:`.github/workflows/ci.yml`。 + +## 6. 升级前预检(平台升级第一跳准备) + +在当前执行阶段(Boot 3 + JDK17)先跑一次预检脚本,快速确认运行时基线和兼容性指标: + +```bash +./scripts/upgrade-precheck.sh --phase boot3 +``` + +CI 严格模式(用于门禁失败): + +```bash +./scripts/upgrade-precheck.sh --phase boot3 --strict +``` + +输出报告文件(便于 CI 归档): + +```bash +./scripts/upgrade-precheck.sh --phase boot3 --strict --report build/upgrade-precheck-report.txt +``` + +该脚本会输出: + +- 本地 Java / Maven 版本 +- `pom.xml` 中 `java.version` 与 Spring Boot Parent 版本 +- `javax.*` import 的出现次数(用于评估后续 Boot 3/Jakarta 改造工作量) +- `javax.*` import 热点文件 Top N(用于升级分工和优先级排序) +- `javax` 分类计数(servlet/validation/persistence/websocket/annotation/other),用于下一阶段迁移分批策略 + +在 CI 中,预检结果会同时: + +- 作为 artifact `upgrade-precheck-report` 上传 +- 同步写入 GitHub Actions Job Summary,便于在 PR 页面快速查看 +- 基于报告自动生成 `upgrade-execution-checklist` artifact(用于升级分工执行) + - 清单包含热点风险等级和 owner 信息(默认来自 `config/upgrade-owners.map`) + - CI 使用 `--fail-on-unmapped`,若热点未命中 owner 映射将直接失败 + - 清单会基于 `javax` 分类计数自动给出迁移批次建议(Batch A/B/C/D) + - CI 会校验清单格式关键段(Phase 0 / owner / unmapped owners)避免生成异常内容 + +## 7. 最近一次执行结果(2026-04-02) + +已完成: + +- `./scripts/upgrade-precheck.sh --phase boot3 --strict --report build/upgrade-precheck-report.txt`(通过) +- `./scripts/upgrade-build-checklist.sh build/upgrade-precheck-report.txt build/upgrade-execution-checklist.md config/upgrade-owners.map`(通过) + +受环境限制未完成: + +- `./scripts/test-gate.sh smoke` +- `./scripts/test-gate.sh validation` +- `./scripts/test-gate.sh full` + +失败原因:当前环境无法访问 `https://maven.aliyun.com/repository/public`,导致 Spring Boot Parent POM 解析失败。 + +自动化处理建议: + +- 若为 **非手动** 执行链路且检测到同类网络错误(Unknown host / Network is unreachable),可将该任务标记为 `ignored_due_to_network` 并继续后续非依赖该测试结果的步骤。 +- 人工执行窗口恢复网络后,必须补跑 `full` 并回填结果。 + diff --git a/pom.xml b/pom.xml index 00a9dde..3be2c9c 100644 --- a/pom.xml +++ b/pom.xml @@ -14,14 +14,14 @@ org.springframework.boot spring-boot-starter-parent - 1.5.9.RELEASE + 3.3.5 UTF-8 UTF-8 - 1.8 + 17 @@ -55,9 +55,28 @@ mysql - mysql-connector-java + mysql-connector-j runtime + + org.springframework.boot + spring-boot-starter-validation + + + jakarta.validation + jakarta.validation-api + 3.0.2 + + + jakarta.persistence + jakarta.persistence-api + 3.1.0 + + + jakarta.websocket + jakarta.websocket-api + 2.1.1 + org.springframework.boot spring-boot-starter-test @@ -67,6 +86,10 @@ org.springframework.boot spring-boot-autoconfigure + + org.springframework.security + spring-security-crypto + commons-fileupload commons-fileupload @@ -98,6 +121,13 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + + org.springframework.boot spring-boot-maven-plugin diff --git a/scripts/boot3-freeze-guard.sh b/scripts/boot3-freeze-guard.sh new file mode 100755 index 0000000..f0f69af --- /dev/null +++ b/scripts/boot3-freeze-guard.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +BATCH="${1:-}" +BASE_REF="${2:-HEAD~1}" +TARGET_REF="${3:-HEAD}" + +if [[ -z "$BATCH" ]]; then + echo "Usage: $0 [base_ref] [target_ref]" >&2 + echo "Example: $0 A origin/main HEAD" >&2 + exit 2 +fi + +case "$BATCH" in + A) + PATTERN='^src/main/java/com/sy/travel/(dto|rest|handler)/' + LABEL='Batch A (dto/rest/handler)' + GATE='smoke' + ;; + B) + PATTERN='^src/main/java/com/sy/travel/entity/' + LABEL='Batch B (entity)' + GATE='smoke + validation' + ;; + C) + PATTERN='^src/main/java/com/sy/travel/service/SYWebsocketService\.java$' + LABEL='Batch C (websocket protocol layer)' + GATE='full' + ;; + *) + echo "Invalid batch: $BATCH (expected A|B|C)" >&2 + exit 2 + ;; +esac + +changed_files="$(git diff --name-only "$BASE_REF" "$TARGET_REF" -- src/main/java || true)" + +if [[ -z "$changed_files" ]]; then + echo "[freeze-guard] No Java source changes detected in range $BASE_REF..$TARGET_REF" + echo "[freeze-guard] Batch: $LABEL" + echo "[freeze-guard] Suggested acceptance gate: ./scripts/test-gate.sh $GATE" + exit 0 +fi + +violations="$(echo "$changed_files" | rg -v "$PATTERN" || true)" + +if [[ -n "$violations" ]]; then + echo "[freeze-guard] FAIL: Found cross-batch changes for $LABEL in range $BASE_REF..$TARGET_REF" >&2 + echo "$violations" >&2 + echo "[freeze-guard] Rule: Task 1 batch freeze forbids cross-batch insertion." >&2 + exit 1 +fi + +echo "[freeze-guard] PASS: All Java source changes belong to $LABEL" +echo "[freeze-guard] Files checked:" +echo "$changed_files" | sed 's/^/ - /' +echo "[freeze-guard] Suggested acceptance gate: ./scripts/test-gate.sh $GATE" diff --git a/scripts/boot3-migration-plan.sh b/scripts/boot3-migration-plan.sh new file mode 100755 index 0000000..3ec6e5f --- /dev/null +++ b/scripts/boot3-migration-plan.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +OUT_FILE="${1:-build/boot3-jakarta-migration-plan.md}" +mkdir -p "$(dirname "$OUT_FILE")" + +collect_count() { + local pattern="$1" + (rg -n "$pattern" src/main/java src/test/java 2>/dev/null || true) | wc -l | tr -d ' ' +} + +SERVLET_COUNT="$(collect_count 'import javax\.servlet\.')" +VALIDATION_COUNT="$(collect_count 'import javax\.validation\.')" +PERSISTENCE_COUNT="$(collect_count 'import javax\.persistence\.')" +WEBSOCKET_COUNT="$(collect_count 'import javax\.websocket\.')" +ANNOTATION_COUNT="$(collect_count 'import javax\.annotation\.')" +TOTAL_COUNT="$(collect_count 'import javax\.')" + +top_hotspots="$( + rg -n 'import javax\.' src/main/java src/test/java 2>/dev/null \ + | cut -d: -f1 \ + | sort \ + | uniq -c \ + | sort -nr \ + | head -n 20 || true +)" + +{ + echo "# Boot 3 / Jakarta Migration Plan (Auto-generated)" + echo + echo "- generated_at_utc: $(date -u +"%Y-%m-%dT%H:%M:%SZ")" + echo "- total_javax_imports: ${TOTAL_COUNT}" + echo "- servlet: ${SERVLET_COUNT}" + echo "- validation: ${VALIDATION_COUNT}" + echo "- persistence: ${PERSISTENCE_COUNT}" + echo "- websocket: ${WEBSOCKET_COUNT}" + echo "- annotation: ${ANNOTATION_COUNT}" + echo + echo "## Suggested Migration Batches" + echo + echo "1. **Batch A (Low-risk / High-volume):** \`javax.validation.*\` and \`javax.annotation.*\` -> \`jakarta.*\`" + echo "2. **Batch B (Data layer):** \`javax.persistence.*\` -> \`jakarta.persistence.*\`" + echo "3. **Batch C (Protocol layer):** \`javax.servlet.*\` and \`javax.websocket.*\` -> \`jakarta.*\`" + echo + echo "## Top Hotspots (by javax import count)" + echo + echo "| File | javax imports |" + echo "| --- | ---: |" + if [[ -n "$top_hotspots" ]]; then + while read -r row; do + [[ -z "$row" ]] && continue + count="$(echo "$row" | awk '{print $1}')" + file="$(echo "$row" | awk '{print $2}')" + echo "| \`${file}\` | ${count} |" + done <<< "$top_hotspots" + else + echo "| _none_ | 0 |" + fi + echo + echo "## Next Stage Checklist" + echo + echo "- [ ] Complete Batch A and run smoke gate" + echo "- [ ] Complete Batch B and run smoke + validation" + echo "- [ ] Complete Batch C and run full regression" + echo "- [ ] Remove all \`javax.*\` imports and switch Boot parent to 3.x branch" +} > "$OUT_FILE" + +echo "[boot3-migration-plan] wrote: $OUT_FILE" diff --git a/scripts/boot3-task-board.sh b/scripts/boot3-task-board.sh new file mode 100755 index 0000000..f514d90 --- /dev/null +++ b/scripts/boot3-task-board.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +OUT_FILE="${1:-build/boot3-jakarta-task-board.md}" +OWNERS_MAP_FILE="${2:-config/upgrade-owners.map}" +mkdir -p "$(dirname "$OUT_FILE")" + +owner_for_file() { + local target="$1" + if [[ ! -f "$OWNERS_MAP_FILE" ]]; then + echo "TBD" + return + fi + while IFS= read -r line; do + [[ -z "$line" || "$line" =~ ^# ]] && continue + local pattern="${line%%=*}" + local owner="${line#*=}" + if [[ "$target" == $pattern ]]; then + echo "$owner" + return + fi + done < "$OWNERS_MAP_FILE" + echo "TBD" +} + +emit_batch() { + local title="$1" + local pattern="$2" + local owner_missing=0 + local files + files="$(rg -n "$pattern" src/main/java src/test/java 2>/dev/null | cut -d: -f1 | sort -u || true)" + echo "## ${title}" + echo + echo "| File | Owner |" + echo "| --- | --- |" + if [[ -z "$files" ]]; then + echo "| _none_ | _none_ |" + else + while read -r f; do + [[ -z "$f" ]] && continue + owner="$(owner_for_file "$f")" + if [[ "$owner" == "TBD" ]]; then + owner_missing=$((owner_missing + 1)) + fi + echo "| \`${f}\` | ${owner} |" + done <<< "$files" + fi + echo + echo "- unmapped_owners: ${owner_missing}" + echo +} + +{ + echo "# Boot 3 Jakarta Task Board (Auto-generated)" + echo + echo "- generated_at_utc: $(date -u +"%Y-%m-%dT%H:%M:%SZ")" + echo "- owners_map: ${OWNERS_MAP_FILE}" + echo + emit_batch "Batch A - validation/annotation" 'import javax\.(validation|annotation)\.' + emit_batch "Batch B - persistence" 'import javax\.persistence\.' + emit_batch "Batch C - servlet/websocket" 'import javax\.(servlet|websocket)\.' +} > "$OUT_FILE" + +echo "[boot3-task-board] wrote: $OUT_FILE" diff --git a/scripts/create-maven-settings.sh b/scripts/create-maven-settings.sh new file mode 100755 index 0000000..1d28a17 --- /dev/null +++ b/scripts/create-maven-settings.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 2 ]]; then + echo "Usage: $0 [mirror-id]" + echo "Example: $0 https://your-nexus/repository/maven-public/ .mvn/settings-mirror.xml company-mirror" + exit 2 +fi + +MIRROR_URL="$1" +OUT_FILE="$2" +MIRROR_ID="${3:-custom-mirror}" + +mkdir -p "$(dirname "$OUT_FILE")" +cat > "$OUT_FILE" < + + + ${MIRROR_ID} + ${MIRROR_ID} + ${MIRROR_URL} + * + + + +EOF + +echo "[create-maven-settings] wrote: $OUT_FILE" +echo "[create-maven-settings] export MAVEN_SETTINGS_FILE=$OUT_FILE" diff --git a/scripts/test-gate.sh b/scripts/test-gate.sh new file mode 100755 index 0000000..8ab0ee9 --- /dev/null +++ b/scripts/test-gate.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -euo pipefail + +MODE="${1:-smoke}" +TMP_LOG="$(mktemp)" +trap 'rm -f "${TMP_LOG}"' EXIT +MAVEN_SETTINGS_FILE="${MAVEN_SETTINGS_FILE:-}" +if [[ -z "$MAVEN_SETTINGS_FILE" && -f ".mvn/settings-mirror.xml" ]]; then + MAVEN_SETTINGS_FILE=".mvn/settings-mirror.xml" +fi + +UNIT_TESTS="PasswordSupportTest,SYLoginServiceTest,SYUserServiceTest,PermissionGuardTest,SYWebsocketServiceTest,GlobalExceptionHandlerTest" +VALIDATION_TESTS="SYLoginRestValidationTest,SYUserRestValidationTest,SYProjectRestValidationTest,SYTeamRestValidationTest,SYProductRestValidationTest,SYRoleRestValidationTest,SYClassesRestValidationTest,SYLoggerRestValidationTest" + +MAVEN_CMD=("mvn") +if [[ -n "$MAVEN_SETTINGS_FILE" ]]; then + if [[ ! -f "$MAVEN_SETTINGS_FILE" ]]; then + echo "[test-gate] ERROR: MAVEN_SETTINGS_FILE does not exist: $MAVEN_SETTINGS_FILE" >&2 + exit 2 + fi + MAVEN_CMD+=("-s" "$MAVEN_SETTINGS_FILE") + echo "[test-gate] using maven settings: $MAVEN_SETTINGS_FILE" +fi + +run_mvn() { + local cmd="$*" + set +e + bash -lc "${cmd}" 2>&1 | tee "${TMP_LOG}" + local code=${PIPESTATUS[0]} + set -e + if [[ ${code} -ne 0 ]]; then + if grep -q "status code: 403" "${TMP_LOG}"; then + echo "" >&2 + echo "[test-gate] Maven repository access failed (HTTP 403)." >&2 + echo "[test-gate] Please switch to a network/mirror that can access required artifacts." >&2 + fi + return "${code}" + fi +} + +run_unit() { + echo "[test-gate] unit tests: ${UNIT_TESTS}" + run_mvn "${MAVEN_CMD[@]}" -q -Dtest="${UNIT_TESTS}" test +} + +run_validation() { + echo "[test-gate] validation tests: ${VALIDATION_TESTS}" + run_mvn "${MAVEN_CMD[@]}" -q -Dtest="${VALIDATION_TESTS}" test +} + +run_full() { + run_mvn "${MAVEN_CMD[@]}" test +} + +case "${MODE}" in + smoke) + run_unit + run_validation + ;; + unit) + run_unit + ;; + validation) + run_validation + ;; + full) + run_full + ;; + *) + echo "Unknown mode: ${MODE}" >&2 + echo "Usage: $0 [smoke|unit|validation|full]" >&2 + exit 2 + ;; +esac diff --git a/scripts/upgrade-build-checklist.sh b/scripts/upgrade-build-checklist.sh new file mode 100755 index 0000000..7e4cc48 --- /dev/null +++ b/scripts/upgrade-build-checklist.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 2 ]]; then + echo "Usage: $0 [owners-map-file] [--fail-on-unmapped]" + exit 2 +fi + +REPORT_FILE="$1" +OUT_FILE="$2" +OWNERS_MAP_FILE="config/upgrade-owners.map" +FAIL_ON_UNMAPPED=0 +for arg in "${@:3}"; do + if [[ "$arg" == "--fail-on-unmapped" ]]; then + FAIL_ON_UNMAPPED=1 + else + OWNERS_MAP_FILE="$arg" + fi +done + +if [[ ! -f "$REPORT_FILE" ]]; then + echo "[upgrade-build-checklist] ERROR: report file not found: $REPORT_FILE" + exit 2 +fi + +get_value() { + local key="$1" + (grep -E "^${key}=" "$REPORT_FILE" || true) | head -n 1 | cut -d= -f2- +} + +risk_level_for_hotspot() { + local hotspot="$1" + case "$hotspot" in + src/main/java/com/sy/travel/service/*) echo "HIGH" ;; + src/main/java/com/sy/travel/rest/*) echo "MEDIUM" ;; + src/main/java/com/sy/travel/*) echo "MEDIUM" ;; + src/test/java/*) echo "LOW" ;; + *) echo "MEDIUM" ;; + esac +} + +owner_for_hotspot() { + local hotspot="$1" + if [[ ! -f "$OWNERS_MAP_FILE" ]]; then + echo "TBD" + return + fi + while IFS= read -r line; do + [[ -z "$line" || "$line" =~ ^# ]] && continue + pattern="${line%%=*}" + owner="${line#*=}" + if [[ "$hotspot" == $pattern ]]; then + echo "$owner" + return + fi + done < "$OWNERS_MAP_FILE" + echo "TBD" +} + +STRICT_MODE="$(get_value upgrade_precheck_strict_mode)" +STRICT_FAILED="$(get_value upgrade_precheck_strict_failed)" +TARGET_PHASE="$(get_value target_phase)" +JAVA_MAJOR="$(get_value java_major)" +POM_JAVA_VERSION="$(get_value pom_java_version)" +BOOT_PARENT="$(get_value spring_boot_parent)" +RUNTIME_JAVA_OK="$(get_value runtime_java_ok)" +POM_JAVA_OK="$(get_value pom_java_ok)" +BOOT_PARENT_OK="$(get_value spring_boot_parent_ok)" +JAVAX_COUNT="$(get_value javax_import_occurrences)" +JAVAX_SERVLET_COUNT="$(get_value javax_servlet_count)" +JAVAX_VALIDATION_COUNT="$(get_value javax_validation_count)" +JAVAX_PERSISTENCE_COUNT="$(get_value javax_persistence_count)" +JAVAX_WEBSOCKET_COUNT="$(get_value javax_websocket_count)" +JAVAX_ANNOTATION_COUNT="$(get_value javax_annotation_count)" +JAVAX_OTHER_COUNT="$(get_value javax_other_count)" + +mkdir -p "$(dirname "$OUT_FILE")" +UNMAPPED_COUNT=0 + +{ + echo "# Upgrade Execution Checklist (Auto-generated)" + echo + echo "- strict mode: \`${STRICT_MODE:-unknown}\`" + echo "- strict failed: \`${STRICT_FAILED:-unknown}\`" + echo "- target phase: \`${TARGET_PHASE:-unknown}\`" + echo "- runtime java major: \`${JAVA_MAJOR:-unknown}\`" + echo "- pom java.version: \`${POM_JAVA_VERSION:-unknown}\`" + echo "- spring boot parent: \`${BOOT_PARENT:-unknown}\`" + echo "- phase checks: runtime_java_ok=\`${RUNTIME_JAVA_OK:-unknown}\`, pom_java_ok=\`${POM_JAVA_OK:-unknown}\`, spring_boot_parent_ok=\`${BOOT_PARENT_OK:-unknown}\`" + echo "- javax import occurrences: \`${JAVAX_COUNT:-unknown}\`" + echo "- javax category breakdown: servlet=\`${JAVAX_SERVLET_COUNT:-0}\`, validation=\`${JAVAX_VALIDATION_COUNT:-0}\`, persistence=\`${JAVAX_PERSISTENCE_COUNT:-0}\`, websocket=\`${JAVAX_WEBSOCKET_COUNT:-0}\`, annotation=\`${JAVAX_ANNOTATION_COUNT:-0}\`, other=\`${JAVAX_OTHER_COUNT:-0}\`" + echo + echo "## Phase 0 - Recommended Migration Batches" + if [[ "${JAVAX_VALIDATION_COUNT:-0}" -gt 0 || "${JAVAX_ANNOTATION_COUNT:-0}" -gt 0 ]]; then + echo "- [ ] Batch A (High ROI): validation + annotation migration (\`javax.validation.*\` / \`javax.annotation.*\`) [owner: backend-api]" + fi + if [[ "${JAVAX_PERSISTENCE_COUNT:-0}" -gt 0 ]]; then + echo "- [ ] Batch B (Data Layer): persistence migration (\`javax.persistence.*\`) [owner: backend-core]" + fi + if [[ "${JAVAX_SERVLET_COUNT:-0}" -gt 0 || "${JAVAX_WEBSOCKET_COUNT:-0}" -gt 0 ]]; then + echo "- [ ] Batch C (Protocol Layer): servlet/websocket migration (\`javax.servlet.*\` / \`javax.websocket.*\`) [owner: backend-platform]" + fi + if [[ "${JAVAX_OTHER_COUNT:-0}" -gt 0 ]]; then + echo "- [ ] Batch D (Misc): remaining \`javax.*\` imports [owner: backend-core]" + fi + echo + echo "## Phase 1 - Current Baseline Stabilization (Boot 3 + JDK17)" + echo "- [ ] Ensure CI strict precheck is green on PR branch" + echo "- [ ] Confirm runtime Java and pom java.version are aligned with current target phase" + echo "- [ ] Confirm Spring Boot parent stays on 3.x during current phase" + echo "- [ ] Run smoke + validation gates and capture baseline report" + echo + echo "## Phase 2 - javax Hotspot Refactor Order" + for i in 1 2 3 4 5; do + hotspot="$(get_value "javax_hotspot_${i}")" + if [[ -n "$hotspot" ]]; then + hotspot_path="${hotspot%%:*}" + hotspot_count="${hotspot##*:}" + risk_level="$(risk_level_for_hotspot "$hotspot_path")" + hotspot_owner="$(owner_for_hotspot "$hotspot_path")" + if [[ "$hotspot_owner" == "TBD" ]]; then + UNMAPPED_COUNT=$((UNMAPPED_COUNT + 1)) + fi + echo "- [ ] hotspot ${i}: \`${hotspot_path}\` (imports: ${hotspot_count}, risk: ${risk_level}, owner: ${hotspot_owner})" + fi + done + echo + echo "## Phase 3 - Next-Hop Execution Gates" + echo "- [ ] JDK17 gate: run precheck \`./scripts/upgrade-precheck.sh --phase jdk17 --strict\`" + echo "- [ ] JDK17 gate: run smoke gate \`./scripts/test-gate.sh smoke\`" + echo "- [ ] Boot3 planning gate: estimate javax->jakarta migration batches from hotspot list" + echo "- [ ] Main-branch full gate: \`./scripts/test-gate.sh full\`" + echo + echo "## Meta" + echo "- unmapped owners: \`${UNMAPPED_COUNT}\`" +} > "$OUT_FILE" + +echo "[upgrade-build-checklist] wrote: $OUT_FILE" +if [[ "$FAIL_ON_UNMAPPED" -eq 1 && "$UNMAPPED_COUNT" -gt 0 ]]; then + echo "[upgrade-build-checklist] ERROR: found ${UNMAPPED_COUNT} hotspot(s) without owner mapping" + exit 1 +fi diff --git a/scripts/upgrade-precheck.sh b/scripts/upgrade-precheck.sh new file mode 100755 index 0000000..b10567b --- /dev/null +++ b/scripts/upgrade-precheck.sh @@ -0,0 +1,242 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +STRICT_MODE=0 +REPORT_FILE="" +TARGET_PHASE="boot3" +MAVEN_SETTINGS_FILE="${MAVEN_SETTINGS_FILE:-}" +if [[ -z "$MAVEN_SETTINGS_FILE" && -f ".mvn/settings-mirror.xml" ]]; then + MAVEN_SETTINGS_FILE=".mvn/settings-mirror.xml" +fi +while [[ $# -gt 0 ]]; do + case "$1" in + --phase) + TARGET_PHASE="${2:-}" + if [[ -z "$TARGET_PHASE" ]]; then + echo "[upgrade-precheck] ERROR: --phase requires a value" + exit 2 + fi + shift 2 + ;; + --strict) + STRICT_MODE=1 + shift + ;; + --report) + REPORT_FILE="${2:-}" + if [[ -z "$REPORT_FILE" ]]; then + echo "[upgrade-precheck] ERROR: --report requires a file path" + exit 2 + fi + shift 2 + ;; + *) + echo "[upgrade-precheck] ERROR: unknown argument: $1" + exit 2 + ;; + esac +done + +echo "[upgrade-precheck] project: $ROOT_DIR" +echo "[upgrade-precheck] target phase: $TARGET_PHASE" + +if ! command -v java >/dev/null 2>&1; then + echo "[upgrade-precheck] ERROR: java not found in PATH" + exit 2 +fi + +if ! command -v mvn >/dev/null 2>&1; then + echo "[upgrade-precheck] ERROR: mvn not found in PATH" + exit 2 +fi + +MAVEN_CMD=("mvn") +if [[ -n "$MAVEN_SETTINGS_FILE" ]]; then + if [[ ! -f "$MAVEN_SETTINGS_FILE" ]]; then + echo "[upgrade-precheck] ERROR: MAVEN_SETTINGS_FILE does not exist: $MAVEN_SETTINGS_FILE" + exit 2 + fi + MAVEN_CMD+=("-s" "$MAVEN_SETTINGS_FILE") + echo "[upgrade-precheck] using maven settings: $MAVEN_SETTINGS_FILE" +fi + +JAVA_VERSION_RAW="$(java -version 2>&1 | head -n 1)" +MVN_VERSION_RAW="$("${MAVEN_CMD[@]}" -v 2>/dev/null | head -n 1)" +JAVA_VERSION_TOKEN="$(echo "$JAVA_VERSION_RAW" | awk -F'"' '{print $2}')" +JAVA_MAJOR="$(echo "$JAVA_VERSION_TOKEN" | awk -F. '{if ($1 == "1") print $2; else print $1}')" + +echo "[upgrade-precheck] java: $JAVA_VERSION_RAW" +echo "[upgrade-precheck] maven: $MVN_VERSION_RAW" + +POM_JAVA_VERSION="$(sed -n 's:.*\(.*\).*:\1:p' pom.xml | head -n 1)" +BOOT_PARENT_VERSION="$( + awk ' + // {in_parent=1} + in_parent && /spring-boot-starter-parent<\/artifactId>/ {target=1} + in_parent && target && // { + gsub(/^.*|<\/version>.*$/, "", $0); + print; + exit + } + /<\/parent>/ {in_parent=0} + ' pom.xml +)" + +echo "[upgrade-precheck] pom.java.version: ${POM_JAVA_VERSION:-unknown}" +echo "[upgrade-precheck] spring-boot-parent: ${BOOT_PARENT_VERSION:-unknown}" +EXPECTED_JAVA_MAJOR="" +EXPECTED_POM_JAVA="" +EXPECTED_BOOT_PARENT="" +case "$TARGET_PHASE" in + boot27-jdk11) + EXPECTED_JAVA_MAJOR="11" + EXPECTED_POM_JAVA="11" + EXPECTED_BOOT_PARENT="2.7.18" + ;; + jdk17) + EXPECTED_JAVA_MAJOR="17" + EXPECTED_POM_JAVA="17" + EXPECTED_BOOT_PARENT="2.7.18" + ;; + boot3) + EXPECTED_JAVA_MAJOR="17" + EXPECTED_POM_JAVA="17" + EXPECTED_BOOT_PARENT="3." + ;; + *) + echo "[upgrade-precheck] ERROR: unsupported phase '$TARGET_PHASE' (supported: boot27-jdk11, jdk17, boot3)" + exit 2 + ;; +esac + +match_parent_version() { + local actual="${1:-}" + local expected="${2:-}" + if [[ -z "$actual" || -z "$expected" ]]; then + return 1 + fi + if [[ "$expected" == *"." ]]; then + [[ "$actual" == "$expected"* ]] + else + [[ "$actual" == "$expected" ]] + fi +} + +RUNTIME_OK=1 +POM_JAVA_OK=1 +BOOT_PARENT_OK=1 +if [[ -z "${JAVA_MAJOR:-}" ]]; then + echo "[upgrade-precheck] WARN: runtime Java major is unknown" + RUNTIME_OK=0 +elif [[ "$TARGET_PHASE" == "boot3" ]]; then + if [[ "$JAVA_MAJOR" -lt "$EXPECTED_JAVA_MAJOR" ]]; then + echo "[upgrade-precheck] WARN: expected runtime Java major >= $EXPECTED_JAVA_MAJOR for boot3, got ${JAVA_MAJOR:-unknown}" + RUNTIME_OK=0 + fi +else + if [[ "${JAVA_MAJOR:-}" != "$EXPECTED_JAVA_MAJOR" ]]; then + echo "[upgrade-precheck] WARN: expected runtime Java major $EXPECTED_JAVA_MAJOR, got ${JAVA_MAJOR:-unknown}" + RUNTIME_OK=0 + fi +fi +if [[ "${POM_JAVA_VERSION:-}" != "$EXPECTED_POM_JAVA" ]]; then + echo "[upgrade-precheck] WARN: expected pom java.version=$EXPECTED_POM_JAVA, got ${POM_JAVA_VERSION:-unknown}" + POM_JAVA_OK=0 +fi +if ! match_parent_version "${BOOT_PARENT_VERSION:-}" "$EXPECTED_BOOT_PARENT"; then + echo "[upgrade-precheck] WARN: expected spring-boot-starter-parent=${EXPECTED_BOOT_PARENT}*, got ${BOOT_PARENT_VERSION:-unknown}" + BOOT_PARENT_OK=0 +fi + +STRICT_FAILED=0 +if [[ "$STRICT_MODE" -eq 1 ]]; then + if [[ "$RUNTIME_OK" -eq 0 ]]; then + echo "[upgrade-precheck] STRICT-ERROR: runtime Java does not match target phase requirement" + STRICT_FAILED=1 + fi + if [[ "$POM_JAVA_OK" -eq 0 ]]; then + echo "[upgrade-precheck] STRICT-ERROR: pom java.version does not match target phase requirement" + STRICT_FAILED=1 + fi + if [[ "$BOOT_PARENT_OK" -eq 0 ]]; then + echo "[upgrade-precheck] STRICT-ERROR: spring boot parent does not match target phase requirement" + STRICT_FAILED=1 + fi +fi + +echo "[upgrade-precheck] scanning javax.* imports (for Boot 3 migration impact sizing)..." +JAVA_FILES_WITH_JAVAX="$( (rg -n "import javax\\." src/main/java src/test/java || true) | wc -l | tr -d ' ' )" +echo "[upgrade-precheck] javax import occurrences: $JAVA_FILES_WITH_JAVAX" +JAVAX_HOTSPOTS="$( + (rg -n "import javax\\." src/main/java src/test/java || true) \ + | cut -d: -f1 \ + | sort \ + | uniq -c \ + | sort -nr \ + | head -n 5 \ + | awk '{print $2":"$1}' +)" +JAVAX_CATEGORY_COUNTS="$( + rg -n "import javax\\." src/main/java src/test/java || true +)" +read -r JAVAX_SERVLET_COUNT JAVAX_VALIDATION_COUNT JAVAX_PERSISTENCE_COUNT JAVAX_WEBSOCKET_COUNT JAVAX_ANNOTATION_COUNT JAVAX_OTHER_COUNT < "$REPORT_FILE" + echo "[upgrade-precheck] report written: $REPORT_FILE" +fi + +if [[ "$STRICT_MODE" -eq 1 && "$STRICT_FAILED" -eq 1 ]]; then + echo "[upgrade-precheck] strict check failed" + exit 1 +fi + +echo "[upgrade-precheck] done" diff --git a/src/main/java/com/sy/travel/common/AjaxResult.java b/src/main/java/com/sy/travel/common/AjaxResult.java index f808a04..c3a9bfe 100644 --- a/src/main/java/com/sy/travel/common/AjaxResult.java +++ b/src/main/java/com/sy/travel/common/AjaxResult.java @@ -20,6 +20,22 @@ public AjaxResult(int code, String msg, T data) { this.data = data; } + public static AjaxResult success(T data) { + return new AjaxResult(200, ApiMessages.SUCCESS, data); + } + + public static AjaxResult failed(int code, T data) { + return new AjaxResult(code, ApiMessages.FAILED, data); + } + + public static AjaxResult validationError(String message) { + return new AjaxResult(400, ErrorCodes.VALIDATION_ERROR, message); + } + + public static AjaxResult internalError(String message) { + return new AjaxResult(500, ErrorCodes.INTERNAL_ERROR, message); + } + public int getCode() { return code; } diff --git a/src/main/java/com/sy/travel/common/ApiMessages.java b/src/main/java/com/sy/travel/common/ApiMessages.java new file mode 100644 index 0000000..2b9499d --- /dev/null +++ b/src/main/java/com/sy/travel/common/ApiMessages.java @@ -0,0 +1,7 @@ +package com.sy.travel.common; + +public interface ApiMessages { + String SUCCESS = "success"; + String FAILED = "failed"; + String INTERNAL_ERROR_MESSAGE = "internal server error"; +} diff --git a/src/main/java/com/sy/travel/common/Commons.java b/src/main/java/com/sy/travel/common/Commons.java index 73a6b0f..a2a2729 100644 --- a/src/main/java/com/sy/travel/common/Commons.java +++ b/src/main/java/com/sy/travel/common/Commons.java @@ -104,7 +104,17 @@ public class Commons { public final static String USER_DELETE = "删除用户"; public final static String USER_DELETE_FAILED = "删除用户失败"; public final static String USER_UPDATE = "修改用户"; + @Deprecated public final static String USER_UPDATE_NOT_FOUNT ="该用户不存在"; + public final static String USER_UPDATE_NOT_FOUND ="该用户不存在"; + @Deprecated public final static String USER_UPDTE_PWD = "修改超级管理员密码"; + public final static String USER_UPDATE_PWD = "修改超级管理员密码"; public final static String USER_UPDATE_PWD_NOT_PERMISSION = "只有超级管理员才有修改密码的权限"; + public final static String USER_OPERATOR_NOT_EXISTS = "操作用户不存在"; + public final static String USER_OPERATOR_NOT_PERMISSION = "当前用户没有操作权限"; + public final static String USER_NOT_LOGIN = "用户未登录"; + public final static String USER_ADMIN_OLD_PASSWORD_EMPTY = "超级管理员原密码不能为空"; + public final static String USER_ADMIN_OLD_PASSWORD_ERROR = "超级管理员原密码输入错误"; + public final static String USER_ADMIN_NEW_PASSWORD_EMPTY = "超级管理员新密码不能为空"; } diff --git a/src/main/java/com/sy/travel/common/ErrorCodes.java b/src/main/java/com/sy/travel/common/ErrorCodes.java new file mode 100644 index 0000000..c352404 --- /dev/null +++ b/src/main/java/com/sy/travel/common/ErrorCodes.java @@ -0,0 +1,6 @@ +package com.sy.travel.common; + +public interface ErrorCodes { + String VALIDATION_ERROR = "VALIDATION_ERROR"; + String INTERNAL_ERROR = "INTERNAL_ERROR"; +} diff --git a/src/main/java/com/sy/travel/common/PasswordSupport.java b/src/main/java/com/sy/travel/common/PasswordSupport.java new file mode 100644 index 0000000..76ece67 --- /dev/null +++ b/src/main/java/com/sy/travel/common/PasswordSupport.java @@ -0,0 +1,48 @@ +package com.sy.travel.common; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +public final class PasswordSupport { + + private static final BCryptPasswordEncoder B_CRYPT = new BCryptPasswordEncoder(); + + private PasswordSupport() { + } + + public static String hash(String password) { + return B_CRYPT.encode(password); + } + + public static boolean isBcryptHash(String encodedPassword) { + return encodedPassword != null && encodedPassword.matches("^\\$2[aby]\\$.{56}$"); + } + + public static boolean matches(String username, String rawPassword, String encodedPassword) { + if (isBcryptHash(encodedPassword)) { + return B_CRYPT.matches(rawPassword, encodedPassword); + } + return legacyEncode(username, rawPassword).equals(encodedPassword); + } + + public static String legacyEncode(String username, String password) { + String source = username + ":" + password; + return Base64.getEncoder().encodeToString(source.getBytes(StandardCharsets.UTF_8)); + } + + public static String legacyDecode(String encodedPassword) { + if (encodedPassword == null) { + return ""; + } + try { + byte[] decode = Base64.getDecoder().decode(encodedPassword); + String pass = new String(decode, StandardCharsets.UTF_8); + int separator = pass.indexOf(":"); + return separator >= 0 ? pass.substring(separator + 1) : pass; + } catch (IllegalArgumentException ex) { + return encodedPassword; + } + } +} diff --git a/src/main/java/com/sy/travel/common/ResultBuilder.java b/src/main/java/com/sy/travel/common/ResultBuilder.java new file mode 100644 index 0000000..5d8a943 --- /dev/null +++ b/src/main/java/com/sy/travel/common/ResultBuilder.java @@ -0,0 +1,25 @@ +package com.sy.travel.common; + +import org.apache.commons.lang3.StringUtils; + +import com.sy.travel.entity.Logger; + +import java.util.Date; + +public final class ResultBuilder { + private ResultBuilder() { + } + + public static AjaxResult byStatus(String status, String reason) { + return "1".equals(status) ? AjaxResult.success(reason) : AjaxResult.failed(200, reason); + } + + public static String operationMessage(String operator, String operation, String reason) { + return StringUtils.isBlank(reason) ? operator + operation + ":成功" : operator + operation + "失败原因:" + reason; + } + + public static Logger operationLogger(String operator, Date start, String reason, String status, String operation) { + return new Logger(operator, DateFormat.sdf.format(start), DateFormat.sdf.format(new Date()), + operationMessage(operator, operation, reason), status, operation); + } +} diff --git a/src/main/java/com/sy/travel/dto/classes/ClassesCreateRequest.java b/src/main/java/com/sy/travel/dto/classes/ClassesCreateRequest.java new file mode 100644 index 0000000..85ff568 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/classes/ClassesCreateRequest.java @@ -0,0 +1,25 @@ +package com.sy.travel.dto.classes; + +import jakarta.validation.constraints.NotBlank; + +public class ClassesCreateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotBlank(message = "name不能为空") + private String name; + @NotBlank(message = "sortId不能为空") + private String sortId; + private String parentId; + private String remark; + + public String getOperator() { return operator; } + public void setOperator(String operator) { this.operator = operator; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getSortId() { return sortId; } + public void setSortId(String sortId) { this.sortId = sortId; } + public String getParentId() { return parentId; } + public void setParentId(String parentId) { this.parentId = parentId; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} diff --git a/src/main/java/com/sy/travel/dto/classes/ClassesUpdateRequest.java b/src/main/java/com/sy/travel/dto/classes/ClassesUpdateRequest.java new file mode 100644 index 0000000..83f1b87 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/classes/ClassesUpdateRequest.java @@ -0,0 +1,28 @@ +package com.sy.travel.dto.classes; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public class ClassesUpdateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotNull(message = "id不能为空") + private Integer id; + private String name; + private String sortId; + private String parentId; + private String remark; + + public String getOperator() { return operator; } + public void setOperator(String operator) { this.operator = operator; } + public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getSortId() { return sortId; } + public void setSortId(String sortId) { this.sortId = sortId; } + public String getParentId() { return parentId; } + public void setParentId(String parentId) { this.parentId = parentId; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} diff --git a/src/main/java/com/sy/travel/dto/login/LoginCheckRequest.java b/src/main/java/com/sy/travel/dto/login/LoginCheckRequest.java new file mode 100644 index 0000000..f2157a6 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/login/LoginCheckRequest.java @@ -0,0 +1,15 @@ +package com.sy.travel.dto.login; + +import jakarta.validation.constraints.NotBlank; + +public class LoginCheckRequest { + @NotBlank(message = "username不能为空") + private String username; + @NotBlank(message = "password不能为空") + private String password; + + 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; } +} diff --git a/src/main/java/com/sy/travel/dto/product/ProductCreateRequest.java b/src/main/java/com/sy/travel/dto/product/ProductCreateRequest.java new file mode 100644 index 0000000..e586728 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/product/ProductCreateRequest.java @@ -0,0 +1,66 @@ +package com.sy.travel.dto.product; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public class ProductCreateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotBlank(message = "code不能为空") + private String code; + @NotBlank(message = "name不能为空") + private String name; + @NotBlank(message = "teamId不能为空") + private String teamId; + private String exText; + @NotBlank(message = "onlineDate不能为空") + private String onlineDate; + @NotBlank(message = "offlineDate不能为空") + private String offlineDate; + @NotBlank(message = "classId不能为空") + private String classId; + @NotNull(message = "quantity不能为空") + private Integer quantity; + @NotNull(message = "minQty不能为空") + private Integer minQty; + @NotNull(message = "soldQty不能为空") + private Integer soldQty; + @NotNull(message = "price不能为空") + private Integer price; + @NotNull(message = "nights不能为空") + private Integer nights; + @NotBlank(message = "status不能为空") + private String status; + private String remark; + + public String getOperator() { return operator; } + public void setOperator(String operator) { this.operator = operator; } + public String getCode() { return code; } + public void setCode(String code) { this.code = code; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getTeamId() { return teamId; } + public void setTeamId(String teamId) { this.teamId = teamId; } + public String getExText() { return exText; } + public void setExText(String exText) { this.exText = exText; } + public String getOnlineDate() { return onlineDate; } + public void setOnlineDate(String onlineDate) { this.onlineDate = onlineDate; } + public String getOfflineDate() { return offlineDate; } + public void setOfflineDate(String offlineDate) { this.offlineDate = offlineDate; } + public String getClassId() { return classId; } + public void setClassId(String classId) { this.classId = classId; } + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + public Integer getMinQty() { return minQty; } + public void setMinQty(Integer minQty) { this.minQty = minQty; } + public Integer getSoldQty() { return soldQty; } + public void setSoldQty(Integer soldQty) { this.soldQty = soldQty; } + public Integer getPrice() { return price; } + public void setPrice(Integer price) { this.price = price; } + public Integer getNights() { return nights; } + public void setNights(Integer nights) { this.nights = nights; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} diff --git a/src/main/java/com/sy/travel/dto/product/ProductUpdateRequest.java b/src/main/java/com/sy/travel/dto/product/ProductUpdateRequest.java new file mode 100644 index 0000000..91e9ec3 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/product/ProductUpdateRequest.java @@ -0,0 +1,49 @@ +package com.sy.travel.dto.product; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public class ProductUpdateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotNull(message = "id不能为空") + private Integer id; + private String name; + private String exText; + @NotNull(message = "quantity不能为空") + private Integer quantity; + @NotNull(message = "minQty不能为空") + private Integer minQty; + @NotNull(message = "soldQty不能为空") + private Integer soldQty; + @NotNull(message = "price不能为空") + private Integer price; + @NotNull(message = "nights不能为空") + private Integer nights; + @NotBlank(message = "status不能为空") + private String status; + private String remark; + + public String getOperator() { return operator; } + public void setOperator(String operator) { this.operator = operator; } + public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getExText() { return exText; } + public void setExText(String exText) { this.exText = exText; } + public Integer getQuantity() { return quantity; } + public void setQuantity(Integer quantity) { this.quantity = quantity; } + public Integer getMinQty() { return minQty; } + public void setMinQty(Integer minQty) { this.minQty = minQty; } + public Integer getSoldQty() { return soldQty; } + public void setSoldQty(Integer soldQty) { this.soldQty = soldQty; } + public Integer getPrice() { return price; } + public void setPrice(Integer price) { this.price = price; } + public Integer getNights() { return nights; } + public void setNights(Integer nights) { this.nights = nights; } + public String getStatus() { return status; } + public void setStatus(String status) { this.status = status; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} diff --git a/src/main/java/com/sy/travel/dto/project/ProjectCreateRequest.java b/src/main/java/com/sy/travel/dto/project/ProjectCreateRequest.java new file mode 100644 index 0000000..e67bfe1 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/project/ProjectCreateRequest.java @@ -0,0 +1,75 @@ +package com.sy.travel.dto.project; + +import jakarta.validation.constraints.NotBlank; + +public class ProjectCreateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotBlank(message = "code不能为空") + private String code; + @NotBlank(message = "name不能为空") + private String name; + @NotBlank(message = "beginDate不能为空") + private String beginDate; + @NotBlank(message = "endDate不能为空") + private String endDate; + @NotBlank(message = "valid不能为空") + private String valid; + private String remark; + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBeginDate() { + return beginDate; + } + + public void setBeginDate(String beginDate) { + this.beginDate = beginDate; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } +} diff --git a/src/main/java/com/sy/travel/dto/project/ProjectUpdateRequest.java b/src/main/java/com/sy/travel/dto/project/ProjectUpdateRequest.java new file mode 100644 index 0000000..1a9a312 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/project/ProjectUpdateRequest.java @@ -0,0 +1,81 @@ +package com.sy.travel.dto.project; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public class ProjectUpdateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotNull(message = "id不能为空") + private Integer id; + private String code; + private String name; + private String beginDate; + private String endDate; + private String valid; + private String remark; + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getBeginDate() { + return beginDate; + } + + public void setBeginDate(String beginDate) { + this.beginDate = beginDate; + } + + public String getEndDate() { + return endDate; + } + + public void setEndDate(String endDate) { + this.endDate = endDate; + } + + public String getValid() { + return valid; + } + + public void setValid(String valid) { + this.valid = valid; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } +} diff --git a/src/main/java/com/sy/travel/dto/role/RoleCreateRequest.java b/src/main/java/com/sy/travel/dto/role/RoleCreateRequest.java new file mode 100644 index 0000000..994a6b3 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/role/RoleCreateRequest.java @@ -0,0 +1,37 @@ +package com.sy.travel.dto.role; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +public class RoleCreateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotBlank(message = "name不能为空") + private String name; + @NotBlank(message = "teamId不能为空") + private String teamId; + @NotBlank(message = "role不能为空") + private String role; + @Pattern(regexp = "^$|^([a-z0-9A-Z]+[-|_|\\.]?)+[a-z0-9A-Z]@([a-z0-9A-Z]+(-[a-z0-9A-Z]+)?\\.)+[a-zA-Z]{2,}$", + message = "邮箱格式错误") + private String email; + @NotBlank(message = "mobile不能为空") + @Pattern(regexp = "^1[3|4|5|7|8][0-9]{9}$", message = "手机号格式错误") + private String mobile; + private String remark; + + public String getOperator() { return operator; } + public void setOperator(String operator) { this.operator = operator; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getTeamId() { return teamId; } + public void setTeamId(String teamId) { this.teamId = teamId; } + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getMobile() { return mobile; } + public void setMobile(String mobile) { this.mobile = mobile; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} diff --git a/src/main/java/com/sy/travel/dto/role/RoleUpdateRequest.java b/src/main/java/com/sy/travel/dto/role/RoleUpdateRequest.java new file mode 100644 index 0000000..7051e86 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/role/RoleUpdateRequest.java @@ -0,0 +1,28 @@ +package com.sy.travel.dto.role; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public class RoleUpdateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotNull(message = "id不能为空") + private Integer id; + private String role; + private String email; + private String mobile; + private String remark; + + public String getOperator() { return operator; } + public void setOperator(String operator) { this.operator = operator; } + public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + public String getRole() { return role; } + public void setRole(String role) { this.role = role; } + public String getEmail() { return email; } + public void setEmail(String email) { this.email = email; } + public String getMobile() { return mobile; } + public void setMobile(String mobile) { this.mobile = mobile; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} diff --git a/src/main/java/com/sy/travel/dto/team/TeamCreateRequest.java b/src/main/java/com/sy/travel/dto/team/TeamCreateRequest.java new file mode 100644 index 0000000..4f78f59 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/team/TeamCreateRequest.java @@ -0,0 +1,26 @@ +package com.sy.travel.dto.team; + +import jakarta.validation.constraints.NotBlank; + +public class TeamCreateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotBlank(message = "name不能为空") + private String name; + @NotBlank(message = "projectId不能为空") + private String projectId; + @NotBlank(message = "valid不能为空") + private String valid; + private String remark; + + public String getOperator() { return operator; } + public void setOperator(String operator) { this.operator = operator; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getProjectId() { return projectId; } + public void setProjectId(String projectId) { this.projectId = projectId; } + public String getValid() { return valid; } + public void setValid(String valid) { this.valid = valid; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} diff --git a/src/main/java/com/sy/travel/dto/team/TeamUpdateRequest.java b/src/main/java/com/sy/travel/dto/team/TeamUpdateRequest.java new file mode 100644 index 0000000..2938bd2 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/team/TeamUpdateRequest.java @@ -0,0 +1,28 @@ +package com.sy.travel.dto.team; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public class TeamUpdateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotNull(message = "id不能为空") + private Integer id; + private String name; + private String projectId; + private String valid; + private String remark; + + public String getOperator() { return operator; } + public void setOperator(String operator) { this.operator = operator; } + public Integer getId() { return id; } + public void setId(Integer id) { this.id = id; } + public String getName() { return name; } + public void setName(String name) { this.name = name; } + public String getProjectId() { return projectId; } + public void setProjectId(String projectId) { this.projectId = projectId; } + public String getValid() { return valid; } + public void setValid(String valid) { this.valid = valid; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} diff --git a/src/main/java/com/sy/travel/dto/user/UserAdminPwdUpdateRequest.java b/src/main/java/com/sy/travel/dto/user/UserAdminPwdUpdateRequest.java new file mode 100644 index 0000000..6e57bd2 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/user/UserAdminPwdUpdateRequest.java @@ -0,0 +1,36 @@ +package com.sy.travel.dto.user; + +import jakarta.validation.constraints.NotBlank; + +public class UserAdminPwdUpdateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotBlank(message = "password不能为空") + private String password; + @NotBlank(message = "newPwd不能为空") + private String newPwd; + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getNewPwd() { + return newPwd; + } + + public void setNewPwd(String newPwd) { + this.newPwd = newPwd; + } +} diff --git a/src/main/java/com/sy/travel/dto/user/UserCreateRequest.java b/src/main/java/com/sy/travel/dto/user/UserCreateRequest.java new file mode 100644 index 0000000..1411b96 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/user/UserCreateRequest.java @@ -0,0 +1,57 @@ +package com.sy.travel.dto.user; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; + +public class UserCreateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotBlank(message = "username不能为空") + private String username; + @NotBlank(message = "password不能为空") + private String password; + @NotBlank(message = "permission不能为空") + @Pattern(regexp = "^[0-5]$", message = "permission取值范围为0-5") + private String permission; + private String remark; + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + 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 getPermission() { + return permission; + } + + public void setPermission(String permission) { + this.permission = permission; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } +} diff --git a/src/main/java/com/sy/travel/dto/user/UserUpdateRequest.java b/src/main/java/com/sy/travel/dto/user/UserUpdateRequest.java new file mode 100644 index 0000000..1f6e834 --- /dev/null +++ b/src/main/java/com/sy/travel/dto/user/UserUpdateRequest.java @@ -0,0 +1,56 @@ +package com.sy.travel.dto.user; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + +public class UserUpdateRequest { + @NotBlank(message = "operator不能为空") + private String operator; + @NotNull(message = "id不能为空") + private Integer id; + private String password; + @Pattern(regexp = "^[0-5]$", message = "permission取值范围为0-5") + private String permission; + private String remark; + + public String getOperator() { + return operator; + } + + public void setOperator(String operator) { + this.operator = operator; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPermission() { + return permission; + } + + public void setPermission(String permission) { + this.permission = permission; + } + + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } +} diff --git a/src/main/java/com/sy/travel/entity/Classes.java b/src/main/java/com/sy/travel/entity/Classes.java index c5049b4..14282ad 100644 --- a/src/main/java/com/sy/travel/entity/Classes.java +++ b/src/main/java/com/sy/travel/entity/Classes.java @@ -3,9 +3,9 @@ import java.util.HashMap; import java.util.Map; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; import com.sy.travel.common.SY; diff --git a/src/main/java/com/sy/travel/entity/Logger.java b/src/main/java/com/sy/travel/entity/Logger.java index 6e648ef..d84a370 100644 --- a/src/main/java/com/sy/travel/entity/Logger.java +++ b/src/main/java/com/sy/travel/entity/Logger.java @@ -3,9 +3,9 @@ import java.util.HashMap; import java.util.Map; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; import com.sy.travel.common.SY; diff --git a/src/main/java/com/sy/travel/entity/Product.java b/src/main/java/com/sy/travel/entity/Product.java index 0f5aee7..98c597c 100644 --- a/src/main/java/com/sy/travel/entity/Product.java +++ b/src/main/java/com/sy/travel/entity/Product.java @@ -3,9 +3,9 @@ import java.util.HashMap; import java.util.Map; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; import com.sy.travel.common.SY; diff --git a/src/main/java/com/sy/travel/entity/Project.java b/src/main/java/com/sy/travel/entity/Project.java index 8431b2e..b3fc4bb 100644 --- a/src/main/java/com/sy/travel/entity/Project.java +++ b/src/main/java/com/sy/travel/entity/Project.java @@ -3,9 +3,9 @@ import java.util.HashMap; import java.util.Map; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; import com.sy.travel.common.SY; diff --git a/src/main/java/com/sy/travel/entity/Role.java b/src/main/java/com/sy/travel/entity/Role.java index fe3fee8..0be283b 100644 --- a/src/main/java/com/sy/travel/entity/Role.java +++ b/src/main/java/com/sy/travel/entity/Role.java @@ -3,9 +3,9 @@ import java.util.HashMap; import java.util.Map; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; import com.sy.travel.common.SY; /** diff --git a/src/main/java/com/sy/travel/entity/Team.java b/src/main/java/com/sy/travel/entity/Team.java index 058ebd7..942cf69 100644 --- a/src/main/java/com/sy/travel/entity/Team.java +++ b/src/main/java/com/sy/travel/entity/Team.java @@ -3,9 +3,9 @@ import java.util.HashMap; import java.util.Map; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; import com.sy.travel.common.SY; diff --git a/src/main/java/com/sy/travel/entity/User.java b/src/main/java/com/sy/travel/entity/User.java index 030dbea..624a6ad 100644 --- a/src/main/java/com/sy/travel/entity/User.java +++ b/src/main/java/com/sy/travel/entity/User.java @@ -1,14 +1,14 @@ package com.sy.travel.entity; -import java.util.Base64; import java.util.HashMap; import java.util.Map; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; import com.sy.travel.common.SY; +import com.sy.travel.common.PasswordSupport; /** * 用户信息 @@ -52,15 +52,14 @@ public void setUsername(String username) { } public String getPassword() { - byte[] decode = Base64.getDecoder().decode(this.password); - String pass = new String(decode); - return pass.substring(pass.indexOf(":") + 1); + if (PasswordSupport.isBcryptHash(this.password)) { + return this.password; + } + return PasswordSupport.legacyDecode(this.password); } - private String setPassword(String username, String password) { - String pwd = username + ":" + password; - byte[] encode = Base64.getEncoder().encode(pwd.getBytes()); - return new String(encode); + public String getEncodedPassword() { + return this.password; } public void setPassword(String password) { @@ -92,7 +91,7 @@ public String toString() { public Map toMap() { Map m = new HashMap<>(); m.put("username", username); - m.put("password", setPassword(username, password)); + m.put("password", password); m.put("remark", remark); m.put("permission", permission); m.put("id", id); diff --git a/src/main/java/com/sy/travel/handler/GlobalExceptionHandler.java b/src/main/java/com/sy/travel/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..d0c0635 --- /dev/null +++ b/src/main/java/com/sy/travel/handler/GlobalExceptionHandler.java @@ -0,0 +1,47 @@ +package com.sy.travel.handler; + +import java.util.stream.Collectors; + +import jakarta.validation.ConstraintViolationException; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MissingServletRequestParameterException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +import com.sy.travel.common.AjaxResult; +import com.sy.travel.common.ApiMessages; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public AjaxResult handleValidation(MethodArgumentNotValidException ex) { + String message = ex.getBindingResult().getFieldErrors().stream().map(fieldError -> fieldError.getDefaultMessage()) + .collect(Collectors.joining(";")); + return AjaxResult.validationError(message); + } + + @ExceptionHandler({ + ConstraintViolationException.class, + MissingServletRequestParameterException.class, + MethodArgumentTypeMismatchException.class }) + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ResponseBody + public AjaxResult handleBadRequest(Exception ex) { + return AjaxResult.validationError(ex.getMessage()); + } + + @ExceptionHandler(Exception.class) + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ResponseBody + public AjaxResult handleUnexpected(Exception ex) { + return AjaxResult.internalError(ApiMessages.INTERNAL_ERROR_MESSAGE); + } +} diff --git a/src/main/java/com/sy/travel/rest/SYClassesRest.java b/src/main/java/com/sy/travel/rest/SYClassesRest.java index 19891b8..79f498d 100644 --- a/src/main/java/com/sy/travel/rest/SYClassesRest.java +++ b/src/main/java/com/sy/travel/rest/SYClassesRest.java @@ -2,10 +2,13 @@ import java.util.Map; -import javax.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -13,8 +16,9 @@ import org.springframework.web.bind.annotation.RestController; import com.sy.travel.common.AjaxResult; +import com.sy.travel.dto.classes.ClassesCreateRequest; +import com.sy.travel.dto.classes.ClassesUpdateRequest; import com.sy.travel.service.SYClassesService; -import com.sy.travel.utils.JSON; /** * 分类的对页面接口 @@ -22,6 +26,7 @@ * */ @RestController +@Validated @RequestMapping(value = "/sy/classes", produces = MediaType.APPLICATION_JSON_VALUE) public class SYClassesRest { @Autowired @@ -32,24 +37,24 @@ public class SYClassesRest { @RequestMapping(value = "/all", method = RequestMethod.GET) public AjaxResult> queryAll( @RequestParam(defaultValue = "") String name, - @RequestParam(defaultValue = "1") int currentPage, - @RequestParam(defaultValue = "10") int pageSize){ - return new AjaxResult>(200, "success", syClassesService.queryAll(name, currentPage, pageSize)); + @RequestParam(defaultValue = "1") @Min(1) int currentPage, + @RequestParam(defaultValue = "10") @Min(1) int pageSize){ + return AjaxResult.success(syClassesService.queryAll(name, currentPage, pageSize)); } /** * 添加分类信息 */ @RequestMapping(value = "/add", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult add(@RequestBody JSON json, HttpServletRequest request){ - return syClassesService.add(json, request.getRemoteUser()); + public AjaxResult add(@Valid @RequestBody ClassesCreateRequest request){ + return syClassesService.add(request); } /** * 删除分类信息:如果这个分类下有产品信息则不能删除这个分类 */ @RequestMapping(value = "/delete", method = RequestMethod.GET) - public AjaxResult delete(@RequestParam("id") int id, @RequestParam("operator") String operator, HttpServletRequest request){ + public AjaxResult delete(@RequestParam("id") @Min(1) int id, @RequestParam("operator") @NotBlank String operator){ return syClassesService.delete(id, operator); } @@ -57,15 +62,15 @@ public AjaxResult delete(@RequestParam("id") int id, @RequestParam("oper * 修改分类信息 */ @RequestMapping(value = "/update", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult delete(@RequestBody JSON json, HttpServletRequest request) { - return syClassesService.update(json, request.getRemoteUser()); + public AjaxResult update(@Valid @RequestBody ClassesUpdateRequest request) { + return syClassesService.update(request); } /** * 查看这个分类下的产品信息 */ @RequestMapping(value = "/info", method = RequestMethod.GET) - public AjaxResult> info(@RequestParam("id") String id){ - return new AjaxResult>(200, "success", syClassesService.info(id)); + public AjaxResult> info(@RequestParam("id") @Min(1) Integer id){ + return AjaxResult.success(syClassesService.info(String.valueOf(id))); } } diff --git a/src/main/java/com/sy/travel/rest/SYLoggerRest.java b/src/main/java/com/sy/travel/rest/SYLoggerRest.java index 6368b66..cb6cd55 100644 --- a/src/main/java/com/sy/travel/rest/SYLoggerRest.java +++ b/src/main/java/com/sy/travel/rest/SYLoggerRest.java @@ -2,8 +2,11 @@ import java.util.Map; +import jakarta.validation.constraints.Min; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; @@ -18,6 +21,7 @@ * */ @RestController +@Validated @RequestMapping(value = "/sy/logger", produces = MediaType.APPLICATION_JSON_VALUE) public class SYLoggerRest { @Autowired @@ -28,9 +32,9 @@ public class SYLoggerRest { */ @RequestMapping(value = "/all", method = RequestMethod.GET) public AjaxResult> queryAll( - @RequestParam(defaultValue = "1") int currentPage, - @RequestParam(defaultValue = "10") int pageSize + @RequestParam(defaultValue = "1") @Min(1) int currentPage, + @RequestParam(defaultValue = "10") @Min(1) int pageSize ){ - return new AjaxResult>(200, "success", syLoggerService.all(currentPage, pageSize)); + return AjaxResult.success(syLoggerService.all(currentPage, pageSize)); } } diff --git a/src/main/java/com/sy/travel/rest/SYLoginRest.java b/src/main/java/com/sy/travel/rest/SYLoginRest.java index 026dc17..fcd20d9 100644 --- a/src/main/java/com/sy/travel/rest/SYLoginRest.java +++ b/src/main/java/com/sy/travel/rest/SYLoginRest.java @@ -1,6 +1,8 @@ package com.sy.travel.rest; +import jakarta.validation.Valid; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.RequestBody; @@ -9,8 +11,8 @@ import org.springframework.web.bind.annotation.RestController; import com.sy.travel.common.AjaxResult; +import com.sy.travel.dto.login.LoginCheckRequest; import com.sy.travel.service.SYLoginService; -import com.sy.travel.utils.JSON; /** * 登陆接口 @@ -27,7 +29,7 @@ public class SYLoginRest { * 检测登陆用户信息是否正确 */ @RequestMapping(value="/check", method=RequestMethod.POST, consumes="application/json") - public AjaxResult loginCheck(@RequestBody JSON json) { - return syLoginService.loginCheck(json); + public AjaxResult loginCheck(@Valid @RequestBody LoginCheckRequest request) { + return syLoginService.loginCheck(request); } } diff --git a/src/main/java/com/sy/travel/rest/SYProductRest.java b/src/main/java/com/sy/travel/rest/SYProductRest.java index 4267e05..5915243 100644 --- a/src/main/java/com/sy/travel/rest/SYProductRest.java +++ b/src/main/java/com/sy/travel/rest/SYProductRest.java @@ -2,10 +2,13 @@ import java.util.Map; -import javax.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -13,14 +16,16 @@ import org.springframework.web.bind.annotation.RestController; import com.sy.travel.common.AjaxResult; +import com.sy.travel.dto.product.ProductCreateRequest; +import com.sy.travel.dto.product.ProductUpdateRequest; import com.sy.travel.service.SYProductService; -import com.sy.travel.utils.JSON; /** * 产品模块的接口 * @author liuxin * */ @RestController +@Validated @RequestMapping(value = "/sy/product", produces = MediaType.APPLICATION_JSON_VALUE) public class SYProductRest { @Autowired @@ -34,40 +39,40 @@ public class SYProductRest { @RequestMapping(value = "/all", method = RequestMethod.GET) public AjaxResult> queryAll( @RequestParam(defaultValue = "") String name, - @RequestParam(defaultValue = "1") int currentPage, - @RequestParam(defaultValue = "10") int pageSize) { - return new AjaxResult>(200,"success", syProductService.findAll(name, currentPage, pageSize)); + @RequestParam(defaultValue = "1") @Min(1) int currentPage, + @RequestParam(defaultValue = "10") @Min(1) int pageSize) { + return AjaxResult.success(syProductService.findAll(name, currentPage, pageSize)); } /** * 点击团队时查看这个团队下的产品信息 */ @RequestMapping(value = "/teamid", method = RequestMethod.GET) - public AjaxResult> queryByTeamId(@RequestParam("id") String id){ - return new AjaxResult>(200,"success", syProductService.findByTeamId(id)); + public AjaxResult> queryByTeamId(@RequestParam("id") @Min(1) Integer id){ + return AjaxResult.success(syProductService.findByTeamId(String.valueOf(id))); } /** * 点击团队时查看这个团队下的产品信息 */ @RequestMapping(value = "/classid", method = RequestMethod.GET) - public AjaxResult> queryByClassesId(@RequestParam("id") String id){ - return new AjaxResult>(200,"success", syProductService.findByClassId(id)); + public AjaxResult> queryByClassesId(@RequestParam("id") @Min(1) Integer id){ + return AjaxResult.success(syProductService.findByClassId(String.valueOf(id))); } /** * 添加产品信息 */ @RequestMapping(value = "/add", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult add(@RequestBody JSON json, HttpServletRequest request){ - return syProductService.add(json, request.getRemoteUser()); + public AjaxResult add(@Valid @RequestBody ProductCreateRequest request){ + return syProductService.add(request); } /** * 删除产品信息 */ @RequestMapping(value = "/delete", method = RequestMethod.GET) - public AjaxResult delete(@RequestParam("id") int id, @RequestParam("operator") String operator, HttpServletRequest request){ + public AjaxResult delete(@RequestParam("id") @Min(1) int id, @RequestParam("operator") @NotBlank String operator){ return syProductService.delete(id, operator); } @@ -75,7 +80,7 @@ public AjaxResult delete(@RequestParam("id") int id, @RequestParam("oper * 修改产品信息 */ @RequestMapping(value = "/update", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult update(@RequestBody JSON json, HttpServletRequest request){ - return syProductService.update(json, request.getRemoteUser()); + public AjaxResult update(@Valid @RequestBody ProductUpdateRequest request){ + return syProductService.update(request); } } diff --git a/src/main/java/com/sy/travel/rest/SYProjectRest.java b/src/main/java/com/sy/travel/rest/SYProjectRest.java index 705c419..6e08c02 100644 --- a/src/main/java/com/sy/travel/rest/SYProjectRest.java +++ b/src/main/java/com/sy/travel/rest/SYProjectRest.java @@ -2,10 +2,13 @@ import java.util.Map; -import javax.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -13,8 +16,9 @@ import org.springframework.web.bind.annotation.RestController; import com.sy.travel.common.AjaxResult; +import com.sy.travel.dto.project.ProjectCreateRequest; +import com.sy.travel.dto.project.ProjectUpdateRequest; import com.sy.travel.service.SYProjectService; -import com.sy.travel.utils.JSON; /** * 项目模块的接口 @@ -23,6 +27,7 @@ * */ @RestController +@Validated @RequestMapping(value = "/sy/project", produces = MediaType.APPLICATION_JSON_VALUE) public class SYProjectRest { @Autowired @@ -39,11 +44,11 @@ public class SYProjectRest { @RequestMapping(value = "/all", method = RequestMethod.GET) public AjaxResult> all( @RequestParam(defaultValue = "") String name, - @RequestParam(defaultValue = "1") int currentPage, - @RequestParam(defaultValue = "10") int pageSize + @RequestParam(defaultValue = "1") @Min(1) int currentPage, + @RequestParam(defaultValue = "10") @Min(1) int pageSize ) { Map data = syProjectService.queryAll(name, currentPage, pageSize); - return new AjaxResult>(200, "success", data); + return AjaxResult.success(data); } /** @@ -52,8 +57,8 @@ public AjaxResult> all( * @return */ @RequestMapping(value = "/info", method = RequestMethod.GET) - public AjaxResult> info(@RequestParam(defaultValue = "0") Integer id) { - return new AjaxResult>(200, "success", syProjectService.info(id)); + public AjaxResult> info(@RequestParam(defaultValue = "1") @Min(1) Integer id) { + return AjaxResult.success(syProjectService.info(id)); } /** * 添加项目信息的接口 @@ -62,8 +67,8 @@ public AjaxResult> info(@RequestParam(defaultValue = "0") In * @return */ @RequestMapping(value = "/add", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult add(@RequestBody JSON json, HttpServletRequest request) { - return syProjectService.add(json, request.getRemoteUser()); + public AjaxResult add(@Valid @RequestBody ProjectCreateRequest request) { + return syProjectService.add(request); } /** @@ -72,7 +77,7 @@ public AjaxResult add(@RequestBody JSON json, HttpServletRequest request * @return */ @RequestMapping(value = "/delete", method = RequestMethod.GET) - public AjaxResult delete(@RequestParam("id") Integer id, @RequestParam("operator") String operator, HttpServletRequest request){ + public AjaxResult delete(@RequestParam("id") @Min(1) Integer id, @RequestParam("operator") @NotBlank String operator){ return syProjectService.deleteById(id, operator); } @@ -83,7 +88,7 @@ public AjaxResult delete(@RequestParam("id") Integer id, @RequestParam(" * @return */ @RequestMapping(value = "/update", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult update(@RequestBody JSON json, HttpServletRequest request){ - return syProjectService.update(json, request.getRemoteUser()); + public AjaxResult update(@Valid @RequestBody ProjectUpdateRequest request){ + return syProjectService.update(request); } } diff --git a/src/main/java/com/sy/travel/rest/SYRoleRest.java b/src/main/java/com/sy/travel/rest/SYRoleRest.java index 7413ac0..210d7a0 100644 --- a/src/main/java/com/sy/travel/rest/SYRoleRest.java +++ b/src/main/java/com/sy/travel/rest/SYRoleRest.java @@ -2,10 +2,13 @@ import java.util.Map; -import javax.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -13,8 +16,9 @@ import org.springframework.web.bind.annotation.RestController; import com.sy.travel.common.AjaxResult; +import com.sy.travel.dto.role.RoleCreateRequest; +import com.sy.travel.dto.role.RoleUpdateRequest; import com.sy.travel.service.SYRoleService; -import com.sy.travel.utils.JSON; /** * 角色模块接口 @@ -23,6 +27,7 @@ * */ @RestController +@Validated @RequestMapping(value = "/sy/role", produces = MediaType.APPLICATION_JSON_VALUE) public class SYRoleRest { @Autowired @@ -38,9 +43,9 @@ public class SYRoleRest { @RequestMapping(value = "/all", method = RequestMethod.GET) public AjaxResult> queryAll( @RequestParam(defaultValue = "") String teamId, - @RequestParam(defaultValue = "1") int currentPage, - @RequestParam(defaultValue = "10") int pageSize) { - return new AjaxResult>(200, "success", syRoleService.queryAll(teamId, currentPage, pageSize)); + @RequestParam(defaultValue = "1") @Min(1) int currentPage, + @RequestParam(defaultValue = "10") @Min(1) int pageSize) { + return AjaxResult.success(syRoleService.queryAll(teamId, currentPage, pageSize)); } /** @@ -51,8 +56,8 @@ public AjaxResult> queryAll( * @return */ @RequestMapping(value = "/add", method = RequestMethod.POST) - public AjaxResult add(@RequestBody JSON json, HttpServletRequest request) { - return syRoleService.add(json, request.getRemoteUser()); + public AjaxResult add(@Valid @RequestBody RoleCreateRequest request) { + return syRoleService.add(request); } /** @@ -63,8 +68,7 @@ public AjaxResult add(@RequestBody JSON json, HttpServletRequest request * @return */ @RequestMapping(value = "/delete", method = RequestMethod.GET) - public AjaxResult delete(@RequestParam("id") int id, @RequestParam("operator") String operator, - HttpServletRequest request) { + public AjaxResult delete(@RequestParam("id") @Min(1) int id, @RequestParam("operator") @NotBlank String operator) { return syRoleService.delete(id, operator); } @@ -75,7 +79,7 @@ public AjaxResult delete(@RequestParam("id") int id, @RequestParam("oper * @return */ @RequestMapping(value = "/update", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult update(@RequestBody JSON json, HttpServletRequest request){ - return syRoleService.update(json, request.getRemoteUser()); + public AjaxResult update(@Valid @RequestBody RoleUpdateRequest request){ + return syRoleService.update(request); } } diff --git a/src/main/java/com/sy/travel/rest/SYTeamRest.java b/src/main/java/com/sy/travel/rest/SYTeamRest.java index 01a200a..1064008 100644 --- a/src/main/java/com/sy/travel/rest/SYTeamRest.java +++ b/src/main/java/com/sy/travel/rest/SYTeamRest.java @@ -3,10 +3,13 @@ import java.util.List; import java.util.Map; -import javax.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -14,8 +17,9 @@ import org.springframework.web.bind.annotation.RestController; import com.sy.travel.common.AjaxResult; +import com.sy.travel.dto.team.TeamCreateRequest; +import com.sy.travel.dto.team.TeamUpdateRequest; import com.sy.travel.service.SYTeamService; -import com.sy.travel.utils.JSON; /** * 团队管理接口: 查看所有团队信息、 @@ -24,6 +28,7 @@ * */ @RestController +@Validated @RequestMapping(value = "/sy/team", produces = MediaType.APPLICATION_JSON_VALUE) public class SYTeamRest { @Autowired @@ -37,9 +42,9 @@ public class SYTeamRest { @RequestMapping(value = "/all", method = RequestMethod.GET) public AjaxResult> findAll( @RequestParam(defaultValue = "") String name, - @RequestParam(defaultValue = "1") int currentPage, - @RequestParam(defaultValue = "5") int pageSize) { - return new AjaxResult>(200, "success", syTeamService.findAll(name,currentPage,pageSize)); + @RequestParam(defaultValue = "1") @Min(1) int currentPage, + @RequestParam(defaultValue = "5") @Min(1) int pageSize) { + return AjaxResult.success(syTeamService.findAll(name,currentPage,pageSize)); } /** @@ -50,8 +55,8 @@ public AjaxResult> findAll( * @return */ @RequestMapping(value = "/add", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult add(@RequestBody JSON json, HttpServletRequest request) { - return syTeamService.add(json, request.getRemoteUser()); + public AjaxResult add(@Valid @RequestBody TeamCreateRequest request) { + return syTeamService.add(request); } /** @@ -60,7 +65,7 @@ public AjaxResult add(@RequestBody JSON json, HttpServletRequest request * @return */ @RequestMapping(value = "/info", method = RequestMethod.GET) - public AjaxResult>> info(@RequestParam("id") int id){ + public AjaxResult>> info(@RequestParam("id") @Min(1) int id){ return syTeamService.info(id); } @@ -71,9 +76,9 @@ public AjaxResult>> info(@RequestParam("id") int id){ * @return */ @RequestMapping(value = "/allbypid", method = RequestMethod.GET) - public AjaxResult> findAllByProjectId(@RequestParam(defaultValue = "") String projectId) { + public AjaxResult> findAllByProjectId(@RequestParam("projectId") @NotBlank String projectId) { Map data = syTeamService.queryByProjectId(projectId); - return new AjaxResult>(200, "success", data); + return AjaxResult.success(data); } /** @@ -85,7 +90,7 @@ public AjaxResult> findAllByProjectId(@RequestParam(defaultV * @return */ @RequestMapping(value = "/delete", method = RequestMethod.GET) - public AjaxResult deleteByTeamId(@RequestParam("id") Integer id, @RequestParam("operator") String operator, HttpServletRequest request) { + public AjaxResult deleteByTeamId(@RequestParam("id") @Min(1) Integer id, @RequestParam("operator") @NotBlank String operator) { return syTeamService.deleteByTeamId(id, operator); } /** @@ -95,7 +100,7 @@ public AjaxResult deleteByTeamId(@RequestParam("id") Integer id, @Reques * @return */ @RequestMapping(value="/update", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult update(@RequestBody JSON json, HttpServletRequest request) { - return syTeamService.update(json, request.getRemoteUser()); + public AjaxResult update(@Valid @RequestBody TeamUpdateRequest request) { + return syTeamService.update(request); } } diff --git a/src/main/java/com/sy/travel/rest/SYUploadRest.java b/src/main/java/com/sy/travel/rest/SYUploadRest.java index 7883882..0fa4686 100644 --- a/src/main/java/com/sy/travel/rest/SYUploadRest.java +++ b/src/main/java/com/sy/travel/rest/SYUploadRest.java @@ -18,14 +18,14 @@ public class SYUploadRest { @RequestMapping(value="/file", method=RequestMethod.POST) public AjaxResult upload(MultipartFile file) { if(file.isEmpty()) { - return new AjaxResult(500, "failed", "file is null"); + return AjaxResult.failed(500, "file is null"); } try { FileUtils.writeByteArrayToFile(new File("D:\\upload\\" + file.getOriginalFilename()), file.getBytes()); - return new AjaxResult(200, "success", "ok"); + return AjaxResult.success("ok"); } catch (IOException e) { e.printStackTrace(); - return new AjaxResult(500, "failed", "failed"); + return AjaxResult.failed(500, "failed"); } } } diff --git a/src/main/java/com/sy/travel/rest/SYUserRest.java b/src/main/java/com/sy/travel/rest/SYUserRest.java index ad512af..1bfb5e0 100644 --- a/src/main/java/com/sy/travel/rest/SYUserRest.java +++ b/src/main/java/com/sy/travel/rest/SYUserRest.java @@ -2,10 +2,13 @@ import java.util.Map; -import javax.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -13,8 +16,10 @@ import org.springframework.web.bind.annotation.RestController; import com.sy.travel.common.AjaxResult; +import com.sy.travel.dto.user.UserAdminPwdUpdateRequest; +import com.sy.travel.dto.user.UserCreateRequest; +import com.sy.travel.dto.user.UserUpdateRequest; import com.sy.travel.service.SYUserService; -import com.sy.travel.utils.JSON; /** * 用户模块接口 @@ -22,6 +27,7 @@ * */ @RestController +@Validated @RequestMapping(value = "/sy/user", produces = MediaType.APPLICATION_JSON_VALUE) public class SYUserRest { @Autowired @@ -36,56 +42,43 @@ public class SYUserRest { @RequestMapping(value = "/all", method = RequestMethod.GET) public AjaxResult> queryAll( @RequestParam(defaultValue = "") String name, - @RequestParam(defaultValue = "1") int currentPage, - @RequestParam(defaultValue = "10") int pageSize) { - return new AjaxResult>(200, "success", syUserservice.queryAll(name, currentPage, pageSize)); + @RequestParam(defaultValue = "1") @Min(1) int currentPage, + @RequestParam(defaultValue = "10") @Min(1) int pageSize) { + return AjaxResult.success(syUserservice.queryAll(name, currentPage, pageSize)); } /** * 添加用户信息 - * - * @param json - * @param request - * @return */ @RequestMapping(value = "/add", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult add(@RequestBody JSON json, HttpServletRequest request) { - return syUserservice.add(json, request.getRemoteUser()); + public AjaxResult add(@Valid @RequestBody UserCreateRequest request) { + return syUserservice.add(request); } /** * 删除用户信息 - * @param id - * @param operator - * @param request - * @return */ @RequestMapping(value = "/delete", method = RequestMethod.GET) - public AjaxResult delete(@RequestParam("id") int id, @RequestParam("operator") String operator, - HttpServletRequest request) { + public AjaxResult delete(@RequestParam("id") @Min(1) int id, @RequestParam("operator") @NotBlank String operator) { return syUserservice.delete(id, operator); } /** * 修改用户信息 - * @param json - * @param request - * @return */ @RequestMapping(value = "/update", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult update(@RequestBody JSON json, HttpServletRequest request){ - return syUserservice.update(json, request.getRemoteUser()); + public AjaxResult update(@Valid @RequestBody UserUpdateRequest request){ + return syUserservice.update(request); } /** * 修改超级管理员密码 * @param json - * @param request * @return */ @RequestMapping(value = "/adminpwd", method = RequestMethod.POST, consumes = "application/json") - public AjaxResult updatePwd(@RequestBody JSON json, HttpServletRequest request){ - return syUserservice.updateAdminPwd(json, request.getRemoteUser()); + public AjaxResult updatePwd(@Valid @RequestBody UserAdminPwdUpdateRequest request){ + return syUserservice.updateAdminPwd(request); } /** diff --git a/src/main/java/com/sy/travel/service/SYClassesService.java b/src/main/java/com/sy/travel/service/SYClassesService.java index ac4cab6..c41f953 100644 --- a/src/main/java/com/sy/travel/service/SYClassesService.java +++ b/src/main/java/com/sy/travel/service/SYClassesService.java @@ -19,8 +19,11 @@ import com.sy.travel.common.Commons; import com.sy.travel.common.DateFormat; import com.sy.travel.dao.SYClassesRepository; +import com.sy.travel.dto.classes.ClassesCreateRequest; +import com.sy.travel.dto.classes.ClassesUpdateRequest; import com.sy.travel.entity.Classes; -import com.sy.travel.entity.Logger; +import com.sy.travel.service.support.OperationResultSupport; +import com.sy.travel.service.support.PermissionGuard; import com.sy.travel.utils.JSON; /** * 分类信息的服务层处理 @@ -35,6 +38,8 @@ public class SYClassesService implements DateFormat{ private SYClassesRepository syClassesRepository; @Autowired private SYLoggerService syLoggerService; + @Autowired + private PermissionGuard permissionGuard; /** * 添加分类信息 @@ -46,6 +51,10 @@ public AjaxResult add(JSON json, String operator) { Date start = new Date(); operator = (String) json.get("operator"); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.CLASSES_ADD); + } String name = (String) json.get("name"); if(StringUtils.isBlank(name)) { reason = Commons.CLASSES_ADD_NAME_NOT_NULL; @@ -84,6 +93,16 @@ public AjaxResult add(JSON json, String operator) { } return result(operator, start, reason, "1", Commons.CLASSES_ADD); } + + public AjaxResult add(ClassesCreateRequest request) { + JSON json = new JSON(); + json.put("operator", request.getOperator()); + json.put("name", request.getName()); + json.put("sortId", request.getSortId()); + json.put("parentId", request.getParentId()); + json.put("remark", request.getRemark()); + return add(json, request.getOperator()); + } /** * 删除所选分类信息 @@ -94,6 +113,10 @@ public AjaxResult add(JSON json, String operator) { public AjaxResult delete(int id, String operator) { Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.CLASSES_DELETE); + } Classes classes = syClassesRepository.findOne(id); if(classes == null) { reason = Commons.CLASSES_DELETE_DATA_NOT_EXISTS; @@ -120,6 +143,10 @@ public AjaxResult update(JSON json, String operator) { operator = (String) json.get("operator"); Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.CLASSES_UPDATE); + } int id = Integer.valueOf((String)json.get("id")); Classes classes = syClassesRepository.findOne(id); String name = (String) json.get("name"); @@ -164,6 +191,17 @@ public AjaxResult update(JSON json, String operator) { } return result(operator, start, reason, "1", Commons.CLASSES_UPDATE); } + + public AjaxResult update(ClassesUpdateRequest request) { + JSON json = new JSON(); + json.put("operator", request.getOperator()); + json.put("id", request.getId() == null ? null : String.valueOf(request.getId())); + json.put("name", request.getName()); + json.put("sortId", request.getSortId()); + json.put("parentId", request.getParentId()); + json.put("remark", request.getRemark()); + return update(json, request.getOperator()); + } /** * 查询分类信息: @@ -238,10 +276,6 @@ public Map info(String id) { * @return */ private AjaxResult result(String operator, Date start, String reason, String status, String operation) { - Logger logger = new Logger(operator, sdf.format(start), sdf.format(new Date()), - StringUtils.isBlank(reason) ? operator + operation + ":成功" : operator + operation + "失败原因:" + reason, - status, operation);// 记录操作日志 - syLoggerService.save(logger); - return new AjaxResult(200, "1".equals(status) ? "success" : "failed", reason); + return OperationResultSupport.build(syLoggerService, operator, start, reason, status, operation); } } diff --git a/src/main/java/com/sy/travel/service/SYLoginService.java b/src/main/java/com/sy/travel/service/SYLoginService.java index 23dfaf4..e8a8121 100644 --- a/src/main/java/com/sy/travel/service/SYLoginService.java +++ b/src/main/java/com/sy/travel/service/SYLoginService.java @@ -6,9 +6,10 @@ import com.sy.travel.common.AjaxResult; import com.sy.travel.common.Commons; +import com.sy.travel.common.PasswordSupport; import com.sy.travel.dao.SYUserRepository; +import com.sy.travel.dto.login.LoginCheckRequest; import com.sy.travel.entity.User; -import com.sy.travel.utils.JSON; /** * 登陆处理 @@ -20,31 +21,31 @@ public class SYLoginService { @Autowired private SYUserRepository syUserRepository; - public AjaxResult loginCheck(JSON json) { - String username = (String) json.get("username"); - String password = (String) json.get("password"); + public AjaxResult loginCheck(LoginCheckRequest request) { + String username = request.getUsername(); + String password = request.getPassword(); String reason = ""; if(StringUtils.isBlank(username)) { reason = Commons.LOGIN_CHECK_NAME_NOT_NULL; - return returnResult("failed", reason); + return AjaxResult.failed(200, reason); } if(StringUtils.isBlank(password)) { reason = Commons.LOGIN_CHECK_PWD_NOT_NULl; - return returnResult("failed", reason); + return AjaxResult.failed(200, reason); } User user = syUserRepository.findByUsername(username); if(user == null) { reason = Commons.LOGIN_CHECK_NAME_NOT_EXISTS; - return returnResult("failed", reason); + return AjaxResult.failed(200, reason); } - if(!password.equals(user.getPassword())) { + if(!PasswordSupport.matches(user.getUsername(), password, user.getEncodedPassword())) { reason = Commons.LOGIN_CHECK_PWD_ERROR; - return returnResult("failed", reason); + return AjaxResult.failed(200, reason); } - return returnResult("success",user.getPermission()); - } - - private AjaxResult returnResult(String result, String reason) { - return new AjaxResult(200, result, reason); + if(!PasswordSupport.isBcryptHash(user.getEncodedPassword())) { + user.setPassword(PasswordSupport.hash(password)); + syUserRepository.save(user); + } + return AjaxResult.success(user.getPermission()); } } diff --git a/src/main/java/com/sy/travel/service/SYProductService.java b/src/main/java/com/sy/travel/service/SYProductService.java index 41b0370..29c7a83 100644 --- a/src/main/java/com/sy/travel/service/SYProductService.java +++ b/src/main/java/com/sy/travel/service/SYProductService.java @@ -21,8 +21,11 @@ import com.sy.travel.common.DateFormat; import com.sy.travel.dao.SYClassesRepository; import com.sy.travel.dao.SYProductRepository; -import com.sy.travel.entity.Logger; +import com.sy.travel.dto.product.ProductCreateRequest; +import com.sy.travel.dto.product.ProductUpdateRequest; import com.sy.travel.entity.Product; +import com.sy.travel.service.support.OperationResultSupport; +import com.sy.travel.service.support.PermissionGuard; import com.sy.travel.utils.JSON; /** @@ -40,6 +43,8 @@ public class SYProductService implements DateFormat{ private SYClassesRepository syClassesRepository; @Autowired private SYLoggerService syLoggerService; + @Autowired + private PermissionGuard permissionGuard; /** * 添加产品信息 @@ -51,6 +56,10 @@ public AjaxResult add(JSON json, String operator) { operator = (String) json.get("operator"); Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.PRODUCT_ADD); + } String code = (String) json.get("code"); if(StringUtils.isBlank(code)) { reason = Commons.PRODUCT_ADD_CODE_NOT_NULL; @@ -126,6 +135,26 @@ public AjaxResult add(JSON json, String operator) { } return result(operator, start, reason, "1", Commons.PRODUCT_ADD); } + + public AjaxResult add(ProductCreateRequest request) { + JSON json = new JSON(); + json.put("operator", request.getOperator()); + json.put("code", request.getCode()); + json.put("name", request.getName()); + json.put("teamId", request.getTeamId()); + json.put("exText", request.getExText()); + json.put("onlineDate", request.getOnlineDate()); + json.put("offlineDate", request.getOfflineDate()); + json.put("classId", request.getClassId()); + json.put("quantity", request.getQuantity()); + json.put("minQty", request.getMinQty()); + json.put("soldQty", request.getSoldQty()); + json.put("price", request.getPrice()); + json.put("nights", request.getNights()); + json.put("status", request.getStatus()); + json.put("remark", request.getRemark()); + return add(json, request.getOperator()); + } /** * 删除产品信息 @@ -136,6 +165,10 @@ public AjaxResult add(JSON json, String operator) { public AjaxResult delete(int id, String operator){ Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.PRODUCT_DELETE); + } try { syProductRepository.delete(id); return result(operator, start, reason, "1", Commons.PRODUCT_DELETE); @@ -157,6 +190,10 @@ public AjaxResult update(JSON json, String operator){ operator = (String) json.get("operator"); Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.PRODUCT_DELETE); + } int id = (int) json.get("id"); Product product = syProductRepository.findOne(id); if(product == null) { @@ -193,6 +230,22 @@ public AjaxResult update(JSON json, String operator){ } return result(operator, start, reason, "1", Commons.PRODUCT_DELETE); } + + public AjaxResult update(ProductUpdateRequest request){ + JSON json = new JSON(); + json.put("operator", request.getOperator()); + json.put("id", request.getId()); + json.put("name", request.getName()); + json.put("exText", request.getExText()); + json.put("quantity", request.getQuantity()); + json.put("minQty", request.getMinQty()); + json.put("soldQty", request.getSoldQty()); + json.put("price", request.getPrice()); + json.put("nights", request.getNights()); + json.put("status", request.getStatus()); + json.put("remark", request.getRemark()); + return update(json, request.getOperator()); + } /** * 查看所有的产品信息 @@ -307,10 +360,6 @@ public Map findByClassId(String id) { * @return */ private AjaxResult result(String operator, Date start, String reason, String status, String operation) { - Logger logger = new Logger(operator, sdf.format(start), sdf.format(new Date()), - StringUtils.isBlank(reason) ? operator + operation + ":成功" : operator + operation + "失败原因:" + reason, - status, operation);// 记录操作日志 - syLoggerService.save(logger); - return new AjaxResult(200, "1".equals(status) ? "success" : "failed", reason); + return OperationResultSupport.build(syLoggerService, operator, start, reason, status, operation); } } diff --git a/src/main/java/com/sy/travel/service/SYProjectService.java b/src/main/java/com/sy/travel/service/SYProjectService.java index 6a18554..09838d1 100644 --- a/src/main/java/com/sy/travel/service/SYProjectService.java +++ b/src/main/java/com/sy/travel/service/SYProjectService.java @@ -19,8 +19,11 @@ import com.sy.travel.common.Commons; import com.sy.travel.common.DateFormat; import com.sy.travel.dao.SYProjectRepository; -import com.sy.travel.entity.Logger; +import com.sy.travel.dto.project.ProjectCreateRequest; +import com.sy.travel.dto.project.ProjectUpdateRequest; import com.sy.travel.entity.Project; +import com.sy.travel.service.support.OperationResultSupport; +import com.sy.travel.service.support.PermissionGuard; import com.sy.travel.utils.JSON; @@ -40,6 +43,8 @@ public class SYProjectService implements DateFormat{ private SYProductService syProductService; @Autowired private SYLoggerService syLoggerService; + @Autowired + private PermissionGuard permissionGuard; /** * 添加项目信息 @@ -54,6 +59,10 @@ public AjaxResult add(JSON json, String operator) { operator = (String) json.get("operator");// 操作人 Date start = new Date();// 添加操作开始时间 String reason = "";// 记录操作过程中的操作结果 + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.PROJECT_ADD); + } String code = (String) json.get("code"); if (StringUtils.isBlank(code)) { reason = Commons.PROJECT_ADD_CODE_NOT_NULL; @@ -130,6 +139,18 @@ public AjaxResult add(JSON json, String operator) { return result(operator, start, reason, "1", Commons.PROJECT_ADD); } + public AjaxResult add(ProjectCreateRequest request) { + JSON json = new JSON(); + json.put("operator", request.getOperator()); + json.put("code", request.getCode()); + json.put("name", request.getName()); + json.put("beginDate", request.getBeginDate()); + json.put("endDate", request.getEndDate()); + json.put("valid", request.getValid()); + json.put("remark", request.getRemark()); + return add(json, request.getOperator()); + } + /** * 根据id删除这个项目及项目下的所有团队和产品信息 * @@ -140,6 +161,10 @@ public AjaxResult add(JSON json, String operator) { public AjaxResult deleteById(int id, String operator) { Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.PROJECT_DELETE); + } try { // 根据项目id查询这个项目下的团队信息 Map teamMap = syTeamService.queryByProjectId(String.valueOf(id)); @@ -172,6 +197,10 @@ public AjaxResult update(JSON json, String operator) { operator = (String) json.get("operator"); Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.PROJECT_UPDATE); + } if (StringUtils.isBlank((String) json.get("id"))) { reason = Commons.PROJECT_UPDATE_ID_NOT_NULL; return result(operator, start, reason, "0", Commons.PROJECT_UPDATE); @@ -283,6 +312,19 @@ public AjaxResult update(JSON json, String operator) { return result(operator, start, reason, "1", Commons.PROJECT_UPDATE); } + public AjaxResult update(ProjectUpdateRequest request) { + JSON json = new JSON(); + json.put("operator", request.getOperator()); + json.put("id", request.getId() == null ? null : String.valueOf(request.getId())); + json.put("code", request.getCode()); + json.put("name", request.getName()); + json.put("beginDate", request.getBeginDate()); + json.put("endDate", request.getEndDate()); + json.put("valid", request.getValid()); + json.put("remark", request.getRemark()); + return update(json, request.getOperator()); + } + /** * 查看所有项目的信息 根据项目名称查询这个项目的信息 当项目名称为空时,返回的是所有项目的信息 * @@ -380,10 +422,6 @@ public Map info(int id) { * @return */ private AjaxResult result(String operator, Date start, String reason, String status, String operation) { - Logger logger = new Logger(operator, sdf.format(start), sdf.format(new Date()), - StringUtils.isBlank(reason) ? operator + operation + ":成功" : operator + operation + "失败原因:" + reason, - status, operation);// 记录操作日志 - syLoggerService.save(logger); - return new AjaxResult(200, "1".equals(status) ? "success" : "failed", reason); + return OperationResultSupport.build(syLoggerService, operator, start, reason, status, operation); } } diff --git a/src/main/java/com/sy/travel/service/SYRoleService.java b/src/main/java/com/sy/travel/service/SYRoleService.java index fd4bb1d..3bbddda 100644 --- a/src/main/java/com/sy/travel/service/SYRoleService.java +++ b/src/main/java/com/sy/travel/service/SYRoleService.java @@ -19,8 +19,11 @@ import com.sy.travel.common.Commons; import com.sy.travel.common.DateFormat; import com.sy.travel.dao.SYRoleRepository; -import com.sy.travel.entity.Logger; +import com.sy.travel.dto.role.RoleCreateRequest; +import com.sy.travel.dto.role.RoleUpdateRequest; import com.sy.travel.entity.Role; +import com.sy.travel.service.support.OperationResultSupport; +import com.sy.travel.service.support.PermissionGuard; import com.sy.travel.utils.JSON; /** @@ -37,6 +40,8 @@ public class SYRoleService implements DateFormat{ private SYTeamService syTeamService; @Autowired private SYLoggerService syLoggerService; + @Autowired + private PermissionGuard permissionGuard; /** * 添加角色信息 @@ -48,6 +53,10 @@ public AjaxResult add(JSON json, String operator){ operator = (String) json.get("operator"); Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.ROLE_ADD); + } String name = (String) json.get("name"); if(StringUtils.isBlank(name)) { reason = Commons.ROLE_ADD_NAME_NOT_NULL; @@ -101,6 +110,18 @@ public AjaxResult add(JSON json, String operator){ } return result(operator, start, reason, "1", Commons.ROLE_ADD); } + + public AjaxResult add(RoleCreateRequest request) { + JSON json = new JSON(); + json.put("operator", request.getOperator()); + json.put("name", request.getName()); + json.put("teamId", request.getTeamId()); + json.put("role", request.getRole()); + json.put("email", request.getEmail()); + json.put("mobile", request.getMobile()); + json.put("remark", request.getRemark()); + return add(json, request.getOperator()); + } /** * 删除角色信息 @@ -110,6 +131,10 @@ public AjaxResult add(JSON json, String operator){ public AjaxResult delete(int id, String operator){ Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.ROLE_DELETE); + } try { syRoleRepository.delete(id); return result(operator, start, reason, "1", Commons.ROLE_DELETE); @@ -129,6 +154,10 @@ public AjaxResult update(JSON json, String operator){ operator = (String) json.get("operator"); Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.ROLE_UPDATE); + } if(StringUtils.isBlank((String)json.get("id"))) { reason = Commons.ROLE_ADD_TEAMID_NOT_NULL; return result(operator, start, reason, "0", Commons.ROLE_UPDATE); @@ -175,6 +204,17 @@ public AjaxResult update(JSON json, String operator){ } return result(operator, start, reason, "1", Commons.ROLE_UPDATE); } + + public AjaxResult update(RoleUpdateRequest request){ + JSON json = new JSON(); + json.put("operator", request.getOperator()); + json.put("id", String.valueOf(request.getId())); + json.put("role", request.getRole()); + json.put("email", request.getEmail()); + json.put("mobile", request.getMobile()); + json.put("remark", request.getRemark()); + return update(json, request.getOperator()); + } /** @@ -229,10 +269,6 @@ public Map queryAll(String teamId, int currentPage, int pageSize * @return */ private AjaxResult result(String operator, Date start, String reason, String status, String operation) { - Logger logger = new Logger(operator, sdf.format(start), sdf.format(new Date()), - StringUtils.isBlank(reason) ? operator + operation + ":成功" : operator + operation + "失败原因:" + reason, - status, operation);// 记录操作日志 - syLoggerService.save(logger); - return new AjaxResult(200, "1".equals(status) ? "success" : "failed", reason); + return OperationResultSupport.build(syLoggerService, operator, start, reason, status, operation); } } diff --git a/src/main/java/com/sy/travel/service/SYTeamService.java b/src/main/java/com/sy/travel/service/SYTeamService.java index 1db5c33..df71ba3 100644 --- a/src/main/java/com/sy/travel/service/SYTeamService.java +++ b/src/main/java/com/sy/travel/service/SYTeamService.java @@ -22,11 +22,14 @@ import com.sy.travel.dao.SYProjectRepository; import com.sy.travel.dao.SYRoleRepository; import com.sy.travel.dao.SYTeamRepository; -import com.sy.travel.entity.Logger; +import com.sy.travel.dto.team.TeamCreateRequest; +import com.sy.travel.dto.team.TeamUpdateRequest; import com.sy.travel.entity.Product; import com.sy.travel.entity.Project; import com.sy.travel.entity.Role; import com.sy.travel.entity.Team; +import com.sy.travel.service.support.OperationResultSupport; +import com.sy.travel.service.support.PermissionGuard; import com.sy.travel.utils.JSON; /** @@ -49,6 +52,8 @@ public class SYTeamService implements DateFormat{ private SYLoggerService syLoggerService; @Autowired private SYRoleRepository syRoleRepository; + @Autowired + private PermissionGuard permissionGuard; /** * 查询所有团队信息的逻辑操作 @@ -123,8 +128,12 @@ public Team findOne(Integer id) { public AjaxResult add(JSON json, String operator) { operator = (String) json.get("operator"); Date start = new Date(); - String name = (String) json.get("name"); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.TEAM_ADD); + } + String name = (String) json.get("name"); if (StringUtils.isBlank(name)) { reason = Commons.TEAM_ADD_NAME_NOT_NULL; return result(operator, start, reason, "0", Commons.TEAM_ADD); @@ -167,6 +176,16 @@ public AjaxResult add(JSON json, String operator) { return result(operator, start, reason, "1", Commons.TEAM_ADD); } + public AjaxResult add(TeamCreateRequest request) { + JSON json = new JSON(); + json.put("operator", request.getOperator()); + json.put("name", request.getName()); + json.put("projectId", request.getProjectId()); + json.put("valid", request.getValid()); + json.put("remark", request.getRemark()); + return add(json, request.getOperator()); + } + public Map queryByProjectId(String projectId) { Map resultMap = new HashMap<>(); if (StringUtils.isBlank(projectId)) { @@ -203,6 +222,10 @@ public Map queryByProjectId(String projectId) { public AjaxResult deleteByTeamId(Integer id, String operator) { Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.TEAM_DELETE); + } try { teamRepository.delete(id); //删除这个团队下的产品信息 @@ -255,6 +278,10 @@ public AjaxResult update(JSON json, String operator) { operator = (String) json.get("operator"); Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.TEAM_UPDATE); + } if (StringUtils.isBlank((String) json.get("id"))) { reason = Commons.PROJECT_UPDATE_ID_NOT_NULL; return result(operator, start, reason, "0", Commons.TEAM_UPDATE); @@ -309,6 +336,17 @@ public AjaxResult update(JSON json, String operator) { } return result(operator, start, reason, "1", Commons.TEAM_UPDATE); } + + public AjaxResult update(TeamUpdateRequest request) { + JSON json = new JSON(); + json.put("operator", request.getOperator()); + json.put("id", request.getId() == null ? null : String.valueOf(request.getId())); + json.put("name", request.getName()); + json.put("projectId", request.getProjectId()); + json.put("valid", request.getValid()); + json.put("remark", request.getRemark()); + return update(json, request.getOperator()); + } /** * 根据团队id查看这个团队下所有的产品信息 @@ -320,10 +358,10 @@ public AjaxResult>> info(int id) { Map temp = syProductService.findByTeamId(String.valueOf(id)); int totalP = (int) temp.get("total"); if (totalP == 0) { - return new AjaxResult>>(200, "success", new ArrayList<>()); + return AjaxResult.success(new ArrayList<>()); } List> list = (List>) temp.get("documents"); - return new AjaxResult>>(200, "success", list); + return AjaxResult.success(list); } /** @@ -342,10 +380,6 @@ public AjaxResult>> info(int id) { * @return */ private AjaxResult result(String operator, Date start, String reason, String status, String operation) { - Logger logger = new Logger(operator, sdf.format(start), sdf.format(new Date()), - StringUtils.isBlank(reason) ? operator + operation + ":成功" : operator + operation + "失败原因:" + reason, - status, operation);// 记录操作日志 - syLoggerService.save(logger); - return new AjaxResult(200, "1".equals(status) ? "success" : "failed", reason); + return OperationResultSupport.build(syLoggerService, operator, start, reason, status, operation); } } diff --git a/src/main/java/com/sy/travel/service/SYUserService.java b/src/main/java/com/sy/travel/service/SYUserService.java index 36c7c6e..fa4e028 100644 --- a/src/main/java/com/sy/travel/service/SYUserService.java +++ b/src/main/java/com/sy/travel/service/SYUserService.java @@ -1,7 +1,6 @@ package com.sy.travel.service; import java.util.ArrayList; -import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -19,10 +18,14 @@ import com.sy.travel.common.AjaxResult; import com.sy.travel.common.Commons; import com.sy.travel.common.DateFormat; +import com.sy.travel.common.PasswordSupport; import com.sy.travel.dao.SYUserRepository; -import com.sy.travel.entity.Logger; +import com.sy.travel.dto.user.UserAdminPwdUpdateRequest; +import com.sy.travel.dto.user.UserCreateRequest; +import com.sy.travel.dto.user.UserUpdateRequest; import com.sy.travel.entity.User; -import com.sy.travel.utils.JSON; +import com.sy.travel.service.support.OperationResultSupport; +import com.sy.travel.service.support.PermissionGuard; /** @@ -36,22 +39,23 @@ public class SYUserService implements DateFormat{ private SYUserRepository syUserRepository; @Autowired private SYLoggerService syLoggerService; - private String setPassword(String username, String password) { - String pwd = username + ":" + password; - byte[] encode = Base64.getEncoder().encode(pwd.getBytes()); - return new String(encode); - } + @Autowired + private PermissionGuard permissionGuard; /** * 添加用户信息 * @param json * @param operator * @return */ - public AjaxResult add(JSON json, String operator){ - operator = (String) json.get("operator"); + public AjaxResult add(UserCreateRequest request){ + String operator = request.getOperator(); Date start = new Date(); String reason = ""; - String username = (String) json.get("username"); + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.USER_ADD); + } + String username = request.getUsername(); if(StringUtils.isBlank(username)) { reason = Commons.USER_ADD_USRENAME_NOT_NULL; return result(operator, start, reason, "0", Commons.USER_ADD); @@ -61,19 +65,19 @@ public AjaxResult add(JSON json, String operator){ reason = Commons.USER_ADD_USRENAME_IS_EXISTS; return result(operator, start, reason, "0", Commons.USER_ADD); } - String password = (String) json.get("password"); + String password = request.getPassword(); if(StringUtils.isBlank(password)) { reason = Commons.USER_ADD_PASSWORD_NOT_NULL; return result(operator, start, reason, "0", Commons.USER_ADD); } - String permissionStr = (String) json.get("permission"); + String permissionStr = request.getPermission(); if(StringUtils.isBlank(permissionStr)) { reason = Commons.USER_ADD_PERMISSION_NOT_NULL; return result(operator, start, reason, "0", Commons.USER_ADD); } - String remark = (String) json.get("remark"); + String remark = request.getRemark(); remark = StringUtils.isBlank(remark)? "" : remark; - User user = new User(username, setPassword(username, password), remark, permissionStr); + User user = new User(username, PasswordSupport.hash(password), remark, permissionStr); User resl = syUserRepository.save(user); if(resl == null) { reason = Commons.USER_ADD_SAVE_FAILED; @@ -90,6 +94,10 @@ public AjaxResult add(JSON json, String operator){ public AjaxResult delete(int id, String operator){ Date start = new Date(); String reason = ""; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.USER_DELETE); + } try { syUserRepository.delete(id); return result(operator, start, reason, "1", Commons.USER_DELETE); @@ -107,9 +115,9 @@ public AjaxResult delete(int id, String operator){ public AjaxResult getPermission(String username){ User user = syUserRepository.findByUsername(username); if(user == null) { - return new AjaxResult(400, "failed", "用户未登录"); + return AjaxResult.failed(400, Commons.USER_NOT_LOGIN); } else { - return new AjaxResult(200, "success", user.getPermission()); + return AjaxResult.success(user.getPermission()); } } @@ -119,34 +127,40 @@ public AjaxResult getPermission(String username){ * @param operator * @return */ - public AjaxResult update(JSON json, String operator){ - operator = (String) json.get("operator"); + public AjaxResult update(UserUpdateRequest request){ + String operator = request.getOperator(); Date start = new Date(); String reason = ""; - String id = (String) json.get("id"); - if(StringUtils.isBlank(id)) { - reason = Commons.USER_UPDATE_NOT_FOUNT; + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.USER_UPDATE); + } + Integer id = request.getId(); + if(id == null) { + reason = Commons.USER_UPDATE_NOT_FOUND; return result(operator, start, reason, "0", Commons.USER_UPDATE); } - User uTemp = syUserRepository.findOne(Integer.valueOf(id)); + User uTemp = syUserRepository.findOne(id); if(uTemp == null) { - reason = Commons.USER_UPDATE_NOT_FOUNT; + reason = Commons.USER_UPDATE_NOT_FOUND; return result(operator, start, reason, "0", Commons.USER_UPDATE); } - String password = (String) json.get("password"); + String password = request.getPassword(); if(StringUtils.isBlank(password)) { - password = uTemp.getPassword(); + password = uTemp.getEncodedPassword(); + } else { + password = PasswordSupport.hash(password); } - String permissionStr = (String) json.get("permission"); + String permissionStr = request.getPermission(); if(StringUtils.isBlank(permissionStr)) { permissionStr = uTemp.getPermission(); } - String remark = (String) json.get("remark"); + String remark = request.getRemark(); if(StringUtils.isBlank(remark)) { remark = uTemp.getRemark(); } - User user = new User(uTemp.getUsername(), setPassword(uTemp.getUsername(), password), remark, permissionStr); - user.setId(Integer.valueOf(id)); + User user = new User(uTemp.getUsername(), password, remark, permissionStr); + user.setId(id); User resl = syUserRepository.save(user); if(resl == null) { reason = Commons.USER_ADD_SAVE_FAILED; @@ -161,41 +175,41 @@ public AjaxResult update(JSON json, String operator){ * @param operator * @return */ - public AjaxResult updateAdminPwd(JSON json, String operator){ - operator = (String) json.get("operator"); + public AjaxResult updateAdminPwd(UserAdminPwdUpdateRequest request){ + String operator = request.getOperator(); Date start = new Date(); String reason = ""; - if(!"admin".equals(operator)) { - reason = Commons.USER_UPDATE_PWD_NOT_PERMISSION; - return result(operator, start, reason, "0", Commons.USER_UPDTE_PWD); + reason = permissionGuard.requireAdmin(operator); + if(StringUtils.isNotBlank(reason)) { + return result(operator, start, reason, "0", Commons.USER_UPDATE_PWD); } User uTemp = syUserRepository.findByUsername("admin"); if(uTemp == null) { - reason = Commons.USER_UPDATE_NOT_FOUNT; - return result(operator, start, reason, "0", Commons.USER_UPDTE_PWD); + reason = Commons.USER_UPDATE_NOT_FOUND; + return result(operator, start, reason, "0", Commons.USER_UPDATE_PWD); } - String password = (String) json.get("password"); + String password = request.getPassword(); if(StringUtils.isBlank(password)) { - reason = "超级管理员原密码不能为空"; - return result(operator, start, reason, "0", Commons.USER_UPDTE_PWD); + reason = Commons.USER_ADMIN_OLD_PASSWORD_EMPTY; + return result(operator, start, reason, "0", Commons.USER_UPDATE_PWD); } - if(!password.equals(uTemp.getPassword())) { - reason = "超级管理员原密码输入错误"; - return result(operator, start, reason, "0", Commons.USER_UPDTE_PWD); + if(!PasswordSupport.matches(uTemp.getUsername(), password, uTemp.getEncodedPassword())) { + reason = Commons.USER_ADMIN_OLD_PASSWORD_ERROR; + return result(operator, start, reason, "0", Commons.USER_UPDATE_PWD); } - String newPwd = (String) json.get("newPwd"); + String newPwd = request.getNewPwd(); if(StringUtils.isBlank(newPwd)) { - reason = "超级管理员新密码不能为空"; - return result(operator, start, reason, "0", Commons.USER_UPDTE_PWD); + reason = Commons.USER_ADMIN_NEW_PASSWORD_EMPTY; + return result(operator, start, reason, "0", Commons.USER_UPDATE_PWD); } - User user = new User(uTemp.getUsername(), setPassword(uTemp.getUsername(), newPwd), uTemp.getRemark(), uTemp.getPermission()); + User user = new User(uTemp.getUsername(), PasswordSupport.hash(newPwd), uTemp.getRemark(), uTemp.getPermission()); user.setId(uTemp.getId()); User resl = syUserRepository.save(user); if(resl == null) { reason = Commons.USER_ADD_SAVE_FAILED; - return result(operator, start, reason, "0", Commons.USER_UPDTE_PWD); + return result(operator, start, reason, "0", Commons.USER_UPDATE_PWD); } - return result(operator, start, reason, "1", Commons.USER_UPDTE_PWD); + return result(operator, start, reason, "1", Commons.USER_UPDATE_PWD); } @@ -254,10 +268,6 @@ public Map queryAll(String username, int currentPage, int pageSi * @return */ private AjaxResult result(String operator, Date start, String reason, String status, String operation) { - Logger logger = new Logger(operator, sdf.format(start), sdf.format(new Date()), - StringUtils.isBlank(reason) ? operator + operation + ":成功" : operator + operation + "失败原因:" + reason, - status, operation);// 记录操作日志 - syLoggerService.save(logger); - return new AjaxResult(200, "1".equals(status) ? "success" : "failed", reason); + return OperationResultSupport.build(syLoggerService, operator, start, reason, status, operation); } } diff --git a/src/main/java/com/sy/travel/service/SYWebsocketService.java b/src/main/java/com/sy/travel/service/SYWebsocketService.java index 889263b..67fdac0 100644 --- a/src/main/java/com/sy/travel/service/SYWebsocketService.java +++ b/src/main/java/com/sy/travel/service/SYWebsocketService.java @@ -7,19 +7,22 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import javax.websocket.CloseReason; -import javax.websocket.OnClose; -import javax.websocket.OnError; -import javax.websocket.OnMessage; -import javax.websocket.OnOpen; -import javax.websocket.Session; -import javax.websocket.server.ServerEndpoint; +import jakarta.websocket.CloseReason; +import jakarta.websocket.OnClose; +import jakarta.websocket.OnError; +import jakarta.websocket.OnMessage; +import jakarta.websocket.OnOpen; +import jakarta.websocket.Session; +import jakarta.websocket.server.ServerEndpoint; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.gson.Gson; import com.sy.travel.common.AjaxResult; @@ -27,65 +30,133 @@ @ServerEndpoint("/sy/refresh") @Component public class SYWebsocketService { + private static final Logger LOGGER = LoggerFactory.getLogger(SYWebsocketService.class); private static SYProjectService sYProjectService; private static SYTeamService syTeamService; private static SYProductService syProductService; private ScheduledExecutorService scheduledService; + private ScheduledFuture monitorTask; + private boolean projectServiceUnavailableLogged; + private boolean teamServiceUnavailableLogged; + private boolean productServiceUnavailableLogged; @Autowired public void get(SYProjectService sYProjectService, SYTeamService syTeamService, SYProductService syProductService) { SYWebsocketService.sYProjectService = sYProjectService; SYWebsocketService.syTeamService = syTeamService; SYWebsocketService.syProductService = syProductService; + projectServiceUnavailableLogged = false; + teamServiceUnavailableLogged = false; + productServiceUnavailableLogged = false; } @OnOpen public void onOpen(Session session){ - scheduledService = Executors.newSingleThreadScheduledExecutor(); + if (scheduledService == null || scheduledService.isShutdown()) { + scheduledService = Executors.newSingleThreadScheduledExecutor(); + } } @OnMessage public void onMessage(String message, Session session) { - scheduledService.scheduleAtFixedRate(new Runnable() { + if (scheduledService == null || scheduledService.isShutdown()) { + scheduledService = Executors.newSingleThreadScheduledExecutor(); + } + cancelMonitorTask(); + int intervalMillis = resolveIntervalMillis(message); + monitorTask = scheduledService.scheduleAtFixedRate(new Runnable() { @Override public void run() { + if (session == null || !session.isOpen()) { + cancelMonitorTask(); + return; + } try { Gson gson = new Gson(); session.getBasicRemote().sendText(gson.toJson(getMonitor())); - } catch (IOException e) { - e.printStackTrace(); + } catch (IOException | IllegalStateException e) { + LOGGER.warn("websocket monitor push failed", e); + cancelMonitorTask(); } } - }, 0, Integer.valueOf(message), TimeUnit.MILLISECONDS); + }, 0, intervalMillis, TimeUnit.MILLISECONDS); } @OnClose public void onClose(Session session, CloseReason closeReason) { + stopMonitor(); } @OnError public void onError(Throwable t) { - + stopMonitor(); + } + + private void stopMonitor() { + cancelMonitorTask(); + if (scheduledService != null) { + scheduledService.shutdownNow(); + scheduledService = null; + } + monitorTask = null; + } + + private void cancelMonitorTask() { + if (monitorTask != null) { + monitorTask.cancel(true); + } + } + + int resolveIntervalMillis(String message) { + try { + int value = Integer.parseInt(message); + return Math.max(100, value); + } catch (NumberFormatException ex) { + return 1000; + } } public AjaxResult> getMonitor(){ Map resultMap = new HashMap<>(); - Map projectMap = SYWebsocketService.sYProjectService.queryAll("",1, Integer.MAX_VALUE); - if(projectMap == null) { + if (SYWebsocketService.sYProjectService == null) { + logServiceUnavailableOnce("project", projectServiceUnavailableLogged); + projectServiceUnavailableLogged = true; resultMap.put("project", new ArrayList<>()); } else { - resultMap.put("project", (List>)projectMap.get("documents")); + Map projectMap = SYWebsocketService.sYProjectService.queryAll("",1, Integer.MAX_VALUE); + resultMap.put("project", extractDocuments(projectMap)); } - Map teamMap = SYWebsocketService.syTeamService.findAll("",1,Integer.MAX_VALUE); - if(projectMap == null) { + if (SYWebsocketService.syTeamService == null) { + logServiceUnavailableOnce("team", teamServiceUnavailableLogged); + teamServiceUnavailableLogged = true; resultMap.put("team", new ArrayList<>()); } else { - resultMap.put("team", (List>)teamMap.get("documents")); + Map teamMap = SYWebsocketService.syTeamService.findAll("",1,Integer.MAX_VALUE); + resultMap.put("team", extractDocuments(teamMap)); } - Map productMap = SYWebsocketService.syProductService.findAll("", 1, Integer.MAX_VALUE); - if(projectMap == null) { + if (SYWebsocketService.syProductService == null) { + logServiceUnavailableOnce("product", productServiceUnavailableLogged); + productServiceUnavailableLogged = true; resultMap.put("product", new ArrayList<>()); } else { - resultMap.put("product", (List>)productMap.get("documents")); + Map productMap = SYWebsocketService.syProductService.findAll("", 1, Integer.MAX_VALUE); + resultMap.put("product", extractDocuments(productMap)); + } + return AjaxResult.success(resultMap); + } + + private List> extractDocuments(Map source) { + if (source == null) { + return new ArrayList<>(); + } + Object documents = source.get("documents"); + if (documents instanceof List) { + return (List>) documents; + } + return new ArrayList<>(); + } + + private void logServiceUnavailableOnce(String name, boolean logged) { + if (!logged) { + LOGGER.warn("{} service is unavailable, fallback to empty monitor data", name); } - return new AjaxResult>(200, "success", resultMap); } } diff --git a/src/main/java/com/sy/travel/service/support/OperationResultSupport.java b/src/main/java/com/sy/travel/service/support/OperationResultSupport.java new file mode 100644 index 0000000..d98001c --- /dev/null +++ b/src/main/java/com/sy/travel/service/support/OperationResultSupport.java @@ -0,0 +1,21 @@ +package com.sy.travel.service.support; + +import java.util.Date; + +import com.sy.travel.common.AjaxResult; +import com.sy.travel.common.ResultBuilder; +import com.sy.travel.entity.Logger; +import com.sy.travel.service.SYLoggerService; + +public final class OperationResultSupport { + + private OperationResultSupport() { + } + + public static AjaxResult build(SYLoggerService loggerService, String operator, Date start, String reason, + String status, String operation) { + Logger logger = ResultBuilder.operationLogger(operator, start, reason, status, operation); + loggerService.save(logger); + return ResultBuilder.byStatus(status, reason); + } +} diff --git a/src/main/java/com/sy/travel/service/support/PermissionGuard.java b/src/main/java/com/sy/travel/service/support/PermissionGuard.java new file mode 100644 index 0000000..147f49a --- /dev/null +++ b/src/main/java/com/sy/travel/service/support/PermissionGuard.java @@ -0,0 +1,23 @@ +package com.sy.travel.service.support; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.sy.travel.common.Commons; +import com.sy.travel.dao.SYUserRepository; +import com.sy.travel.entity.User; + +@Component +public class PermissionGuard { + + @Autowired + private SYUserRepository syUserRepository; + + public String requireAdmin(String operator) { + User user = syUserRepository.findByUsername(operator); + if (user == null) { + return Commons.USER_OPERATOR_NOT_EXISTS; + } + return "0".equals(user.getPermission()) ? "" : Commons.USER_OPERATOR_NOT_PERMISSION; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 844d2ef..c32d13d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,12 +2,12 @@ spring: profiles: active: dev datasource: - driver-class-name: com.mysql.jdbc.Driver - url: jdbc:mysql://localhost:3306/sytravel + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/sytravel?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC username: root password: jpa: hibernate: ddl-auto: update show-sql: false - \ No newline at end of file + diff --git a/src/test/java/com/sy/travel/common/PasswordSupportTest.java b/src/test/java/com/sy/travel/common/PasswordSupportTest.java new file mode 100644 index 0000000..f689a54 --- /dev/null +++ b/src/test/java/com/sy/travel/common/PasswordSupportTest.java @@ -0,0 +1,24 @@ +package com.sy.travel.common; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class PasswordSupportTest { + + @Test + public void shouldMatchBcryptHash() { + String encoded = PasswordSupport.hash("123456"); + assertTrue(PasswordSupport.isBcryptHash(encoded)); + assertTrue(PasswordSupport.matches("admin", "123456", encoded)); + assertFalse(PasswordSupport.matches("admin", "bad", encoded)); + } + + @Test + public void shouldMatchLegacyBase64() { + String encoded = PasswordSupport.legacyEncode("admin", "123456"); + assertTrue(PasswordSupport.matches("admin", "123456", encoded)); + assertFalse(PasswordSupport.matches("admin", "bad", encoded)); + } +} diff --git a/src/test/java/com/sy/travel/handler/GlobalExceptionHandlerTest.java b/src/test/java/com/sy/travel/handler/GlobalExceptionHandlerTest.java new file mode 100644 index 0000000..a43efb3 --- /dev/null +++ b/src/test/java/com/sy/travel/handler/GlobalExceptionHandlerTest.java @@ -0,0 +1,21 @@ +package com.sy.travel.handler; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.sy.travel.common.AjaxResult; +import com.sy.travel.common.ApiMessages; +import com.sy.travel.common.ErrorCodes; + +public class GlobalExceptionHandlerTest { + + @Test + public void handleUnexpectedShouldHideInternalExceptionMessage() { + GlobalExceptionHandler handler = new GlobalExceptionHandler(); + AjaxResult result = handler.handleUnexpected(new RuntimeException("db-password-leak")); + assertEquals(500, result.getCode()); + assertEquals(ErrorCodes.INTERNAL_ERROR, result.getMsg()); + assertEquals(ApiMessages.INTERNAL_ERROR_MESSAGE, result.getData()); + } +} diff --git a/src/test/java/com/sy/travel/rest/SYClassesRestValidationTest.java b/src/test/java/com/sy/travel/rest/SYClassesRestValidationTest.java new file mode 100644 index 0000000..5081c78 --- /dev/null +++ b/src/test/java/com/sy/travel/rest/SYClassesRestValidationTest.java @@ -0,0 +1,72 @@ +package com.sy.travel.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.sy.travel.service.SYClassesService; + +@RunWith(SpringRunner.class) +@WebMvcTest(SYClassesRest.class) +public class SYClassesRestValidationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SYClassesService syClassesService; + + @Test + public void addShouldReturnBadRequestWhenNameMissing() throws Exception { + String body = "{\"operator\":\"admin\",\"sortId\":\"1\"}"; + mockMvc.perform(post("/sy/classes/add").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void updateShouldReturnBadRequestWhenIdMissing() throws Exception { + String body = "{\"operator\":\"admin\"}"; + mockMvc.perform(post("/sy/classes/update").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryAllShouldReturnBadRequestWhenCurrentPageInvalid() throws Exception { + mockMvc.perform(get("/sy/classes/all").param("currentPage", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryAllShouldReturnBadRequestWhenPageSizeInvalid() throws Exception { + mockMvc.perform(get("/sy/classes/all").param("pageSize", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/classes/delete").param("id", "0").param("operator", "admin")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteShouldReturnBadRequestWhenOperatorBlank() throws Exception { + mockMvc.perform(get("/sy/classes/delete").param("id", "1").param("operator", " ")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void infoShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/classes/info").param("id", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } +} diff --git a/src/test/java/com/sy/travel/rest/SYLoggerRestValidationTest.java b/src/test/java/com/sy/travel/rest/SYLoggerRestValidationTest.java new file mode 100644 index 0000000..650fc58 --- /dev/null +++ b/src/test/java/com/sy/travel/rest/SYLoggerRestValidationTest.java @@ -0,0 +1,38 @@ +package com.sy.travel.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.sy.travel.service.SYLoggerService; + +@RunWith(SpringRunner.class) +@WebMvcTest(SYLoggerRest.class) +public class SYLoggerRestValidationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SYLoggerService syLoggerService; + + @Test + public void queryAllShouldReturnBadRequestWhenCurrentPageInvalid() throws Exception { + mockMvc.perform(get("/sy/logger/all").param("currentPage", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryAllShouldReturnBadRequestWhenPageSizeInvalid() throws Exception { + mockMvc.perform(get("/sy/logger/all").param("pageSize", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } +} diff --git a/src/test/java/com/sy/travel/rest/SYLoginRestValidationTest.java b/src/test/java/com/sy/travel/rest/SYLoginRestValidationTest.java new file mode 100644 index 0000000..f89ade2 --- /dev/null +++ b/src/test/java/com/sy/travel/rest/SYLoginRestValidationTest.java @@ -0,0 +1,43 @@ +package com.sy.travel.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.sy.travel.service.SYLoginService; + +@RunWith(SpringRunner.class) +@WebMvcTest(SYLoginRest.class) +public class SYLoginRestValidationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SYLoginService syLoginService; + + @Test + public void loginShouldReturnBadRequestWhenUsernameMissing() throws Exception { + String body = "{\"password\":\"123456\"}"; + mockMvc.perform(post("/sy/login/check").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void loginShouldReturnBadRequestWhenPasswordMissing() throws Exception { + String body = "{\"username\":\"admin\"}"; + mockMvc.perform(post("/sy/login/check").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)); + } +} diff --git a/src/test/java/com/sy/travel/rest/SYProductRestValidationTest.java b/src/test/java/com/sy/travel/rest/SYProductRestValidationTest.java new file mode 100644 index 0000000..3f08b41 --- /dev/null +++ b/src/test/java/com/sy/travel/rest/SYProductRestValidationTest.java @@ -0,0 +1,78 @@ +package com.sy.travel.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.sy.travel.service.SYProductService; + +@RunWith(SpringRunner.class) +@WebMvcTest(SYProductRest.class) +public class SYProductRestValidationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SYProductService syProductService; + + @Test + public void addShouldReturnBadRequestWhenCodeMissing() throws Exception { + String body = "{\"operator\":\"admin\",\"name\":\"p\",\"teamId\":\"1\",\"onlineDate\":\"2026-01-01 00:00:00\",\"offlineDate\":\"2026-01-02 00:00:00\",\"classId\":\"1\",\"quantity\":1,\"minQty\":1,\"soldQty\":0,\"price\":1,\"nights\":1,\"status\":\"1\"}"; + mockMvc.perform(post("/sy/product/add").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void updateShouldReturnBadRequestWhenIdMissing() throws Exception { + String body = "{\"operator\":\"admin\",\"quantity\":1,\"minQty\":1,\"soldQty\":0,\"price\":1,\"nights\":1,\"status\":\"1\"}"; + mockMvc.perform(post("/sy/product/update").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryAllShouldReturnBadRequestWhenCurrentPageInvalid() throws Exception { + mockMvc.perform(get("/sy/product/all").param("currentPage", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryAllShouldReturnBadRequestWhenPageSizeInvalid() throws Exception { + mockMvc.perform(get("/sy/product/all").param("pageSize", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/product/delete").param("id", "0").param("operator", "admin")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteShouldReturnBadRequestWhenOperatorBlank() throws Exception { + mockMvc.perform(get("/sy/product/delete").param("id", "1").param("operator", " ")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryByTeamIdShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/product/teamid").param("id", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryByClassIdShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/product/classid").param("id", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } +} diff --git a/src/test/java/com/sy/travel/rest/SYProjectRestValidationTest.java b/src/test/java/com/sy/travel/rest/SYProjectRestValidationTest.java new file mode 100644 index 0000000..9dd0921 --- /dev/null +++ b/src/test/java/com/sy/travel/rest/SYProjectRestValidationTest.java @@ -0,0 +1,72 @@ +package com.sy.travel.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.sy.travel.service.SYProjectService; + +@RunWith(SpringRunner.class) +@WebMvcTest(SYProjectRest.class) +public class SYProjectRestValidationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SYProjectService syProjectService; + + @Test + public void addShouldReturnBadRequestWhenCodeMissing() throws Exception { + String body = "{\"operator\":\"admin\",\"name\":\"p1\",\"beginDate\":\"2026-01-01 00:00:00\",\"endDate\":\"2026-01-02 00:00:00\",\"valid\":\"1\"}"; + mockMvc.perform(post("/sy/project/add").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void updateShouldReturnBadRequestWhenIdMissing() throws Exception { + String body = "{\"operator\":\"admin\"}"; + mockMvc.perform(post("/sy/project/update").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void allShouldReturnBadRequestWhenCurrentPageInvalid() throws Exception { + mockMvc.perform(get("/sy/project/all").param("currentPage", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void allShouldReturnBadRequestWhenPageSizeInvalid() throws Exception { + mockMvc.perform(get("/sy/project/all").param("pageSize", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/project/delete").param("id", "0").param("operator", "admin")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteShouldReturnBadRequestWhenOperatorBlank() throws Exception { + mockMvc.perform(get("/sy/project/delete").param("id", "1").param("operator", " ")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void infoShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/project/info").param("id", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } +} diff --git a/src/test/java/com/sy/travel/rest/SYRoleRestValidationTest.java b/src/test/java/com/sy/travel/rest/SYRoleRestValidationTest.java new file mode 100644 index 0000000..0cb16fc --- /dev/null +++ b/src/test/java/com/sy/travel/rest/SYRoleRestValidationTest.java @@ -0,0 +1,66 @@ +package com.sy.travel.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.sy.travel.service.SYRoleService; + +@RunWith(SpringRunner.class) +@WebMvcTest(SYRoleRest.class) +public class SYRoleRestValidationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SYRoleService syRoleService; + + @Test + public void addShouldReturnBadRequestWhenNameMissing() throws Exception { + String body = "{\"operator\":\"admin\",\"teamId\":\"1\",\"role\":\"dev\",\"mobile\":\"13800000000\"}"; + mockMvc.perform(post("/sy/role/add").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void updateShouldReturnBadRequestWhenIdMissing() throws Exception { + String body = "{\"operator\":\"admin\"}"; + mockMvc.perform(post("/sy/role/update").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryAllShouldReturnBadRequestWhenCurrentPageInvalid() throws Exception { + mockMvc.perform(get("/sy/role/all").param("currentPage", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryAllShouldReturnBadRequestWhenPageSizeInvalid() throws Exception { + mockMvc.perform(get("/sy/role/all").param("pageSize", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/role/delete").param("id", "0").param("operator", "admin")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteShouldReturnBadRequestWhenOperatorBlank() throws Exception { + mockMvc.perform(get("/sy/role/delete").param("id", "1").param("operator", " ")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } +} diff --git a/src/test/java/com/sy/travel/rest/SYTeamRestValidationTest.java b/src/test/java/com/sy/travel/rest/SYTeamRestValidationTest.java new file mode 100644 index 0000000..4065dd2 --- /dev/null +++ b/src/test/java/com/sy/travel/rest/SYTeamRestValidationTest.java @@ -0,0 +1,90 @@ +package com.sy.travel.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.sy.travel.service.SYTeamService; + +@RunWith(SpringRunner.class) +@WebMvcTest(SYTeamRest.class) +public class SYTeamRestValidationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SYTeamService syTeamService; + + @Test + public void addShouldReturnBadRequestWhenNameMissing() throws Exception { + String body = "{\"operator\":\"admin\",\"projectId\":\"1\",\"valid\":\"1\"}"; + mockMvc.perform(post("/sy/team/add").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void updateShouldReturnBadRequestWhenIdMissing() throws Exception { + String body = "{\"operator\":\"admin\"}"; + mockMvc.perform(post("/sy/team/update").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void findAllShouldReturnBadRequestWhenCurrentPageInvalid() throws Exception { + mockMvc.perform(get("/sy/team/all").param("currentPage", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void findAllShouldReturnBadRequestWhenPageSizeInvalid() throws Exception { + mockMvc.perform(get("/sy/team/all").param("pageSize", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteByTeamIdShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/team/delete").param("id", "0").param("operator", "admin")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteByTeamIdShouldReturnBadRequestWhenOperatorBlank() throws Exception { + mockMvc.perform(get("/sy/team/delete").param("id", "1").param("operator", " ")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void findAllByProjectIdShouldReturnBadRequestWhenProjectIdBlank() throws Exception { + mockMvc.perform(get("/sy/team/allbypid").param("projectId", " ")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void findAllByProjectIdShouldReturnBadRequestWhenProjectIdMissing() throws Exception { + mockMvc.perform(get("/sy/team/allbypid")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void infoShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/team/info").param("id", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void infoShouldReturnBadRequestWhenIdNotNumber() throws Exception { + mockMvc.perform(get("/sy/team/info").param("id", "abc")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } +} diff --git a/src/test/java/com/sy/travel/rest/SYUserRestValidationTest.java b/src/test/java/com/sy/travel/rest/SYUserRestValidationTest.java new file mode 100644 index 0000000..3215dfb --- /dev/null +++ b/src/test/java/com/sy/travel/rest/SYUserRestValidationTest.java @@ -0,0 +1,81 @@ +package com.sy.travel.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.sy.travel.service.SYUserService; + +@RunWith(SpringRunner.class) +@WebMvcTest(SYUserRest.class) +public class SYUserRestValidationTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private SYUserService syUserService; + + @Test + public void addShouldReturnBadRequestWhenUsernameMissing() throws Exception { + String body = "{\"operator\":\"admin\",\"password\":\"123456\",\"permission\":\"0\"}"; + mockMvc.perform(post("/sy/user/add").contentType(MediaType.APPLICATION_JSON).content(body)).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)) + .andExpect(jsonPath("$.msg").value("VALIDATION_ERROR")); + } + + @Test + public void updateShouldReturnBadRequestWhenIdMissing() throws Exception { + String body = "{\"operator\":\"admin\"}"; + mockMvc.perform(post("/sy/user/update").contentType(MediaType.APPLICATION_JSON).content(body)).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void addShouldReturnBadRequestWhenPermissionOutOfRange() throws Exception { + String body = "{\"operator\":\"admin\",\"username\":\"u1\",\"password\":\"123456\",\"permission\":\"9\"}"; + mockMvc.perform(post("/sy/user/add").contentType(MediaType.APPLICATION_JSON).content(body)).andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void adminPwdShouldReturnBadRequestWhenNewPwdMissing() throws Exception { + String body = "{\"operator\":\"admin\",\"password\":\"old\"}"; + mockMvc.perform(post("/sy/user/adminpwd").contentType(MediaType.APPLICATION_JSON).content(body)) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryAllShouldReturnBadRequestWhenCurrentPageInvalid() throws Exception { + mockMvc.perform(get("/sy/user/all").param("currentPage", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void queryAllShouldReturnBadRequestWhenPageSizeInvalid() throws Exception { + mockMvc.perform(get("/sy/user/all").param("pageSize", "0")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteShouldReturnBadRequestWhenIdInvalid() throws Exception { + mockMvc.perform(get("/sy/user/delete").param("id", "0").param("operator", "admin")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } + + @Test + public void deleteShouldReturnBadRequestWhenOperatorBlank() throws Exception { + mockMvc.perform(get("/sy/user/delete").param("id", "1").param("operator", " ")) + .andExpect(status().isBadRequest()).andExpect(jsonPath("$.code").value(400)); + } +} diff --git a/src/test/java/com/sy/travel/service/SYLoginServiceTest.java b/src/test/java/com/sy/travel/service/SYLoginServiceTest.java new file mode 100644 index 0000000..a19d535 --- /dev/null +++ b/src/test/java/com/sy/travel/service/SYLoginServiceTest.java @@ -0,0 +1,56 @@ +package com.sy.travel.service; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.sy.travel.common.AjaxResult; +import com.sy.travel.common.PasswordSupport; +import com.sy.travel.dao.SYUserRepository; +import com.sy.travel.dto.login.LoginCheckRequest; +import com.sy.travel.entity.User; + +@RunWith(MockitoJUnitRunner.class) +public class SYLoginServiceTest { + + @Mock + private SYUserRepository syUserRepository; + + @InjectMocks + private SYLoginService syLoginService; + + @Test + public void shouldLoginWithLegacyPasswordAndUpgradeToBcrypt() { + User user = new User("admin", PasswordSupport.legacyEncode("admin", "123456"), "", "0"); + when(syUserRepository.findByUsername("admin")).thenReturn(user); + + LoginCheckRequest request = new LoginCheckRequest(); + request.setUsername("admin"); + request.setPassword("123456"); + AjaxResult result = syLoginService.loginCheck(request); + + assertEquals("success", result.getMsg()); + verify(syUserRepository).save(user); + } + + @Test + public void shouldLoginWithBcryptWithoutReSaving() { + User user = new User("admin", PasswordSupport.hash("123456"), "", "0"); + when(syUserRepository.findByUsername("admin")).thenReturn(user); + + LoginCheckRequest request = new LoginCheckRequest(); + request.setUsername("admin"); + request.setPassword("123456"); + AjaxResult result = syLoginService.loginCheck(request); + + assertEquals("success", result.getMsg()); + verify(syUserRepository, never()).save(user); + } +} diff --git a/src/test/java/com/sy/travel/service/SYUserServiceTest.java b/src/test/java/com/sy/travel/service/SYUserServiceTest.java new file mode 100644 index 0000000..a039465 --- /dev/null +++ b/src/test/java/com/sy/travel/service/SYUserServiceTest.java @@ -0,0 +1,62 @@ +package com.sy.travel.service; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.sy.travel.common.AjaxResult; +import com.sy.travel.dao.SYUserRepository; +import com.sy.travel.dto.user.UserCreateRequest; +import com.sy.travel.dto.user.UserUpdateRequest; +import com.sy.travel.entity.User; +import com.sy.travel.service.support.PermissionGuard; + +@RunWith(MockitoJUnitRunner.class) +public class SYUserServiceTest { + + @Mock + private SYUserRepository syUserRepository; + + @Mock + private SYLoggerService syLoggerService; + @Mock + private PermissionGuard permissionGuard; + + @InjectMocks + private SYUserService syUserService; + + @Test + public void addShouldFailWhenUsernameBlank() { + UserCreateRequest request = new UserCreateRequest(); + request.setOperator("admin"); + request.setUsername(" "); + request.setPassword("123456"); + request.setPermission("0"); + when(permissionGuard.requireAdmin("admin")).thenReturn(""); + + AjaxResult result = syUserService.add(request); + + assertEquals("failed", result.getMsg()); + verify(syUserRepository, never()).save(org.mockito.ArgumentMatchers.any(User.class)); + } + + @Test + public void updateShouldFailWhenUserNotFound() { + UserUpdateRequest request = new UserUpdateRequest(); + request.setOperator("admin"); + request.setId(99); + when(permissionGuard.requireAdmin("admin")).thenReturn(""); + when(syUserRepository.findOne(99)).thenReturn(null); + + AjaxResult result = syUserService.update(request); + + assertEquals("failed", result.getMsg()); + } +} diff --git a/src/test/java/com/sy/travel/service/SYWebsocketServiceTest.java b/src/test/java/com/sy/travel/service/SYWebsocketServiceTest.java new file mode 100644 index 0000000..e92dcfb --- /dev/null +++ b/src/test/java/com/sy/travel/service/SYWebsocketServiceTest.java @@ -0,0 +1,196 @@ +package com.sy.travel.service; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +import jakarta.websocket.Session; + +import com.sy.travel.common.AjaxResult; + +public class SYWebsocketServiceTest { + + @Test + public void resolveIntervalMillisShouldUseDefaultWhenInvalid() { + SYWebsocketService service = new SYWebsocketService(); + assertEquals(1000, service.resolveIntervalMillis("abc")); + } + + @Test + public void resolveIntervalMillisShouldClampTooSmallValues() { + SYWebsocketService service = new SYWebsocketService(); + assertEquals(100, service.resolveIntervalMillis("1")); + } + + @Test + public void getMonitorShouldHandleNullTeamAndProductMapsIndependently() { + SYProjectService projectService = mock(SYProjectService.class); + SYTeamService teamService = mock(SYTeamService.class); + SYProductService productService = mock(SYProductService.class); + + Map projectMap = new HashMap<>(); + List> projectDocs = new ArrayList<>(); + projectMap.put("documents", projectDocs); + + when(projectService.queryAll("", 1, Integer.MAX_VALUE)).thenReturn(projectMap); + when(teamService.findAll("", 1, Integer.MAX_VALUE)).thenReturn(null); + when(productService.findAll("", 1, Integer.MAX_VALUE)).thenReturn(null); + + SYWebsocketService service = new SYWebsocketService(); + service.get(projectService, teamService, productService); + + AjaxResult> result = service.getMonitor(); + assertEquals(projectDocs, result.getData().get("project")); + assertTrue(result.getData().get("team") instanceof List); + assertTrue(result.getData().get("product") instanceof List); + } + + @Test + public void getMonitorShouldReturnEmptyListsWhenServicesUnavailable() throws Exception { + SYWebsocketService service = new SYWebsocketService(); + Object oldProjectService = getStaticField(SYWebsocketService.class, "sYProjectService"); + Object oldTeamService = getStaticField(SYWebsocketService.class, "syTeamService"); + Object oldProductService = getStaticField(SYWebsocketService.class, "syProductService"); + try { + setStaticField(SYWebsocketService.class, "sYProjectService", null); + setStaticField(SYWebsocketService.class, "syTeamService", null); + setStaticField(SYWebsocketService.class, "syProductService", null); + + AjaxResult> result = service.getMonitor(); + assertTrue(result.getData().get("project") instanceof List); + assertTrue(((List) result.getData().get("project")).isEmpty()); + assertTrue(result.getData().get("team") instanceof List); + assertTrue(((List) result.getData().get("team")).isEmpty()); + assertTrue(result.getData().get("product") instanceof List); + assertTrue(((List) result.getData().get("product")).isEmpty()); + } finally { + setStaticField(SYWebsocketService.class, "sYProjectService", oldProjectService); + setStaticField(SYWebsocketService.class, "syTeamService", oldTeamService); + setStaticField(SYWebsocketService.class, "syProductService", oldProductService); + } + } + + @Test + public void getMonitorShouldReturnEmptyListsWhenDocumentsMissing() { + SYProjectService projectService = mock(SYProjectService.class); + SYTeamService teamService = mock(SYTeamService.class); + SYProductService productService = mock(SYProductService.class); + + when(projectService.queryAll("", 1, Integer.MAX_VALUE)).thenReturn(new HashMap()); + when(teamService.findAll("", 1, Integer.MAX_VALUE)).thenReturn(new HashMap()); + when(productService.findAll("", 1, Integer.MAX_VALUE)).thenReturn(new HashMap()); + + SYWebsocketService service = new SYWebsocketService(); + service.get(projectService, teamService, productService); + + AjaxResult> result = service.getMonitor(); + assertTrue(((List) result.getData().get("project")).isEmpty()); + assertTrue(((List) result.getData().get("team")).isEmpty()); + assertTrue(((List) result.getData().get("product")).isEmpty()); + } + + @Test + public void getMonitorShouldReturnEmptyWhenDocumentsTypeIsInvalid() { + SYProjectService projectService = mock(SYProjectService.class); + SYTeamService teamService = mock(SYTeamService.class); + SYProductService productService = mock(SYProductService.class); + + Map invalid = new HashMap<>(); + invalid.put("documents", "invalid"); + when(projectService.queryAll("", 1, Integer.MAX_VALUE)).thenReturn(invalid); + when(teamService.findAll("", 1, Integer.MAX_VALUE)).thenReturn(invalid); + when(productService.findAll("", 1, Integer.MAX_VALUE)).thenReturn(invalid); + + SYWebsocketService service = new SYWebsocketService(); + service.get(projectService, teamService, productService); + + AjaxResult> result = service.getMonitor(); + assertTrue(((List) result.getData().get("project")).isEmpty()); + assertTrue(((List) result.getData().get("team")).isEmpty()); + assertTrue(((List) result.getData().get("product")).isEmpty()); + } + + @Test + public void onCloseShouldCancelTaskAndShutdownScheduler() throws Exception { + SYWebsocketService service = new SYWebsocketService(); + ScheduledFuture future = mock(ScheduledFuture.class); + ScheduledExecutorService executorService = mock(ScheduledExecutorService.class); + setField(service, "monitorTask", future); + setField(service, "scheduledService", executorService); + + service.onClose(null, null); + + verify(future).cancel(true); + verify(executorService).shutdownNow(); + assertEquals(null, getField(service, "monitorTask")); + assertEquals(null, getField(service, "scheduledService")); + } + + @Test + public void onMessageShouldCancelTaskWhenSessionClosed() throws Exception { + SYWebsocketService service = new SYWebsocketService(); + ScheduledExecutorService executorService = mock(ScheduledExecutorService.class); + ScheduledFuture previousTask = mock(ScheduledFuture.class); + ScheduledFuture newTask = mock(ScheduledFuture.class); + Session session = mock(Session.class); + + when(session.isOpen()).thenReturn(false); + when(executorService.scheduleAtFixedRate(org.mockito.ArgumentMatchers.any(Runnable.class), anyLong(), anyLong(), + eq(TimeUnit.MILLISECONDS))).thenReturn(newTask); + + setField(service, "scheduledService", executorService); + setField(service, "monitorTask", previousTask); + + service.onMessage("1000", session); + + verify(previousTask).cancel(true); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Runnable.class); + verify(executorService).scheduleAtFixedRate(captor.capture(), eq(0L), eq(1000L), eq(TimeUnit.MILLISECONDS)); + captor.getValue().run(); + + verify(newTask).cancel(true); + verify(session, never()).getBasicRemote(); + } + + private void setField(Object target, String fieldName, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } + + private Object getField(Object target, String fieldName) throws Exception { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(target); + } + + private void setStaticField(Class type, String fieldName, Object value) throws Exception { + Field field = type.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(null, value); + } + + private Object getStaticField(Class type, String fieldName) throws Exception { + Field field = type.getDeclaredField(fieldName); + field.setAccessible(true); + return field.get(null); + } +} diff --git a/src/test/java/com/sy/travel/service/support/PermissionGuardTest.java b/src/test/java/com/sy/travel/service/support/PermissionGuardTest.java new file mode 100644 index 0000000..0c62919 --- /dev/null +++ b/src/test/java/com/sy/travel/service/support/PermissionGuardTest.java @@ -0,0 +1,38 @@ +package com.sy.travel.service.support; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.sy.travel.common.Commons; +import com.sy.travel.dao.SYUserRepository; +import com.sy.travel.entity.User; + +@RunWith(MockitoJUnitRunner.class) +public class PermissionGuardTest { + + @Mock + private SYUserRepository syUserRepository; + + @InjectMocks + private PermissionGuard permissionGuard; + + @Test + public void shouldReturnEmptyWhenAdmin() { + User user = new User("admin", "pwd", "", "0"); + when(syUserRepository.findByUsername("admin")).thenReturn(user); + assertEquals("", permissionGuard.requireAdmin("admin")); + } + + @Test + public void shouldReturnNoPermissionWhenNotAdmin() { + User user = new User("pm", "pwd", "", "3"); + when(syUserRepository.findByUsername("pm")).thenReturn(user); + assertEquals(Commons.USER_OPERATOR_NOT_PERMISSION, permissionGuard.requireAdmin("pm")); + } +}