Compare commits

...

4 Commits

Author SHA1 Message Date
gin 98eae63435 feat: show collaboration record creators 2026-05-27 20:29:27 +08:00
gin d26a012edb chore: update web layout 2026-05-27 15:29:41 +08:00
gin ebccb7283f refactor: remove project settings panel 2026-05-26 21:08:58 +08:00
gin 2a702fa6a9 feat: app功能基本实现 2026-05-26 11:54:24 +08:00
249 changed files with 7071 additions and 7436 deletions
+12
View File
@@ -0,0 +1,12 @@
MYSQL_ROOT_PASSWORD=root123
MYSQL_DATABASE=todo_agileboot_pure
MYSQL_APP_USERNAME=todo_app
MYSQL_APP_PASSWORD=todo_app123
MYSQL_PORT=3306
REDIS_PASSWORD=redis123
REDIS_PORT=6379
APP_PORT=8080
WEB_PORT=8081
JAVA_OPTS=-Xms256m -Xmx512m
+2 -1
View File
@@ -1,5 +1,6 @@
# Common # Common
.DS_Store .DS_Store
/.env
.idea/ .idea/
*.iws *.iws
*.iml *.iml
@@ -68,4 +69,4 @@
!/backend/*/build/*.xml !/backend/*/build/*.xml
# Backend local configuration # Backend local configuration
/backend/agileboot-admin/src/main/resources/application-prod.yml /backend/.env
+185 -80
View File
@@ -1,127 +1,232 @@
# Simple Template # Simple Todo
## Quick Start `simple-todo` 是一个前后端分离的待办/后台管理模板项目。后端基于 AgileBoot 的 Spring Boot 分层架构改造,前端包含 Web 管理端和 Taro 多端应用,根目录提供 Docker Compose 用于本地依赖启动和生产部署。
This project contains: ## 项目架构
- `backend`: Spring Boot admin API. ### 原仓库地址
- `frontend/web`: Vite + Vue 3 admin frontend.
- `frontend/app`: Taro + Vue 3 app frontend.
The local development flow starts MySQL and Redis with Docker, then runs the 当前项目是在以下开源项目基础上改造:
backend and web frontend locally.
### Requirements - 后端原仓库:<https://github.com/valarchie/AgileBoot-Back-End>
- Web 前端原仓库:<https://github.com/valarchie/agileboot-front-end-pure>
- Web UI 上游项目:<https://github.com/pure-admin/vue-pure-admin>
### 目录结构
```text
simple-todo
├── backend # Spring Boot 后端
│ ├── agileboot-admin # 后台管理接口启动模块
│ ├── agileboot-api # 开放接口模块
│ ├── agileboot-common # 通用工具与基础能力
│ ├── agileboot-domain # 业务领域模块
│ ├── agileboot-infrastructure # 基础设施、配置、集成能力
│ └── sql # 初始化 SQL
├── frontend
│ ├── web # Vite + Vue 3 Web 管理端
│ └── app # Taro + Vue 3 多端应用
├── docs # 项目开发约定
├── docker-compose.yml # MySQL、Redis、后端、Web 编排
└── .env.example # Docker Compose 环境变量示例
```
## 基础环境
- JDK 8+ - JDK 8+
- Docker Desktop or Docker Engine with Compose - Maven 3.8+,也可以直接使用 `backend/mvnw``backend/mvnw.cmd`
- Node.js - Node.js 22+,推荐配合 Corepack 使用 pnpm
- pnpm - pnpmDockerfile 中使用 `pnpm@11.1.3`
- Docker Desktop 或 Docker Engine + Docker Compose
### Start Infrastructure ## 开发启动步骤
The development backend is configured to use the same defaults as ### 1. 准备环境变量
`backend/docker-compose.yml`:
- MySQL: `localhost:3306`, user `root`, password `root123` Docker Compose 在项目根目录执行时会自动读取 `.env`。可以从示例文件复制:
- Redis: `localhost:6379`, password `redis123`
- Database name: `agileboot_pure`
You can start MySQL and Redis manually:
```bash ```bash
cd backend cp .env.example .env
docker compose up -d
``` ```
The compose configuration mounts `backend/sql/agileboot.sql` into the MySQL Windows PowerShell
container as `/docker-entrypoint-initdb.d/01-agileboot.sql`.
The official MySQL image only runs files in `/docker-entrypoint-initdb.d` when
the database directory is empty, during the first initialization of the
`mysql_data` volume.
If MySQL has already been started before, the `mysql_data` volume already ```powershell
contains data and `docker compose up -d` will not import the SQL again. To Copy-Item .env.example .env
reinitialize the local database, remove the volumes first: ```
`.env.example` 默认把 MySQL 和 Redis 映射到宿主机 `3306``6379`,与后端 `application-dev.yml` 的本地开发配置保持一致。
### 2. 启动 MySQL 和 Redis
```bash
docker compose up -d mysql redis
```
首次启动 MySQL 容器时,Compose 会把 `backend/sql/agileboot.sql` 挂载到 `/docker-entrypoint-initdb.d/01-agileboot.sql`,由 MySQL 镜像自动初始化数据库。
如果数据库卷已经存在,初始化 SQL 不会重复执行。需要重建本地数据时可以执行:
```bash ```bash
cd backend
docker compose down -v docker compose down -v
docker compose up -d docker compose up -d mysql redis
``` ```
Warning: `docker compose down -v` deletes the local MySQL and Redis volumes, `docker compose down -v` 会删除本地 MySQL Redis 数据卷,请确认不需要保留本地数据后再执行。
including existing database data and Redis data.
If you do not want to delete the volumes, import the SQL manually: ### 3. 启动后端
```bash Windows PowerShell
```powershell
cd backend cd backend
docker exec -i mysql-server mysql -uroot -proot123 agileboot_pure < sql/agileboot.sql .\mvnw.cmd -pl agileboot-admin spring-boot:run
``` ```
### Install Frontend Dependencies macOS / Linux
```bash
cd frontend
pnpm install
```
### Start Backend and Web Frontend
Start the backend:
```bash ```bash
cd backend cd backend
./mvnw -pl agileboot-admin spring-boot:run ./mvnw -pl agileboot-admin spring-boot:run
``` ```
Start the web frontend: 后端默认地址:
```bash ```text
cd frontend http://localhost:8080
pnpm dev:web
``` ```
- Backend: `http://localhost:8080` 接口文档地址:
- Frontend: Vite output, usually `http://localhost:80/`
The frontend development proxy maps `/dev-api` to `http://localhost:8080`. ```text
http://localhost:8080/swagger-ui/index.html
Optional app commands: http://localhost:8080/v3/api-docs
```bash
cd frontend
pnpm dev:app:weapp
pnpm dev:app:h5
``` ```
## Git Hooks ### 4. 启动 Web 管理端
This repository uses `.githooks` as the single Git hooks entrypoint.
```bash
git config core.hooksPath .githooks
```
The frontend workspace uses pnpm:
```bash ```bash
cd frontend cd frontend
pnpm install pnpm install
pnpm dev:web pnpm dev:web
pnpm build:web
pnpm dev:app:weapp
pnpm build:app:weapp
pnpm lint
pnpm typecheck
``` ```
## Frontend `frontend/web/.env.development` 默认配置:
`frontend` is a pnpm workspace: ```env
VITE_PORT=80
VITE_APP_BASE_API=/dev-api
```
- `web`: Vite + Vue 3 admin frontend. Web 开发服务默认地址:
- `app`: Taro + Vue 3 app frontend.
Shared engineering configuration lives in `frontend` root. Subprojects should extend the shared TypeScript, ESLint, Stylelint, Prettier, commitlint, and lint-staged configuration instead of duplicating it. ```text
http://localhost:80
```
如果 80 端口被占用,可以修改 `frontend/web/.env.development` 中的 `VITE_PORT`
### 5. 启动 App 端,可选
```bash
cd frontend
pnpm dev:app:weapp
```
H5 模式:
```bash
cd frontend
pnpm dev:app:h5
```
## 部署步骤
生产部署推荐使用根目录的 Docker Compose。它会启动 MySQL、Redis、后端服务和 Web Nginx 服务。
### 1. 准备生产环境变量
```bash
cp .env.example .env
```
按实际环境修改 `.env`,至少建议调整:
```env
MYSQL_ROOT_PASSWORD=请替换为强密码
MYSQL_DATABASE=todo_agileboot_pure
MYSQL_APP_USERNAME=todo_app
MYSQL_APP_PASSWORD=请替换为强密码
MYSQL_PORT=13306
REDIS_PASSWORD=请替换为强密码
REDIS_PORT=16379
APP_PORT=8080
WEB_PORT=8081
JAVA_OPTS=-Xms256m -Xmx512m
```
生产环境建议把 `MYSQL_PORT``REDIS_PORT` 改成非默认宿主机端口,例如上面的 `13306``16379`。这两个变量只影响宿主机暴露端口,不影响容器内部端口;后端容器仍通过 Compose 内部网络访问 MySQL `3306` 和 Redis `6379`
如果数据库和 Redis 只给后端容器使用,建议进一步通过服务器防火墙限制这些端口的外部访问,或按实际部署需要移除 `docker-compose.yml` 中 MySQL/Redis 的 `ports` 暴露配置。
不要提交真实生产 `.env` 文件。
### 2. 构建并启动完整服务
```bash
docker compose --profile prod up -d --build
```
Compose 会执行以下构建:
- `backend/Dockerfile`:使用 Maven 构建 `agileboot-admin`,运行 Spring Boot Jar。
- `frontend/web/Dockerfile`:构建 Web 静态文件,并用 Nginx 提供访问。
Web 容器中的 Nginx 会把 `/prod-api/` 代理到 Compose 内部的后端服务 `app:8080`
### 3. 访问服务
默认访问地址:
```text
Webhttp://localhost:8081
后端:http://localhost:8080
```
如果修改了 `.env` 中的 `WEB_PORT``APP_PORT`,以实际端口为准。
### 4. 常用运维命令
查看服务状态:
```bash
docker compose --profile prod ps
```
查看后端日志:
```bash
docker compose --profile prod logs -f app
```
查看 Web 日志:
```bash
docker compose --profile prod logs -f web
```
停止服务:
```bash
docker compose --profile prod down
```
停止并删除数据卷:
```bash
docker compose --profile prod down -v
```
`down -v` 会删除数据库和 Redis 数据,生产环境谨慎使用。
+12
View File
@@ -0,0 +1,12 @@
.git
.gitignore
.idea
*.iml
*.log
**/target
build
dist
.gradle
.mvn/wrapper/maven-wrapper.jar
.env
docker-compose.yml
+19
View File
@@ -0,0 +1,19 @@
FROM maven:3.8.8-eclipse-temurin-8 AS build-stage
WORKDIR /app
COPY . .
RUN mvn -pl agileboot-admin -am package -Dmaven.test.skip=true -B
FROM eclipse-temurin:8-jre
WORKDIR /app
ENV TZ=Asia/Shanghai
COPY --from=build-stage /app/agileboot-admin/target/*.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
@@ -0,0 +1,84 @@
package com.agileboot.admin.controller.app;
import com.agileboot.admin.customize.service.login.LoginService;
import com.agileboot.admin.customize.service.login.command.LoginCommand;
import com.agileboot.admin.customize.service.login.dto.CaptchaDTO;
import com.agileboot.admin.customize.service.login.dto.ConfigDTO;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.domain.common.dto.CurrentLoginUserDTO;
import com.agileboot.domain.common.dto.TokenDTO;
import com.agileboot.domain.system.user.UserApplicationService;
import com.agileboot.domain.system.user.command.RegisterUserCommand;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author codex
*/
@Tag(name = "小程序登录API", description = "小程序登录注册相关接口")
@RestController
@RequestMapping("/app")
@RequiredArgsConstructor
public class AppAuthController {
private final LoginService loginService;
private final UserApplicationService userApplicationService;
@Operation(summary = "小程序配置")
@GetMapping("/getConfig")
public ResponseDTO<ConfigDTO> getConfig() {
return ResponseDTO.ok(loginService.getConfig());
}
@Operation(summary = "小程序验证码")
@GetMapping("/captchaImage")
public ResponseDTO<CaptchaDTO> getCaptchaImg() {
return ResponseDTO.ok(loginService.generateCaptchaImg());
}
@Operation(summary = "小程序登录")
@PostMapping("/login")
public ResponseDTO<TokenDTO> login(@RequestBody LoginCommand command) {
String token = loginService.login(command);
return ResponseDTO.ok(buildTokenDTO(token));
}
@Operation(summary = "小程序注册")
@PostMapping("/register")
public ResponseDTO<TokenDTO> register(@Validated @RequestBody RegisterUserCommand command) {
decryptRegisterPassword(command);
loginService.validateCaptchaIfEnabled(command.getUsername(), command.getCaptchaCode(),
command.getCaptchaCodeKey());
userApplicationService.registerUser(command);
loginService.recordRegisterInfo(command.getUsername());
return ResponseDTO.ok(buildTokenDTO(loginService.createTokenForRegisteredUser(command.getUsername())));
}
@Operation(summary = "小程序当前登录用户")
@GetMapping("/getLoginUserInfo")
public ResponseDTO<CurrentLoginUserDTO> getLoginUserInfo() {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
return ResponseDTO.ok(userApplicationService.getLoginUserInfo(loginUser));
}
private TokenDTO buildTokenDTO(String token) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
CurrentLoginUserDTO currentUser = userApplicationService.getLoginUserInfo(loginUser);
return new TokenDTO(token, currentUser);
}
private void decryptRegisterPassword(RegisterUserCommand command) {
command.setPassword(loginService.decryptPassword(command.getPassword()));
command.setConfirmPassword(loginService.decryptPassword(command.getConfirmPassword()));
}
}
@@ -0,0 +1,90 @@
package com.agileboot.admin.controller.app;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.collaboration.record.CollaborationRecordApplicationService;
import com.agileboot.domain.collaboration.record.command.AddCollaborationRecordCommand;
import com.agileboot.domain.collaboration.record.command.UpdateCollaborationRecordCommand;
import com.agileboot.domain.collaboration.record.dto.CollaborationMonthlyStatisticsDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationOptionDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationRecordDTO;
import com.agileboot.domain.collaboration.record.dto.CollaborationRecordDetailDTO;
import com.agileboot.domain.collaboration.record.query.CollaborationRecordQuery;
import com.agileboot.domain.common.command.BulkOperationCommand;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author codex
*/
@Tag(name = "小程序合作记录API", description = "小程序合作记录相关接口")
@RestController
@RequestMapping("/app/collaboration/record")
@Validated
@RequiredArgsConstructor
public class AppCollaborationRecordController {
private final CollaborationRecordApplicationService recordApplicationService;
@Operation(summary = "小程序合作记录列表")
@GetMapping("/list")
public ResponseDTO<PageDTO<CollaborationRecordDTO>> list(CollaborationRecordQuery query) {
return ResponseDTO.ok(recordApplicationService.getRecordList(query));
}
@Operation(summary = "小程序合作记录详情")
@GetMapping("/{recordId}")
public ResponseDTO<CollaborationRecordDetailDTO> getInfo(@PathVariable @Positive Long recordId) {
return ResponseDTO.ok(recordApplicationService.getRecordInfo(recordId));
}
@Operation(summary = "小程序合作记录选项")
@GetMapping("/options")
public ResponseDTO<List<CollaborationOptionDTO>> options() {
return ResponseDTO.ok(recordApplicationService.getOptions());
}
@Operation(summary = "小程序合作记录月度统计")
@GetMapping("/monthly-statistics")
public ResponseDTO<List<CollaborationMonthlyStatisticsDTO>> monthlyStatistics(@RequestParam Integer year) {
return ResponseDTO.ok(recordApplicationService.getMonthlyStatistics(year));
}
@Operation(summary = "小程序新增合作记录")
@PostMapping
public ResponseDTO<Void> add(@Valid @RequestBody AddCollaborationRecordCommand command) {
recordApplicationService.addRecord(command);
return ResponseDTO.ok();
}
@Operation(summary = "小程序修改合作记录")
@PutMapping
public ResponseDTO<Void> edit(@Valid @RequestBody UpdateCollaborationRecordCommand command) {
recordApplicationService.updateRecord(command);
return ResponseDTO.ok();
}
@Operation(summary = "小程序删除合作记录")
@DeleteMapping
public ResponseDTO<Void> remove(@RequestParam @NotNull @NotEmpty List<Long> ids) {
recordApplicationService.deleteRecord(new BulkOperationCommand<>(ids));
return ResponseDTO.ok();
}
}
@@ -0,0 +1,56 @@
package com.agileboot.admin.controller.app;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.domain.system.user.UserApplicationService;
import com.agileboot.domain.system.user.command.UpdateProfileCommand;
import com.agileboot.domain.system.user.command.UpdateUserPasswordCommand;
import com.agileboot.domain.system.user.dto.UserProfileDTO;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author codex
*/
@Tag(name = "小程序个人信息API", description = "小程序个人信息相关接口")
@RestController
@RequestMapping("/app/user/profile")
@RequiredArgsConstructor
public class AppProfileController {
private final UserApplicationService userApplicationService;
@Operation(summary = "小程序获取个人信息")
@GetMapping
public ResponseDTO<UserProfileDTO> profile() {
SystemLoginUser user = AuthenticationUtils.getSystemLoginUser();
return ResponseDTO.ok(userApplicationService.getUserProfile(user.getUserId()));
}
@Operation(summary = "小程序修改个人信息")
@PutMapping
public ResponseDTO<Void> updateProfile(@RequestBody UpdateProfileCommand command) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
command.setUserId(loginUser.getUserId());
userApplicationService.updateUserProfile(command);
return ResponseDTO.ok();
}
@Operation(summary = "小程序修改个人密码")
@PutMapping("/password")
public ResponseDTO<Void> updatePassword(@Validated @RequestBody UpdateUserPasswordCommand command) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
command.setUserId(loginUser.getUserId());
userApplicationService.updatePasswordBySelf(loginUser, command);
return ResponseDTO.ok();
}
}
@@ -70,8 +70,9 @@ public class CollaborationRecordController extends BaseController {
@Operation(summary = "合作记录月度统计") @Operation(summary = "合作记录月度统计")
@PreAuthorize("@permission.has('collaboration:record:statistics')") @PreAuthorize("@permission.has('collaboration:record:statistics')")
@GetMapping("/monthly-statistics") @GetMapping("/monthly-statistics")
public ResponseDTO<List<CollaborationMonthlyStatisticsDTO>> monthlyStatistics(@RequestParam Integer year) { public ResponseDTO<List<CollaborationMonthlyStatisticsDTO>> monthlyStatistics(
return ResponseDTO.ok(recordApplicationService.getMonthlyStatistics(year)); @RequestParam Integer year, @RequestParam(required = false) Long creatorId) {
return ResponseDTO.ok(recordApplicationService.getMonthlyStatistics(year, creatorId));
} }
@Operation(summary = "新增合作记录") @Operation(summary = "新增合作记录")
@@ -24,6 +24,7 @@ import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@@ -73,7 +74,7 @@ public class FileController {
*/ */
@Operation(summary = "单个上传文件") @Operation(summary = "单个上传文件")
@PostMapping("/upload") @PostMapping("/upload")
public ResponseDTO<UploadDTO> uploadFile(MultipartFile file) { public ResponseDTO<UploadDTO> uploadFile(@RequestParam("file") MultipartFile file) {
if (file == null) { if (file == null) {
throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY); throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY);
} }
@@ -101,7 +102,7 @@ public class FileController {
*/ */
@Operation(summary = "多个上传文件") @Operation(summary = "多个上传文件")
@PostMapping("/uploads") @PostMapping("/uploads")
public ResponseDTO<List<UploadDTO>> uploadFiles(List<MultipartFile> files) { public ResponseDTO<List<UploadDTO>> uploadFiles(@RequestParam("files") List<MultipartFile> files) {
if (CollUtil.isEmpty(files)) { if (CollUtil.isEmpty(files)) {
throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY); throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY);
} }
@@ -3,14 +3,12 @@ package com.agileboot.admin.controller.common;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.agileboot.common.config.AgileBootConfig; import com.agileboot.common.config.AgileBootConfig;
import com.agileboot.common.core.dto.ResponseDTO; import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.domain.common.dto.CurrentLoginUserDTO; import com.agileboot.domain.common.dto.CurrentLoginUserDTO;
import com.agileboot.domain.common.dto.TokenDTO; import com.agileboot.domain.common.dto.TokenDTO;
import com.agileboot.domain.system.menu.MenuApplicationService; import com.agileboot.domain.system.menu.MenuApplicationService;
import com.agileboot.domain.system.menu.dto.RouterDTO; import com.agileboot.domain.system.menu.dto.RouterDTO;
import com.agileboot.domain.system.user.UserApplicationService; import com.agileboot.domain.system.user.UserApplicationService;
import com.agileboot.domain.system.user.command.AddUserCommand; import com.agileboot.domain.system.user.command.RegisterUserCommand;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit; import com.agileboot.infrastructure.annotations.ratelimit.RateLimit;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.CacheType; import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.CacheType;
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.LimitType; import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.LimitType;
@@ -25,6 +23,7 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List; import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@@ -130,10 +129,24 @@ public class LoginController {
} }
@Operation(summary = "注册接口", description = "暂未实现") @Operation(summary = "注册接口")
@PostMapping("/register") @PostMapping("/register")
public ResponseDTO<Void> register(@RequestBody AddUserCommand command) { public ResponseDTO<TokenDTO> register(@Validated @RequestBody RegisterUserCommand command) {
return ResponseDTO.fail(new ApiException(Business.COMMON_UNSUPPORTED_OPERATION)); decryptRegisterPassword(command);
loginService.validateCaptchaIfEnabled(command.getUsername(), command.getCaptchaCode(), command.getCaptchaCodeKey());
userApplicationService.registerUser(command);
loginService.recordRegisterInfo(command.getUsername());
String token = loginService.createTokenForRegisteredUser(command.getUsername());
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
CurrentLoginUserDTO currentUserDTO = userApplicationService.getLoginUserInfo(loginUser);
return ResponseDTO.ok(new TokenDTO(token, currentUserDTO));
}
private void decryptRegisterPassword(RegisterUserCommand command) {
command.setPassword(loginService.decryptPassword(command.getPassword()));
command.setConfirmPassword(loginService.decryptPassword(command.getConfirmPassword()));
} }
} }
@@ -1,111 +0,0 @@
package com.agileboot.admin.controller.system;
import cn.hutool.core.lang.tree.Tree;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.domain.system.dept.DeptApplicationService;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.command.UpdateDeptCommand;
import com.agileboot.domain.system.dept.dto.DeptDTO;
import com.agileboot.domain.system.dept.query.DeptQuery;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 部门信息
*
* @author valarchie
*/
@RestController
@RequestMapping("/system")
@Validated
@RequiredArgsConstructor
@Tag(name = "部门API", description = "部门相关的增删查改")
public class SysDeptController extends BaseController {
private final DeptApplicationService deptApplicationService;
/**
* 获取部门列表
*/
@Operation(summary = "部门列表")
@PreAuthorize("@permission.has('system:dept:list')")
@GetMapping("/depts")
public ResponseDTO<List<DeptDTO>> list(DeptQuery query) {
List<DeptDTO> deptList = deptApplicationService.getDeptList(query);
return ResponseDTO.ok(deptList);
}
/**
* 根据部门编号获取详细信息
*/
@Operation(summary = "部门详情")
@PreAuthorize("@permission.has('system:dept:query')")
@GetMapping(value = "/dept/{deptId}")
public ResponseDTO<DeptDTO> getInfo(@PathVariable Long deptId) {
DeptDTO dept = deptApplicationService.getDeptInfo(deptId);
return ResponseDTO.ok(dept);
}
/**
* 获取部门下拉树列表
*/
@Operation(summary = "获取部门树级结构")
@GetMapping("/depts/dropdown")
public ResponseDTO<List<Tree<Long>>> dropdownList() {
List<Tree<Long>> deptTree = deptApplicationService.getDeptTree();
return ResponseDTO.ok(deptTree);
}
/**
* 新增部门
*/
@Operation(summary = "新增部门")
@PreAuthorize("@permission.has('system:dept:add')")
@AccessLog(title = "部门管理", businessType = BusinessTypeEnum.ADD)
@PostMapping("/dept")
public ResponseDTO<Void> add(@RequestBody AddDeptCommand addCommand) {
deptApplicationService.addDept(addCommand);
return ResponseDTO.ok();
}
/**
* 修改部门
*/
@Operation(summary = "修改部门")
@PreAuthorize("@permission.has('system:dept:edit') AND @dataScope.checkDeptId(#updateCommand.deptId)")
@AccessLog(title = "部门管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/dept/{deptId}")
public ResponseDTO<Void> edit(@PathVariable("deptId")Long deptId, @RequestBody UpdateDeptCommand updateCommand) {
updateCommand.setDeptId(deptId);
deptApplicationService.updateDept(updateCommand);
return ResponseDTO.ok();
}
/**
* 删除部门
*/
@Operation(summary = "删除部门")
@PreAuthorize("@permission.has('system:dept:remove') AND @dataScope.checkDeptId(#deptId)")
@AccessLog(title = "部门管理", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping("/dept/{deptId}")
public ResponseDTO<Void> remove(@PathVariable @NotNull Long deptId) {
deptApplicationService.removeDept(deptId);
return ResponseDTO.ok();
}
}
@@ -1,122 +0,0 @@
package com.agileboot.admin.controller.system;
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
import com.agileboot.common.core.base.BaseController;
import com.agileboot.common.core.dto.ResponseDTO;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.enums.common.BusinessTypeEnum;
import com.agileboot.common.utils.poi.CustomExcelUtil;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.system.post.PostApplicationService;
import com.agileboot.domain.system.post.command.AddPostCommand;
import com.agileboot.domain.system.post.command.UpdatePostCommand;
import com.agileboot.domain.system.post.dto.PostDTO;
import com.agileboot.domain.system.post.query.PostQuery;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* 岗位信息操作处理
*
* @author ruoyi
*/
@Tag(name = "职位API", description = "职位相关的增删查改")
@RestController
@RequestMapping("/system/post")
@Validated
@RequiredArgsConstructor
public class SysPostController extends BaseController {
private final PostApplicationService postApplicationService;
/**
* 获取岗位列表
*/
@Operation(summary = "职位列表")
@PreAuthorize("@permission.has('system:post:list')")
@GetMapping("/list")
public ResponseDTO<PageDTO<PostDTO>> list(PostQuery query) {
PageDTO<PostDTO> pageDTO = postApplicationService.getPostList(query);
return ResponseDTO.ok(pageDTO);
}
/**
* 导出查询到的所有岗位信息到excel文件
* @param response http响应
* @param query 查询参数
* @author Kevin Zhang
* @date 2023-10-02
*/
@Operation(summary = "职位列表导出")
@AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.EXPORT)
@PreAuthorize("@permission.has('system:post:export')")
@GetMapping("/excel")
public void export(HttpServletResponse response, PostQuery query) {
List<PostDTO> all = postApplicationService.getPostListAll(query);
CustomExcelUtil.writeToResponse(all, PostDTO.class, response);
}
/**
* 根据岗位编号获取详细信息
*/
@Operation(summary = "职位详情")
@PreAuthorize("@permission.has('system:post:query')")
@GetMapping(value = "/{postId}")
public ResponseDTO<PostDTO> getInfo(@PathVariable Long postId) {
PostDTO post = postApplicationService.getPostInfo(postId);
return ResponseDTO.ok(post);
}
/**
* 新增岗位
*/
@Operation(summary = "添加职位")
@PreAuthorize("@permission.has('system:post:add')")
@AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.ADD)
@PostMapping
public ResponseDTO<Void> add(@RequestBody AddPostCommand addCommand) {
postApplicationService.addPost(addCommand);
return ResponseDTO.ok();
}
/**
* 修改岗位
*/
@Operation(summary = "修改职位")
@PreAuthorize("@permission.has('system:post:edit')")
@AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping
public ResponseDTO<Void> edit(@RequestBody UpdatePostCommand updateCommand) {
postApplicationService.updatePost(updateCommand);
return ResponseDTO.ok();
}
/**
* 删除岗位
*/
@Operation(summary = "删除职位")
@PreAuthorize("@permission.has('system:post:remove')")
@AccessLog(title = "岗位管理", businessType = BusinessTypeEnum.DELETE)
@DeleteMapping
public ResponseDTO<Void> remove(@RequestParam @NotNull @NotEmpty List<Long> ids) {
postApplicationService.deletePost(new BulkOperationCommand<>(ids));
return ResponseDTO.ok();
}
}
@@ -19,6 +19,7 @@ import com.agileboot.common.enums.common.BusinessTypeEnum;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
@@ -71,7 +72,7 @@ public class SysProfileController extends BaseController {
@Operation(summary = "重置个人密码") @Operation(summary = "重置个人密码")
@AccessLog(title = "个人信息", businessType = BusinessTypeEnum.MODIFY) @AccessLog(title = "个人信息", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/password") @PutMapping("/password")
public ResponseDTO<Void> updatePassword(@RequestBody UpdateUserPasswordCommand command) { public ResponseDTO<Void> updatePassword(@Validated @RequestBody UpdateUserPasswordCommand command) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
command.setUserId(loginUser.getUserId()); command.setUserId(loginUser.getUserId());
userApplicationService.updatePasswordBySelf(loginUser, command); userApplicationService.updatePasswordBySelf(loginUser, command);
@@ -6,7 +6,6 @@ import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.utils.poi.CustomExcelUtil; import com.agileboot.common.utils.poi.CustomExcelUtil;
import com.agileboot.domain.system.role.RoleApplicationService; import com.agileboot.domain.system.role.RoleApplicationService;
import com.agileboot.domain.system.role.command.AddRoleCommand; import com.agileboot.domain.system.role.command.AddRoleCommand;
import com.agileboot.domain.system.role.command.UpdateDataScopeCommand;
import com.agileboot.domain.system.role.command.UpdateRoleCommand; import com.agileboot.domain.system.role.command.UpdateRoleCommand;
import com.agileboot.domain.system.role.command.UpdateStatusCommand; import com.agileboot.domain.system.role.command.UpdateStatusCommand;
import com.agileboot.domain.system.role.dto.RoleDTO; import com.agileboot.domain.system.role.dto.RoleDTO;
@@ -111,21 +110,6 @@ public class SysRoleController extends BaseController {
return ResponseDTO.ok(); return ResponseDTO.ok();
} }
/**
* 修改保存数据权限
*/
@Operation(summary = "修改角色数据权限")
@PreAuthorize("@permission.has('system:role:edit')")
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.MODIFY)
@PutMapping("/{roleId}/dataScope")
public ResponseDTO<Void> dataScope(@PathVariable("roleId") Long roleId,
@RequestBody UpdateDataScopeCommand command) {
command.setRoleId(roleId);
roleApplicationService.updateDataScope(command);
return ResponseDTO.ok();
}
/** /**
* 角色状态修改 * 角色状态修改
*/ */
@@ -52,7 +52,7 @@ public class SysUserController extends BaseController {
* 获取用户列表 * 获取用户列表
*/ */
@Operation(summary = "用户列表") @Operation(summary = "用户列表")
@PreAuthorize("@permission.has('system:user:list') AND @dataScope.checkDeptId(#query.deptId)") @PreAuthorize("@permission.has('system:user:list')")
@GetMapping @GetMapping
public ResponseDTO<PageDTO<UserDTO>> userList(SearchUserQuery<SearchUserDO> query) { public ResponseDTO<PageDTO<UserDTO>> userList(SearchUserQuery<SearchUserDO> query) {
PageDTO<UserDTO> page = userApplicationService.getUserList(query); PageDTO<UserDTO> page = userApplicationService.getUserList(query);
@@ -105,7 +105,7 @@ public class SysUserController extends BaseController {
* 新增用户 * 新增用户
*/ */
@Operation(summary = "新增用户") @Operation(summary = "新增用户")
@PreAuthorize("@permission.has('system:user:add') AND @dataScope.checkDeptId(#command.deptId)") @PreAuthorize("@permission.has('system:user:add')")
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.ADD) @AccessLog(title = "用户管理", businessType = BusinessTypeEnum.ADD)
@PostMapping @PostMapping
public ResponseDTO<Void> add(@Validated @RequestBody AddUserCommand command) { public ResponseDTO<Void> add(@Validated @RequestBody AddUserCommand command) {
@@ -135,6 +135,7 @@ public class SecurityConfig {
// 对于登录login 注册register 验证码captchaImage 以及公共Api的请求允许匿名访问 // 对于登录login 注册register 验证码captchaImage 以及公共Api的请求允许匿名访问
// 注意: 当携带token请求以下这几个接口时 会返回403的错误 // 注意: 当携带token请求以下这几个接口时 会返回403的错误
.antMatchers("/login", "/register", "/getConfig", "/captchaImage", "/api/**").anonymous() .antMatchers("/login", "/register", "/getConfig", "/captchaImage", "/api/**").anonymous()
.antMatchers("/app/login", "/app/register", "/app/getConfig", "/app/captchaImage").anonymous()
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
"/profile/**").permitAll() "/profile/**").permitAll()
// TODO this is danger. // TODO this is danger.
@@ -40,6 +40,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.FastByteArrayOutputStream; import org.springframework.util.FastByteArrayOutputStream;
@@ -61,6 +62,8 @@ public class LoginService {
private final AuthenticationManager authenticationManager; private final AuthenticationManager authenticationManager;
private final UserDetailsService userDetailsService;
@Resource(name = "captchaProducer") @Resource(name = "captchaProducer")
private Producer captchaProducer; private Producer captchaProducer;
@@ -115,6 +118,7 @@ public class LoginService {
boolean isCaptchaOn = isCaptchaOn(); boolean isCaptchaOn = isCaptchaOn();
configDTO.setIsCaptchaOn(isCaptchaOn); configDTO.setIsCaptchaOn(isCaptchaOn);
configDTO.setIsRegisterUserOn(isRegisterUserOn());
configDTO.setDictionary(MapCache.dictionaryCache()); configDTO.setDictionary(MapCache.dictionaryCache());
return configDTO; return configDTO;
} }
@@ -179,6 +183,9 @@ public class LoginService {
* @param captchaCodeKey 验证码对应的缓存key * @param captchaCodeKey 验证码对应的缓存key
*/ */
public void validateCaptcha(String username, String captchaCode, String captchaCodeKey) { public void validateCaptcha(String username, String captchaCode, String captchaCodeKey) {
if (StrUtil.isBlank(captchaCode) || StrUtil.isBlank(captchaCodeKey)) {
throw new ApiException(ErrorCode.Business.LOGIN_CAPTCHA_CODE_NULL);
}
String captcha = redisCache.captchaCache.getObjectById(captchaCodeKey); String captcha = redisCache.captchaCache.getObjectById(captchaCodeKey);
redisCache.captchaCache.delete(captchaCodeKey); redisCache.captchaCache.delete(captchaCodeKey);
if (captcha == null) { if (captcha == null) {
@@ -193,6 +200,26 @@ public class LoginService {
} }
} }
public void validateCaptchaIfEnabled(String username, String captchaCode, String captchaCodeKey) {
if (isCaptchaOn()) {
validateCaptcha(username, captchaCode, captchaCodeKey);
}
}
public String createTokenForRegisteredUser(String username) {
SystemLoginUser loginUser = (SystemLoginUser) userDetailsService.loadUserByUsername(username);
Authentication authentication = new UsernamePasswordAuthenticationToken(loginUser, null,
loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
updateLoginInfo(loginUser);
return tokenService.createTokenAndPutUserInCache(loginUser);
}
public void recordRegisterInfo(String username) {
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.REGISTER,
LoginStatusEnum.REGISTER.description()));
}
/** /**
* 记录登录信息 * 记录登录信息
* @param loginUser 登录用户 * @param loginUser 登录用户
@@ -200,7 +227,10 @@ public class LoginService {
public void recordLoginInfo(SystemLoginUser loginUser) { public void recordLoginInfo(SystemLoginUser loginUser) {
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginUser.getUsername(), LoginStatusEnum.LOGIN_SUCCESS, ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginUser.getUsername(), LoginStatusEnum.LOGIN_SUCCESS,
LoginStatusEnum.LOGIN_SUCCESS.description())); LoginStatusEnum.LOGIN_SUCCESS.description()));
updateLoginInfo(loginUser);
}
private void updateLoginInfo(SystemLoginUser loginUser) {
SysUserEntity entity = redisCache.userCache.getObjectById(loginUser.getUserId()); SysUserEntity entity = redisCache.userCache.getObjectById(loginUser.getUserId());
entity.setLoginIp(ServletUtil.getClientIP(ServletHolderUtil.getRequest())); entity.setLoginIp(ServletUtil.getClientIP(ServletHolderUtil.getRequest()));
@@ -219,4 +249,8 @@ public class LoginService {
return Convert.toBool(guavaCache.configCache.get(ConfigKeyEnum.CAPTCHA.getValue())); return Convert.toBool(guavaCache.configCache.get(ConfigKeyEnum.CAPTCHA.getValue()));
} }
private boolean isRegisterUserOn() {
return Convert.toBool(guavaCache.configCache.get(ConfigKeyEnum.REGISTER.getValue()));
}
} }
@@ -1,7 +1,5 @@
package com.agileboot.admin.customize.service.login; package com.agileboot.admin.customize.service.login;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
@@ -24,7 +22,6 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.SetUtils;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
@@ -67,7 +64,7 @@ public class UserDetailsServiceImpl implements UserDetailsService {
RoleInfo roleInfo = getRoleInfo(userEntity.getRoleId(), userEntity.getIsAdmin()); RoleInfo roleInfo = getRoleInfo(userEntity.getRoleId(), userEntity.getIsAdmin());
SystemLoginUser loginUser = new SystemLoginUser(userEntity.getUserId(), userEntity.getIsAdmin(), userEntity.getUsername(), SystemLoginUser loginUser = new SystemLoginUser(userEntity.getUserId(), userEntity.getIsAdmin(), userEntity.getUsername(),
userEntity.getPassword(), roleInfo, userEntity.getDeptId()); userEntity.getPassword(), roleInfo);
loginUser.fillLoginInfo(); loginUser.fillLoginInfo();
loginUser.setAutoRefreshCacheTime(loginUser.getLoginInfo().getLoginTime() loginUser.setAutoRefreshCacheTime(loginUser.getLoginInfo().getLoginTime()
+ TimeUnit.MINUTES.toMillis(tokenService.getAutoRefreshTime())); + TimeUnit.MINUTES.toMillis(tokenService.getAutoRefreshTime()));
@@ -86,7 +83,7 @@ public class UserDetailsServiceImpl implements UserDetailsService {
Set<Long> allMenuIds = allMenus.stream().map(SysMenuEntity::getMenuId).collect(Collectors.toSet()); Set<Long> allMenuIds = allMenus.stream().map(SysMenuEntity::getMenuId).collect(Collectors.toSet());
return new RoleInfo(RoleInfo.ADMIN_ROLE_ID, RoleInfo.ADMIN_ROLE_KEY, DataScopeEnum.ALL, SetUtils.emptySet(), return new RoleInfo(RoleInfo.ADMIN_ROLE_ID, RoleInfo.ADMIN_ROLE_KEY, DataScopeEnum.ALL,
RoleInfo.ADMIN_PERMISSIONS, allMenuIds); RoleInfo.ADMIN_PERMISSIONS, allMenuIds);
} }
@@ -104,13 +101,7 @@ public class UserDetailsServiceImpl implements UserDetailsService {
DataScopeEnum dataScopeEnum = BasicEnumUtil.fromValue(DataScopeEnum.class, roleEntity.getDataScope()); DataScopeEnum dataScopeEnum = BasicEnumUtil.fromValue(DataScopeEnum.class, roleEntity.getDataScope());
Set<Long> deptIdSet = SetUtils.emptySet(); return new RoleInfo(roleId, roleEntity.getRoleKey(), dataScopeEnum, permissions, menuIds);
if (StrUtil.isNotEmpty(roleEntity.getDeptIdSet())) {
deptIdSet = StrUtil.split(roleEntity.getDeptIdSet(), ",").stream()
.map(Convert::toLong).collect(Collectors.toSet());
}
return new RoleInfo(roleId, roleEntity.getRoleKey(), dataScopeEnum, deptIdSet, permissions, menuIds);
} }
@@ -13,6 +13,8 @@ public class ConfigDTO {
private Boolean isCaptchaOn; private Boolean isCaptchaOn;
private Boolean isRegisterUserOn;
private Map<String, List<DictionaryData>> dictionary; private Map<String, List<DictionaryData>> dictionary;
} }
@@ -1,16 +1,11 @@
package com.agileboot.admin.customize.service.permission; package com.agileboot.admin.customize.service.permission;
import cn.hutool.extra.spring.SpringUtil;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker; import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.checker.AllDataPermissionChecker; import com.agileboot.admin.customize.service.permission.model.checker.AllDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.checker.CustomDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.checker.DefaultDataPermissionChecker; import com.agileboot.admin.customize.service.permission.model.checker.DefaultDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.checker.DeptTreeDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.checker.OnlySelfDataPermissionChecker; import com.agileboot.admin.customize.service.permission.model.checker.OnlySelfDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.checker.SingleDeptDataPermissionChecker;
import com.agileboot.infrastructure.user.web.DataScopeEnum; import com.agileboot.infrastructure.user.web.DataScopeEnum;
import com.agileboot.domain.system.dept.db.SysDeptService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -21,41 +16,31 @@ import org.springframework.stereotype.Component;
@Component @Component
public class DataPermissionCheckerFactory { public class DataPermissionCheckerFactory {
private static AbstractDataPermissionChecker allChecker; private static AbstractDataPermissionChecker allChecker;
private static AbstractDataPermissionChecker customChecker;
private static AbstractDataPermissionChecker singleDeptChecker;
private static AbstractDataPermissionChecker deptTreeChecker;
private static AbstractDataPermissionChecker onlySelfChecker; private static AbstractDataPermissionChecker onlySelfChecker;
private static AbstractDataPermissionChecker defaultSelfChecker; private static AbstractDataPermissionChecker defaultSelfChecker;
@PostConstruct @PostConstruct
public void initAllChecker() { public void initAllChecker() {
SysDeptService deptService = SpringUtil.getBean(SysDeptService.class);
allChecker = new AllDataPermissionChecker(); allChecker = new AllDataPermissionChecker();
customChecker = new CustomDataPermissionChecker(deptService); onlySelfChecker = new OnlySelfDataPermissionChecker();
singleDeptChecker = new SingleDeptDataPermissionChecker(deptService);
deptTreeChecker = new DeptTreeDataPermissionChecker(deptService);
onlySelfChecker = new OnlySelfDataPermissionChecker(deptService);
defaultSelfChecker = new DefaultDataPermissionChecker(); defaultSelfChecker = new DefaultDataPermissionChecker();
} }
public static AbstractDataPermissionChecker getChecker(SystemLoginUser loginUser) { public static AbstractDataPermissionChecker getChecker(SystemLoginUser loginUser) {
if (loginUser == null) { if (loginUser == null) {
return deptTreeChecker; return defaultSelfChecker;
}
if (loginUser.getRoleInfo() == null || loginUser.getRoleInfo().getDataScope() == null) {
return defaultSelfChecker;
} }
DataScopeEnum dataScope = loginUser.getRoleInfo().getDataScope(); DataScopeEnum dataScope = loginUser.getRoleInfo().getDataScope();
switch (dataScope) { switch (dataScope) {
case ALL: case ALL:
return allChecker; return allChecker;
case CUSTOM_DEFINE:
return customChecker;
case SINGLE_DEPT:
return singleDeptChecker;
case DEPT_TREE:
return deptTreeChecker;
case ONLY_SELF: case ONLY_SELF:
return onlySelfChecker; return onlySelfChecker;
default: default:
@@ -33,7 +33,7 @@ public class DataPermissionService {
if (targetUser == null) { if (targetUser == null) {
return true; return true;
} }
return checkDataScope(loginUser, targetUser.getDeptId(), userId); return checkDataScope(loginUser, userId);
} }
/** /**
@@ -53,14 +53,8 @@ public class DataPermissionService {
return true; return true;
} }
public boolean checkDeptId(Long deptId) { public boolean checkDataScope(SystemLoginUser loginUser, Long targetUserId) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); DataCondition dataCondition = DataCondition.builder().targetUserId(targetUserId).build();
return checkDataScope(loginUser, deptId, null);
}
public boolean checkDataScope(SystemLoginUser loginUser, Long targetDeptId, Long targetUserId) {
DataCondition dataCondition = DataCondition.builder().targetDeptId(targetDeptId).targetUserId(targetUserId).build();
AbstractDataPermissionChecker checker = DataPermissionCheckerFactory.getChecker(loginUser); AbstractDataPermissionChecker checker = DataPermissionCheckerFactory.getChecker(loginUser);
return checker.check(loginUser, dataCondition); return checker.check(loginUser, dataCondition);
} }
@@ -1,7 +1,6 @@
package com.agileboot.admin.customize.service.permission.model; package com.agileboot.admin.customize.service.permission.model;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.Data; import lombok.Data;
/** /**
@@ -11,8 +10,6 @@ import lombok.Data;
@Data @Data
public abstract class AbstractDataPermissionChecker { public abstract class AbstractDataPermissionChecker {
private SysDeptService deptService;
/** /**
* 检测当前用户对于 给定条件的数据 是否有权限 * 检测当前用户对于 给定条件的数据 是否有权限
* *
@@ -15,7 +15,6 @@ import lombok.NoArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
public class DataCondition { public class DataCondition {
private Long targetDeptId;
private Long targetUserId; private Long targetUserId;
} }
@@ -3,7 +3,6 @@ package com.agileboot.admin.customize.service.permission.model.checker;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker; import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition; import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -15,9 +14,6 @@ import lombok.EqualsAndHashCode;
@Data @Data
public class AllDataPermissionChecker extends AbstractDataPermissionChecker { public class AllDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override @Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) { public boolean check(SystemLoginUser loginUser, DataCondition condition) {
return true; return true;
@@ -1,42 +0,0 @@
package com.agileboot.admin.customize.service.permission.model.checker;
import cn.hutool.core.collection.CollUtil;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 数据权限测试接口
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CustomDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
if (condition == null || loginUser == null) {
return false;
}
if (loginUser.getRoleInfo() == null) {
return false;
}
Set<Long> deptIdSet = loginUser.getRoleInfo().getDeptIdSet();
Long targetDeptId = condition.getTargetDeptId();
return condition.getTargetDeptId() != null && CollUtil.safeContains(deptIdSet, targetDeptId);
}
}
@@ -3,7 +3,6 @@ package com.agileboot.admin.customize.service.permission.model.checker;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker; import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition; import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -15,8 +14,6 @@ import lombok.EqualsAndHashCode;
@Data @Data
public class DefaultDataPermissionChecker extends AbstractDataPermissionChecker { public class DefaultDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override @Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) { public boolean check(SystemLoginUser loginUser, DataCondition condition) {
return false; return false;
@@ -1,44 +0,0 @@
package com.agileboot.admin.customize.service.permission.model.checker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 数据权限测试接口
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DeptTreeDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
if (condition == null || loginUser == null) {
return false;
}
if (loginUser.getDeptId() == null || condition.getTargetDeptId() == null) {
return false;
}
Long currentDeptId = loginUser.getDeptId();
Long targetDeptId = condition.getTargetDeptId();
boolean isContainsTargetDept = deptService.isChildOfTheDept(loginUser.getDeptId(), targetDeptId);
boolean isSameDept = Objects.equals(currentDeptId, targetDeptId);
return isContainsTargetDept || isSameDept;
}
}
@@ -3,9 +3,7 @@ package com.agileboot.admin.customize.service.permission.model.checker;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker; import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition; import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Objects; import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@@ -16,12 +14,9 @@ import lombok.NoArgsConstructor;
*/ */
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
public class OnlySelfDataPermissionChecker extends AbstractDataPermissionChecker { public class OnlySelfDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override @Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) { public boolean check(SystemLoginUser loginUser, DataCondition condition) {
if (condition == null || loginUser == null) { if (condition == null || loginUser == null) {
@@ -1,42 +0,0 @@
package com.agileboot.admin.customize.service.permission.model.checker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Objects;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* 数据权限测试接口
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@AllArgsConstructor
@NoArgsConstructor
public class SingleDeptDataPermissionChecker extends AbstractDataPermissionChecker {
private SysDeptService deptService;
@Override
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
if (condition == null || loginUser == null) {
return false;
}
if (loginUser.getDeptId() == null || condition.getTargetDeptId() == null) {
return false;
}
Long currentDeptId = loginUser.getDeptId();
Long targetDeptId = condition.getTargetDeptId();
return Objects.equals(currentDeptId, targetDeptId);
}
}
@@ -50,7 +50,7 @@ spring:
datasource: datasource:
# 主库数据源 # 主库数据源
master: master:
url: jdbc:mysql://localhost:33061/todo_agileboot_pure?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 url: jdbc:mysql://localhost:3306/todo_agileboot_pure?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root username: root
password: root123 password: root123
# 从库数据源 # 从库数据源
@@ -64,7 +64,7 @@ spring:
# 地址 # 地址
host: localhost host: localhost
# 端口,默认为6379 # 端口,默认为6379
port: 63791 port: 6379
# 数据库索引 # 数据库索引
database: 0 database: 0
# 密码 # 密码
@@ -0,0 +1,65 @@
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
webStatFilter:
enabled: true
statViewServlet:
enabled: false
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic:
primary: master
strict: false
druid:
initialSize: 5
minIdle: 10
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
maxEvictableIdleTimeMillis: 900000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
datasource:
master:
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/${MYSQL_DATABASE:todo_agileboot_pure}?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
username: ${MYSQL_USERNAME:todo_app}
password: ${MYSQL_PASSWORD:todo_app123}
redis:
host: ${REDIS_HOST:redis}
port: ${REDIS_PORT:6379}
database: 0
password: ${REDIS_PASSWORD:redis123}
timeout: 10s
lettuce:
pool:
min-idle: 0
max-idle: 8
max-active: 8
max-wait: -1ms
logging:
file:
path: /home/agileboot/logs/agileboot-prod
springdoc:
swagger-ui:
enabled: false
agileboot:
file-base-dir: /home/agileboot
api-prefix: /dev-api
demo-enabled: false
@@ -1,83 +0,0 @@
package com.agileboot.admin.customize.service.permission;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.agileboot.admin.customize.service.permission.model.checker.CustomDataPermissionChecker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.infrastructure.user.web.RoleInfo;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import org.apache.commons.collections4.SetUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class CustomDataPermissionCheckerTest {
private final SysDeptService deptService = mock(SysDeptService.class);
public SystemLoginUser loginUser = mock(SystemLoginUser.class);
@BeforeEach
public void mockBefore() {
when(loginUser.getRoleInfo()).thenReturn(RoleInfo.EMPTY_ROLE);
}
@Test
void testCheckWhenParameterNull() {
CustomDataPermissionChecker customChecker = new CustomDataPermissionChecker(deptService);
boolean check1 = customChecker.check(null, null);
boolean check2 = customChecker.check(loginUser, null);
boolean check3 = customChecker.check(null, new DataCondition());
assertFalse(check1);
assertFalse(check2);
assertFalse(check3);
}
@Test
void testCheckWhenTargetDeptIdNull() {
CustomDataPermissionChecker customChecker = new CustomDataPermissionChecker(deptService);
boolean check = customChecker.check(loginUser, new DataCondition(null, 1L));
assertFalse(check);
}
@Test
void testCheckWhenRoleIsNull() {
CustomDataPermissionChecker customChecker = new CustomDataPermissionChecker(deptService);
when(loginUser.getRoleInfo()).thenReturn(null);
boolean check = customChecker.check(loginUser, new DataCondition(1L, 1L));
assertFalse(check);
}
@Test
void testCheckWhenNotContainTargetDeptId() {
CustomDataPermissionChecker customChecker = new CustomDataPermissionChecker(deptService);
loginUser.getRoleInfo().setDeptIdSet(SetUtils.hashSet(2L));
boolean check = customChecker.check(loginUser, new DataCondition(1L, 1L));
assertFalse(check);
}
@Test
void testCheckWhenContainTargetDeptId() {
CustomDataPermissionChecker customChecker = new CustomDataPermissionChecker(deptService);
loginUser.getRoleInfo().setDeptIdSet(SetUtils.hashSet(1L));
boolean check = customChecker.check(loginUser, new DataCondition(1L, 1L));
assertTrue(check);
}
}
@@ -1,92 +0,0 @@
package com.agileboot.admin.customize.service.permission;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.agileboot.admin.customize.service.permission.model.checker.DeptTreeDataPermissionChecker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.infrastructure.user.web.RoleInfo;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class DeptTreeDataPermissionCheckerTest {
private final SysDeptService deptService = mock(SysDeptService.class);
public SystemLoginUser loginUser = mock(SystemLoginUser.class);
@BeforeEach
public void mockBefore() {
when(loginUser.getRoleInfo()).thenReturn(RoleInfo.EMPTY_ROLE);
}
@Test
void testCheckWhenParameterNull() {
DeptTreeDataPermissionChecker checker = new DeptTreeDataPermissionChecker(deptService);
boolean check1 = checker.check(null, null);
boolean check2 = checker.check(new SystemLoginUser(), null);
boolean check3 = checker.check(null, new DataCondition());
boolean check4 = checker.check(loginUser, new DataCondition());
assertFalse(check1);
assertFalse(check2);
assertFalse(check3);
assertFalse(check4);
}
@Test
void testCheckWhenIsChildOfDept() {
DeptTreeDataPermissionChecker checker = new DeptTreeDataPermissionChecker(deptService);
when(deptService.isChildOfTheDept(any(), any())).thenReturn(true);
when(loginUser.getDeptId()).thenReturn(1L);
DataCondition dataCondition = new DataCondition();
dataCondition.setTargetDeptId(2L);
boolean check = checker.check(loginUser, dataCondition);
assertTrue(check);
}
@Test
void testCheckWhenIsSameDept() {
DeptTreeDataPermissionChecker checker = new DeptTreeDataPermissionChecker(deptService);
when(deptService.isChildOfTheDept(any(), any())).thenReturn(false);
when(loginUser.getDeptId()).thenReturn(1L);
DataCondition dataCondition = new DataCondition();
dataCondition.setTargetDeptId(1L);
boolean check = checker.check(loginUser, dataCondition);
assertTrue(check);
}
@Test
void testCheckWhenFailed() {
DeptTreeDataPermissionChecker checker = new DeptTreeDataPermissionChecker(deptService);
when(deptService.isChildOfTheDept(any(), any())).thenReturn(false);
when(loginUser.getDeptId()).thenReturn(1L);
DataCondition dataCondition = new DataCondition();
dataCondition.setTargetDeptId(2L);
boolean check = checker.check(loginUser, dataCondition);
assertFalse(check);
}
}
@@ -2,21 +2,17 @@ package com.agileboot.admin.customize.service.permission;
import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import com.agileboot.admin.customize.service.permission.model.checker.OnlySelfDataPermissionChecker; import com.agileboot.admin.customize.service.permission.model.checker.OnlySelfDataPermissionChecker;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.admin.customize.service.permission.model.DataCondition; import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
class OnlySelfDataPermissionCheckerTest { class OnlySelfDataPermissionCheckerTest {
private final SysDeptService deptService = mock(SysDeptService.class);
@Test @Test
void testCheckWhenParameterNull() { void testCheckWhenParameterNull() {
OnlySelfDataPermissionChecker checker = new OnlySelfDataPermissionChecker(deptService); OnlySelfDataPermissionChecker checker = new OnlySelfDataPermissionChecker();
boolean check1 = checker.check(null, null); boolean check1 = checker.check(null, null);
boolean check2 = checker.check(new SystemLoginUser(), null); boolean check2 = checker.check(new SystemLoginUser(), null);
@@ -31,7 +27,7 @@ class OnlySelfDataPermissionCheckerTest {
@Test @Test
void testCheckWhenSameUserId() { void testCheckWhenSameUserId() {
OnlySelfDataPermissionChecker checker = new OnlySelfDataPermissionChecker(deptService); OnlySelfDataPermissionChecker checker = new OnlySelfDataPermissionChecker();
SystemLoginUser loginUser = new SystemLoginUser(); SystemLoginUser loginUser = new SystemLoginUser();
loginUser.setUserId(1L); loginUser.setUserId(1L);
DataCondition dataCondition = new DataCondition(); DataCondition dataCondition = new DataCondition();
@@ -45,11 +41,11 @@ class OnlySelfDataPermissionCheckerTest {
@Test @Test
void testCheckWhenDifferentUserId() { void testCheckWhenDifferentUserId() {
OnlySelfDataPermissionChecker checker = new OnlySelfDataPermissionChecker(deptService); OnlySelfDataPermissionChecker checker = new OnlySelfDataPermissionChecker();
SystemLoginUser loginUser = new SystemLoginUser(); SystemLoginUser loginUser = new SystemLoginUser();
loginUser.setUserId(1L); loginUser.setUserId(1L);
DataCondition dataCondition = new DataCondition(); DataCondition dataCondition = new DataCondition();
dataCondition.setTargetDeptId(2L); dataCondition.setTargetUserId(2L);
boolean check = checker.check(loginUser, dataCondition); boolean check = checker.check(loginUser, dataCondition);
@@ -1,72 +0,0 @@
package com.agileboot.admin.customize.service.permission;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.agileboot.admin.customize.service.permission.model.checker.SingleDeptDataPermissionChecker;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.infrastructure.user.web.RoleInfo;
import com.agileboot.admin.customize.service.permission.model.DataCondition;
import com.agileboot.domain.system.dept.db.SysDeptService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
class SingleDeptDataPermissionCheckerTest {
private final SysDeptService deptService = mock(SysDeptService.class);
public SystemLoginUser loginUser = mock(SystemLoginUser.class);
@BeforeEach
public void mockBefore() {
when(loginUser.getRoleInfo()).thenReturn(RoleInfo.EMPTY_ROLE);
}
@Test
void testCheckWhenParameterNull() {
SingleDeptDataPermissionChecker checker = new SingleDeptDataPermissionChecker(deptService);
boolean check1 = checker.check(null, null);
boolean check2 = checker.check(new SystemLoginUser(), null);
boolean check3 = checker.check(null, new DataCondition());
boolean check4 = checker.check(loginUser, new DataCondition());
assertFalse(check1);
assertFalse(check2);
assertFalse(check3);
assertFalse(check4);
}
@Test
void testCheckWhenSameDeptId() {
SingleDeptDataPermissionChecker checker = new SingleDeptDataPermissionChecker(deptService);
when(loginUser.getDeptId()).thenReturn(1L);
DataCondition dataCondition = new DataCondition();
dataCondition.setTargetDeptId(1L);
boolean check = checker.check(loginUser, dataCondition);
assertTrue(check);
}
@Test
void testCheckWhenDifferentDeptId() {
SingleDeptDataPermissionChecker checker = new SingleDeptDataPermissionChecker(deptService);
when(loginUser.getDeptId()).thenReturn(1L);
DataCondition dataCondition = new DataCondition();
dataCondition.setTargetUserId(2L);
boolean check = checker.check(loginUser, dataCondition);
assertFalse(check);
}
}
@@ -3,18 +3,13 @@ package com.agileboot.common.enums.common;
import com.agileboot.common.enums.BasicEnum; import com.agileboot.common.enums.BasicEnum;
/** /**
* 系统配置 * 系统配置,对应 sys_config 表的 config_key 字段。
*
* @author valarchie * @author valarchie
* 对应 sys_config表的config_key字段
*/ */
public enum ConfigKeyEnum implements BasicEnum<String> { public enum ConfigKeyEnum implements BasicEnum<String> {
/**
* 菜单类型
*/
SKIN_THEME("sys.index.skinName", "系统皮肤主题"),
INIT_PASSWORD("sys.user.initPassword", "初始密码"), INIT_PASSWORD("sys.user.initPassword", "初始密码"),
SIDE_BAR_THEME("sys.index.sideTheme", "侧边栏开关"),
CAPTCHA("sys.account.captchaOnOff", "验证码开关"), CAPTCHA("sys.account.captchaOnOff", "验证码开关"),
REGISTER("sys.account.registerUser", "注册开放功能"); REGISTER("sys.account.registerUser", "注册开放功能");
@@ -36,5 +31,4 @@ public enum ConfigKeyEnum implements BasicEnum<String> {
return description; return description;
} }
} }
@@ -118,28 +118,6 @@ public enum ErrorCode implements ErrorCodeInterface {
CONFIG_VALUE_IS_NOT_IN_OPTIONS(10602, "参数键值不存在列表中", "Business.CONFIG_VALUE_IS_NOT_IN_OPTIONS"), CONFIG_VALUE_IS_NOT_IN_OPTIONS(10602, "参数键值不存在列表中", "Business.CONFIG_VALUE_IS_NOT_IN_OPTIONS"),
// ------------------------------- POST --------------------------------------------
POST_NAME_IS_NOT_UNIQUE(10701, "岗位名称:{}, 已存在", "Business.POST_NAME_IS_NOT_UNIQUE"),
POST_CODE_IS_NOT_UNIQUE(10702, "岗位编号:{}, 已存在", "Business.POST_CODE_IS_NOT_UNIQUE"),
POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED(10703, "职位已分配给用户,请先取消分配再删除", "Business.POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED"),
// ------------------------------- DEPT ---------------------------------------------
DEPT_NAME_IS_NOT_UNIQUE(10801, "部门名称:{}, 已存在", "Business.DEPT_NAME_IS_NOT_UNIQUE"),
DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF(10802, "父级部门不能选择自己", "Business.DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF"),
DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE(10803, "子部门还有正在启用的部门,暂时不能停用该部门", "Business.DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE"),
DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE(10804, "该部门存在下级部门不允许删除", "Business.DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE"),
DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE(10805, "该部门存在关联的用户不允许删除", "Business.DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE"),
DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED(10806, "该父级部门不存在或已停用", "Business.DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED"),
// ------------------------------- MENU ------------------------------------------------- // ------------------------------- MENU -------------------------------------------------
MENU_NAME_IS_NOT_UNIQUE(10901, "新增菜单:{} 失败,菜单名称已存在", "Business.MENU_NAME_IS_NOT_UNIQUE"), MENU_NAME_IS_NOT_UNIQUE(10901, "新增菜单:{} 失败,菜单名称已存在", "Business.MENU_NAME_IS_NOT_UNIQUE"),
@@ -164,8 +142,6 @@ public enum ErrorCode implements ErrorCodeInterface {
ROLE_KEY_IS_NOT_UNIQUE(11002, "角色标识:{}, 已存在", "Business.ROLE_KEY_IS_NOT_UNIQUE"), ROLE_KEY_IS_NOT_UNIQUE(11002, "角色标识:{}, 已存在", "Business.ROLE_KEY_IS_NOT_UNIQUE"),
ROLE_DATA_SCOPE_DUPLICATED_DEPT(11003, "重复的部门id", "Business.ROLE_DATA_SCOPE_DUPLICATED_DEPT"),
ROLE_ALREADY_ASSIGN_TO_USER(11004, "角色已分配给用户,请先取消分配,再删除角色", "Business.ROLE_ALREADY_ASSIGN_TO_USER"), ROLE_ALREADY_ASSIGN_TO_USER(11004, "角色已分配给用户,请先取消分配,再删除角色", "Business.ROLE_ALREADY_ASSIGN_TO_USER"),
ROLE_IS_NOT_AVAILABLE(11005, "角色:{} 已禁用,无法分配给用户", "Business.ROLE_IS_NOT_AVAILABLE"), ROLE_IS_NOT_AVAILABLE(11005, "角色:{} 已禁用,无法分配给用户", "Business.ROLE_IS_NOT_AVAILABLE"),
@@ -180,8 +156,6 @@ public enum ErrorCode implements ErrorCodeInterface {
USER_FAIL_TO_GET_USER_ID(11004, "获取用户ID失败", "Business.USER_FAIL_TO_GET_USER_ID"), USER_FAIL_TO_GET_USER_ID(11004, "获取用户ID失败", "Business.USER_FAIL_TO_GET_USER_ID"),
USER_FAIL_TO_GET_DEPT_ID(10504, "获取用户部门ID失败", "Business.USER_FAIL_TO_GET_DEPT_ID"),
USER_FAIL_TO_GET_ACCOUNT(10505, "获取用户账户失败", "Business.USER_FAIL_TO_GET_ACCOUNT"), USER_FAIL_TO_GET_ACCOUNT(10505, "获取用户账户失败", "Business.USER_FAIL_TO_GET_ACCOUNT"),
USER_FAIL_TO_GET_USER_INFO(10506, "获取用户信息失败", "Business.USER_FAIL_TO_GET_USER_INFO"), USER_FAIL_TO_GET_USER_INFO(10506, "获取用户信息失败", "Business.USER_FAIL_TO_GET_USER_INFO"),
@@ -204,6 +178,12 @@ public enum ErrorCode implements ErrorCodeInterface {
USER_ADMIN_CAN_NOT_BE_MODIFY(10515, "管理员不允许做任何修改", "Business.USER_ADMIN_CAN_NOT_BE_MODIFY"), USER_ADMIN_CAN_NOT_BE_MODIFY(10515, "管理员不允许做任何修改", "Business.USER_ADMIN_CAN_NOT_BE_MODIFY"),
USER_REGISTER_IS_CLOSED(10516, "当前系统未开启用户注册功能", "Business.USER_REGISTER_IS_CLOSED"),
USER_REGISTER_PASSWORD_NOT_MATCH(10517, "两次输入的密码不一致", "Business.USER_REGISTER_PASSWORD_NOT_MATCH"),
USER_PASSWORD_IS_EMPTY(10518, "密码不能为空", "Business.USER_PASSWORD_IS_EMPTY"),
; ;
@@ -1,6 +1,9 @@
package com.agileboot.domain.collaboration.record; package com.agileboot.domain.collaboration.record;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.PageDTO; import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.collaboration.record.command.AddCollaborationRecordCommand; import com.agileboot.domain.collaboration.record.command.AddCollaborationRecordCommand;
import com.agileboot.domain.collaboration.record.command.CollaborationExpenditureCommand; import com.agileboot.domain.collaboration.record.command.CollaborationExpenditureCommand;
import com.agileboot.domain.collaboration.record.command.CollaborationFileCommand; import com.agileboot.domain.collaboration.record.command.CollaborationFileCommand;
@@ -31,7 +34,11 @@ import com.agileboot.domain.collaboration.record.enumtype.SettlementStatusEnum;
import com.agileboot.domain.collaboration.record.model.CollaborationRecordModel; import com.agileboot.domain.collaboration.record.model.CollaborationRecordModel;
import com.agileboot.domain.collaboration.record.model.CollaborationRecordModelFactory; import com.agileboot.domain.collaboration.record.model.CollaborationRecordModelFactory;
import com.agileboot.domain.collaboration.record.query.CollaborationRecordQuery; import com.agileboot.domain.collaboration.record.query.CollaborationRecordQuery;
import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.common.command.BulkOperationCommand; import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.math.BigDecimal; import java.math.BigDecimal;
@@ -42,6 +49,7 @@ import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -57,6 +65,8 @@ public class CollaborationRecordApplicationService {
private static final String PURCHASE_FEE = "拍单费用"; private static final String PURCHASE_FEE = "拍单费用";
private static final String DELIVERY_FEE = "快递费用"; private static final String DELIVERY_FEE = "快递费用";
private static final String REMUNERATION_FEE = "稿费"; private static final String REMUNERATION_FEE = "稿费";
private static final String TASK_STATUS_COMPLETED = "COMPLETED";
private static final String TASK_STATUS_INCOMPLETE = "INCOMPLETE";
private final CollaborationRecordModelFactory recordModelFactory; private final CollaborationRecordModelFactory recordModelFactory;
private final CollaborationRecordService recordService; private final CollaborationRecordService recordService;
@@ -66,7 +76,10 @@ public class CollaborationRecordApplicationService {
private final CollaborationFileService fileService; private final CollaborationFileService fileService;
public PageDTO<CollaborationRecordDTO> getRecordList(CollaborationRecordQuery query) { public PageDTO<CollaborationRecordDTO> getRecordList(CollaborationRecordQuery query) {
Page<CollaborationRecordEntity> page = recordService.page(query.toPage(), query.toQueryWrapper()); if (hasComputedFilters(query)) {
return getComputedFilteredRecordList(query);
}
Page<CollaborationRecordEntity> page = recordService.page(query.toPage(), getScopedRecordWrapper(query));
List<CollaborationRecordDTO> records = page.getRecords().stream() List<CollaborationRecordDTO> records = page.getRecords().stream()
.map(this::buildRecordDTO) .map(this::buildRecordDTO)
.collect(Collectors.toList()); .collect(Collectors.toList());
@@ -74,7 +87,7 @@ public class CollaborationRecordApplicationService {
} }
public CollaborationRecordDetailDTO getRecordInfo(Long recordId) { public CollaborationRecordDetailDTO getRecordInfo(Long recordId) {
CollaborationRecordModel model = recordModelFactory.loadById(recordId); CollaborationRecordModel model = loadRecordInScope(recordId);
CollaborationRecordDetailDTO dto = new CollaborationRecordDetailDTO(model); CollaborationRecordDetailDTO dto = new CollaborationRecordDetailDTO(model);
fillDetailChildren(dto, recordId); fillDetailChildren(dto, recordId);
fillRecordStats(dto, recordId); fillRecordStats(dto, recordId);
@@ -85,14 +98,16 @@ public class CollaborationRecordApplicationService {
public void addRecord(AddCollaborationRecordCommand command) { public void addRecord(AddCollaborationRecordCommand command) {
CollaborationRecordModel model = recordModelFactory.create(); CollaborationRecordModel model = recordModelFactory.create();
model.loadFromAddCommand(command); model.loadFromAddCommand(command);
model.setCompleteDate(getRecordCompleteDate(command.getTasks(), CollaborationTaskCommand::getReleaseDate));
model.saveRecord(); model.saveRecord();
saveChildren(model.getRecordId(), command); saveChildren(model.getRecordId(), command);
} }
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void updateRecord(UpdateCollaborationRecordCommand command) { public void updateRecord(UpdateCollaborationRecordCommand command) {
CollaborationRecordModel model = recordModelFactory.loadById(command.getRecordId()); CollaborationRecordModel model = loadRecordInScope(command.getRecordId());
model.loadFromUpdateCommand(command); model.loadFromUpdateCommand(command);
model.setCompleteDate(getRecordCompleteDate(command.getTasks(), CollaborationTaskCommand::getReleaseDate));
model.updateRecord(); model.updateRecord();
replaceChildren(command.getRecordId(), command); replaceChildren(command.getRecordId(), command);
} }
@@ -103,6 +118,7 @@ public class CollaborationRecordApplicationService {
if (ids == null || ids.isEmpty()) { if (ids == null || ids.isEmpty()) {
return; return;
} }
checkAllRecordsInScope(ids);
taskService.removeByRecordIds(ids); taskService.removeByRecordIds(ids);
expenditureService.removeByRecordIds(ids); expenditureService.removeByRecordIds(ids);
settlementService.removeByRecordIds(ids); settlementService.removeByRecordIds(ids);
@@ -116,20 +132,145 @@ public class CollaborationRecordApplicationService {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public List<CollaborationMonthlyStatisticsDTO> getMonthlyStatistics(Integer year) { public List<CollaborationMonthlyStatisticsDTO> getMonthlyStatistics(Integer year, Long creatorId) {
List<CollaborationMonthlyStatisticsDTO> statistics = new ArrayList<>(); List<CollaborationMonthlyStatisticsDTO> statistics = new ArrayList<>();
for (int month = 1; month <= 12; month++) { for (int month = 1; month <= 12; month++) {
statistics.add(buildMonthlyStatistics(year, month)); statistics.add(buildMonthlyStatistics(year, month, creatorId));
} }
return statistics; return statistics;
} }
public List<CollaborationMonthlyStatisticsDTO> getMonthlyStatistics(Integer year) {
return getMonthlyStatistics(year, null);
}
private QueryWrapper<CollaborationRecordEntity> getScopedRecordWrapper(CollaborationRecordQuery query) {
applyQueryCreatorScope(query);
QueryWrapper<CollaborationRecordEntity> wrapper = query.toQueryWrapper();
return wrapper;
}
private PageDTO<CollaborationRecordDTO> getComputedFilteredRecordList(CollaborationRecordQuery query) {
List<CollaborationRecordDTO> records = recordService.list(getScopedRecordWrapper(query)).stream()
.map(this::buildRecordDTO)
.filter(record -> matchesComputedFilters(record, query))
.collect(Collectors.toList());
return new PageDTO<>(pageRecords(records, query), (long) records.size());
}
private List<CollaborationRecordDTO> pageRecords(List<CollaborationRecordDTO> records, CollaborationRecordQuery query) {
Page<CollaborationRecordEntity> page = query.toPage();
int start = (int) Math.min((page.getCurrent() - 1) * page.getSize(), records.size());
int end = (int) Math.min(start + page.getSize(), records.size());
return records.subList(start, end);
}
private boolean hasComputedFilters(CollaborationRecordQuery query) {
return StrUtil.isNotEmpty(query.getSettlementStatus()) || StrUtil.isNotEmpty(query.getTaskStatus());
}
private boolean matchesComputedFilters(CollaborationRecordDTO record, CollaborationRecordQuery query) {
return matchesSettlementStatus(record, query.getSettlementStatus())
&& matchesTaskStatus(record, query.getTaskStatus());
}
private boolean matchesSettlementStatus(CollaborationRecordDTO record, String status) {
if (StrUtil.isEmpty(status)) {
return true;
}
return status.equals(getRecordSettlementStatus(record));
}
private String getRecordSettlementStatus(CollaborationRecordDTO record) {
return SettlementStatusEnum.aggregate(Arrays.asList(
getSettlementStatusValue(record.getPurchaseSettlementStatus()),
getSettlementStatusValue(record.getDeliverySettlementStatus()),
getSettlementStatusValue(record.getRemunerationSettlementStatus())
));
}
private String getSettlementStatusValue(SettlementStatusDTO status) {
return status == null ? SettlementStatusEnum.NONE.getValue() : status.getStatus();
}
private boolean matchesTaskStatus(CollaborationRecordDTO record, String status) {
if (StrUtil.isEmpty(status)) {
return true;
}
boolean isCompleted = isRecordCompleted(record);
if (TASK_STATUS_COMPLETED.equals(status)) {
return isCompleted;
}
return TASK_STATUS_INCOMPLETE.equals(status) && !isCompleted;
}
private boolean isRecordCompleted(CollaborationRecordDTO record) {
Integer tasksNum = record.getTasksNum();
Integer completedTasksNum = record.getCompletedTasksNum();
return tasksNum != null && tasksNum > 0 && tasksNum.equals(completedTasksNum);
}
private CollaborationRecordModel loadRecordInScope(Long recordId) {
CollaborationRecordEntity entity = recordService.getOne(buildScopedRecordIdWrapper(recordId));
if (entity == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, recordId, "合作记录");
}
return new CollaborationRecordModel(entity, recordService);
}
private QueryWrapper<CollaborationRecordEntity> buildScopedRecordIdWrapper(Long recordId) {
QueryWrapper<CollaborationRecordEntity> wrapper = new QueryWrapper<CollaborationRecordEntity>()
.eq("record_id", recordId);
applyCurrentCreatorScope(wrapper);
return wrapper;
}
private void checkAllRecordsInScope(Set<Long> ids) {
QueryWrapper<CollaborationRecordEntity> wrapper = new QueryWrapper<CollaborationRecordEntity>()
.in("record_id", ids);
applyCurrentCreatorScope(wrapper);
if (recordService.count(wrapper) != ids.size()) {
throw new ApiException(ErrorCode.Business.PERMISSION_NOT_ALLOWED_TO_OPERATE);
}
}
private void applyCurrentCreatorScope(QueryWrapper<CollaborationRecordEntity> wrapper) {
Long creatorId = getCurrentCreatorScope();
if (creatorId != null) {
wrapper.eq("creator_id", creatorId);
}
}
private void applyQueryCreatorScope(CollaborationRecordQuery query) {
Long creatorId = getCurrentCreatorScope();
if (creatorId != null) {
query.setCreatorId(creatorId);
}
}
private Long getCurrentCreatorScope() {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
return loginUser.isAdmin() ? null : loginUser.getUserId();
}
private CollaborationRecordDTO buildRecordDTO(CollaborationRecordEntity entity) { private CollaborationRecordDTO buildRecordDTO(CollaborationRecordEntity entity) {
CollaborationRecordDTO dto = new CollaborationRecordDTO(entity); CollaborationRecordDTO dto = new CollaborationRecordDTO(entity);
dto.setCreatorName(getCreatorName(entity.getCreatorId()));
fillRecordStats(dto, entity.getRecordId()); fillRecordStats(dto, entity.getRecordId());
return dto; return dto;
} }
private String getCreatorName(Long creatorId) {
if (creatorId == null) {
return "";
}
SysUserEntity user = CacheCenter.userCache.getObjectById(creatorId);
if (user == null) {
return "";
}
return StrUtil.blankToDefault(user.getNickname(), user.getUsername());
}
private void fillDetailChildren(CollaborationRecordDetailDTO dto, Long recordId) { private void fillDetailChildren(CollaborationRecordDetailDTO dto, Long recordId) {
dto.setTasks(taskService.listByRecordId(recordId).stream() dto.setTasks(taskService.listByRecordId(recordId).stream()
.map(CollaborationTaskDTO::new) .map(CollaborationTaskDTO::new)
@@ -151,11 +292,25 @@ public class CollaborationRecordApplicationService {
List<CollaborationSettlementEntity> settlements = settlementService.listByRecordId(recordId); List<CollaborationSettlementEntity> settlements = settlementService.listByRecordId(recordId);
dto.setTasksNum(tasks.size()); dto.setTasksNum(tasks.size());
dto.setCompletedTasksNum((int) tasks.stream().filter(item -> item.getReleaseDate() != null).count()); dto.setCompletedTasksNum((int) tasks.stream().filter(item -> item.getReleaseDate() != null).count());
dto.setCompleteDate(getRecordCompleteDate(tasks, CollaborationTaskEntity::getReleaseDate));
dto.setPurchaseSettlementStatus(getStatus(dto.getPurchasePrice(), settlements, PURCHASE_FEE)); dto.setPurchaseSettlementStatus(getStatus(dto.getPurchasePrice(), settlements, PURCHASE_FEE));
dto.setDeliverySettlementStatus(getStatus(sumExpenditure(expenditures, DELIVERY_FEE), settlements, DELIVERY_FEE)); dto.setDeliverySettlementStatus(getStatus(sumExpenditure(expenditures, DELIVERY_FEE), settlements, DELIVERY_FEE));
dto.setRemunerationSettlementStatus(getStatus(dto.getRemuneration(), settlements, REMUNERATION_FEE)); dto.setRemunerationSettlementStatus(getStatus(dto.getRemuneration(), settlements, REMUNERATION_FEE));
} }
private <T> Date getRecordCompleteDate(List<T> tasks, Function<T, Date> getReleaseDate) {
if (tasks == null || tasks.isEmpty()) {
return null;
}
if (tasks.stream().anyMatch(item -> getReleaseDate.apply(item) == null)) {
return null;
}
return tasks.stream()
.map(getReleaseDate)
.max(Date::compareTo)
.orElse(null);
}
private SettlementStatusDTO getStatus( private SettlementStatusDTO getStatus(
BigDecimal expectedAmount, List<CollaborationSettlementEntity> settlements, String purpose) { BigDecimal expectedAmount, List<CollaborationSettlementEntity> settlements, String purpose) {
List<CollaborationSettlementEntity> matched = filterSettlements(settlements, purpose); List<CollaborationSettlementEntity> matched = filterSettlements(settlements, purpose);
@@ -324,43 +479,74 @@ public class CollaborationRecordApplicationService {
return entity; return entity;
} }
private CollaborationMonthlyStatisticsDTO buildMonthlyStatistics(Integer year, Integer month) { private CollaborationMonthlyStatisticsDTO buildMonthlyStatistics(Integer year, Integer month, Long creatorId) {
Date[] range = getMonthRange(year, month); Date[] range = getMonthRange(year, month);
BigDecimal purchasePrice = sumPurchasePrice(range); BigDecimal purchasePrice = sumPurchasePrice(range, creatorId);
BigDecimal expenditureAmount = sumExpenditure(range); BigDecimal expenditureAmount = sumExpenditure(range, creatorId);
BigDecimal settledRemuneration = sumSettlement(range, REMUNERATION_FEE); BigDecimal settledRemuneration = sumSettlement(range, REMUNERATION_FEE, creatorId);
BigDecimal settledTotal = sumSettlement(range, null); BigDecimal settledTotal = sumSettlement(range, null, creatorId);
return new CollaborationMonthlyStatisticsDTO(month, purchasePrice, expenditureAmount, return new CollaborationMonthlyStatisticsDTO(month, purchasePrice, expenditureAmount,
settledRemuneration, settledTotal); settledRemuneration, settledTotal);
} }
private BigDecimal sumPurchasePrice(Date[] range) { private BigDecimal sumPurchasePrice(Date[] range, Long creatorId) {
List<CollaborationRecordEntity> records = recordService.list(new QueryWrapper<CollaborationRecordEntity>() QueryWrapper<CollaborationRecordEntity> wrapper = new QueryWrapper<CollaborationRecordEntity>()
.ge("purchase_date", range[0]) .ge("purchase_date", range[0])
.le("purchase_date", range[1])); .le("purchase_date", range[1]);
applyCreatorScope(wrapper, creatorId);
List<CollaborationRecordEntity> records = recordService.list(wrapper);
return records.stream() return records.stream()
.map(CollaborationRecordEntity::getPurchasePrice) .map(CollaborationRecordEntity::getPurchasePrice)
.map(this::defaultAmount) .map(this::defaultAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
} }
private BigDecimal sumExpenditure(Date[] range) { private BigDecimal sumExpenditure(Date[] range, Long creatorId) {
List<CollaborationExpenditureEntity> expenditures = expenditureService.list( QueryWrapper<CollaborationExpenditureEntity> wrapper = new QueryWrapper<CollaborationExpenditureEntity>()
new QueryWrapper<CollaborationExpenditureEntity>().ge("spend_date", range[0]).le("spend_date", range[1])); .ge("spend_date", range[0])
.le("spend_date", range[1]);
applyChildRecordScope(wrapper, creatorId);
List<CollaborationExpenditureEntity> expenditures = expenditureService.list(wrapper);
return expenditures.stream() return expenditures.stream()
.map(CollaborationExpenditureEntity::getAmount) .map(CollaborationExpenditureEntity::getAmount)
.map(this::defaultAmount) .map(this::defaultAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
} }
private BigDecimal sumSettlement(Date[] range, String purpose) { private BigDecimal sumSettlement(Date[] range, String purpose, Long creatorId) {
QueryWrapper<CollaborationSettlementEntity> wrapper = new QueryWrapper<CollaborationSettlementEntity>() QueryWrapper<CollaborationSettlementEntity> wrapper = new QueryWrapper<CollaborationSettlementEntity>()
.ge("settle_date", range[0]) .ge("settle_date", range[0])
.le("settle_date", range[1]) .le("settle_date", range[1])
.eq(purpose != null, "purpose", purpose); .eq(purpose != null, "purpose", purpose);
applyChildRecordScope(wrapper, creatorId);
return sumSettlement(settlementService.list(wrapper)); return sumSettlement(settlementService.list(wrapper));
} }
private void applyChildRecordScope(QueryWrapper<?> wrapper, Long requestedCreatorId) {
Long creatorId = getEffectiveCreatorScope(requestedCreatorId);
if (creatorId == null) {
return;
}
wrapper.inSql("record_id", buildCreatorRecordSubQuery(creatorId));
}
private void applyCreatorScope(QueryWrapper<CollaborationRecordEntity> wrapper, Long requestedCreatorId) {
Long creatorId = getEffectiveCreatorScope(requestedCreatorId);
if (creatorId == null) {
return;
}
wrapper.eq("creator_id", creatorId);
}
private Long getEffectiveCreatorScope(Long requestedCreatorId) {
Long currentCreatorScope = getCurrentCreatorScope();
return currentCreatorScope == null ? requestedCreatorId : currentCreatorScope;
}
private String buildCreatorRecordSubQuery(Long creatorId) {
return "select record_id from collaboration_record where deleted = 0 and creator_id = " + creatorId;
}
private BigDecimal sumExpenditure(List<CollaborationExpenditureEntity> expenditures, String purpose) { private BigDecimal sumExpenditure(List<CollaborationExpenditureEntity> expenditures, String purpose) {
return expenditures.stream() return expenditures.stream()
.filter(item -> purpose.equals(item.getPurpose())) .filter(item -> purpose.equals(item.getPurpose()))
@@ -56,6 +56,10 @@ public class CollaborationRecordDTO {
private String remark; private String remark;
private Long creatorId;
private String creatorName;
private Integer tasksNum; private Integer tasksNum;
private Integer completedTasksNum; private Integer completedTasksNum;
@@ -1,5 +1,8 @@
package com.agileboot.domain.collaboration.record.enumtype; package com.agileboot.domain.collaboration.record.enumtype;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Getter; import lombok.Getter;
/** /**
@@ -24,4 +27,31 @@ public enum SettlementStatusEnum {
this.label = label; this.label = label;
} }
public static String aggregate(Collection<String> statuses) {
if (statuses == null || statuses.isEmpty()) {
return NONE.value;
}
List<String> effectiveStatuses = statuses.stream()
.filter(SettlementStatusEnum::isEffectiveStatus)
.collect(Collectors.toList());
if (effectiveStatuses.isEmpty()) {
return NONE.value;
}
if (isSameStatus(effectiveStatuses, SETTLED)) {
return SETTLED.value;
}
if (isSameStatus(effectiveStatuses, UNSETTLED)) {
return UNSETTLED.value;
}
return PARTIAL.value;
}
private static boolean isEffectiveStatus(String status) {
return status != null && !NONE.value.equals(status);
}
private static boolean isSameStatus(List<String> statuses, SettlementStatusEnum expected) {
return statuses.stream().allMatch(status -> expected.value.equals(status));
}
} }
@@ -33,7 +33,7 @@ public class CollaborationRecordModel extends CollaborationRecordEntity {
if (command == null) { if (command == null) {
return; return;
} }
BeanUtil.copyProperties(command, this, "recordId"); BeanUtil.copyProperties(command, this, "recordId", "completeDate");
loadDefaultValues(); loadDefaultValues();
} }
@@ -23,6 +23,12 @@ public class CollaborationRecordQuery extends AbstractPageQuery<CollaborationRec
private String cooperationPlatform; private String cooperationPlatform;
private String settlementStatus;
private String taskStatus;
private Long creatorId;
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
private Date purchaseBeginTime; private Date purchaseBeginTime;
@@ -35,6 +41,7 @@ public class CollaborationRecordQuery extends AbstractPageQuery<CollaborationRec
.like(StrUtil.isNotEmpty(brand), "brand", brand) .like(StrUtil.isNotEmpty(brand), "brand", brand)
.like(StrUtil.isNotEmpty(goods), "goods", goods) .like(StrUtil.isNotEmpty(goods), "goods", goods)
.eq(StrUtil.isNotEmpty(cooperationPlatform), "cooperation_platform", cooperationPlatform) .eq(StrUtil.isNotEmpty(cooperationPlatform), "cooperation_platform", cooperationPlatform)
.eq(creatorId != null, "creator_id", creatorId)
.ge(purchaseBeginTime != null, "purchase_date", DatePickUtil.getBeginOfTheDay(purchaseBeginTime)) .ge(purchaseBeginTime != null, "purchase_date", DatePickUtil.getBeginOfTheDay(purchaseBeginTime))
.le(purchaseEndTime != null, "purchase_date", DatePickUtil.getEndOfTheDay(purchaseEndTime)); .le(purchaseEndTime != null, "purchase_date", DatePickUtil.getEndOfTheDay(purchaseEndTime));
@@ -4,8 +4,6 @@ import cn.hutool.extra.spring.SpringUtil;
import com.agileboot.infrastructure.cache.guava.AbstractGuavaCacheTemplate; import com.agileboot.infrastructure.cache.guava.AbstractGuavaCacheTemplate;
import com.agileboot.infrastructure.cache.redis.RedisCacheTemplate; import com.agileboot.infrastructure.cache.redis.RedisCacheTemplate;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity; import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity; import com.agileboot.domain.system.user.db.SysUserEntity;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@@ -22,8 +20,6 @@ public class CacheCenter {
public static AbstractGuavaCacheTemplate<String> configCache; public static AbstractGuavaCacheTemplate<String> configCache;
public static AbstractGuavaCacheTemplate<SysDeptEntity> deptCache;
public static RedisCacheTemplate<String> captchaCache; public static RedisCacheTemplate<String> captchaCache;
public static RedisCacheTemplate<SystemLoginUser> loginUserCache; public static RedisCacheTemplate<SystemLoginUser> loginUserCache;
@@ -32,21 +28,17 @@ public class CacheCenter {
public static RedisCacheTemplate<SysRoleEntity> roleCache; public static RedisCacheTemplate<SysRoleEntity> roleCache;
public static RedisCacheTemplate<SysPostEntity> postCache;
@PostConstruct @PostConstruct
public void init() { public void init() {
GuavaCacheService guavaCache = SpringUtil.getBean(GuavaCacheService.class); GuavaCacheService guavaCache = SpringUtil.getBean(GuavaCacheService.class);
RedisCacheService redisCache = SpringUtil.getBean(RedisCacheService.class); RedisCacheService redisCache = SpringUtil.getBean(RedisCacheService.class);
configCache = guavaCache.configCache; configCache = guavaCache.configCache;
deptCache = guavaCache.deptCache;
captchaCache = redisCache.captchaCache; captchaCache = redisCache.captchaCache;
loginUserCache = redisCache.loginUserCache; loginUserCache = redisCache.loginUserCache;
userCache = redisCache.userCache; userCache = redisCache.userCache;
roleCache = redisCache.roleCache; roleCache = redisCache.roleCache;
postCache = redisCache.postCache;
} }
} }
@@ -2,9 +2,7 @@ package com.agileboot.domain.common.cache;
import com.agileboot.infrastructure.cache.guava.AbstractGuavaCacheTemplate; import com.agileboot.infrastructure.cache.guava.AbstractGuavaCacheTemplate;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.config.db.SysConfigService; import com.agileboot.domain.system.config.db.SysConfigService;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -19,8 +17,6 @@ public class GuavaCacheService {
private final SysConfigService configService; private final SysConfigService configService;
private final SysDeptService deptService;
public final AbstractGuavaCacheTemplate<String> configCache = new AbstractGuavaCacheTemplate<String>() { public final AbstractGuavaCacheTemplate<String> configCache = new AbstractGuavaCacheTemplate<String>() {
@Override @Override
public String getObjectFromDb(Object id) { public String getObjectFromDb(Object id) {
@@ -28,12 +24,4 @@ public class GuavaCacheService {
} }
}; };
public final AbstractGuavaCacheTemplate<SysDeptEntity> deptCache = new AbstractGuavaCacheTemplate<SysDeptEntity>() {
@Override
public SysDeptEntity getObjectFromDb(Object id) {
return deptService.getById(id.toString());
}
};
} }
@@ -5,10 +5,8 @@ import com.agileboot.infrastructure.cache.RedisUtil;
import com.agileboot.infrastructure.cache.redis.CacheKeyEnum; import com.agileboot.infrastructure.cache.redis.CacheKeyEnum;
import com.agileboot.infrastructure.cache.redis.RedisCacheTemplate; import com.agileboot.infrastructure.cache.redis.RedisCacheTemplate;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity; import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity; import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.post.db.SysPostService;
import com.agileboot.domain.system.role.db.SysRoleService; import com.agileboot.domain.system.role.db.SysRoleService;
import com.agileboot.domain.system.user.db.SysUserService; import com.agileboot.domain.system.user.db.SysUserService;
import java.io.Serializable; import java.io.Serializable;
@@ -30,8 +28,6 @@ public class RedisCacheService {
public RedisCacheTemplate<SysUserEntity> userCache; public RedisCacheTemplate<SysUserEntity> userCache;
public RedisCacheTemplate<SysRoleEntity> roleCache; public RedisCacheTemplate<SysRoleEntity> roleCache;
public RedisCacheTemplate<SysPostEntity> postCache;
// public RedisCacheTemplate<RoleInfo> roleModelInfoCache; // public RedisCacheTemplate<RoleInfo> roleModelInfoCache;
@PostConstruct @PostConstruct
@@ -66,16 +62,6 @@ public class RedisCacheService {
// //
// }; // };
postCache = new RedisCacheTemplate<SysPostEntity>(redisUtil, CacheKeyEnum.POST_ENTITY_KEY) {
@Override
public SysPostEntity getObjectFromDb(Object id) {
SysPostService postService = SpringUtil.getBean(SysPostService.class);
return postService.getById((Serializable) id);
}
};
} }
@@ -14,6 +14,5 @@ public class TreeSelectedDTO {
private List<Long> checkedKeys; private List<Long> checkedKeys;
private List<Tree<Long>> menus; private List<Tree<Long>> menus;
private List<Tree<Long>> depts;
} }
@@ -1,88 +0,0 @@
package com.agileboot.domain.system.dept;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeUtil;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.command.UpdateDeptCommand;
import com.agileboot.domain.system.dept.dto.DeptDTO;
import com.agileboot.domain.system.dept.model.DeptModel;
import com.agileboot.domain.system.dept.model.DeptModelFactory;
import com.agileboot.domain.system.dept.query.DeptQuery;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.dept.db.SysDeptService;
import com.agileboot.domain.system.role.db.SysRoleService;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* 部门服务
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class DeptApplicationService {
private final SysDeptService deptService;
private final SysRoleService roleService;
private final DeptModelFactory deptModelFactory;
public List<DeptDTO> getDeptList(DeptQuery query) {
List<SysDeptEntity> list = deptService.list(query.toQueryWrapper());
return list.stream().map(DeptDTO::new).collect(Collectors.toList());
}
public DeptDTO getDeptInfo(Long id) {
SysDeptEntity byId = deptService.getById(id);
return new DeptDTO(byId);
}
public List<Tree<Long>> getDeptTree() {
List<SysDeptEntity> list = deptService.list();
return TreeUtil.build(list, 0L, (dept, tree) -> {
tree.setId(dept.getDeptId());
tree.setParentId(dept.getParentId());
tree.putExtra("label", dept.getDeptName());
});
}
public void addDept(AddDeptCommand addCommand) {
DeptModel deptModel = deptModelFactory.create();
deptModel.loadAddCommand(addCommand);
deptModel.checkDeptNameUnique();
deptModel.generateAncestors();
deptModel.insert();
}
public void updateDept(UpdateDeptCommand updateCommand) {
DeptModel deptModel = deptModelFactory.loadById(updateCommand.getDeptId());
deptModel.loadUpdateCommand(updateCommand);
deptModel.checkDeptNameUnique();
deptModel.checkParentIdConflict();
deptModel.checkStatusAllowChange();
deptModel.generateAncestors();
deptModel.updateById();
}
public void removeDept(Long deptId) {
DeptModel deptModel = deptModelFactory.loadById(deptId);
deptModel.checkHasChildDept();
deptModel.checkDeptAssignedToUsers();
deptModel.deleteById();
}
}
@@ -1,58 +0,0 @@
package com.agileboot.domain.system.dept.command;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class AddDeptCommand {
/**
* 父部门ID
*/
@NotNull
@PositiveOrZero
private Long parentId;
/**
* 部门名称
*/
@NotBlank(message = "部门名称不能为空")
@Size(max = 30, message = "部门名称长度不能超过30个字符")
private String deptName;
/**
* 显示顺序
*/
@NotNull(message = "显示顺序不能为空")
private Integer orderNum;
/**
* 负责人
*/
private String leaderName;
/**
* 联系电话
*/
@Size(max = 11, message = "联系电话长度不能超过11个字符")
private String phone;
/**
* 邮箱
*/
@Email(message = "邮箱格式不正确")
@Size(max = 50, message = "邮箱长度不能超过50个字符")
private String email;
private Integer status;
}
@@ -1,19 +0,0 @@
package com.agileboot.domain.system.dept.command;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UpdateDeptCommand extends AddDeptCommand {
@NotNull
@PositiveOrZero
private Long deptId;
}
@@ -1,75 +0,0 @@
package com.agileboot.domain.system.dept.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 部门表
* </p>
*
* @author valarchie
* @since 2022-10-02
*/
@Getter
@Setter
@TableName("sys_dept")
@ApiModel(value = "SysDeptEntity对象", description = "部门表")
public class SysDeptEntity extends BaseEntity<SysDeptEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("部门id")
@TableId(value = "dept_id", type = IdType.AUTO)
private Long deptId;
@ApiModelProperty("父部门id")
@TableField("parent_id")
private Long parentId;
@ApiModelProperty("祖级列表")
@TableField("ancestors")
private String ancestors;
@ApiModelProperty("部门名称")
@TableField("dept_name")
private String deptName;
@ApiModelProperty("显示顺序")
@TableField("order_num")
private Integer orderNum;
@TableField("leader_id")
private Long leaderId;
@ApiModelProperty("负责人")
@TableField("leader_name")
private String leaderName;
@ApiModelProperty("联系电话")
@TableField("phone")
private String phone;
@ApiModelProperty("邮箱")
@TableField("email")
private String email;
@ApiModelProperty("部门状态(0正常 1停用)")
@TableField("`status`")
private Integer status;
@Override
public Serializable pkVal() {
return this.deptId;
}
}
@@ -1,15 +0,0 @@
package com.agileboot.domain.system.dept.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 部门表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysDeptMapper extends BaseMapper<SysDeptEntity> {
}
@@ -1,50 +0,0 @@
package com.agileboot.domain.system.dept.db;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 部门表 服务类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysDeptService extends IService<SysDeptEntity> {
/**
* 检测部门名称是否一致
*
* @param deptName 部门名称
* @param deptId 部门id
* @param parentId 父级部门id
* @return 校验结果
*/
boolean isDeptNameDuplicated(String deptName, Long deptId, Long parentId);
/**
* 检测部门底下是否还有正在使用中的子部门
* @param deptId 部门id
* @param enabled 部门是否开启
* @return 结果
*/
boolean hasChildrenDept(Long deptId, Boolean enabled);
/**
* 是否是目标部门的子部门
* @param parentId 目标部门id
* @param childId 子部门id
* @return 校验结果
*/
boolean isChildOfTheDept(Long parentId, Long childId);
/**
* 检测该部门是否已有用户使用
* @param deptId 部门id
* @return 校验结果
*/
boolean isDeptAssignedToUsers(Long deptId);
}
@@ -1,62 +0,0 @@
package com.agileboot.domain.system.dept.db;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.user.db.SysUserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* <p>
* 部门表 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
@Service
@RequiredArgsConstructor
public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDeptEntity> implements SysDeptService {
private final SysUserMapper userMapper;
@Override
public boolean isDeptNameDuplicated(String deptName, Long deptId, Long parentId) {
QueryWrapper<SysDeptEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("dept_name", deptName)
.ne(deptId != null, "dept_id", deptId)
.eq(parentId != null, "parent_id", parentId);
return this.baseMapper.exists(queryWrapper);
}
@Override
public boolean hasChildrenDept(Long deptId, Boolean enabled) {
QueryWrapper<SysDeptEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(enabled != null, "status", 1)
.and(o -> o.eq("parent_id", deptId).or()
.apply("FIND_IN_SET (" + deptId + " , ancestors)")
);
return this.baseMapper.exists(queryWrapper);
}
@Override
public boolean isChildOfTheDept(Long parentId, Long childId) {
QueryWrapper<SysDeptEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.apply("dept_id = '" + childId + "' and FIND_IN_SET ( " + parentId + " , ancestors)");
return this.baseMapper.exists(queryWrapper);
}
@Override
public boolean isDeptAssignedToUsers(Long deptId) {
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("dept_id", deptId);
return userMapper.exists(queryWrapper);
}
}
@@ -1,51 +0,0 @@
package com.agileboot.domain.system.dept.dto;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import java.util.Date;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class DeptDTO {
public DeptDTO(SysDeptEntity entity) {
if (entity != null) {
this.id = entity.getDeptId();
this.parentId = entity.getParentId();
this.deptName = entity.getDeptName();
this.orderNum = entity.getOrderNum();
this.leaderName = entity.getLeaderName();
this.email = entity.getEmail();
this.phone = entity.getPhone();
this.status = entity.getStatus();
this.createTime = entity.getCreateTime();
this.statusStr = BasicEnumUtil.getDescriptionByValue(StatusEnum.class, entity.getStatus());
}
}
private Long id;
private Long parentId;
private String deptName;
private Integer orderNum;
private String leaderName;
private String phone;
private String email;
private Integer status;
private String statusStr;
private Date createTime;
}
@@ -1,106 +0,0 @@
package com.agileboot.domain.system.dept.model;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.convert.Convert;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.command.UpdateDeptCommand;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.dept.db.SysDeptService;
import java.util.Objects;
/**
* @author valarchie
*/
public class DeptModel extends SysDeptEntity {
private final SysDeptService deptService;
public DeptModel(SysDeptService deptService) {
this.deptService = deptService;
}
public DeptModel(SysDeptEntity entity, SysDeptService deptService) {
if (entity != null) {
// 如果大数据量的话 可以用MapStruct优化
BeanUtil.copyProperties(entity, this);
}
this.deptService = deptService;
}
public void loadAddCommand(AddDeptCommand addCommand) {
this.setParentId(addCommand.getParentId());
this.setDeptName(addCommand.getDeptName());
this.setOrderNum(addCommand.getOrderNum());
this.setLeaderName(addCommand.getLeaderName());
this.setPhone(addCommand.getPhone());
this.setEmail(addCommand.getEmail());
this.setStatus(addCommand.getStatus());
}
public void loadUpdateCommand(UpdateDeptCommand updateCommand) {
loadAddCommand(updateCommand);
setStatus(Convert.toInt(updateCommand.getStatus(), 0));
}
public void checkDeptNameUnique() {
if (deptService.isDeptNameDuplicated(getDeptName(), getDeptId(), getParentId())) {
throw new ApiException(ErrorCode.Business.DEPT_NAME_IS_NOT_UNIQUE, getDeptName());
}
}
public void checkParentIdConflict() {
if (Objects.equals(getParentId(), getDeptId())) {
throw new ApiException(ErrorCode.Business.DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF);
}
}
public void checkHasChildDept() {
if (deptService.hasChildrenDept(getDeptId(), null)) {
throw new ApiException(ErrorCode.Business.DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE);
}
}
public void checkDeptAssignedToUsers() {
if (deptService.isDeptAssignedToUsers(getDeptId())) {
throw new ApiException(ErrorCode.Business.DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE);
}
}
public void generateAncestors() {
// 处理 getParentId 可能为 null 的情况
if (getParentId() == null || getParentId() == 0) {
setAncestors(String.valueOf(getParentId() == null ? 0 : getParentId()));
return;
}
SysDeptEntity parentDept = deptService.getById(getParentId());
// 检查 parentDept 是否为 null 或者状态为禁用
if (parentDept == null || StatusEnum.DISABLE.equals(
BasicEnumUtil.fromValue(StatusEnum.class, parentDept.getStatus()))) {
throw new ApiException(ErrorCode.Business.DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED);
}
// 处理 parentDept.getAncestors() 可能为 null 的情况
String ancestors = parentDept.getAncestors() == null ? "" : parentDept.getAncestors();
setAncestors(ancestors + "," + getParentId());
}
/**
* DDD 有些阻抗 如果为了追求性能的话 还是得通过 数据库的方式来判断
*/
public void checkStatusAllowChange() {
if (StatusEnum.DISABLE.getValue().equals(getStatus()) &&
deptService.hasChildrenDept(getDeptId(), true)) {
throw new ApiException(ErrorCode.Business.DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE);
}
}
}
@@ -1,45 +0,0 @@
package com.agileboot.domain.system.dept.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.dept.command.AddDeptCommand;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.dept.db.SysDeptService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* 部门模型工厂
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class DeptModelFactory {
private final SysDeptService deptService;
public DeptModel loadById(Long deptId) {
SysDeptEntity byId = deptService.getById(deptId);
if (byId == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, deptId, "部门");
}
return new DeptModel(byId, deptService);
}
public DeptModel create() {
return new DeptModel(deptService);
}
public DeptModel loadFromAddCommand(AddDeptCommand addCommand, DeptModel model) {
model.setParentId(addCommand.getParentId());
model.setDeptName(addCommand.getDeptName());
model.setOrderNum(addCommand.getOrderNum());
model.setLeaderName(addCommand.getLeaderName());
model.setPhone(addCommand.getPhone());
model.setEmail(addCommand.getEmail());
return model;
}
}
@@ -1,36 +0,0 @@
package com.agileboot.domain.system.dept.query;
import com.agileboot.common.core.page.AbstractQuery;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
@NoArgsConstructor
public class DeptQuery extends AbstractQuery<SysDeptEntity> {
private Long deptId;
private Long parentId;
@Override
public QueryWrapper<SysDeptEntity> addQueryCondition() {
// TODO parentId 这个似乎没使用
return new QueryWrapper<SysDeptEntity>()
// .eq(status != null, "status", status)
.eq(parentId != null, "parent_id", parentId);
// .like(StrUtil.isNotEmpty(deptName), "dept_name", deptName);
// .and(deptId != null && isExcludeCurrentDept, o ->
// o.ne("dept_id", deptId)
// .or()
// .apply("FIND_IN_SET (dept_id , ancestors)")
// );
}
}
@@ -73,14 +73,6 @@ public class SysOperationLogEntity extends Model<SysOperationLogEntity> {
@TableField("operator_location") @TableField("operator_location")
private String operatorLocation; private String operatorLocation;
@ApiModelProperty("部门ID")
@TableField("dept_id")
private Long deptId;
@ApiModelProperty("部门名称")
@TableField("dept_name")
private String deptName;
@ApiModelProperty("请求参数") @ApiModelProperty("请求参数")
@TableField("operation_param") @TableField("operation_param")
private String operationParam; private String operationParam;
@@ -69,12 +69,6 @@ public class OperationLogDTO {
@ExcelColumn(name = "ip地点") @ExcelColumn(name = "ip地点")
private String operatorLocation; private String operatorLocation;
@ExcelColumn(name = "部门ID")
private Long deptId;
@ExcelColumn(name = "部门")
private String deptName;
@ExcelColumn(name = "操作参数") @ExcelColumn(name = "操作参数")
private String operationParam; private String operationParam;
@@ -105,7 +105,7 @@ public class MenuApplicationService {
//默认为id可以不设置 //默认为id可以不设置
config.setIdKey("menuId"); config.setIdKey("menuId");
return TreeUtil.build(menus, 0L, config, (menu, tree) -> { return TreeUtil.build(menus, 0L, config, (menu, tree) -> {
// 也可以使用 tree.setId(dept.getId());等一些默认值 // 也可以使用 tree.setId(menu.getId());等一些默认值
tree.setId(menu.getMenuId()); tree.setId(menu.getMenuId());
tree.setParentId(menu.getParentId()); tree.setParentId(menu.getParentId());
tree.putExtra("label", menu.getMenuName()); tree.putExtra("label", menu.getMenuName());
@@ -132,7 +132,7 @@ public class MenuApplicationService {
config.setIdKey("menuId"); config.setIdKey("menuId");
return TreeUtil.build(noButtonMenus, 0L, config, (menu, tree) -> { return TreeUtil.build(noButtonMenus, 0L, config, (menu, tree) -> {
// 也可以使用 tree.setId(dept.getId());等一些默认值 // 也可以使用 tree.setId(menu.getId());等一些默认值
tree.setId(menu.getMenuId()); tree.setId(menu.getMenuId());
tree.setParentId(menu.getParentId()); tree.setParentId(menu.getParentId());
// TODO 可以取meta中的rank来排序 // TODO 可以取meta中的rank来排序
@@ -1,8 +1,6 @@
package com.agileboot.domain.system.monitor.dto; package com.agileboot.domain.system.monitor.dto;
import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import lombok.Data; import lombok.Data;
/** /**
@@ -18,11 +16,6 @@ public class OnlineUserDTO {
*/ */
private String tokenId; private String tokenId;
/**
* 部门名称
*/
private String deptName;
/** /**
* 用户名称 * 用户名称
*/ */
@@ -66,12 +59,6 @@ public class OnlineUserDTO {
this.browser = user.getLoginInfo().getBrowser(); this.browser = user.getLoginInfo().getBrowser();
this.operationSystem = user.getLoginInfo().getOperationSystem(); this.operationSystem = user.getLoginInfo().getOperationSystem();
this.loginTime = user.getLoginInfo().getLoginTime(); this.loginTime = user.getLoginInfo().getLoginTime();
SysDeptEntity deptEntity = CacheCenter.deptCache.get(user.getDeptId() + "");
if (deptEntity != null) {
this.deptName = deptEntity.getDeptName();
}
} }
} }
@@ -1,85 +0,0 @@
package com.agileboot.domain.system.post;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.system.post.command.AddPostCommand;
import com.agileboot.domain.system.post.command.UpdatePostCommand;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.post.db.SysPostService;
import com.agileboot.domain.system.post.dto.PostDTO;
import com.agileboot.domain.system.post.model.PostModel;
import com.agileboot.domain.system.post.model.PostModelFactory;
import com.agileboot.domain.system.post.query.PostQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class PostApplicationService {
private final PostModelFactory postModelFactory;
private final SysPostService postService;
public PageDTO<PostDTO> getPostList(PostQuery query) {
Page<SysPostEntity> page = postService.page(query.toPage(), query.toQueryWrapper());
List<PostDTO> records = page.getRecords().stream().map(PostDTO::new).collect(Collectors.toList());
return new PageDTO<>(records, page.getTotal());
}
/**
* 查询满足条件的所有岗位,不分页
* @param query 查询条件
* @return 满足查询条件的岗位列表
* @author Kevin Zhang
* @date 2023-10-02
*/
public List<PostDTO> getPostListAll(PostQuery query) {
List<SysPostEntity> all = postService.list(query.toQueryWrapper());
List<PostDTO> records = all.stream().map(PostDTO::new).collect(Collectors.toList());
return records;
}
public PostDTO getPostInfo(Long postId) {
SysPostEntity byId = postService.getById(postId);
return new PostDTO(byId);
}
public void addPost(AddPostCommand addCommand) {
PostModel postModel = postModelFactory.create();
postModel.loadFromAddCommand(addCommand);
postModel.checkPostNameUnique();
postModel.checkPostCodeUnique();
postModel.insert();
}
public void updatePost(UpdatePostCommand updateCommand) {
PostModel postModel = postModelFactory.loadById(updateCommand.getPostId());
postModel.loadFromUpdateCommand(updateCommand);
postModel.checkPostNameUnique();
postModel.checkPostCodeUnique();
postModel.updateById();
}
public void deletePost(BulkOperationCommand<Long> deleteCommand) {
for (Long id : deleteCommand.getIds()) {
PostModel postModel = postModelFactory.loadById(id);
postModel.checkCanBeDelete();
}
postService.removeBatchByIds(deleteCommand.getIds());
}
}
@@ -1,37 +0,0 @@
package com.agileboot.domain.system.post.command;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.PositiveOrZero;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class AddPostCommand {
@NotBlank(message = "岗位编码不能为空")
@Size(max = 64, message = "岗位编码长度不能超过64个字符")
protected String postCode;
/**
* 岗位名称
*/
@NotBlank(message = "岗位名称不能为空")
@Size(max = 64, message = "岗位名称长度不能超过64个字符")
protected String postName;
/**
* 岗位排序
*/
@NotNull(message = "显示顺序不能为空")
protected Integer postSort;
protected String remark;
@PositiveOrZero
protected String status;
}
@@ -1,19 +0,0 @@
package com.agileboot.domain.system.post.command;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class UpdatePostCommand extends AddPostCommand {
@NotNull(message = "岗位ID不能为空")
@Positive
private Long postId;
}
@@ -1,60 +0,0 @@
package com.agileboot.domain.system.post.db;
import com.agileboot.common.core.base.BaseEntity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
import lombok.Getter;
import lombok.Setter;
/**
* <p>
* 岗位信息表
* </p>
*
* @author valarchie
* @since 2022-10-02
*/
@Getter
@Setter
@TableName("sys_post")
@ApiModel(value = "SysPostEntity对象", description = "岗位信息表")
public class SysPostEntity extends BaseEntity<SysPostEntity> {
private static final long serialVersionUID = 1L;
@ApiModelProperty("岗位ID")
@TableId(value = "post_id", type = IdType.AUTO)
private Long postId;
@ApiModelProperty("岗位编码")
@TableField("post_code")
private String postCode;
@ApiModelProperty("岗位名称")
@TableField("post_name")
private String postName;
@ApiModelProperty("显示顺序")
@TableField("post_sort")
private Integer postSort;
@ApiModelProperty("状态(1正常 0停用)")
@TableField("`status`")
private Integer status;
@ApiModelProperty("备注")
@TableField("remark")
private String remark;
@Override
public Serializable pkVal() {
return this.postId;
}
}
@@ -1,15 +0,0 @@
package com.agileboot.domain.system.post.db;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 岗位信息表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysPostMapper extends BaseMapper<SysPostEntity> {
}
@@ -1,40 +0,0 @@
package com.agileboot.domain.system.post.db;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 岗位信息表 服务类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysPostService extends IService<SysPostEntity> {
/**
* 校验岗位名称
* @param postId 职位Id
* @param postName 职位名称
* @return 结果
*/
boolean isPostNameDuplicated(Long postId, String postName);
/**
* 校验岗位编码
* @param postId 职位id
* @param postCode 职位代码
* @return 结果
*/
boolean isPostCodeDuplicated(Long postId, String postCode);
/**
* 检测职位是否分配给用户
*
* @param postId 职位id
* @return 校验结果
*/
boolean isAssignedToUsers(Long postId);
}
@@ -1,55 +0,0 @@
package com.agileboot.domain.system.post.db;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.user.db.SysUserMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
/**
* <p>
* 岗位信息表 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
@Service
@RequiredArgsConstructor
public class SysPostServiceImpl extends ServiceImpl<SysPostMapper, SysPostEntity> implements SysPostService {
private final SysUserMapper userMapper;
/**
* 校验岗位名称是否唯一
*
* @param postName 岗位名称
* @return 结果
*/
@Override
public boolean isPostNameDuplicated(Long postId, String postName) {
QueryWrapper<SysPostEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.ne(postId != null, "post_id", postId)
.eq("post_name", postName);
return baseMapper.exists(queryWrapper);
}
@Override
public boolean isPostCodeDuplicated(Long postId, String postCode) {
QueryWrapper<SysPostEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.ne(postId != null, "post_id", postId)
.eq("post_code", postCode);
return baseMapper.exists(queryWrapper);
}
@Override
public boolean isAssignedToUsers(Long postId) {
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("post_id", postId);
return userMapper.exists(queryWrapper);
}
}
@@ -1,52 +0,0 @@
package com.agileboot.domain.system.post.dto;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.annotation.ExcelColumn;
import com.agileboot.common.enums.common.StatusEnum;
import com.agileboot.common.enums.BasicEnumUtil;
import com.agileboot.domain.system.post.db.SysPostEntity;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@AllArgsConstructor
@NoArgsConstructor
@Data
public class PostDTO {
public PostDTO(SysPostEntity entity) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
statusStr = BasicEnumUtil.getDescriptionByValue(StatusEnum.class, entity.getStatus());
}
}
@ExcelColumn(name = "岗位ID")
private Long postId;
@ExcelColumn(name = "岗位编码")
private String postCode;
@ExcelColumn(name = "岗位名称")
private String postName;
@ExcelColumn(name = "岗位排序")
private Integer postSort;
@ExcelColumn(name = "备注")
private String remark;
private Integer status;
@ExcelColumn(name = "状态")
private String statusStr;
private Date createTime;
}
@@ -1,63 +0,0 @@
package com.agileboot.domain.system.post.model;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.post.command.AddPostCommand;
import com.agileboot.domain.system.post.command.UpdatePostCommand;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.post.db.SysPostService;
import lombok.NoArgsConstructor;
/**
* @author valarchie
*/
@NoArgsConstructor
public class PostModel extends SysPostEntity {
private SysPostService postService;
public PostModel(SysPostService postService) {
this.postService = postService;
}
public PostModel(SysPostEntity entity, SysPostService postService) {
if (entity != null) {
BeanUtil.copyProperties(entity, this);
}
this.postService = postService;
}
public void loadFromAddCommand(AddPostCommand addCommand) {
if (addCommand != null) {
BeanUtil.copyProperties(addCommand, this, "postId");
}
}
public void loadFromUpdateCommand(UpdatePostCommand command) {
if (command != null) {
loadFromAddCommand(command);
}
}
public void checkCanBeDelete() {
if (postService.isAssignedToUsers(this.getPostId())) {
throw new ApiException(ErrorCode.Business.POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED);
}
}
public void checkPostNameUnique() {
if (postService.isPostNameDuplicated(getPostId(), getPostName())) {
throw new ApiException(ErrorCode.Business.POST_NAME_IS_NOT_UNIQUE, getPostName());
}
}
public void checkPostCodeUnique() {
if (postService.isPostCodeDuplicated(getPostId(), getPostCode())) {
throw new ApiException(ErrorCode.Business.POST_CODE_IS_NOT_UNIQUE, getPostCode());
}
}
}
@@ -1,31 +0,0 @@
package com.agileboot.domain.system.post.model;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.post.db.SysPostService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
/**
* @author valarchie
*/
@Component
@RequiredArgsConstructor
public class PostModelFactory {
private final SysPostService postService;
public PostModel loadById(Long postId) {
SysPostEntity byId = postService.getById(postId);
if (byId == null) {
throw new ApiException(Business.COMMON_OBJECT_NOT_FOUND, postId, "职位");
}
return new PostModel(byId, postService);
}
public PostModel create() {
return new PostModel(postService);
}
}
@@ -1,35 +0,0 @@
package com.agileboot.domain.system.post.query;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.AbstractPageQuery;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author valarchie
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class PostQuery extends AbstractPageQuery<SysPostEntity> {
private String postCode;
private String postName;
private Integer status;
@Override
public QueryWrapper<SysPostEntity> addQueryCondition() {
QueryWrapper<SysPostEntity> queryWrapper = new QueryWrapper<SysPostEntity>()
.eq(status != null, "status", status)
.eq(StrUtil.isNotEmpty(postCode), "post_code", postCode)
.like(StrUtil.isNotEmpty(postName), "post_name", postName);
// 当前端没有选择排序字段时,则使用post_sort字段升序排序(在父类AbstractQuery中默认为升序)
if (StrUtil.isEmpty(this.getOrderColumn())) {
this.setOrderColumn("post_sort");
}
this.setTimeRangeColumn("create_time");
return queryWrapper;
}
}
@@ -1,11 +1,9 @@
package com.agileboot.domain.system.role; package com.agileboot.domain.system.role;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.page.PageDTO; import com.agileboot.common.core.page.PageDTO;
import com.agileboot.domain.common.cache.CacheCenter; import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.system.role.command.AddRoleCommand; import com.agileboot.domain.system.role.command.AddRoleCommand;
import com.agileboot.domain.system.role.command.UpdateDataScopeCommand;
import com.agileboot.domain.system.role.command.UpdateRoleCommand; import com.agileboot.domain.system.role.command.UpdateRoleCommand;
import com.agileboot.domain.system.role.command.UpdateStatusCommand; import com.agileboot.domain.system.role.command.UpdateStatusCommand;
import com.agileboot.domain.system.role.dto.RoleDTO; import com.agileboot.domain.system.role.dto.RoleDTO;
@@ -56,9 +54,6 @@ public class RoleApplicationService {
public RoleDTO getRoleInfo(Long roleId) { public RoleDTO getRoleInfo(Long roleId) {
SysRoleEntity byId = roleService.getById(roleId); SysRoleEntity byId = roleService.getById(roleId);
RoleDTO roleDTO = new RoleDTO(byId); RoleDTO roleDTO = new RoleDTO(byId);
List<Long> selectedDeptList = StrUtil.split(byId.getDeptIdSet(), ",")
.stream().filter(StrUtil::isNotEmpty).map(Long::new).collect(Collectors.toList());
roleDTO.setSelectedDeptList(selectedDeptList);
roleDTO.setSelectedMenuList(menuService.getMenuIdsByRoleId(roleId)); roleDTO.setSelectedMenuList(menuService.getMenuIdsByRoleId(roleId));
return roleDTO; return roleDTO;
} }
@@ -105,17 +100,6 @@ public class RoleApplicationService {
roleModel.updateById(); roleModel.updateById();
} }
public void updateDataScope(UpdateDataScopeCommand command) {
RoleModel roleModel = roleModelFactory.loadById(command.getRoleId());
roleModel.setDeptIds(command.getDeptIds());
roleModel.setDataScope(command.getDataScope());
roleModel.generateDeptIdSet();
roleModel.updateById();
}
public PageDTO<UserDTO> getAllocatedUserList(AllocatedRoleQuery query) { public PageDTO<UserDTO> getAllocatedUserList(AllocatedRoleQuery query) {
Page<SysUserEntity> page = userService.getUserListByRole(query); Page<SysUserEntity> page = userService.getUserListByRole(query);
List<UserDTO> dtoList = page.getRecords().stream().map(UserDTO::new).collect(Collectors.toList()); List<UserDTO> dtoList = page.getRecords().stream().map(UserDTO::new).collect(Collectors.toList());
@@ -40,7 +40,6 @@ public class AddRoleCommand {
private String remark; private String remark;
@ExcelColumn(name = "数据范围")
private String dataScope; private String dataScope;
@PositiveOrZero @PositiveOrZero
@@ -1,26 +0,0 @@
package com.agileboot.domain.system.role.command;
import java.util.List;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import lombok.Data;
/**
* @author valarchie
*/
@Data
public class UpdateDataScopeCommand {
@NotNull
@Positive
private Long roleId;
@NotNull
@NotEmpty
private List<Long> deptIds;
private Integer dataScope;
}
@@ -43,14 +43,10 @@ public class SysRoleEntity extends BaseEntity<SysRoleEntity> {
@TableField("role_sort") @TableField("role_sort")
private Integer roleSort; private Integer roleSort;
@ApiModelProperty("数据范围(1:全部数据权限 2:自定数据权限 3: 本部门数据权限 4: 本部门及以下数据权限 5: 本人权限)") @ApiModelProperty("数据范围(1:全部数据权限 5: 本人权限)")
@TableField("data_scope") @TableField("data_scope")
private Integer dataScope; private Integer dataScope;
@ApiModelProperty("角色所拥有的部门数据权限")
@TableField("dept_id_set")
private String deptIdSet;
@ApiModelProperty("角色状态(1正常 0停用)") @ApiModelProperty("角色状态(1正常 0停用)")
@TableField("`status`") @TableField("`status`")
private Integer status; private Integer status;
@@ -32,6 +32,13 @@ public interface SysRoleService extends IService<SysRoleEntity> {
*/ */
boolean isRoleKeyDuplicated(Long roleId, String roleKey); boolean isRoleKeyDuplicated(Long roleId, String roleKey);
/**
* 根据角色标识获取可用角色
* @param roleKey 角色标识
* @return 角色信息
*/
SysRoleEntity getEnabledRoleByKey(String roleKey);
/** /**
* 检测角色是否分配给用户 * 检测角色是否分配给用户
@@ -3,6 +3,7 @@ package com.agileboot.domain.system.role.db;
import com.agileboot.domain.system.menu.db.SysMenuEntity; import com.agileboot.domain.system.menu.db.SysMenuEntity;
import com.agileboot.domain.system.user.db.SysUserEntity; import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.user.db.SysUserMapper; import com.agileboot.domain.system.user.db.SysUserMapper;
import com.agileboot.common.enums.common.StatusEnum;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import java.util.List; import java.util.List;
@@ -39,6 +40,14 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRoleEntity
return this.baseMapper.exists(queryWrapper); return this.baseMapper.exists(queryWrapper);
} }
@Override
public SysRoleEntity getEnabledRoleByKey(String roleKey) {
QueryWrapper<SysRoleEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("role_key", roleKey)
.eq("status", StatusEnum.ENABLE.getValue());
return getOne(queryWrapper);
}
@Override @Override
public boolean isAssignedToUsers(Long roleId) { public boolean isAssignedToUsers(Long roleId) {
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>(); QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
@@ -45,6 +45,4 @@ public class RoleDTO {
private Integer dataScope; private Integer dataScope;
private List<Long> selectedMenuList; private List<Long> selectedMenuList;
private List<Long> selectedDeptList;
} }
@@ -1,10 +1,10 @@
package com.agileboot.domain.system.role.model; package com.agileboot.domain.system.role.model;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.exception.error.ErrorCode.Business; import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.infrastructure.user.web.DataScopeEnum;
import com.agileboot.domain.system.role.command.AddRoleCommand; import com.agileboot.domain.system.role.command.AddRoleCommand;
import com.agileboot.domain.system.role.command.UpdateRoleCommand; import com.agileboot.domain.system.role.command.UpdateRoleCommand;
import com.agileboot.common.enums.common.StatusEnum; import com.agileboot.common.enums.common.StatusEnum;
@@ -14,7 +14,6 @@ import com.agileboot.domain.system.role.db.SysRoleMenuService;
import com.agileboot.domain.system.role.db.SysRoleService; import com.agileboot.domain.system.role.db.SysRoleService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
@@ -30,8 +29,6 @@ public class RoleModel extends SysRoleEntity {
private List<Long> menuIds; private List<Long> menuIds;
private List<Long> deptIds;
private SysRoleService roleService; private SysRoleService roleService;
private SysRoleMenuService roleMenuService; private SysRoleMenuService roleMenuService;
@@ -51,6 +48,7 @@ public class RoleModel extends SysRoleEntity {
public void loadAddCommand(AddRoleCommand command) { public void loadAddCommand(AddRoleCommand command) {
if (command != null) { if (command != null) {
BeanUtil.copyProperties(command, this, "roleId"); BeanUtil.copyProperties(command, this, "roleId");
fillDefaultDataScope();
} }
} }
@@ -84,22 +82,12 @@ public class RoleModel extends SysRoleEntity {
} }
} }
public void generateDeptIdSet() { private void fillDefaultDataScope() {
if (deptIds == null) { if (getDataScope() == null) {
setDeptIdSet(""); setDataScope(DataScopeEnum.ONLY_SELF.getValue());
return;
} }
if (deptIds.size() > new HashSet<>(deptIds).size()) {
throw new ApiException(ErrorCode.Business.ROLE_DATA_SCOPE_DUPLICATED_DEPT);
} }
String deptIdSet = StrUtil.join(",", deptIds);
setDeptIdSet(deptIdSet);
}
@Override @Override
public boolean insert() { public boolean insert() {
super.insert(); super.insert();
@@ -1,7 +1,5 @@
package com.agileboot.domain.system.role.model; package com.agileboot.domain.system.role.model;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.role.db.SysRoleEntity; import com.agileboot.domain.system.role.db.SysRoleEntity;
@@ -36,13 +34,9 @@ public class RoleModelFactory {
queryWrapper.eq(SysRoleMenuEntity::getRoleId, roleId); queryWrapper.eq(SysRoleMenuEntity::getRoleId, roleId);
List<Long> menuIds = roleMenuService.list(queryWrapper).stream().map(SysRoleMenuEntity::getMenuId) List<Long> menuIds = roleMenuService.list(queryWrapper).stream().map(SysRoleMenuEntity::getMenuId)
.collect(Collectors.toList()); .collect(Collectors.toList());
List<Long> deptIds = StrUtil.split(byId.getDeptIdSet(), ",").stream()
.map(Convert::toLong).collect( Collectors.toList());
RoleModel roleModel = new RoleModel(byId, roleService, roleMenuService); RoleModel roleModel = new RoleModel(byId, roleService, roleMenuService);
roleModel.setMenuIds(menuIds); roleModel.setMenuIds(menuIds);
roleModel.setDeptIds(deptIds);
return roleModel; return roleModel;
} }
@@ -1,14 +1,21 @@
package com.agileboot.domain.system.user; package com.agileboot.domain.system.user;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import com.agileboot.common.core.page.PageDTO; import com.agileboot.common.core.page.PageDTO;
import com.agileboot.common.enums.common.ConfigKeyEnum;
import com.agileboot.common.enums.common.GenderEnum;
import com.agileboot.common.enums.common.UserStatusEnum;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.common.cache.CacheCenter; import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.common.cache.GuavaCacheService;
import com.agileboot.domain.common.command.BulkOperationCommand; import com.agileboot.domain.common.command.BulkOperationCommand;
import com.agileboot.domain.common.dto.CurrentLoginUserDTO; import com.agileboot.domain.common.dto.CurrentLoginUserDTO;
import com.agileboot.domain.system.post.dto.PostDTO;
import com.agileboot.domain.system.role.dto.RoleDTO; import com.agileboot.domain.system.role.dto.RoleDTO;
import com.agileboot.domain.system.user.command.AddUserCommand; import com.agileboot.domain.system.user.command.AddUserCommand;
import com.agileboot.domain.system.user.command.ChangeStatusCommand; import com.agileboot.domain.system.user.command.ChangeStatusCommand;
import com.agileboot.domain.system.user.command.RegisterUserCommand;
import com.agileboot.domain.system.user.command.ResetPasswordCommand; import com.agileboot.domain.system.user.command.ResetPasswordCommand;
import com.agileboot.domain.system.user.command.UpdateProfileCommand; import com.agileboot.domain.system.user.command.UpdateProfileCommand;
import com.agileboot.domain.system.user.command.UpdateUserAvatarCommand; import com.agileboot.domain.system.user.command.UpdateUserAvatarCommand;
@@ -22,12 +29,12 @@ import com.agileboot.domain.system.user.model.UserModel;
import com.agileboot.domain.system.user.model.UserModelFactory; import com.agileboot.domain.system.user.model.UserModelFactory;
import com.agileboot.domain.system.user.query.SearchUserQuery; import com.agileboot.domain.system.user.query.SearchUserQuery;
import com.agileboot.infrastructure.user.web.SystemLoginUser; import com.agileboot.infrastructure.user.web.SystemLoginUser;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity; import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity; import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.post.db.SysPostService;
import com.agileboot.domain.system.role.db.SysRoleService; import com.agileboot.domain.system.role.db.SysRoleService;
import com.agileboot.domain.system.user.db.SysUserService; import com.agileboot.domain.system.user.db.SysUserService;
import com.agileboot.infrastructure.user.AuthenticationUtils;
import com.agileboot.infrastructure.user.web.DataScopeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List; import java.util.List;
@@ -42,16 +49,19 @@ import org.springframework.stereotype.Service;
@RequiredArgsConstructor @RequiredArgsConstructor
public class UserApplicationService { public class UserApplicationService {
private static final String DEFAULT_REGISTER_ROLE_KEY = "common";
private final SysUserService userService; private final SysUserService userService;
private final SysRoleService roleService; private final SysRoleService roleService;
private final SysPostService postService;
private final UserModelFactory userModelFactory; private final UserModelFactory userModelFactory;
private final GuavaCacheService guavaCacheService;
public PageDTO<UserDTO> getUserList(SearchUserQuery<SearchUserDO> query) { public PageDTO<UserDTO> getUserList(SearchUserQuery<SearchUserDO> query) {
applyOnlySelfScope(query);
Page<SearchUserDO> userPage = userService.getUserList(query); Page<SearchUserDO> userPage = userService.getUserList(query);
List<UserDTO> userDTOList = userPage.getRecords().stream().map(UserDTO::new).collect(Collectors.toList()); List<UserDTO> userDTOList = userPage.getRecords().stream().map(UserDTO::new).collect(Collectors.toList());
return new PageDTO<>(userDTOList, userPage.getTotal()); return new PageDTO<>(userDTOList, userPage.getTotal());
@@ -60,10 +70,9 @@ public class UserApplicationService {
public UserProfileDTO getUserProfile(Long userId) { public UserProfileDTO getUserProfile(Long userId) {
SysUserEntity userEntity = userService.getById(userId); SysUserEntity userEntity = userService.getById(userId);
SysPostEntity postEntity = userService.getPostOfUser(userId);
SysRoleEntity roleEntity = userService.getRoleOfUser(userId); SysRoleEntity roleEntity = userService.getRoleOfUser(userId);
return new UserProfileDTO(userEntity, postEntity, roleEntity); return new UserProfileDTO(userEntity, roleEntity);
} }
@@ -102,14 +111,11 @@ public class UserApplicationService {
LambdaQueryWrapper<SysRoleEntity> roleQuery = new LambdaQueryWrapper<SysRoleEntity>() LambdaQueryWrapper<SysRoleEntity> roleQuery = new LambdaQueryWrapper<SysRoleEntity>()
.orderByAsc(SysRoleEntity::getRoleSort); .orderByAsc(SysRoleEntity::getRoleSort);
List<RoleDTO> roleDtoList = roleService.list(roleQuery).stream().map(RoleDTO::new).collect(Collectors.toList()); List<RoleDTO> roleDtoList = roleService.list(roleQuery).stream().map(RoleDTO::new).collect(Collectors.toList());
List<PostDTO> postDtoList = postService.list().stream().map(PostDTO::new).collect(Collectors.toList());
detailDTO.setRoleOptions(roleDtoList); detailDTO.setRoleOptions(roleDtoList);
detailDTO.setPostOptions(postDtoList);
if (userEntity != null) { if (userEntity != null) {
detailDTO.setUser(new UserDTO(userEntity)); detailDTO.setUser(new UserDTO(userEntity));
detailDTO.setRoleId(userEntity.getRoleId()); detailDTO.setRoleId(userEntity.getRoleId());
detailDTO.setPostId(userEntity.getPostId());
} }
return detailDTO; return detailDTO;
} }
@@ -127,6 +133,20 @@ public class UserApplicationService {
model.insert(); model.insert();
} }
public void registerUser(RegisterUserCommand command) {
checkRegisterIsOpen();
checkRegisterPassword(command);
UserModel model = userModelFactory.create();
loadRegisterCommand(model, command);
model.checkUsernameIsUnique();
model.checkPhoneNumberIsUnique();
model.checkEmailIsUnique();
model.resetPassword(command.getPassword());
model.insert();
}
public void updateUser(UpdateUserCommand command) { public void updateUser(UpdateUserCommand command) {
UserModel model = userModelFactory.loadById(command.getUserId()); UserModel model = userModelFactory.loadById(command.getUserId());
model.loadUpdateUserCommand(command); model.loadUpdateUserCommand(command);
@@ -182,5 +202,58 @@ public class UserApplicationService {
CacheCenter.userCache.delete(userModel.getUserId()); CacheCenter.userCache.delete(userModel.getUserId());
} }
private void checkRegisterIsOpen() {
Boolean isRegisterUserOn = Convert.toBool(guavaCacheService.configCache.get(ConfigKeyEnum.REGISTER.getValue()));
if (!Boolean.TRUE.equals(isRegisterUserOn)) {
throw new ApiException(ErrorCode.Business.USER_REGISTER_IS_CLOSED);
}
}
private void checkRegisterPassword(RegisterUserCommand command) {
if (!StrUtil.equals(command.getPassword(), command.getConfirmPassword())) {
throw new ApiException(ErrorCode.Business.USER_REGISTER_PASSWORD_NOT_MATCH);
}
}
private void applyOnlySelfScope(SearchUserQuery<SearchUserDO> query) {
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
if (loginUser == null || loginUser.getRoleInfo() == null) {
return;
}
if (DataScopeEnum.ONLY_SELF.equals(loginUser.getRoleInfo().getDataScope())) {
query.setUserId(loginUser.getUserId());
}
}
private void loadRegisterCommand(UserModel model, RegisterUserCommand command) {
SysRoleEntity role = getDefaultRegisterRole();
model.setUsername(command.getUsername());
model.setNickname(getRegisterNickname(command));
model.setEmail(command.getEmail());
model.setPhoneNumber(command.getPhoneNumber());
model.setRoleId(role.getRoleId());
model.setStatus(UserStatusEnum.NORMAL.getValue());
model.setUserType(0);
model.setSex(GenderEnum.UNKNOWN.getValue());
model.setIsAdmin(false);
model.setRemark("自助注册用户");
}
private SysRoleEntity getDefaultRegisterRole() {
SysRoleEntity role = roleService.getEnabledRoleByKey(DEFAULT_REGISTER_ROLE_KEY);
if (role == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, DEFAULT_REGISTER_ROLE_KEY, "普通角色");
}
return role;
}
private String getRegisterNickname(RegisterUserCommand command) {
if (StrUtil.isNotBlank(command.getNickname())) {
return command.getNickname();
}
return command.getUsername();
}
} }
@@ -9,9 +9,6 @@ import lombok.Data;
@Data @Data
public class AddUserCommand { public class AddUserCommand {
@ExcelColumn(name = "部门ID")
private Long deptId;
@ExcelColumn(name = "用户名") @ExcelColumn(name = "用户名")
private String username; private String username;
@@ -39,9 +36,6 @@ public class AddUserCommand {
@ExcelColumn(name = "角色ID") @ExcelColumn(name = "角色ID")
private Long roleId; private Long roleId;
@ExcelColumn(name = "职位ID")
private Long postId;
@ExcelColumn(name = "备注") @ExcelColumn(name = "备注")
private String remark; private String remark;
@@ -0,0 +1,40 @@
package com.agileboot.domain.system.user.command;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import lombok.Data;
/**
* 自助注册用户请求。
*
* @author codex
*/
@Data
public class RegisterUserCommand {
@NotBlank(message = "账号不能为空")
@Size(max = 64, message = "账号长度不能超过64个字符")
private String username;
@Size(max = 32, message = "昵称长度不能超过32个字符")
private String nickname;
@Email(message = "邮箱格式不正确")
@Size(max = 128, message = "邮箱长度不能超过128个字符")
private String email;
@Size(max = 18, message = "手机号长度不能超过18个字符")
private String phoneNumber;
@NotBlank(message = "密码不能为空")
private String password;
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
private String captchaCode;
private String captchaCodeKey;
}
@@ -1,5 +1,6 @@
package com.agileboot.domain.system.user.command; package com.agileboot.domain.system.user.command;
import javax.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;
/** /**
@@ -9,7 +10,9 @@ import lombok.Data;
public class UpdateUserPasswordCommand { public class UpdateUserPasswordCommand {
private Long userId; private Long userId;
@NotBlank(message = "密码不能为空")
private String newPassword; private String newPassword;
private String oldPassword; @NotBlank(message = "确认密码不能为空")
private String confirmPassword;
} }
@@ -7,11 +7,8 @@ import lombok.EqualsAndHashCode;
* 如果Entity的字段和复杂查询不匹配时 自定义类来接收 * 如果Entity的字段和复杂查询不匹配时 自定义类来接收
* @author valarchie * @author valarchie
*/ */
@EqualsAndHashCode(callSuper = true)
@Data @Data
@EqualsAndHashCode(callSuper = true)
public class SearchUserDO extends SysUserEntity { public class SearchUserDO extends SysUserEntity {
private String deptName;
private String deptLeader;
} }
@@ -32,18 +32,10 @@ public class SysUserEntity extends BaseEntity<SysUserEntity> {
@TableId(value = "user_id", type = IdType.AUTO) @TableId(value = "user_id", type = IdType.AUTO)
private Long userId; private Long userId;
@ApiModelProperty("职位id")
@TableField("post_id")
private Long postId;
@ApiModelProperty("角色id") @ApiModelProperty("角色id")
@TableField("role_id") @TableField("role_id")
private Long roleId; private Long roleId;
@ApiModelProperty("部门ID")
@TableField("dept_id")
private Long deptId;
@ApiModelProperty("用户账号") @ApiModelProperty("用户账号")
@TableField("username") @TableField("username")
private String username; private String username;
@@ -1,6 +1,5 @@
package com.agileboot.domain.system.user.db; package com.agileboot.domain.system.user.db;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity; import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@@ -35,19 +34,6 @@ public interface SysUserMapper extends BaseMapper<SysUserEntity> {
+ " AND u.user_id = #{userId}") + " AND u.user_id = #{userId}")
List<SysRoleEntity> getRolesByUserId(Long userId); List<SysRoleEntity> getRolesByUserId(Long userId);
/**
* 查询用户所属岗位组
*
* @param userId 用户名
* @return 结果
*/
@Select("SELECT p.* "
+ "FROM sys_post p "
+ " LEFT JOIN sys_user u ON p.post_id = u.post_id "
+ "WHERE u.user_id = #{userId} "
+ " AND p.deleted = 0")
List<SysPostEntity> getPostsByUserId(Long userId);
/** /**
* 根据用户ID查询权限 * 根据用户ID查询权限
* *
@@ -71,10 +57,9 @@ public interface SysUserMapper extends BaseMapper<SysUserEntity> {
* @param queryWrapper 条件选择器 * @param queryWrapper 条件选择器
* @return 分页处理后的用户列表 * @return 分页处理后的用户列表
*/ */
@Select("SELECT DISTINCT u.user_id, u.dept_id, u.username, u.nick_name, u.email " @Select("SELECT DISTINCT u.user_id, u.username, u.nick_name, u.email "
+ " , u.phone_number, u.status, u.create_time " + " , u.phone_number, u.status, u.create_time "
+ "FROM sys_user u " + "FROM sys_user u "
+ " LEFT JOIN sys_dept d ON u.dept_id = d.dept_id "
+ " LEFT JOIN sys_role r ON r.role_id = u.role_id" + " LEFT JOIN sys_role r ON r.role_id = u.role_id"
+ " ${ew.customSqlSegment}") + " ${ew.customSqlSegment}")
Page<SysUserEntity> getUserListByRole(Page<SysUserEntity> page, Page<SysUserEntity> getUserListByRole(Page<SysUserEntity> page,
@@ -86,9 +71,8 @@ public interface SysUserMapper extends BaseMapper<SysUserEntity> {
* @param queryWrapper 查询对象 * @param queryWrapper 查询对象
* @return 用户信息集合信息 * @return 用户信息集合信息
*/ */
@Select("SELECT u.*, d.dept_name, d.leader_name as dept_leader " @Select("SELECT u.* "
+ "FROM sys_user u " + "FROM sys_user u "
+ " LEFT JOIN sys_dept d ON u.dept_id = d.dept_id "
+ "${ew.customSqlSegment}") + "${ew.customSqlSegment}")
Page<SearchUserDO> getUserList(Page<SearchUserDO> page, Page<SearchUserDO> getUserList(Page<SearchUserDO> page,
@Param(Constants.WRAPPER) Wrapper<SearchUserDO> queryWrapper); @Param(Constants.WRAPPER) Wrapper<SearchUserDO> queryWrapper);
@@ -1,7 +1,6 @@
package com.agileboot.domain.system.user.db; package com.agileboot.domain.system.user.db;
import com.agileboot.common.core.page.AbstractPageQuery; import com.agileboot.common.core.page.AbstractPageQuery;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity; import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
@@ -49,13 +48,6 @@ public interface SysUserService extends IService<SysUserEntity> {
*/ */
SysRoleEntity getRoleOfUser(Long userId); SysRoleEntity getRoleOfUser(Long userId);
/**
* 获取用户的岗位
* @param userId 用户id
* @return 用户岗位
*/
SysPostEntity getPostOfUser(Long userId);
/** /**
* 获取用户的权限列表 * 获取用户的权限列表
* @param userId 用户id * @param userId 用户id
@@ -1,7 +1,6 @@
package com.agileboot.domain.system.user.db; package com.agileboot.domain.system.user.db;
import com.agileboot.common.core.page.AbstractPageQuery; import com.agileboot.common.core.page.AbstractPageQuery;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity; import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -55,13 +54,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUserEntity
} }
@Override
public SysPostEntity getPostOfUser(Long userId) {
List<SysPostEntity> list = baseMapper.getPostsByUserId(userId);
return list.isEmpty() ? null : list.get(0);
}
@Override @Override
public Set<String> getMenuPermissionsForUser(Long userId) { public Set<String> getMenuPermissionsForUser(Long userId) {
return baseMapper.getMenuPermsByUserId(userId); return baseMapper.getMenuPermsByUserId(userId);
@@ -4,11 +4,9 @@ import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.annotation.ExcelColumn; import com.agileboot.common.annotation.ExcelColumn;
import com.agileboot.common.annotation.ExcelSheet; import com.agileboot.common.annotation.ExcelSheet;
import com.agileboot.domain.common.cache.CacheCenter; import com.agileboot.domain.common.cache.CacheCenter;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity; import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.user.db.SearchUserDO; import com.agileboot.domain.system.user.db.SearchUserDO;
import com.agileboot.domain.system.user.db.SysUserEntity;
import java.util.Date; import java.util.Date;
import lombok.Data; import lombok.Data;
@@ -23,11 +21,6 @@ public class UserDTO {
if (entity != null) { if (entity != null) {
BeanUtil.copyProperties(entity, this); BeanUtil.copyProperties(entity, this);
SysDeptEntity dept = CacheCenter.deptCache.get(entity.getDeptId() + "");
if (dept != null) {
this.deptName = dept.getDeptName();
}
SysUserEntity creator = CacheCenter.userCache.getObjectById(entity.getCreatorId()); SysUserEntity creator = CacheCenter.userCache.getObjectById(entity.getCreatorId());
if (creator != null) { if (creator != null) {
this.creatorName = creator.getUsername(); this.creatorName = creator.getUsername();
@@ -38,11 +31,6 @@ public class UserDTO {
this.roleName = roleEntity != null ? roleEntity.getRoleName() : ""; this.roleName = roleEntity != null ? roleEntity.getRoleName() : "";
} }
if (entity.getPostId() != null) {
SysPostEntity post = CacheCenter.postCache.getObjectById(entity.getRoleId());
this.postName = post != null ? post.getPostName() : "";
}
} }
} }
@@ -61,24 +49,12 @@ public class UserDTO {
@ExcelColumn(name = "用户ID") @ExcelColumn(name = "用户ID")
private Long userId; private Long userId;
@ExcelColumn(name = "职位ID")
private Long postId;
@ExcelColumn(name = "职位名称")
private String postName;
@ExcelColumn(name = "角色ID") @ExcelColumn(name = "角色ID")
private Long roleId; private Long roleId;
@ExcelColumn(name = "角色名称") @ExcelColumn(name = "角色名称")
private String roleName; private String roleName;
@ExcelColumn(name = "部门ID")
private Long deptId;
@ExcelColumn(name = "部门名称")
private String deptName;
@ExcelColumn(name = "用户名") @ExcelColumn(name = "用户名")
private String username; private String username;
@@ -1,6 +1,5 @@
package com.agileboot.domain.system.user.dto; package com.agileboot.domain.system.user.dto;
import com.agileboot.domain.system.post.dto.PostDTO;
import com.agileboot.domain.system.role.dto.RoleDTO; import com.agileboot.domain.system.role.dto.RoleDTO;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -19,13 +18,6 @@ public class UserDetailDTO {
*/ */
private List<RoleDTO> roleOptions; private List<RoleDTO> roleOptions;
/**
* 返回所有posts
*/
private List<PostDTO> postOptions;
private Long postId;
private Long roleId; private Long roleId;
private Set<String> permissions; private Set<String> permissions;
@@ -1,6 +1,5 @@
package com.agileboot.domain.system.user.dto; package com.agileboot.domain.system.user.dto;
import com.agileboot.domain.system.post.db.SysPostEntity;
import com.agileboot.domain.system.role.db.SysRoleEntity; import com.agileboot.domain.system.role.db.SysRoleEntity;
import com.agileboot.domain.system.user.db.SysUserEntity; import com.agileboot.domain.system.user.db.SysUserEntity;
import lombok.Data; import lombok.Data;
@@ -11,15 +10,11 @@ import lombok.Data;
@Data @Data
public class UserProfileDTO { public class UserProfileDTO {
public UserProfileDTO(SysUserEntity userEntity, SysPostEntity postEntity, SysRoleEntity roleEntity) { public UserProfileDTO(SysUserEntity userEntity, SysRoleEntity roleEntity) {
if (userEntity != null) { if (userEntity != null) {
this.user = new UserDTO(userEntity); this.user = new UserDTO(userEntity);
} }
if (postEntity != null) {
this.postName = postEntity.getPostName();
}
if (roleEntity != null) { if (roleEntity != null) {
this.roleName = roleEntity.getRoleName(); this.roleName = roleEntity.getRoleName();
} }
@@ -27,6 +22,5 @@ public class UserProfileDTO {
private UserDTO user; private UserDTO user;
private String roleName; private String roleName;
private String postName;
} }
@@ -6,8 +6,6 @@ import com.agileboot.common.config.AgileBootConfig;
import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.common.exception.error.ErrorCode.Business; import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.domain.system.dept.model.DeptModelFactory;
import com.agileboot.domain.system.post.model.PostModelFactory;
import com.agileboot.domain.system.role.model.RoleModelFactory; import com.agileboot.domain.system.role.model.RoleModelFactory;
import com.agileboot.domain.system.user.command.AddUserCommand; import com.agileboot.domain.system.user.command.AddUserCommand;
import com.agileboot.domain.system.user.command.UpdateProfileCommand; import com.agileboot.domain.system.user.command.UpdateProfileCommand;
@@ -32,26 +30,18 @@ public class UserModel extends SysUserEntity {
private SysUserService userService; private SysUserService userService;
private PostModelFactory postModelFactory;
private DeptModelFactory deptModelFactory;
private RoleModelFactory roleModelFactory; private RoleModelFactory roleModelFactory;
public UserModel(SysUserEntity entity, SysUserService userService, PostModelFactory postModelFactory, public UserModel(SysUserEntity entity, SysUserService userService, RoleModelFactory roleModelFactory) {
DeptModelFactory deptModelFactory, RoleModelFactory roleModelFactory) { this(userService, roleModelFactory);
this(userService, postModelFactory, deptModelFactory, roleModelFactory);
if (entity != null) { if (entity != null) {
BeanUtil.copyProperties(entity, this); BeanUtil.copyProperties(entity, this);
} }
} }
public UserModel(SysUserService userService, PostModelFactory postModelFactory, public UserModel(SysUserService userService, RoleModelFactory roleModelFactory) {
DeptModelFactory deptModelFactory, RoleModelFactory roleModelFactory) {
this.userService = userService; this.userService = userService;
this.postModelFactory = postModelFactory;
this.deptModelFactory = deptModelFactory;
this.roleModelFactory = roleModelFactory; this.roleModelFactory = roleModelFactory;
} }
@@ -92,19 +82,9 @@ public class UserModel extends SysUserEntity {
} }
public void checkFieldRelatedEntityExist() { public void checkFieldRelatedEntityExist() {
if (getPostId() != null) {
postModelFactory.loadById(getPostId());
}
if (getDeptId() != null) {
deptModelFactory.loadById(getDeptId());
}
if (getRoleId() != null) { if (getRoleId() != null) {
roleModelFactory.loadById(getRoleId()); roleModelFactory.loadById(getRoleId());
} }
} }
@@ -123,8 +103,13 @@ public class UserModel extends SysUserEntity {
public void modifyPassword(UpdateUserPasswordCommand command) { public void modifyPassword(UpdateUserPasswordCommand command) {
if (!AuthenticationUtils.matchesPassword(command.getOldPassword(), getPassword())) { if (command == null || StrUtil.isBlank(command.getNewPassword())
throw new ApiException(ErrorCode.Business.USER_PASSWORD_IS_NOT_CORRECT); || StrUtil.isBlank(command.getConfirmPassword())) {
throw new ApiException(ErrorCode.Business.USER_PASSWORD_IS_EMPTY);
}
if (!StrUtil.equals(command.getNewPassword(), command.getConfirmPassword())) {
throw new ApiException(ErrorCode.Business.USER_REGISTER_PASSWORD_NOT_MATCH);
} }
if (AuthenticationUtils.matchesPassword(command.getNewPassword(), getPassword())) { if (AuthenticationUtils.matchesPassword(command.getNewPassword(), getPassword())) {
@@ -2,8 +2,6 @@ package com.agileboot.domain.system.user.model;
import com.agileboot.common.exception.ApiException; import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode; import com.agileboot.common.exception.error.ErrorCode;
import com.agileboot.domain.system.dept.model.DeptModelFactory;
import com.agileboot.domain.system.post.model.PostModelFactory;
import com.agileboot.domain.system.role.model.RoleModelFactory; import com.agileboot.domain.system.role.model.RoleModelFactory;
import com.agileboot.domain.system.user.db.SysUserEntity; import com.agileboot.domain.system.user.db.SysUserEntity;
import com.agileboot.domain.system.user.db.SysUserService; import com.agileboot.domain.system.user.db.SysUserService;
@@ -20,10 +18,6 @@ public class UserModelFactory {
private final SysUserService userService; private final SysUserService userService;
private final PostModelFactory postModelFactory;
private final DeptModelFactory deptModelFactory;
private final RoleModelFactory roleModelFactory; private final RoleModelFactory roleModelFactory;
public UserModel loadById(Long userId) { public UserModel loadById(Long userId) {
@@ -31,11 +25,11 @@ public class UserModelFactory {
if (byId == null) { if (byId == null) {
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, userId, "用户"); throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, userId, "用户");
} }
return new UserModel(byId, userService, postModelFactory, deptModelFactory, roleModelFactory); return new UserModel(byId, userService, roleModelFactory);
} }
public UserModel create() { public UserModel create() {
return new UserModel(userService, postModelFactory, deptModelFactory, roleModelFactory); return new UserModel(userService, roleModelFactory);
} }
} }
@@ -18,7 +18,6 @@ public class SearchUserQuery<T> extends AbstractPageQuery<T> {
protected String username; protected String username;
protected Integer status; protected Integer status;
protected String phoneNumber; protected String phoneNumber;
protected Long deptId;
@Override @Override
public QueryWrapper<T> addQueryCondition() { public QueryWrapper<T> addQueryCondition() {
@@ -28,12 +27,7 @@ public class SearchUserQuery<T> extends AbstractPageQuery<T> {
.like(StrUtil.isNotEmpty(phoneNumber), "u.phone_number", phoneNumber) .like(StrUtil.isNotEmpty(phoneNumber), "u.phone_number", phoneNumber)
.eq(userId != null, "u.user_id", userId) .eq(userId != null, "u.user_id", userId)
.eq(status != null, "u.status", status) .eq(status != null, "u.status", status)
.eq("u.deleted", 0) .eq("u.deleted", 0);
.and(deptId != null, o ->
o.eq("u.dept_id", deptId)
.or()
.apply("u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(" + deptId
+ ", ancestors))"));
// 设置排序字段 // 设置排序字段
this.timeRangeColumn = "u.create_time"; this.timeRangeColumn = "u.create_time";
@@ -0,0 +1,38 @@
package com.agileboot.domain.collaboration.record.enumtype;
import java.util.Arrays;
import java.util.Collections;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class SettlementStatusEnumTest {
@Test
void aggregateShouldIgnoreNoneStatus() {
String status = SettlementStatusEnum.aggregate(Arrays.asList("NONE", "SETTLED"));
Assertions.assertEquals("SETTLED", status);
}
@Test
void aggregateShouldReturnPartialWhenStatusesAreMixed() {
String status = SettlementStatusEnum.aggregate(Arrays.asList("SETTLED", "UNSETTLED"));
Assertions.assertEquals("PARTIAL", status);
}
@Test
void aggregateShouldReturnNoneWhenNoEffectiveStatusExists() {
String status = SettlementStatusEnum.aggregate(Collections.singletonList("NONE"));
Assertions.assertEquals("NONE", status);
}
@Test
void aggregateShouldReturnNoneWhenStatusListIsEmpty() {
String status = SettlementStatusEnum.aggregate(Collections.emptyList());
Assertions.assertEquals("NONE", status);
}
}
@@ -1,157 +0,0 @@
package com.agileboot.domain.system.dept.model;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.domain.system.dept.db.SysDeptEntity;
import com.agileboot.domain.system.dept.db.SysDeptService;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
class DeptModelTest {
private static final Long DEPT_ID = 1L;
private static final Long PARENT_ID = 2L;
private final SysDeptService deptService = mock(SysDeptService.class);
private final DeptModelFactory deptModelFactory = new DeptModelFactory(deptService);
@Test
void testCheckDeptNameUnique() {
DeptModel deptModel = deptModelFactory.create();
deptModel.setDeptName("dept 1");
when(
deptService.isDeptNameDuplicated(ArgumentMatchers.any(), ArgumentMatchers.any(),
ArgumentMatchers.any())).thenReturn(true);
ApiException exception = assertThrows(ApiException.class, deptModel::checkDeptNameUnique);
Assertions.assertEquals(Business.DEPT_NAME_IS_NOT_UNIQUE, exception.getErrorCode());
}
@Test
void testCheckParentIdConflict() {
DeptModel deptModel = deptModelFactory.create();
Long sameId = 1L;
deptModel.setDeptId(sameId);
deptModel.setParentId(sameId);
ApiException exception = assertThrows(ApiException.class, deptModel::checkParentIdConflict);
Assertions.assertEquals(Business.DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF, exception.getErrorCode());
}
@Test
void testCheckHasChildDept() {
DeptModel deptModel = deptModelFactory.create();
deptModel.setDeptId(DEPT_ID);
when(deptService.hasChildrenDept((DEPT_ID), eq(null))).thenReturn(true);
ApiException exception = assertThrows(ApiException.class, deptModel::checkHasChildDept);
Assertions.assertEquals(Business.DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE, exception.getErrorCode());
}
@Test
void testCheckDeptAssignedToUsers() {
DeptModel deptModel = deptModelFactory.create();
deptModel.setDeptId(DEPT_ID);
when(deptService.isDeptAssignedToUsers(DEPT_ID)).thenReturn(true);
ApiException exception = assertThrows(ApiException.class,
deptModel::checkDeptAssignedToUsers);
Assertions.assertEquals(Business.DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE, exception.getErrorCode());
}
@Test
void testGenerateAncestorsWhenParentIdZero() {
DeptModel deptModel = deptModelFactory.create();
deptModel.setParentId(0L);
deptModel.generateAncestors();
Assertions.assertEquals(deptModel.getAncestors(), deptModel.getParentId().toString());
}
@Test
void testGenerateAncestorsWhenParentDeptNotExist() {
DeptModel deptModel = deptModelFactory.create();
deptModel.setParentId(PARENT_ID);
when(deptService.getById(PARENT_ID)).thenReturn(null);
ApiException exception = assertThrows(ApiException.class, deptModel::generateAncestors);
Assertions.assertEquals(Business.DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED, exception.getErrorCode());
}
@Test
void testGenerateAncestorsWhenParentDeptDisabled() {
DeptModel deptModel = deptModelFactory.create();
deptModel.setParentId(PARENT_ID);
SysDeptEntity parentDept = new SysDeptEntity();
parentDept.setStatus(0);
when(deptService.getById(PARENT_ID)).thenReturn(parentDept);
ApiException exception = assertThrows(ApiException.class, deptModel::generateAncestors);
Assertions.assertEquals(Business.DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED, exception.getErrorCode());
}
@Test
void testGenerateAncestorsSuccessful() {
DeptModel deptModel = deptModelFactory.create();
deptModel.setParentId(PARENT_ID);
SysDeptEntity parentDept = new SysDeptEntity();
parentDept.setStatus(1);
parentDept.setAncestors("1,100");
when(deptService.getById(PARENT_ID)).thenReturn(parentDept);
deptModel.generateAncestors();
Assertions.assertEquals("1,100,2", deptModel.getAncestors());
}
@Test
void testCheckStatusAllowChangeWhenDisableButHasChildDept() {
DeptModel deptModel = deptModelFactory.create();
deptModel.setDeptId(DEPT_ID);
deptModel.setStatus(0);
when(deptService.hasChildrenDept(DEPT_ID, true)).thenReturn(true);
ApiException exception = assertThrows(ApiException.class, deptModel::checkStatusAllowChange);
Assertions.assertEquals(Business.DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE, exception.getErrorCode());
}
@Test
void testCheckStatusAllowChangeWhenDisableButNoChildDept() {
DeptModel deptModel = deptModelFactory.create();
deptModel.setDeptId(DEPT_ID);
deptModel.setStatus(0);
when(deptService.hasChildrenDept(DEPT_ID, true)).thenReturn(false);
Assertions.assertDoesNotThrow(deptModel::checkStatusAllowChange);
}
@Test
void testCheckStatusAllowChangeWhenEnableAndHasChildDept() {
DeptModel deptModel = deptModelFactory.create();
deptModel.setDeptId(DEPT_ID);
deptModel.setStatus(1);
when(deptService.hasChildrenDept(DEPT_ID, true)).thenReturn(true);
Assertions.assertDoesNotThrow(deptModel::checkStatusAllowChange);
}
}
@@ -1,86 +0,0 @@
package com.agileboot.domain.system.post.model;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import com.agileboot.common.exception.ApiException;
import com.agileboot.common.exception.error.ErrorCode.Business;
import com.agileboot.domain.system.post.db.SysPostService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
class PostModelTest {
private final SysPostService postService = mock(SysPostService.class);
private final PostModelFactory postModelFactory = new PostModelFactory(postService);
private static final long POST_ID = 1L;
@AfterEach
public void clean() {
Mockito.reset(postService);
}
@Test
void testCheckCanBeDeleteWhenFailed() {
PostModel postModel = postModelFactory.create();
postModel.setPostId(POST_ID);
when(postService.isAssignedToUsers(POST_ID)).thenReturn(true);
ApiException exception = assertThrows(ApiException.class, postModel::checkCanBeDelete);
Assertions.assertEquals(Business.POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED, exception.getErrorCode());
}
@Test
void testCheckCanBeDeleteWhenSuccessful() {
PostModel postModel = postModelFactory.create();
postModel.setPostId(POST_ID);
when(postService.isAssignedToUsers(POST_ID)).thenReturn(true);
ApiException exception = assertThrows(ApiException.class, postModel::checkCanBeDelete);
Assertions.assertEquals(Business.POST_ALREADY_ASSIGNED_TO_USER_CAN_NOT_BE_DELETED, exception.getErrorCode());
}
@Test
void testCheckPostNameUnique() {
PostModel postWithSameName = postModelFactory.create();
postWithSameName.setPostId(POST_ID);
postWithSameName.setPostName("post 1");
PostModel postWithNewName = postModelFactory.create();
postWithNewName.setPostName("post 2");
postWithNewName.setPostId(POST_ID);
when(postService.isPostNameDuplicated(POST_ID, eq("post 1"))).thenReturn(true);
when(postService.isPostNameDuplicated(POST_ID, eq("post 2"))).thenReturn(false);
ApiException exception = assertThrows(ApiException.class, postWithSameName::checkPostNameUnique);
Assertions.assertEquals(Business.POST_NAME_IS_NOT_UNIQUE, exception.getErrorCode());
Assertions.assertDoesNotThrow(postWithNewName::checkPostNameUnique);
}
@Test
void testCheckPostCodeUnique() {
PostModel postWithSameCode = postModelFactory.create();
postWithSameCode.setPostId(POST_ID);
postWithSameCode.setPostCode("code 1");
PostModel postWithNewCode = postModelFactory.create();
postWithNewCode.setPostId(POST_ID);
postWithNewCode.setPostCode("code 2");
when(postService.isPostCodeDuplicated(POST_ID, "code 1")).thenReturn(true);
when(postService.isPostCodeDuplicated(POST_ID, "code 2")).thenReturn(false);
ApiException exception = assertThrows(ApiException.class, postWithSameCode::checkPostCodeUnique);
Assertions.assertEquals(Business.POST_CODE_IS_NOT_UNIQUE, exception.getErrorCode());
Assertions.assertDoesNotThrow(postWithNewCode::checkPostCodeUnique);
}
}

Some files were not shown because too many files have changed in this diff Show More