diff --git a/ReadMe.md b/ReadMe.md index 082f176..34d4220 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -8,6 +8,22 @@ > > **Pure-admin文档**:https://pure-admin.github.io/pure-admin-doc +> [!TIP] +> +> 项目中有一个默认管理员,数据库中用户`id`是`1`: +> +> 用户名:`Administrator` +> +> 密码:`admin123` + +> [!WARNING] +> +> 如果刚初始化登录的时候,发现管理员【`Administrator`】密码错误。 +> +> 找到数据库`sys_user`,将`Administrator`替换成下面的密码。 +> +> `$2a$10$h5BUwmMaVcEuu7Bz0TPPy.PQV8JP6CFJlbHTgT78G1s0YPIu2kfXe` + ## 视频说明地址 **介绍视频视频** @@ -38,8 +54,6 @@ ## ✨ v4.0.0 重大更新 -新分支`sysn_6.0.0`与上游【Pure Admin】合并,旧版放在`master-v1`中,最新的`sysn_6.0.0`放在`dev`中。 - ### 核心改进 - **全面重构**:后端接口、实体类等重构,前端重构部分j+优化操作体验 @@ -77,10 +91,10 @@ 通过 `WebSecurityConfig` 配置 -| 路径类型 | 示例 | 访问要求 | 配置方式 | -|------|-------------------|------|--------------------| +| 路径类型 | 示例 | 访问要求 | 配置方式 | +| -------- | ----------------- | -------- | ------------------------- | | 公开接口 | `/api/public/**` | 无需认证 | 路径包含 `public` 关键字 | -| 私有接口 | `/api/private/**` | 需登录 | 路径包含 `private` 关键字 | +| 私有接口 | `/api/private/**` | 需登录 | 路径包含 `private` 关键字 | ### 路径匹配策略 @@ -94,6 +108,21 @@ http.authorizeHttpRequests(auth -> auth ); ``` +### Maven工程结构 + +```mermaid +graph TD + +父工程 -->|主项目| auth-api +父工程 -->|代码生成器| generator-code +auth-api -->|启动项、控制器| service +service -->|mapper| dao +service -->|包含domain、配置等| auth-core +dao -->|包含domain、配置等| auth-code + + +``` + ## 🛠️ 应用场景 ### 1. 纯前端控制模式 @@ -144,10 +173,10 @@ http.authorizeHttpRequests(auth -> auth AntPath详情:https://juejin.cn/spost/7498247273660743732 -| 模式 | 示例 | 说明 | -|------|-----------------|------------| +| 模式 | 示例 | 说明 | +| -------- | --------------- | ---------------- | | 精确匹配 | `/api/user` | 完全匹配路径 | -| 通配符 | `/api/user/*` | 匹配单级路径 | +| 通配符 | `/api/user/*` | 匹配单级路径 | | 多级通配 | `/api/user/**` | 匹配多级路径 | | 方法限定 | `GET /api/user` | 匹配特定HTTP方法 | @@ -188,13 +217,12 @@ docker compose up -d ``` 2. **权限码设计**: -- 模块::操作 (如 `user::create`) -- 分层级设计 (如 `system:user:update`) + - 模块::操作 (如 `user::create`) + - 分层级设计 (如 `system:user:update`) 3. **批量操作**: - -- 使用 Excel/JSON 管理大量权限配置 -- 定期备份权限配置 + - 使用 Excel/JSON 管理大量权限配置 + - 定期备份权限配置 ## 🌟 项目优势 @@ -217,30 +245,33 @@ docker compose up -d - [ ] 用户设置持久化存储到数据库 - [ ] 权限弹窗页面优化 - [ ] 后端文档注释完善 -- [ ] 系统监控后端返回403停止请求 +- [x] 系统监控后端返回403停止请求 +- [ ] 优化用户配置权限逻辑,配置后热更新逻辑等 +- [ ] 完善后端注释,有需要添加ReadMe文档 +- [ ] Redis中获取活跃用户 ## 前后端接口规范 ### 前端示例规范 -| **操作** | **API 层** | **Pinia 层** | -|:-------|:--------------|:----------------| -| 查询单个 | `getUser` | `loadUser` | -| 查询列表 | `getUserList` | `loadUserList` | -| 分页查询 | `getUserPage` | `fetchUserPage` | -| 新增数据 | `createUser` | `addUser` | -| 更新数据 | `updateUser` | `editUser` | -| 删除数据 | `deleteUser` | `removeUser` | +| **操作** | **API 层** | **Pinia 层** | +| :------- | :------------ | :-------------- | +| 查询单个 | `getUser` | `loadUser` | +| 查询列表 | `getUserList` | `loadUserList` | +| 分页查询 | `getUserPage` | `fetchUserPage` | +| 新增数据 | `createUser` | `addUser` | +| 更新数据 | `updateUser` | `editUser` | +| 删除数据 | `deleteUser` | `removeUser` | ### 后端接口示例规范 遵循Restful | **操作** | **RESTful** | -|:-------|:----------------------------| -| 查询列表 | `GET /users` | -| 分页查询 | `GET /users/{page}/{limit}` | -| 查询单个 | `GET /users/{id}` | +| :------- | :-------------------------- | +| 查询列表 | `GET /users` | +| 分页查询 | `GET /users/{page}/{limit}` | +| 查询单个 | `GET /users/{id}` | | 新增 | `POST /users` | | 更新 | `PUT /users/{id}` | | 删除 | `DELETE /users/{id}` | diff --git a/src/App.vue b/src/App.vue index dcb315f..7948879 100644 --- a/src/App.vue +++ b/src/App.vue @@ -56,7 +56,7 @@ onMounted(() => { setI18n(); }); -onBeforeMount(() => { +onBeforeMount(async () => { const { version, name: title } = __APP_INFO__.pkg; const { VITE_PUBLIC_PATH, MODE } = import.meta.env; // https://github.com/guMcrey/version-rocket/blob/main/README.zh-CN.md#api diff --git a/src/api/service/mockRequest.ts b/src/api/service/mockRequest.ts index 8c45a71..63b6fbc 100644 --- a/src/api/service/mockRequest.ts +++ b/src/api/service/mockRequest.ts @@ -24,7 +24,7 @@ class PureHttp { private static retryOriginalRequest(config: PureHttpRequestConfig) { return new Promise((resolve) => { PureHttp.requests.push((token: string) => { - config.headers['token'] = formatToken(token); + config.headers['Authorization'] = formatToken(token); resolve(config); }); }); diff --git a/src/api/service/request.ts b/src/api/service/request.ts index 154faa4..19f5ff2 100644 --- a/src/api/service/request.ts +++ b/src/api/service/request.ts @@ -26,7 +26,8 @@ class PureHttp { private static retryOriginalRequest(config: PureHttpRequestConfig) { return new Promise((resolve) => { PureHttp.requests.push((token: string) => { - config.headers['token'] = formatToken(token); + // TODO Authorization + config.headers['Authorization'] = formatToken(token); resolve(config); }); }); @@ -97,7 +98,8 @@ class PureHttp { .then((res: any) => { // 从结果中获取token const token = res.data.token; - config.headers['token'] = formatToken(token); + // TODO Authorization + config.headers['Authorization'] = formatToken(token); PureHttp.requests.forEach((cb) => cb(token)); PureHttp.requests = []; }) @@ -107,7 +109,8 @@ class PureHttp { } resolve(PureHttp.retryOriginalRequest(config)); } else { - config.headers['token'] = formatToken(data.token); + // TODO Authorization + config.headers['Authorization'] = formatToken(data.token); resolve(config); } } else { diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 43baf0c..c62a361 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -105,7 +105,7 @@ export function removeToken() { /** 格式化token(jwt格式) */ export const formatToken = (token: string): string => { - return token; + return `Bearer ${token}`; }; /** 是否有按钮级别的权限(根据登录接口返回的`permissions`字段进行判断)*/ diff --git a/src/views/message-manger/message-editing/components/rich-editor.vue b/src/views/message-manger/message-editing/components/rich-editor.vue index cffbf78..279b5df 100644 --- a/src/views/message-manger/message-editing/components/rich-editor.vue +++ b/src/views/message-manger/message-editing/components/rich-editor.vue @@ -21,7 +21,8 @@ editorConfig.MENU_CONF['uploadImage'] = { // 选择文件时的类型限制,根据实际业务改写 allowedFileTypes: ['image/png', 'image/jpg', 'image/jpeg'], meta: { type: 'message' }, - headers: { token: token.value }, + // TODO 修改為 Authorization + headers: { Authorization: token.value }, // 自定义插入图片 customInsert(res: any, insertFn) { // res.data.url是后端返回的图片地址,根据实际业务改写 diff --git a/src/views/message-manger/message-send/components/rich-editor.vue b/src/views/message-manger/message-send/components/rich-editor.vue index fb6a518..c47c212 100644 --- a/src/views/message-manger/message-send/components/rich-editor.vue +++ b/src/views/message-manger/message-send/components/rich-editor.vue @@ -20,7 +20,8 @@ editorConfig.MENU_CONF['uploadImage'] = { // 选择文件时的类型限制,根据实际业务改写 allowedFileTypes: ['image/png', 'image/jpg', 'image/jpeg'], meta: { type: 'message' }, - headers: { token: token.value }, + // TODO 修改為 Authorization + headers: { Authorization: token.value }, // 自定义插入图片 customInsert(res: any, insertFn) { // res.data.url是后端返回的图片地址,根据实际业务改写 diff --git a/src/views/monitor/server/components/system-cpu.vue b/src/views/monitor/server/components/system-cpu.vue index 78ccea2..1f3f2ef 100644 --- a/src/views/monitor/server/components/system-cpu.vue +++ b/src/views/monitor/server/components/system-cpu.vue @@ -8,6 +8,7 @@ import { useIntervalFn } from '@vueuse/core'; import dayjs from 'dayjs'; import { fetchSystemCPU } from '@/api/v1/actuator'; import SystemCardItem from '@/components/ReCol/SystemCardItem.vue'; +import { message } from '@/utils/message'; const cupECharts = ref(); const myChart = ref(); @@ -18,6 +19,8 @@ const xSeriesData = ref([]); // 数据显示长度 const dateDisplayLength = ref(20); +const hasAuthority = ref(true); + const option = reactive({ tooltip: { trigger: 'axis', @@ -71,6 +74,18 @@ const onSearch = async () => { // 获取数据 const result = await fetchSystemCPU(); + + // 当前i请求是否可以继续 + if (result.code) { + if (result.code == 403) { + hasAuthority.value = false; + message('Access Denied'); + } + if (result.code != 200) { + hasAuthority.value = false; + } + } + const measurement = result.measurements[0]; if (measurement) { const value = measurement.value; @@ -91,8 +106,8 @@ onMounted(() => { onSearch(); - // 定时刷新 - useIntervalFn(() => onSearch(), 2000); + // 定时刷新,并且当前有权限才能继续请求 + useIntervalFn(() => hasAuthority.value && onSearch(), 2000); }); diff --git a/src/views/monitor/server/components/system-jvm-cpu.vue b/src/views/monitor/server/components/system-jvm-cpu.vue index 02ba075..52d9947 100644 --- a/src/views/monitor/server/components/system-jvm-cpu.vue +++ b/src/views/monitor/server/components/system-jvm-cpu.vue @@ -8,6 +8,7 @@ import { useIntervalFn } from '@vueuse/core'; import dayjs from 'dayjs'; import { fetchSystemProcessCPU } from '@/api/v1/actuator'; import SystemCardItem from '@/components/ReCol/SystemCardItem.vue'; +import { message } from '@/utils/message'; const jvmCPUECharts = ref(); const myChart = ref(); @@ -18,6 +19,9 @@ const xSeriesData = ref([]); // 数据显示长度 const dateDisplayLength = ref(20); +// 是否有权限继续访问 +const hasAuthority = ref(true); + const option = reactive({ tooltip: { trigger: 'axis', @@ -70,6 +74,18 @@ const onSearch = async () => { // 获取数据 const result = await fetchSystemProcessCPU(); + + // 当前i请求是否可以继续 + if (result.code) { + if (result.code == 403) { + hasAuthority.value = false; + message('Access Denied'); + } + if (result.code != 200) { + hasAuthority.value = false; + } + } + const measurement = result.measurements[0]; if (measurement) { const value = measurement.value; @@ -90,8 +106,8 @@ onMounted(() => { onSearch(); - // 定时刷新 - useIntervalFn(() => onSearch(), 2000); + // 定时刷新,并且当前有权限才能继续请求 + useIntervalFn(() => hasAuthority.value && onSearch(), 2000); }); diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index 3642348..b1d9e50 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -34,6 +34,7 @@ import Refresh from '~icons/ep/refresh'; import AddFill from '~icons/ri/add-circle-line'; import Upload from '~icons/ri/upload-line'; import PowersToRole from '@/views/system/role/components/powers-to-role.vue'; +import { useRouter } from 'vue-router'; defineOptions({ name: 'RoleManger' }); @@ -93,6 +94,7 @@ const onUpdateByFile = () => { }, }); }; +const router = useRouter(); onMounted(() => { onSearch(); diff --git a/src/views/welcome/index.vue b/src/views/welcome/index.vue index 3a32ffa..507768a 100644 --- a/src/views/welcome/index.vue +++ b/src/views/welcome/index.vue @@ -72,13 +72,13 @@ onMounted(() => { :xs="24" class="mb-[18px]" > - - + + - + - + @@ -135,26 +135,7 @@ onMounted(() => {