外观
日志监控系统设计文档
概述
日志监控系统(Bugs System)是一个用于监控网站错误、用户行为、API 调用等数据的系统。系统支持多项目管理,每个项目可以独立配置和查看数据。系统提供了完整的 SDK 供前端集成,支持通过项目 ID 或 App Token 进行认证。
注意:日志监控系统位于
apps/bugs目录下,对应的后端路由和控制器位于src/routes/bugs和src/controllers/bugs目录。
核心功能
1. 项目管理
- 项目列表展示(分页)
- 项目新增、编辑、删除
- 项目详情查看
- 项目统计数据展示
2. 数据监控
- 错误日志(Bug)监控
- 用户行为(Action)监控
- API 调用监控
- 页面访问(PV)统计
- 独立访客(UV)统计
- 浏览器访问(BV)统计
- 事件统计
3. SDK 配置接口
提供 SDK 配置接口,支持通过项目 ID 或 App Token 获取项目配置信息。
App Token 机制
概述
App Token 是一种用于 SDK 认证的机制,允许客户端通过 token 而不是项目 ID 来访问 SDK 配置接口。Token 不设置过期时间,但可以通过管理系统进行更新。
Token 生成
Token 使用 JWT(JSON Web Token)生成,包含以下信息:
projectId: 项目 ID(数字类型)
Token 使用 JWT_SECRET_KEY 作为密钥进行签名,不设置过期时间。
生成函数:src/scripts/BugUtils.mts 中的 generateProjectToken(projectId: number)
typescript
import { generateProjectToken } from "#scripts/BugUtils";
const token = generateProjectToken(projectId);1
2
3
2
3
Token 验证
Token 验证流程:
- 解析 JWT token,获取
projectId - 在数据库中查询对应
projectId的项目 - 验证传入的 token 是否与数据库中的
token或token_bak字段匹配 - 如果匹配,返回
projectId;如果不匹配,返回0(表示无效 token)
验证函数:src/scripts/BugUtils.mts 中的 getProjectIdFromToken(projectToken: string)
typescript
import { getProjectIdFromToken } from "#scripts/BugUtils";
const projectId = await getProjectIdFromToken(token);
if (projectId === 0) {
// token 无效
}1
2
3
4
5
6
2
3
4
5
6
Token 存储
Token 存储在 b_project 表中:
token: 当前使用的 token(可为空,唯一索引)token_bak: 备份的 token(可为空,唯一索引,用于存储旧的 token)
唯一性约束:
token字段添加唯一索引(允许多个 NULL)token_bak字段添加唯一索引(允许多个 NULL)- token 和 token_bak 之间的互斥性需要在应用层校验(在更新 token 时检查新 token 是否与另一个字段的值重复)
Token 管理功能
系统提供了完整的 Token 管理界面,支持以下功能:
1. Token 列表展示
在项目列表页面,每个项目都有一个"管理token"按钮,点击后打开 Token 管理对话框。对话框显示:
- 新token(对应
token字段):当前正在使用的 token - 旧token(对应
token_bak字段):备份的旧 token - 最多显示 2 个 token(新token 和旧token)
2. Token 操作
新token 操作:
- 复制:一键复制 token 值到剪贴板
- 更新:支持手动更新 token 值(需要输入新的 JWT 格式 token)
旧token 操作:
- 复制:一键复制 token 值到剪贴板
- 删除:删除旧 token(将
token_bak字段设置为null)
3. 新增 Token
点击"新增"按钮可以生成新的 token:
- 如果当前已有 2 个 token:系统会提示用户"最多只能有2个token,当前已经有2个token了,新增将会删除最早的一个token(旧token)",用户确认后才会执行
- 如果当前不到 2 个 token:直接生成新 token,无需确认
新增逻辑:
- 如果
token字段有值,将其备份到token_bak字段 - 调用
generateProjectToken(projectId)生成新 token - 将新 token 更新到
token字段
4. 权限控制
- 只有项目创建者可以管理该项目的 token
- 所有操作都需要登录验证
SDK 配置接口
SDK 配置接口 /api/bugs/project/sdk-config 支持两种认证方式:
项目 ID 认证(向后兼容):
typescriptGET /api/bugs/project/sdk-config?id=1231Token 认证(推荐):
typescriptGET /api/bugs/project/sdk-config?id=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...1
接口实现:
typescript
// Schema 定义:支持数字(projectId)或字符串(token)
export const sQuerySdkData = zod.object({
id: zod.union([
zod.coerce.number().int().positive("项目ID必须为正整数"),
zod.string().min(1, "token不能为空"),
]),
});
// 控制器逻辑
export const cQuerySdkData: ExpressRequestHandler = async (req, res, next) => {
try {
const query = sQuerySdkData.parse(req.query);
const id = query.id;
let projectId: number;
// 判断 id 是数字类型(projectId)还是字符串类型(token)
if (typeof id === "number") {
projectId = id;
} else {
// 字符串类型,认为是 token
projectId = await getProjectIdFromToken(id);
if (projectId === 0) {
res.fail("无效的 token");
return;
}
}
const project = await findProjectById(projectId);
if (!project) {
res.fail("项目不存在");
return;
}
// 返回 SDK 配置信息
res.success<ReturnApiGetSdkData>({
projectName: project.name,
originList: project.origin.includes(",")
? project.origin.split(",")
: [project.origin],
fields: {
/* ... */
},
});
} catch (err) {
next(err);
}
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
数据库设计
b_project(项目表)
存储项目的基本信息。
主要字段:
id: 主键,自增name: 项目名称(varchar(128),非空,唯一)user_id: 用户ID(integer,非空,外键关联 user.id)origin: 支持的域名列表(varchar(50),非空,多个域名用逗号分隔)token: 项目token(varchar(255),可为空,唯一索引)token_bak: 项目token备份(varchar(255),可为空,唯一索引)deleted: 是否删除(boolean,默认0,软删除)created_at: 创建时间updated_at: 更新时间
详细字段说明请参考 数据库设计文档。
其他数据表
b_bug: 错误日志表b_action: 用户行为表b_api: API 调用表b_pv: 页面访问表b_uv: 独立访客表b_bv: 浏览器访问表b_event_statistics: 事件统计表- 以及对应的统计表(
*_statistics)
权限控制
访问权限
- 项目列表/详情:需要登录,普通用户只能查看自己创建的项目,管理员可以查看所有项目
- 项目新增/编辑/删除:需要 VIP 及以上权限
- SDK 配置接口:无需登录,支持通过项目 ID 或 Token 访问
操作权限
- 项目新增:VIP、SVIP、管理员可新增项目
- 项目编辑:VIP、SVIP、管理员可编辑项目
- 项目删除:VIP、SVIP、管理员可删除项目
业务流程
1. SDK 配置获取流程
客户端请求 SDK 配置
↓
判断 id 参数类型
↓
如果是数字 → 直接使用 projectId 查询
如果是字符串 → 解析 token 获取 projectId
↓
验证 token 是否有效(与数据库中的 token 或 token_bak 匹配)
↓
查询项目信息
↓
返回 SDK 配置(项目名称、域名列表、字段限制等)1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
2. Token 生成流程
管理员在管理界面生成 token
↓
调用 generateProjectToken(projectId) 生成 token
↓
将 token 保存到数据库(token 或 token_bak 字段)
↓
返回 token 给管理员1
2
3
4
5
6
7
2
3
4
5
6
7
3. Token 验证流程
客户端使用 token 访问接口
↓
调用 getProjectIdFromToken(token) 解析 token
↓
验证 token 是否与数据库中的 token 或 token_bak 匹配
↓
如果匹配 → 返回 projectId
如果不匹配 → 返回 0(无效 token)1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
4. Token 管理流程
用户在项目列表点击"管理token"按钮
↓
打开 Token 管理对话框
↓
加载项目信息(token 和 token_bak)
↓
用户执行操作(新增/更新/删除)
↓
调用对应的后端接口
↓
操作成功后刷新项目列表1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
API 接口
项目管理接口
POST /api/bugs/project/addProject- 创建项目(需要 VIP 及以上权限)POST /api/bugs/project/updateProject- 更新项目(需要 VIP 及以上权限)POST /api/bugs/project/deleteProjects- 批量删除项目(需要 VIP 及以上权限)GET /api/bugs/project/getAllProjectList- 获取所有项目列表(不分页,需要登录)GET /api/bugs/project/getProjectListByPage- 获取项目列表(分页,需要登录)GET /api/bugs/project/getProjectDetail- 获取项目详情(需要登录)
SDK 配置接口
GET /api/bugs/project/sdk-config- 获取 SDK 配置(无需登录,支持项目 ID 或 Token)
请求参数:
typescript
{
id: number | string; // 项目 ID(数字)或 Token(字符串)
}1
2
3
2
3
返回数据:
typescript
{
projectName: string; // 项目名称
originList: string[]; // 支持的域名列表
fields: {
MAX_LENGTH_USER_ID: number;
MAX_LENGTH_PAGE_URL: number;
MAX_LENGTH_API_URL: number;
// ... 其他字段限制
};
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Token 管理接口
POST /api/bugs/project/generateToken- 生成新 token(需要登录,仅项目创建者可操作)
请求参数:
typescript
{
id: number; // 项目 ID
}1
2
3
2
3
返回数据:
typescript
{
token: string; // 新生成的 token
}1
2
3
2
3
POST /api/bugs/project/updateToken- 更新 token(需要登录,仅项目创建者可操作)
请求参数:
typescript
{
id: number; // 项目 ID
token: string; // 新的 token 值(JWT 格式)
}1
2
3
4
2
3
4
返回数据:
typescript
null;1
验证规则:
Token 必须是有效的 JWT 格式(包含 3 个部分,用
.分隔)新 token 不能与当前 token 重复
新 token 不能与旧 token(token_bak)重复
POST /api/bugs/project/deleteTokenBak- 删除旧 token(需要登录,仅项目创建者可操作)
请求参数:
typescript
{
id: number; // 项目 ID
}1
2
3
2
3
返回数据:
typescript
null;1
主要涉及文件
后端文件
src/models/bugs/Project.mts- 项目表模型定义src/models/bugs/Bug.mts- 错误日志表模型定义src/controllers/bugs/project.mts- 项目相关控制器src/controllers/bugs/bug.mts- 错误日志相关控制器src/services/bugs/project.mts- 项目相关服务层src/routes/bugs/project.mts- 项目相关路由src/scripts/BugUtils.mts- Bug 系统工具函数(token 生成和验证)
前端文件
apps/bugs/src/- 前端应用代码apps/bugs/src/views/DashboardView/DashboardView.vue- 项目列表页面(包含 Token 管理入口)apps/bugs/src/components/TokenManageDialog/TokenManageDialog.vue- Token 管理对话框组件
类型定义
typings/bugs-project.d.ts- 项目相关类型定义typings/bugs-*.d.ts- 其他 Bug 系统相关类型定义
数据库迁移
migrations/20251127101031_add_token_to_b_project.mts- 添加 token 字段的迁移文件
注意事项
- Token 安全性:Token 使用 JWT 签名,确保 token 的完整性和真实性
- Token 唯一性:Token 和 token_bak 字段都有唯一索引,确保不会重复
- 向后兼容:SDK 配置接口同时支持项目 ID 和 Token,保持向后兼容
- Token 管理:系统提供了完整的 Token 管理界面,支持新增、更新、删除操作
- 权限控制:只有项目创建者可以管理该项目的 token,确保安全性
- 用户体验:Token 管理对话框支持一键复制、操作确认提示等功能,提升用户体验
- 错误处理:Token 验证失败时返回明确的错误信息,便于调试
- 数据刷新:Token 操作成功后自动刷新项目列表,确保数据同步