feat(tools): 抽象通用组件成包并接入 #27
Some checks failed
Egg Server MIflow / build-image (push) Failing after 30s

This commit is contained in:
zhaoyingbo 2024-08-21 00:52:38 +00:00
parent 09e352a9c1
commit 5a2990cd7e
15 changed files with 35 additions and 568 deletions

1
.npmrc Normal file
View File

@ -0,0 +1 @@
@egg:registry=https://git.yingbo.im:333/api/packages/zhaoyingbo/npm/

BIN
bun.lockb

Binary file not shown.

View File

@ -1,4 +1,5 @@
import loggerIns from "../../log"
import logger from "@egg/logger"
import appInfo from "../appInfo"
import pbClient from "../pbClient"
@ -20,7 +21,7 @@ const update = async (id: string, appName: string, value: string) => {
}
tokenCache[appName] = value
loggerIns.info(`reset ${appName} access token success: ${value}`)
logger.info(`reset ${appName} access token success: ${value}`)
}
/**

View File

@ -1,11 +1,12 @@
import loggerIns from "./log"
import logger from "@egg/logger"
import { makeCheckPathTool } from "@egg/path-tool"
import { manageBotReq } from "./routes/bot"
import { manageMessageReq } from "./routes/message"
import { manageMicroAppReq } from "./routes/microApp"
import { manageSheetReq } from "./routes/sheet"
import { initSchedule } from "./schedule"
import genContext from "./utils/genContext"
import { makeCheckPathTool } from "./utils/pathTools"
initSchedule()
@ -38,4 +39,4 @@ const server = Bun.serve({
port: 3000,
})
loggerIns.info(`Listening on ${server.hostname}:${server.port}`)
logger.info(`Listening on ${server.hostname}:${server.port}`)

View File

@ -1,55 +0,0 @@
import "winston-daily-rotate-file"
import winston, { format } from "winston"
const isProd = process.env.NODE_ENV === "production"
const transports: any[] = [
new winston.transports.Console({
level: "info",
}),
]
if (isProd) {
const config = {
datePattern: "YYYY-MM-DD",
zippedArchive: true,
maxSize: "20m",
maxFiles: "14d",
}
transports.push(
new winston.transports.DailyRotateFile({
level: "info",
filename: "/home/work/log/egg-info-%DATE%.log",
...config,
})
)
transports.push(
new winston.transports.DailyRotateFile({
level: "debug",
filename: "/home/work/log/egg-debug-%DATE%.log",
...config,
})
)
}
const loggerIns = winston.createLogger({
level: "silly",
format: format.combine(
format.colorize({
level: !isProd,
}), // 开发环境下输出彩色日志
format.simple(), // 简单文本格式化
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
format.printf(({ level, message, timestamp, requestId }) => {
const singleLineMessage = isProd
? message.replace(/\n/g, " ") // 将换行符替换为空格
: message
return `${timestamp} [${level}]${requestId ? ` [RequestId: ${requestId}]` : ""}: ${singleLineMessage}`
})
),
transports,
})
export default loggerIns

View File

@ -16,26 +16,30 @@
]
},
"devDependencies": {
"@commitlint/cli": "^19.3.0",
"@commitlint/cli": "^19.4.0",
"@commitlint/config-conventional": "^19.2.2",
"@eslint/js": "^9.7.0",
"@eslint/js": "^9.9.0",
"@types/node-schedule": "^2.1.7",
"@types/uuid": "^10.0.0",
"bun-types": "latest",
"eslint": "^9.7.0",
"bun-types": "^1.1.24",
"eslint": "^9.9.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"husky": "^9.1.1",
"lint-staged": "^15.2.7",
"husky": "^9.1.5",
"lint-staged": "^15.2.9",
"prettier": "^3.3.3",
"typescript-eslint": "^7.17.0"
"typescript-eslint": "^8.2.0"
},
"peerDependencies": {
"typescript": "^5.0.0"
"typescript": "^5.5.4"
},
"dependencies": {
"@egg/hooks": "^1.1.0",
"@egg/logger": "^1.2.1",
"@egg/net-tool": "^1.2.1",
"@egg/path-tool": "^1.2.1",
"node-schedule": "^2.1.1",
"p-limit": "^6.1.0",
"pocketbase": "^0.21.3",
"pocketbase": "^0.21.4",
"uuid": "^10.0.0",
"winston": "^3.14.2",
"winston-daily-rotate-file": "^5.0.0"

View File

@ -1,6 +1,7 @@
import { stringifyJson } from "@egg/hooks"
import db from "../../db"
import { Context, DB, LarkServer, MsgProxy } from "../../types"
import { safeJsonStringify } from "../../utils/pathTools"
const LOG_COLLECTION = "message_log"
@ -49,7 +50,7 @@ export const manageMessageReq = async (
// 处理消息内容
const finalContent =
typeof body.content !== "string"
? safeJsonStringify(body.content)
? stringifyJson(body.content)
: body.content
// 初始化发送结果对象

View File

@ -1,5 +1,6 @@
import { makeCheckPathTool } from "@egg/path-tool"
import { Context } from "../../types"
import { makeCheckPathTool } from "../../utils/pathTools"
/**
*

View File

@ -1,7 +1,7 @@
import logger from "@egg/logger"
import pLimit from "p-limit"
import db from "../db"
import loggerIns from "../log"
import { LarkService } from "../services"
export const resetAccessToken = async () => {
@ -23,7 +23,7 @@ export const resetAccessToken = async () => {
)
await Promise.allSettled(promiseList)
} catch (error: any) {
loggerIns
logger
.child({ requestId: "schedule" })
.error(`resetAccessToken error: ${error.message}`)
}

View File

@ -1,5 +1,6 @@
import { NetToolBase } from "@egg/net-tool"
import { LarkEvent } from "../../types"
import { NetToolBase } from "../../utils/netTool"
class AttachService extends NetToolBase {
/**

View File

@ -1,5 +1,6 @@
import { NetError, NetToolBase } from "@egg/net-tool"
import db from "../../db"
import { NetError, NetToolBase } from "../../utils/netTool"
class LarkBaseService extends NetToolBase {
constructor(appName: string, requestId: string) {

View File

@ -1,7 +1,7 @@
import { NetTool } from "@egg/net-tool"
import { Logger } from "winston"
import { AttachService, LarkService } from "../services"
import NetTool from "../utils/netTool"
export namespace Context {
export interface Data {

View File

@ -1,9 +1,9 @@
import loggerIns from "@egg/logger"
import { NetTool } from "@egg/net-tool"
import { v4 as uuid } from "uuid"
import loggerIns from "../log"
import { AttachService, LarkService } from "../services"
import { Context } from "../types"
import NetTool from "./netTool"
/**
*

View File

@ -1,424 +0,0 @@
import { Logger } from "winston"
import loggerIns from "../log"
interface NetRequestParams {
url: string
method: string
queryParams?: any
payload?: any
additionalHeaders?: any
}
interface NetErrorDetail {
httpStatus: number
code: number
message: string
}
export class NetError extends Error {
public code: number
public message: string
public httpStatus: number
constructor({ code, message, httpStatus }: NetErrorDetail) {
super(message)
this.code = code
this.message = message
this.httpStatus = httpStatus
}
}
/**
* HTTP请求的方法
*/
class NetToolBase {
protected prefix: string
protected headers: any
protected getHeaders: () => any
protected logger: Logger
protected requestId: string
/**
*
*
* @param {Object} params -
* @param {string} [params.prefix] - URL前缀
* @param {any} [params.headers] -
* @param {Function} [params.getHeaders] -
* @param {string} [params.requestId] - ID
*/
constructor({
prefix,
headers,
getHeaders,
requestId,
}: {
prefix?: string
headers?: any
getHeaders?: () => any
requestId?: string
} = {}) {
this.prefix = prefix || ""
this.headers = headers || {}
this.getHeaders = getHeaders || (() => ({}))
this.requestId = requestId || ""
this.logger = loggerIns.child({ requestId })
}
/**
*
* @param response -
* @param method - 使HTTP方法
* @param headers -
* @param requestBody -
* @param responseBody -
* @returns
*/
private logResponse(
response: Response,
method: string,
headers: any,
requestBody: any,
responseBody: any
) {
const responseLog = {
ok: response.ok,
status: response.status,
statusText: response.statusText,
url: response.url,
method: method,
requestHeaders: headers,
responseHeaders: response.headers,
requestBody,
responseBody,
}
this.logger.http(JSON.stringify(responseLog, null, 2))
return responseLog
}
/**
* Promise
* @param url - URL
* @param method - 使HTTP方法
* @param queryParams - URL中的查询参数
* @param payload -
* @param additionalHeaders -
* @returns Promise
* @throws
*/
protected async request<T = any>({
url,
method,
queryParams,
payload,
additionalHeaders,
}: NetRequestParams): Promise<T> {
// 拼接完整的URL
let fullUrl = `${this.prefix}${url}`
if (queryParams) {
if (typeof queryParams === "string") {
fullUrl = `${fullUrl}?${queryParams}`
} else {
const queryString = new URLSearchParams(queryParams).toString()
if (queryString) fullUrl = `${fullUrl}?${queryString}`
}
}
// 设置请求头
const headers = {
...this.headers,
...(await this.getHeaders()),
...additionalHeaders,
}
// 设置请求Header
if (!(payload instanceof FormData)) {
headers["Content-Type"] = "application/json"
}
// 处理请求数据
const body = payload instanceof FormData ? payload : JSON.stringify(payload)
// 发送请求
const res = await fetch(fullUrl, {
method,
body,
headers,
})
// 获取响应数据
let resData: any = null
let resText: string = ""
try {
resText = await res.text()
resData = JSON.parse(resText)
} catch {
/* empty */
}
// 记录响应
this.logResponse(res, method, headers, payload, resData || resText)
if (!res.ok) {
if (resData?.message || resData?.msg) {
throw new NetError({
httpStatus: res.status,
code: resData?.code,
message: resData?.message || resData?.msg,
})
}
throw new NetError({
httpStatus: res.status,
code: res.status,
message: resText || "网络响应异常",
})
}
// http 错误码正常,但解析异常
if (!resData) {
throw new NetError({
httpStatus: res.status,
code: 1,
message: "解析响应数据异常",
})
}
// 响应数据异常
if ("code" in resData && resData.code !== 0) {
throw new NetError({
httpStatus: res.status,
code: resData.code,
message: resData.message || resData.msg || "网络请求失败",
})
}
return resData as T
}
/**
* GET请求并返回一个解析为响应数据的Promise
*
* @param url - URL
* @param queryParams - URL中的查询参数
* @param additionalHeaders -
* @returns Promise
*/
protected get<T = any>(
url: string,
queryParams?: any,
additionalHeaders?: any
): Promise<T> {
return this.request({ url, method: "get", queryParams, additionalHeaders })
}
/**
* POST请求并返回一个解析为响应数据的Promise
*
* @param url - URL
* @param payload -
* @param queryParams - URL中的查询参数
* @param additionalHeaders -
* @returns Promise
*/
protected post<T = any>(
url: string,
payload?: any,
queryParams?: any,
additionalHeaders?: any
): Promise<T> {
return this.request({
url,
method: "post",
payload,
queryParams,
additionalHeaders,
})
}
/**
* PUT请求并返回一个解析为响应数据的Promise
*
* @param url - URL
* @param payload -
* @param queryParams - URL中的查询参数
* @param additionalHeaders -
* @returns Promise
*/
protected put<T = any>(
url: string,
payload: any,
queryParams?: any,
additionalHeaders?: any
): Promise<T> {
return this.request({
url,
method: "put",
payload,
queryParams,
additionalHeaders,
})
}
/**
* DELETE请求并返回一个解析为响应数据的Promise
*
* @param url - URL
* @param payload -
* @param queryParams - URL中的查询参数
* @param additionalHeaders -
* @returns Promise
*/
protected del<T = any>(
url: string,
payload: any,
queryParams?: any,
additionalHeaders?: any
): Promise<T> {
return this.request({
url,
method: "delete",
payload,
queryParams,
additionalHeaders,
})
}
/**
* PATCH请求并返回一个解析为响应数据的Promise
*
* @param url - URL
* @param payload -
* @param queryParams - URL中的查询参数
* @param additionalHeaders -
* @returns Promise
*/
protected patch<T = any>(
url: string,
payload: any,
queryParams?: any,
additionalHeaders?: any
): Promise<T> {
return this.request({
url,
method: "patch",
payload,
queryParams,
additionalHeaders,
})
}
}
class NetTool extends NetToolBase {
public request<T = any>({
url,
method,
queryParams,
payload,
additionalHeaders,
}: NetRequestParams): Promise<T> {
return super.request<T>({
url,
method,
queryParams,
payload,
additionalHeaders,
})
}
public get<T = any>(
url: string,
queryParams?: any,
additionalHeaders?: any
): Promise<T> {
return super.get<T>(url, queryParams, additionalHeaders)
}
public post<T = any>(
url: string,
payload?: any,
queryParams?: any,
additionalHeaders?: any
): Promise<T> {
return super.post<T>(url, payload, queryParams, additionalHeaders)
}
public put<T = any>(
url: string,
payload: any,
queryParams?: any,
additionalHeaders?: any
): Promise<T> {
return super.put<T>(url, payload, queryParams, additionalHeaders)
}
public del<T = any>(
url: string,
payload: any,
queryParams?: any,
additionalHeaders?: any
): Promise<T> {
return super.del<T>(url, payload, queryParams, additionalHeaders)
}
public patch<T = any>(
url: string,
payload: any,
queryParams?: any,
additionalHeaders?: any
): Promise<T> {
return super.patch<T>(url, payload, queryParams, additionalHeaders)
}
/**
* 400 Bad Request的响应对象
*
* @param message -
* @returns 400 Bad Request的响应对象
*/
badRequest(message: string) {
this.logger.error(`return a bad request response: ${message}`)
return Response.json(
{ code: 400, message, requestId: this.requestId },
{ status: 400 }
)
}
/**
* 404 Not Found的响应对象
*
* @param message -
* @returns 404 Not Found的响应对象
*/
notFound(message: string) {
this.logger.error(`return a not found response: ${message}`)
return Response.json(
{ code: 404, message, requestId: this.requestId },
{ status: 404 }
)
}
/**
* 500 Internal Server Error的响应对象
*
* @param message -
* @param data -
* @returns 500 Internal Server Error的响应对象
*/
serverError(message: string, data?: any) {
this.logger.error(`return a server error response: ${message}`)
return Response.json(
{ code: 500, message, data, requestId: this.requestId },
{ status: 500 }
)
}
/**
* 200 OK的响应对象
*
* @param data -
* @returns 200 OK的响应对象
*/
ok(data?: any) {
this.logger.info(`return a ok response: ${JSON.stringify(data)}`)
return Response.json({
code: 0,
message: "success",
data,
requestId: this.requestId,
})
}
}
export { NetToolBase }
export default NetTool

