Merge branch 'dev'
This commit is contained in:
commit
76c6be939e
83
ReadMe.md
83
ReadMe.md
|
@ -8,6 +8,22 @@
|
||||||
>
|
>
|
||||||
> **Pure-admin文档**:https://pure-admin.github.io/pure-admin-doc
|
> **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 重大更新
|
## ✨ v4.0.0 重大更新
|
||||||
|
|
||||||
新分支`sysn_6.0.0`与上游【Pure Admin】合并,旧版放在`master-v1`中,最新的`sysn_6.0.0`放在`dev`中。
|
|
||||||
|
|
||||||
### 核心改进
|
### 核心改进
|
||||||
|
|
||||||
- **全面重构**:后端接口、实体类等重构,前端重构部分j+优化操作体验
|
- **全面重构**:后端接口、实体类等重构,前端重构部分j+优化操作体验
|
||||||
|
@ -77,10 +91,10 @@
|
||||||
|
|
||||||
通过 `WebSecurityConfig` 配置
|
通过 `WebSecurityConfig` 配置
|
||||||
|
|
||||||
| 路径类型 | 示例 | 访问要求 | 配置方式 |
|
| 路径类型 | 示例 | 访问要求 | 配置方式 |
|
||||||
|------|-------------------|------|--------------------|
|
| -------- | ----------------- | -------- | ------------------------- |
|
||||||
| 公开接口 | `/api/public/**` | 无需认证 | 路径包含 `public` 关键字 |
|
| 公开接口 | `/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. 纯前端控制模式
|
### 1. 纯前端控制模式
|
||||||
|
@ -144,10 +173,10 @@ http.authorizeHttpRequests(auth -> auth
|
||||||
|
|
||||||
AntPath详情:https://juejin.cn/spost/7498247273660743732
|
AntPath详情:https://juejin.cn/spost/7498247273660743732
|
||||||
|
|
||||||
| 模式 | 示例 | 说明 |
|
| 模式 | 示例 | 说明 |
|
||||||
|------|-----------------|------------|
|
| -------- | --------------- | ---------------- |
|
||||||
| 精确匹配 | `/api/user` | 完全匹配路径 |
|
| 精确匹配 | `/api/user` | 完全匹配路径 |
|
||||||
| 通配符 | `/api/user/*` | 匹配单级路径 |
|
| 通配符 | `/api/user/*` | 匹配单级路径 |
|
||||||
| 多级通配 | `/api/user/**` | 匹配多级路径 |
|
| 多级通配 | `/api/user/**` | 匹配多级路径 |
|
||||||
| 方法限定 | `GET /api/user` | 匹配特定HTTP方法 |
|
| 方法限定 | `GET /api/user` | 匹配特定HTTP方法 |
|
||||||
|
|
||||||
|
@ -188,13 +217,12 @@ docker compose up -d
|
||||||
```
|
```
|
||||||
2. **权限码设计**:
|
2. **权限码设计**:
|
||||||
|
|
||||||
- 模块::操作 (如 `user::create`)
|
- 模块::操作 (如 `user::create`)
|
||||||
- 分层级设计 (如 `system:user:update`)
|
- 分层级设计 (如 `system:user:update`)
|
||||||
|
|
||||||
3. **批量操作**:
|
3. **批量操作**:
|
||||||
|
- 使用 Excel/JSON 管理大量权限配置
|
||||||
- 使用 Excel/JSON 管理大量权限配置
|
- 定期备份权限配置
|
||||||
- 定期备份权限配置
|
|
||||||
|
|
||||||
## 🌟 项目优势
|
## 🌟 项目优势
|
||||||
|
|
||||||
|
@ -217,30 +245,33 @@ docker compose up -d
|
||||||
- [ ] 用户设置持久化存储到数据库
|
- [ ] 用户设置持久化存储到数据库
|
||||||
- [ ] 权限弹窗页面优化
|
- [ ] 权限弹窗页面优化
|
||||||
- [ ] 后端文档注释完善
|
- [ ] 后端文档注释完善
|
||||||
- [ ] 系统监控后端返回403停止请求
|
- [x] 系统监控后端返回403停止请求
|
||||||
|
- [ ] 优化用户配置权限逻辑,配置后热更新逻辑等
|
||||||
|
- [ ] 完善后端注释,有需要添加ReadMe文档
|
||||||
|
- [ ] Redis中获取活跃用户
|
||||||
|
|
||||||
## 前后端接口规范
|
## 前后端接口规范
|
||||||
|
|
||||||
### 前端示例规范
|
### 前端示例规范
|
||||||
|
|
||||||
| **操作** | **API 层** | **Pinia 层** |
|
| **操作** | **API 层** | **Pinia 层** |
|
||||||
|:-------|:--------------|:----------------|
|
| :------- | :------------ | :-------------- |
|
||||||
| 查询单个 | `getUser` | `loadUser` |
|
| 查询单个 | `getUser` | `loadUser` |
|
||||||
| 查询列表 | `getUserList` | `loadUserList` |
|
| 查询列表 | `getUserList` | `loadUserList` |
|
||||||
| 分页查询 | `getUserPage` | `fetchUserPage` |
|
| 分页查询 | `getUserPage` | `fetchUserPage` |
|
||||||
| 新增数据 | `createUser` | `addUser` |
|
| 新增数据 | `createUser` | `addUser` |
|
||||||
| 更新数据 | `updateUser` | `editUser` |
|
| 更新数据 | `updateUser` | `editUser` |
|
||||||
| 删除数据 | `deleteUser` | `removeUser` |
|
| 删除数据 | `deleteUser` | `removeUser` |
|
||||||
|
|
||||||
### 后端接口示例规范
|
### 后端接口示例规范
|
||||||
|
|
||||||
遵循Restful
|
遵循Restful
|
||||||
|
|
||||||
| **操作** | **RESTful** |
|
| **操作** | **RESTful** |
|
||||||
|:-------|:----------------------------|
|
| :------- | :-------------------------- |
|
||||||
| 查询列表 | `GET /users` |
|
| 查询列表 | `GET /users` |
|
||||||
| 分页查询 | `GET /users/{page}/{limit}` |
|
| 分页查询 | `GET /users/{page}/{limit}` |
|
||||||
| 查询单个 | `GET /users/{id}` |
|
| 查询单个 | `GET /users/{id}` |
|
||||||
| 新增 | `POST /users` |
|
| 新增 | `POST /users` |
|
||||||
| 更新 | `PUT /users/{id}` |
|
| 更新 | `PUT /users/{id}` |
|
||||||
| 删除 | `DELETE /users/{id}` |
|
| 删除 | `DELETE /users/{id}` |
|
||||||
|
|
|
@ -56,7 +56,7 @@ onMounted(() => {
|
||||||
setI18n();
|
setI18n();
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(async () => {
|
||||||
const { version, name: title } = __APP_INFO__.pkg;
|
const { version, name: title } = __APP_INFO__.pkg;
|
||||||
const { VITE_PUBLIC_PATH, MODE } = import.meta.env;
|
const { VITE_PUBLIC_PATH, MODE } = import.meta.env;
|
||||||
// https://github.com/guMcrey/version-rocket/blob/main/README.zh-CN.md#api
|
// https://github.com/guMcrey/version-rocket/blob/main/README.zh-CN.md#api
|
||||||
|
|
|
@ -24,7 +24,7 @@ class PureHttp {
|
||||||
private static retryOriginalRequest(config: PureHttpRequestConfig) {
|
private static retryOriginalRequest(config: PureHttpRequestConfig) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
PureHttp.requests.push((token: string) => {
|
PureHttp.requests.push((token: string) => {
|
||||||
config.headers['token'] = formatToken(token);
|
config.headers['Authorization'] = formatToken(token);
|
||||||
resolve(config);
|
resolve(config);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,7 +26,8 @@ class PureHttp {
|
||||||
private static retryOriginalRequest(config: PureHttpRequestConfig) {
|
private static retryOriginalRequest(config: PureHttpRequestConfig) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
PureHttp.requests.push((token: string) => {
|
PureHttp.requests.push((token: string) => {
|
||||||
config.headers['token'] = formatToken(token);
|
// TODO Authorization
|
||||||
|
config.headers['Authorization'] = formatToken(token);
|
||||||
resolve(config);
|
resolve(config);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -97,7 +98,8 @@ class PureHttp {
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
// 从结果中获取token
|
// 从结果中获取token
|
||||||
const token = res.data.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.forEach((cb) => cb(token));
|
||||||
PureHttp.requests = [];
|
PureHttp.requests = [];
|
||||||
})
|
})
|
||||||
|
@ -107,7 +109,8 @@ class PureHttp {
|
||||||
}
|
}
|
||||||
resolve(PureHttp.retryOriginalRequest(config));
|
resolve(PureHttp.retryOriginalRequest(config));
|
||||||
} else {
|
} else {
|
||||||
config.headers['token'] = formatToken(data.token);
|
// TODO Authorization
|
||||||
|
config.headers['Authorization'] = formatToken(data.token);
|
||||||
resolve(config);
|
resolve(config);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -105,7 +105,7 @@ export function removeToken() {
|
||||||
|
|
||||||
/** 格式化token(jwt格式) */
|
/** 格式化token(jwt格式) */
|
||||||
export const formatToken = (token: string): string => {
|
export const formatToken = (token: string): string => {
|
||||||
return token;
|
return `Bearer ${token}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 是否有按钮级别的权限(根据登录接口返回的`permissions`字段进行判断)*/
|
/** 是否有按钮级别的权限(根据登录接口返回的`permissions`字段进行判断)*/
|
||||||
|
|
|
@ -21,7 +21,8 @@ editorConfig.MENU_CONF['uploadImage'] = {
|
||||||
// 选择文件时的类型限制,根据实际业务改写
|
// 选择文件时的类型限制,根据实际业务改写
|
||||||
allowedFileTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
allowedFileTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
||||||
meta: { type: 'message' },
|
meta: { type: 'message' },
|
||||||
headers: { token: token.value },
|
// TODO 修改為 Authorization
|
||||||
|
headers: { Authorization: token.value },
|
||||||
// 自定义插入图片
|
// 自定义插入图片
|
||||||
customInsert(res: any, insertFn) {
|
customInsert(res: any, insertFn) {
|
||||||
// res.data.url是后端返回的图片地址,根据实际业务改写
|
// res.data.url是后端返回的图片地址,根据实际业务改写
|
||||||
|
|
|
@ -20,7 +20,8 @@ editorConfig.MENU_CONF['uploadImage'] = {
|
||||||
// 选择文件时的类型限制,根据实际业务改写
|
// 选择文件时的类型限制,根据实际业务改写
|
||||||
allowedFileTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
allowedFileTypes: ['image/png', 'image/jpg', 'image/jpeg'],
|
||||||
meta: { type: 'message' },
|
meta: { type: 'message' },
|
||||||
headers: { token: token.value },
|
// TODO 修改為 Authorization
|
||||||
|
headers: { Authorization: token.value },
|
||||||
// 自定义插入图片
|
// 自定义插入图片
|
||||||
customInsert(res: any, insertFn) {
|
customInsert(res: any, insertFn) {
|
||||||
// res.data.url是后端返回的图片地址,根据实际业务改写
|
// res.data.url是后端返回的图片地址,根据实际业务改写
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { useIntervalFn } from '@vueuse/core';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { fetchSystemCPU } from '@/api/v1/actuator';
|
import { fetchSystemCPU } from '@/api/v1/actuator';
|
||||||
import SystemCardItem from '@/components/ReCol/SystemCardItem.vue';
|
import SystemCardItem from '@/components/ReCol/SystemCardItem.vue';
|
||||||
|
import { message } from '@/utils/message';
|
||||||
|
|
||||||
const cupECharts = ref();
|
const cupECharts = ref();
|
||||||
const myChart = ref<any>();
|
const myChart = ref<any>();
|
||||||
|
@ -18,6 +19,8 @@ const xSeriesData = ref([]);
|
||||||
// 数据显示长度
|
// 数据显示长度
|
||||||
const dateDisplayLength = ref(20);
|
const dateDisplayLength = ref(20);
|
||||||
|
|
||||||
|
const hasAuthority = ref(true);
|
||||||
|
|
||||||
const option = reactive<UtilsEChartsOption>({
|
const option = reactive<UtilsEChartsOption>({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
|
@ -71,6 +74,18 @@ const onSearch = async () => {
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const result = await fetchSystemCPU();
|
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];
|
const measurement = result.measurements[0];
|
||||||
if (measurement) {
|
if (measurement) {
|
||||||
const value = measurement.value;
|
const value = measurement.value;
|
||||||
|
@ -91,8 +106,8 @@ onMounted(() => {
|
||||||
|
|
||||||
onSearch();
|
onSearch();
|
||||||
|
|
||||||
// 定时刷新
|
// 定时刷新,并且当前有权限才能继续请求
|
||||||
useIntervalFn(() => onSearch(), 2000);
|
useIntervalFn(() => hasAuthority.value && onSearch(), 2000);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { useIntervalFn } from '@vueuse/core';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { fetchSystemProcessCPU } from '@/api/v1/actuator';
|
import { fetchSystemProcessCPU } from '@/api/v1/actuator';
|
||||||
import SystemCardItem from '@/components/ReCol/SystemCardItem.vue';
|
import SystemCardItem from '@/components/ReCol/SystemCardItem.vue';
|
||||||
|
import { message } from '@/utils/message';
|
||||||
|
|
||||||
const jvmCPUECharts = ref();
|
const jvmCPUECharts = ref();
|
||||||
const myChart = ref<any>();
|
const myChart = ref<any>();
|
||||||
|
@ -18,6 +19,9 @@ const xSeriesData = ref([]);
|
||||||
// 数据显示长度
|
// 数据显示长度
|
||||||
const dateDisplayLength = ref(20);
|
const dateDisplayLength = ref(20);
|
||||||
|
|
||||||
|
// 是否有权限继续访问
|
||||||
|
const hasAuthority = ref(true);
|
||||||
|
|
||||||
const option = reactive<UtilsEChartsOption>({
|
const option = reactive<UtilsEChartsOption>({
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
|
@ -70,6 +74,18 @@ const onSearch = async () => {
|
||||||
|
|
||||||
// 获取数据
|
// 获取数据
|
||||||
const result = await fetchSystemProcessCPU();
|
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];
|
const measurement = result.measurements[0];
|
||||||
if (measurement) {
|
if (measurement) {
|
||||||
const value = measurement.value;
|
const value = measurement.value;
|
||||||
|
@ -90,8 +106,8 @@ onMounted(() => {
|
||||||
|
|
||||||
onSearch();
|
onSearch();
|
||||||
|
|
||||||
// 定时刷新
|
// 定时刷新,并且当前有权限才能继续请求
|
||||||
useIntervalFn(() => onSearch(), 2000);
|
useIntervalFn(() => hasAuthority.value && onSearch(), 2000);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ import Refresh from '~icons/ep/refresh';
|
||||||
import AddFill from '~icons/ri/add-circle-line';
|
import AddFill from '~icons/ri/add-circle-line';
|
||||||
import Upload from '~icons/ri/upload-line';
|
import Upload from '~icons/ri/upload-line';
|
||||||
import PowersToRole from '@/views/system/role/components/powers-to-role.vue';
|
import PowersToRole from '@/views/system/role/components/powers-to-role.vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
defineOptions({ name: 'RoleManger' });
|
defineOptions({ name: 'RoleManger' });
|
||||||
|
|
||||||
|
@ -93,6 +94,7 @@ const onUpdateByFile = () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
onSearch();
|
onSearch();
|
||||||
|
|
|
@ -72,13 +72,13 @@ onMounted(() => {
|
||||||
:xs="24"
|
:xs="24"
|
||||||
class="mb-[18px]"
|
class="mb-[18px]"
|
||||||
>
|
>
|
||||||
<el-card class="h-[1178px] overflow-y-auto" shadow="never">
|
<el-card class="h-[1178px]" shadow="never">
|
||||||
<el-tabs v-model="activeName" class="demo-tabs" @tab-click="onTabClick">
|
<el-tabs v-model="activeName" @tab-click="onTabClick">
|
||||||
<el-tab-pane label="前端文档" name="web">
|
<el-tab-pane label="前端文档" name="web">
|
||||||
<web-read-me class="mt-3 h-[100%]" />
|
<web-read-me class="mt-3" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="后端文档" name="server">
|
<el-tab-pane label="后端文档" name="server">
|
||||||
<server-read-me class="mt-3 h-[100%]" />
|
<server-read-me class="mt-3" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
@ -135,26 +135,7 @@ onMounted(() => {
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:deep(.el-card) {
|
:deep(.el-card) {
|
||||||
--el-card-border-color: none;
|
--el-card-border-color: none;
|
||||||
|
overflow-y: auto;
|
||||||
/* 解决概率进度条宽度 */
|
|
||||||
.el-progress--line {
|
|
||||||
width: 85%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 解决概率进度条字体大小 */
|
|
||||||
.el-progress-bar__innerText {
|
|
||||||
font-size: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 隐藏 el-scrollbar 滚动条 */
|
|
||||||
.el-scrollbar__bar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* el-timeline 每一项上下、左右边距 */
|
|
||||||
.el-timeline-item {
|
|
||||||
margin: 0 6px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.main-content {
|
||||||
|
|
Loading…
Reference in New Issue