@befly-addon/admin 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apis/admin/del.ts +35 -0
- package/apis/admin/info.ts +50 -0
- package/apis/admin/ins.ts +60 -0
- package/apis/admin/list.ts +20 -0
- package/apis/admin/roleDetail.ts +35 -0
- package/apis/admin/roleSave.ts +40 -0
- package/apis/admin/upd.ts +51 -0
- package/apis/api/all.ts +37 -0
- package/apis/auth/login.ts +76 -0
- package/apis/auth/logout.ts +23 -0
- package/apis/auth/register.ts +48 -0
- package/apis/auth/sendSmsCode.ts +35 -0
- package/apis/dashboard/addonList.ts +47 -0
- package/apis/dashboard/changelog.ts +37 -0
- package/apis/dashboard/configStatus.ts +54 -0
- package/apis/dashboard/environmentInfo.ts +46 -0
- package/apis/dashboard/performanceMetrics.ts +23 -0
- package/apis/dashboard/permissionStats.ts +31 -0
- package/apis/dashboard/serviceStatus.ts +81 -0
- package/apis/dashboard/systemInfo.ts +25 -0
- package/apis/dashboard/systemOverview.ts +32 -0
- package/apis/dashboard/systemResources.ts +119 -0
- package/apis/dict/all.ts +25 -0
- package/apis/dict/del.ts +19 -0
- package/apis/dict/detail.ts +21 -0
- package/apis/dict/ins.ts +27 -0
- package/apis/dict/list.ts +18 -0
- package/apis/dict/upd.ts +31 -0
- package/apis/menu/all.ts +68 -0
- package/apis/menu/del.ts +37 -0
- package/apis/menu/ins.ts +20 -0
- package/apis/menu/list.ts +21 -0
- package/apis/menu/upd.ts +29 -0
- package/apis/role/apiDetail.ts +30 -0
- package/apis/role/apiSave.ts +41 -0
- package/apis/role/del.ts +44 -0
- package/apis/role/detail.ts +24 -0
- package/apis/role/ins.ts +39 -0
- package/apis/role/list.ts +14 -0
- package/apis/role/menuDetail.ts +30 -0
- package/apis/role/menuSave.ts +38 -0
- package/apis/role/save.ts +44 -0
- package/apis/role/upd.ts +40 -0
- package/package.json +2 -2
- package/util.ts +1 -1
- package/apis/.gitkeep +0 -0
- package/checks/.gitkeep +0 -0
- package/config/.gitkeep +0 -0
- package/plugins/.gitkeep +0 -0
- package/scripts/.gitkeep +0 -0
- package/tables/.gitkeep +0 -0
- package/types/index.ts +0 -44
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取运行环境信息
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Yes } from 'befly';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
name: '获取运行环境信息',
|
|
10
|
+
handler: async (befly, ctx) => {
|
|
11
|
+
// 获取数据库版本
|
|
12
|
+
let databaseVersion = 'Unknown';
|
|
13
|
+
try {
|
|
14
|
+
const versionResult = await befly.db.query({
|
|
15
|
+
sql: 'SELECT VERSION() as version',
|
|
16
|
+
type: 'one'
|
|
17
|
+
});
|
|
18
|
+
databaseVersion = versionResult.version || 'Unknown';
|
|
19
|
+
} catch (error) {
|
|
20
|
+
// 忽略错误
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 获取 Redis 版本
|
|
24
|
+
let redisVersion = '未配置';
|
|
25
|
+
if (befly.redis) {
|
|
26
|
+
try {
|
|
27
|
+
const info = await befly.redis.info('server');
|
|
28
|
+
const match = info.match(/redis_version:([^\r\n]+)/);
|
|
29
|
+
if (match) {
|
|
30
|
+
redisVersion = match[1];
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
redisVersion = '未知';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return Yes('获取成功', {
|
|
38
|
+
os: `${os.type()} ${os.arch()}`,
|
|
39
|
+
server: `${os.platform()} ${os.release()}`,
|
|
40
|
+
nodeVersion: process.version,
|
|
41
|
+
database: `MySQL ${databaseVersion}`,
|
|
42
|
+
cache: `Redis ${redisVersion}`,
|
|
43
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取性能指标
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Yes } from 'befly';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: '获取性能指标',
|
|
9
|
+
handler: async (befly, ctx) => {
|
|
10
|
+
// 实际项目中,这些数据应该从监控系统或日志中获取
|
|
11
|
+
// 这里提供示例数据结构
|
|
12
|
+
return Yes('获取成功', {
|
|
13
|
+
avgResponseTime: 125,
|
|
14
|
+
qps: 856,
|
|
15
|
+
errorRate: 0.8,
|
|
16
|
+
activeConnections: 45,
|
|
17
|
+
slowestApi: {
|
|
18
|
+
path: '/addon/admin/menuList',
|
|
19
|
+
time: 450
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取权限统计信息
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Yes } from 'befly';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: '获取权限统计',
|
|
9
|
+
handler: async (befly, ctx) => {
|
|
10
|
+
// 统计菜单数量
|
|
11
|
+
const menuCount = await befly.db.count({
|
|
12
|
+
table: 'core_menu'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// 统计接口数量
|
|
16
|
+
const apiCount = await befly.db.count({
|
|
17
|
+
table: 'core_api'
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// 统计角色数量
|
|
21
|
+
const roleCount = await befly.db.count({
|
|
22
|
+
table: 'core_role'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return Yes('获取成功', {
|
|
26
|
+
menuCount: menuCount,
|
|
27
|
+
apiCount: apiCount,
|
|
28
|
+
roleCount: roleCount
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取服务状态
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Yes, Env } from 'befly';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: '获取服务状态',
|
|
9
|
+
handler: async (befly, ctx) => {
|
|
10
|
+
const services = [];
|
|
11
|
+
|
|
12
|
+
// 数据库状态
|
|
13
|
+
try {
|
|
14
|
+
const startTime = Date.now();
|
|
15
|
+
await befly.db.query('SELECT 1');
|
|
16
|
+
const responseTime = Date.now() - startTime;
|
|
17
|
+
services.push({
|
|
18
|
+
name: '数据库',
|
|
19
|
+
status: 'running',
|
|
20
|
+
responseTime: `${responseTime}ms`
|
|
21
|
+
});
|
|
22
|
+
} catch (error) {
|
|
23
|
+
befly.logger.error('数据库状态检测失败:', error);
|
|
24
|
+
services.push({
|
|
25
|
+
name: '数据库',
|
|
26
|
+
status: 'stopped',
|
|
27
|
+
responseTime: '-'
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Redis 状态
|
|
32
|
+
if (Env.REDIS_ENABLE === 1 && befly.redis) {
|
|
33
|
+
try {
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
await befly.redis.ping();
|
|
36
|
+
const responseTime = Date.now() - startTime;
|
|
37
|
+
services.push({
|
|
38
|
+
name: 'Redis',
|
|
39
|
+
status: 'running',
|
|
40
|
+
responseTime: `${responseTime}ms`
|
|
41
|
+
});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
befly.logger.error('Redis状态检测失败:', error);
|
|
44
|
+
services.push({
|
|
45
|
+
name: 'Redis',
|
|
46
|
+
status: 'stopped',
|
|
47
|
+
responseTime: '-'
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
services.push({
|
|
52
|
+
name: 'Redis',
|
|
53
|
+
status: 'stopped',
|
|
54
|
+
responseTime: '-'
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 文件系统状态
|
|
59
|
+
services.push({
|
|
60
|
+
name: '文件系统',
|
|
61
|
+
status: 'running',
|
|
62
|
+
responseTime: '-'
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// 邮件服务状态(示例)
|
|
66
|
+
services.push({
|
|
67
|
+
name: '邮件服务',
|
|
68
|
+
status: 'unconfigured',
|
|
69
|
+
responseTime: '-'
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// OSS存储状态(示例)
|
|
73
|
+
services.push({
|
|
74
|
+
name: 'OSS存储',
|
|
75
|
+
status: 'unconfigured',
|
|
76
|
+
responseTime: '-'
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return Yes('获取成功', { services });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取系统信息
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Yes, Env } from 'befly';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: '获取系统信息',
|
|
9
|
+
handler: async (befly, ctx) => {
|
|
10
|
+
// 系统启动时间(从进程启动时间计算)
|
|
11
|
+
const startTime = Date.now() - Math.floor(process.uptime() * 1000);
|
|
12
|
+
|
|
13
|
+
// 运行时长(毫秒)
|
|
14
|
+
const uptime = Math.floor(process.uptime() * 1000);
|
|
15
|
+
|
|
16
|
+
// 环境
|
|
17
|
+
const environment = Env.NODE_ENV || 'development';
|
|
18
|
+
|
|
19
|
+
return Yes('获取成功', {
|
|
20
|
+
environment: environment,
|
|
21
|
+
startTime: startTime,
|
|
22
|
+
uptime: uptime
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取系统概览数据
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Yes } from 'befly';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: '获取系统概览数据',
|
|
9
|
+
handler: async (befly, ctx) => {
|
|
10
|
+
// 权限统计
|
|
11
|
+
const menuCount = await befly.db.getCount({
|
|
12
|
+
table: 'core_menu',
|
|
13
|
+
where: { state: 1 }
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const roleCount = await befly.db.getCount({
|
|
17
|
+
table: 'core_role',
|
|
18
|
+
where: { state: 1 }
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const apiCount = await befly.db.getCount({
|
|
22
|
+
table: 'core_api',
|
|
23
|
+
where: { state: 1 }
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return Yes('获取成功', {
|
|
27
|
+
menuCount,
|
|
28
|
+
roleCount,
|
|
29
|
+
apiCount
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取系统资源使用情况
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { Yes } from 'befly';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
name: '获取系统资源',
|
|
12
|
+
auth: true,
|
|
13
|
+
fields: {},
|
|
14
|
+
handler: async (befly, ctx) => {
|
|
15
|
+
// CPU 使用率
|
|
16
|
+
const cpus = os.cpus();
|
|
17
|
+
const cpuCount = cpus.length; // 逻辑核心数(包括超线程)
|
|
18
|
+
|
|
19
|
+
// 计算 CPU 使用率(需要两次采样)
|
|
20
|
+
// 第一次采样
|
|
21
|
+
const startMeasure = cpus.map((cpu) => {
|
|
22
|
+
let total = 0;
|
|
23
|
+
for (const type in cpu.times) {
|
|
24
|
+
total += cpu.times[type];
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
idle: cpu.times.idle,
|
|
28
|
+
total
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// 等待 100ms
|
|
33
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
34
|
+
|
|
35
|
+
// 第二次采样
|
|
36
|
+
const endCpus = os.cpus();
|
|
37
|
+
const endMeasure = endCpus.map((cpu) => {
|
|
38
|
+
let total = 0;
|
|
39
|
+
for (const type in cpu.times) {
|
|
40
|
+
total += cpu.times[type];
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
idle: cpu.times.idle,
|
|
44
|
+
total
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 计算平均 CPU 使用率
|
|
49
|
+
let totalIdle = 0;
|
|
50
|
+
let totalTick = 0;
|
|
51
|
+
for (let i = 0; i < startMeasure.length; i++) {
|
|
52
|
+
const idleDiff = endMeasure[i].idle - startMeasure[i].idle;
|
|
53
|
+
const totalDiff = endMeasure[i].total - startMeasure[i].total;
|
|
54
|
+
totalIdle += idleDiff;
|
|
55
|
+
totalTick += totalDiff;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const cpuUsage = totalTick > 0 ? Math.round(((totalTick - totalIdle) / totalTick) * 100) : 0;
|
|
59
|
+
|
|
60
|
+
// 内存使用率
|
|
61
|
+
const totalMem = os.totalmem();
|
|
62
|
+
const freeMem = os.freemem();
|
|
63
|
+
const usedMem = totalMem - freeMem;
|
|
64
|
+
const memoryPercentage = totalMem > 0 ? Math.round((usedMem / totalMem) * 100) : 0;
|
|
65
|
+
|
|
66
|
+
// 磁盘使用率(使用 fs.statfs 获取当前工作目录所在磁盘)
|
|
67
|
+
let diskPercentage = 0;
|
|
68
|
+
let diskUsed = 0;
|
|
69
|
+
let diskTotal = 0;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// 检查是否有 statfs 方法(Node.js 19+ 或 Bun)
|
|
73
|
+
if (fs.statfs) {
|
|
74
|
+
const statfsAsync = promisify(fs.statfs);
|
|
75
|
+
const stats = await statfsAsync(process.cwd());
|
|
76
|
+
|
|
77
|
+
// 计算磁盘使用情况
|
|
78
|
+
// bsize: 文件系统块大小
|
|
79
|
+
// blocks: 总块数
|
|
80
|
+
// bfree: 可用块数
|
|
81
|
+
const totalSpace = stats.blocks * stats.bsize;
|
|
82
|
+
const freeSpace = stats.bfree * stats.bsize;
|
|
83
|
+
const usedSpace = totalSpace - freeSpace;
|
|
84
|
+
|
|
85
|
+
if (totalSpace > 0) {
|
|
86
|
+
diskTotal = Math.round(totalSpace / 1024 / 1024 / 1024);
|
|
87
|
+
diskUsed = Math.round(usedSpace / 1024 / 1024 / 1024);
|
|
88
|
+
diskPercentage = Math.round((usedSpace / totalSpace) * 100);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// statfs 不可用,返回默认值
|
|
92
|
+
befly.logger.warn('fs.statfs 不可用,无法获取磁盘信息');
|
|
93
|
+
}
|
|
94
|
+
} catch (error) {
|
|
95
|
+
befly.logger.warn('获取磁盘信息失败', error);
|
|
96
|
+
// 获取失败时返回 0
|
|
97
|
+
diskPercentage = 0;
|
|
98
|
+
diskUsed = 0;
|
|
99
|
+
diskTotal = 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return Yes('获取成功', {
|
|
103
|
+
cpu: {
|
|
104
|
+
usage: cpuUsage,
|
|
105
|
+
cores: cpuCount
|
|
106
|
+
},
|
|
107
|
+
memory: {
|
|
108
|
+
used: (usedMem / 1024 / 1024 / 1024).toFixed(1),
|
|
109
|
+
total: (totalMem / 1024 / 1024 / 1024).toFixed(1),
|
|
110
|
+
percentage: memoryPercentage
|
|
111
|
+
},
|
|
112
|
+
disk: {
|
|
113
|
+
used: diskUsed,
|
|
114
|
+
total: diskTotal,
|
|
115
|
+
percentage: diskPercentage
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
};
|
package/apis/dict/all.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Yes, No } from 'befly';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 获取所有字典(不分页)
|
|
5
|
+
*/
|
|
6
|
+
export default {
|
|
7
|
+
name: '获取所有字典',
|
|
8
|
+
handler: async (befly, ctx) => {
|
|
9
|
+
try {
|
|
10
|
+
const dicts = await befly.db.getAll({
|
|
11
|
+
table: 'core_dict',
|
|
12
|
+
fields: ['id', 'name', 'code', 'value', 'sort', 'pid', 'description', 'state', 'created_at', 'updated_at'],
|
|
13
|
+
orderBy: [
|
|
14
|
+
{ field: 'sort', direction: 'ASC' },
|
|
15
|
+
{ field: 'id', direction: 'ASC' }
|
|
16
|
+
]
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return Yes('操作成功', dicts);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
befly.logger.error('获取所有字典失败:', error);
|
|
22
|
+
return No('操作失败');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
};
|
package/apis/dict/del.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Field, Yes, No } from 'befly';
|
|
2
|
+
export default {
|
|
3
|
+
name: '删除字典',
|
|
4
|
+
handler: async (befly, ctx) => {
|
|
5
|
+
try {
|
|
6
|
+
await befly.db.delData({
|
|
7
|
+
table: 'core_dict',
|
|
8
|
+
where: {
|
|
9
|
+
id: ctx.body.id
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
return Yes('操作成功');
|
|
14
|
+
} catch (error) {
|
|
15
|
+
befly.logger.error('删除字典失败:', error);
|
|
16
|
+
return No('操作失败');
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Yes, No } from 'befly';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
name: '获取字典详情',
|
|
5
|
+
handler: async (befly, ctx) => {
|
|
6
|
+
try {
|
|
7
|
+
const dict = await befly.db.getDetail({
|
|
8
|
+
table: 'core_dict',
|
|
9
|
+
fields: ['id', 'name', 'code', 'value', 'sort', 'pid', 'description', 'state', 'created_at', 'updated_at'],
|
|
10
|
+
where: {
|
|
11
|
+
id: ctx.body.id
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return Yes('操作成功', dict);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
befly.logger.error('获取字典详情失败:', error);
|
|
18
|
+
return No('操作失败');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
package/apis/dict/ins.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Yes, No } from 'befly';
|
|
2
|
+
import adminDictTable from '../../tables/dict.json';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: '添加字典',
|
|
6
|
+
fields: adminDictTable,
|
|
7
|
+
handler: async (befly, ctx) => {
|
|
8
|
+
try {
|
|
9
|
+
const dictId = await befly.db.insData({
|
|
10
|
+
table: 'core_dict',
|
|
11
|
+
data: {
|
|
12
|
+
name: ctx.body.name,
|
|
13
|
+
code: ctx.body.code,
|
|
14
|
+
value: ctx.body.value,
|
|
15
|
+
sort: ctx.body.sort,
|
|
16
|
+
pid: ctx.body.pid,
|
|
17
|
+
description: ctx.body.description
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return Yes('操作成功', { id: dictId });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
befly.logger.error('添加字典失败:', error);
|
|
24
|
+
return No('操作失败');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Yes, No } from 'befly';
|
|
2
|
+
export default {
|
|
3
|
+
name: '获取字典列表',
|
|
4
|
+
handler: async (befly, ctx) => {
|
|
5
|
+
const result = await befly.db.getList({
|
|
6
|
+
table: 'core_dict',
|
|
7
|
+
fields: ['id', 'name', 'code', 'value', 'sort', 'pid', 'description', 'state', 'created_at', 'updated_at'],
|
|
8
|
+
page: ctx.body.page,
|
|
9
|
+
limit: ctx.body.limit,
|
|
10
|
+
orderBy: [
|
|
11
|
+
{ field: 'sort', direction: 'ASC' },
|
|
12
|
+
{ field: 'id', direction: 'ASC' }
|
|
13
|
+
]
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return Yes('操作成功', result);
|
|
17
|
+
}
|
|
18
|
+
};
|
package/apis/dict/upd.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Yes, No } from 'befly';
|
|
2
|
+
import adminDictTable from '../../tables/dict.json';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: '更新字典',
|
|
6
|
+
fields: adminDictTable,
|
|
7
|
+
handler: async (befly, ctx) => {
|
|
8
|
+
try {
|
|
9
|
+
await befly.db.updData({
|
|
10
|
+
table: 'core_dict',
|
|
11
|
+
data: {
|
|
12
|
+
name: ctx.body.name,
|
|
13
|
+
code: ctx.body.code,
|
|
14
|
+
value: ctx.body.value,
|
|
15
|
+
sort: ctx.body.sort,
|
|
16
|
+
pid: ctx.body.pid,
|
|
17
|
+
description: ctx.body.description,
|
|
18
|
+
state: ctx.body.state
|
|
19
|
+
},
|
|
20
|
+
where: {
|
|
21
|
+
id: ctx.body.id
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return Yes('操作成功');
|
|
26
|
+
} catch (error) {
|
|
27
|
+
befly.logger.error('更新字典失败:', error);
|
|
28
|
+
return No('操作失败');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
};
|
package/apis/menu/all.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 获取当前用户的菜单权限
|
|
3
|
+
* 说明:
|
|
4
|
+
* 1. 从 Redis 缓存读取所有菜单(如果缓存不存在则从数据库查询并缓存)
|
|
5
|
+
* 2. 根据当前登录用户的角色过滤可访问的菜单
|
|
6
|
+
* 3. 返回一维数组(由前端构建树形结构)
|
|
7
|
+
* 4. 仅返回状态为启用的菜单
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Yes, No } from 'befly';
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
name: '获取用户菜单',
|
|
14
|
+
handler: async (befly, ctx) => {
|
|
15
|
+
try {
|
|
16
|
+
// 2. 查询角色信息获取菜单权限(使用 roleCode 而非 roleId)
|
|
17
|
+
const role = await befly.db.getOne({
|
|
18
|
+
table: 'core_role',
|
|
19
|
+
where: { code: ctx.user.roleCode }
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (!role) {
|
|
23
|
+
return No('角色不存在', []);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// 3. 解析菜单ID列表(逗号分隔的字符串)
|
|
27
|
+
const menuIds = role.menus
|
|
28
|
+
.split(',')
|
|
29
|
+
.map((id: string) => parseInt(id.trim()))
|
|
30
|
+
.filter((id: number) => !isNaN(id));
|
|
31
|
+
|
|
32
|
+
if (menuIds.length === 0) {
|
|
33
|
+
return Yes('菜单为空', []);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 4. 从 Redis 缓存读取所有菜单
|
|
37
|
+
let allMenus = await befly.redis.getObject<any[]>('menus:all');
|
|
38
|
+
|
|
39
|
+
// 如果缓存不存在,从数据库查询并缓存
|
|
40
|
+
if (!allMenus || allMenus.length === 0) {
|
|
41
|
+
befly.logger.info('菜单缓存未命中,从数据库查询');
|
|
42
|
+
allMenus = await befly.db.getAll({
|
|
43
|
+
table: 'core_menu',
|
|
44
|
+
fields: ['id', 'pid', 'name', 'path', 'icon', 'type', 'sort'],
|
|
45
|
+
orderBy: ['sort#ASC', 'id#ASC']
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 回写缓存
|
|
49
|
+
if (allMenus.length > 0) {
|
|
50
|
+
await befly.redis.setObject('menus:all', allMenus);
|
|
51
|
+
befly.logger.info(`已缓存 ${allMenus.length} 个菜单到 Redis`);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
befly.logger.debug(`从 Redis 缓存读取 ${allMenus.length} 个菜单`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 5. 根据角色权限过滤菜单
|
|
58
|
+
const menuIdSet = new Set(menuIds.map(String)); // 转为字符串 Set 方便比较
|
|
59
|
+
const authorizedMenus = allMenus.filter((menu: any) => menuIdSet.has(String(menu.id)));
|
|
60
|
+
|
|
61
|
+
// 6. 返回一维数组(由前端构建树形结构)
|
|
62
|
+
return Yes('获取菜单成功', authorizedMenus);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
befly.logger.error('获取用户菜单失败:', error);
|
|
65
|
+
return No('获取菜单失败');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
package/apis/menu/del.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 删除菜单
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Yes, No } from 'befly';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
name: '删除菜单',
|
|
9
|
+
handler: async (befly, ctx) => {
|
|
10
|
+
try {
|
|
11
|
+
// 检查是否有子菜单(使用 getList 代替 getAll)
|
|
12
|
+
const childrenList = await befly.db.getList({
|
|
13
|
+
table: 'core_menu',
|
|
14
|
+
where: { pid: ctx.body.id }
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (childrenList.total > 0) {
|
|
18
|
+
return No('该菜单下有子菜单,无法删除');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 删除菜单
|
|
22
|
+
await befly.db.delData({
|
|
23
|
+
table: 'core_menu',
|
|
24
|
+
where: { id: ctx.body.id }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// 注意:菜单权限现在存储在 role.menus 字段中
|
|
28
|
+
// 如果需要从角色权限中移除此菜单,需要额外处理
|
|
29
|
+
// 这里暂时不处理,由管理员在角色管理界面手动调整
|
|
30
|
+
|
|
31
|
+
return Yes('操作成功');
|
|
32
|
+
} catch (error) {
|
|
33
|
+
befly.logger.error('删除菜单失败:', error);
|
|
34
|
+
return No('操作失败');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
package/apis/menu/ins.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Yes, No } from 'befly';
|
|
2
|
+
import adminMenuTable from '../../tables/menu.json';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
name: '创建菜单',
|
|
6
|
+
fields: adminMenuTable,
|
|
7
|
+
handler: async (befly, ctx) => {
|
|
8
|
+
try {
|
|
9
|
+
const menuId = await befly.db.insData({
|
|
10
|
+
table: 'core_menu',
|
|
11
|
+
data: ctx.body
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return Yes('操作成功', { id: menuId });
|
|
15
|
+
} catch (error) {
|
|
16
|
+
befly.logger.error('创建菜单失败:', error);
|
|
17
|
+
return No('操作失败');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Yes, No } from 'befly';
|
|
2
|
+
export default {
|
|
3
|
+
name: '获取菜单列表',
|
|
4
|
+
handler: async (befly, ctx) => {
|
|
5
|
+
try {
|
|
6
|
+
const menus = await befly.db.getAll({
|
|
7
|
+
table: 'core_menu',
|
|
8
|
+
fields: ['id', 'name', 'path', 'icon', 'sort', 'pid', 'type', 'state', 'created_at', 'updated_at'],
|
|
9
|
+
orderBy: [
|
|
10
|
+
{ field: 'sort', direction: 'ASC' },
|
|
11
|
+
{ field: 'id', direction: 'ASC' }
|
|
12
|
+
]
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return Yes('操作成功', menus);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
befly.logger.error('获取菜单列表失败:', error);
|
|
18
|
+
return No('操作失败');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|