View File

@ -1,65 +0,0 @@
/**
*
* @param {string} url - URL
* @param {string} [prefix] -
* @returns {object}
*/
export const makeCheckPathTool = (url: string, prefix?: string) => {
const { pathname } = new URL(url)
const makePath = (path: string) => `${prefix || ""}${path}`
return {
/**
* URL
* @param {string} path -
* @returns {boolean} true false
*/
exactCheck: (path: string) => {
return pathname === makePath(path)
},
/**
* URL
* @param {string} path -
* @returns {boolean} URL true false
*/
startsWithCheck: (path: string) => pathname.startsWith(makePath(path)),
/**
* URL
* @param {string} path -
* @returns {boolean} URL true false
*/
fullCheck: (path: string) => pathname === path,
}
}
/**
* 20
*
* @param {string} path -
* @returns {string} - 20
*/
export const shortenPath = (path: string): string => {
if (path.length <= 20) {
return path
}
const parts = path.split("/")
if (parts.length <= 2) {
return path
}
return `.../${parts[parts.length - 2]}/${parts[parts.length - 1]}`
}
/**
* JSON
*
* @param {any} obj -
* @returns {string} - JSON
*/
export const safeJsonStringify = (obj: any) => {
try {
return JSON.stringify(obj)
} catch (e) {
return String(obj)
}
}