mirror of
https://github.com/certd/certd.git
synced 2026-07-14 17:53:28 +08:00
feat: 支持审计日志,操作日志
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
/// <reference types="mocha" />
|
||||
|
||||
import assert from "node:assert/strict";
|
||||
import { AuditLogContext } from "./audit.js";
|
||||
|
||||
// AuditLog decorator and getAuditLogOptions are removed since auditLog()
|
||||
// now signals audit intent directly via ctx.auditLog.enabled
|
||||
|
||||
describe("AuditLogContext type", () => {
|
||||
it("supports enabled flag", () => {
|
||||
const ctx: AuditLogContext = {
|
||||
type: "pipeline",
|
||||
action: "删除流水线",
|
||||
append: ["ID:5"],
|
||||
content: "删除了流水线(ID:5)",
|
||||
projectId: 3,
|
||||
projectName: "默认项目",
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
assert.equal(ctx.enabled, true);
|
||||
assert.equal(ctx.type, "pipeline");
|
||||
assert.equal(ctx.content, "删除了流水线(ID:5)");
|
||||
assert.equal(ctx.projectId, 3);
|
||||
});
|
||||
|
||||
it("works with minimal fields", () => {
|
||||
const ctx: AuditLogContext = {
|
||||
enabled: true,
|
||||
append: ["提交2条"],
|
||||
};
|
||||
|
||||
assert.equal(ctx.enabled, true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
export type AuditLogOptions = {
|
||||
type?: string;
|
||||
action?: string;
|
||||
content?: string;
|
||||
template?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type AuditLogContext = {
|
||||
type?: string;
|
||||
action?: string;
|
||||
append?: string | string[];
|
||||
content?: string;
|
||||
projectId?: number;
|
||||
projectName?: string;
|
||||
enabled?: boolean;
|
||||
allowAnonymous?: boolean;
|
||||
scope?: string;
|
||||
userId?: number;
|
||||
username?: string;
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import type { IMidwayContainer } from "@midwayjs/core";
|
||||
import * as koa from "@midwayjs/koa";
|
||||
import { Constants } from "./constants.js";
|
||||
import { isEnterprise } from "./mode.js";
|
||||
import type { AuditLogContext } from "./audit.js";
|
||||
|
||||
export abstract class BaseController {
|
||||
@Inject()
|
||||
@@ -128,17 +129,42 @@ export abstract class BaseController {
|
||||
return { projectId, userId };
|
||||
}
|
||||
|
||||
async auditLog(bean: { type: string; action: string; content: string; projectId?: number; projectName?: string }) {
|
||||
const auditService: any = await this.applicationContext.getAsync("auditService");
|
||||
await auditService.log({
|
||||
userId: this.getUserId(),
|
||||
type: bean.type,
|
||||
action: bean.action,
|
||||
content: bean.content,
|
||||
username: this.ctx.user?.username,
|
||||
projectId: bean.projectId,
|
||||
projectName: bean.projectName,
|
||||
ipAddress: this.ctx.ip,
|
||||
});
|
||||
getAuditType(): string {
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
auditLog(bean: { type?: string; action?: string; content?: string; append?: string | string[]; projectId?: number; userId?: number; username?: string } = {}) {
|
||||
const auditLog = this.ensureAuditLogContext();
|
||||
auditLog.enabled = true;
|
||||
if (bean.userId != null) {
|
||||
auditLog.userId = bean.userId;
|
||||
}
|
||||
if (bean.username != null) {
|
||||
auditLog.username = bean.username;
|
||||
}
|
||||
if (bean.type != null) {
|
||||
auditLog.type = bean.type;
|
||||
}
|
||||
if (bean.action != null) {
|
||||
auditLog.action = bean.action;
|
||||
}
|
||||
if (bean.projectId != null) {
|
||||
auditLog.projectId = bean.projectId;
|
||||
}
|
||||
if (bean.content) {
|
||||
auditLog.content = bean.content;
|
||||
}
|
||||
if (bean.append) {
|
||||
const items = Array.isArray(bean.append) ? bean.append : [bean.append];
|
||||
const old = Array.isArray(auditLog.append) ? auditLog.append : auditLog.append ? [auditLog.append] : [];
|
||||
auditLog.append = [...old, ...items].filter(item => item && String(item).trim());
|
||||
}
|
||||
}
|
||||
|
||||
private ensureAuditLogContext(): AuditLogContext {
|
||||
if (!this.ctx.auditLog) {
|
||||
this.ctx.auditLog = {};
|
||||
}
|
||||
return this.ctx.auditLog;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ export abstract class BaseService<T> {
|
||||
return item != null && item != "";
|
||||
});
|
||||
}
|
||||
async batchDelete(ids: number[], userId: number, projectId?: number) {
|
||||
async batchDelete(ids: number[], userId: number, projectId?: number): Promise<number> {
|
||||
ids = this.filterIds(ids);
|
||||
if (userId != null) {
|
||||
const userProjectQuery = this.buildUserProjectQuery(userId, projectId);
|
||||
@@ -295,6 +295,7 @@ export abstract class BaseService<T> {
|
||||
}
|
||||
|
||||
await this.delete(ids);
|
||||
return ids.length;
|
||||
}
|
||||
|
||||
async findOne(options: FindOneOptions<T>) {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
export const Constants = {
|
||||
dataDir: './data',
|
||||
dataDir: "./data",
|
||||
role: {
|
||||
defaultUser: 3,
|
||||
},
|
||||
per: {
|
||||
//无需登录
|
||||
guest: '_guest_',
|
||||
guest: "_guest_",
|
||||
//无需登录
|
||||
anonymous: '_guest_',
|
||||
anonymous: "_guest_",
|
||||
//无需登录,有 token 时解析当前用户
|
||||
guestOptionalAuth: '_guestOptionalAuth_',
|
||||
guestOptionalAuth: "_guestOptionalAuth_",
|
||||
//仅需要登录
|
||||
authOnly: '_authOnly_',
|
||||
authOnly: "_authOnly_",
|
||||
//仅需要登录
|
||||
loginOnly: '_authOnly_',
|
||||
loginOnly: "_authOnly_",
|
||||
|
||||
open: '_open_',
|
||||
open: "_open_",
|
||||
},
|
||||
res: {
|
||||
serverError(message: string) {
|
||||
@@ -26,102 +26,102 @@ export const Constants = {
|
||||
},
|
||||
error: {
|
||||
code: 1,
|
||||
message: 'Internal server error',
|
||||
message: "Internal server error",
|
||||
},
|
||||
success: {
|
||||
code: 0,
|
||||
message: 'success',
|
||||
message: "success",
|
||||
},
|
||||
validation: {
|
||||
code: 10,
|
||||
message: '参数错误',
|
||||
message: "参数错误",
|
||||
},
|
||||
needvip: {
|
||||
code: 88,
|
||||
message: '需要VIP',
|
||||
message: "需要VIP",
|
||||
},
|
||||
needsuite: {
|
||||
code: 89,
|
||||
message: '需要购买或升级套餐',
|
||||
message: "需要购买或升级套餐",
|
||||
},
|
||||
loginError: {
|
||||
code: 2,
|
||||
message: '登录失败',
|
||||
message: "登录失败",
|
||||
},
|
||||
codeError: {
|
||||
code: 3,
|
||||
message: '验证码错误',
|
||||
message: "验证码错误",
|
||||
},
|
||||
auth: {
|
||||
code: 401,
|
||||
message: '您还未登录或token已过期',
|
||||
message: "您还未登录或token已过期",
|
||||
},
|
||||
permission: {
|
||||
code: 402,
|
||||
message: '您没有权限',
|
||||
message: "您没有权限",
|
||||
},
|
||||
param: {
|
||||
code: 400,
|
||||
message: '参数错误',
|
||||
message: "参数错误",
|
||||
},
|
||||
notFound: {
|
||||
code: 404,
|
||||
message: '页面/文件/资源不存在',
|
||||
message: "页面/文件/资源不存在",
|
||||
},
|
||||
|
||||
preview: {
|
||||
code: 10001,
|
||||
message: '对不起,预览环境不允许修改此数据',
|
||||
message: "对不起,预览环境不允许修改此数据",
|
||||
},
|
||||
siteOff:{
|
||||
siteOff: {
|
||||
code: 10010,
|
||||
message: '站点已关闭',
|
||||
message: "站点已关闭",
|
||||
},
|
||||
need2fa:{
|
||||
need2fa: {
|
||||
code: 10020,
|
||||
message: '需要2FA认证',
|
||||
message: "需要2FA认证",
|
||||
},
|
||||
openKeyError: {
|
||||
code: 20000,
|
||||
message: 'ApiToken错误',
|
||||
message: "ApiToken错误",
|
||||
},
|
||||
openKeySignError: {
|
||||
code: 20001,
|
||||
message: 'ApiToken签名错误',
|
||||
message: "ApiToken签名错误",
|
||||
},
|
||||
openKeyExpiresError: {
|
||||
code: 20002,
|
||||
message: 'ApiToken时间戳错误',
|
||||
message: "ApiToken时间戳错误",
|
||||
},
|
||||
openKeySignTypeError: {
|
||||
code: 20003,
|
||||
message: 'ApiToken签名类型不支持',
|
||||
message: "ApiToken签名类型不支持",
|
||||
},
|
||||
openParamError: {
|
||||
code: 20010,
|
||||
message: '请求参数错误',
|
||||
message: "请求参数错误",
|
||||
},
|
||||
openCertNotFound: {
|
||||
code: 20011,
|
||||
message: '证书不存在',
|
||||
message: "证书不存在",
|
||||
},
|
||||
openCertNotReady: {
|
||||
code: 20012,
|
||||
message: '证书还未生成',
|
||||
message: "证书还未生成",
|
||||
},
|
||||
openCertApplying: {
|
||||
code: 20013,
|
||||
message: '证书正在申请中,请稍后重新获取',
|
||||
message: "证书正在申请中,请稍后重新获取",
|
||||
},
|
||||
openDomainNoVerifier:{
|
||||
openDomainNoVerifier: {
|
||||
code: 20014,
|
||||
message: '域名校验方式未配置',
|
||||
message: "域名校验方式未配置",
|
||||
},
|
||||
openEmailNotFound: {
|
||||
code: 20021,
|
||||
message: '用户邮箱还未配置',
|
||||
message: "用户邮箱还未配置",
|
||||
},
|
||||
},
|
||||
systemUserId: 0, // 系统级别userid固定为0
|
||||
enterpriseUserId: -1 // 企业模式用户id固定为-1
|
||||
enterpriseUserId: -1, // 企业模式用户id固定为-1
|
||||
};
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import { Constants } from '../constants.js';
|
||||
import { BaseException } from './base-exception.js';
|
||||
import { Constants } from "../constants.js";
|
||||
import { BaseException } from "./base-exception.js";
|
||||
/**
|
||||
* 通用异常
|
||||
*/
|
||||
export class LoginErrorException extends BaseException {
|
||||
leftCount: number;
|
||||
constructor(message, leftCount: number) {
|
||||
super('LoginErrorException', Constants.res.loginError.code, message ? message : Constants.res.loginError.message);
|
||||
userId?: number;
|
||||
constructor(message, leftCount: number, userId?: number) {
|
||||
super("LoginErrorException", Constants.res.loginError.code, message ? message : Constants.res.loginError.message);
|
||||
this.leftCount = leftCount;
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
export * from './base-controller.js';
|
||||
export * from './constants.js';
|
||||
export * from './crud-controller.js';
|
||||
export * from './enum-item.js';
|
||||
export * from './exception/index.js';
|
||||
export * from './result.js';
|
||||
export * from './base-service.js';
|
||||
export * from "./mode.js"
|
||||
export * from "./base-controller.js";
|
||||
export * from "./constants.js";
|
||||
export * from "./crud-controller.js";
|
||||
export * from "./enum-item.js";
|
||||
export * from "./exception/index.js";
|
||||
export * from "./result.js";
|
||||
export * from "./base-service.js";
|
||||
export * from "./audit.js";
|
||||
export * from "./mode.js";
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
let adminMode = "saas"
|
||||
let adminMode = "saas";
|
||||
|
||||
export function setAdminMode(mode:string = "saas"){
|
||||
adminMode = mode
|
||||
export function setAdminMode(mode: string = "saas") {
|
||||
adminMode = mode;
|
||||
}
|
||||
export function getAdminMode(){
|
||||
return adminMode
|
||||
export function getAdminMode() {
|
||||
return adminMode;
|
||||
}
|
||||
|
||||
export function isEnterprise(){
|
||||
return adminMode === "enterprise"
|
||||
}
|
||||
export function isEnterprise() {
|
||||
return adminMode === "enterprise";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user