diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2a02a83 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,24 @@ +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 diff --git a/.env.example b/.env.example index 4ac59f0..166decf 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,8 @@ MYSQL_ROOT_PASSWORD=root123 -MYSQL_DATABASE=todo_agileboot_pure -MYSQL_APP_USERNAME=todo_app -MYSQL_APP_PASSWORD=todo_app123 +MYSQL_DATABASE=collab_ledger +MYSQL_APP_USERNAME=collab_ledger_app +MYSQL_APP_PASSWORD=collab_ledger_app123 MYSQL_PORT=3306 REDIS_PASSWORD=redis123 REDIS_PORT=6379 - -APP_PORT=8080 -WEB_PORT=8081 -JAVA_OPTS=-Xms256m -Xmx512m diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..f493682 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,13 @@ +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 diff --git a/.gitignore b/.gitignore index 86ead69..3774679 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Common .DS_Store /.env +/node_modules/ .idea/ *.iws *.iml @@ -37,6 +38,7 @@ /frontend/web/.vscode/launch.json # Backend build tools +/backend/node_modules/ /backend/.gradle/ /backend/build/ !/backend/gradle/wrapper/gradle-wrapper.jar diff --git a/frontend/.npmrc b/.npmrc similarity index 98% rename from frontend/.npmrc rename to .npmrc index 3b89c40..e2ad808 100644 --- a/frontend/.npmrc +++ b/.npmrc @@ -1,4 +1,3 @@ shamefully-hoist=true strict-peer-dependencies=false shell-emulator=true - diff --git a/frontend/.eslintignore b/.prettierignore similarity index 52% rename from frontend/.eslintignore rename to .prettierignore index e80a802..25ddeea 100644 --- a/frontend/.eslintignore +++ b/.prettierignore @@ -4,8 +4,8 @@ build .taro .cache coverage -*.d.ts +pnpm-lock.yaml public src/assets -web/public -web/src/assets +frontend/web/public +frontend/web/src/assets diff --git a/frontend/.prettierrc.cjs b/.prettierrc.cjs similarity index 100% rename from frontend/.prettierrc.cjs rename to .prettierrc.cjs diff --git a/.stylelintignore b/.stylelintignore new file mode 100644 index 0000000..efc9dc2 --- /dev/null +++ b/.stylelintignore @@ -0,0 +1,17 @@ +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 diff --git a/AGENTS.md b/AGENTS.md index 4f85cd9..a6dc5c5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,11 +2,10 @@ - 新增或修改代码前,必须阅读并遵循 `docs/clean-code-contract.md`。 - 后端新增或修改业务功能前,必须阅读 `docs/backend-feature-development.md`。 -- 后端代码必须遵循 `Controller -> ApplicationService -> Model -> db Service/Mapper` 的组织方式。 -- 不要把业务规则写在 Controller。 -- 不要让 Controller 直接调用 Mapper。 -- 不要直接把 Entity 或 DO 返回给前端。 -- 复杂查询结果对象可以使用 `XxxDO`,放在 `db` 包下,并由 ApplicationService 转换为 DTO。 +- 后端代码必须遵循 `Route -> Feature Service -> Prisma/Redis/shared Service` 的组织方式。 +- 不要把业务规则堆在 Route。 +- 不要在 Route 中直接扩散 Prisma 原始记录给前端。 +- 复杂查询结果由 Feature Service 转换为稳定 DTO。 - 字典类需求不要默认创建字典管理表;本项目现有字典数据使用 Enum 和缓存。 - `frontend/web` 新增或修改业务功能前,必须阅读 `docs/web-feature-development.md`。 - Web 前端接口必须通过 `@/utils/http` 封装调用,不要直接使用 Axios。 diff --git a/README.md b/README.md index 0d56a1b..c99e3c9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Simple Todo +# CollabLedger -`simple-todo` 是一个前后端分离的待办/后台管理模板项目。后端基于 AgileBoot 的 Spring Boot 分层架构改造,前端包含 Web 管理端和 Taro 多端应用,根目录提供 Docker Compose 用于本地依赖启动和生产部署。 +`CollabLedger` 是一个前后端分离的协作账本/后台管理项目。后端使用 Hono,前端包含 Web 管理端和 Taro 多端应用,根目录 Docker Compose 用于启动 MySQL 和 Redis。 ## 项目架构 @@ -8,37 +8,35 @@ 当前项目是在以下开源项目基础上改造: -- 后端原仓库: -- Web 前端原仓库: +- 后端原仓库: +- Web 前端原仓库: - Web UI 上游项目: ### 目录结构 ```text -simple-todo -├── backend # Spring Boot 后端 -│ ├── agileboot-admin # 后台管理接口启动模块 -│ ├── agileboot-api # 开放接口模块 -│ ├── agileboot-common # 通用工具与基础能力 -│ ├── agileboot-domain # 业务领域模块 -│ ├── agileboot-infrastructure # 基础设施、配置、集成能力 -│ └── sql # 初始化 SQL +CollabLedger +├── backend # Hono 后端 +│ ├── prisma # Prisma schema +│ ├── sql # MySQL 初始化 SQL +│ └── src # 后端源码 ├── frontend │ ├── web # Vite + Vue 3 Web 管理端 │ └── app # Taro + Vue 3 多端应用 ├── docs # 项目开发约定 -├── docker-compose.yml # MySQL、Redis、后端、Web 编排 +├── docker-compose.yml # MySQL、Redis 编排 └── .env.example # Docker Compose 环境变量示例 ``` ## 基础环境 -- JDK 8+ -- Maven 3.8+,也可以直接使用 `backend/mvnw` 或 `backend/mvnw.cmd` - Node.js 22+,推荐配合 Corepack 使用 pnpm -- pnpm,Dockerfile 中使用 `pnpm@11.1.3` +- pnpm 11.1.3+ - Docker Desktop 或 Docker Engine + Docker Compose +项目根目录使用 pnpm workspace 统一管理 `backend`、`frontend/web` 和 +`frontend/app`。依赖安装和常用脚本都在根目录执行。 + ## 开发启动步骤 ### 1. 准备环境变量 @@ -55,7 +53,7 @@ Windows PowerShell: Copy-Item .env.example .env ``` -`.env.example` 默认把 MySQL 和 Redis 映射到宿主机 `3306`、`6379`,与后端 `application-dev.yml` 的本地开发配置保持一致。 +`.env.example` 默认把 MySQL 和 Redis 映射到宿主机 `3306`、`6379`。 ### 2. 启动 MySQL 和 Redis @@ -63,7 +61,7 @@ Copy-Item .env.example .env docker compose up -d mysql redis ``` -首次启动 MySQL 容器时,Compose 会把 `backend/sql/agileboot.sql` 挂载到 `/docker-entrypoint-initdb.d/01-agileboot.sql`,由 MySQL 镜像自动初始化数据库。 +首次启动 MySQL 容器时,Compose 会把 `backend/sql/init.sql` 挂载到 `/docker-entrypoint-initdb.d/01-init.sql`,由 MySQL 镜像自动初始化数据库。 如果数据库卷已经存在,初始化 SQL 不会重复执行。需要重建本地数据时可以执行: @@ -74,40 +72,35 @@ docker compose up -d mysql redis `docker compose down -v` 会删除本地 MySQL 和 Redis 数据卷,请确认不需要保留本地数据后再执行。 -### 3. 启动后端 - -Windows PowerShell: - -```powershell -cd backend -.\mvnw.cmd -pl agileboot-admin spring-boot:run -``` - -macOS / Linux: +### 3. 安装依赖 ```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 +``` + +### 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 ``` @@ -124,25 +117,27 @@ Web 开发服务默认地址: http://localhost:80 ``` +开发环境下 `frontend/web/vite.config.ts` 已默认把 `/dev-api` 代理到 `http://localhost:3000`。 + 如果 80 端口被占用,可以修改 `frontend/web/.env.development` 中的 `VITE_PORT`。 -### 5. 启动 App 端,可选 +### 6. 启动 App 端,可选 ```bash -cd frontend pnpm dev:app:weapp ``` H5 模式: ```bash -cd frontend pnpm dev:app:h5 ``` +`frontend/app/config/dev.ts` 已默认把 `TARO_APP_API_BASE` 指向 `http://localhost:3000`。 + ## 部署步骤 -生产部署推荐使用根目录的 Docker Compose。它会启动 MySQL、Redis、后端服务和 Web Nginx 服务。 +Hono 后端和 Web 管理端推荐分别部署到 Vercel 或其他 Node.js/静态托管环境。根目录 Docker Compose 只负责启动 MySQL 和 Redis。 ### 1. 准备生产环境变量 @@ -154,79 +149,122 @@ cp .env.example .env ```env MYSQL_ROOT_PASSWORD=请替换为强密码 -MYSQL_DATABASE=todo_agileboot_pure -MYSQL_APP_USERNAME=todo_app +MYSQL_DATABASE=collab_ledger +MYSQL_APP_USERNAME=collab_ledger_app MYSQL_APP_PASSWORD=请替换为强密码 MYSQL_PORT=13306 REDIS_PASSWORD=请替换为强密码 REDIS_PORT=16379 - -APP_PORT=8080 -WEB_PORT=8081 -JAVA_OPTS=-Xms256m -Xmx512m ``` -生产环境建议把 `MYSQL_PORT`、`REDIS_PORT` 改成非默认宿主机端口,例如上面的 `13306`、`16379`。这两个变量只影响宿主机暴露端口,不影响容器内部端口;后端容器仍通过 Compose 内部网络访问 MySQL `3306` 和 Redis `6379`。 +生产环境建议把 `MYSQL_PORT`、`REDIS_PORT` 改成非默认宿主机端口,例如上面的 `13306`、`16379`。这两个变量只影响宿主机暴露端口,不影响容器内部端口。 -如果数据库和 Redis 只给后端容器使用,建议进一步通过服务器防火墙限制这些端口的外部访问,或按实际部署需要移除 `docker-compose.yml` 中 MySQL/Redis 的 `ports` 暴露配置。 +如果数据库和 Redis 只给后端使用,建议进一步通过服务器防火墙限制这些端口的外部访问,或按实际部署需要移除 `docker-compose.yml` 中 MySQL/Redis 的 `ports` 暴露配置。 不要提交真实生产 `.env` 文件。 -### 2. 构建并启动完整服务 +### 2. 启动数据库和 Redis ```bash -docker compose --profile prod up -d --build +docker compose up -d mysql redis ``` -Compose 会执行以下构建: +### 3. 使用 Vercel CLI 部署后端 -- `backend/Dockerfile`:使用 Maven 构建 `agileboot-admin`,运行 Spring Boot Jar。 -- `frontend/web/Dockerfile`:构建 Web 静态文件,并用 Nginx 提供访问。 +先全局安装并登录 Vercel CLI: -Web 容器中的 Nginx 会把 `/prod-api/` 代理到 Compose 内部的后端服务 `app:8080`。 +```bash +pnpm add -g vercel +vercel login +``` -### 3. 访问服务 +后端作为单独的 Vercel Project 部署,Root Directory 为 `backend`: -默认访问地址: +```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 -Web:http://localhost:8081 -后端:http://localhost:8080 +Install Command: pnpm install +Build Command: pnpm build +Output Directory: dist ``` -如果修改了 `.env` 中的 `WEB_PORT` 或 `APP_PORT`,以实际端口为准。 - -### 4. 常用运维命令 - -查看服务状态: +配置 Web 指向后端公网地址: ```bash -docker compose --profile prod ps +vercel env add VITE_APP_BASE_API production ``` -查看后端日志: +填入上一步得到的后端域名,例如 `https://your-backend.vercel.app`。 + +部署 Web: ```bash -docker compose --profile prod logs -f app +vercel deploy --prod +cd ../.. ``` -查看 Web 日志: +`frontend/web/vercel.json` 已配置前端路由回退,直接刷新管理端子路由时会返回 `index.html`。 + +### 5. 常用运维命令 + +查看数据库和 Redis 状态: ```bash -docker compose --profile prod logs -f web +docker compose ps ``` 停止服务: ```bash -docker compose --profile prod down +docker compose down ``` 停止并删除数据卷: ```bash -docker compose --profile prod down -v +docker compose down -v ``` `down -v` 会删除数据库和 Redis 数据,生产环境谨慎使用。 diff --git a/backend/.dockerignore b/backend/.dockerignore deleted file mode 100644 index f54f1e8..0000000 --- a/backend/.dockerignore +++ /dev/null @@ -1,12 +0,0 @@ -.git -.gitignore -.idea -*.iml -*.log -**/target -build -dist -.gradle -.mvn/wrapper/maven-wrapper.jar -.env -docker-compose.yml diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..ab14bf8 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,10 @@ +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="" diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..c8a7336 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,2 @@ +.vercel +.env*.local diff --git a/backend/.mvn/wrapper/maven-wrapper.jar b/backend/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index cb28b0e..0000000 Binary files a/backend/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/backend/.mvn/wrapper/maven-wrapper.properties b/backend/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index d36117e..0000000 --- a/backend/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,18 +0,0 @@ -# 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 diff --git a/backend/Dockerfile b/backend/Dockerfile deleted file mode 100644 index 6f8a122..0000000 --- a/backend/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -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"] diff --git a/backend/GoogleStyle.xml b/backend/GoogleStyle.xml deleted file mode 100644 index 1214d64..0000000 --- a/backend/GoogleStyle.xml +++ /dev/null @@ -1,569 +0,0 @@ - - - diff --git a/backend/LICENSE b/backend/LICENSE deleted file mode 100644 index 50fca70..0000000 --- a/backend/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -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. diff --git a/backend/README.md b/backend/README.md index 9ca2de4..b7660f0 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,344 +1,108 @@ +# Backend -

