add base code
This commit is contained in:
parent
375eba0718
commit
29a9fc2264
@ -11,6 +11,11 @@ yarn add file:packages/mongobase
|
||||
# or
|
||||
yarn add link:packages/mongobase
|
||||
```
|
||||
db的配置, 请在环境变量中配置
|
||||
|
||||
```bash
|
||||
process.env.DB_DBNAME=xxx
|
||||
```
|
||||
|
||||
```typescript
|
||||
|
||||
|
90
dist/index.d.ts
vendored
Normal file
90
dist/index.d.ts
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
import * as tracer from 'tracer';
|
||||
import * as _typegoose_typegoose from '@typegoose/typegoose';
|
||||
import { ReturnModelType } from '@typegoose/typegoose';
|
||||
import * as _typegoose_typegoose_lib_types from '@typegoose/typegoose/lib/types';
|
||||
import { AnyParamConstructor } from '@typegoose/typegoose/lib/types';
|
||||
import * as mongoose from 'mongoose';
|
||||
import { Connection } from 'mongoose';
|
||||
|
||||
/**
|
||||
* 为model指定数据库连接
|
||||
* @param {string} name 数据库连接名字, 在config中必须要有对应的配置, 比如main, 则必须要有 db_main
|
||||
* */
|
||||
declare function dbconn(name?: string): (target: any) => void;
|
||||
|
||||
declare class NoJsonClass {
|
||||
private noJsonPropSet;
|
||||
addKey(className: string, propertyKey: string): void;
|
||||
checkExist(className: string, propertyKey: string): boolean;
|
||||
}
|
||||
/**
|
||||
* 在不需要toJson方法输出的字段上加上 @noJson
|
||||
* @return {{(target: Function): void, (target: Object, propertyKey: (string | symbol)): void}}
|
||||
*/
|
||||
declare function noJson(): (target: Object, propertyKey: string) => void;
|
||||
declare function checkJson(target: any, propertyKey: string): boolean;
|
||||
|
||||
declare const logger: tracer.Tracer.Logger<string>;
|
||||
|
||||
declare abstract class BaseModule {
|
||||
static db: Connection;
|
||||
updateFromReq(data: any): void;
|
||||
/**
|
||||
* 插入或更新
|
||||
* @param condition
|
||||
* @param data
|
||||
*/
|
||||
static insertOrUpdate<T extends BaseModule>(this: ReturnModelType<AnyParamConstructor<T>>, condition: any, data: any): mongoose.QueryWithHelpers<mongoose.IfAny<T, any, mongoose.Document<unknown, _typegoose_typegoose_lib_types.BeAnObject, T> & Omit<mongoose.Require_id<T>, "typegooseName"> & _typegoose_typegoose_lib_types.IObjectWithTypegooseFunction>, mongoose.IfAny<T, any, mongoose.Document<unknown, _typegoose_typegoose_lib_types.BeAnObject, T> & Omit<mongoose.Require_id<T>, "typegooseName"> & _typegoose_typegoose_lib_types.IObjectWithTypegooseFunction>, _typegoose_typegoose_lib_types.BeAnObject, T, "findOneAndUpdate">;
|
||||
/**
|
||||
* 虚拟删除
|
||||
* @param {string[]} ids
|
||||
*/
|
||||
static deleteVirtual<T extends BaseModule>(this: ReturnModelType<AnyParamConstructor<T>>, ids: string[]): mongoose.QueryWithHelpers<mongoose.UpdateWriteOpResult, mongoose.IfAny<T, any, mongoose.Document<unknown, _typegoose_typegoose_lib_types.BeAnObject, T> & Omit<mongoose.Require_id<T>, "typegooseName"> & _typegoose_typegoose_lib_types.IObjectWithTypegooseFunction>, _typegoose_typegoose_lib_types.BeAnObject, T, "updateMany">;
|
||||
/**
|
||||
* 自定义分页查询
|
||||
* @param data
|
||||
* @param {boolean} json
|
||||
*/
|
||||
static pageQuery<T extends BaseModule>(this: ReturnModelType<AnyParamConstructor<T>>, data: any, json?: boolean): Promise<{
|
||||
records: mongoose.IfAny<T, any, mongoose.Document<unknown, _typegoose_typegoose_lib_types.BeAnObject, T> & Omit<mongoose.Require_id<T>, "typegooseName"> & _typegoose_typegoose_lib_types.IObjectWithTypegooseFunction>[];
|
||||
total: number;
|
||||
start: any;
|
||||
limit: any;
|
||||
}>;
|
||||
toJson(): any;
|
||||
/**
|
||||
* 通用的查询条件拼接方法
|
||||
* @param {{}} params req.params
|
||||
* @param options
|
||||
* sort: 排序 比如: {createdAt: 1} 默认是 {_id: 1}
|
||||
* opt: 设置一些特殊的过滤条件, 比如{deleted: 0}
|
||||
* timeKey: 如果需要查询创建时间, 而且创建时间不为 createdAt, 可以用此字段设置
|
||||
* matchKey: 指定关键字查询的匹配字段, 可为string或[string]
|
||||
*
|
||||
* @return {{opt: any, sort: {_id: number}}}
|
||||
*/
|
||||
static parseQueryParam(params: {}, options?: any): {
|
||||
opt: any;
|
||||
sort: {
|
||||
_id: number;
|
||||
};
|
||||
};
|
||||
getTimestampOfID(): any;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户操作记录
|
||||
*/
|
||||
declare class UserLogClass extends BaseModule {
|
||||
user: string;
|
||||
name: string;
|
||||
method: string;
|
||||
path: string;
|
||||
referer: string;
|
||||
user_agent: string;
|
||||
ip: string;
|
||||
params: any;
|
||||
}
|
||||
declare const UserLog: _typegoose_typegoose.ReturnModelType<typeof UserLogClass, _typegoose_typegoose_lib_types.BeAnObject>;
|
||||
|
||||
export { BaseModule, NoJsonClass, UserLog, checkJson, dbconn, logger, noJson };
|
4040
dist/index.js
vendored
Normal file
4040
dist/index.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
dist/index.js.map
vendored
Normal file
1
dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
19
package.json
19
package.json
@ -2,13 +2,28 @@
|
||||
"name": "mongobase",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "./dist/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"build": "tsup",
|
||||
"lint": "eslint --ext .ts src/**",
|
||||
"format": "eslint --ext .ts src/** --fix"
|
||||
},
|
||||
"author": "zhl",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@typegoose/typegoose": "^12.1.0",
|
||||
"mongoose": "^8.1.0",
|
||||
"tracer": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16",
|
||||
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
||||
"@typescript-eslint/parser": "^6.19.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
|
107
src/common/AsyncQueue.ts
Normal file
107
src/common/AsyncQueue.ts
Normal file
@ -0,0 +1,107 @@
|
||||
type Callback<T> = () => Promise<T>
|
||||
|
||||
export type AsyncQueue<T = void> = {
|
||||
push: (task: Callback<T>) => Promise<T>
|
||||
flush: () => Promise<void>
|
||||
size: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that each callback pushed onto the queue is executed in series.
|
||||
* Such a quetie 😻
|
||||
* @param opts.dedupeConcurrent If dedupeConcurrent is `true` it ensures that if multiple
|
||||
* tasks are pushed onto the queue while there is an active task, only the
|
||||
* last one will be executed, once the active task has completed.
|
||||
* e.g. in the below example, only 0 and 3 will be executed.
|
||||
* ```
|
||||
* const queue = createAsyncQueue({ dedupeConcurrent: true })
|
||||
* queue.push(async () => console.log(0)) // returns 0
|
||||
* queue.push(async () => console.log(1)) // returns 3
|
||||
* queue.push(async () => console.log(2)) // returns 3
|
||||
* queue.push(async () => console.log(3)) // returns 3
|
||||
* ```
|
||||
* */
|
||||
export function createAsyncQueue<T = void>(opts = { dedupeConcurrent: false }): AsyncQueue<T> {
|
||||
const { dedupeConcurrent } = opts
|
||||
let queue: Callback<T>[] = []
|
||||
let running: Promise<void> | undefined
|
||||
let nextPromise = new DeferredPromise<T>()
|
||||
const push = (task: Callback<T>) => {
|
||||
let taskPromise = new DeferredPromise<T>()
|
||||
if (dedupeConcurrent) {
|
||||
queue = []
|
||||
if (nextPromise.started) nextPromise = new DeferredPromise<T>()
|
||||
taskPromise = nextPromise
|
||||
}
|
||||
queue.push(() => {
|
||||
taskPromise.started = true
|
||||
task().then(taskPromise.resolve).catch(taskPromise.reject)
|
||||
return taskPromise.promise
|
||||
})
|
||||
if (!running) running = start()
|
||||
return taskPromise.promise
|
||||
}
|
||||
const start = async () => {
|
||||
while (queue.length) {
|
||||
const task = queue.shift()!
|
||||
await task().catch(() => {})
|
||||
}
|
||||
running = undefined
|
||||
}
|
||||
return {
|
||||
push,
|
||||
flush: () => running || Promise.resolve(),
|
||||
get size() {
|
||||
return queue.length
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const createAsyncQueues = <T = void>(opts = { dedupeConcurrent: false }) => {
|
||||
const queues: { [queueId: string]: AsyncQueue<T> } = {}
|
||||
const push = (queueId: string, task: Callback<T>) => {
|
||||
if (!queues[queueId]) queues[queueId] = createAsyncQueue<T>(opts)
|
||||
return queues[queueId].push(task)
|
||||
}
|
||||
const flush = (queueId: string) => {
|
||||
if (!queues[queueId]) queues[queueId] = createAsyncQueue<T>(opts)
|
||||
return queues[queueId].flush()
|
||||
}
|
||||
return { push, flush }
|
||||
}
|
||||
|
||||
class DeferredPromise<T = void, E = any> {
|
||||
started = false
|
||||
resolve: (x: T | PromiseLike<T>) => void = () => {}
|
||||
reject: (x: E) => void = () => {}
|
||||
promise: Promise<T>
|
||||
|
||||
constructor() {
|
||||
this.promise = new Promise<T>((res, rej) => {
|
||||
this.resolve = res
|
||||
this.reject = rej
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// function main() {
|
||||
// const queue = createAsyncQueue()
|
||||
// queue.push(async () => {
|
||||
// console.log(0)
|
||||
// }) // returns 0
|
||||
// queue.push(async () => {
|
||||
// console.log(1)
|
||||
|
||||
// return new Promise((resolve, reject) => {
|
||||
// setTimeout(() => {
|
||||
// console.log('12')
|
||||
// resolve()
|
||||
// }, 1000)
|
||||
// })
|
||||
// }) // returns 3
|
||||
// queue.push(async () => console.log(2)) // returns 3
|
||||
// queue.push(async () => console.log(3)) // returns 3
|
||||
// console.log('hi')
|
||||
// }
|
||||
|
||||
// main()
|
14
src/decorators/dbconn.ts
Normal file
14
src/decorators/dbconn.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { mongoose } from '@typegoose/typegoose'
|
||||
|
||||
/**
|
||||
* 为model指定数据库连接
|
||||
* @param {string} name 数据库连接名字, 在config中必须要有对应的配置, 比如main, 则必须要有 db_main
|
||||
* */
|
||||
export function dbconn(name?: string) {
|
||||
return target => {
|
||||
name = name || 'main'
|
||||
const dbName = ('db_' + name).toUpperCase()
|
||||
const url = process.env[dbName]
|
||||
target['db'] = mongoose.createConnection(url, {})
|
||||
}
|
||||
}
|
30
src/decorators/nojson.ts
Normal file
30
src/decorators/nojson.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import 'reflect-metadata'
|
||||
import { singleton } from './singleton'
|
||||
|
||||
|
||||
@singleton
|
||||
export class NoJsonClass {
|
||||
private noJsonPropSet: Set<string> = new Set()
|
||||
|
||||
public addKey(className: string, propertyKey: string) {
|
||||
this.noJsonPropSet.add(className + '_' + propertyKey)
|
||||
}
|
||||
|
||||
public checkExist(className: string, propertyKey: string) {
|
||||
return this.noJsonPropSet.has(className + '_' + propertyKey)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 在不需要toJson方法输出的字段上加上 @noJson
|
||||
* @return {{(target: Function): void, (target: Object, propertyKey: (string | symbol)): void}}
|
||||
*/
|
||||
export function noJson() {
|
||||
// return Reflect.metadata(noJsonMetadataKey, !0)
|
||||
return function (target: Object, propertyKey: string) {
|
||||
new NoJsonClass().addKey(target.constructor.name, propertyKey)
|
||||
}
|
||||
}
|
||||
|
||||
export function checkJson(target: any, propertyKey: string) {
|
||||
return !new NoJsonClass().checkExist(target.constructor.modelName, propertyKey)
|
||||
}
|
29
src/decorators/singleton.ts
Normal file
29
src/decorators/singleton.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 单例化一个class
|
||||
* 使用方法:
|
||||
* @singleton
|
||||
* class Test {}
|
||||
* new Test() === new Test() // returns `true`
|
||||
* 也可以不使用 decorator
|
||||
* const TestSingleton = singleton(Test)
|
||||
* new TestSingleton() === new TestSingleton() //returns 'true'
|
||||
*/
|
||||
|
||||
export const SINGLETON_KEY = Symbol()
|
||||
|
||||
export type Singleton<T extends new (...args: any[]) => any> = T & {
|
||||
[SINGLETON_KEY]: T extends new (...args: any[]) => infer I ? I : never
|
||||
}
|
||||
export const singleton = <T extends new (...args: any[]) => any>(classTarget: T) =>
|
||||
new Proxy(classTarget, {
|
||||
construct(target: Singleton<T>, argumentsList, newTarget) {
|
||||
// Skip proxy for children
|
||||
if (target.prototype !== newTarget.prototype) {
|
||||
return Reflect.construct(target, argumentsList, newTarget)
|
||||
}
|
||||
if (!target[SINGLETON_KEY]) {
|
||||
target[SINGLETON_KEY] = Reflect.construct(target, argumentsList, newTarget)
|
||||
}
|
||||
return target[SINGLETON_KEY]
|
||||
},
|
||||
})
|
5
src/index.ts
Normal file
5
src/index.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export * from './decorators/dbconn'
|
||||
export * from './decorators/nojson'
|
||||
export { logger } from './logger/logger'
|
||||
export { UserLog } from './models/UserLog'
|
||||
export { BaseModule } from './models/Base'
|
19
src/logger/logger.ts
Normal file
19
src/logger/logger.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { LoggerQueue } from 'queue/logger.queue'
|
||||
import { colorConsole } from 'tracer'
|
||||
|
||||
|
||||
declare module Tracer {
|
||||
interface Logger<M extends string> {
|
||||
db: (name: string, req: any, logObj?: any) => void
|
||||
}
|
||||
}
|
||||
|
||||
const level = process.env.NODE_ENV === 'production' ? 'info' : 'log'
|
||||
const _logger = colorConsole({ dateformat: 'yyyy-mm-dd HH:MM:ss.L', level })
|
||||
Object.assign(_logger, {
|
||||
db: (name: string, req: any, logObj?: any) => {
|
||||
logObj = logObj || {}
|
||||
new LoggerQueue().addLog(name, req, logObj)
|
||||
}
|
||||
})
|
||||
export const logger = _logger
|
216
src/models/Base.ts
Normal file
216
src/models/Base.ts
Normal file
@ -0,0 +1,216 @@
|
||||
import { checkJson } from '../decorators/nojson'
|
||||
import { ReturnModelType } from '@typegoose/typegoose'
|
||||
|
||||
import { Connection } from 'mongoose'
|
||||
import { ObjectId } from 'bson'
|
||||
import { AnyParamConstructor } from '@typegoose/typegoose/lib/types'
|
||||
|
||||
const jsonExcludeKeys = ['updatedAt', '__v']
|
||||
const saveExcludeKeys = ['createdAt', 'updatedAt', '__v', '_id']
|
||||
|
||||
const isTrue = (obj: any): boolean =>{
|
||||
return (
|
||||
obj === 'true' ||
|
||||
obj === 'TRUE' ||
|
||||
obj === 'True' ||
|
||||
obj === 'on' ||
|
||||
obj === 'ON' ||
|
||||
obj === true ||
|
||||
obj === 1 ||
|
||||
obj === '1' ||
|
||||
obj === 'YES' ||
|
||||
obj === 'yes'
|
||||
)
|
||||
}
|
||||
|
||||
export abstract class BaseModule {
|
||||
static db: Connection
|
||||
|
||||
public updateFromReq(data: any) {
|
||||
for (let key in data) {
|
||||
if (saveExcludeKeys.indexOf(key) == -1) {
|
||||
this[key] = data[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入或更新
|
||||
* @param condition
|
||||
* @param data
|
||||
*/
|
||||
public static insertOrUpdate<T extends BaseModule>(
|
||||
this: ReturnModelType<AnyParamConstructor<T>>,
|
||||
condition: any,
|
||||
data: any,
|
||||
) {
|
||||
return this.findOneAndUpdate(condition, data, { upsert: true, new: true, setDefaultsOnInsert: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 虚拟删除
|
||||
* @param {string[]} ids
|
||||
*/
|
||||
public static deleteVirtual<T extends BaseModule>(this: ReturnModelType<AnyParamConstructor<T>>, ids: string[]) {
|
||||
return this.updateMany(
|
||||
// @ts-ignore
|
||||
{
|
||||
_id: { $in: ids },
|
||||
},
|
||||
{
|
||||
$set: {
|
||||
deleted: true,
|
||||
deleteTime: new Date(),
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义分页查询
|
||||
* @param data
|
||||
* @param {boolean} json
|
||||
*/
|
||||
public static async pageQuery<T extends BaseModule>(
|
||||
this: ReturnModelType<AnyParamConstructor<T>>,
|
||||
data: any,
|
||||
json: boolean = false,
|
||||
) {
|
||||
let { start, limit, page } = data
|
||||
limit = +limit || 10
|
||||
start = +start || (+page - 1) * limit || 0
|
||||
// @ts-ignore
|
||||
let { opt, sort } = this.parseQueryParam(data)
|
||||
let records = await this.find(opt).sort(sort).skip(start).limit(limit)
|
||||
let total = await this.countDocuments(opt)
|
||||
if (json) {
|
||||
records.map(o => o.toJson())
|
||||
}
|
||||
return { records, total, start, limit }
|
||||
}
|
||||
|
||||
public toJson() {
|
||||
let result: any = {}
|
||||
// @ts-ignore
|
||||
for (let key in this._doc) {
|
||||
if (checkJson(this, key + '') && jsonExcludeKeys.indexOf(key) == -1) {
|
||||
result[key] = this[key]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的查询条件拼接方法
|
||||
* @param {{}} params req.params
|
||||
* @param options
|
||||
* sort: 排序 比如: {createdAt: 1} 默认是 {_id: 1}
|
||||
* opt: 设置一些特殊的过滤条件, 比如{deleted: 0}
|
||||
* timeKey: 如果需要查询创建时间, 而且创建时间不为 createdAt, 可以用此字段设置
|
||||
* matchKey: 指定关键字查询的匹配字段, 可为string或[string]
|
||||
*
|
||||
* @return {{opt: any, sort: {_id: number}}}
|
||||
*/
|
||||
public static parseQueryParam(params: {}, options?: any) {
|
||||
const opt: any = { deleted: { $ne: true } }
|
||||
// @ts-ignore
|
||||
let obj = this.schema.paths
|
||||
for (let key in params) {
|
||||
if (key !== 'sort' && obj.hasOwnProperty(key)) {
|
||||
switch (obj[key].instance) {
|
||||
case 'String':
|
||||
opt[key] = { $regex: params[key], $options: 'i' }
|
||||
break
|
||||
case 'Number':
|
||||
opt[key] = params[key]
|
||||
break
|
||||
case 'Array':
|
||||
if (Array.isArray(params[key])) {
|
||||
opt[key] = { $in: params[key] }
|
||||
} else {
|
||||
opt[key] = params[key]
|
||||
}
|
||||
break
|
||||
case 'Date':
|
||||
// TODO:
|
||||
break
|
||||
case 'Boolean':
|
||||
opt[key] = isTrue(params[key])
|
||||
break
|
||||
case 'ObjectID':
|
||||
if (/^[0-9a-fA-F]{24}$/.test(params[key])) {
|
||||
opt[key] = new ObjectId(params[key])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (params.hasOwnProperty('key') && params['key']) {
|
||||
let orArr = []
|
||||
if (options?.matchKey) {
|
||||
if (Array.isArray(options?.matchKey)) {
|
||||
for (let key in options?.matchKey) {
|
||||
let _tmp = {}
|
||||
_tmp[key] = { $regex: params['key'], $options: 'i' }
|
||||
orArr.push(_tmp)
|
||||
}
|
||||
} else {
|
||||
let _tmp = {}
|
||||
_tmp[options.matchKey] = { $regex: params['key'], $options: 'i' }
|
||||
orArr.push(_tmp)
|
||||
}
|
||||
} else {
|
||||
for (let key in obj) {
|
||||
if (obj[key].instance === 'String') {
|
||||
let _tmp = {}
|
||||
_tmp[key] = { $regex: params['key'], $options: 'i' }
|
||||
orArr.push(_tmp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(opt, { $or: orArr })
|
||||
}
|
||||
let timeKey = options?.timeKey ? options.timeKey : 'createdAt'
|
||||
if (params.hasOwnProperty('timeBegin') && !params.hasOwnProperty('timeEnd')) {
|
||||
let timeBegin = params['timeBegin']
|
||||
if (!(timeBegin instanceof Date)) {
|
||||
timeBegin = new Date(parseInt(timeBegin))
|
||||
}
|
||||
opt[timeKey] = { $gte: timeBegin }
|
||||
} else if (params.hasOwnProperty('timeBegin') && params.hasOwnProperty('timeEnd')) {
|
||||
let timeBegin = params['timeBegin']
|
||||
if (!(timeBegin instanceof Date)) {
|
||||
timeBegin = new Date(parseInt(timeBegin))
|
||||
}
|
||||
let timeEnd = params['timeEnd']
|
||||
if (!(timeEnd instanceof Date)) {
|
||||
timeEnd = new Date(parseInt(timeEnd))
|
||||
}
|
||||
let tmpB = {}
|
||||
tmpB[timeKey] = { $gte: timeBegin }
|
||||
let tmpE = {}
|
||||
tmpE[timeKey] = { $lte: timeEnd }
|
||||
opt['$and'] = [tmpB, tmpE]
|
||||
} else if (!params.hasOwnProperty('timeBegin') && params.hasOwnProperty('timeEnd')) {
|
||||
let timeEnd = params['timeEnd']
|
||||
if (!(timeEnd instanceof Date)) {
|
||||
timeEnd = new Date(parseInt(timeEnd))
|
||||
}
|
||||
opt[timeKey] = { $lte: timeEnd }
|
||||
}
|
||||
if (options?.opt) {
|
||||
Object.assign(opt, options.opt)
|
||||
}
|
||||
let sort = { _id: 1 }
|
||||
if (params.hasOwnProperty('sort')) {
|
||||
sort = params['sort']
|
||||
}
|
||||
return { opt, sort }
|
||||
}
|
||||
public getTimestampOfID() {
|
||||
// Extract the timestamp from the ObjectId
|
||||
// @ts-ignore
|
||||
return this._id.getTimestamp()
|
||||
}
|
||||
}
|
31
src/models/UserLog.ts
Normal file
31
src/models/UserLog.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { dbconn } from 'decorators/dbconn'
|
||||
import { getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose'
|
||||
import { Severity } from '@typegoose/typegoose/lib/internal/constants'
|
||||
import { BaseModule } from './Base'
|
||||
|
||||
/**
|
||||
* 用户操作记录
|
||||
*/
|
||||
@dbconn()
|
||||
@index({ user: 1 }, { unique: false })
|
||||
@index({ name: 1 }, { unique: false })
|
||||
@modelOptions({ schemaOptions: { collection: 'user_log', timestamps: true }, options: { allowMixed: Severity.ALLOW } })
|
||||
class UserLogClass extends BaseModule {
|
||||
@prop()
|
||||
public user: string
|
||||
@prop()
|
||||
public name: string
|
||||
@prop()
|
||||
public method: string
|
||||
@prop()
|
||||
public path: string
|
||||
@prop()
|
||||
public referer: string
|
||||
@prop()
|
||||
public user_agent: string
|
||||
@prop()
|
||||
public ip: string
|
||||
@prop({ type: mongoose.Schema.Types.Mixed })
|
||||
public params: any
|
||||
}
|
||||
export const UserLog = getModelForClass(UserLogClass, { existingConnection: UserLogClass['db'] })
|
39
src/queue/logger.queue.ts
Normal file
39
src/queue/logger.queue.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { AsyncQueue, createAsyncQueue } from 'common/AsyncQueue'
|
||||
import { singleton } from 'decorators/singleton'
|
||||
import { logger } from 'logger/logger'
|
||||
import { UserLog } from 'models/UserLog'
|
||||
|
||||
@singleton
|
||||
export class LoggerQueue {
|
||||
private queue: AsyncQueue
|
||||
|
||||
constructor() {
|
||||
this.queue = createAsyncQueue()
|
||||
}
|
||||
|
||||
public addLog(name: string, req: any, logObj: any = {}) {
|
||||
this.queue.push(async () => {
|
||||
const user = req.user
|
||||
const ip = req.headers['x-forwarded-for'] || req.ip
|
||||
const path = req.url
|
||||
const params = req.method === 'GET' ? req.query : req.body
|
||||
const dataObj = JSON.stringify(logObj) === '{}' ? params : logObj
|
||||
try {
|
||||
const history = new UserLog({
|
||||
user: user ? user.id : '',
|
||||
path: path,
|
||||
method: req.method,
|
||||
params: dataObj,
|
||||
referer: req.headers['referer'],
|
||||
user_agent: req.headers['user-agent'],
|
||||
ip,
|
||||
name,
|
||||
})
|
||||
await history.save()
|
||||
} catch (err) {
|
||||
logger.error('error add user log: ')
|
||||
logger.error(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -5,17 +5,17 @@
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"module": "commonjs",
|
||||
"module": "esnext",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"target": "es2020",
|
||||
"target": "es2022",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./src",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"lib": ["es2020"],
|
||||
"lib": ["es2021"],
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
"typings/extend.d.ts"
|
||||
|
10
tsup.config.ts
Normal file
10
tsup.config.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { defineConfig } from "tsup";
|
||||
|
||||
export default defineConfig({
|
||||
entry: ["src/index.ts"],
|
||||
format: ["esm"], // Build for and ESmodules
|
||||
dts: true, // Generate declaration file (.d.ts)
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
clean: true,
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user