|
|
@@ -0,0 +1,271 @@
|
|
|
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';
|
|
|
+import { z } from '@hono/zod-openapi';
|
|
|
+
|
|
|
+@Entity('operation_logs')
|
|
|
+export class OperationLog {
|
|
|
+ @PrimaryGeneratedColumn({ unsigned: true })
|
|
|
+ id!: number;
|
|
|
+
|
|
|
+ @Column({ name: 'request_id', type: 'varchar', length: 50, comment: '请求唯一标识' })
|
|
|
+ requestId!: string;
|
|
|
+
|
|
|
+ @Column({ name: 'user_id', type: 'int', nullable: true, comment: '操作用户ID' })
|
|
|
+ userId?: number;
|
|
|
+
|
|
|
+ @Column({ name: 'username', type: 'varchar', length: 50, nullable: true, comment: '用户名' })
|
|
|
+ username?: string;
|
|
|
+
|
|
|
+ @Column({ name: 'resource_type', type: 'varchar', length: 50, comment: '资源类型' })
|
|
|
+ resourceType!: string;
|
|
|
+
|
|
|
+ @Column({ name: 'resource_id', type: 'varchar', length: 50, nullable: true, comment: '资源ID' })
|
|
|
+ resourceId?: string;
|
|
|
+
|
|
|
+ @Column({ name: 'action', type: 'varchar', length: 50, comment: '操作动作' })
|
|
|
+ action!: string;
|
|
|
+
|
|
|
+ @Column({ name: 'method', type: 'varchar', length: 10, comment: 'HTTP方法' })
|
|
|
+ method!: string;
|
|
|
+
|
|
|
+ @Column({ name: 'endpoint', type: 'varchar', length: 255, comment: 'API端点' })
|
|
|
+ endpoint!: string;
|
|
|
+
|
|
|
+ @Column({ name: 'ip_address', type: 'varchar', length: 45, nullable: true, comment: 'IP地址' })
|
|
|
+ ipAddress?: string;
|
|
|
+
|
|
|
+ @Column({ name: 'user_agent', type: 'varchar', length: 500, nullable: true, comment: '用户代理' })
|
|
|
+ userAgent?: string;
|
|
|
+
|
|
|
+ @Column({ name: 'request_data', type: 'json', nullable: true, comment: '请求数据(JSON)' })
|
|
|
+ requestData?: any;
|
|
|
+
|
|
|
+ @Column({ name: 'response_data', type: 'json', nullable: true, comment: '响应数据(JSON)' })
|
|
|
+ responseData?: any;
|
|
|
+
|
|
|
+ @Column({ name: 'before_data', type: 'json', nullable: true, comment: '操作前数据(JSON)' })
|
|
|
+ beforeData?: any;
|
|
|
+
|
|
|
+ @Column({ name: 'after_data', type: 'json', nullable: true, comment: '操作后数据(JSON)' })
|
|
|
+ afterData?: any;
|
|
|
+
|
|
|
+ @Column({ name: 'duration', type: 'int', default: 0, comment: '操作耗时(ms)' })
|
|
|
+ duration!: number;
|
|
|
+
|
|
|
+ @Column({ name: 'status', type: 'enum', enum: ['success', 'failed', 'permission_denied'], comment: '操作状态' })
|
|
|
+ status!: 'success' | 'failed' | 'permission_denied';
|
|
|
+
|
|
|
+ @Column({ name: 'error_message', type: 'text', nullable: true, comment: '错误信息' })
|
|
|
+ errorMessage?: string;
|
|
|
+
|
|
|
+ @Column({ name: 'stack_trace', type: 'text', nullable: true, comment: '错误堆栈' })
|
|
|
+ stackTrace?: string;
|
|
|
+
|
|
|
+ @Column({ name: 'permission_required', type: 'varchar', length: 255, nullable: true, comment: '所需权限' })
|
|
|
+ permissionRequired?: string;
|
|
|
+
|
|
|
+ @CreateDateColumn({ name: 'created_at' })
|
|
|
+ createdAt!: Date;
|
|
|
+
|
|
|
+ @UpdateDateColumn({ name: 'updated_at' })
|
|
|
+ updatedAt!: Date;
|
|
|
+}
|
|
|
+
|
|
|
+// Zod Schema定义
|
|
|
+export const OperationLogSchema = z.object({
|
|
|
+ id: z.number().int().positive().openapi({
|
|
|
+ description: '操作日志ID',
|
|
|
+ example: 1
|
|
|
+ }),
|
|
|
+ requestId: z.string().max(50).openapi({
|
|
|
+ description: '请求唯一标识',
|
|
|
+ example: 'req_20231215120000_abc123'
|
|
|
+ }),
|
|
|
+ userId: z.number().int().positive().nullable().openapi({
|
|
|
+ description: '操作用户ID',
|
|
|
+ example: 1001
|
|
|
+ }),
|
|
|
+ username: z.string().max(50).nullable().openapi({
|
|
|
+ description: '用户名',
|
|
|
+ example: 'admin'
|
|
|
+ }),
|
|
|
+ resourceType: z.string().max(50).openapi({
|
|
|
+ description: '资源类型',
|
|
|
+ example: 'client'
|
|
|
+ }),
|
|
|
+ resourceId: z.string().max(50).nullable().openapi({
|
|
|
+ description: '资源ID',
|
|
|
+ example: 'C20230001'
|
|
|
+ }),
|
|
|
+ action: z.string().max(50).openapi({
|
|
|
+ description: '操作动作',
|
|
|
+ example: 'update'
|
|
|
+ }),
|
|
|
+ method: z.string().max(10).openapi({
|
|
|
+ description: 'HTTP方法',
|
|
|
+ example: 'PUT'
|
|
|
+ }),
|
|
|
+ endpoint: z.string().max(255).openapi({
|
|
|
+ description: 'API端点',
|
|
|
+ example: '/api/v1/clients/C20230001'
|
|
|
+ }),
|
|
|
+ ipAddress: z.string().max(45).nullable().openapi({
|
|
|
+ description: 'IP地址',
|
|
|
+ example: '192.168.1.100'
|
|
|
+ }),
|
|
|
+ userAgent: z.string().max(500).nullable().openapi({
|
|
|
+ description: '用户代理',
|
|
|
+ example: 'Mozilla/5.0...'
|
|
|
+ }),
|
|
|
+ requestData: z.any().nullable().openapi({
|
|
|
+ description: '请求数据',
|
|
|
+ example: { name: '新客户名称' }
|
|
|
+ }),
|
|
|
+ responseData: z.any().nullable().openapi({
|
|
|
+ description: '响应数据',
|
|
|
+ example: { id: 'C20230001', name: '新客户名称' }
|
|
|
+ }),
|
|
|
+ beforeData: z.any().nullable().openapi({
|
|
|
+ description: '操作前数据',
|
|
|
+ example: { name: '旧客户名称' }
|
|
|
+ }),
|
|
|
+ afterData: z.any().nullable().openapi({
|
|
|
+ description: '操作后数据',
|
|
|
+ example: { name: '新客户名称' }
|
|
|
+ }),
|
|
|
+ duration: z.number().int().nonnegative().openapi({
|
|
|
+ description: '操作耗时(ms)',
|
|
|
+ example: 150
|
|
|
+ }),
|
|
|
+ status: z.enum(['success', 'failed', 'permission_denied']).openapi({
|
|
|
+ description: '操作状态',
|
|
|
+ example: 'success'
|
|
|
+ }),
|
|
|
+ errorMessage: z.string().nullable().openapi({
|
|
|
+ description: '错误信息',
|
|
|
+ example: '权限不足'
|
|
|
+ }),
|
|
|
+ stackTrace: z.string().nullable().openapi({
|
|
|
+ description: '错误堆栈',
|
|
|
+ example: 'Error: ...'
|
|
|
+ }),
|
|
|
+ permissionRequired: z.string().max(255).nullable().openapi({
|
|
|
+ description: '所需权限',
|
|
|
+ example: 'system:client:update'
|
|
|
+ }),
|
|
|
+ createdAt: z.date().openapi({
|
|
|
+ description: '创建时间',
|
|
|
+ example: '2023-12-15T12:00:00Z'
|
|
|
+ }),
|
|
|
+ updatedAt: z.date().openapi({
|
|
|
+ description: '更新时间',
|
|
|
+ example: '2023-12-15T12:00:00Z'
|
|
|
+ })
|
|
|
+});
|
|
|
+
|
|
|
+// 创建DTO
|
|
|
+export const CreateOperationLogDto = z.object({
|
|
|
+ requestId: z.string().max(50).openapi({
|
|
|
+ description: '请求唯一标识',
|
|
|
+ example: 'req_20231215120000_abc123'
|
|
|
+ }),
|
|
|
+ userId: z.number().int().positive().nullable().optional().openapi({
|
|
|
+ description: '操作用户ID',
|
|
|
+ example: 1001
|
|
|
+ }),
|
|
|
+ username: z.string().max(50).nullable().optional().openapi({
|
|
|
+ description: '用户名',
|
|
|
+ example: 'admin'
|
|
|
+ }),
|
|
|
+ resourceType: z.string().max(50).openapi({
|
|
|
+ description: '资源类型',
|
|
|
+ example: 'client'
|
|
|
+ }),
|
|
|
+ resourceId: z.string().max(50).nullable().optional().openapi({
|
|
|
+ description: '资源ID',
|
|
|
+ example: 'C20230001'
|
|
|
+ }),
|
|
|
+ action: z.string().max(50).openapi({
|
|
|
+ description: '操作动作',
|
|
|
+ example: 'update'
|
|
|
+ }),
|
|
|
+ method: z.string().max(10).openapi({
|
|
|
+ description: 'HTTP方法',
|
|
|
+ example: 'PUT'
|
|
|
+ }),
|
|
|
+ endpoint: z.string().max(255).openapi({
|
|
|
+ description: 'API端点',
|
|
|
+ example: '/api/v1/clients/C20230001'
|
|
|
+ }),
|
|
|
+ ipAddress: z.string().max(45).nullable().optional().openapi({
|
|
|
+ description: 'IP地址',
|
|
|
+ example: '192.168.1.100'
|
|
|
+ }),
|
|
|
+ userAgent: z.string().max(500).nullable().optional().openapi({
|
|
|
+ description: '用户代理',
|
|
|
+ example: 'Mozilla/5.0...'
|
|
|
+ }),
|
|
|
+ requestData: z.any().nullable().optional().openapi({
|
|
|
+ description: '请求数据',
|
|
|
+ example: { name: '新客户名称' }
|
|
|
+ }),
|
|
|
+ responseData: z.any().nullable().optional().openapi({
|
|
|
+ description: '响应数据',
|
|
|
+ example: { id: 'C20230001', name: '新客户名称' }
|
|
|
+ }),
|
|
|
+ beforeData: z.any().nullable().optional().openapi({
|
|
|
+ description: '操作前数据',
|
|
|
+ example: { name: '旧客户名称' }
|
|
|
+ }),
|
|
|
+ afterData: z.any().nullable().optional().openapi({
|
|
|
+ description: '操作后数据',
|
|
|
+ example: { name: '新客户名称' }
|
|
|
+ }),
|
|
|
+ duration: z.coerce.number().int().nonnegative().optional().openapi({
|
|
|
+ description: '操作耗时(ms)',
|
|
|
+ example: 150
|
|
|
+ }),
|
|
|
+ status: z.enum(['success', 'failed', 'permission_denied']).optional().openapi({
|
|
|
+ description: '操作状态',
|
|
|
+ example: 'success'
|
|
|
+ }),
|
|
|
+ errorMessage: z.string().nullable().optional().openapi({
|
|
|
+ description: '错误信息',
|
|
|
+ example: '权限不足'
|
|
|
+ }),
|
|
|
+ stackTrace: z.string().nullable().optional().openapi({
|
|
|
+ description: '错误堆栈',
|
|
|
+ example: 'Error: ...'
|
|
|
+ }),
|
|
|
+ permissionRequired: z.string().max(255).nullable().optional().openapi({
|
|
|
+ description: '所需权限',
|
|
|
+ example: 'system:client:update'
|
|
|
+ })
|
|
|
+});
|
|
|
+
|
|
|
+// 更新DTO
|
|
|
+export const UpdateOperationLogDto = z.object({
|
|
|
+ responseData: z.any().nullable().optional().openapi({
|
|
|
+ description: '响应数据',
|
|
|
+ example: { id: 'C20230001', name: '新客户名称' }
|
|
|
+ }),
|
|
|
+ afterData: z.any().nullable().optional().openapi({
|
|
|
+ description: '操作后数据',
|
|
|
+ example: { name: '新客户名称' }
|
|
|
+ }),
|
|
|
+ duration: z.coerce.number().int().nonnegative().optional().openapi({
|
|
|
+ description: '操作耗时(ms)',
|
|
|
+ example: 150
|
|
|
+ }),
|
|
|
+ status: z.enum(['success', 'failed', 'permission_denied']).optional().openapi({
|
|
|
+ description: '操作状态',
|
|
|
+ example: 'success'
|
|
|
+ }),
|
|
|
+ errorMessage: z.string().nullable().optional().openapi({
|
|
|
+ description: '错误信息',
|
|
|
+ example: '权限不足'
|
|
|
+ }),
|
|
|
+ stackTrace: z.string().nullable().optional().openapi({
|
|
|
+ description: '错误堆栈',
|
|
|
+ example: 'Error: ...'
|
|
|
+ })
|
|
|
+});
|