- Downloads - Build Status - Build Status - Downloads - - Downloads - - - Downloads - -

-

+这是 `CollabLedger` 的后端实现,按 Vercel 官方的 Hono 方式组织:入口放在 `src/index.ts`,默认导出 `app`,部署到 `Vercel Functions` 的 `Node.js runtime`,并保持现有前端 API 协议兼容。 -logo -

-

AgileBoot v2.0.0

-

基于SpringBoot+Vue3前后端分离的Java快速开发脚手架

-

-

+## 启动 -## ⚡平台简介⚡ - -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 +```bash +pnpm install +pnpm dev:backend ``` -#### 安装好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 ``` -#### 前端启动 -详细步骤请查看对应前端部分 - -``` -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. +`pnpm dev:backend` 使用直接的 Node 开发入口: +```bash +pnpm dev:backend ``` -详细过程在这个文章中:[AgileBoot - 手把手一步一步带你Run起全栈项目(SpringBoot+Vue3)](https://juejin.cn/post/7153812187834744845) +如果要按 Vercel 官方方式本地模拟,需要先全局安装 Vercel CLI: - -> 对于想要尝试全栈项目的前端人员,这边提供更简便的后端启动方式,无需配置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 add -g 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 +pnpm dev:backend:vercel ``` -### 代码流转 +## 环境变量 -请求分为两类:一类是查询,一类是操作(即对数据有进行更新)。 +先复制: -**查询**:Controller > xxxQuery > xxxApplicationService > xxxService(Db) > xxxMapper -**操作**: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 -- 应用服务(事务层,操作领域模型类完成业务逻辑) -└─ +```bash +cp .env.example .env ``` +最少需要配置: +- `DATABASE_URL` +- `REDIS_URL` +- `JWT_SECRET` +- `UPLOAD_DIR` ---- +## 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) -* 持续输出中 +后端作为独立的 Vercel Project 部署。先全局安装并登录 Vercel CLI: +```bash +pnpm add -g vercel +vercel login +``` +进入后端目录并关联项目: -## 🌻 注意事项 🌻 -- IDEA会自动将.properties文件的编码设置为ISO-8859-1,请在Settings > Editor > File Encodings > Properties Files > 设置为UTF-8 -- 请导入统一的代码格式化模板(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 +```bash +cd backend +vercel link +``` -## 🎬 AgileBoot全栈交流群 🎬 +配置生产环境变量: -QQ群: [![加入QQ群](https://img.shields.io/badge/1398880-blue.svg)](https://qm.qq.com/cgi-bin/qm/qr?k=TR5guoXS0HssErVWefmdFRirJvfpEvp1&jump_from=webapi&authKey=VkWMmVhp/pNdWuRD8sqgM+Sv2+Vy2qCJQSeLmeXlLtfER2RJBi6zL56PdcRlCmTs) 点击按钮入群。 +```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 内网地址。 -logo +部署: -## 💕 特别鸣谢 +```bash +vercel deploy --prod +``` +## 当前状态 -- @pokr 感谢提供ChatGpt账号助力本项目开发 +已包含: -## 💒 相关框架 -- 基于node.js开发的后端 Midwayjs +- Hono + Vercel 官方入口 +- 统一响应/错误协议 +- JWT + Redis 会话 +- 登录/注册/验证码/当前用户 +- 合作记录 CRUD +- 用户/角色/菜单/配置/公告/日志/监控接口骨架 + +仍需继续完善: + +- 动态路由 `/getRouters` +- 更完整的权限与数据范围控制 +- Excel 导出 +- 在线用户、Redis 监控细节 +- 上传文件持久化改造为对象存储或 Vercel Blob diff --git a/backend/agileboot-admin/pom.xml b/backend/agileboot-admin/pom.xml deleted file mode 100644 index d53826a..0000000 --- a/backend/agileboot-admin/pom.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - agileboot - com.agileboot - 1.0.0 - - jar - 4.0.0 - - agileboot-admin - - - web服务入口 - - - - - - - com.agileboot - agileboot-domain - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - - - - org.apache.maven.plugins - maven-surefire-plugin - ${maven.surefire.plugin.version} - - - false - - - - - - - - diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/AgileBootAdminApplication.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/AgileBootAdminApplication.java deleted file mode 100644 index 7fdfd4c..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/AgileBootAdminApplication.java +++ /dev/null @@ -1,32 +0,0 @@ -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); - } -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppAuthController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppAuthController.java deleted file mode 100644 index 20bf9f3..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppAuthController.java +++ /dev/null @@ -1,84 +0,0 @@ -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 getConfig() { - return ResponseDTO.ok(loginService.getConfig()); - } - - @Operation(summary = "小程序验证码") - @GetMapping("/captchaImage") - public ResponseDTO getCaptchaImg() { - return ResponseDTO.ok(loginService.generateCaptchaImg()); - } - - @Operation(summary = "小程序登录") - @PostMapping("/login") - public ResponseDTO login(@RequestBody LoginCommand command) { - String token = loginService.login(command); - return ResponseDTO.ok(buildTokenDTO(token)); - } - - @Operation(summary = "小程序注册") - @PostMapping("/register") - public ResponseDTO 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 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())); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppCollaborationRecordController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppCollaborationRecordController.java deleted file mode 100644 index c3e0044..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppCollaborationRecordController.java +++ /dev/null @@ -1,90 +0,0 @@ -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> list(CollaborationRecordQuery query) { - return ResponseDTO.ok(recordApplicationService.getRecordList(query)); - } - - @Operation(summary = "小程序合作记录详情") - @GetMapping("/{recordId}") - public ResponseDTO getInfo(@PathVariable @Positive Long recordId) { - return ResponseDTO.ok(recordApplicationService.getRecordInfo(recordId)); - } - - @Operation(summary = "小程序合作记录选项") - @GetMapping("/options") - public ResponseDTO> options() { - return ResponseDTO.ok(recordApplicationService.getOptions()); - } - - @Operation(summary = "小程序合作记录月度统计") - @GetMapping("/monthly-statistics") - public ResponseDTO> monthlyStatistics(@RequestParam Integer year) { - return ResponseDTO.ok(recordApplicationService.getMonthlyStatistics(year)); - } - - @Operation(summary = "小程序新增合作记录") - @PostMapping - public ResponseDTO add(@Valid @RequestBody AddCollaborationRecordCommand command) { - recordApplicationService.addRecord(command); - return ResponseDTO.ok(); - } - - @Operation(summary = "小程序修改合作记录") - @PutMapping - public ResponseDTO edit(@Valid @RequestBody UpdateCollaborationRecordCommand command) { - recordApplicationService.updateRecord(command); - return ResponseDTO.ok(); - } - - @Operation(summary = "小程序删除合作记录") - @DeleteMapping - public ResponseDTO remove(@RequestParam @NotNull @NotEmpty List ids) { - recordApplicationService.deleteRecord(new BulkOperationCommand<>(ids)); - return ResponseDTO.ok(); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppProfileController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppProfileController.java deleted file mode 100644 index c4ebde8..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/app/AppProfileController.java +++ /dev/null @@ -1,56 +0,0 @@ -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 profile() { - SystemLoginUser user = AuthenticationUtils.getSystemLoginUser(); - return ResponseDTO.ok(userApplicationService.getUserProfile(user.getUserId())); - } - - @Operation(summary = "小程序修改个人信息") - @PutMapping - public ResponseDTO updateProfile(@RequestBody UpdateProfileCommand command) { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - command.setUserId(loginUser.getUserId()); - userApplicationService.updateUserProfile(command); - return ResponseDTO.ok(); - } - - @Operation(summary = "小程序修改个人密码") - @PutMapping("/password") - public ResponseDTO updatePassword(@Validated @RequestBody UpdateUserPasswordCommand command) { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - command.setUserId(loginUser.getUserId()); - userApplicationService.updatePasswordBySelf(loginUser, command); - return ResponseDTO.ok(); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/collaboration/CollaborationRecordController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/collaboration/CollaborationRecordController.java deleted file mode 100644 index b0b4ee3..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/collaboration/CollaborationRecordController.java +++ /dev/null @@ -1,105 +0,0 @@ -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> list(CollaborationRecordQuery query) { - return ResponseDTO.ok(recordApplicationService.getRecordList(query)); - } - - @Operation(summary = "合作记录详情") - @PreAuthorize("@permission.has('collaboration:record:query')") - @GetMapping("/{recordId}") - public ResponseDTO getInfo(@PathVariable @Positive Long recordId) { - return ResponseDTO.ok(recordApplicationService.getRecordInfo(recordId)); - } - - @Operation(summary = "合作记录选项") - @PreAuthorize("@permission.has('collaboration:record:list')") - @GetMapping("/options") - public ResponseDTO> options() { - return ResponseDTO.ok(recordApplicationService.getOptions()); - } - - @Operation(summary = "合作记录月度统计") - @PreAuthorize("@permission.has('collaboration:record:statistics')") - @GetMapping("/monthly-statistics") - public ResponseDTO> 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 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 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 remove(@RequestParam @NotNull @NotEmpty List ids) { - recordApplicationService.deleteRecord(new BulkOperationCommand<>(ids)); - return ResponseDTO.ok(); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java deleted file mode 100644 index d6fce6b..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/FileController.java +++ /dev/null @@ -1,130 +0,0 @@ -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 fileDownload(String fileName, HttpServletResponse response) { - try { - if (!FileUploadUtils.isAllowDownload(fileName)) { - // 返回类型是ResponseEntity 不能捕获异常, 需要手动将错误填到 ResponseEntity - ResponseDTO 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 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> uploadFiles(@RequestParam("files") List files) { - if (CollUtil.isEmpty(files)) { - throw new ApiException(ErrorCode.Business.UPLOAD_FILE_IS_EMPTY); - } - - List 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); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java deleted file mode 100644 index 66b513e..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/common/LoginController.java +++ /dev/null @@ -1,152 +0,0 @@ -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 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 getCaptchaImg() { - CaptchaDTO captchaImg = loginService.generateCaptchaImg(); - return ResponseDTO.ok(captchaImg); - } - - /** - * 登录方法 - * - * @param loginCommand 登录信息 - * @return 结果 - */ - @Operation(summary = "登录") - @PostMapping("/login") - public ResponseDTO 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 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> getRouters() { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - List routerTree = menuApplicationService.getRouterTree(loginUser); - return ResponseDTO.ok(routerTree); - } - - - @Operation(summary = "注册接口") - @PostMapping("/register") - public ResponseDTO 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())); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java deleted file mode 100644 index a0d29cf..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/MonitorController.java +++ /dev/null @@ -1,82 +0,0 @@ -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 getRedisCacheInfo() { - RedisCacheInfoDTO redisCacheInfo = monitorApplicationService.getRedisCacheInfo(); - return ResponseDTO.ok(redisCacheInfo); - } - - - @Operation(summary = "服务器信息") - @PreAuthorize("@permission.has('monitor:server:list')") - @GetMapping("/serverInfo") - public ResponseDTO 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> onlineUsers(String ipAddress, String username) { - List 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 logoutOnlineUser(@PathVariable String tokenId) { - CacheCenter.loginUserCache.delete(tokenId); - return ResponseDTO.ok(); - } - - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java deleted file mode 100644 index 38072ab..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysConfigController.java +++ /dev/null @@ -1,88 +0,0 @@ -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> list(ConfigQuery query) { - PageDTO page = configApplicationService.getConfigList(query); - return ResponseDTO.ok(page); - } - - /** - * 根据参数编号获取详细信息 - */ - @PreAuthorize("@permission.has('system:config:query')") - @GetMapping(value = "/config/{configId}") - @Operation(summary = "配置信息", description = "配置的详细信息") - public ResponseDTO 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 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 refreshCache() { - CacheCenter.configCache.invalidateAll(); - return ResponseDTO.ok(); - } -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLogsController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLogsController.java deleted file mode 100644 index 741cc09..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysLogsController.java +++ /dev/null @@ -1,120 +0,0 @@ -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> loginInfoList(LoginLogQuery query) { - PageDTO 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 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 removeLoginInfos(@RequestParam @NotNull @NotEmpty List ids) { - logApplicationService.deleteLoginInfo(new BulkOperationCommand<>(ids)); - return ResponseDTO.ok(); - } - - @Operation(summary = "操作日志列表") - @PreAuthorize("@permission.has('monitor:operlog:list')") - @GetMapping("/operationLogs") - public ResponseDTO> operationLogs(OperationLogQuery query) { - PageDTO pageDTO = logApplicationService.getOperationLogList(query); - return ResponseDTO.ok(pageDTO); - } - -// @GetMapping("/download") -// public ResponseEntity 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 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 removeOperationLogs(@RequestParam List operationIds) { - logApplicationService.deleteOperationLog(new BulkOperationCommand<>(operationIds)); - return ResponseDTO.ok(); - } - - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java deleted file mode 100644 index 9469f7e..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysMenuController.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.agileboot.admin.controller.system; - -import cn.hutool.core.lang.tree.Tree; -import com.agileboot.common.core.base.BaseController; -import com.agileboot.common.core.dto.ResponseDTO; -import com.agileboot.domain.system.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> menuList(MenuQuery menuQuery) { - List menuList = menuApplicationService.getMenuList(menuQuery); - return ResponseDTO.ok(menuList); - } - - /** - * 根据菜单编号获取详细信息 - */ - @Operation(summary = "菜单详情") - @PreAuthorize("@permission.has('system:menu:query')") - @GetMapping(value = "/{menuId}") - public ResponseDTO menuInfo(@PathVariable @NotNull @PositiveOrZero Long menuId) { - MenuDetailDTO menu = menuApplicationService.getMenuInfo(menuId); - return ResponseDTO.ok(menu); - } - - /** - * 获取菜单下拉树列表 - */ - @Operation(summary = "菜单列表(树级)", description = "菜单树级下拉框") - @GetMapping("/dropdown") - public ResponseDTO>> dropdownList() { - SystemLoginUser loginUser = AuthenticationUtils.getSystemLoginUser(); - List> 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 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 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 remove(@PathVariable("menuId") Long menuId) { - menuApplicationService.remove(menuId); - return ResponseDTO.ok(); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java deleted file mode 100644 index 2e149c0..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysNoticeController.java +++ /dev/null @@ -1,122 +0,0 @@ -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> list(NoticeQuery query) { - PageDTO pageDTO = noticeApplicationService.getNoticeList(query); - return ResponseDTO.ok(pageDTO); - } - - /** - * 获取通知公告列表 - * 从从库获取数据 例子 仅供参考 - */ - @Operation(summary = "公告列表(从数据库从库获取)", description = "演示主从库的例子") - @DS("slave") - @PreAuthorize("@permission.has('system:notice:list')") - @GetMapping("/database/slave") - public ResponseDTO> listFromSlave(NoticeQuery query) { - PageDTO pageDTO = noticeApplicationService.getNoticeList(query); - return ResponseDTO.ok(pageDTO); - } - - /** - * 根据通知公告编号获取详细信息 - */ - @Operation(summary = "公告详情") - @PreAuthorize("@permission.has('system:notice:query')") - @GetMapping(value = "/{noticeId}") - public ResponseDTO 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 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 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 remove(@RequestParam List noticeIds) { - noticeApplicationService.deleteNotice(new BulkOperationCommand<>(noticeIds)); - return ResponseDTO.ok(); - } - - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java deleted file mode 100644 index 7a4a8a2..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysProfileController.java +++ /dev/null @@ -1,98 +0,0 @@ -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 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 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 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 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)); - } -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java deleted file mode 100644 index ba3e709..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysRoleController.java +++ /dev/null @@ -1,181 +0,0 @@ -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> list(RoleQuery query) { - PageDTO 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 pageDTO = roleApplicationService.getRoleList(query); - CustomExcelUtil.writeToResponse(pageDTO.getRows(), RoleDTO.class, response); - } - - /** - * 根据角色编号获取详细信息 - */ - @Operation(summary = "角色详情") - @PreAuthorize("@permission.has('system:role:query')") - @GetMapping(value = "/{roleId}") - public ResponseDTO 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 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 remove(@PathVariable("roleId") List roleIds) { - roleApplicationService.deleteRoleByBulk(roleIds); - return ResponseDTO.ok(); - } - - /** - * 修改保存角色 - */ - @Operation(summary = "修改角色") - @PreAuthorize("@permission.has('system:role:edit')") - @AccessLog(title = "角色管理", businessType = BusinessTypeEnum.MODIFY) - @PutMapping - public ResponseDTO 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 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> allocatedUserList(@PathVariable("roleId") Long roleId, - AllocatedRoleQuery query) { - query.setRoleId(roleId); - PageDTO page = roleApplicationService.getAllocatedUserList(query); - return ResponseDTO.ok(page); - } - - /** - * 查询未分配用户角色列表 - */ - @Operation(summary = "未关联该角色的用户列表") - @PreAuthorize("@permission.has('system:role:list')") - @GetMapping("/{roleId}/unallocated/list") - public ResponseDTO> unallocatedUserList(@PathVariable("roleId") Long roleId, - UnallocatedRoleQuery query) { - query.setRoleId(roleId); - PageDTO 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 deleteRoleOfUserByBulk(@PathVariable("userIds") List 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 addRoleForUserByBulk(@PathVariable("roleId") Long roleId, - @PathVariable("userIds") List userIds) { - roleApplicationService.addRoleOfUserByBulk(roleId, userIds); - return ResponseDTO.ok(); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java deleted file mode 100644 index 722bf2f..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/controller/system/SysUserController.java +++ /dev/null @@ -1,169 +0,0 @@ -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> userList(SearchUserQuery query) { - PageDTO 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 query) { - PageDTO 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 importUserByExcel(MultipartFile file) { - List 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 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 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 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 remove(@PathVariable List userIds) { - BulkOperationCommand 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 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 changeStatus(@PathVariable Long userId, @RequestBody ChangeStatusCommand command) { - command.setUserId(userId); - userApplicationService.changeUserStatus(command); - return ResponseDTO.ok(); - } - - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLog.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLog.java deleted file mode 100644 index a00cd7c..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLog.java +++ /dev/null @@ -1,45 +0,0 @@ -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; -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLogAspect.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLogAspect.java deleted file mode 100644 index 75145de..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/AccessLogAspect.java +++ /dev/null @@ -1,59 +0,0 @@ -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); - } - } - - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/OperationLogModel.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/OperationLogModel.java deleted file mode 100644 index b1d3da2..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/aop/accessLog/OperationLogModel.java +++ /dev/null @@ -1,160 +0,0 @@ -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; - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/async/AsyncTaskFactory.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/async/AsyncTaskFactory.java deleted file mode 100644 index a5f66c9..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/async/AsyncTaskFactory.java +++ /dev/null @@ -1,78 +0,0 @@ -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); - }; - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/JwtAuthenticationTokenFilter.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/JwtAuthenticationTokenFilter.java deleted file mode 100644 index 6fa0d86..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/JwtAuthenticationTokenFilter.java +++ /dev/null @@ -1,53 +0,0 @@ -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); - } - -} diff --git a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java b/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java deleted file mode 100644 index c793f58..0000000 --- a/backend/agileboot-admin/src/main/java/com/agileboot/admin/customize/config/SecurityConfig.java +++ /dev/null @@ -1,165 +0,0 @@ -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 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 响应头,用于防止网页被嵌入到其他网页的 、