Compare commits
4 Commits
main
..
98eae63435
| Author | SHA1 | Date | |
|---|---|---|---|
| 98eae63435 | |||
| d26a012edb | |||
| ebccb7283f | |||
| 2a702fa6a9 |
@@ -1,24 +0,0 @@
|
|||||||
node_modules
|
|
||||||
**/node_modules
|
|
||||||
.pnpm-store
|
|
||||||
|
|
||||||
.git
|
|
||||||
.gitignore
|
|
||||||
.DS_Store
|
|
||||||
*.local
|
|
||||||
*.log
|
|
||||||
|
|
||||||
backend/.vercel
|
|
||||||
backend/node_modules
|
|
||||||
|
|
||||||
frontend/app/dist
|
|
||||||
frontend/app/deploy_versions
|
|
||||||
frontend/app/.temp
|
|
||||||
frontend/app/.rn_temp
|
|
||||||
frontend/app/.swc
|
|
||||||
|
|
||||||
frontend/web/dist
|
|
||||||
frontend/web/dist-ssr
|
|
||||||
frontend/web/report.html
|
|
||||||
frontend/web/.eslintcache
|
|
||||||
frontend/web/tests/**/coverage
|
|
||||||
+7
-3
@@ -1,8 +1,12 @@
|
|||||||
MYSQL_ROOT_PASSWORD=root123
|
MYSQL_ROOT_PASSWORD=root123
|
||||||
MYSQL_DATABASE=collab_ledger
|
MYSQL_DATABASE=todo_agileboot_pure
|
||||||
MYSQL_APP_USERNAME=collab_ledger_app
|
MYSQL_APP_USERNAME=todo_app
|
||||||
MYSQL_APP_PASSWORD=collab_ledger_app123
|
MYSQL_APP_PASSWORD=todo_app123
|
||||||
MYSQL_PORT=3306
|
MYSQL_PORT=3306
|
||||||
|
|
||||||
REDIS_PASSWORD=redis123
|
REDIS_PASSWORD=redis123
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
APP_PORT=8080
|
||||||
|
WEB_PORT=8081
|
||||||
|
JAVA_OPTS=-Xms256m -Xmx512m
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
build
|
|
||||||
.taro
|
|
||||||
.cache
|
|
||||||
coverage
|
|
||||||
*.d.ts
|
|
||||||
public
|
|
||||||
src/assets
|
|
||||||
frontend/web/public
|
|
||||||
frontend/web/src/assets
|
|
||||||
frontend/app/.taro
|
|
||||||
frontend/app/dist
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
# Common
|
# Common
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/.env
|
/.env
|
||||||
/node_modules/
|
|
||||||
.idea/
|
.idea/
|
||||||
*.iws
|
*.iws
|
||||||
*.iml
|
*.iml
|
||||||
@@ -38,7 +37,6 @@
|
|||||||
/frontend/web/.vscode/launch.json
|
/frontend/web/.vscode/launch.json
|
||||||
|
|
||||||
# Backend build tools
|
# Backend build tools
|
||||||
/backend/node_modules/
|
|
||||||
/backend/.gradle/
|
/backend/.gradle/
|
||||||
/backend/build/
|
/backend/build/
|
||||||
!/backend/gradle/wrapper/gradle-wrapper.jar
|
!/backend/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
||||||
build
|
|
||||||
.taro
|
|
||||||
.cache
|
|
||||||
coverage
|
|
||||||
public
|
|
||||||
src/assets
|
|
||||||
src/style/reset.scss
|
|
||||||
frontend/web/public
|
|
||||||
frontend/web/src/assets
|
|
||||||
frontend/web/src/style/reset.scss
|
|
||||||
frontend/app/src/app.scss
|
|
||||||
frontend/app/src/pages/index/index.scss
|
|
||||||
src/style/reset.scss
|
|
||||||
src/app.scss
|
|
||||||
src/pages/index/index.scss
|
|
||||||
@@ -2,10 +2,11 @@
|
|||||||
|
|
||||||
- 新增或修改代码前,必须阅读并遵循 `docs/clean-code-contract.md`。
|
- 新增或修改代码前,必须阅读并遵循 `docs/clean-code-contract.md`。
|
||||||
- 后端新增或修改业务功能前,必须阅读 `docs/backend-feature-development.md`。
|
- 后端新增或修改业务功能前,必须阅读 `docs/backend-feature-development.md`。
|
||||||
- 后端代码必须遵循 `Route -> Feature Service -> Prisma/Redis/shared Service` 的组织方式。
|
- 后端代码必须遵循 `Controller -> ApplicationService -> Model -> db Service/Mapper` 的组织方式。
|
||||||
- 不要把业务规则堆在 Route。
|
- 不要把业务规则写在 Controller。
|
||||||
- 不要在 Route 中直接扩散 Prisma 原始记录给前端。
|
- 不要让 Controller 直接调用 Mapper。
|
||||||
- 复杂查询结果由 Feature Service 转换为稳定 DTO。
|
- 不要直接把 Entity 或 DO 返回给前端。
|
||||||
|
- 复杂查询结果对象可以使用 `XxxDO`,放在 `db` 包下,并由 ApplicationService 转换为 DTO。
|
||||||
- 字典类需求不要默认创建字典管理表;本项目现有字典数据使用 Enum 和缓存。
|
- 字典类需求不要默认创建字典管理表;本项目现有字典数据使用 Enum 和缓存。
|
||||||
- `frontend/web` 新增或修改业务功能前,必须阅读 `docs/web-feature-development.md`。
|
- `frontend/web` 新增或修改业务功能前,必须阅读 `docs/web-feature-development.md`。
|
||||||
- Web 前端接口必须通过 `@/utils/http` 封装调用,不要直接使用 Axios。
|
- Web 前端接口必须通过 `@/utils/http` 封装调用,不要直接使用 Axios。
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# CollabLedger
|
# Simple Todo
|
||||||
|
|
||||||
`CollabLedger` 是一个前后端分离的协作账本/后台管理项目。后端使用 Hono,前端包含 Web 管理端和 Taro 多端应用,根目录 Docker Compose 用于启动 MySQL 和 Redis。
|
`simple-todo` 是一个前后端分离的待办/后台管理模板项目。后端基于 AgileBoot 的 Spring Boot 分层架构改造,前端包含 Web 管理端和 Taro 多端应用,根目录提供 Docker Compose 用于本地依赖启动和生产部署。
|
||||||
|
|
||||||
## 项目架构
|
## 项目架构
|
||||||
|
|
||||||
@@ -8,35 +8,37 @@
|
|||||||
|
|
||||||
当前项目是在以下开源项目基础上改造:
|
当前项目是在以下开源项目基础上改造:
|
||||||
|
|
||||||
- 后端原仓库:<https://github.com/valarchie/collab_ledger-Back-End>
|
- 后端原仓库:<https://github.com/valarchie/AgileBoot-Back-End>
|
||||||
- Web 前端原仓库:<https://github.com/valarchie/collab_ledger-front-end-pure>
|
- Web 前端原仓库:<https://github.com/valarchie/agileboot-front-end-pure>
|
||||||
- Web UI 上游项目:<https://github.com/pure-admin/vue-pure-admin>
|
- Web UI 上游项目:<https://github.com/pure-admin/vue-pure-admin>
|
||||||
|
|
||||||
### 目录结构
|
### 目录结构
|
||||||
|
|
||||||
```text
|
```text
|
||||||
CollabLedger
|
simple-todo
|
||||||
├── backend # Hono 后端
|
├── backend # Spring Boot 后端
|
||||||
│ ├── prisma # Prisma schema
|
│ ├── agileboot-admin # 后台管理接口启动模块
|
||||||
│ ├── sql # MySQL 初始化 SQL
|
│ ├── agileboot-api # 开放接口模块
|
||||||
│ └── src # 后端源码
|
│ ├── agileboot-common # 通用工具与基础能力
|
||||||
|
│ ├── agileboot-domain # 业务领域模块
|
||||||
|
│ ├── agileboot-infrastructure # 基础设施、配置、集成能力
|
||||||
|
│ └── sql # 初始化 SQL
|
||||||
├── frontend
|
├── frontend
|
||||||
│ ├── web # Vite + Vue 3 Web 管理端
|
│ ├── web # Vite + Vue 3 Web 管理端
|
||||||
│ └── app # Taro + Vue 3 多端应用
|
│ └── app # Taro + Vue 3 多端应用
|
||||||
├── docs # 项目开发约定
|
├── docs # 项目开发约定
|
||||||
├── docker-compose.yml # MySQL、Redis 编排
|
├── docker-compose.yml # MySQL、Redis、后端、Web 编排
|
||||||
└── .env.example # Docker Compose 环境变量示例
|
└── .env.example # Docker Compose 环境变量示例
|
||||||
```
|
```
|
||||||
|
|
||||||
## 基础环境
|
## 基础环境
|
||||||
|
|
||||||
|
- JDK 8+
|
||||||
|
- Maven 3.8+,也可以直接使用 `backend/mvnw` 或 `backend/mvnw.cmd`
|
||||||
- Node.js 22+,推荐配合 Corepack 使用 pnpm
|
- Node.js 22+,推荐配合 Corepack 使用 pnpm
|
||||||
- pnpm 11.1.3+
|
- pnpm,Dockerfile 中使用 `pnpm@11.1.3`
|
||||||
- Docker Desktop 或 Docker Engine + Docker Compose
|
- Docker Desktop 或 Docker Engine + Docker Compose
|
||||||
|
|
||||||
项目根目录使用 pnpm workspace 统一管理 `backend`、`frontend/web` 和
|
|
||||||
`frontend/app`。依赖安装和常用脚本都在根目录执行。
|
|
||||||
|
|
||||||
## 开发启动步骤
|
## 开发启动步骤
|
||||||
|
|
||||||
### 1. 准备环境变量
|
### 1. 准备环境变量
|
||||||
@@ -53,7 +55,7 @@ Windows PowerShell:
|
|||||||
Copy-Item .env.example .env
|
Copy-Item .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
`.env.example` 默认把 MySQL 和 Redis 映射到宿主机 `3306`、`6379`。
|
`.env.example` 默认把 MySQL 和 Redis 映射到宿主机 `3306`、`6379`,与后端 `application-dev.yml` 的本地开发配置保持一致。
|
||||||
|
|
||||||
### 2. 启动 MySQL 和 Redis
|
### 2. 启动 MySQL 和 Redis
|
||||||
|
|
||||||
@@ -61,7 +63,7 @@ Copy-Item .env.example .env
|
|||||||
docker compose up -d mysql redis
|
docker compose up -d mysql redis
|
||||||
```
|
```
|
||||||
|
|
||||||
首次启动 MySQL 容器时,Compose 会把 `backend/sql/init.sql` 挂载到 `/docker-entrypoint-initdb.d/01-init.sql`,由 MySQL 镜像自动初始化数据库。
|
首次启动 MySQL 容器时,Compose 会把 `backend/sql/agileboot.sql` 挂载到 `/docker-entrypoint-initdb.d/01-agileboot.sql`,由 MySQL 镜像自动初始化数据库。
|
||||||
|
|
||||||
如果数据库卷已经存在,初始化 SQL 不会重复执行。需要重建本地数据时可以执行:
|
如果数据库卷已经存在,初始化 SQL 不会重复执行。需要重建本地数据时可以执行:
|
||||||
|
|
||||||
@@ -72,35 +74,40 @@ docker compose up -d mysql redis
|
|||||||
|
|
||||||
`docker compose down -v` 会删除本地 MySQL 和 Redis 数据卷,请确认不需要保留本地数据后再执行。
|
`docker compose down -v` 会删除本地 MySQL 和 Redis 数据卷,请确认不需要保留本地数据后再执行。
|
||||||
|
|
||||||
### 3. 安装依赖
|
### 3. 启动后端
|
||||||
|
|
||||||
|
Windows PowerShell:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
cd backend
|
||||||
|
.\mvnw.cmd -pl agileboot-admin spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
macOS / Linux:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
cd backend
|
||||||
|
./mvnw -pl agileboot-admin spring-boot:run
|
||||||
|
```
|
||||||
|
|
||||||
|
后端默认地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://localhost:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
接口文档地址:
|
||||||
|
|
||||||
|
```text
|
||||||
|
http://localhost:8080/swagger-ui/index.html
|
||||||
|
http://localhost:8080/v3/api-docs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 启动 Web 管理端
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd frontend
|
||||||
pnpm install
|
pnpm install
|
||||||
```
|
|
||||||
|
|
||||||
### 4. 启动 Hono 后端
|
|
||||||
|
|
||||||
本地开发地址为 `http://localhost:3000`。
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dev:backend
|
|
||||||
```
|
|
||||||
|
|
||||||
如果要按 Vercel 本地模拟方式启动,需要先全局安装 Vercel CLI:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add -g vercel
|
|
||||||
```
|
|
||||||
|
|
||||||
然后执行:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dev:backend:vercel
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. 启动 Web 管理端
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dev:web
|
pnpm dev:web
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -117,27 +124,25 @@ Web 开发服务默认地址:
|
|||||||
http://localhost:80
|
http://localhost:80
|
||||||
```
|
```
|
||||||
|
|
||||||
开发环境下 `frontend/web/vite.config.ts` 已默认把 `/dev-api` 代理到 `http://localhost:3000`。
|
|
||||||
|
|
||||||
如果 80 端口被占用,可以修改 `frontend/web/.env.development` 中的 `VITE_PORT`。
|
如果 80 端口被占用,可以修改 `frontend/web/.env.development` 中的 `VITE_PORT`。
|
||||||
|
|
||||||
### 6. 启动 App 端,可选
|
### 5. 启动 App 端,可选
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
cd frontend
|
||||||
pnpm dev:app:weapp
|
pnpm dev:app:weapp
|
||||||
```
|
```
|
||||||
|
|
||||||
H5 模式:
|
H5 模式:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
cd frontend
|
||||||
pnpm dev:app:h5
|
pnpm dev:app:h5
|
||||||
```
|
```
|
||||||
|
|
||||||
`frontend/app/config/dev.ts` 已默认把 `TARO_APP_API_BASE` 指向 `http://localhost:3000`。
|
|
||||||
|
|
||||||
## 部署步骤
|
## 部署步骤
|
||||||
|
|
||||||
Hono 后端和 Web 管理端推荐分别部署到 Vercel 或其他 Node.js/静态托管环境。根目录 Docker Compose 只负责启动 MySQL 和 Redis。
|
生产部署推荐使用根目录的 Docker Compose。它会启动 MySQL、Redis、后端服务和 Web Nginx 服务。
|
||||||
|
|
||||||
### 1. 准备生产环境变量
|
### 1. 准备生产环境变量
|
||||||
|
|
||||||
@@ -149,122 +154,79 @@ cp .env.example .env
|
|||||||
|
|
||||||
```env
|
```env
|
||||||
MYSQL_ROOT_PASSWORD=请替换为强密码
|
MYSQL_ROOT_PASSWORD=请替换为强密码
|
||||||
MYSQL_DATABASE=collab_ledger
|
MYSQL_DATABASE=todo_agileboot_pure
|
||||||
MYSQL_APP_USERNAME=collab_ledger_app
|
MYSQL_APP_USERNAME=todo_app
|
||||||
MYSQL_APP_PASSWORD=请替换为强密码
|
MYSQL_APP_PASSWORD=请替换为强密码
|
||||||
MYSQL_PORT=13306
|
MYSQL_PORT=13306
|
||||||
|
|
||||||
REDIS_PASSWORD=请替换为强密码
|
REDIS_PASSWORD=请替换为强密码
|
||||||
REDIS_PORT=16379
|
REDIS_PORT=16379
|
||||||
|
|
||||||
|
APP_PORT=8080
|
||||||
|
WEB_PORT=8081
|
||||||
|
JAVA_OPTS=-Xms256m -Xmx512m
|
||||||
```
|
```
|
||||||
|
|
||||||
生产环境建议把 `MYSQL_PORT`、`REDIS_PORT` 改成非默认宿主机端口,例如上面的 `13306`、`16379`。这两个变量只影响宿主机暴露端口,不影响容器内部端口。
|
生产环境建议把 `MYSQL_PORT`、`REDIS_PORT` 改成非默认宿主机端口,例如上面的 `13306`、`16379`。这两个变量只影响宿主机暴露端口,不影响容器内部端口;后端容器仍通过 Compose 内部网络访问 MySQL `3306` 和 Redis `6379`。
|
||||||
|
|
||||||
如果数据库和 Redis 只给后端使用,建议进一步通过服务器防火墙限制这些端口的外部访问,或按实际部署需要移除 `docker-compose.yml` 中 MySQL/Redis 的 `ports` 暴露配置。
|
如果数据库和 Redis 只给后端容器使用,建议进一步通过服务器防火墙限制这些端口的外部访问,或按实际部署需要移除 `docker-compose.yml` 中 MySQL/Redis 的 `ports` 暴露配置。
|
||||||
|
|
||||||
不要提交真实生产 `.env` 文件。
|
不要提交真实生产 `.env` 文件。
|
||||||
|
|
||||||
### 2. 启动数据库和 Redis
|
### 2. 构建并启动完整服务
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d mysql redis
|
docker compose --profile prod up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 使用 Vercel CLI 部署后端
|
Compose 会执行以下构建:
|
||||||
|
|
||||||
先全局安装并登录 Vercel CLI:
|
- `backend/Dockerfile`:使用 Maven 构建 `agileboot-admin`,运行 Spring Boot Jar。
|
||||||
|
- `frontend/web/Dockerfile`:构建 Web 静态文件,并用 Nginx 提供访问。
|
||||||
|
|
||||||
```bash
|
Web 容器中的 Nginx 会把 `/prod-api/` 代理到 Compose 内部的后端服务 `app:8080`。
|
||||||
pnpm add -g vercel
|
|
||||||
vercel login
|
|
||||||
```
|
|
||||||
|
|
||||||
后端作为单独的 Vercel Project 部署,Root Directory 为 `backend`:
|
### 3. 访问服务
|
||||||
|
|
||||||
```bash
|
默认访问地址:
|
||||||
cd backend
|
|
||||||
vercel link
|
|
||||||
```
|
|
||||||
|
|
||||||
按提示创建或关联后端项目。然后配置后端环境变量:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vercel env add DATABASE_URL production
|
|
||||||
vercel env add REDIS_URL production
|
|
||||||
vercel env add JWT_SECRET production
|
|
||||||
vercel env add PUBLIC_FILE_BASE_URL production
|
|
||||||
```
|
|
||||||
|
|
||||||
常见连接串格式:
|
|
||||||
|
|
||||||
```env
|
|
||||||
DATABASE_URL=mysql://user:password@host:3306/collab_ledger
|
|
||||||
REDIS_URL=redis://:password@host:6379
|
|
||||||
```
|
|
||||||
|
|
||||||
如果 Redis 服务要求 TLS,使用 `rediss://`。Vercel Functions 不能连接本机 `127.0.0.1`、`localhost` 或 Docker 内网地址,MySQL 和 Redis 必须使用 Vercel 能访问的公网或托管服务地址。
|
|
||||||
|
|
||||||
部署后端:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
vercel deploy --prod
|
|
||||||
cd ..
|
|
||||||
```
|
|
||||||
|
|
||||||
记下部署后的后端域名,例如 `https://your-backend.vercel.app`。
|
|
||||||
|
|
||||||
### 4. 使用 Vercel CLI 部署 Web 管理端
|
|
||||||
|
|
||||||
Web 管理端作为另一个 Vercel Project 部署,Root Directory 为 `frontend/web`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend/web
|
|
||||||
vercel link
|
|
||||||
```
|
|
||||||
|
|
||||||
按提示创建或关联 Web 项目。Vercel 识别 Vite 后,构建配置保持:
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Install Command: pnpm install
|
Web:http://localhost:8081
|
||||||
Build Command: pnpm build
|
后端:http://localhost:8080
|
||||||
Output Directory: dist
|
|
||||||
```
|
```
|
||||||
|
|
||||||
配置 Web 指向后端公网地址:
|
如果修改了 `.env` 中的 `WEB_PORT` 或 `APP_PORT`,以实际端口为准。
|
||||||
|
|
||||||
|
### 4. 常用运维命令
|
||||||
|
|
||||||
|
查看服务状态:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vercel env add VITE_APP_BASE_API production
|
docker compose --profile prod ps
|
||||||
```
|
```
|
||||||
|
|
||||||
填入上一步得到的后端域名,例如 `https://your-backend.vercel.app`。
|
查看后端日志:
|
||||||
|
|
||||||
部署 Web:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
vercel deploy --prod
|
docker compose --profile prod logs -f app
|
||||||
cd ../..
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`frontend/web/vercel.json` 已配置前端路由回退,直接刷新管理端子路由时会返回 `index.html`。
|
查看 Web 日志:
|
||||||
|
|
||||||
### 5. 常用运维命令
|
|
||||||
|
|
||||||
查看数据库和 Redis 状态:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose ps
|
docker compose --profile prod logs -f web
|
||||||
```
|
```
|
||||||
|
|
||||||
停止服务:
|
停止服务:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose down
|
docker compose --profile prod down
|
||||||
```
|
```
|
||||||
|
|
||||||
停止并删除数据卷:
|
停止并删除数据卷:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose down -v
|
docker compose --profile prod down -v
|
||||||
```
|
```
|
||||||
|
|
||||||
`down -v` 会删除数据库和 Redis 数据,生产环境谨慎使用。
|
`down -v` 会删除数据库和 Redis 数据,生产环境谨慎使用。
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
*.log
|
||||||
|
**/target
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
.gradle
|
||||||
|
.mvn/wrapper/maven-wrapper.jar
|
||||||
|
.env
|
||||||
|
docker-compose.yml
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
DATABASE_URL="mysql://collab_ledger_app:collab_ledger_app123@127.0.0.1:3306/collab_ledger"
|
|
||||||
REDIS_URL="redis://:redis123@127.0.0.1:6379"
|
|
||||||
JWT_SECRET="sdhfkjshBN6rr32df38"
|
|
||||||
JWT_HEADER="Authorization"
|
|
||||||
JWT_AUTO_REFRESH_MINUTES="20"
|
|
||||||
UPLOAD_DIR="./uploads"
|
|
||||||
PUBLIC_FILE_BASE_URL="http://localhost:3000/profile"
|
|
||||||
CAPTCHA_ENABLED="false"
|
|
||||||
REGISTER_ENABLED="true"
|
|
||||||
RSA_PRIVATE_KEY=""
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
.vercel
|
|
||||||
.env*.local
|
|
||||||
BIN
Binary file not shown.
+18
@@ -0,0 +1,18 @@
|
|||||||
|
# Licensed to the Apache Software Foundation (ASF) under one
|
||||||
|
# or more contributor license agreements. See the NOTICE file
|
||||||
|
# distributed with this work for additional information
|
||||||
|
# regarding copyright ownership. The ASF licenses this file
|
||||||
|
# to you under the Apache License, Version 2.0 (the
|
||||||
|
# "License"); you may not use this file except in compliance
|
||||||
|
# with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing,
|
||||||
|
# software distributed under the License is distributed on an
|
||||||
|
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||||
|
# KIND, either express or implied. See the License for the
|
||||||
|
# specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip
|
||||||
|
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
|
||||||
@@ -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,569 @@
|
|||||||
|
<code_scheme name="GoogleStyle" version="173">
|
||||||
|
<option name="OTHER_INDENT_OPTIONS">
|
||||||
|
<value>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="RIGHT_MARGIN" value="100" />
|
||||||
|
<AndroidXmlCodeStyleSettings>
|
||||||
|
<option name="USE_CUSTOM_SETTINGS" value="true" />
|
||||||
|
<option name="LAYOUT_SETTINGS">
|
||||||
|
<value>
|
||||||
|
<option name="INSERT_BLANK_LINE_BEFORE_TAG" value="false" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</AndroidXmlCodeStyleSettings>
|
||||||
|
<JSCodeStyleSettings version="0">
|
||||||
|
<option name="INDENT_CHAINED_CALLS" value="false" />
|
||||||
|
</JSCodeStyleSettings>
|
||||||
|
<JavaCodeStyleSettings>
|
||||||
|
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
|
||||||
|
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
|
||||||
|
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" />
|
||||||
|
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="IMPORT_LAYOUT_TABLE">
|
||||||
|
<value>
|
||||||
|
<package name="" withSubpackages="true" static="true" />
|
||||||
|
<emptyLine />
|
||||||
|
<package name="" withSubpackages="true" static="false" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="JD_ALIGN_PARAM_COMMENTS" value="false" />
|
||||||
|
<option name="JD_ALIGN_EXCEPTION_COMMENTS" value="false" />
|
||||||
|
<option name="JD_P_AT_EMPTY_LINES" value="false" />
|
||||||
|
<option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
|
||||||
|
<option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
|
||||||
|
<option name="JD_KEEP_EMPTY_RETURN" value="false" />
|
||||||
|
</JavaCodeStyleSettings>
|
||||||
|
<Objective-C>
|
||||||
|
<option name="INDENT_NAMESPACE_MEMBERS" value="0" />
|
||||||
|
<option name="INDENT_C_STRUCT_MEMBERS" value="2" />
|
||||||
|
<option name="INDENT_CLASS_MEMBERS" value="2" />
|
||||||
|
<option name="INDENT_VISIBILITY_KEYWORDS" value="1" />
|
||||||
|
<option name="INDENT_INSIDE_CODE_BLOCK" value="2" />
|
||||||
|
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="FUNCTION_PARAMETERS_WRAP" value="5" />
|
||||||
|
<option name="FUNCTION_CALL_ARGUMENTS_WRAP" value="5" />
|
||||||
|
<option name="TEMPLATE_CALL_ARGUMENTS_WRAP" value="5" />
|
||||||
|
<option name="TEMPLATE_CALL_ARGUMENTS_ALIGN_MULTILINE" value="true" />
|
||||||
|
<option name="ALIGN_INIT_LIST_IN_COLUMNS" value="false" />
|
||||||
|
<option name="SPACE_BEFORE_SUPERCLASS_COLON" value="false" />
|
||||||
|
</Objective-C>
|
||||||
|
<Objective-C-extensions>
|
||||||
|
<option name="GENERATE_INSTANCE_VARIABLES_FOR_PROPERTIES" value="ASK" />
|
||||||
|
<option name="RELEASE_STYLE" value="IVAR" />
|
||||||
|
<option name="TYPE_QUALIFIERS_PLACEMENT" value="BEFORE" />
|
||||||
|
<file>
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
|
||||||
|
</file>
|
||||||
|
<class>
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
|
||||||
|
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
|
||||||
|
</class>
|
||||||
|
<extensions>
|
||||||
|
<pair source="cc" header="h" />
|
||||||
|
<pair source="c" header="h" />
|
||||||
|
</extensions>
|
||||||
|
</Objective-C-extensions>
|
||||||
|
<Python>
|
||||||
|
<option name="USE_CONTINUATION_INDENT_FOR_ARGUMENTS" value="true" />
|
||||||
|
</Python>
|
||||||
|
<TypeScriptCodeStyleSettings version="0">
|
||||||
|
<option name="INDENT_CHAINED_CALLS" value="false" />
|
||||||
|
</TypeScriptCodeStyleSettings>
|
||||||
|
<XML>
|
||||||
|
<option name="XML_ALIGN_ATTRIBUTES" value="false" />
|
||||||
|
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
|
||||||
|
</XML>
|
||||||
|
<codeStyleSettings language="CSS">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="ECMA Script Level 4">
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||||
|
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||||
|
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||||
|
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||||
|
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||||
|
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||||
|
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||||
|
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||||
|
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="HTML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JAVA">
|
||||||
|
<option name="RIGHT_MARGIN" value="120" />
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
|
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="1" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||||
|
<option name="ALIGN_MULTILINE_RESOURCES" value="false" />
|
||||||
|
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||||
|
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||||
|
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||||
|
<option name="EXTENDS_LIST_WRAP" value="1" />
|
||||||
|
<option name="THROWS_KEYWORD_WRAP" value="1" />
|
||||||
|
<option name="METHOD_CALL_CHAIN_WRAP" value="1" />
|
||||||
|
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||||
|
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||||
|
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||||
|
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||||
|
<option name="WRAP_COMMENTS" value="true" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WRAP_ON_TYPING" value="0" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JSON">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<option name="RIGHT_MARGIN" value="80" />
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||||
|
<option name="ALIGN_MULTILINE_FOR" value="false" />
|
||||||
|
<option name="CALL_PARAMETERS_WRAP" value="1" />
|
||||||
|
<option name="METHOD_PARAMETERS_WRAP" value="1" />
|
||||||
|
<option name="BINARY_OPERATION_WRAP" value="1" />
|
||||||
|
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="TERNARY_OPERATION_WRAP" value="1" />
|
||||||
|
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||||
|
<option name="ARRAY_INITIALIZER_WRAP" value="1" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="ObjectiveC">
|
||||||
|
<option name="RIGHT_MARGIN" value="80" />
|
||||||
|
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
|
||||||
|
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
|
||||||
|
<option name="BLANK_LINES_AFTER_IMPORTS" value="0" />
|
||||||
|
<option name="BLANK_LINES_AROUND_CLASS" value="0" />
|
||||||
|
<option name="BLANK_LINES_AROUND_METHOD" value="0" />
|
||||||
|
<option name="BLANK_LINES_AROUND_METHOD_IN_INTERFACE" value="0" />
|
||||||
|
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="false" />
|
||||||
|
<option name="BINARY_OPERATION_SIGN_ON_NEXT_LINE" value="true" />
|
||||||
|
<option name="FOR_STATEMENT_WRAP" value="1" />
|
||||||
|
<option name="ASSIGNMENT_WRAP" value="1" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="PROTO">
|
||||||
|
<option name="RIGHT_MARGIN" value="80" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Python">
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
|
<option name="RIGHT_MARGIN" value="80" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
|
||||||
|
<option name="PARENT_SETTINGS_INSTALLED" value="true" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="SASS">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="SCSS">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="TypeScript">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
<arrangement>
|
||||||
|
<rules>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:android</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>xmlns:.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:id</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:.*Style</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_width</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_height</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_weight</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_margin</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_marginTop</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_marginBottom</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_marginStart</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_marginEnd</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_marginLeft</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_marginRight</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:layout_.*</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:padding</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:paddingTop</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:paddingBottom</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:paddingStart</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:paddingEnd</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:paddingLeft</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*:paddingRight</NAME>
|
||||||
|
<XML_ATTRIBUTE />
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/apk/res-auto</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_NAMESPACE>http://schemas.android.com/tools</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<rule>
|
||||||
|
<match>
|
||||||
|
<AND>
|
||||||
|
<NAME>.*</NAME>
|
||||||
|
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||||
|
</AND>
|
||||||
|
</match>
|
||||||
|
<order>BY_NAME</order>
|
||||||
|
</rule>
|
||||||
|
</section>
|
||||||
|
</rules>
|
||||||
|
</arrangement>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="protobuf">
|
||||||
|
<option name="RIGHT_MARGIN" value="80" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 valarchie
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
+307
-71
@@ -1,108 +1,344 @@
|
|||||||
# Backend
|
|
||||||
|
|
||||||
这是 `CollabLedger` 的后端实现,按 Vercel 官方的 Hono 方式组织:入口放在 `src/index.ts`,默认导出 `app`,部署到 `Vercel Functions` 的 `Node.js runtime`,并保持现有前端 API 协议兼容。
|
<p align="center">
|
||||||
|
<img src="https://img.shields.io/badge/Release-V1.8.0-green.svg" alt="Downloads">
|
||||||
|
<img src="https://img.shields.io/badge/JDK-1.8+-green.svg" alt="Build Status">
|
||||||
|
<img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="Build Status">
|
||||||
|
<img src="https://img.shields.io/badge/Spring%20Boot-2.7.1-blue.svg" alt="Downloads">
|
||||||
|
<a target="_blank" href="https://bladex.vip">
|
||||||
|
<img src="https://img.shields.io/badge/Author-valarchie-ff69b4.svg" alt="Downloads">
|
||||||
|
</a>
|
||||||
|
<a target="_blank" href="https://bladex.vip">
|
||||||
|
<img src="https://img.shields.io/badge/Copyright%20-@Agileboot-%23ff3f59.svg" alt="Downloads">
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p align="center">
|
||||||
|
|
||||||
## 启动
|
<img alt="logo" height="200" src="https://oscimg.oschina.net/oscnet/up-eda2a402cc061f1f5f40d9ac4c084f4c98c.png">
|
||||||
|
</p>
|
||||||
|
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">AgileBoot v2.0.0 </h1>
|
||||||
|
<h4 align="center">基于SpringBoot+Vue3前后端分离的Java快速开发脚手架</h4>
|
||||||
|
<p align="center">
|
||||||
|
</p>
|
||||||
|
|
||||||
```bash
|
## ⚡平台简介⚡
|
||||||
pnpm install
|
|
||||||
pnpm dev:backend
|
AgileBoot是一套开源的全栈精简快速开发平台,毫无保留给个人及企业免费使用。本项目的目标是做一款精简可靠,代码风格优良,项目规范的小型开发脚手架。
|
||||||
|
适合个人开发者的小型项目或者公司内部项目使用。也可作为供初学者学习使用的案例。
|
||||||
|
|
||||||
|
|
||||||
|
* 前端是基于优秀的开源项目[Pure-Admin](https://github.com/pure-admin/vue-pure-admin)开发而成。在此感谢Pure-Admin作者。
|
||||||
|
* 前端采用Vue3、Element Plus、TypeScript、Pinia。对应前端仓库 [AgileBoot-Front-End](https://github.com/valarchie/AgileBoot-Front-End) ,保持同步更新。
|
||||||
|
* 后端采用Spring Boot、Spring Security & Jwt、Redis & MySql、Mybatis Plus、Hutool工具包。
|
||||||
|
* 权限认证使用Jwt,支持多终端认证系统。
|
||||||
|
* 支持注解式主从数据库切换,注解式请求限流,注解式重复请求拦截。
|
||||||
|
* 支持注解式菜单权限拦截,注解式数据权限拦截。
|
||||||
|
* 支持加载动态权限菜单,实时权限控制。
|
||||||
|
* ***有大量的单元测试,集成测试覆盖确保业务逻辑正确***。
|
||||||
|
|
||||||
|
***V1.0.0版本使用JS开发,V2.0.0版本使用TS开发***。
|
||||||
|
***V1.0.0地址:[后端(AgileBoot-Back-End-Basic)](https://github.com/valarchie/AgileBoot-Back-End-Basic) - [前端(AgileBoot-Front-End-Basic)](https://github.com/valarchie/AgileBoot-Front-End-Basic)***
|
||||||
|
|
||||||
|
> 有任何问题或者建议,可以在 _Issues_ 中提给作者。
|
||||||
|
>
|
||||||
|
> 您的Issue比Star更重要
|
||||||
|
>
|
||||||
|
> 如果觉得项目对您有帮助,可以来个Star ⭐
|
||||||
|
|
||||||
|
|
||||||
|
## 💥 在线体验 💥
|
||||||
|
演示地址:
|
||||||
|
- www.agileboot.vip
|
||||||
|
- www.agileboot.cc
|
||||||
|
> 账号密码:admin/admin123
|
||||||
|
|
||||||
|
|
||||||
|
## 🌴 项目背景 🌴
|
||||||
|
业余时间想做一些个人小项目,一开始找了很多开源项目比如Ruoyi / Jeecg / ElAdmin / RenRen-Fast / Guns / EAdmin
|
||||||
|
最后本项目选择基于Ruoyi项目进行完全重构改造。
|
||||||
|
首先非常感谢Ruoyi作者。但是Ruoyi项目存在太多缺陷。
|
||||||
|
- 命名比较乱七八糟(很多很糟糕的命名,包括机翻英语乱用)
|
||||||
|
- 项目分包以及模块比较乱
|
||||||
|
- 比较原始的Controller > Service > DAO的开发模式。过于面向过程。
|
||||||
|
- 一大堆自己造的轮子,并且没有UT覆盖。
|
||||||
|
- 大量逻辑嵌套在if else块当中
|
||||||
|
- 值的前后不统一,比如有的地方1代表是,有的地方1代表否
|
||||||
|
- 很多很奇怪的代码写法(比如return result > 0 ? true:false.. 一言难尽)
|
||||||
|
- 业务逻辑不集中,代码可读性较差。
|
||||||
|
|
||||||
|
|
||||||
|
于是我做了大量的重构工作。
|
||||||
|
|
||||||
|
### 重构内容
|
||||||
|
|
||||||
|
- 规范:
|
||||||
|
- 切分不同环境的启动文件
|
||||||
|
- 统一设计异常类
|
||||||
|
- 统一设计错误码并集中处理异常
|
||||||
|
- 统一系统内的变量并集中管理
|
||||||
|
- 统一返回模型
|
||||||
|
- 引入Google代码格式化模板
|
||||||
|
- 后端代码的命名基本都整改OK
|
||||||
|
- 前端代码的命名也非常混乱,进行了整改
|
||||||
|
- 规范系统内的常量
|
||||||
|
- 整改:
|
||||||
|
- 引入hutool包以及guava包去掉大量自己造的轮子,尽可能使用现成的轮子
|
||||||
|
- 去除代码中大量的warning
|
||||||
|
- 引入lombok去除大量getter setter代码
|
||||||
|
- 调整日志级别
|
||||||
|
- 字典类型数据完全用Enum进行代替
|
||||||
|
- 移除SQL注入的Filter,因为迁移到Mybatis Plus就不会有这个注入的问题
|
||||||
|
- XSS直接通过JSON序列化进行转义。
|
||||||
|
- 替换掉很多Deprecated的类以及配置
|
||||||
|
- 替换fastJson为Jackson
|
||||||
|
- 数据库的整体重构设计,缩减至10张表。
|
||||||
|
- 重新设计异步代码
|
||||||
|
- 前后端密码加密传输(更严谨的话,还是需要HTTPS)
|
||||||
|
- 重构权限校验和数据权限校验(直接都通过注解的形式)
|
||||||
|
- 优化:
|
||||||
|
- 优化异步服务
|
||||||
|
- 优化Redis缓存类,封装各个业务缓存,提供多级缓存实现(Redis+Guava)
|
||||||
|
- 提供三个层级的缓存供使用者调用(Map,Guava,Redis使用者可依情况选择使用哪个缓存类)
|
||||||
|
- 权限判断使用多级缓存
|
||||||
|
- IP地址查询引入离线包
|
||||||
|
- 前端优化字典数据缓存
|
||||||
|
- 启动优化
|
||||||
|
- i18n支持
|
||||||
|
- 优化excel工具类,代码更加简洁
|
||||||
|
- 将所有逻辑集中于Domain模块中
|
||||||
|
- 切面记录修改者和创建者
|
||||||
|
- 统一设置事务
|
||||||
|
|
||||||
|
## ✨ 使用 ✨
|
||||||
|
|
||||||
|
|
||||||
|
### 开发环境
|
||||||
|
|
||||||
|
- JDK
|
||||||
|
- Mysql
|
||||||
|
- Redis
|
||||||
|
- Node.js
|
||||||
|
|
||||||
|
### 技术栈
|
||||||
|
|
||||||
|
| 技术 | 说明 | 版本 |
|
||||||
|
|----------------|-----------------|-------------------|
|
||||||
|
| `springboot` | Java项目必备框架 | 2.7 |
|
||||||
|
| `druid` | alibaba数据库连接池 | 1.2.8 |
|
||||||
|
| `springdoc` | 文档生成 | 3.0.0 |
|
||||||
|
| `mybatis-plus` | 数据库框架 | 3.5.2 |
|
||||||
|
| `hutool` | 国产工具包(简单易用) | 3.5.2 |
|
||||||
|
| `mockito` | 单元测试模拟 | 1.10.19 |
|
||||||
|
| `guava` | 谷歌工具包(提供简易缓存实现) | 31.0.1-jre |
|
||||||
|
| `junit` | 单元测试 | 1.10.19 |
|
||||||
|
| `h2` | 内存数据库 | 1.10.19 |
|
||||||
|
| `jackson` | 比较安全的Json框架 | follow springboot |
|
||||||
|
| `knife4j` | 接口文档框架 | 3.0.3 |
|
||||||
|
| `Spring Task` | 定时任务框架(适合小型项目) | follow springboot |
|
||||||
|
|
||||||
|
|
||||||
|
### 启动说明
|
||||||
|
|
||||||
|
#### 前置准备: 下载前后端代码
|
||||||
|
|
||||||
|
```
|
||||||
|
git clone https://github.com/valarchie/AgileBoot-Back-End
|
||||||
|
git clone https://github.com/valarchie/AgileBoot-Front-End
|
||||||
```
|
```
|
||||||
|
|
||||||
默认本地地址:
|
#### 安装好Mysql和Redis
|
||||||
|
|
||||||
|
|
||||||
|
#### 后端启动
|
||||||
|
```
|
||||||
|
1. 生成所需的数据库表
|
||||||
|
找到后端项目根目录下的sql目录中的agileboot_xxxxx.sql脚本文件(取最新的sql文件)。 导入到你新建的数据库中。
|
||||||
|
|
||||||
|
2. 在admin模块底下,找到resource目录下的application-dev.yml文件
|
||||||
|
配置数据库以及Redis的 地址、端口、账号密码
|
||||||
|
|
||||||
|
3. 在根目录执行mvn install
|
||||||
|
|
||||||
|
4. 找到agileboot-admin模块中的AgileBootAdminApplication启动类,直接启动即可
|
||||||
|
|
||||||
|
5. 当出现以下字样即为启动成功
|
||||||
|
____ _ _ __ _ _
|
||||||
|
/ ___| | |_ __ _ _ __ | |_ _ _ _ __ ___ _ _ ___ ___ ___ ___ ___ / _| _ _ | || |
|
||||||
|
\___ \ | __|/ _` || '__|| __| | | | || '_ \ / __|| | | | / __|/ __|/ _ \/ __|/ __|| |_ | | | || || |
|
||||||
|
___) || |_| (_| || | | |_ | |_| || |_) | \__ \| |_| || (__| (__| __/\__ \\__ \| _|| |_| || ||_|
|
||||||
|
|____/ \__|\__,_||_| \__| \__,_|| .__/ |___/ \__,_| \___|\___|\___||___/|___/|_| \__,_||_|(_)
|
||||||
|
|_|
|
||||||
|
|
||||||
```text
|
|
||||||
http://localhost:3000
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`pnpm dev:backend` 使用直接的 Node 开发入口:
|
#### 前端启动
|
||||||
|
详细步骤请查看对应前端部分
|
||||||
|
|
||||||
|
```
|
||||||
|
1. pnpm install
|
||||||
|
|
||||||
|
2. pnpm run dev
|
||||||
|
|
||||||
|
3. 当出现以下字样时即为启动成功
|
||||||
|
|
||||||
|
vite v2.6.14 dev server running at:
|
||||||
|
|
||||||
|
> Local: http://127.0.0.1:80/
|
||||||
|
|
||||||
|
ready in 4376ms.
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dev:backend
|
|
||||||
```
|
```
|
||||||
|
|
||||||
如果要按 Vercel 官方方式本地模拟,需要先全局安装 Vercel CLI:
|
详细过程在这个文章中:[AgileBoot - 手把手一步一步带你Run起全栈项目(SpringBoot+Vue3)](https://juejin.cn/post/7153812187834744845)
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add -g vercel
|
> 对于想要尝试全栈项目的前端人员,这边提供更简便的后端启动方式,无需配置Mysql和Redis直接启动
|
||||||
|
#### 无Mysql/Redis 后端启动
|
||||||
|
```
|
||||||
|
1. 找到agilboot-admin模块下的resource文件中的application.yml文件
|
||||||
|
|
||||||
|
2. 配置以下两个值
|
||||||
|
spring.profiles.active: basic,dev
|
||||||
|
改为
|
||||||
|
spring.profiles.active: basic,test
|
||||||
|
|
||||||
|
agileboot.embedded.mysql: false
|
||||||
|
agileboot.embedded.redis: false
|
||||||
|
改为
|
||||||
|
agileboot.embedded.mysql: true
|
||||||
|
agileboot.embedded.redis: true
|
||||||
|
|
||||||
|
请注意:高版本的MacOS系统,无法启动内置的Redis
|
||||||
|
|
||||||
|
|
||||||
|
3. 找到agileboot-admin模块中的AgileBootAdminApplication启动类,直接启动即可
|
||||||
```
|
```
|
||||||
|
|
||||||
然后执行:
|
|
||||||
|
|
||||||
```bash
|
## 🙊 系统内置功能 🙊
|
||||||
pnpm dev:backend:vercel
|
|
||||||
|
|
||||||
|
🙂 大部分功能,均有通过 **单元测试** **集成测试** 保证质量。
|
||||||
|
|
||||||
|
| | 功能 | 描述 |
|
||||||
|
|-----|-------|---------------------------------|
|
||||||
|
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
|
||||||
|
| ⭐ | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
|
||||||
|
| ⭐ | 岗位管理 | 配置系统用户所属担任职务 |
|
||||||
|
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
|
||||||
|
| ⭐ | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
|
||||||
|
| | 参数管理 | 对系统动态配置常用参数 |
|
||||||
|
| | 通知公告 | 系统通知公告信息发布维护 |
|
||||||
|
| 🚀 | 操作日志 | 系统正常操作日志记录和查询;系统异常信息日志记录和查询 |
|
||||||
|
| | 登录日志 | 系统登录日志记录查询包含登录异常 |
|
||||||
|
| | 在线用户 | 当前系统中活跃用户状态监控 |
|
||||||
|
| | 系统接口 | 根据业务代码自动生成相关的api接口文档 |
|
||||||
|
| | 服务监控 | 监视当前系统CPU、内存、磁盘、堆栈等相关信息 |
|
||||||
|
| | 缓存监控 | 对系统的缓存信息查询,命令统计等 |
|
||||||
|
| | 连接池监视 | 监视当前系统数据库连接池状态,可进行分析SQL找出系统性能瓶颈 |
|
||||||
|
|
||||||
|
|
||||||
|
## 🐯 工程结构 🐯
|
||||||
|
|
||||||
|
```
|
||||||
|
agileboot
|
||||||
|
├── agileboot-admin -- 管理后台接口模块(供后台调用)
|
||||||
|
│
|
||||||
|
├── agileboot-api -- 开放接口模块(供客户端调用)
|
||||||
|
│
|
||||||
|
├── agileboot-common -- 精简基础工具模块
|
||||||
|
│
|
||||||
|
├── agileboot-infrastructure -- 基础设施模块(主要是配置和集成,不包含业务逻辑)
|
||||||
|
│
|
||||||
|
├── agileboot-domain -- 业务模块
|
||||||
|
├ ├── user -- 用户模块(举例)
|
||||||
|
├ ├── command -- 命令参数接收模型(命令)
|
||||||
|
├ ├── dto -- 返回数据类
|
||||||
|
├ ├── db -- DB操作类
|
||||||
|
├ ├── entity -- 实体类
|
||||||
|
├ ├── service -- DB Service
|
||||||
|
├ ├── mapper -- DB Dao
|
||||||
|
├ ├── model -- 领域模型类
|
||||||
|
├ ├── query -- 查询参数模型(查询)
|
||||||
|
│ ├────── UserApplicationService -- 应用服务(事务层,操作领域模型类完成业务逻辑)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 环境变量
|
### 代码流转
|
||||||
|
|
||||||
先复制:
|
请求分为两类:一类是查询,一类是操作(即对数据有进行更新)。
|
||||||
|
|
||||||
```bash
|
**查询**:Controller > xxxQuery > xxxApplicationService > xxxService(Db) > xxxMapper
|
||||||
cp .env.example .env
|
**操作**:Controller > xxxCommand > xxxApplicationService > xxxModel(处理逻辑) > save 或者 update (本项目直接采用JPA的方式进行插入已经更新数据)
|
||||||
|
|
||||||
|
这是借鉴CQRS的开发理念,将查询和操作分开处理。操作类的业务实现借鉴了DDD战术设计的理念,使用领域类,工厂类更面向对象的实现逻辑。
|
||||||
|
如果你不太适应这样的开发模式的话。可以在domain模块中按照你之前从Controller->Service->DAO的模式进行开发。it is up to you.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 二次开发指南
|
||||||
|
|
||||||
|
假设你要新增一个会员member业务,可以在以下三个模块新增对应的包来实现你的业务
|
||||||
|
```
|
||||||
|
agileboot
|
||||||
|
├── agileboot-admin --
|
||||||
|
│ ├── member -- 会员模块
|
||||||
|
│
|
||||||
|
├── agileboot-domain --
|
||||||
|
├ ├── member -- 会员模块(举例)
|
||||||
|
├ ├── command -- 命令参数接收模型(命令)
|
||||||
|
├ ├── dto -- 返回数据类
|
||||||
|
├ ├── db -- DB操作类
|
||||||
|
├ ├── entity -- 实体类
|
||||||
|
├ ├── service -- DB Service
|
||||||
|
├ ├── mapper -- DB Dao
|
||||||
|
├ ├── model -- 领域模型类
|
||||||
|
├ ├── query -- 查询参数模型(查询)
|
||||||
|
│ ├────── MemberApplicationService -- 应用服务(事务层,操作领域模型类完成业务逻辑)
|
||||||
|
└─
|
||||||
```
|
```
|
||||||
|
|
||||||
最少需要配置:
|
|
||||||
|
|
||||||
- `DATABASE_URL`
|
|
||||||
- `REDIS_URL`
|
|
||||||
- `JWT_SECRET`
|
|
||||||
- `UPLOAD_DIR`
|
|
||||||
|
|
||||||
## Vercel CLI 部署
|
---
|
||||||
|
|
||||||
后端作为独立的 Vercel Project 部署。先全局安装并登录 Vercel CLI:
|
## 🎅 技术文档 🎅
|
||||||
|
* [AgileBoot - 基于SpringBoot + Vue3的前后端快速开发脚手架](https://juejin.cn/post/7152871067151777829)
|
||||||
|
* [AgileBoot - 手把手一步一步带你Run起全栈项目(SpringBoot+Vue3)](https://juejin.cn/post/7153812187834744845)
|
||||||
|
* [AgileBoot - 项目内统一的错误码设计](https://juejin.cn/post/7156062116712022023)
|
||||||
|
* [AgileBoot - 如何集成内置数据库H2和内置Redis](https://juejin.cn/post/7158793441198112781)
|
||||||
|
* [AgileBoot - Mybatis Plus 框架项目落地实践总结](https://juejin.cn/post/7202573260659195963)
|
||||||
|
* [AgileBoot - SpringBoot项目多层级多环境yml设计](https://juejin.cn/post/7205171975647215676)
|
||||||
|
* [AgileBoot - 项目中多级缓存设计实践总结](https://juejin.cn/post/7208112485764857914)
|
||||||
|
* 持续输出中
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add -g vercel
|
|
||||||
vercel login
|
|
||||||
```
|
|
||||||
|
|
||||||
进入后端目录并关联项目:
|
|
||||||
|
|
||||||
```bash
|
## 🌻 注意事项 🌻
|
||||||
cd backend
|
- IDEA会自动将.properties文件的编码设置为ISO-8859-1,请在Settings > Editor > File Encodings > Properties Files > 设置为UTF-8
|
||||||
vercel link
|
- 请导入统一的代码格式化模板(Google): Settings > Editor > Code Style > Java > 设置按钮 > import schema > 选择项目根目录下的GoogleStyle.xml文件
|
||||||
```
|
- 如需要生成新的表,请使用CodeGenerator类进行生成。
|
||||||
|
- 填入数据库地址,账号密码,库名。然后填入所需的表名执行代码即可。(大概看一下代码就知道怎么填啦)
|
||||||
|
- 生成的类在infrastructure模块下的target/classes目录下
|
||||||
|
- 不同的数据库keywordsHandler方法请填入对应不同数据库handler。(搜索keywordsHandler关键字)
|
||||||
|
- 项目基础环境搭建,请参考docker目录下的指南搭建。保姆级启动说明:
|
||||||
|
- [AgileBoot - 手把手一步一步带你Run起全栈项目(SpringBoot+Vue3)](https://juejin.cn/post/7153812187834744845)
|
||||||
|
- 注意:管理后台的后端启动类是AgileBoot**Admin**Application
|
||||||
|
- Swagger的API地址为 http://localhost:8080/v3/api-docs
|
||||||
|
|
||||||
配置生产环境变量:
|
## 🎬 AgileBoot全栈交流群 🎬
|
||||||
|
|
||||||
```bash
|
QQ群: [](https://qm.qq.com/cgi-bin/qm/qr?k=TR5guoXS0HssErVWefmdFRirJvfpEvp1&jump_from=webapi&authKey=VkWMmVhp/pNdWuRD8sqgM+Sv2+Vy2qCJQSeLmeXlLtfER2RJBi6zL56PdcRlCmTs) 点击按钮入群。
|
||||||
vercel env add DATABASE_URL production
|
|
||||||
vercel env add REDIS_URL production
|
|
||||||
vercel env add JWT_SECRET production
|
|
||||||
vercel env add PUBLIC_FILE_BASE_URL production
|
|
||||||
```
|
|
||||||
|
|
||||||
连接串示例:
|
|
||||||
|
|
||||||
```env
|
如果觉得该项目对您有帮助,可以小额捐赠支持本项目演示网站服务器等费用~
|
||||||
DATABASE_URL=mysql://user:password@host:3306/collab_ledger
|
|
||||||
REDIS_URL=redis://:password@host:6379
|
|
||||||
```
|
|
||||||
|
|
||||||
如果 Redis 服务要求 TLS,使用 `rediss://`。Vercel Functions 不能连接本机 `127.0.0.1`、`localhost` 或 Docker 内网地址。
|
|
||||||
|
|
||||||
部署:
|
<img alt="logo" height="200" src="https://oscimg.oschina.net/oscnet/up-28b63fdd7b3ce003bd30c25883f2276212b.png">
|
||||||
|
|
||||||
```bash
|
## 💕 特别鸣谢
|
||||||
vercel deploy --prod
|
|
||||||
```
|
|
||||||
|
|
||||||
## 当前状态
|
|
||||||
|
|
||||||
已包含:
|
- <a href="https://github.com/FerryboatSeranade" target="_blank">@pokr</a> 感谢提供ChatGpt账号助力本项目开发
|
||||||
|
|
||||||
- Hono + Vercel 官方入口
|
## 💒 相关框架
|
||||||
- 统一响应/错误协议
|
- 基于node.js开发的后端 <a href="https://gitee.com/TsMask/mask_api_midwayjs" target="_blank">Midwayjs</a>
|
||||||
- JWT + Redis 会话
|
|
||||||
- 登录/注册/验证码/当前用户
|
|
||||||
- 合作记录 CRUD
|
|
||||||
- 用户/角色/菜单/配置/公告/日志/监控接口骨架
|
|
||||||
|
|
||||||
仍需继续完善:
|
|
||||||
|
|
||||||
- 动态路由 `/getRouters`
|
|
||||||
- 更完整的权限与数据范围控制
|
|
||||||
- Excel 导出
|
|
||||||
- 在线用户、Redis 监控细节
|
|
||||||
- 上传文件持久化改造为对象存储或 Vercel Blob
|
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>agileboot</artifactId>
|
||||||
|
<groupId>com.agileboot</groupId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>agileboot-admin</artifactId>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
web服务入口
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- 业务领域 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.agileboot</groupId>
|
||||||
|
<artifactId>agileboot-domain</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.junit.vintage</groupId>
|
||||||
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>${maven.surefire.plugin.version}</version>
|
||||||
|
<!-- 想跑test的话 设置成false -->
|
||||||
|
<configuration>
|
||||||
|
<skipTests>false</skipTests>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package com.agileboot.admin;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动程序
|
||||||
|
* 定制banner.txt的网站
|
||||||
|
* http://patorjk.com/software/taag
|
||||||
|
* http://www.network-science.de/ascii/
|
||||||
|
* http://www.degraeve.com/img2txt.php
|
||||||
|
* http://life.chacuo.net/convertfont2char
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
|
||||||
|
@ComponentScan(basePackages = "com.agileboot.*")
|
||||||
|
public class AgileBootAdminApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AgileBootAdminApplication.class, args);
|
||||||
|
String successMsg = " ____ _ _ __ _ _ \n"
|
||||||
|
+ " / ___| | |_ __ _ _ __ | |_ _ _ _ __ ___ _ _ ___ ___ ___ ___ ___ / _| _ _ | || |\n"
|
||||||
|
+ " \\___ \\ | __|/ _` || '__|| __| | | | || '_ \\ / __|| | | | / __|/ __|/ _ \\/ __|/ __|| |_ | | | || || |\n"
|
||||||
|
+ " ___) || |_| (_| || | | |_ | |_| || |_) | \\__ \\| |_| || (__| (__| __/\\__ \\\\__ \\| _|| |_| || ||_|\n"
|
||||||
|
+ " |____/ \\__|\\__,_||_| \\__| \\__,_|| .__/ |___/ \\__,_| \\___|\\___|\\___||___/|___/|_| \\__,_||_|(_)\n"
|
||||||
|
+ " |_| ";
|
||||||
|
|
||||||
|
System.out.println(successMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
+84
@@ -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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+90
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+56
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+105
@@ -0,0 +1,105 @@
|
|||||||
|
package com.agileboot.admin.controller.collaboration;
|
||||||
|
|
||||||
|
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.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.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 codex
|
||||||
|
*/
|
||||||
|
@Tag(name = "合作记录API", description = "合作记录相关的增删查改和统计")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/collaboration/record")
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CollaborationRecordController extends BaseController {
|
||||||
|
|
||||||
|
private final CollaborationRecordApplicationService recordApplicationService;
|
||||||
|
|
||||||
|
@Operation(summary = "合作记录列表")
|
||||||
|
@PreAuthorize("@permission.has('collaboration:record:list')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ResponseDTO<PageDTO<CollaborationRecordDTO>> list(CollaborationRecordQuery query) {
|
||||||
|
return ResponseDTO.ok(recordApplicationService.getRecordList(query));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "合作记录详情")
|
||||||
|
@PreAuthorize("@permission.has('collaboration:record:query')")
|
||||||
|
@GetMapping("/{recordId}")
|
||||||
|
public ResponseDTO<CollaborationRecordDetailDTO> getInfo(@PathVariable @Positive Long recordId) {
|
||||||
|
return ResponseDTO.ok(recordApplicationService.getRecordInfo(recordId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "合作记录选项")
|
||||||
|
@PreAuthorize("@permission.has('collaboration:record:list')")
|
||||||
|
@GetMapping("/options")
|
||||||
|
public ResponseDTO<List<CollaborationOptionDTO>> options() {
|
||||||
|
return ResponseDTO.ok(recordApplicationService.getOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "合作记录月度统计")
|
||||||
|
@PreAuthorize("@permission.has('collaboration:record:statistics')")
|
||||||
|
@GetMapping("/monthly-statistics")
|
||||||
|
public ResponseDTO<List<CollaborationMonthlyStatisticsDTO>> monthlyStatistics(
|
||||||
|
@RequestParam Integer year, @RequestParam(required = false) Long creatorId) {
|
||||||
|
return ResponseDTO.ok(recordApplicationService.getMonthlyStatistics(year, creatorId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "新增合作记录")
|
||||||
|
@PreAuthorize("@permission.has('collaboration:record:add')")
|
||||||
|
@AccessLog(title = "合作记录", businessType = BusinessTypeEnum.ADD)
|
||||||
|
@PostMapping
|
||||||
|
public ResponseDTO<Void> add(@Valid @RequestBody AddCollaborationRecordCommand command) {
|
||||||
|
recordApplicationService.addRecord(command);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "修改合作记录")
|
||||||
|
@PreAuthorize("@permission.has('collaboration:record:edit')")
|
||||||
|
@AccessLog(title = "合作记录", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@PutMapping
|
||||||
|
public ResponseDTO<Void> edit(@Valid @RequestBody UpdateCollaborationRecordCommand command) {
|
||||||
|
recordApplicationService.updateRecord(command);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除合作记录")
|
||||||
|
@PreAuthorize("@permission.has('collaboration:record:remove')")
|
||||||
|
@AccessLog(title = "合作记录", businessType = BusinessTypeEnum.DELETE)
|
||||||
|
@DeleteMapping
|
||||||
|
public ResponseDTO<Void> remove(@RequestParam @NotNull @NotEmpty List<Long> ids) {
|
||||||
|
recordApplicationService.deleteRecord(new BulkOperationCommand<>(ids));
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
package com.agileboot.admin.controller.common;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.io.file.FileNameUtil;
|
||||||
|
import com.agileboot.common.constant.Constants.UploadSubDir;
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode.Business;
|
||||||
|
import com.agileboot.common.utils.ServletHolderUtil;
|
||||||
|
import com.agileboot.common.utils.file.FileUploadUtils;
|
||||||
|
import com.agileboot.common.utils.jackson.JacksonUtil;
|
||||||
|
import com.agileboot.domain.common.dto.UploadDTO;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用请求处理
|
||||||
|
* TODO 需要重构
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Tag(name = "上传API", description = "上传相关接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/file")
|
||||||
|
@Slf4j
|
||||||
|
public class FileController {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用下载请求
|
||||||
|
* download接口 其实不是很有必要
|
||||||
|
* @param fileName 文件名称
|
||||||
|
*/
|
||||||
|
@Operation(summary = "下载文件")
|
||||||
|
@GetMapping("/download")
|
||||||
|
public ResponseEntity<byte[]> fileDownload(String fileName, HttpServletResponse response) {
|
||||||
|
try {
|
||||||
|
if (!FileUploadUtils.isAllowDownload(fileName)) {
|
||||||
|
// 返回类型是ResponseEntity 不能捕获异常, 需要手动将错误填到 ResponseEntity
|
||||||
|
ResponseDTO<Object> fail = ResponseDTO.fail(
|
||||||
|
new ApiException(Business.COMMON_FILE_NOT_ALLOWED_TO_DOWNLOAD, fileName));
|
||||||
|
return new ResponseEntity<>(JacksonUtil.to(fail).getBytes(), null, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
String filePath = FileUploadUtils.getFileAbsolutePath(UploadSubDir.DOWNLOAD_PATH, fileName);
|
||||||
|
|
||||||
|
HttpHeaders downloadHeader = FileUploadUtils.getDownloadHeader(fileName);
|
||||||
|
|
||||||
|
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||||
|
return new ResponseEntity<>(FileUtil.readBytes(filePath), downloadHeader, HttpStatus.OK);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("下载文件失败", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用上传请求(单个)
|
||||||
|
*/
|
||||||
|
@Operation(summary = "单个上传文件")
|
||||||
|
@PostMapping("/upload")
|
||||||
|
public ResponseDTO<UploadDTO> uploadFile(@RequestParam("file") MultipartFile file) {
|
||||||
|
if (file == null) {
|
||||||
|
throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传并返回新文件名称
|
||||||
|
String fileName = FileUploadUtils.upload(UploadSubDir.UPLOAD_PATH, file);
|
||||||
|
|
||||||
|
String url = ServletHolderUtil.getContextUrl() + fileName;
|
||||||
|
|
||||||
|
UploadDTO uploadDTO = UploadDTO.builder()
|
||||||
|
// 全路径
|
||||||
|
.url(url)
|
||||||
|
// 相对路径
|
||||||
|
.fileName(fileName)
|
||||||
|
// 新生成的文件名
|
||||||
|
.newFileName(FileNameUtil.getName(fileName))
|
||||||
|
// 原始的文件名
|
||||||
|
.originalFilename(file.getOriginalFilename()).build();
|
||||||
|
|
||||||
|
return ResponseDTO.ok(uploadDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用上传请求(多个)
|
||||||
|
*/
|
||||||
|
@Operation(summary = "多个上传文件")
|
||||||
|
@PostMapping("/uploads")
|
||||||
|
public ResponseDTO<List<UploadDTO>> uploadFiles(@RequestParam("files") List<MultipartFile> files) {
|
||||||
|
if (CollUtil.isEmpty(files)) {
|
||||||
|
throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<UploadDTO> uploads = new ArrayList<>();
|
||||||
|
|
||||||
|
for (MultipartFile file : files) {
|
||||||
|
if (file != null) {
|
||||||
|
// 上传并返回新文件名称
|
||||||
|
String fileName = FileUploadUtils.upload(UploadSubDir.UPLOAD_PATH, file);
|
||||||
|
String url = ServletHolderUtil.getContextUrl() + fileName;
|
||||||
|
UploadDTO uploadDTO = UploadDTO.builder()
|
||||||
|
.url(url)
|
||||||
|
.fileName(fileName)
|
||||||
|
.newFileName(FileNameUtil.getName(fileName))
|
||||||
|
.originalFilename(file.getOriginalFilename()).build();
|
||||||
|
|
||||||
|
uploads.add(uploadDTO);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResponseDTO.ok(uploads);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+152
@@ -0,0 +1,152 @@
|
|||||||
|
package com.agileboot.admin.controller.common;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.agileboot.common.config.AgileBootConfig;
|
||||||
|
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.menu.MenuApplicationService;
|
||||||
|
import com.agileboot.domain.system.menu.dto.RouterDTO;
|
||||||
|
import com.agileboot.domain.system.user.UserApplicationService;
|
||||||
|
import com.agileboot.domain.system.user.command.RegisterUserCommand;
|
||||||
|
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit;
|
||||||
|
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.CacheType;
|
||||||
|
import com.agileboot.infrastructure.annotations.ratelimit.RateLimit.LimitType;
|
||||||
|
import com.agileboot.infrastructure.user.AuthenticationUtils;
|
||||||
|
import com.agileboot.admin.customize.service.login.dto.CaptchaDTO;
|
||||||
|
import com.agileboot.admin.customize.service.login.dto.ConfigDTO;
|
||||||
|
import com.agileboot.admin.customize.service.login.command.LoginCommand;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.infrastructure.annotations.ratelimit.RateLimitKey;
|
||||||
|
import com.agileboot.admin.customize.service.login.LoginService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import java.util.List;
|
||||||
|
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.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首页
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Tag(name = "登录API", description = "登录相关接口")
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LoginController {
|
||||||
|
|
||||||
|
private final LoginService loginService;
|
||||||
|
|
||||||
|
private final MenuApplicationService menuApplicationService;
|
||||||
|
|
||||||
|
private final UserApplicationService userApplicationService;
|
||||||
|
|
||||||
|
private final AgileBootConfig agileBootConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问首页,提示语
|
||||||
|
*/
|
||||||
|
@Operation(summary = "首页")
|
||||||
|
@GetMapping("/")
|
||||||
|
@RateLimit(key = RateLimitKey.TEST_KEY, time = 10, maxCount = 5, cacheType = CacheType.Map,
|
||||||
|
limitType = LimitType.GLOBAL)
|
||||||
|
public String index() {
|
||||||
|
return StrUtil.format("欢迎使用{}后台管理框架,当前版本:v{},请通过前端地址访问。",
|
||||||
|
agileBootConfig.getName(), agileBootConfig.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取系统的内置配置
|
||||||
|
*
|
||||||
|
* @return 配置信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/getConfig")
|
||||||
|
public ResponseDTO<ConfigDTO> getConfig() {
|
||||||
|
ConfigDTO configDTO = loginService.getConfig();
|
||||||
|
return ResponseDTO.ok(configDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成验证码
|
||||||
|
*/
|
||||||
|
@Operation(summary = "验证码")
|
||||||
|
@RateLimit(key = RateLimitKey.LOGIN_CAPTCHA_KEY, time = 10, maxCount = 10, cacheType = CacheType.REDIS,
|
||||||
|
limitType = LimitType.IP)
|
||||||
|
@GetMapping("/captchaImage")
|
||||||
|
public ResponseDTO<CaptchaDTO> getCaptchaImg() {
|
||||||
|
CaptchaDTO captchaImg = loginService.generateCaptchaImg();
|
||||||
|
return ResponseDTO.ok(captchaImg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录方法
|
||||||
|
*
|
||||||
|
* @param loginCommand 登录信息
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
@Operation(summary = "登录")
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseDTO<TokenDTO> login(@RequestBody LoginCommand loginCommand) {
|
||||||
|
// 生成令牌
|
||||||
|
String token = loginService.login(loginCommand);
|
||||||
|
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
CurrentLoginUserDTO currentUserDTO = userApplicationService.getLoginUserInfo(loginUser);
|
||||||
|
|
||||||
|
return ResponseDTO.ok(new TokenDTO(token, currentUserDTO));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息
|
||||||
|
*
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取当前登录用户信息")
|
||||||
|
@GetMapping("/getLoginUserInfo")
|
||||||
|
public ResponseDTO<CurrentLoginUserDTO> getLoginUserInfo() {
|
||||||
|
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
|
||||||
|
CurrentLoginUserDTO currentUserDTO = userApplicationService.getLoginUserInfo(loginUser);
|
||||||
|
|
||||||
|
return ResponseDTO.ok(currentUserDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取路由信息
|
||||||
|
* TODO 如果要在前端开启路由缓存的话 需要在ServerConfig.json 中 设置CachingAsyncRoutes=true 避免一直重复请求路由接口
|
||||||
|
* @return 路由信息
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取用户对应的菜单路由", description = "用于动态生成路由")
|
||||||
|
@GetMapping("/getRouters")
|
||||||
|
public ResponseDTO<List<RouterDTO>> getRouters() {
|
||||||
|
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
List<RouterDTO> routerTree = menuApplicationService.getRouterTree(loginUser);
|
||||||
|
return ResponseDTO.ok(routerTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@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());
|
||||||
|
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+82
@@ -0,0 +1,82 @@
|
|||||||
|
package com.agileboot.admin.controller.system;
|
||||||
|
|
||||||
|
import com.agileboot.common.core.base.BaseController;
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.common.core.page.PageDTO;
|
||||||
|
import com.agileboot.domain.common.cache.CacheCenter;
|
||||||
|
import com.agileboot.domain.system.monitor.MonitorApplicationService;
|
||||||
|
import com.agileboot.domain.system.monitor.dto.OnlineUserDTO;
|
||||||
|
import com.agileboot.domain.system.monitor.dto.RedisCacheInfoDTO;
|
||||||
|
import com.agileboot.domain.system.monitor.dto.ServerInfo;
|
||||||
|
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 lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓存监控
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Tag(name = "监控API", description = "监控相关信息")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/monitor")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MonitorController extends BaseController {
|
||||||
|
|
||||||
|
private final MonitorApplicationService monitorApplicationService;
|
||||||
|
|
||||||
|
@Operation(summary = "Redis信息")
|
||||||
|
@PreAuthorize("@permission.has('monitor:cache:list')")
|
||||||
|
@GetMapping("/cacheInfo")
|
||||||
|
public ResponseDTO<RedisCacheInfoDTO> getRedisCacheInfo() {
|
||||||
|
RedisCacheInfoDTO redisCacheInfo = monitorApplicationService.getRedisCacheInfo();
|
||||||
|
return ResponseDTO.ok(redisCacheInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Operation(summary = "服务器信息")
|
||||||
|
@PreAuthorize("@permission.has('monitor:server:list')")
|
||||||
|
@GetMapping("/serverInfo")
|
||||||
|
public ResponseDTO<ServerInfo> getServerInfo() {
|
||||||
|
ServerInfo serverInfo = monitorApplicationService.getServerInfo();
|
||||||
|
return ResponseDTO.ok(serverInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取在线用户列表
|
||||||
|
*
|
||||||
|
* @param ipAddress ip地址
|
||||||
|
* @param username 用户名
|
||||||
|
* @return 分页处理后的在线用户信息
|
||||||
|
*/
|
||||||
|
@Operation(summary = "在线用户列表")
|
||||||
|
@PreAuthorize("@permission.has('monitor:online:list')")
|
||||||
|
@GetMapping("/onlineUsers")
|
||||||
|
public ResponseDTO<PageDTO<OnlineUserDTO>> onlineUsers(String ipAddress, String username) {
|
||||||
|
List<OnlineUserDTO> onlineUserList = monitorApplicationService.getOnlineUserList(username, ipAddress);
|
||||||
|
return ResponseDTO.ok(new PageDTO<>(onlineUserList));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强退用户
|
||||||
|
*/
|
||||||
|
@Operation(summary = "强退用户")
|
||||||
|
@PreAuthorize("@permission.has('monitor:online:forceLogout')")
|
||||||
|
@AccessLog(title = "在线用户", businessType = BusinessTypeEnum.FORCE_LOGOUT)
|
||||||
|
@DeleteMapping("/onlineUser/{tokenId}")
|
||||||
|
public ResponseDTO<Void> logoutOnlineUser(@PathVariable String tokenId) {
|
||||||
|
CacheCenter.loginUserCache.delete(tokenId);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+88
@@ -0,0 +1,88 @@
|
|||||||
|
package com.agileboot.admin.controller.system;
|
||||||
|
|
||||||
|
import com.agileboot.common.core.base.BaseController;
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.common.core.page.PageDTO;
|
||||||
|
import com.agileboot.domain.common.cache.CacheCenter;
|
||||||
|
import com.agileboot.domain.system.config.ConfigApplicationService;
|
||||||
|
import com.agileboot.domain.system.config.command.ConfigUpdateCommand;
|
||||||
|
import com.agileboot.domain.system.config.dto.ConfigDTO;
|
||||||
|
import com.agileboot.domain.system.config.query.ConfigQuery;
|
||||||
|
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 javax.validation.constraints.NotNull;
|
||||||
|
import javax.validation.constraints.Positive;
|
||||||
|
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.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 SysConfigController extends BaseController {
|
||||||
|
|
||||||
|
private final ConfigApplicationService configApplicationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取参数配置列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "参数列表", description = "分页获取配置参数列表")
|
||||||
|
@PreAuthorize("@permission.has('system:config:list')")
|
||||||
|
@GetMapping("/configs")
|
||||||
|
public ResponseDTO<PageDTO<ConfigDTO>> list(ConfigQuery query) {
|
||||||
|
PageDTO<ConfigDTO> page = configApplicationService.getConfigList(query);
|
||||||
|
return ResponseDTO.ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据参数编号获取详细信息
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@permission.has('system:config:query')")
|
||||||
|
@GetMapping(value = "/config/{configId}")
|
||||||
|
@Operation(summary = "配置信息", description = "配置的详细信息")
|
||||||
|
public ResponseDTO<ConfigDTO> getInfo(@NotNull @Positive @PathVariable Long configId) {
|
||||||
|
ConfigDTO config = configApplicationService.getConfigInfo(configId);
|
||||||
|
return ResponseDTO.ok(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改参数配置
|
||||||
|
*/
|
||||||
|
@PreAuthorize("@permission.has('system:config:edit')")
|
||||||
|
@AccessLog(title = "参数管理", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@Operation(summary = "配置修改", description = "配置修改")
|
||||||
|
@PutMapping(value = "/config/{configId}")
|
||||||
|
public ResponseDTO<Void> edit(@NotNull @Positive @PathVariable Long configId, @RequestBody ConfigUpdateCommand config) {
|
||||||
|
config.setConfigId(configId);
|
||||||
|
configApplicationService.updateConfig(config);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新参数缓存
|
||||||
|
*/
|
||||||
|
@Operation(summary = "刷新配置缓存")
|
||||||
|
@PreAuthorize("@permission.has('system:config:remove')")
|
||||||
|
@AccessLog(title = "参数管理", businessType = BusinessTypeEnum.CLEAN)
|
||||||
|
@DeleteMapping("/configs/cache")
|
||||||
|
public ResponseDTO<Void> refreshCache() {
|
||||||
|
CacheCenter.configCache.invalidateAll();
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
package com.agileboot.admin.controller.system;
|
||||||
|
|
||||||
|
|
||||||
|
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.utils.poi.CustomExcelUtil;
|
||||||
|
import com.agileboot.domain.common.command.BulkOperationCommand;
|
||||||
|
import com.agileboot.domain.system.log.LogApplicationService;
|
||||||
|
import com.agileboot.domain.system.log.dto.LoginLogDTO;
|
||||||
|
import com.agileboot.domain.system.log.query.LoginLogQuery;
|
||||||
|
import com.agileboot.domain.system.log.dto.OperationLogDTO;
|
||||||
|
import com.agileboot.domain.system.log.query.OperationLogQuery;
|
||||||
|
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.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.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统访问记录
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Tag(name = "日志API", description = "日志相关API")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/logs")
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysLogsController extends BaseController {
|
||||||
|
|
||||||
|
private final LogApplicationService logApplicationService;
|
||||||
|
|
||||||
|
@Operation(summary = "登录日志列表")
|
||||||
|
@PreAuthorize("@permission.has('monitor:logininfor:list')")
|
||||||
|
@GetMapping("/loginLogs")
|
||||||
|
public ResponseDTO<PageDTO<LoginLogDTO>> loginInfoList(LoginLogQuery query) {
|
||||||
|
PageDTO<LoginLogDTO> pageDTO = logApplicationService.getLoginInfoList(query);
|
||||||
|
return ResponseDTO.ok(pageDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "登录日志导出", description = "将登录日志导出到excel")
|
||||||
|
@AccessLog(title = "登录日志", businessType = BusinessTypeEnum.EXPORT)
|
||||||
|
@PreAuthorize("@permission.has('monitor:logininfor:export')")
|
||||||
|
@GetMapping("/loginLogs/excel")
|
||||||
|
public void loginInfosExcel(HttpServletResponse response, LoginLogQuery query) {
|
||||||
|
PageDTO<LoginLogDTO> pageDTO = logApplicationService.getLoginInfoList(query);
|
||||||
|
CustomExcelUtil.writeToResponse(pageDTO.getRows(), LoginLogDTO.class, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除登录日志")
|
||||||
|
@PreAuthorize("@permission.has('monitor:logininfor:remove')")
|
||||||
|
@AccessLog(title = "登录日志", businessType = BusinessTypeEnum.DELETE)
|
||||||
|
@DeleteMapping("/loginLogs")
|
||||||
|
public ResponseDTO<Void> removeLoginInfos(@RequestParam @NotNull @NotEmpty List<Long> ids) {
|
||||||
|
logApplicationService.deleteLoginInfo(new BulkOperationCommand<>(ids));
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "操作日志列表")
|
||||||
|
@PreAuthorize("@permission.has('monitor:operlog:list')")
|
||||||
|
@GetMapping("/operationLogs")
|
||||||
|
public ResponseDTO<PageDTO<OperationLogDTO>> operationLogs(OperationLogQuery query) {
|
||||||
|
PageDTO<OperationLogDTO> pageDTO = logApplicationService.getOperationLogList(query);
|
||||||
|
return ResponseDTO.ok(pageDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @GetMapping("/download")
|
||||||
|
// public ResponseEntity<InputStreamResource> downloadFile() throws IOException {
|
||||||
|
// // 从文件系统或其他位置获取文件输入流
|
||||||
|
// File file = new File("path/to/file");
|
||||||
|
// InputStream inputStream = new FileInputStream(file);
|
||||||
|
// CustomExcelUtil.wri
|
||||||
|
//
|
||||||
|
// // 创建一个 InputStreamResource 对象,将文件输入流包装在其中
|
||||||
|
// InputStreamResource resource = new InputStreamResource(inputStream);
|
||||||
|
//
|
||||||
|
// // 返回 ResponseEntity 对象,其中包含 InputStreamResource 对象和文件名
|
||||||
|
// return ResponseEntity.ok()
|
||||||
|
// .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName())
|
||||||
|
// .contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
|
// .contentLength(file.length())
|
||||||
|
// .body(resource);
|
||||||
|
// }
|
||||||
|
/**
|
||||||
|
* 可否改成以上的形式 TODO
|
||||||
|
* @param response
|
||||||
|
* @param query
|
||||||
|
*/
|
||||||
|
@Operation(summary = "操作日志导出")
|
||||||
|
@AccessLog(title = "操作日志", businessType = BusinessTypeEnum.EXPORT)
|
||||||
|
@PreAuthorize("@permission.has('monitor:operlog:export')")
|
||||||
|
@GetMapping("/operationLogs/excel")
|
||||||
|
public void operationLogsExcel(HttpServletResponse response, OperationLogQuery query) {
|
||||||
|
PageDTO<OperationLogDTO> pageDTO = logApplicationService.getOperationLogList(query);
|
||||||
|
CustomExcelUtil.writeToResponse(pageDTO.getRows(), OperationLogDTO.class, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除操作日志")
|
||||||
|
@AccessLog(title = "操作日志", businessType = BusinessTypeEnum.DELETE)
|
||||||
|
@PreAuthorize("@permission.has('monitor:operlog:remove')")
|
||||||
|
@DeleteMapping("/operationLogs")
|
||||||
|
public ResponseDTO<Void> removeOperationLogs(@RequestParam List<Long> operationIds) {
|
||||||
|
logApplicationService.deleteOperationLog(new BulkOperationCommand<>(operationIds));
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
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.menu.MenuApplicationService;
|
||||||
|
import com.agileboot.domain.system.menu.command.AddMenuCommand;
|
||||||
|
import com.agileboot.domain.system.menu.command.UpdateMenuCommand;
|
||||||
|
import com.agileboot.domain.system.menu.dto.MenuDTO;
|
||||||
|
import com.agileboot.domain.system.menu.dto.MenuDetailDTO;
|
||||||
|
import com.agileboot.domain.system.menu.query.MenuQuery;
|
||||||
|
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
|
||||||
|
import com.agileboot.infrastructure.user.AuthenticationUtils;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
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 javax.validation.constraints.PositiveOrZero;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
@Tag(name = "菜单API", description = "菜单相关的增删查改")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/menus")
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysMenuController extends BaseController {
|
||||||
|
|
||||||
|
private final MenuApplicationService menuApplicationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取菜单列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "菜单列表")
|
||||||
|
@PreAuthorize("@permission.has('system:menu:list')")
|
||||||
|
@GetMapping
|
||||||
|
public ResponseDTO<List<MenuDTO>> menuList(MenuQuery menuQuery) {
|
||||||
|
List<MenuDTO> menuList = menuApplicationService.getMenuList(menuQuery);
|
||||||
|
return ResponseDTO.ok(menuList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据菜单编号获取详细信息
|
||||||
|
*/
|
||||||
|
@Operation(summary = "菜单详情")
|
||||||
|
@PreAuthorize("@permission.has('system:menu:query')")
|
||||||
|
@GetMapping(value = "/{menuId}")
|
||||||
|
public ResponseDTO<MenuDetailDTO> menuInfo(@PathVariable @NotNull @PositiveOrZero Long menuId) {
|
||||||
|
MenuDetailDTO menu = menuApplicationService.getMenuInfo(menuId);
|
||||||
|
return ResponseDTO.ok(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取菜单下拉树列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "菜单列表(树级)", description = "菜单树级下拉框")
|
||||||
|
@GetMapping("/dropdown")
|
||||||
|
public ResponseDTO<List<Tree<Long>>> dropdownList() {
|
||||||
|
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
List<Tree<Long>> dropdownList = menuApplicationService.getDropdownList(loginUser);
|
||||||
|
return ResponseDTO.ok(dropdownList);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增菜单
|
||||||
|
* 需支持一级菜单以及 多级菜单 子菜单为一个 或者 多个的情况
|
||||||
|
* 隐藏菜单不显示 以及rank排序
|
||||||
|
* 内链 和 外链
|
||||||
|
*/
|
||||||
|
@Operation(summary = "添加菜单")
|
||||||
|
@PreAuthorize("@permission.has('system:menu:add')")
|
||||||
|
@AccessLog(title = "菜单管理", businessType = BusinessTypeEnum.ADD)
|
||||||
|
@PostMapping
|
||||||
|
public ResponseDTO<Void> add(@RequestBody AddMenuCommand addCommand) {
|
||||||
|
menuApplicationService.addMenu(addCommand);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改菜单
|
||||||
|
*/
|
||||||
|
@Operation(summary = "编辑菜单")
|
||||||
|
@PreAuthorize("@permission.has('system:menu:edit')")
|
||||||
|
@AccessLog(title = "菜单管理", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@PutMapping("/{menuId}")
|
||||||
|
public ResponseDTO<Void> edit(@PathVariable("menuId") Long menuId, @RequestBody UpdateMenuCommand updateCommand) {
|
||||||
|
updateCommand.setMenuId(menuId);
|
||||||
|
menuApplicationService.updateMenu(updateCommand);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除菜单
|
||||||
|
*/
|
||||||
|
@Operation(summary = "删除菜单")
|
||||||
|
@PreAuthorize("@permission.has('system:menu:remove')")
|
||||||
|
@AccessLog(title = "菜单管理", businessType = BusinessTypeEnum.DELETE)
|
||||||
|
@DeleteMapping("/{menuId}")
|
||||||
|
public ResponseDTO<Void> remove(@PathVariable("menuId") Long menuId) {
|
||||||
|
menuApplicationService.remove(menuId);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+122
@@ -0,0 +1,122 @@
|
|||||||
|
package com.agileboot.admin.controller.system;
|
||||||
|
|
||||||
|
import com.agileboot.common.core.base.BaseController;
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.common.core.page.PageDTO;
|
||||||
|
import com.agileboot.domain.common.command.BulkOperationCommand;
|
||||||
|
import com.agileboot.domain.system.notice.NoticeApplicationService;
|
||||||
|
import com.agileboot.domain.system.notice.command.NoticeAddCommand;
|
||||||
|
import com.agileboot.domain.system.notice.command.NoticeUpdateCommand;
|
||||||
|
import com.agileboot.domain.system.notice.dto.NoticeDTO;
|
||||||
|
import com.agileboot.domain.system.notice.query.NoticeQuery;
|
||||||
|
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
|
||||||
|
import com.agileboot.infrastructure.annotations.unrepeatable.Unrepeatable;
|
||||||
|
import com.agileboot.infrastructure.annotations.unrepeatable.Unrepeatable.CheckType;
|
||||||
|
import com.agileboot.common.enums.common.BusinessTypeEnum;
|
||||||
|
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||||
|
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 javax.validation.constraints.Positive;
|
||||||
|
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 valarchie
|
||||||
|
*/
|
||||||
|
@Tag(name = "公告API", description = "公告相关的增删查改")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/notices")
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysNoticeController extends BaseController {
|
||||||
|
|
||||||
|
private final NoticeApplicationService noticeApplicationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取通知公告列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "公告列表")
|
||||||
|
@PreAuthorize("@permission.has('system:notice:list')")
|
||||||
|
@GetMapping
|
||||||
|
public ResponseDTO<PageDTO<NoticeDTO>> list(NoticeQuery query) {
|
||||||
|
PageDTO<NoticeDTO> pageDTO = noticeApplicationService.getNoticeList(query);
|
||||||
|
return ResponseDTO.ok(pageDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取通知公告列表
|
||||||
|
* 从从库获取数据 例子 仅供参考
|
||||||
|
*/
|
||||||
|
@Operation(summary = "公告列表(从数据库从库获取)", description = "演示主从库的例子")
|
||||||
|
@DS("slave")
|
||||||
|
@PreAuthorize("@permission.has('system:notice:list')")
|
||||||
|
@GetMapping("/database/slave")
|
||||||
|
public ResponseDTO<PageDTO<NoticeDTO>> listFromSlave(NoticeQuery query) {
|
||||||
|
PageDTO<NoticeDTO> pageDTO = noticeApplicationService.getNoticeList(query);
|
||||||
|
return ResponseDTO.ok(pageDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据通知公告编号获取详细信息
|
||||||
|
*/
|
||||||
|
@Operation(summary = "公告详情")
|
||||||
|
@PreAuthorize("@permission.has('system:notice:query')")
|
||||||
|
@GetMapping(value = "/{noticeId}")
|
||||||
|
public ResponseDTO<NoticeDTO> getInfo(@PathVariable @NotNull @Positive Long noticeId) {
|
||||||
|
return ResponseDTO.ok(noticeApplicationService.getNoticeInfo(noticeId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增通知公告
|
||||||
|
*/
|
||||||
|
@Operation(summary = "添加公告")
|
||||||
|
@Unrepeatable(interval = 60, checkType = CheckType.SYSTEM_USER)
|
||||||
|
@PreAuthorize("@permission.has('system:notice:add')")
|
||||||
|
@AccessLog(title = "通知公告", businessType = BusinessTypeEnum.ADD)
|
||||||
|
@PostMapping
|
||||||
|
public ResponseDTO<Void> add(@RequestBody NoticeAddCommand addCommand) {
|
||||||
|
noticeApplicationService.addNotice(addCommand);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改通知公告
|
||||||
|
*/
|
||||||
|
@Operation(summary = "修改公告")
|
||||||
|
@PreAuthorize("@permission.has('system:notice:edit')")
|
||||||
|
@AccessLog(title = "通知公告", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@PutMapping("/{noticeId}")
|
||||||
|
public ResponseDTO<Void> edit(@PathVariable Long noticeId, @RequestBody NoticeUpdateCommand updateCommand) {
|
||||||
|
updateCommand.setNoticeId(noticeId);
|
||||||
|
noticeApplicationService.updateNotice(updateCommand);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除通知公告
|
||||||
|
*/
|
||||||
|
@Operation(summary = "删除公告")
|
||||||
|
@PreAuthorize("@permission.has('system:notice:remove')")
|
||||||
|
@AccessLog(title = "通知公告", businessType = BusinessTypeEnum.DELETE)
|
||||||
|
@DeleteMapping
|
||||||
|
public ResponseDTO<Void> remove(@RequestParam List<Integer> noticeIds) {
|
||||||
|
noticeApplicationService.deleteNotice(new BulkOperationCommand<>(noticeIds));
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+98
@@ -0,0 +1,98 @@
|
|||||||
|
package com.agileboot.admin.controller.system;
|
||||||
|
|
||||||
|
import com.agileboot.common.constant.Constants.UploadSubDir;
|
||||||
|
import com.agileboot.common.core.base.BaseController;
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode;
|
||||||
|
import com.agileboot.common.utils.file.FileUploadUtils;
|
||||||
|
import com.agileboot.domain.common.dto.UploadFileDTO;
|
||||||
|
import com.agileboot.domain.system.user.UserApplicationService;
|
||||||
|
import com.agileboot.domain.system.user.command.UpdateProfileCommand;
|
||||||
|
import com.agileboot.domain.system.user.command.UpdateUserAvatarCommand;
|
||||||
|
import com.agileboot.domain.system.user.command.UpdateUserPasswordCommand;
|
||||||
|
import com.agileboot.domain.system.user.dto.UserProfileDTO;
|
||||||
|
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
|
||||||
|
import com.agileboot.infrastructure.user.AuthenticationUtils;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.common.enums.common.BusinessTypeEnum;
|
||||||
|
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.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个人信息 业务处理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Tag(name = "个人信息API", description = "个人信息相关接口")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/user/profile")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysProfileController extends BaseController {
|
||||||
|
|
||||||
|
private final UserApplicationService userApplicationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个人信息
|
||||||
|
*/
|
||||||
|
@Operation(summary = "获取个人信息")
|
||||||
|
@GetMapping
|
||||||
|
public ResponseDTO<UserProfileDTO> profile() {
|
||||||
|
SystemLoginUser user = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
UserProfileDTO userProfile = userApplicationService.getUserProfile(user.getUserId());
|
||||||
|
return ResponseDTO.ok(userProfile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改用户
|
||||||
|
*/
|
||||||
|
@Operation(summary = "修改个人信息")
|
||||||
|
@AccessLog(title = "个人信息", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@PutMapping
|
||||||
|
public ResponseDTO<Void> updateProfile(@RequestBody UpdateProfileCommand command) {
|
||||||
|
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
command.setUserId(loginUser.getUserId());
|
||||||
|
userApplicationService.updateUserProfile(command);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置密码
|
||||||
|
*/
|
||||||
|
@Operation(summary = "重置个人密码")
|
||||||
|
@AccessLog(title = "个人信息", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 头像上传
|
||||||
|
*/
|
||||||
|
@Operation(summary = "修改个人头像")
|
||||||
|
@AccessLog(title = "用户头像", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@PostMapping("/avatar")
|
||||||
|
public ResponseDTO<UploadFileDTO> avatar(@RequestParam("avatarfile") MultipartFile file) {
|
||||||
|
if (file.isEmpty()) {
|
||||||
|
throw new ApiException(ErrorCode.Business.USER_UPLOAD_FILE_FAILED);
|
||||||
|
}
|
||||||
|
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
String avatarUrl = FileUploadUtils.upload(UploadSubDir.AVATAR_PATH, file);
|
||||||
|
|
||||||
|
userApplicationService.updateUserAvatar(new UpdateUserAvatarCommand(loginUser.getUserId(), avatarUrl));
|
||||||
|
return ResponseDTO.ok(new UploadFileDTO(avatarUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
+181
@@ -0,0 +1,181 @@
|
|||||||
|
package com.agileboot.admin.controller.system;
|
||||||
|
|
||||||
|
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.utils.poi.CustomExcelUtil;
|
||||||
|
import com.agileboot.domain.system.role.RoleApplicationService;
|
||||||
|
import com.agileboot.domain.system.role.command.AddRoleCommand;
|
||||||
|
import com.agileboot.domain.system.role.command.UpdateRoleCommand;
|
||||||
|
import com.agileboot.domain.system.role.command.UpdateStatusCommand;
|
||||||
|
import com.agileboot.domain.system.role.dto.RoleDTO;
|
||||||
|
import com.agileboot.domain.system.role.query.AllocatedRoleQuery;
|
||||||
|
import com.agileboot.domain.system.role.query.RoleQuery;
|
||||||
|
import com.agileboot.domain.system.role.query.UnallocatedRoleQuery;
|
||||||
|
import com.agileboot.domain.system.user.dto.UserDTO;
|
||||||
|
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.servlet.http.HttpServletResponse;
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
@Tag(name = "角色API", description = "角色相关的增删查改")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/role")
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysRoleController extends BaseController {
|
||||||
|
|
||||||
|
private final RoleApplicationService roleApplicationService;
|
||||||
|
|
||||||
|
@Operation(summary = "角色列表")
|
||||||
|
@PreAuthorize("@permission.has('system:role:list')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ResponseDTO<PageDTO<RoleDTO>> list(RoleQuery query) {
|
||||||
|
PageDTO<RoleDTO> pageDTO = roleApplicationService.getRoleList(query);
|
||||||
|
return ResponseDTO.ok(pageDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "角色列表导出")
|
||||||
|
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.EXPORT)
|
||||||
|
@PreAuthorize("@permission.has('system:role:export')")
|
||||||
|
@PostMapping("/export")
|
||||||
|
public void export(HttpServletResponse response, RoleQuery query) {
|
||||||
|
PageDTO<RoleDTO> pageDTO = roleApplicationService.getRoleList(query);
|
||||||
|
CustomExcelUtil.writeToResponse(pageDTO.getRows(), RoleDTO.class, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据角色编号获取详细信息
|
||||||
|
*/
|
||||||
|
@Operation(summary = "角色详情")
|
||||||
|
@PreAuthorize("@permission.has('system:role:query')")
|
||||||
|
@GetMapping(value = "/{roleId}")
|
||||||
|
public ResponseDTO<RoleDTO> getInfo(@PathVariable @NotNull Long roleId) {
|
||||||
|
RoleDTO roleInfo = roleApplicationService.getRoleInfo(roleId);
|
||||||
|
return ResponseDTO.ok(roleInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增角色
|
||||||
|
*/
|
||||||
|
@Operation(summary = "添加角色")
|
||||||
|
@PreAuthorize("@permission.has('system:role:add')")
|
||||||
|
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.ADD)
|
||||||
|
@PostMapping
|
||||||
|
public ResponseDTO<Void> add(@RequestBody AddRoleCommand addCommand) {
|
||||||
|
roleApplicationService.addRole(addCommand);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除角色
|
||||||
|
*/
|
||||||
|
@Operation(summary = "删除角色")
|
||||||
|
@PreAuthorize("@permission.has('system:role:remove')")
|
||||||
|
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.DELETE)
|
||||||
|
@DeleteMapping(value = "/{roleId}")
|
||||||
|
public ResponseDTO<Void> remove(@PathVariable("roleId") List<Long> roleIds) {
|
||||||
|
roleApplicationService.deleteRoleByBulk(roleIds);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改保存角色
|
||||||
|
*/
|
||||||
|
@Operation(summary = "修改角色")
|
||||||
|
@PreAuthorize("@permission.has('system:role:edit')")
|
||||||
|
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@PutMapping
|
||||||
|
public ResponseDTO<Void> edit(@Validated @RequestBody UpdateRoleCommand updateCommand) {
|
||||||
|
roleApplicationService.updateRole(updateCommand);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 角色状态修改
|
||||||
|
*/
|
||||||
|
@Operation(summary = "修改角色状态")
|
||||||
|
@PreAuthorize("@permission.has('system:role:edit')")
|
||||||
|
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@PutMapping("/{roleId}/status")
|
||||||
|
public ResponseDTO<Void> changeStatus(@PathVariable("roleId") Long roleId,
|
||||||
|
@RequestBody UpdateStatusCommand command) {
|
||||||
|
command.setRoleId(roleId);
|
||||||
|
|
||||||
|
roleApplicationService.updateStatus(command);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询已分配用户角色列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "已关联该角色的用户列表")
|
||||||
|
@PreAuthorize("@permission.has('system:role:list')")
|
||||||
|
@GetMapping("/{roleId}/allocated/list")
|
||||||
|
public ResponseDTO<PageDTO<UserDTO>> allocatedUserList(@PathVariable("roleId") Long roleId,
|
||||||
|
AllocatedRoleQuery query) {
|
||||||
|
query.setRoleId(roleId);
|
||||||
|
PageDTO<UserDTO> page = roleApplicationService.getAllocatedUserList(query);
|
||||||
|
return ResponseDTO.ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询未分配用户角色列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "未关联该角色的用户列表")
|
||||||
|
@PreAuthorize("@permission.has('system:role:list')")
|
||||||
|
@GetMapping("/{roleId}/unallocated/list")
|
||||||
|
public ResponseDTO<PageDTO<UserDTO>> unallocatedUserList(@PathVariable("roleId") Long roleId,
|
||||||
|
UnallocatedRoleQuery query) {
|
||||||
|
query.setRoleId(roleId);
|
||||||
|
PageDTO<UserDTO> page = roleApplicationService.getUnallocatedUserList(query);
|
||||||
|
return ResponseDTO.ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量取消授权用户
|
||||||
|
*/
|
||||||
|
@Operation(summary = "批量解除角色和用户的关联")
|
||||||
|
@PreAuthorize("@permission.has('system:role:edit')")
|
||||||
|
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.GRANT)
|
||||||
|
@DeleteMapping("/users/{userIds}/grant/bulk")
|
||||||
|
public ResponseDTO<Void> deleteRoleOfUserByBulk(@PathVariable("userIds") List<Long> userIds) {
|
||||||
|
roleApplicationService.deleteRoleOfUserByBulk(userIds);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量选择用户授权
|
||||||
|
*/
|
||||||
|
@Operation(summary = "批量添加用户和角色关联")
|
||||||
|
@PreAuthorize("@permission.has('system:role:edit')")
|
||||||
|
@AccessLog(title = "角色管理", businessType = BusinessTypeEnum.GRANT)
|
||||||
|
@PostMapping("/{roleId}/users/{userIds}/grant/bulk")
|
||||||
|
public ResponseDTO<Void> addRoleForUserByBulk(@PathVariable("roleId") Long roleId,
|
||||||
|
@PathVariable("userIds") List<Long> userIds) {
|
||||||
|
roleApplicationService.addRoleOfUserByBulk(roleId, userIds);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+169
@@ -0,0 +1,169 @@
|
|||||||
|
package com.agileboot.admin.controller.system;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.ListUtil;
|
||||||
|
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.utils.poi.CustomExcelUtil;
|
||||||
|
import com.agileboot.domain.common.command.BulkOperationCommand;
|
||||||
|
import com.agileboot.domain.system.user.UserApplicationService;
|
||||||
|
import com.agileboot.domain.system.user.command.AddUserCommand;
|
||||||
|
import com.agileboot.domain.system.user.command.ChangeStatusCommand;
|
||||||
|
import com.agileboot.domain.system.user.command.ResetPasswordCommand;
|
||||||
|
import com.agileboot.domain.system.user.command.UpdateUserCommand;
|
||||||
|
import com.agileboot.domain.system.user.dto.UserDTO;
|
||||||
|
import com.agileboot.domain.system.user.dto.UserDetailDTO;
|
||||||
|
import com.agileboot.domain.system.user.query.SearchUserQuery;
|
||||||
|
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
|
||||||
|
import com.agileboot.infrastructure.user.AuthenticationUtils;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.common.enums.common.BusinessTypeEnum;
|
||||||
|
import com.agileboot.domain.system.user.db.SearchUserDO;
|
||||||
|
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 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;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Tag(name = "用户API", description = "用户相关的增删查改")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/system/users")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysUserController extends BaseController {
|
||||||
|
|
||||||
|
private final UserApplicationService userApplicationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户列表
|
||||||
|
*/
|
||||||
|
@Operation(summary = "用户列表")
|
||||||
|
@PreAuthorize("@permission.has('system:user:list')")
|
||||||
|
@GetMapping
|
||||||
|
public ResponseDTO<PageDTO<UserDTO>> userList(SearchUserQuery<SearchUserDO> query) {
|
||||||
|
PageDTO<UserDTO> page = userApplicationService.getUserList(query);
|
||||||
|
return ResponseDTO.ok(page);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "用户列表导出")
|
||||||
|
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.EXPORT)
|
||||||
|
@PreAuthorize("@permission.has('system:user:export')")
|
||||||
|
@GetMapping("/excel")
|
||||||
|
public void exportUserByExcel(HttpServletResponse response, SearchUserQuery<SearchUserDO> query) {
|
||||||
|
PageDTO<UserDTO> userList = userApplicationService.getUserList(query);
|
||||||
|
CustomExcelUtil.writeToResponse(userList.getRows(), UserDTO.class, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "用户列表导入")
|
||||||
|
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.IMPORT)
|
||||||
|
@PreAuthorize("@permission.has('system:user:import')")
|
||||||
|
@PostMapping("/excel")
|
||||||
|
public ResponseDTO<Void> importUserByExcel(MultipartFile file) {
|
||||||
|
List<AddUserCommand> commands = CustomExcelUtil.readFromRequest(AddUserCommand.class, file);
|
||||||
|
|
||||||
|
for (AddUserCommand command : commands) {
|
||||||
|
userApplicationService.addUser(command);
|
||||||
|
}
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载批量导入模板
|
||||||
|
*/
|
||||||
|
@Operation(summary = "用户导入excel下载")
|
||||||
|
@GetMapping("/excelTemplate")
|
||||||
|
public void downloadExcelTemplate(HttpServletResponse response) {
|
||||||
|
CustomExcelUtil.writeToResponse(ListUtil.toList(new AddUserCommand()), AddUserCommand.class, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户编号获取详细信息
|
||||||
|
*/
|
||||||
|
@Operation(summary = "用户详情")
|
||||||
|
@PreAuthorize("@permission.has('system:user:query')")
|
||||||
|
@GetMapping("/{userId}")
|
||||||
|
public ResponseDTO<UserDetailDTO> getUserDetailInfo(@PathVariable(value = "userId", required = false) Long userId) {
|
||||||
|
UserDetailDTO userDetailInfo = userApplicationService.getUserDetailInfo(userId);
|
||||||
|
return ResponseDTO.ok(userDetailInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增用户
|
||||||
|
*/
|
||||||
|
@Operation(summary = "新增用户")
|
||||||
|
@PreAuthorize("@permission.has('system:user:add')")
|
||||||
|
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.ADD)
|
||||||
|
@PostMapping
|
||||||
|
public ResponseDTO<Void> add(@Validated @RequestBody AddUserCommand command) {
|
||||||
|
userApplicationService.addUser(command);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改用户
|
||||||
|
*/
|
||||||
|
@Operation(summary = "修改用户")
|
||||||
|
@PreAuthorize("@permission.has('system:user:edit') AND @dataScope.checkUserId(#command.userId)")
|
||||||
|
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@PutMapping("/{userId}")
|
||||||
|
public ResponseDTO<Void> edit(@Validated @RequestBody UpdateUserCommand command) {
|
||||||
|
userApplicationService.updateUser(command);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户
|
||||||
|
*/
|
||||||
|
@Operation(summary = "删除用户")
|
||||||
|
@PreAuthorize("@permission.has('system:user:remove') AND @dataScope.checkUserIds(#userIds)")
|
||||||
|
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.DELETE)
|
||||||
|
@DeleteMapping("/{userIds}")
|
||||||
|
public ResponseDTO<Void> remove(@PathVariable List<Long> userIds) {
|
||||||
|
BulkOperationCommand<Long> bulkDeleteCommand = new BulkOperationCommand<>(userIds);
|
||||||
|
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
userApplicationService.deleteUsers(loginUser, bulkDeleteCommand);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置密码
|
||||||
|
*/
|
||||||
|
@Operation(summary = "重置用户密码")
|
||||||
|
@PreAuthorize("@permission.has('system:user:resetPwd') AND @dataScope.checkUserId(#userId)")
|
||||||
|
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@PutMapping("/{userId}/password")
|
||||||
|
public ResponseDTO<Void> resetPassword(@PathVariable Long userId, @RequestBody ResetPasswordCommand command) {
|
||||||
|
command.setUserId(userId);
|
||||||
|
userApplicationService.resetUserPassword(command);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态修改
|
||||||
|
*/
|
||||||
|
@Operation(summary = "修改用户状态")
|
||||||
|
@PreAuthorize("@permission.has('system:user:edit') AND @dataScope.checkUserId(#command.userId)")
|
||||||
|
@AccessLog(title = "用户管理", businessType = BusinessTypeEnum.MODIFY)
|
||||||
|
@PutMapping("/{userId}/status")
|
||||||
|
public ResponseDTO<Void> changeStatus(@PathVariable Long userId, @RequestBody ChangeStatusCommand command) {
|
||||||
|
command.setUserId(userId);
|
||||||
|
userApplicationService.changeUserStatus(command);
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package com.agileboot.admin.customize.aop.accessLog;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.common.BusinessTypeEnum;
|
||||||
|
import com.agileboot.common.enums.common.OperatorTypeEnum;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义操作日志记录注解
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Target({ElementType.PARAMETER, ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface AccessLog {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 模块
|
||||||
|
*/
|
||||||
|
String title() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能
|
||||||
|
*/
|
||||||
|
BusinessTypeEnum businessType() default BusinessTypeEnum.OTHER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作人类别
|
||||||
|
*/
|
||||||
|
OperatorTypeEnum operatorType() default OperatorTypeEnum.WEB;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否保存请求的参数
|
||||||
|
*/
|
||||||
|
boolean isSaveRequestData() default true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否保存响应的参数
|
||||||
|
*/
|
||||||
|
boolean isSaveResponseData() default false;
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package com.agileboot.admin.customize.aop.accessLog;
|
||||||
|
|
||||||
|
import com.agileboot.admin.customize.async.AsyncTaskFactory;
|
||||||
|
import com.agileboot.infrastructure.thread.ThreadPoolManager;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.JoinPoint;
|
||||||
|
import org.aspectj.lang.annotation.AfterReturning;
|
||||||
|
import org.aspectj.lang.annotation.AfterThrowing;
|
||||||
|
import org.aspectj.lang.annotation.Aspect;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作日志记录处理
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Aspect
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class AccessLogAspect {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理完请求后执行
|
||||||
|
*
|
||||||
|
* @param joinPoint 切点
|
||||||
|
*/
|
||||||
|
@AfterReturning(pointcut = "@annotation(controllerLog)", returning = "jsonResult")
|
||||||
|
public void doAfterReturning(JoinPoint joinPoint, AccessLog controllerLog, Object jsonResult) {
|
||||||
|
handleLog(joinPoint, controllerLog, null, jsonResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拦截异常操作
|
||||||
|
*
|
||||||
|
* @param joinPoint 切点
|
||||||
|
* @param e 异常
|
||||||
|
*/
|
||||||
|
@AfterThrowing(value = "@annotation(controllerLog)", throwing = "e")
|
||||||
|
public void doAfterThrowing(JoinPoint joinPoint, AccessLog controllerLog, Exception e) {
|
||||||
|
handleLog(joinPoint, controllerLog, e, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void handleLog(final JoinPoint joinPoint, AccessLog accessLog, final Exception e, Object jsonResult) {
|
||||||
|
try {
|
||||||
|
OperationLogModel operationLog = new OperationLogModel();
|
||||||
|
operationLog.fillOperatorInfo();
|
||||||
|
operationLog.fillRequestInfo(joinPoint, accessLog, jsonResult);
|
||||||
|
operationLog.fillStatus(e);
|
||||||
|
operationLog.fillAccessLogInfo(accessLog);
|
||||||
|
|
||||||
|
// 保存数据库
|
||||||
|
ThreadPoolManager.execute(AsyncTaskFactory.recordOperationLog(operationLog));
|
||||||
|
} catch (Exception exp) {
|
||||||
|
log.error("写入操作日式失败", exp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+160
@@ -0,0 +1,160 @@
|
|||||||
|
package com.agileboot.admin.customize.aop.accessLog;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.util.EnumUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.extra.servlet.ServletUtil;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.agileboot.common.utils.ServletHolderUtil;
|
||||||
|
import com.agileboot.infrastructure.user.AuthenticationUtils;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.common.enums.common.OperationStatusEnum;
|
||||||
|
import com.agileboot.common.enums.common.RequestMethodEnum;
|
||||||
|
import com.agileboot.common.enums.BasicEnumUtil;
|
||||||
|
import com.agileboot.domain.system.log.db.SysOperationLogEntity;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.aspectj.lang.JoinPoint;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
import org.springframework.web.servlet.HandlerMapping;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class OperationLogModel extends SysOperationLogEntity {
|
||||||
|
|
||||||
|
public static final int MAX_DATA_LENGTH = 512;
|
||||||
|
|
||||||
|
HttpServletRequest request = ServletHolderUtil.getRequest();
|
||||||
|
|
||||||
|
public void fillOperatorInfo() {
|
||||||
|
// 获取当前的用户
|
||||||
|
String ip = ServletUtil.getClientIP(request);
|
||||||
|
setOperatorIp(ip);
|
||||||
|
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
if (loginUser != null) {
|
||||||
|
this.setUsername(loginUser.getUsername());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setOperationTime(DateUtil.date());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void fillRequestInfo(final JoinPoint joinPoint, AccessLog accessLog, Object jsonResult) {
|
||||||
|
this.setRequestUrl(request.getRequestURI());
|
||||||
|
// 设置方法名称
|
||||||
|
String className = joinPoint.getTarget().getClass().getName();
|
||||||
|
String methodName = joinPoint.getSignature().getName();
|
||||||
|
String methodFormat = StrUtil.format("{}.{}()", className, methodName);
|
||||||
|
this.setCalledMethod(methodFormat);
|
||||||
|
// 设置请求方式
|
||||||
|
RequestMethodEnum requestMethodEnum = EnumUtil.fromString(RequestMethodEnum.class,
|
||||||
|
request.getMethod());
|
||||||
|
this.setRequestMethod(requestMethodEnum != null ? requestMethodEnum.getValue() : RequestMethodEnum.UNKNOWN.getValue());
|
||||||
|
|
||||||
|
|
||||||
|
// 是否需要保存request,参数和值
|
||||||
|
if (accessLog.isSaveRequestData()) {
|
||||||
|
// 获取参数的信息,传入到数据库中。
|
||||||
|
recordRequestData(joinPoint);
|
||||||
|
}
|
||||||
|
// 是否需要保存response,参数和值
|
||||||
|
if (accessLog.isSaveResponseData() && jsonResult != null) {
|
||||||
|
this.setOperationResult(StrUtil.sub(JSONUtil.toJsonStr(jsonResult), 0, MAX_DATA_LENGTH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void fillAccessLogInfo(AccessLog log) {
|
||||||
|
// 设置action动作
|
||||||
|
this.setBusinessType(log.businessType().ordinal());
|
||||||
|
// 设置标题
|
||||||
|
this.setRequestModule(log.title());
|
||||||
|
// 设置操作人类别
|
||||||
|
this.setOperatorType(log.operatorType().ordinal());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void fillStatus(Exception e) {
|
||||||
|
if (e != null) {
|
||||||
|
this.setStatus(OperationStatusEnum.FAIL.getValue());
|
||||||
|
this.setErrorStack(StrUtil.sub(e.getMessage(), 0, MAX_DATA_LENGTH));
|
||||||
|
} else {
|
||||||
|
this.setStatus(OperationStatusEnum.SUCCESS.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求的参数,放到log中
|
||||||
|
*
|
||||||
|
* @param joinPoint 方法切面
|
||||||
|
*/
|
||||||
|
private void recordRequestData(JoinPoint joinPoint) {
|
||||||
|
RequestMethodEnum requestMethodEnum = BasicEnumUtil.fromValue(RequestMethodEnum.class,
|
||||||
|
this.getRequestMethod());
|
||||||
|
|
||||||
|
if (requestMethodEnum == RequestMethodEnum.GET || requestMethodEnum == RequestMethodEnum.POST) {
|
||||||
|
String params = argsArrayToString(joinPoint.getArgs());
|
||||||
|
this.setOperationParam(StrUtil.sub(params, 0, MAX_DATA_LENGTH));
|
||||||
|
} else {
|
||||||
|
Map<?, ?> paramsMap = (Map<?, ?>) request
|
||||||
|
.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
|
||||||
|
this.setOperationParam(StrUtil.sub(paramsMap.toString(), 0, MAX_DATA_LENGTH));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 参数拼装
|
||||||
|
*/
|
||||||
|
private String argsArrayToString(Object[] paramsArray) {
|
||||||
|
StringBuilder params = new StringBuilder();
|
||||||
|
if (paramsArray != null) {
|
||||||
|
for (Object o : paramsArray) {
|
||||||
|
if (o != null && !isCanNotBeParseToJson(o)) {
|
||||||
|
try {
|
||||||
|
Object jsonObj = JSONUtil.parseObj(o);
|
||||||
|
params.append(jsonObj).append(",");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.info("参数拼接错误", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params.toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否需要过滤的对象。
|
||||||
|
*
|
||||||
|
* @param o 对象信息。
|
||||||
|
* @return 如果是需要过滤的对象,则返回true;否则返回false。
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public boolean isCanNotBeParseToJson(final Object o) {
|
||||||
|
Class<?> clazz = o.getClass();
|
||||||
|
if (clazz.isArray()) {
|
||||||
|
return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
|
||||||
|
} else if (Collection.class.isAssignableFrom(clazz)) {
|
||||||
|
Collection collection = (Collection) o;
|
||||||
|
for (Object value : collection) {
|
||||||
|
return value instanceof MultipartFile;
|
||||||
|
}
|
||||||
|
} else if (Map.class.isAssignableFrom(clazz)) {
|
||||||
|
Map map = (Map) o;
|
||||||
|
for (Object value : map.entrySet()) {
|
||||||
|
Map.Entry entry = (Map.Entry) value;
|
||||||
|
return entry.getValue() instanceof MultipartFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
|
||||||
|
|| o instanceof BindingResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+78
@@ -0,0 +1,78 @@
|
|||||||
|
package com.agileboot.admin.customize.async;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.extra.servlet.ServletUtil;
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import com.agileboot.common.utils.ServletHolderUtil;
|
||||||
|
import com.agileboot.common.utils.ip.IpRegionUtil;
|
||||||
|
import com.agileboot.common.enums.common.LoginStatusEnum;
|
||||||
|
import com.agileboot.domain.system.log.db.SysLoginInfoEntity;
|
||||||
|
import com.agileboot.domain.system.log.db.SysOperationLogEntity;
|
||||||
|
import com.agileboot.domain.system.log.db.SysLoginInfoService;
|
||||||
|
import com.agileboot.domain.system.log.db.SysOperationLogService;
|
||||||
|
import eu.bitwalker.useragentutils.UserAgent;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步工厂(产生任务用)
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class AsyncTaskFactory {
|
||||||
|
|
||||||
|
private AsyncTaskFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录登录信息
|
||||||
|
*
|
||||||
|
* @param username 用户名
|
||||||
|
* @param loginStatusEnum 状态
|
||||||
|
* @param message 消息
|
||||||
|
* @return 任务task
|
||||||
|
*/
|
||||||
|
public static Runnable loginInfoTask(final String username, final LoginStatusEnum loginStatusEnum, final String message) {
|
||||||
|
// 优化一下这个类
|
||||||
|
final UserAgent userAgent = UserAgent.parseUserAgentString(
|
||||||
|
ServletHolderUtil.getRequest().getHeader("User-Agent"));
|
||||||
|
// 获取客户端浏览器
|
||||||
|
final String browser = userAgent.getBrowser() != null ? userAgent.getBrowser().getName() : "";
|
||||||
|
final String ip = ServletUtil.getClientIP(ServletHolderUtil.getRequest());
|
||||||
|
final String address = IpRegionUtil.getBriefLocationByIp(ip);
|
||||||
|
// 获取客户端操作系统
|
||||||
|
final String os = userAgent.getOperatingSystem() != null ? userAgent.getOperatingSystem().getName() : "";
|
||||||
|
|
||||||
|
log.info("ip: {}, address: {}, username: {}, loginStatusEnum: {}, message: {}", ip, address, username,
|
||||||
|
loginStatusEnum, message);
|
||||||
|
return () -> {
|
||||||
|
// 封装对象
|
||||||
|
SysLoginInfoEntity loginInfo = new SysLoginInfoEntity();
|
||||||
|
loginInfo.setUsername(username);
|
||||||
|
loginInfo.setIpAddress(ip);
|
||||||
|
loginInfo.setLoginLocation(address);
|
||||||
|
loginInfo.setBrowser(browser);
|
||||||
|
loginInfo.setOperationSystem(os);
|
||||||
|
loginInfo.setMsg(message);
|
||||||
|
loginInfo.setLoginTime(DateUtil.date());
|
||||||
|
loginInfo.setStatus(loginStatusEnum.getValue());
|
||||||
|
// 插入数据
|
||||||
|
SpringUtil.getBean(SysLoginInfoService.class).save(loginInfo);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作日志记录
|
||||||
|
*
|
||||||
|
* @param operationLog 操作日志信息
|
||||||
|
* @return 任务task
|
||||||
|
*/
|
||||||
|
public static Runnable recordOperationLog(final SysOperationLogEntity operationLog) {
|
||||||
|
return () -> {
|
||||||
|
// 远程查询操作地点
|
||||||
|
operationLog.setOperatorLocation(IpRegionUtil.getBriefLocationByIp(operationLog.getOperatorIp()));
|
||||||
|
SpringUtil.getBean(SysOperationLogService.class).save(operationLog);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
package com.agileboot.admin.customize.config;
|
||||||
|
|
||||||
|
import com.agileboot.infrastructure.user.AuthenticationUtils;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.admin.customize.service.login.TokenService;
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token过滤器 验证token有效性
|
||||||
|
* 继承OncePerRequestFilter类的话 可以确保只执行filter一次, 避免执行多次
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private final TokenService tokenService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
SystemLoginUser loginUser = tokenService.getLoginUser(request);
|
||||||
|
if (loginUser != null && AuthenticationUtils.getAuthentication() == null) {
|
||||||
|
tokenService.refreshToken(loginUser);
|
||||||
|
// 如果没有将当前登录用户放入到上下文中的话,会认定用户未授权,返回用户未登陆的错误
|
||||||
|
putCurrentLoginUserIntoContext(request, loginUser);
|
||||||
|
|
||||||
|
log.debug("request process in jwt token filter. get login user id: {}", loginUser.getUserId());
|
||||||
|
}
|
||||||
|
chain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void putCurrentLoginUserIntoContext(HttpServletRequest request, SystemLoginUser loginUser) {
|
||||||
|
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(loginUser,
|
||||||
|
null, loginUser.getAuthorities());
|
||||||
|
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+165
@@ -0,0 +1,165 @@
|
|||||||
|
package com.agileboot.admin.customize.config;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import com.agileboot.admin.customize.service.login.LoginService;
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode.Client;
|
||||||
|
import com.agileboot.common.utils.ServletHolderUtil;
|
||||||
|
import com.agileboot.domain.common.cache.RedisCacheService;
|
||||||
|
import com.agileboot.admin.customize.async.AsyncTaskFactory;
|
||||||
|
import com.agileboot.infrastructure.thread.ThreadPoolManager;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.admin.customize.service.login.TokenService;
|
||||||
|
import com.agileboot.common.enums.common.LoginStatusEnum;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.security.web.authentication.logout.LogoutFilter;
|
||||||
|
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主要配置登录流程逻辑涉及以下几个类
|
||||||
|
* @see UserDetailsServiceImpl#loadUserByUsername 用于登录流程通过用户名加载用户
|
||||||
|
* @see this#unauthorizedHandler() 用于用户未授权或登录失败处理
|
||||||
|
* @see this#logOutSuccessHandler 用于退出登录成功后的逻辑
|
||||||
|
* @see JwtAuthenticationTokenFilter#doFilter token的校验和刷新
|
||||||
|
* @see LoginService#login 登录逻辑
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final TokenService tokenService;
|
||||||
|
|
||||||
|
private final RedisCacheService redisCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token认证过滤器
|
||||||
|
*/
|
||||||
|
private final JwtAuthenticationTokenFilter jwtTokenFilter;
|
||||||
|
|
||||||
|
private final UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跨域过滤器
|
||||||
|
*/
|
||||||
|
private final CorsFilter corsFilter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录异常处理类
|
||||||
|
* 用户未登陆的话 在这个Bean中处理
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public AuthenticationEntryPoint unauthorizedHandler() {
|
||||||
|
return (request, response, exception) -> {
|
||||||
|
ResponseDTO<Object> responseDTO = ResponseDTO.fail(
|
||||||
|
new ApiException(Client.COMMON_NO_AUTHORIZATION, request.getRequestURI())
|
||||||
|
);
|
||||||
|
ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(responseDTO));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出成功处理类 返回成功
|
||||||
|
* 在SecurityConfig类当中 定义了/logout 路径对应处理逻辑
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public LogoutSuccessHandler logOutSuccessHandler() {
|
||||||
|
return (request, response, authentication) -> {
|
||||||
|
SystemLoginUser loginUser = tokenService.getLoginUser(request);
|
||||||
|
if (loginUser != null) {
|
||||||
|
String userName = loginUser.getUsername();
|
||||||
|
// 删除用户缓存记录
|
||||||
|
redisCache.loginUserCache.delete(loginUser.getCachedKey());
|
||||||
|
// 记录用户退出日志
|
||||||
|
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(
|
||||||
|
userName, LoginStatusEnum.LOGOUT, LoginStatusEnum.LOGOUT.description()));
|
||||||
|
}
|
||||||
|
ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(ResponseDTO.ok()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强散列哈希加密实现
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public BCryptPasswordEncoder bCryptPasswordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鉴权管理类
|
||||||
|
* @see UserDetailsServiceImpl#loadUserByUsername
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authManager(HttpSecurity http) throws Exception {
|
||||||
|
return http.getSharedObject(AuthenticationManagerBuilder.class)
|
||||||
|
.userDetailsService(userDetailsService)
|
||||||
|
.passwordEncoder(bCryptPasswordEncoder())
|
||||||
|
.and()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
|
||||||
|
httpSecurity
|
||||||
|
// CSRF禁用,因为不使用session
|
||||||
|
.csrf().disable()
|
||||||
|
// 认证失败处理类
|
||||||
|
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler()).and()
|
||||||
|
// 基于token,所以不需要session
|
||||||
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||||
|
// 过滤请求
|
||||||
|
.authorizeRequests()
|
||||||
|
// 对于登录login 注册register 验证码captchaImage 以及公共Api的请求允许匿名访问
|
||||||
|
// 注意: 当携带token请求以下这几个接口时 会返回403的错误
|
||||||
|
.antMatchers("/login", "/register", "/getConfig", "/captchaImage", "/api/**").anonymous()
|
||||||
|
.antMatchers("/app/login", "/app/register", "/app/getConfig", "/app/captchaImage").anonymous()
|
||||||
|
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
|
||||||
|
"/profile/**").permitAll()
|
||||||
|
// TODO this is danger.
|
||||||
|
.antMatchers("/swagger-ui.html").anonymous()
|
||||||
|
.antMatchers("/swagger-resources/**").anonymous()
|
||||||
|
.antMatchers("/webjars/**").anonymous()
|
||||||
|
.antMatchers("/*/api-docs","/*/api-docs/swagger-config").anonymous()
|
||||||
|
.antMatchers("/**/api-docs.yaml" ).anonymous()
|
||||||
|
.antMatchers("/druid/**").anonymous()
|
||||||
|
// 除上面外的所有请求全部需要鉴权认证
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.and()
|
||||||
|
// 禁用 X-Frame-Options 响应头。下面是具体解释:
|
||||||
|
// X-Frame-Options 是一个 HTTP 响应头,用于防止网页被嵌入到其他网页的 <frame>、<iframe> 或 <object> 标签中,从而可以减少点击劫持攻击的风险
|
||||||
|
.headers().frameOptions().disable();
|
||||||
|
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logOutSuccessHandler());
|
||||||
|
// 添加JWT filter 需要一开始就通过token识别出登录用户 并放到上下文中 所以jwtFilter需要放前面
|
||||||
|
httpSecurity.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
// 添加CORS filter
|
||||||
|
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
|
||||||
|
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
|
||||||
|
|
||||||
|
return httpSecurity.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+256
@@ -0,0 +1,256 @@
|
|||||||
|
package com.agileboot.admin.customize.service.login;
|
||||||
|
|
||||||
|
import cn.hutool.core.codec.Base64;
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.img.ImgUtil;
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.crypto.SecureUtil;
|
||||||
|
import cn.hutool.crypto.asymmetric.KeyType;
|
||||||
|
import cn.hutool.extra.servlet.ServletUtil;
|
||||||
|
import com.agileboot.common.config.AgileBootConfig;
|
||||||
|
import com.agileboot.common.constant.Constants.Captcha;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode.Business;
|
||||||
|
import com.agileboot.common.utils.ServletHolderUtil;
|
||||||
|
import com.agileboot.common.utils.i18n.MessageUtils;
|
||||||
|
import com.agileboot.domain.common.cache.GuavaCacheService;
|
||||||
|
import com.agileboot.domain.common.cache.MapCache;
|
||||||
|
import com.agileboot.domain.common.cache.RedisCacheService;
|
||||||
|
import com.agileboot.admin.customize.async.AsyncTaskFactory;
|
||||||
|
import com.agileboot.infrastructure.thread.ThreadPoolManager;
|
||||||
|
import com.agileboot.admin.customize.service.login.dto.CaptchaDTO;
|
||||||
|
import com.agileboot.admin.customize.service.login.dto.ConfigDTO;
|
||||||
|
import com.agileboot.admin.customize.service.login.command.LoginCommand;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.common.enums.common.ConfigKeyEnum;
|
||||||
|
import com.agileboot.common.enums.common.LoginStatusEnum;
|
||||||
|
import com.agileboot.domain.system.user.db.SysUserEntity;
|
||||||
|
import com.google.code.kaptcha.Producer;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.FastByteArrayOutputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录校验方法
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class LoginService {
|
||||||
|
|
||||||
|
private final TokenService tokenService;
|
||||||
|
|
||||||
|
private final RedisCacheService redisCache;
|
||||||
|
|
||||||
|
private final GuavaCacheService guavaCache;
|
||||||
|
|
||||||
|
private final AuthenticationManager authenticationManager;
|
||||||
|
|
||||||
|
private final UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Resource(name = "captchaProducer")
|
||||||
|
private Producer captchaProducer;
|
||||||
|
|
||||||
|
@Resource(name = "captchaProducerMath")
|
||||||
|
private Producer captchaProducerMath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录验证
|
||||||
|
*
|
||||||
|
* @param loginCommand 登录参数
|
||||||
|
* @return 结果
|
||||||
|
*/
|
||||||
|
public String login(LoginCommand loginCommand) {
|
||||||
|
// 验证码开关
|
||||||
|
if (isCaptchaOn()) {
|
||||||
|
validateCaptcha(loginCommand.getUsername(), loginCommand.getCaptchaCode(), loginCommand.getCaptchaCodeKey());
|
||||||
|
}
|
||||||
|
// 用户验证
|
||||||
|
Authentication authentication;
|
||||||
|
String decryptPassword = decryptPassword(loginCommand.getPassword());
|
||||||
|
try {
|
||||||
|
// 该方法会去调用UserDetailsServiceImpl#loadUserByUsername 校验用户名和密码 认证鉴权
|
||||||
|
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
|
||||||
|
loginCommand.getUsername(), decryptPassword));
|
||||||
|
} catch (BadCredentialsException e) {
|
||||||
|
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL,
|
||||||
|
MessageUtils.message("Business.LOGIN_WRONG_USER_PASSWORD")));
|
||||||
|
throw new ApiException(e, ErrorCode.Business.LOGIN_WRONG_USER_PASSWORD);
|
||||||
|
} catch (AuthenticationException e) {
|
||||||
|
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, e.getMessage()));
|
||||||
|
throw new ApiException(e, ErrorCode.Business.LOGIN_ERROR, e.getMessage());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginCommand.getUsername(), LoginStatusEnum.LOGIN_FAIL, e.getMessage()));
|
||||||
|
throw new ApiException(e, Business.LOGIN_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
// 把当前登录用户 放入上下文中
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
// 这里获取的loginUser是UserDetailsServiceImpl#loadUserByUsername方法返回的LoginUser
|
||||||
|
SystemLoginUser loginUser = (SystemLoginUser) authentication.getPrincipal();
|
||||||
|
recordLoginInfo(loginUser);
|
||||||
|
// 生成token
|
||||||
|
return tokenService.createTokenAndPutUserInCache(loginUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码 data
|
||||||
|
*
|
||||||
|
* @return {@link ConfigDTO}
|
||||||
|
*/
|
||||||
|
public ConfigDTO getConfig() {
|
||||||
|
ConfigDTO configDTO = new ConfigDTO();
|
||||||
|
|
||||||
|
boolean isCaptchaOn = isCaptchaOn();
|
||||||
|
configDTO.setIsCaptchaOn(isCaptchaOn);
|
||||||
|
configDTO.setIsRegisterUserOn(isRegisterUserOn());
|
||||||
|
configDTO.setDictionary(MapCache.dictionaryCache());
|
||||||
|
return configDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取验证码 data
|
||||||
|
*
|
||||||
|
* @return 验证码
|
||||||
|
*/
|
||||||
|
public CaptchaDTO generateCaptchaImg() {
|
||||||
|
CaptchaDTO captchaDTO = new CaptchaDTO();
|
||||||
|
|
||||||
|
boolean isCaptchaOn = isCaptchaOn();
|
||||||
|
captchaDTO.setIsCaptchaOn(isCaptchaOn);
|
||||||
|
|
||||||
|
if (isCaptchaOn) {
|
||||||
|
String expression;
|
||||||
|
String answer = null;
|
||||||
|
BufferedImage image = null;
|
||||||
|
|
||||||
|
// 生成验证码
|
||||||
|
String captchaType = AgileBootConfig.getCaptchaType();
|
||||||
|
if (Captcha.MATH_TYPE.equals(captchaType)) {
|
||||||
|
String capText = captchaProducerMath.createText();
|
||||||
|
String[] expressionAndAnswer = capText.split("@");
|
||||||
|
expression = expressionAndAnswer[0];
|
||||||
|
answer = expressionAndAnswer[1];
|
||||||
|
image = captchaProducerMath.createImage(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Captcha.CHAR_TYPE.equals(captchaType)) {
|
||||||
|
expression = answer = captchaProducer.createText();
|
||||||
|
image = captchaProducer.createImage(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image == null) {
|
||||||
|
throw new ApiException(ErrorCode.Internal.LOGIN_CAPTCHA_GENERATE_FAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存验证码信息
|
||||||
|
String imgKey = IdUtil.simpleUUID();
|
||||||
|
|
||||||
|
redisCache.captchaCache.set(imgKey, answer);
|
||||||
|
// 转换流信息写出
|
||||||
|
FastByteArrayOutputStream os = new FastByteArrayOutputStream();
|
||||||
|
ImgUtil.writeJpg(image, os);
|
||||||
|
|
||||||
|
captchaDTO.setCaptchaCodeKey(imgKey);
|
||||||
|
captchaDTO.setCaptchaCodeImg(Base64.encode(os.toByteArray()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return captchaDTO;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验验证码
|
||||||
|
*
|
||||||
|
* @param username 用户名
|
||||||
|
* @param captchaCode 验证码
|
||||||
|
* @param captchaCodeKey 验证码对应的缓存key
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
redisCache.captchaCache.delete(captchaCodeKey);
|
||||||
|
if (captcha == null) {
|
||||||
|
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_FAIL,
|
||||||
|
ErrorCode.Business.LOGIN_CAPTCHA_CODE_EXPIRE.message()));
|
||||||
|
throw new ApiException(ErrorCode.Business.LOGIN_CAPTCHA_CODE_EXPIRE);
|
||||||
|
}
|
||||||
|
if (!captchaCode.equalsIgnoreCase(captcha)) {
|
||||||
|
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(username, LoginStatusEnum.LOGIN_FAIL,
|
||||||
|
ErrorCode.Business.LOGIN_CAPTCHA_CODE_WRONG.message()));
|
||||||
|
throw new ApiException(ErrorCode.Business.LOGIN_CAPTCHA_CODE_WRONG);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 登录用户
|
||||||
|
*/
|
||||||
|
public void recordLoginInfo(SystemLoginUser loginUser) {
|
||||||
|
ThreadPoolManager.execute(AsyncTaskFactory.loginInfoTask(loginUser.getUsername(), LoginStatusEnum.LOGIN_SUCCESS,
|
||||||
|
LoginStatusEnum.LOGIN_SUCCESS.description()));
|
||||||
|
updateLoginInfo(loginUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateLoginInfo(SystemLoginUser loginUser) {
|
||||||
|
SysUserEntity entity = redisCache.userCache.getObjectById(loginUser.getUserId());
|
||||||
|
|
||||||
|
entity.setLoginIp(ServletUtil.getClientIP(ServletHolderUtil.getRequest()));
|
||||||
|
entity.setLoginDate(DateUtil.date());
|
||||||
|
entity.updateById();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String decryptPassword(String originalPassword) {
|
||||||
|
byte[] decryptBytes = SecureUtil.rsa(AgileBootConfig.getRsaPrivateKey(), null)
|
||||||
|
.decrypt(Base64.decode(originalPassword), KeyType.PrivateKey);
|
||||||
|
|
||||||
|
return StrUtil.str(decryptBytes, CharsetUtil.CHARSET_UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isCaptchaOn() {
|
||||||
|
return Convert.toBool(guavaCache.configCache.get(ConfigKeyEnum.CAPTCHA.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRegisterUserOn() {
|
||||||
|
return Convert.toBool(guavaCache.configCache.get(ConfigKeyEnum.REGISTER.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+163
@@ -0,0 +1,163 @@
|
|||||||
|
package com.agileboot.admin.customize.service.login;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.agileboot.common.constant.Constants.Token;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode;
|
||||||
|
import com.agileboot.domain.common.cache.RedisCacheService;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.MalformedJwtException;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.SignatureException;
|
||||||
|
import io.jsonwebtoken.UnsupportedJwtException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token验证处理
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@Data
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TokenService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义令牌标识
|
||||||
|
*/
|
||||||
|
@Value("${token.header}")
|
||||||
|
private String header;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌秘钥
|
||||||
|
*/
|
||||||
|
@Value("${token.secret}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动刷新token的时间,当过期时间不足autoRefreshTime的值的时候,会触发刷新用户登录缓存的时间
|
||||||
|
* 比如这个值是20, 用户是8点登录的, 8点半缓存会过期, 当过8.10分的时候,就少于20分钟了,便触发
|
||||||
|
* 刷新登录用户的缓存时间
|
||||||
|
*/
|
||||||
|
@Value("${token.autoRefreshTime}")
|
||||||
|
private long autoRefreshTime;
|
||||||
|
|
||||||
|
private final RedisCacheService redisCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户身份信息
|
||||||
|
*
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
public SystemLoginUser getLoginUser(HttpServletRequest request) {
|
||||||
|
// 获取请求携带的令牌
|
||||||
|
String token = getTokenFromRequest(request);
|
||||||
|
if (StrUtil.isNotEmpty(token)) {
|
||||||
|
try {
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
// 解析对应的权限以及用户信息
|
||||||
|
String uuid = (String) claims.get(Token.LOGIN_USER_KEY);
|
||||||
|
|
||||||
|
return redisCache.loginUserCache.getObjectOnlyInCacheById(uuid);
|
||||||
|
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException jwtException) {
|
||||||
|
log.error("parse token failed.", jwtException);
|
||||||
|
throw new ApiException(jwtException, ErrorCode.Client.INVALID_TOKEN);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("fail to get cached user from redis", e);
|
||||||
|
throw new ApiException(e, ErrorCode.Client.TOKEN_PROCESS_FAILED, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建令牌
|
||||||
|
*
|
||||||
|
* @param loginUser 用户信息
|
||||||
|
* @return 令牌
|
||||||
|
*/
|
||||||
|
public String createTokenAndPutUserInCache(SystemLoginUser loginUser) {
|
||||||
|
loginUser.setCachedKey(IdUtil.fastUUID());
|
||||||
|
|
||||||
|
redisCache.loginUserCache.set(loginUser.getCachedKey(), loginUser);
|
||||||
|
|
||||||
|
return generateToken(MapUtil.of(Token.LOGIN_USER_KEY, loginUser.getCachedKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当超过20分钟,自动刷新token
|
||||||
|
* @param loginUser 登录用户
|
||||||
|
*/
|
||||||
|
public void refreshToken(SystemLoginUser loginUser) {
|
||||||
|
long currentTime = System.currentTimeMillis();
|
||||||
|
if (currentTime > loginUser.getAutoRefreshCacheTime()) {
|
||||||
|
loginUser.setAutoRefreshCacheTime(currentTime + TimeUnit.MINUTES.toMillis(autoRefreshTime));
|
||||||
|
// 根据uuid将loginUser存入缓存
|
||||||
|
redisCache.loginUserCache.set(loginUser.getCachedKey(), loginUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从数据声明生成令牌
|
||||||
|
*
|
||||||
|
* @param claims 数据声明
|
||||||
|
* @return 令牌
|
||||||
|
*/
|
||||||
|
private String generateToken(Map<String, Object> claims) {
|
||||||
|
return Jwts.builder()
|
||||||
|
.setClaims(claims)
|
||||||
|
.signWith(SignatureAlgorithm.HS512, secret).compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从令牌中获取数据声明
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 数据声明
|
||||||
|
*/
|
||||||
|
private Claims parseToken(String token) {
|
||||||
|
return Jwts.parser()
|
||||||
|
.setSigningKey(secret)
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从令牌中获取用户名
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 用户名
|
||||||
|
*/
|
||||||
|
private String getUsernameFromToken(String token) {
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
return claims.getSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求token
|
||||||
|
*
|
||||||
|
* @return token
|
||||||
|
*/
|
||||||
|
private String getTokenFromRequest(HttpServletRequest request) {
|
||||||
|
String token = request.getHeader(header);
|
||||||
|
if (StrUtil.isNotEmpty(token) && token.startsWith(Token.PREFIX)) {
|
||||||
|
token = StrUtil.stripIgnoreCase(token, Token.PREFIX, null);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
package com.agileboot.admin.customize.service.login;
|
||||||
|
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.infrastructure.user.web.RoleInfo;
|
||||||
|
import com.agileboot.infrastructure.user.web.DataScopeEnum;
|
||||||
|
import com.agileboot.common.enums.common.UserStatusEnum;
|
||||||
|
import com.agileboot.common.enums.BasicEnumUtil;
|
||||||
|
import com.agileboot.domain.system.menu.db.SysMenuEntity;
|
||||||
|
import com.agileboot.domain.system.role.db.SysRoleEntity;
|
||||||
|
import com.agileboot.domain.system.user.db.SysUserEntity;
|
||||||
|
import com.agileboot.domain.system.menu.db.SysMenuService;
|
||||||
|
import com.agileboot.domain.system.role.db.SysRoleService;
|
||||||
|
import com.agileboot.domain.system.user.db.SysUserService;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义加载用户信息通过用户名
|
||||||
|
* 用于SpringSecurity 登录流程
|
||||||
|
* 没有办法把这个类 放进loginService中 会在SecurityConfig中造成循环依赖
|
||||||
|
* @see com.agileboot.infrastructure.config.SecurityConfig#filterChain(HttpSecurity)
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||||
|
|
||||||
|
private final SysUserService userService;
|
||||||
|
|
||||||
|
private final SysMenuService menuService;
|
||||||
|
|
||||||
|
private final SysRoleService roleService;
|
||||||
|
|
||||||
|
private final TokenService tokenService;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
SysUserEntity userEntity = userService.getUserByUserName(username);
|
||||||
|
if (userEntity == null) {
|
||||||
|
log.info("登录用户:{} 不存在.", username);
|
||||||
|
throw new ApiException(ErrorCode.Business.USER_NON_EXIST, username);
|
||||||
|
}
|
||||||
|
if (!Objects.equals(UserStatusEnum.NORMAL.getValue(), userEntity.getStatus())) {
|
||||||
|
log.info("登录用户:{} 已被停用.", username);
|
||||||
|
throw new ApiException(ErrorCode.Business.USER_IS_DISABLE, username);
|
||||||
|
}
|
||||||
|
|
||||||
|
RoleInfo roleInfo = getRoleInfo(userEntity.getRoleId(), userEntity.getIsAdmin());
|
||||||
|
|
||||||
|
SystemLoginUser loginUser = new SystemLoginUser(userEntity.getUserId(), userEntity.getIsAdmin(), userEntity.getUsername(),
|
||||||
|
userEntity.getPassword(), roleInfo);
|
||||||
|
loginUser.fillLoginInfo();
|
||||||
|
loginUser.setAutoRefreshCacheTime(loginUser.getLoginInfo().getLoginTime()
|
||||||
|
+ TimeUnit.MINUTES.toMillis(tokenService.getAutoRefreshTime()));
|
||||||
|
return loginUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RoleInfo getRoleInfo(Long roleId, boolean isAdmin) {
|
||||||
|
if (roleId == null) {
|
||||||
|
return RoleInfo.EMPTY_ROLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAdmin) {
|
||||||
|
LambdaQueryWrapper<SysMenuEntity> menuQuery = Wrappers.lambdaQuery();
|
||||||
|
menuQuery.select(SysMenuEntity::getMenuId);
|
||||||
|
List<SysMenuEntity> allMenus = menuService.list(menuQuery);
|
||||||
|
|
||||||
|
Set<Long> allMenuIds = allMenus.stream().map(SysMenuEntity::getMenuId).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
return new RoleInfo(RoleInfo.ADMIN_ROLE_ID, RoleInfo.ADMIN_ROLE_KEY, DataScopeEnum.ALL,
|
||||||
|
RoleInfo.ADMIN_PERMISSIONS, allMenuIds);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SysRoleEntity roleEntity = roleService.getById(roleId);
|
||||||
|
|
||||||
|
if (roleEntity == null) {
|
||||||
|
return RoleInfo.EMPTY_ROLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SysMenuEntity> menuList = roleService.getMenuListByRoleId(roleId);
|
||||||
|
|
||||||
|
Set<Long> menuIds = menuList.stream().map(SysMenuEntity::getMenuId).collect(Collectors.toSet());
|
||||||
|
Set<String> permissions = menuList.stream().map(SysMenuEntity::getPermission).collect(Collectors.toSet());
|
||||||
|
|
||||||
|
DataScopeEnum dataScopeEnum = BasicEnumUtil.fromValue(DataScopeEnum.class, roleEntity.getDataScope());
|
||||||
|
|
||||||
|
return new RoleInfo(roleId, roleEntity.getRoleKey(), dataScopeEnum, permissions, menuIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
package com.agileboot.admin.customize.service.login.command;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录对象
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LoginCommand {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户密码
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码
|
||||||
|
*/
|
||||||
|
private String captchaCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 唯一标识
|
||||||
|
*/
|
||||||
|
private String captchaCodeKey;
|
||||||
|
|
||||||
|
}
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
package com.agileboot.admin.customize.service.login.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CaptchaDTO {
|
||||||
|
|
||||||
|
private Boolean isCaptchaOn;
|
||||||
|
private String captchaCodeKey;
|
||||||
|
private String captchaCodeImg;
|
||||||
|
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package com.agileboot.admin.customize.service.login.dto;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.DictionaryData;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ConfigDTO {
|
||||||
|
|
||||||
|
private Boolean isCaptchaOn;
|
||||||
|
|
||||||
|
private Boolean isRegisterUserOn;
|
||||||
|
|
||||||
|
private Map<String, List<DictionaryData>> dictionary;
|
||||||
|
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
package com.agileboot.admin.customize.service.permission;
|
||||||
|
|
||||||
|
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
|
||||||
|
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.DefaultDataPermissionChecker;
|
||||||
|
import com.agileboot.admin.customize.service.permission.model.checker.OnlySelfDataPermissionChecker;
|
||||||
|
import com.agileboot.infrastructure.user.web.DataScopeEnum;
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限检测器工厂
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class DataPermissionCheckerFactory {
|
||||||
|
private static AbstractDataPermissionChecker allChecker;
|
||||||
|
private static AbstractDataPermissionChecker onlySelfChecker;
|
||||||
|
private static AbstractDataPermissionChecker defaultSelfChecker;
|
||||||
|
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initAllChecker() {
|
||||||
|
allChecker = new AllDataPermissionChecker();
|
||||||
|
onlySelfChecker = new OnlySelfDataPermissionChecker();
|
||||||
|
defaultSelfChecker = new DefaultDataPermissionChecker();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static AbstractDataPermissionChecker getChecker(SystemLoginUser loginUser) {
|
||||||
|
if (loginUser == null) {
|
||||||
|
return defaultSelfChecker;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginUser.getRoleInfo() == null || loginUser.getRoleInfo().getDataScope() == null) {
|
||||||
|
return defaultSelfChecker;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataScopeEnum dataScope = loginUser.getRoleInfo().getDataScope();
|
||||||
|
switch (dataScope) {
|
||||||
|
case ALL:
|
||||||
|
return allChecker;
|
||||||
|
case ONLY_SELF:
|
||||||
|
return onlySelfChecker;
|
||||||
|
default:
|
||||||
|
return defaultSelfChecker;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
package com.agileboot.admin.customize.service.permission;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import com.agileboot.admin.customize.service.permission.model.AbstractDataPermissionChecker;
|
||||||
|
import com.agileboot.admin.customize.service.permission.model.DataCondition;
|
||||||
|
import com.agileboot.infrastructure.user.AuthenticationUtils;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.domain.system.user.db.SysUserEntity;
|
||||||
|
import com.agileboot.domain.system.user.db.SysUserService;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限校验服务
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Service("dataScope")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DataPermissionService {
|
||||||
|
|
||||||
|
private final SysUserService userService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过userId 校验当前用户 对 目标用户是否有操作权限
|
||||||
|
*
|
||||||
|
* @param userId 用户id
|
||||||
|
* @return 检验结果
|
||||||
|
*/
|
||||||
|
public boolean checkUserId(Long userId) {
|
||||||
|
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
SysUserEntity targetUser = userService.getById(userId);
|
||||||
|
if (targetUser == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return checkDataScope(loginUser, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过userId 校验当前用户 对 目标用户是否有操作权限
|
||||||
|
* @param userIds 用户id列表
|
||||||
|
* @return 校验结果
|
||||||
|
*/
|
||||||
|
public boolean checkUserIds(List<Long> userIds) {
|
||||||
|
if (CollUtil.isNotEmpty(userIds)) {
|
||||||
|
for (Long userId : userIds) {
|
||||||
|
boolean checkResult = checkUserId(userId);
|
||||||
|
if (!checkResult) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkDataScope(SystemLoginUser loginUser, Long targetUserId) {
|
||||||
|
DataCondition dataCondition = DataCondition.builder().targetUserId(targetUserId).build();
|
||||||
|
AbstractDataPermissionChecker checker = DataPermissionCheckerFactory.getChecker(loginUser);
|
||||||
|
return checker.check(loginUser, dataCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package com.agileboot.admin.customize.service.permission;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.agileboot.infrastructure.user.AuthenticationUtils;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.infrastructure.user.web.RoleInfo;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Service("permission")
|
||||||
|
public class MenuPermissionService {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用户是否具备某权限
|
||||||
|
*
|
||||||
|
* @param permission 权限字符串
|
||||||
|
* @return 用户是否具备某权限
|
||||||
|
*/
|
||||||
|
public boolean has(String permission) {
|
||||||
|
if (StrUtil.isEmpty(permission)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser();
|
||||||
|
if (loginUser == null || CollUtil.isEmpty(loginUser.getRoleInfo().getMenuPermissions())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return has(loginUser.getRoleInfo().getMenuPermissions(), permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否包含权限
|
||||||
|
*
|
||||||
|
* @param permissions 权限列表
|
||||||
|
* @param permission 权限字符串
|
||||||
|
* @return 用户是否具备某权限
|
||||||
|
*/
|
||||||
|
private boolean has(Set<String> permissions, String permission) {
|
||||||
|
return permissions.contains(RoleInfo.ALL_PERMISSIONS) || permissions.contains(StrUtil.trim(permission));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
package com.agileboot.admin.customize.service.permission.model;
|
||||||
|
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限测试接口
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public abstract class AbstractDataPermissionChecker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测当前用户对于 给定条件的数据 是否有权限
|
||||||
|
*
|
||||||
|
* @param loginUser 登录用户
|
||||||
|
* @param condition 条件
|
||||||
|
* @return 校验结果
|
||||||
|
*/
|
||||||
|
public abstract boolean check(SystemLoginUser loginUser, DataCondition condition);
|
||||||
|
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package com.agileboot.admin.customize.service.permission.model;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author valarchie
|
||||||
|
* 供 DataPermissionChecker使用的 数据条件
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class DataCondition {
|
||||||
|
|
||||||
|
private Long targetUserId;
|
||||||
|
|
||||||
|
}
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
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 lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限测试接口
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class AllDataPermissionChecker extends AbstractDataPermissionChecker {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
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 lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限测试接口
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class DefaultDataPermissionChecker extends AbstractDataPermissionChecker {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
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 java.util.Objects;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据权限测试接口
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class OnlySelfDataPermissionChecker extends AbstractDataPermissionChecker {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean check(SystemLoginUser loginUser, DataCondition condition) {
|
||||||
|
if (condition == null || loginUser == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginUser.getUserId() == null || condition.getTargetUserId() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long currentUserId = loginUser.getUserId();
|
||||||
|
Long targetUserId = condition.getTargetUserId();
|
||||||
|
return Objects.equals(currentUserId, targetUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
# 数据源配置
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
type: com.alibaba.druid.pool.DruidDataSource
|
||||||
|
driverClassName: com.mysql.cj.jdbc.Driver
|
||||||
|
druid:
|
||||||
|
webStatFilter:
|
||||||
|
enabled: true
|
||||||
|
statViewServlet:
|
||||||
|
enabled: true
|
||||||
|
# 设置白名单,不填则允许所有访问
|
||||||
|
allow:
|
||||||
|
url-pattern: /druid/*
|
||||||
|
# 控制台管理用户名和密码
|
||||||
|
login-username: agileboot
|
||||||
|
login-password: 123456
|
||||||
|
filter:
|
||||||
|
stat:
|
||||||
|
enabled: true
|
||||||
|
# 慢SQL记录
|
||||||
|
log-slow-sql: true
|
||||||
|
slow-sql-millis: 1000
|
||||||
|
merge-sql: true
|
||||||
|
wall:
|
||||||
|
config:
|
||||||
|
multi-statement-allow: true
|
||||||
|
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://localhost:3306/todo_agileboot_pure?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||||
|
username: root
|
||||||
|
password: root123
|
||||||
|
# 从库数据源
|
||||||
|
# slave:
|
||||||
|
# url: jdbc:mysql://localhost:3306/agileboot2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||||
|
# username: root
|
||||||
|
# password: root123
|
||||||
|
|
||||||
|
# redis 配置
|
||||||
|
redis:
|
||||||
|
# 地址
|
||||||
|
host: localhost
|
||||||
|
# 端口,默认为6379
|
||||||
|
port: 6379
|
||||||
|
# 数据库索引
|
||||||
|
database: 0
|
||||||
|
# 密码
|
||||||
|
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-dev
|
||||||
|
|
||||||
|
|
||||||
|
springdoc:
|
||||||
|
swagger-ui:
|
||||||
|
# ***注意*** 开启Swagger UI界面 **安全考虑的话生产环境需要关掉**
|
||||||
|
# 因为knife4j的一些配置不灵活 所以重新改回springdoc+swagger的组合 真实开发的时候 使用apifox这种工具效率更高
|
||||||
|
enabled: true
|
||||||
|
url: ${agileboot.api-prefix}/v3/api-docs
|
||||||
|
config-url: ${agileboot.api-prefix}/v3/api-docs/swagger-config
|
||||||
|
|
||||||
|
|
||||||
|
# 项目相关配置
|
||||||
|
agileboot:
|
||||||
|
# 文件基路径 示例(Linux配置 /home/agileboot)
|
||||||
|
file-base-dir: /home/agileboot
|
||||||
|
# 前端url请求转发前缀
|
||||||
|
api-prefix: /dev-api
|
||||||
|
demo-enabled: false
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
# 数据源配置
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
# 驱动
|
||||||
|
driver-class-name: org.h2.Driver
|
||||||
|
dynamic:
|
||||||
|
primary: master
|
||||||
|
strict: false
|
||||||
|
datasource:
|
||||||
|
master:
|
||||||
|
# h2 内存数据库 内存模式连接配置 库名: agileboot
|
||||||
|
url: jdbc:h2:mem:agileboot;DB_CLOSE_DELAY=-1;MODE=MySQL
|
||||||
|
h2:
|
||||||
|
# 开启console 访问 默认false
|
||||||
|
console:
|
||||||
|
enabled: true
|
||||||
|
settings:
|
||||||
|
# 开启h2 console 跟踪 方便调试 默认 false
|
||||||
|
trace: true
|
||||||
|
# 允许console 远程访问 默认false
|
||||||
|
web-allow-others: true
|
||||||
|
# h2 访问路径上下文
|
||||||
|
path: /h2-console
|
||||||
|
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
platform: mysql
|
||||||
|
# 初始化数据
|
||||||
|
schema-locations: classpath:h2sql/agileboot_schema.sql
|
||||||
|
data-locations: classpath:h2sql/agileboot_data.sql
|
||||||
|
|
||||||
|
# redis 配置
|
||||||
|
redis:
|
||||||
|
# 地址
|
||||||
|
host: localhost
|
||||||
|
# 端口,默认为6379
|
||||||
|
port: 36379
|
||||||
|
# 数据库索引
|
||||||
|
database: 0
|
||||||
|
# 连接超时时间
|
||||||
|
timeout: 10s
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
# 连接池中的最小空闲连接
|
||||||
|
min-idle: 0
|
||||||
|
# 连接池中的最大空闲连接
|
||||||
|
max-idle: 8
|
||||||
|
# 连接池的最大数据库连接数
|
||||||
|
max-active: 8
|
||||||
|
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||||
|
max-wait: -1ms
|
||||||
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# 开发环境配置
|
||||||
|
server:
|
||||||
|
# 服务器的HTTP端口,默认为8080
|
||||||
|
port: 8080
|
||||||
|
servlet:
|
||||||
|
# 应用的访问路径
|
||||||
|
context-path: /
|
||||||
|
tomcat:
|
||||||
|
# tomcat的URI编码
|
||||||
|
uri-encoding: UTF-8
|
||||||
|
# 连接数满后的排队数,默认为100
|
||||||
|
accept-count: 1000
|
||||||
|
threads:
|
||||||
|
# tomcat最大线程数,默认为200
|
||||||
|
max: 800
|
||||||
|
# Tomcat启动初始化的线程数,默认值10
|
||||||
|
min-spare: 100
|
||||||
|
|
||||||
|
|
||||||
|
# Spring配置 如果需要无Mysql 无Redis直接启动的话 dev改为test
|
||||||
|
# 生产环境把dev改为prod
|
||||||
|
spring:
|
||||||
|
profiles:
|
||||||
|
active: basic,dev
|
||||||
|
|
||||||
|
# 如果需要无Mysql 无Redis直接启动的话 可以将这两个参数置为true, 并且spring.profile.active: dev换成test
|
||||||
|
# redis的端口可能会被占用,如果被占用请自己修改一下端口号
|
||||||
|
agileboot:
|
||||||
|
embedded:
|
||||||
|
mysql: false
|
||||||
|
redis: false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
groups:
|
||||||
|
enabled: true
|
||||||
|
group-configs:
|
||||||
|
- group: '公共API'
|
||||||
|
packages-to-scan: com.agileboot.admin.controller.common
|
||||||
|
- group: '内置系统API'
|
||||||
|
packages-to-scan: com.agileboot.admin.controller.system
|
||||||
|
|
||||||
|
|
||||||
+41
@@ -0,0 +1,41 @@
|
|||||||
|
package com.agileboot.admin.config;
|
||||||
|
|
||||||
|
|
||||||
|
import com.agileboot.admin.AgileBootAdminApplication;
|
||||||
|
import com.agileboot.common.config.AgileBootConfig;
|
||||||
|
import com.agileboot.common.constant.Constants.UploadSubDir;
|
||||||
|
import java.io.File;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import org.junit.jupiter.api.Assertions;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
@SpringBootTest(classes = AgileBootAdminApplication.class)
|
||||||
|
public class AgileBootConfigTest {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private AgileBootConfig config;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConfig() {
|
||||||
|
String fileBaseDir = "/home/agileboot/profile";
|
||||||
|
|
||||||
|
Assertions.assertEquals("AgileBoot", config.getName());
|
||||||
|
Assertions.assertEquals("1.8.0", config.getVersion());
|
||||||
|
Assertions.assertEquals("2022", config.getCopyrightYear());
|
||||||
|
Assertions.assertFalse(config.isDemoEnabled());
|
||||||
|
Assertions.assertEquals(fileBaseDir, AgileBootConfig.getFileBaseDir());
|
||||||
|
Assertions.assertFalse(AgileBootConfig.isAddressEnabled());
|
||||||
|
Assertions.assertEquals("math", AgileBootConfig.getCaptchaType());
|
||||||
|
Assertions.assertEquals("math", AgileBootConfig.getCaptchaType());
|
||||||
|
Assertions.assertEquals(fileBaseDir + "/import",
|
||||||
|
AgileBootConfig.getFileBaseDir() + File.separator + UploadSubDir.IMPORT_PATH);
|
||||||
|
Assertions.assertEquals(fileBaseDir + "/avatar",
|
||||||
|
AgileBootConfig.getFileBaseDir() + File.separator + UploadSubDir.AVATAR_PATH);
|
||||||
|
Assertions.assertEquals(fileBaseDir + "/download",
|
||||||
|
AgileBootConfig.getFileBaseDir() + File.separator + UploadSubDir.DOWNLOAD_PATH);
|
||||||
|
Assertions.assertEquals(fileBaseDir + "/upload",
|
||||||
|
AgileBootConfig.getFileBaseDir() + File.separator + UploadSubDir.UPLOAD_PATH);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
package com.agileboot.admin.customize.service.permission;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import com.agileboot.admin.customize.service.permission.model.checker.OnlySelfDataPermissionChecker;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import com.agileboot.admin.customize.service.permission.model.DataCondition;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class OnlySelfDataPermissionCheckerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCheckWhenParameterNull() {
|
||||||
|
OnlySelfDataPermissionChecker checker = new OnlySelfDataPermissionChecker();
|
||||||
|
|
||||||
|
boolean check1 = checker.check(null, null);
|
||||||
|
boolean check2 = checker.check(new SystemLoginUser(), null);
|
||||||
|
boolean check3 = checker.check(null, new DataCondition());
|
||||||
|
boolean check4 = checker.check(new SystemLoginUser(), new DataCondition());
|
||||||
|
|
||||||
|
assertFalse(check1);
|
||||||
|
assertFalse(check2);
|
||||||
|
assertFalse(check3);
|
||||||
|
assertFalse(check4);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCheckWhenSameUserId() {
|
||||||
|
OnlySelfDataPermissionChecker checker = new OnlySelfDataPermissionChecker();
|
||||||
|
SystemLoginUser loginUser = new SystemLoginUser();
|
||||||
|
loginUser.setUserId(1L);
|
||||||
|
DataCondition dataCondition = new DataCondition();
|
||||||
|
dataCondition.setTargetUserId(1L);
|
||||||
|
|
||||||
|
boolean check = checker.check(loginUser, dataCondition);
|
||||||
|
|
||||||
|
assertTrue(check);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCheckWhenDifferentUserId() {
|
||||||
|
OnlySelfDataPermissionChecker checker = new OnlySelfDataPermissionChecker();
|
||||||
|
SystemLoginUser loginUser = new SystemLoginUser();
|
||||||
|
loginUser.setUserId(1L);
|
||||||
|
DataCondition dataCondition = new DataCondition();
|
||||||
|
dataCondition.setTargetUserId(2L);
|
||||||
|
|
||||||
|
boolean check = checker.check(loginUser, dataCondition);
|
||||||
|
|
||||||
|
assertFalse(check);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>agileboot</artifactId>
|
||||||
|
<groupId>com.agileboot</groupId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>agileboot-api</artifactId>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
外部API
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- 核心模块-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.agileboot</groupId>
|
||||||
|
<artifactId>agileboot-infrastructure</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--使用undertow依赖-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-undertow</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 业务领域 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.agileboot</groupId>
|
||||||
|
<artifactId>agileboot-domain</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.agileboot.api;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动程序 定制banner.txt的网站
|
||||||
|
* <a href="http://patorjk.com/software/taag">http://patorjk.com/software/taag</a>
|
||||||
|
* <a href="http://www.network-science.de/ascii/">http://www.network-science.de/ascii/</a>
|
||||||
|
* <a href="http://www.degraeve.com/img2txt.php">http://www.degraeve.com/img2txt.php</a>
|
||||||
|
* <a href="http://life.chacuo.net/convertfont2char">http://life.chacuo.net/convertfont2char</a>
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
|
||||||
|
@ComponentScan(basePackages = "com.agileboot.*")
|
||||||
|
public class AgileBooApiApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AgileBooApiApplication.class, args);
|
||||||
|
String successMsg = " ____ _ _ __ _ _ \n"
|
||||||
|
+ " / ___| | |_ __ _ _ __ | |_ _ _ _ __ ___ _ _ ___ ___ ___ ___ ___ / _| _ _ | || |\n"
|
||||||
|
+ " \\___ \\ | __|/ _` || '__|| __| | | | || '_ \\ / __|| | | | / __|/ __|/ _ \\/ __|/ __|| |_ | | | || || |\n"
|
||||||
|
+ " ___) || |_| (_| || | | |_ | |_| || |_) | \\__ \\| |_| || (__| (__| __/\\__ \\\\__ \\| _|| |_| || ||_|\n"
|
||||||
|
+ " |____/ \\__|\\__,_||_| \\__| \\__,_|| .__/ |___/ \\__,_| \\___|\\___|\\___||___/|___/|_| \\__,_||_|(_)\n"
|
||||||
|
+ " |_| ";
|
||||||
|
|
||||||
|
System.out.println(successMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.agileboot.api.controller;
|
||||||
|
|
||||||
|
import com.agileboot.common.core.base.BaseController;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调度日志操作处理
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/order")
|
||||||
|
public class OrderController extends BaseController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问首页,提示语
|
||||||
|
*/
|
||||||
|
@RequestMapping("/")
|
||||||
|
public String index() {
|
||||||
|
return "暂无订单";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package com.agileboot.api.controller.app;
|
||||||
|
|
||||||
|
import com.agileboot.api.customize.service.JwtTokenService;
|
||||||
|
import com.agileboot.common.core.base.BaseController;
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调度日志操作处理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/app")
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AppController extends BaseController {
|
||||||
|
|
||||||
|
private final JwtTokenService jwtTokenService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问首页,提示语
|
||||||
|
*/
|
||||||
|
@PreAuthorize("hasAuthority('annie')")
|
||||||
|
@GetMapping("/list")
|
||||||
|
public ResponseDTO<?> appLogin() {
|
||||||
|
return ResponseDTO.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package com.agileboot.api.controller.common;
|
||||||
|
|
||||||
|
import cn.hutool.core.map.MapUtil;
|
||||||
|
import com.agileboot.api.customize.service.JwtTokenService;
|
||||||
|
import com.agileboot.common.core.base.BaseController;
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import java.util.Map;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调度日志操作处理
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/common")
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class LoginController extends BaseController {
|
||||||
|
|
||||||
|
private final JwtTokenService jwtTokenService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问首页,提示语
|
||||||
|
*/
|
||||||
|
@PostMapping("/app/{appId}/login")
|
||||||
|
public ResponseDTO<String> appLogin() {
|
||||||
|
String token = jwtTokenService.generateToken(MapUtil.of("token", "user1"));
|
||||||
|
return ResponseDTO.ok(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
package com.agileboot.api.customize.config;
|
||||||
|
|
||||||
|
import com.agileboot.api.customize.service.JwtTokenService;
|
||||||
|
import com.agileboot.infrastructure.user.app.AppLoginUser;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import java.io.IOException;
|
||||||
|
import javax.servlet.FilterChain;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token过滤器 验证token有效性
|
||||||
|
* 继承OncePerRequestFilter类的话 可以确保只执行filter一次, 避免执行多次
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private JwtTokenService jwtTokenService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
String tokenFromRequest = jwtTokenService.getTokenFromRequest(request);
|
||||||
|
|
||||||
|
if (tokenFromRequest != null) {
|
||||||
|
Claims claims = jwtTokenService.parseToken(tokenFromRequest);
|
||||||
|
String token = (String) claims.get("token");
|
||||||
|
// 根据token去查缓存里面 有没有对应的App用户
|
||||||
|
// 没有的话 再去数据库中查询
|
||||||
|
if (token != null && token.equals("user1")) {
|
||||||
|
AppLoginUser loginUser = new AppLoginUser(23232323L, false, "dasdsadsds");
|
||||||
|
loginUser.grantAppPermission("annie");
|
||||||
|
UsernamePasswordAuthenticationToken suer1 = new UsernamePasswordAuthenticationToken(loginUser, null,
|
||||||
|
loginUser.getAuthorities());
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(suer1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
+85
@@ -0,0 +1,85 @@
|
|||||||
|
package com.agileboot.api.customize.config;
|
||||||
|
|
||||||
|
import com.agileboot.common.core.dto.ResponseDTO;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode.Client;
|
||||||
|
import com.agileboot.common.utils.ServletHolderUtil;
|
||||||
|
import com.agileboot.common.utils.jackson.JacksonUtil;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主要配置登录流程逻辑涉及以下几个类
|
||||||
|
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
/**
|
||||||
|
* token认证过滤器
|
||||||
|
*/
|
||||||
|
private final JwtAuthenticationFilter jwtTokenFilter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 跨域过滤器
|
||||||
|
*/
|
||||||
|
private final CorsFilter corsFilter;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录异常处理类
|
||||||
|
* 用户未登陆的话 在这个Bean中处理
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public AuthenticationEntryPoint customAuthenticationEntryPoint() {
|
||||||
|
return (request, response, exception) -> {
|
||||||
|
ResponseDTO<Void> responseDTO = ResponseDTO.fail(
|
||||||
|
new ApiException(Client.COMMON_NO_AUTHORIZATION, request.getRequestURI())
|
||||||
|
);
|
||||||
|
ServletHolderUtil.renderString(response, JacksonUtil.to(responseDTO));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
|
||||||
|
httpSecurity.csrf().disable()
|
||||||
|
// 不配这个错误处理的话 会直接返回403
|
||||||
|
.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint())
|
||||||
|
.and()
|
||||||
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 禁用 session
|
||||||
|
.and()
|
||||||
|
.authorizeRequests()
|
||||||
|
.antMatchers("/common/**").permitAll()
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
.and()
|
||||||
|
// 禁用 X-Frame-Options 响应头。下面是具体解释:
|
||||||
|
// X-Frame-Options 是一个 HTTP 响应头,用于防止网页被嵌入到其他网页的 <frame>、<iframe> 或 <object> 标签中,从而可以减少点击劫持攻击的风险
|
||||||
|
.headers().frameOptions().disable()
|
||||||
|
.and()
|
||||||
|
.formLogin().disable();
|
||||||
|
|
||||||
|
httpSecurity.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
|
||||||
|
// 添加CORS filter
|
||||||
|
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationFilter.class);
|
||||||
|
|
||||||
|
|
||||||
|
return httpSecurity.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+127
@@ -0,0 +1,127 @@
|
|||||||
|
package com.agileboot.api.customize.service;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.agileboot.common.constant.Constants.Token;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode;
|
||||||
|
import com.agileboot.domain.common.cache.RedisCacheService;
|
||||||
|
import com.agileboot.infrastructure.user.web.SystemLoginUser;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import io.jsonwebtoken.MalformedJwtException;
|
||||||
|
import io.jsonwebtoken.SignatureAlgorithm;
|
||||||
|
import io.jsonwebtoken.SignatureException;
|
||||||
|
import io.jsonwebtoken.UnsupportedJwtException;
|
||||||
|
import java.util.Map;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* token验证处理
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
@Data
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class JwtTokenService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义令牌标识
|
||||||
|
*/
|
||||||
|
@Value("${token.header}")
|
||||||
|
private String header;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌秘钥
|
||||||
|
*/
|
||||||
|
@Value("${token.secret}")
|
||||||
|
private String secret;
|
||||||
|
|
||||||
|
private final RedisCacheService redisCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户身份信息
|
||||||
|
*
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
public SystemLoginUser getLoginUser(HttpServletRequest request) {
|
||||||
|
// 获取请求携带的令牌
|
||||||
|
String token = getTokenFromRequest(request);
|
||||||
|
if (StrUtil.isNotEmpty(token)) {
|
||||||
|
try {
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
// 解析对应的权限以及用户信息
|
||||||
|
String uuid = (String) claims.get(Token.LOGIN_USER_KEY);
|
||||||
|
|
||||||
|
return redisCache.loginUserCache.getObjectOnlyInCacheById(uuid);
|
||||||
|
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException jwtException) {
|
||||||
|
log.error("parse token failed.", jwtException);
|
||||||
|
throw new ApiException(jwtException, ErrorCode.Client.INVALID_TOKEN);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("fail to get cached user from redis", e);
|
||||||
|
throw new ApiException(e, ErrorCode.Client.TOKEN_PROCESS_FAILED, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从数据声明生成令牌
|
||||||
|
*
|
||||||
|
* @param claims 数据声明
|
||||||
|
* @return 令牌
|
||||||
|
*/
|
||||||
|
public String generateToken(Map<String, Object> claims) {
|
||||||
|
return Jwts.builder()
|
||||||
|
.setClaims(claims)
|
||||||
|
.signWith(SignatureAlgorithm.HS512, secret).compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从令牌中获取数据声明
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 数据声明
|
||||||
|
*/
|
||||||
|
public Claims parseToken(String token) {
|
||||||
|
return Jwts.parser()
|
||||||
|
.setSigningKey(secret)
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从令牌中获取用户名
|
||||||
|
*
|
||||||
|
* @param token 令牌
|
||||||
|
* @return 用户名
|
||||||
|
*/
|
||||||
|
private String getUsernameFromToken(String token) {
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
return claims.getSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求token
|
||||||
|
*
|
||||||
|
* @return token
|
||||||
|
*/
|
||||||
|
public String getTokenFromRequest(HttpServletRequest request) {
|
||||||
|
String token = request.getHeader(header);
|
||||||
|
if (StrUtil.isNotEmpty(token) && token.startsWith(Token.PREFIX)) {
|
||||||
|
token = StrUtil.stripIgnoreCase(token, Token.PREFIX, null);
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
package com.agileboot.api.customize.util;
|
||||||
|
|
||||||
|
public class ApiEncryptor {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# 开发环境配置
|
||||||
|
server:
|
||||||
|
# 服务器的HTTP端口,默认为8080
|
||||||
|
port: 8090
|
||||||
|
servlet:
|
||||||
|
# 应用的访问路径
|
||||||
|
context-path: /
|
||||||
|
tomcat:
|
||||||
|
# tomcat的URI编码
|
||||||
|
uri-encoding: UTF-8
|
||||||
|
# 连接数满后的排队数,默认为100
|
||||||
|
accept-count: 1000
|
||||||
|
threads:
|
||||||
|
# tomcat最大线程数,默认为200
|
||||||
|
max: 800
|
||||||
|
# Tomcat启动初始化的线程数,默认值10
|
||||||
|
min-spare: 100
|
||||||
|
|
||||||
|
|
||||||
|
# Spring配置
|
||||||
|
spring:
|
||||||
|
profiles:
|
||||||
|
active: basic,dev
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,189 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>agileboot</artifactId>
|
||||||
|
<groupId>com.agileboot</groupId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>agileboot-common</artifactId>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
common通用工具
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- Spring框架基本的核心工具 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-context-support</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- SpringWeb模块 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- spring security 安全认证 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 自定义验证注解 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!--常用工具类 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JSON工具类 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-databind</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- io常用工具类 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 文件上传工具类 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-fileupload</groupId>
|
||||||
|
<artifactId>commons-fileupload</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- excel工具 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.poi</groupId>
|
||||||
|
<artifactId>poi-ooxml</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- yml解析器 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Token生成与解析-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Jaxb -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- redis 缓存操作 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- pool 对象池 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-pool2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 解析客户端操作系统、浏览器等 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>eu.bitwalker</groupId>
|
||||||
|
<artifactId>UserAgentUtils</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- servlet包 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-annotations</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.module</groupId>
|
||||||
|
<artifactId>jackson-module-parameter-names</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.lionsoul</groupId>
|
||||||
|
<artifactId>ip2region</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>it.ozimov</groupId>
|
||||||
|
<artifactId>embedded-redis</artifactId>
|
||||||
|
<!-- 不排除掉slf4j的话 会冲突-->
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<!-- 排除掉guava依赖,以本项目的guava依赖为准 -->
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.junit.vintage</groupId>
|
||||||
|
<artifactId>junit-vintage-engine</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 多数据源 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- swagger注解 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.swagger</groupId>
|
||||||
|
<artifactId>swagger-annotations</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package com.agileboot.common.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义导出Excel数据注解
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.FIELD)
|
||||||
|
public @interface ExcelColumn {
|
||||||
|
|
||||||
|
String name() default "";
|
||||||
|
|
||||||
|
}
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
package com.agileboot.common.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface ExcelSheet {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sheet名称
|
||||||
|
*/
|
||||||
|
String name() default "";
|
||||||
|
|
||||||
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
package com.agileboot.common.config;
|
||||||
|
|
||||||
|
import com.agileboot.common.constant.Constants;
|
||||||
|
import java.io.File;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取项目相关配置
|
||||||
|
* TODO 移走 不合适放在这里common包底下
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "agileboot")
|
||||||
|
@Data
|
||||||
|
public class AgileBootConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版本
|
||||||
|
*/
|
||||||
|
private String version;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 版权年份
|
||||||
|
*/
|
||||||
|
private String copyrightYear;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实例演示开关
|
||||||
|
*/
|
||||||
|
private static boolean demoEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传路径
|
||||||
|
*/
|
||||||
|
private static String fileBaseDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取地址开关
|
||||||
|
*/
|
||||||
|
private static boolean addressEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证码类型
|
||||||
|
*/
|
||||||
|
private static String captchaType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rsa private key 静态属性的注入!! set方法一定不能是static 方法
|
||||||
|
*/
|
||||||
|
private static String rsaPrivateKey;
|
||||||
|
|
||||||
|
private static String apiPrefix;
|
||||||
|
|
||||||
|
public static String getFileBaseDir() {
|
||||||
|
return fileBaseDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFileBaseDir(String fileBaseDir) {
|
||||||
|
AgileBootConfig.fileBaseDir = fileBaseDir + File.separator + Constants.RESOURCE_PREFIX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getApiPrefix() {
|
||||||
|
return apiPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setApiPrefix(String apiDocsPathPrefix) {
|
||||||
|
AgileBootConfig.apiPrefix = apiDocsPathPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isAddressEnabled() {
|
||||||
|
return addressEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAddressEnabled(boolean addressEnabled) {
|
||||||
|
AgileBootConfig.addressEnabled = addressEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCaptchaType() {
|
||||||
|
return captchaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCaptchaType(String captchaType) {
|
||||||
|
AgileBootConfig.captchaType = captchaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getRsaPrivateKey() {
|
||||||
|
return rsaPrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRsaPrivateKey(String rsaPrivateKey) {
|
||||||
|
AgileBootConfig.rsaPrivateKey = rsaPrivateKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isDemoEnabled() {
|
||||||
|
return demoEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDemoEnabled(boolean demoEnabled) {
|
||||||
|
AgileBootConfig.demoEnabled = demoEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.agileboot.common.constant;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用常量信息
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
public class Constants {
|
||||||
|
private Constants() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final int KB = 1024;
|
||||||
|
|
||||||
|
public static final int MB = KB * 1024;
|
||||||
|
|
||||||
|
public static final int GB = MB * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http请求
|
||||||
|
*/
|
||||||
|
public static final String HTTP = "http://";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https请求
|
||||||
|
*/
|
||||||
|
public static final String HTTPS = "https://";
|
||||||
|
|
||||||
|
|
||||||
|
public static class Token {
|
||||||
|
|
||||||
|
private Token() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌前缀
|
||||||
|
*/
|
||||||
|
public static final String PREFIX = "Bearer ";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌前缀
|
||||||
|
*/
|
||||||
|
public static final String LOGIN_USER_KEY = "login_user_key";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Captcha {
|
||||||
|
|
||||||
|
private Captcha() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌
|
||||||
|
*/
|
||||||
|
public static final String MATH_TYPE = "math";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 令牌前缀
|
||||||
|
*/
|
||||||
|
public static final String CHAR_TYPE = "char";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源映射路径 前缀
|
||||||
|
*/
|
||||||
|
public static final String RESOURCE_PREFIX = "profile";
|
||||||
|
|
||||||
|
public static class UploadSubDir {
|
||||||
|
|
||||||
|
private UploadSubDir() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String IMPORT_PATH = "import";
|
||||||
|
|
||||||
|
public static final String AVATAR_PATH = "avatar";
|
||||||
|
|
||||||
|
public static final String DOWNLOAD_PATH = "download";
|
||||||
|
|
||||||
|
public static final String UPLOAD_PATH = "upload";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+40
@@ -0,0 +1,40 @@
|
|||||||
|
package com.agileboot.common.core.base;
|
||||||
|
|
||||||
|
import cn.hutool.core.date.DateUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import java.beans.PropertyEditorSupport;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.WebDataBinder;
|
||||||
|
import org.springframework.web.bind.annotation.InitBinder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class BaseController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* 将前台传递过来的日期格式的字符串,自动转化为Date类型
|
||||||
|
*/
|
||||||
|
@InitBinder
|
||||||
|
public void initBinder(WebDataBinder binder) {
|
||||||
|
// Date 类型转换
|
||||||
|
binder.registerCustomEditor(Date.class, new PropertyEditorSupport() {
|
||||||
|
@Override
|
||||||
|
public void setAsText(String text) {
|
||||||
|
setValue(DateUtil.parseDate(text));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面跳转
|
||||||
|
*/
|
||||||
|
public String redirect(String url) {
|
||||||
|
return StrUtil.format("redirect:{}", url);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.agileboot.common.core.base;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||||
|
import com.baomidou.mybatisplus.extension.activerecord.Model;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity基类
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public class BaseEntity<T extends Model<?>> extends Model<T> {
|
||||||
|
|
||||||
|
@ApiModelProperty("创建者ID")
|
||||||
|
@TableField(value = "creator_id", fill = FieldFill.INSERT)
|
||||||
|
private Long creatorId;
|
||||||
|
|
||||||
|
@ApiModelProperty("创建时间")
|
||||||
|
@TableField(value = "create_time", fill = FieldFill.INSERT)
|
||||||
|
private Date createTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("更新者ID")
|
||||||
|
@TableField(value = "updater_id", fill = FieldFill.UPDATE, updateStrategy = FieldStrategy.NOT_NULL)
|
||||||
|
private Long updaterId;
|
||||||
|
|
||||||
|
@ApiModelProperty("更新时间")
|
||||||
|
@TableField(value = "update_time", fill = FieldFill.UPDATE)
|
||||||
|
private Date updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deleted字段请在数据库中 设置为tinyInt 并且非null 默认值为0
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("删除标志(0代表存在 1代表删除)")
|
||||||
|
@TableField("deleted")
|
||||||
|
@TableLogic
|
||||||
|
private Boolean deleted;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.agileboot.common.core.dto;
|
||||||
|
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应信息主体
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ResponseDTO<T> {
|
||||||
|
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
private String msg;
|
||||||
|
|
||||||
|
@JsonInclude
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
public static <T> ResponseDTO<T> ok() {
|
||||||
|
return build(null, ErrorCode.SUCCESS.code(), ErrorCode.SUCCESS.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseDTO<T> ok(T data) {
|
||||||
|
return build(data, ErrorCode.SUCCESS.code(), ErrorCode.SUCCESS.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseDTO<T> fail() {
|
||||||
|
return build(null, ErrorCode.FAILED.code(), ErrorCode.FAILED.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseDTO<T> fail(T data) {
|
||||||
|
return build(data, ErrorCode.FAILED.code(), ErrorCode.FAILED.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseDTO<T> fail(ApiException exception) {
|
||||||
|
return build(null, exception.getErrorCode().code(), exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseDTO<T> fail(ApiException exception, T data) {
|
||||||
|
return build(data, exception.getErrorCode().code(), exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseDTO<T> build(T data, Integer code, String msg) {
|
||||||
|
return new ResponseDTO<>(code, msg, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 去掉直接填充错误码的方式, 这种方式不能拿到i18n的错误消息 统一通过ApiException来构造错误消息
|
||||||
|
// public static <T> ResponseDTO<T> fail(ErrorCodeInterface code, Object... args) {
|
||||||
|
// return build(null, code, args);
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package com.agileboot.common.core.page;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import javax.validation.constraints.Max;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Data
|
||||||
|
public abstract class AbstractPageQuery<T> extends AbstractQuery<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大分页页数
|
||||||
|
*/
|
||||||
|
public static final int MAX_PAGE_NUM = 200;
|
||||||
|
/**
|
||||||
|
* 单页最大大小
|
||||||
|
*/
|
||||||
|
public static final int MAX_PAGE_SIZE = 500;
|
||||||
|
/**
|
||||||
|
* 默认分页页数
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_PAGE_NUM = 1;
|
||||||
|
/**
|
||||||
|
* 默认分页大小
|
||||||
|
*/
|
||||||
|
public static final int DEFAULT_PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
@Max(MAX_PAGE_NUM)
|
||||||
|
protected Integer pageNum;
|
||||||
|
@Max(MAX_PAGE_SIZE)
|
||||||
|
protected Integer pageSize;
|
||||||
|
|
||||||
|
public Page<T> toPage() {
|
||||||
|
pageNum = ObjectUtil.defaultIfNull(pageNum, DEFAULT_PAGE_NUM);
|
||||||
|
pageSize = ObjectUtil.defaultIfNull(pageSize, DEFAULT_PAGE_SIZE);
|
||||||
|
return new Page<>(pageNum, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+90
@@ -0,0 +1,90 @@
|
|||||||
|
package com.agileboot.common.core.page;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.agileboot.common.utils.time.DatePickUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
|
||||||
|
import java.util.Date;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 如果是简单的排序 和 时间范围筛选 可以使用内置的这几个字段
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public abstract class AbstractQuery<T> {
|
||||||
|
|
||||||
|
protected String orderColumn;
|
||||||
|
|
||||||
|
protected String orderDirection;
|
||||||
|
|
||||||
|
protected String timeRangeColumn;
|
||||||
|
|
||||||
|
@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd")
|
||||||
|
private Date beginTime;
|
||||||
|
|
||||||
|
@JsonFormat(shape = Shape.STRING, pattern = "yyyy-MM-dd")
|
||||||
|
private Date endTime;
|
||||||
|
|
||||||
|
private static final String ASC = "ascending";
|
||||||
|
private static final String DESC = "descending";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成query conditions
|
||||||
|
*
|
||||||
|
* @return 添加条件后的QueryWrapper
|
||||||
|
*/
|
||||||
|
public QueryWrapper<T> toQueryWrapper() {
|
||||||
|
QueryWrapper<T> queryWrapper = addQueryCondition();
|
||||||
|
addSortCondition(queryWrapper);
|
||||||
|
addTimeCondition(queryWrapper);
|
||||||
|
|
||||||
|
return queryWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract QueryWrapper<T> addQueryCondition();
|
||||||
|
|
||||||
|
public void addSortCondition(QueryWrapper<T> queryWrapper) {
|
||||||
|
if (queryWrapper == null || StrUtil.isEmpty(orderColumn)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean sortDirection = convertSortDirection();
|
||||||
|
if (sortDirection != null) {
|
||||||
|
queryWrapper.orderBy(StrUtil.isNotEmpty(orderColumn), sortDirection,
|
||||||
|
StrUtil.toUnderlineCase(orderColumn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTimeCondition(QueryWrapper<T> queryWrapper) {
|
||||||
|
if (queryWrapper != null
|
||||||
|
&& StrUtil.isNotEmpty(this.timeRangeColumn)) {
|
||||||
|
queryWrapper
|
||||||
|
.ge(beginTime != null, StrUtil.toUnderlineCase(timeRangeColumn),
|
||||||
|
DatePickUtil.getBeginOfTheDay(beginTime))
|
||||||
|
.le(endTime != null, StrUtil.toUnderlineCase(timeRangeColumn), DatePickUtil.getEndOfTheDay(endTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取前端传来的排序方向 转换成MyBatisPlus所需的排序参数 boolean=isAsc
|
||||||
|
* @return 排序顺序, null为无排序
|
||||||
|
*/
|
||||||
|
public Boolean convertSortDirection() {
|
||||||
|
Boolean isAsc = null;
|
||||||
|
if (StrUtil.isEmpty(this.orderDirection)) {
|
||||||
|
return isAsc;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ASC.equals(this.orderDirection)) {
|
||||||
|
isAsc = true;
|
||||||
|
}
|
||||||
|
if (DESC.equals(this.orderDirection)) {
|
||||||
|
isAsc = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isAsc;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.agileboot.common.core.page;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页模型类
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class PageDTO<T> {
|
||||||
|
/**
|
||||||
|
* 总记录数
|
||||||
|
*/
|
||||||
|
private Long total;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 列表数据
|
||||||
|
*/
|
||||||
|
private List<T> rows;
|
||||||
|
|
||||||
|
public PageDTO(List<T> list) {
|
||||||
|
this.rows = list;
|
||||||
|
this.total = (long) list.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageDTO(Page<T> page) {
|
||||||
|
this.rows = page.getRecords();
|
||||||
|
this.total = page.getTotal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PageDTO(List<T> list, Long count) {
|
||||||
|
this.rows = list;
|
||||||
|
this.total = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.agileboot.common.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author valarchie
|
||||||
|
* 普通的枚举 接口
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
public interface BasicEnum<T>{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取枚举的值
|
||||||
|
* @return 枚举值
|
||||||
|
*/
|
||||||
|
T getValue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取枚举的描述
|
||||||
|
* @return 描述
|
||||||
|
*/
|
||||||
|
String description();
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.agileboot.common.enums;
|
||||||
|
|
||||||
|
import cn.hutool.core.convert.Convert;
|
||||||
|
import com.agileboot.common.exception.ApiException;
|
||||||
|
import com.agileboot.common.exception.error.ErrorCode;
|
||||||
|
import com.agileboot.common.enums.BasicEnum;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
public class BasicEnumUtil {
|
||||||
|
|
||||||
|
private BasicEnumUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final String UNKNOWN = "未知";
|
||||||
|
|
||||||
|
public static <E extends Enum<E>> E fromValueSafely(Class<E> enumClass, Object value) {
|
||||||
|
E target = null;
|
||||||
|
|
||||||
|
for (E enumConstant : enumClass.getEnumConstants()) {
|
||||||
|
BasicEnum<?> basicEnum = (BasicEnum<?>) enumConstant;
|
||||||
|
if (Objects.equals(basicEnum.getValue(), value)) {
|
||||||
|
target = (E) basicEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends Enum<E>> E fromValue(Class<E> enumClass, Object value) {
|
||||||
|
E target = null;
|
||||||
|
|
||||||
|
for (E enumConstant : enumClass.getEnumConstants()) {
|
||||||
|
BasicEnum basicEnum = (BasicEnum) enumConstant;
|
||||||
|
if (Objects.equals(basicEnum.getValue(), value)) {
|
||||||
|
target = (E) basicEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
throw new ApiException(ErrorCode.Internal.GET_ENUM_FAILED, enumClass.getSimpleName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends Enum<E>> String getDescriptionByBool(Class<E> enumClass, Boolean bool) {
|
||||||
|
Integer value = Convert.toInt(bool, 0);
|
||||||
|
return getDescriptionByValue(enumClass, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <E extends Enum<E>> String getDescriptionByValue(Class<E> enumClass, Object value) {
|
||||||
|
E basicEnum = fromValueSafely(enumClass, value);
|
||||||
|
if (basicEnum != null) {
|
||||||
|
return ((BasicEnum<?>) basicEnum).description();
|
||||||
|
}
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.agileboot.common.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型 接口
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
public interface DictionaryEnum<T> extends BasicEnum<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取css标签
|
||||||
|
* @return css标签
|
||||||
|
*/
|
||||||
|
String cssTag();
|
||||||
|
|
||||||
|
}
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.CssTag;
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对应sys_operation_log的business_type
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Dictionary(name = "sysOperationLog.businessType")
|
||||||
|
public enum BusinessTypeEnum implements DictionaryEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作类型
|
||||||
|
*/
|
||||||
|
OTHER(0, "其他操作", CssTag.INFO),
|
||||||
|
ADD(1, "添加", CssTag.PRIMARY),
|
||||||
|
MODIFY(2, "修改", CssTag.PRIMARY),
|
||||||
|
DELETE(3, "删除", CssTag.DANGER),
|
||||||
|
GRANT(4, "授权", CssTag.PRIMARY),
|
||||||
|
EXPORT(5, "导出", CssTag.WARNING),
|
||||||
|
IMPORT(6, "导入", CssTag.WARNING),
|
||||||
|
FORCE_LOGOUT(7, "强退", CssTag.DANGER),
|
||||||
|
CLEAN(8, "清空", CssTag.DANGER),
|
||||||
|
;
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
private final String cssTag;
|
||||||
|
|
||||||
|
BusinessTypeEnum(int value, String description, String cssTag) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
this.cssTag = cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String cssTag() {
|
||||||
|
return cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.BasicEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统配置,对应 sys_config 表的 config_key 字段。
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
public enum ConfigKeyEnum implements BasicEnum<String> {
|
||||||
|
|
||||||
|
INIT_PASSWORD("sys.user.initPassword", "初始密码"),
|
||||||
|
CAPTCHA("sys.account.captchaOnOff", "验证码开关"),
|
||||||
|
REGISTER("sys.account.registerUser", "注册开放功能");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
ConfigKeyEnum(String value, String description) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.CssTag;
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对应sys_user的sex字段
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Dictionary(name = "sysUser.sex")
|
||||||
|
public enum GenderEnum implements DictionaryEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户性别
|
||||||
|
*/
|
||||||
|
MALE(1, "男", CssTag.PRIMARY),
|
||||||
|
FEMALE(2, "女", CssTag.PRIMARY),
|
||||||
|
UNKNOWN(0, "未知", CssTag.PRIMARY);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
private final String cssTag;
|
||||||
|
|
||||||
|
GenderEnum(int value, String description, String cssTag) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
this.cssTag = cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String cssTag() {
|
||||||
|
return cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.CssTag;
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户状态
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
// TODO 表记得改成LoginLog
|
||||||
|
@Dictionary(name = "sysLoginLog.status")
|
||||||
|
public enum LoginStatusEnum implements DictionaryEnum<Integer> {
|
||||||
|
/**
|
||||||
|
* status of user
|
||||||
|
*/
|
||||||
|
LOGIN_SUCCESS(1, "登录成功", CssTag.SUCCESS),
|
||||||
|
LOGOUT(2, "退出成功", CssTag.INFO),
|
||||||
|
REGISTER(3, "注册", CssTag.PRIMARY),
|
||||||
|
LOGIN_FAIL(0, "登录失败", CssTag.DANGER);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String msg;
|
||||||
|
private final String cssTag;
|
||||||
|
|
||||||
|
LoginStatusEnum(int status, String msg, String cssTag) {
|
||||||
|
this.value = status;
|
||||||
|
this.msg = msg;
|
||||||
|
this.cssTag = cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String cssTag() {
|
||||||
|
return cssTag;
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.BasicEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public enum MenuComponentEnum implements BasicEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单组件类型
|
||||||
|
*/
|
||||||
|
LAYOUT(1,"Layout"),
|
||||||
|
PARENT_VIEW(2,"ParentView"),
|
||||||
|
INNER_LINK(3,"InnerLink");
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
MenuComponentEnum(int value, String description) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
}
|
||||||
+38
@@ -0,0 +1,38 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.BasicEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author valarchie
|
||||||
|
* 对应 sys_menu表的menu_type字段
|
||||||
|
*/
|
||||||
|
public enum MenuTypeEnum implements BasicEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单类型
|
||||||
|
*/
|
||||||
|
MENU(1, "页面"),
|
||||||
|
CATALOG(2, "目录"),
|
||||||
|
IFRAME(3, "内嵌Iframe"),
|
||||||
|
OUTSIDE_LINK_REDIRECT(4, "外链跳转");
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
MenuTypeEnum(int value, String description) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.CssTag;
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对应sys_notice的 status字段
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Dictionary(name = "sysNotice.status")
|
||||||
|
public enum NoticeStatusEnum implements DictionaryEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知状态
|
||||||
|
*/
|
||||||
|
OPEN(1, "正常", CssTag.PRIMARY),
|
||||||
|
CLOSE(0, "关闭", CssTag.DANGER);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
private final String cssTag;
|
||||||
|
|
||||||
|
NoticeStatusEnum(int value, String description, String cssTag) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
this.cssTag = cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String cssTag() {
|
||||||
|
return cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.CssTag;
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对应sys_notice的 notice_type字段
|
||||||
|
* 名称一般由对应的表名.字段构成
|
||||||
|
* 全局的话使用common作为表名
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Dictionary(name = "sysNotice.noticeType")
|
||||||
|
public enum NoticeTypeEnum implements DictionaryEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通知类型
|
||||||
|
*/
|
||||||
|
NOTIFICATION(1, "通知", CssTag.WARNING),
|
||||||
|
ANNOUNCEMENT(2, "公告", CssTag.SUCCESS);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
private final String cssTag;
|
||||||
|
|
||||||
|
NoticeTypeEnum(int value, String description, String cssTag) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
this.cssTag = cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String cssTag() {
|
||||||
|
return cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+45
@@ -0,0 +1,45 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.CssTag;
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对应sys_operation_log的status字段
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Dictionary(name = "sysOperationLog.status")
|
||||||
|
public enum OperationStatusEnum implements DictionaryEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作状态
|
||||||
|
*/
|
||||||
|
SUCCESS(1, "成功", CssTag.PRIMARY),
|
||||||
|
FAIL(0, "失败", CssTag.DANGER);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
private final String cssTag;
|
||||||
|
|
||||||
|
OperationStatusEnum(int value, String description, String cssTag) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
this.cssTag = cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String cssTag() {
|
||||||
|
return cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
import com.agileboot.common.enums.BasicEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作者类型
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Dictionary(name = "sysOperationLog.operatorType")
|
||||||
|
public enum OperatorTypeEnum implements BasicEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单类型
|
||||||
|
*/
|
||||||
|
OTHER(1, "其他"),
|
||||||
|
WEB(2, "Web用户"),
|
||||||
|
MOBILE(3, "手机端用户");
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
OperatorTypeEnum(int value, String description) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+39
@@ -0,0 +1,39 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.BasicEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Http Method
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
public enum RequestMethodEnum implements BasicEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 菜单类型
|
||||||
|
*/
|
||||||
|
GET(1, "GET"),
|
||||||
|
POST(2, "POST"),
|
||||||
|
PUT(3, "PUT"),
|
||||||
|
DELETE(4, "DELETE"),
|
||||||
|
UNKNOWN(-1, "UNKNOWN");
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
RequestMethodEnum(int value, String description) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.CssTag;
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 除非表有特殊指明的话,一般用这个枚举代表 status字段
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Dictionary(name = "common.status")
|
||||||
|
public enum StatusEnum implements DictionaryEnum<Integer> {
|
||||||
|
/**
|
||||||
|
* 开关状态
|
||||||
|
*/
|
||||||
|
ENABLE(1, "正常", CssTag.PRIMARY),
|
||||||
|
DISABLE(0, "停用", CssTag.DANGER);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
private final String cssTag;
|
||||||
|
|
||||||
|
StatusEnum(int value, String description, String cssTag) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
this.cssTag = cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String cssTag() {
|
||||||
|
return cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.CssTag;
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对应sys_user的status字段
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Dictionary(name = "sysUser.status")
|
||||||
|
public enum UserStatusEnum implements DictionaryEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户账户状态
|
||||||
|
*/
|
||||||
|
NORMAL(1, "正常", CssTag.PRIMARY),
|
||||||
|
DISABLED(2, "禁用", CssTag.DANGER),
|
||||||
|
FROZEN(3, "冻结", CssTag.WARNING);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
private final String cssTag;
|
||||||
|
|
||||||
|
UserStatusEnum(int value, String description, String cssTag) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
this.cssTag = cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return this.description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String cssTag() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.dictionary.CssTag;
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对应sys_menu表的is_visible字段
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Dictionary(name = "sysMenu.isVisible")
|
||||||
|
public enum VisibleStatusEnum implements DictionaryEnum<Integer> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示与否
|
||||||
|
*/
|
||||||
|
SHOW(1, "显示", CssTag.PRIMARY),
|
||||||
|
HIDE(0, "隐藏", CssTag.DANGER);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
private final String cssTag;
|
||||||
|
|
||||||
|
VisibleStatusEnum(int value, String description, String cssTag) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
this.cssTag = cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String cssTag() {
|
||||||
|
return cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
package com.agileboot.common.enums.common;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
import com.agileboot.common.enums.dictionary.CssTag;
|
||||||
|
import com.agileboot.common.enums.dictionary.Dictionary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统内代表是与否的枚举
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Dictionary(name = "common.yesOrNo")
|
||||||
|
public enum YesOrNoEnum implements DictionaryEnum<Integer> {
|
||||||
|
/**
|
||||||
|
* 是与否
|
||||||
|
*/
|
||||||
|
YES(1, "是", CssTag.PRIMARY),
|
||||||
|
NO(0, "否", CssTag.DANGER);
|
||||||
|
|
||||||
|
private final int value;
|
||||||
|
private final String description;
|
||||||
|
private final String cssTag;
|
||||||
|
|
||||||
|
YesOrNoEnum(int value, String description, String cssTag) {
|
||||||
|
this.value = value;
|
||||||
|
this.description = description;
|
||||||
|
this.cssTag = cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String cssTag() {
|
||||||
|
return cssTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
package com.agileboot.common.enums.dictionary;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Css 样式
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
public class CssTag {
|
||||||
|
|
||||||
|
public static final String PRIMARY = "";
|
||||||
|
public static final String DANGER = "danger";
|
||||||
|
public static final String WARNING = "warning";
|
||||||
|
public static final String SUCCESS = "success";
|
||||||
|
public static final String INFO = "info";
|
||||||
|
|
||||||
|
private CssTag() {
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
@@ -0,0 +1,25 @@
|
|||||||
|
package com.agileboot.common.enums.dictionary;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型注解
|
||||||
|
*
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Target({ElementType.TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
public @interface Dictionary {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型名称
|
||||||
|
*/
|
||||||
|
String name() default "";
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
package com.agileboot.common.enums.dictionary;
|
||||||
|
|
||||||
|
import com.agileboot.common.enums.DictionaryEnum;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典模型类
|
||||||
|
* @author valarchie
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DictionaryData {
|
||||||
|
|
||||||
|
private String label;
|
||||||
|
private Integer value;
|
||||||
|
private String cssTag;
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public DictionaryData(DictionaryEnum enumType) {
|
||||||
|
if (enumType != null) {
|
||||||
|
this.label = enumType.description();
|
||||||
|
this.value = (Integer) enumType.getValue();
|
||||||
|
this.cssTag = enumType.cssTag();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user