From aee6cca80518a84e2165565cebbf4eb6aee99790 Mon Sep 17 00:00:00 2001 From: sunzhuangzhuang <961120009@qq.com> Date: Thu, 25 Sep 2025 11:01:00 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env | 68 + .env.example | 64 + .gitignore | 2 + config/config.js | 17 + config/constants.js | 70 + config/database-init.js | 26 + config/dbv2.js | 363 ++++ config/logger.js | 73 + config/minio.js | 97 ++ config/wechatPay.js | 24 + database.js | 129 ++ logs/audit.log | 0 logs/combined.log | 51 + logs/error.log | 12 + middleware/auth.js | 110 ++ middleware/errorHandler.js | 129 ++ middleware/validation.js | 230 +++ package-lock.json | 3180 ++++++++++++++++++++++++++++++++++ package.json | 34 + routes/auth.js | 355 ++++ routes/captcha.js | 158 ++ routes/sms.js | 175 ++ routes/upload.js | 243 +++ routes/user.js | 0 server.js | 153 ++ services/alipayservice.js | 306 ++++ services/minioService.js | 293 ++++ services/wechatPayService.js | 609 +++++++ 28 files changed, 6971 insertions(+) create mode 100644 .env create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 config/config.js create mode 100644 config/constants.js create mode 100644 config/database-init.js create mode 100644 config/dbv2.js create mode 100644 config/logger.js create mode 100644 config/minio.js create mode 100644 config/wechatPay.js create mode 100644 database.js create mode 100644 logs/audit.log create mode 100644 logs/combined.log create mode 100644 logs/error.log create mode 100644 middleware/auth.js create mode 100644 middleware/errorHandler.js create mode 100644 middleware/validation.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 routes/auth.js create mode 100644 routes/captcha.js create mode 100644 routes/sms.js create mode 100644 routes/upload.js create mode 100644 routes/user.js create mode 100644 server.js create mode 100644 services/alipayservice.js create mode 100644 services/minioService.js create mode 100644 services/wechatPayService.js diff --git a/.env b/.env new file mode 100644 index 0000000..59a6f52 --- /dev/null +++ b/.env @@ -0,0 +1,68 @@ +# 数据库配置 +DB_HOST=114.55.111.44 +DB_USER=maov2 +DB_PASSWORD=5fYhw8z6T62b7heS +DB_NAME=maov2 + +# JWT密钥 +JWT_SECRET=NINGBOJURONGkejiyouxiangongsi202 + +# 阿里云短信服务配置 +# 请在阿里云控制台获取以下配置信息: +# 1. AccessKey ID 和 AccessKey Secret:在阿里云控制台 -> AccessKey管理中创建 +# 2. 短信签名:在阿里云短信服务控制台中申请并审核通过的签名 +# 3. 短信模板CODE:在阿里云短信服务控制台中申请并审核通过的模板CODE +ALIYUN_ACCESS_KEY_ID=LTAI5tBHymRUu1vvo5tgYpaa +ALIYUN_ACCESS_KEY_SECRET=lNsDZvpUVX2b3pfBQCBawOEyr3dNB9 +ALIYUN_SMS_SIGN_NAME=宁波炬融歆创科技 +ALIYUN_SMS_TEMPLATE_CODE=SMS_324470054 + +# 环境配置 +NODE_ENV=development +PORT=3005 + +# 前端地址配置 +FRONTEND_URL=https://www.zrbjr.com/frontend +# FRONTEND_URL=http://114.55.111.44:3001/frontend + +# MinIO 对象存储配置 +# MinIO服务器地址(不包含协议) +MINIO_ENDPOINT=114.55.111.44 +# MinIO服务器端口 +MINIO_PORT=9000 +# 是否使用SSL(true/false) +MINIO_USE_SSL=false +# MinIO访问密钥 +MINIO_ACCESS_KEY=minio +# MinIO秘密密钥 +MINIO_SECRET_KEY=CNy6fMCfyfeaEjbE +# MinIO公开访问地址(用于生成文件URL) +MINIO_PUBLIC_URL=https://minio.zrbjr.com + +# MinIO存储桶配置 +MINIO_BUCKET_UPLOADS=jurongquan +MINIO_BUCKET_AVATARS=jurongquan +MINIO_BUCKET_PRODUCTS=jurongquan +MINIO_BUCKET_DOCUMENTS=jurongquan + +#支付配置 +WECHAT_APP_ID=wx3a702dbe13fd2217 +WECHAT_MCH_ID=1726377336 +WECHAT_API_KEY=NINGBOJURONGkejiyouxiangongsi202 +WECHAT_API_V3_KEY=NINGBOJURONGkejiyouxiangongsi202 +WECHAT_CERT_PATH=./cert/apiclient_cert.pem +WECHAT_KEY_PATH=./cert/apiclient_key.pem +WECHAT_NOTIFY_URL=https://www.zrbjr.com/api/wechat-pay/notify + +# 支付宝配置 +# 请在支付宝开放平台获取以下配置信息: +# 1. 应用ID:在支付宝开放平台创建应用后获得 +# 2. 应用私钥和支付宝公钥现在从文件读取 +ALIPAY_APP_ID=2021005188682022 +ALIPAY_NOTIFY_URL=https://www.zrbjr.com/api/payment/alipay/notify +ALIPAY_RETURN_URL=https://www.zrbjr.com/payment +ALIPAY_QUIT_URL=https://www.zrbjr.com/payment/ +#ALIPAY_APP_ID=9021000151699946 +#ALIPAY_NOTIFY_URL=https://test.zrbjr.com/api/payment/alipay/notify +#ALIPAY_RETURN_URL=http://192.168.1.124:5173/frontend/payment +#ALIPAY_QUIT_URL=http://192.168.1.124:5173/frontend/payment diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..eba2d37 --- /dev/null +++ b/.env.example @@ -0,0 +1,64 @@ +# 数据库配置 +DB_HOST=114.55.111.44 +DB_USER=maov2 +DB_PASSWORD=5fYhw8z6T62b7heS +DB_NAME=maov2 + +# JWT密钥 +JWT_SECRET=your_jwt_secret_key + +# 阿里云短信服务配置 +# 请在阿里云控制台获取以下配置信息: +# 1. AccessKey ID 和 AccessKey Secret:在阿里云控制台 -> AccessKey管理中创建 +# 2. 短信签名:在阿里云短信服务控制台中申请并审核通过的签名 +# 3. 短信模板CODE:在阿里云短信服务控制台中申请并审核通过的模板CODE +ALIYUN_ACCESS_KEY_ID=LTAI5tBHymRUu1vvo5tgYpaa +ALIYUN_ACCESS_KEY_SECRET=lNsDZvpUVX2b3pfBQCBawOEyr3dNB9 +ALIYUN_SMS_SIGN_NAME=宁波炬融歆创科技 +ALIYUN_SMS_TEMPLATE_CODE=SMS_324470054 + +# 环境配置 +NODE_ENV=development +PORT=3000 + +# 前端地址配置 +FRONTEND_URL=https://www.zrbjr.com/frontend +# FRONTEND_URL=http://114.55.111.44:3001/frontend + +# MinIO 对象存储配置 +# MinIO服务器地址(不包含协议) +MINIO_ENDPOINT=114.55.111.44 +# MinIO服务器端口 +MINIO_PORT=9000 +# 是否使用SSL(true/false) +MINIO_USE_SSL=false +# MinIO访问密钥 +MINIO_ACCESS_KEY=minio +# MinIO秘密密钥 +MINIO_SECRET_KEY=CNy6fMCfyfeaEjbE +# MinIO公开访问地址(用于生成文件URL) +MINIO_PUBLIC_URL=https://minio.zrbjr.com + +# MinIO存储桶配置 +MINIO_BUCKET_UPLOADS=jurongquan +MINIO_BUCKET_AVATARS=jurongquan +MINIO_BUCKET_PRODUCTS=jurongquan +MINIO_BUCKET_DOCUMENTS=jurongquan + +#支付配置 +WECHAT_APP_ID=wx3a702dbe13fd2217 +WECHAT_MCH_ID=1726377336 +WECHAT_API_KEY=NINGBOJURONGkejiyouxiangongsi202 +WECHAT_API_V3_KEY=NINGBOJURONGkejiyouxiangongsi202 +WECHAT_CERT_PATH=./cert/apiclient_cert.pem +WECHAT_KEY_PATH=./cert/apiclient_key.pem +WECHAT_NOTIFY_URL=https://www.zrbjr.com/api/wechat-pay/notify + +# 支付宝配置 +# 请在支付宝开放平台获取以下配置信息: +# 1. 应用ID:在支付宝开放平台创建应用后获得 +# 2. 应用私钥和支付宝公钥现在从文件读取 +ALIPAY_APP_ID=2021005188682022 +ALIPAY_NOTIFY_URL=https://www.zrbjr.com/api/payment/alipay/notify +ALIPAY_RETURN_URL=https://www.zrbjr.com/payment/success +ALIPAY_QUIT_URL=https://www.zrbjr.com/payment/cancel \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a0f2b01 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/.idea diff --git a/config/config.js b/config/config.js new file mode 100644 index 0000000..3099ef2 --- /dev/null +++ b/config/config.js @@ -0,0 +1,17 @@ +const mysql = require('mysql2') + +const sql = { + createConnection() { + return mysql.createPool({ + connectionLimit: 10, + host: '114.55.111.44', + user: 'test_mao', + password: 'nK2mPbWriBp25BRd', + database: 'test_mao', + charset: 'utf8mb4', + multipleStatements: true + + }) + } +} +module.exports = sql diff --git a/config/constants.js b/config/constants.js new file mode 100644 index 0000000..3272b44 --- /dev/null +++ b/config/constants.js @@ -0,0 +1,70 @@ +// 系统常量配置 +module.exports = { + // 转账类型 + TRANSFER_TYPES: { + USER_TO_USER: 'user_to_user', + SYSTEM_TO_USER: 'system_to_user', + USER_TO_SYSTEM: 'user_to_system' + }, + + // 转账状态 + TRANSFER_STATUS: { + PENDING: 'pending', + CONFIRMED: 'confirmed', + RECEIVED: 'received', + REJECTED: 'rejected', + CANCELLED: 'cancelled', + NOT_RECEIVED: 'not_received', + FAILED: 'failed' + }, + + // 用户角色 + USER_ROLES: { + ADMIN: 'admin', + USER: 'user' + }, + + // 订单状态 + ORDER_STATUS: { + PENDING: 'pending', + PAID: 'paid', + SHIPPED: 'shipped', + DELIVERED: 'delivered', + CANCELLED: 'cancelled' + }, + + // 错误代码 + ERROR_CODES: { + VALIDATION_ERROR: 'VALIDATION_ERROR', + AUTHENTICATION_ERROR: 'AUTHENTICATION_ERROR', + AUTHORIZATION_ERROR: 'AUTHORIZATION_ERROR', + NOT_FOUND: 'NOT_FOUND', + DUPLICATE_ENTRY: 'DUPLICATE_ENTRY', + DATABASE_ERROR: 'DATABASE_ERROR', + INTERNAL_ERROR: 'INTERNAL_ERROR' + }, + + // HTTP状态码 + HTTP_STATUS: { + OK: 200, + CREATED: 201, + BAD_REQUEST: 400, + UNAUTHORIZED: 401, + FORBIDDEN: 403, + NOT_FOUND: 404, + CONFLICT: 409, + INTERNAL_SERVER_ERROR: 500 + }, + + // 分页默认值 + PAGINATION: { + DEFAULT_PAGE: 1, + DEFAULT_LIMIT: 10, + MAX_LIMIT: 100 + }, + + // JWT配置 + JWT: { + EXPIRES_IN: '24h' + } +}; \ No newline at end of file diff --git a/config/database-init.js b/config/database-init.js new file mode 100644 index 0000000..ff10297 --- /dev/null +++ b/config/database-init.js @@ -0,0 +1,26 @@ + +const { initDB, } = require('../database'); + +/** + * 数据库初始化函数 + * 创建所有必要的表结构和初始数据 + */ +async function initDatabase() { + try { + + + // 初始化数据库连接池 + await initDB(); + console.log('数据库连接池初始化成功'); + } catch (error) { + console.error('数据库初始化失败:', error); + throw error; + } +} + + + + +module.exports = { + initDatabase +}; \ No newline at end of file diff --git a/config/dbv2.js b/config/dbv2.js new file mode 100644 index 0000000..e7e253b --- /dev/null +++ b/config/dbv2.js @@ -0,0 +1,363 @@ +class QueryBuilder { + constructor() { + this.conditions = {}; + this.limit = null; + this.offset = null; + this.groupBy = null; + } + + where(condition, ...params) { + this.conditions[condition] = params; + return this; + } + + setLimit(limit) { + this.limit = limit; + return this; + } + + setOffset(offset) { + this.offset = offset; + return this; + } + + setGroupBy(groupBy) { + this.groupBy = groupBy; + return this; + } + + sqdata(sql, params) { + return new Promise((resolve, reject) => { + global.sqlReq.query(sql, params, (err, result) => { + if (err) { + reject(err); + } + resolve(result); + }); + }); + } + + getParams() { + return Object.values(this.conditions).flat(); + } + + buildConditions() { + return Object.keys(this.conditions).map(condition => `${condition}`).join(' AND '); + } +} + +class SelectBuilder extends QueryBuilder { + constructor() { + super(); + this.selectFields = []; + this.tables = []; + this.orderByField = ''; + this.orderByDirection = 'ASC'; + this.subQueries = []; // 用于存储子查询 + this.unions = []; // 存储UNION查询 + } + // 添加UNION查询 + union(queryBuilder, type = 'UNION') { + this.unions.push({queryBuilder, type}); + return this; + } + + // 添加UNION ALL查询 + unionAll(queryBuilder) { + this.union(queryBuilder, 'UNION ALL'); + return this; + } + + // 构建主查询部分(不含ORDER BY/LIMIT/OFFSET) + buildMainQuery() { + const subQuerySQL = this.subQueries.map(({alias, subQuery}) => `(${subQuery}) AS ${alias}`); + const selectClause = this.selectFields.concat(subQuerySQL).join(', '); + + let sql = `SELECT ${selectClause} + FROM ${this.tables.join(' ')}`; + + const conditionClauses = this.buildConditions(); + if (conditionClauses) { + sql += ` WHERE ${conditionClauses}`; + } + + if (this.groupBy) { + sql += ` GROUP BY ${this.groupBy}`; + } + + const params = this.getParams(); + return {sql, params}; + } + + // 供UNION查询调用的构建方法 + buildForUnion() { + return this.buildMainQuery(); + } + + select(fields) { + this.selectFields = fields.split(',').map(field => field.trim()); + return this; + } + + // 添加子查询 + addSubQuery(alias, subQuery) { + this.subQueries.push({alias, subQuery}); + return this; + } + + whereLike(fields, keyword) { + const likeConditions = fields.map(field => `${field} LIKE ?`).join(' OR '); + this.conditions[likeConditions] = fields.map(() => `%${keyword}%`); + return this; + } + + from(table) { + this.tables.push(table); + return this; + } + + leftJoin(table, condition) { + this.tables.push(`LEFT JOIN ${table} ON ${condition}`); + return this; + } + + orderBy(field, direction = 'ASC') { + this.orderByField = field; + this.orderByDirection = direction.toUpperCase(); + return this; + } + + paginate(page, pageSize) { + if (page <= 0 || pageSize <= 0) { + throw new Error('分页参数必须大于0'); + } + this.limit = pageSize; + this.offset = (page - 1) * pageSize; + return this; + } + + async chidBuild() { + + let sql = `SELECT ${this.selectFields.join(', ')} + FROM ${this.tables.join(' ')}`; + let conditionClauses = this.buildConditions(); + if (conditionClauses) { + sql += ` WHERE ${conditionClauses}`; + } + if (this.orderByField) { + sql += ` ORDER BY ${this.orderByField} ${this.orderByDirection}`; + } + if (this.limit !== null) { + sql += ` LIMIT ${this.limit}`; + } + if (this.offset !== null) { + sql += ` OFFSET ${this.offset}`; + } + return sql; + } + + async build() { + const main = this.buildMainQuery(); + let fullSql = `(${main.sql})`; + const allParams = [...main.params]; + + // 处理UNION部分 + for (const union of this.unions) { + const unionBuilder = union.queryBuilder; + if (!(unionBuilder instanceof SelectBuilder)) { + throw new Error('UNION query must be a SelectBuilder instance'); + } + const unionResult = unionBuilder.buildForUnion(); + fullSql += ` ${union.type} (${unionResult.sql})`; + allParams.push(...unionResult.params); + } + + // 添加ORDER BY、LIMIT、OFFSET + if (this.orderByField) { + fullSql += ` ORDER BY ${this.orderByField} ${this.orderByDirection}`; + } + if (this.limit !== null) { + fullSql += ` LIMIT ${this.limit}`; + } + if (this.offset !== null) { + fullSql += ` OFFSET ${this.offset}`; + } + console.log(fullSql,allParams); + return await this.sqdata(fullSql, allParams); + } +} + + +class UpdateBuilder extends QueryBuilder { + constructor() { + super(); + this.table = ''; + this.updateFields = {}; + } + + update(table) { + this.table = table; + return this; + } + + set(field, value) { + if (value && value.increment && typeof value === 'object' ) { + this.updateFields[field] = {increment: value.increment}; + } else { + this.updateFields[field] = value; + } + return this; + } + + async build() { + let sql = `UPDATE ${this.table} + SET `; + let updateClauses = Object.keys(this.updateFields).map(field => { + const value = this.updateFields[field]; + if (value && value.increment && typeof value === 'object' ) { + return `${field} = ${field} + ?`; + } + return `${field} = ?`; + }).join(', '); + + sql += updateClauses; + + let conditionClauses = this.buildConditions(); + if (conditionClauses) { + sql += ` WHERE ${conditionClauses}`; + } + // 处理参数,确保自增字段也传入增量值 + const params = [ + ...Object.values(this.updateFields).map(value => + (value && value.increment && typeof value === 'object' ) ? value.increment : value + ), + ...this.getParams() + ]; + return await this.sqdata(sql, params); + } +} + +class InsertBuilder extends QueryBuilder { + constructor() { + super(); + this.table = ''; + this.insertValues = []; + this.updateValues = {}; + } + + insertInto(table) { + this.table = table; + return this; + } + + // 仍然保留单条记录的插入 + values(values) { + if (Array.isArray(values)) { + this.insertValues = values; + } else { + this.insertValues = [values]; // 将单条记录包装成数组 + } + return this; + } + + // 新增方法,支持一次插入多条记录 + valuesMultiple(records) { + if (!Array.isArray(records) || records.length === 0) { + throw new Error('Values must be a non-empty array'); + } + + // 确保每一条记录都是对象 + records.forEach(record => { + if (typeof record !== 'object') { + throw new Error('Each record must be an object'); + } + }); + + this.insertValues = records; + return this; + } + + // 新增 upsert 方法,支持更新或插入 + upsert(values, updateFields) { + // values: 要插入的记录 + // updateFields: 如果记录存在时,需要更新的字段 + if (!Array.isArray(values) || values.length === 0) { + throw new Error('Values must be a non-empty array'); + } + + // 检查每条记录是否是对象 + values.forEach(record => { + if (typeof record !== 'object') { + throw new Error('Each record must be an object'); + } + }); + + this.insertValues = values; + this.updateValues = updateFields || {}; + return this; + } + + async build() { + if (this.insertValues.length === 0) { + throw new Error("No values to insert"); + } + + // 获取表单列名,假设所有记录有相同的字段 + const columns = Object.keys(this.insertValues[0]); + + // 构建 VALUES 子句,支持批量插入 + const valuePlaceholders = this.insertValues.map(() => + `(${columns.map(() => '?').join(', ')})` + ).join(', '); + + // 展平所有的插入值 + const params = this.insertValues.flatMap(record => + columns.map(column => record[column]) + ); + + // 如果有 updateFields,构建 ON DUPLICATE KEY UPDATE 子句 + let updateClause = ''; + if (Object.keys(this.updateValues).length > 0) { + updateClause = ' ON DUPLICATE KEY UPDATE ' + + Object.keys(this.updateValues).map(field => { + return `${field} = VALUES(${field})`; + }).join(', '); + } + + // 生成 SQL 语句 + const sql = `INSERT INTO ${this.table} (${columns.join(', ')}) + VALUES ${valuePlaceholders} ${updateClause}`; + // 执行查询 + return await this.sqdata(sql, params); + } +} + + +class DeleteBuilder extends QueryBuilder { + constructor() { + super(); + this.table = ''; + } + + deleteFrom(table) { + this.table = table; + return this; + } + + async build() { + let sql = `DELETE + FROM ${this.table}`; + let conditionClauses = this.buildConditions(); + if (conditionClauses) { + sql += ` WHERE ${conditionClauses}`; + } + return await this.sqdata(sql, this.getParams()); + } +} + +module.exports = { + SelectBuilder, + UpdateBuilder, + InsertBuilder, + DeleteBuilder, +}; diff --git a/config/logger.js b/config/logger.js new file mode 100644 index 0000000..45cda51 --- /dev/null +++ b/config/logger.js @@ -0,0 +1,73 @@ +const winston = require('winston'); +const path = require('path'); + +// 创建日志目录 +const logDir = path.join(__dirname, '../logs'); + +// 日志格式配置 +const logFormat = winston.format.combine( + winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss' + }), + winston.format.errors({ stack: true }), + winston.format.json() +); + +// 控制台日志格式 +const consoleFormat = winston.format.combine( + winston.format.colorize(), + winston.format.timestamp({ + format: 'YYYY-MM-DD HH:mm:ss' + }), + winston.format.printf(({ timestamp, level, message, ...meta }) => { + return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`; + }) +); + +// 创建logger实例 +const logger = winston.createLogger({ + level: process.env.LOG_LEVEL || 'info', + format: logFormat, + defaultMeta: { service: 'integrated-system' }, + transports: [ + // 错误日志文件 + new winston.transports.File({ + filename: path.join(logDir, 'error.log'), + level: 'error', + maxsize: 5242880, // 5MB + maxFiles: 5 + }), + // 所有日志文件 + new winston.transports.File({ + filename: path.join(logDir, 'combined.log'), + maxsize: 5242880, // 5MB + maxFiles: 5 + }) + ] +}); + +// 开发环境添加控制台输出 +if (process.env.NODE_ENV !== 'production') { + logger.add(new winston.transports.Console({ + format: consoleFormat + })); +} + +// 审计日志记录器 +const auditLogger = winston.createLogger({ + level: 'info', + format: logFormat, + defaultMeta: { service: 'audit' }, + transports: [ + new winston.transports.File({ + filename: path.join(logDir, 'audit.log'), + maxsize: 5242880, // 5MB + maxFiles: 10 + }) + ] +}); + +module.exports = { + logger, + auditLogger +}; \ No newline at end of file diff --git a/config/minio.js b/config/minio.js new file mode 100644 index 0000000..f479bea --- /dev/null +++ b/config/minio.js @@ -0,0 +1,97 @@ +const Minio = require('minio'); +require('dotenv').config(); + +/** + * MinIO 配置 + * 用于对象存储服务配置 + */ +const minioConfig = { + // MinIO 服务器配置 + endPoint: process.env.MINIO_ENDPOINT || 'localhost', + port: parseInt(process.env.MINIO_PORT) || 9000, + useSSL: process.env.MINIO_USE_SSL === 'true' || false, + accessKey: process.env.MINIO_ACCESS_KEY || 'minioadmin', + secretKey: process.env.MINIO_SECRET_KEY || 'minioadmin', + + // 存储桶配置 + buckets: { + uploads: process.env.MINIO_BUCKET_UPLOADS || 'uploads', + avatars: process.env.MINIO_BUCKET_AVATARS || 'avatars', + products: process.env.MINIO_BUCKET_PRODUCTS || 'products', + documents: process.env.MINIO_BUCKET_DOCUMENTS || 'documents' + }, + + // 文件访问配置 + publicUrl: process.env.MINIO_PUBLIC_URL || `http://localhost:9000` +}; + +/** + * 创建 MinIO 客户端实例 + */ +const createMinioClient = () => { + return new Minio.Client({ + endPoint: minioConfig.endPoint, + port: minioConfig.port, + useSSL: minioConfig.useSSL, + accessKey: minioConfig.accessKey, + secretKey: minioConfig.secretKey + }); +}; + +/** + * 初始化存储桶 + * 确保所有需要的存储桶都存在 + */ +const initializeBuckets = async () => { + const minioClient = createMinioClient(); + + try { + // 检查并创建存储桶 + for (const [key, bucketName] of Object.entries(minioConfig.buckets)) { + const exists = await minioClient.bucketExists(bucketName); + if (!exists) { + await minioClient.makeBucket(bucketName, 'us-east-1'); + console.log(`✅ 存储桶 '${bucketName}' 创建成功`); + + // 设置存储桶策略为公开读取(可选) + const policy = { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: { AWS: ['*'] }, + Action: ['s3:GetObject'], + Resource: [`arn:aws:s3:::${bucketName}/*`] + } + ] + }; + + try { + await minioClient.setBucketPolicy(bucketName, JSON.stringify(policy)); + console.log(`✅ 存储桶 '${bucketName}' 策略设置成功`); + } catch (policyError) { + console.warn(`⚠️ 存储桶 '${bucketName}' 策略设置失败:`, policyError.message); + } + } else { + console.log(`✅ 存储桶 '${bucketName}' 已存在`); + } + } + } catch (error) { + console.error('❌ 初始化存储桶失败:', error); + throw error; + } +}; + +/** + * 获取文件的公开访问URL + */ +const getPublicUrl = (bucketName, objectName) => { + return `${minioConfig.publicUrl}/${bucketName}/${objectName}`; +}; + +module.exports = { + minioConfig, + createMinioClient, + initializeBuckets, + getPublicUrl +}; \ No newline at end of file diff --git a/config/wechatPay.js b/config/wechatPay.js new file mode 100644 index 0000000..16b61f9 --- /dev/null +++ b/config/wechatPay.js @@ -0,0 +1,24 @@ +// 微信支付配置 +module.exports = { + // 微信支付配置 + wechatPay: { + appId: process.env.WECHAT_APP_ID || '', // 微信公众号AppID + mchId: process.env.WECHAT_MCH_ID || '', // 商户号 + apiKey: process.env.WECHAT_API_KEY || '', // API密钥 + apiV3Key: process.env.WECHAT_API_V3_KEY || '', // APIv3密钥 + notifyUrl: process.env.WECHAT_NOTIFY_URL || 'https://your-domain.com/api/wechat/notify', // 支付回调地址 + + // 证书路径(生产环境需要配置) + certPath: process.env.WECHAT_CERT_PATH || '', + keyPath: process.env.WECHAT_KEY_PATH || '', + + // 支付相关配置 + tradeType: { + h5: 'MWEB', // H5支付 + jsapi: 'JSAPI' // 公众号支付 + }, + + // 注册费用配置(单位:分) + registrationFee: 100 // 1元注册费 + } +}; \ No newline at end of file diff --git a/database.js b/database.js new file mode 100644 index 0000000..fa0f37c --- /dev/null +++ b/database.js @@ -0,0 +1,129 @@ +const mysql = require('mysql2/promise'); + +// 数据库配置 +const dbConfig = { + // host: process.env.DB_HOST || '114.55.111.44', + // user: process.env.DB_USER || 'maov2', + // password: process.env.DB_PASSWORD || '5fYhw8z6T62b7heS', + // database: process.env.DB_NAME || 'maov2', + host: '114.55.111.44', + user: 'test_mao', + password: 'nK2mPbWriBp25BRd', + database: 'test_mao', + charset: 'utf8mb4', + dateStrings: true, + // 连接池配置 + connectionLimit: 20, // 连接池最大连接数 + queueLimit: 0, // 排队等待连接的最大数量,0表示无限制 + // 确保参数正确处理 + supportBigNumbers: true, + bigNumberStrings: false +}; + +// 创建数据库连接池 +let pool; + +/** + * 初始化数据库连接池 + * @returns {Promise} 数据库连接池 + */ +async function initDB() { + if (!pool) { + try { + pool = mysql.createPool(dbConfig); + + // 添加连接池事件监听 + pool.on('connection', function (connection) { + console.log('新的数据库连接建立:', connection.threadId); + }); + pool.on('error', function (err) { + console.error('数据库连接池错误:', err); + if (err.code === 'PROTOCOL_CONNECTION_LOST') { + console.log('数据库连接丢失,尝试重新连接...'); + } else if (err.code === 'ECONNRESET') { + console.log('数据库连接被重置,尝试重新连接...'); + } else if (err.code === 'ETIMEDOUT') { + console.log('数据库连接超时,尝试重新连接...'); + } + }); + + // 测试连接 + const connection = await pool.getConnection(); + console.log('数据库连接池初始化成功'); + connection.release(); + + } catch (error) { + console.error('数据库连接池初始化失败:', error); + throw error; + } + } + return pool; +} + +/** + * 获取数据库连接池 + * @returns {mysql.Pool} 数据库连接池 + */ +function getDB() { + if (!pool) { + throw new Error('数据库连接池未初始化,请先调用 initDB()'); + } + return pool; +} + +/** + * 执行数据库查询(带重试机制) + * @param {string} sql SQL查询语句 + * @param {Array} params 查询参数 + * @param {number} retries 重试次数 + * @returns {Promise} 查询结果 + */ +async function executeQuery(sql, params = [], retries = 3) { + for (let i = 0; i < retries; i++) { + try { + const connection = await pool.getConnection(); + try { + const [results] = await connection.execute(sql, params); + connection.release(); + return results; + } catch (error) { + connection.release(); + throw error; + } + } catch (error) { + console.error(`数据库查询失败 (尝试 ${i + 1}/${retries}):`, error.message); + + if (i === retries - 1) { + throw error; + } + + // 如果是连接相关错误,等待后重试 + if (error.code === 'PROTOCOL_CONNECTION_LOST' || + error.code === 'ECONNRESET' || + error.code === 'ETIMEDOUT') { + console.log(`等待 ${(i + 1) * 1000}ms 后重试...`); + await new Promise(resolve => setTimeout(resolve, (i + 1) * 1000)); + } else { + throw error; + } + } + } +} + +/** + * 关闭数据库连接池 + */ +async function closeDB() { + if (pool) { + await pool.end(); + pool = null; + console.log('数据库连接池已关闭'); + } +} + +module.exports = { + initDB, + getDB, + closeDB, + dbConfig +}; \ No newline at end of file diff --git a/logs/audit.log b/logs/audit.log new file mode 100644 index 0000000..e69de29 diff --git a/logs/combined.log b/logs/combined.log new file mode 100644 index 0000000..fe34376 --- /dev/null +++ b/logs/combined.log @@ -0,0 +1,51 @@ +{"level":"info","message":"Server starting","port":"3005","service":"integrated-system","timestamp":"2025-09-24 10:51:48"} +{"environment":"development","level":"info","message":"Server started successfully","port":"3005","service":"integrated-system","timestamp":"2025-09-24 10:51:49"} +{"duration":"33ms","ip":"::ffff:192.168.0.12","level":"info","message":"HTTP Request","method":"POST","service":"integrated-system","statusCode":400,"timestamp":"2025-09-24 11:31:39","url":"/auth/login","userAgent":"Apifox/1.0.0 (https://apifox.com)"} +{"level":"info","message":"SIGINT received, shutting down gracefully","service":"integrated-system","timestamp":"2025-09-24 11:32:55"} +{"level":"info","message":"Server starting","port":"3005","service":"integrated-system","timestamp":"2025-09-24 11:32:56"} +{"environment":"development","level":"info","message":"Server started successfully","port":"3005","service":"integrated-system","timestamp":"2025-09-24 11:32:56"} +{"ip":"::ffff:192.168.0.15","level":"error","message":"Error occurred: 路径 /api/captcha/generate 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /api/captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:899:7\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:782:5","timestamp":"2025-09-24 11:37:32","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"duration":"7ms","ip":"::ffff:192.168.0.15","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 11:37:32","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"ip":"::ffff:192.168.0.15","level":"error","message":"Error occurred: 路径 /api/captcha/generate 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /api/captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:899:7\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:782:5","timestamp":"2025-09-24 11:38:02","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"duration":"19ms","ip":"::ffff:192.168.0.15","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 11:38:02","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"ip":"::ffff:192.168.0.15","level":"error","message":"Error occurred: 路径 /api/captcha/generate 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /api/captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:899:7\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:782:5","timestamp":"2025-09-24 11:38:03","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"duration":"11ms","ip":"::ffff:192.168.0.15","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 11:38:03","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"ip":"::ffff:192.168.0.15","level":"error","message":"Error occurred: 路径 /api/captcha/generate 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /api/captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:899:7\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:782:5","timestamp":"2025-09-24 11:38:05","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"duration":"16ms","ip":"::ffff:192.168.0.15","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 11:38:05","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"ip":"::ffff:192.168.0.15","level":"error","message":"Error occurred: 路径 /api/captcha/generate 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /api/captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:899:7\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:782:5","timestamp":"2025-09-24 11:38:05","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"duration":"3ms","ip":"::ffff:192.168.0.15","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 11:38:05","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"duration":"51ms","ip":"::ffff:192.168.0.15","level":"info","message":"HTTP Request","method":"POST","service":"integrated-system","statusCode":401,"timestamp":"2025-09-24 11:40:18","url":"/auth/login","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /auth/login 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /auth/login 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 14:36:11","url":"/auth/login","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"duration":"7ms","ip":"::ffff:192.168.0.11","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 14:36:11","url":"/auth/login","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /auth/login 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /auth/login 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 14:36:17","url":"/auth/login","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"duration":"3ms","ip":"::ffff:192.168.0.11","level":"info","message":"HTTP Request","method":"GET","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 14:36:17","url":"/auth/login","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"duration":"1ms","ip":"::ffff:192.168.0.11","level":"info","message":"HTTP Request","method":"POST","service":"integrated-system","statusCode":400,"timestamp":"2025-09-24 14:36:32","url":"/auth/login","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /captcha/generate 未找到","method":"POST","service":"integrated-system","stack":"Error: 路径 /captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 15:40:19","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"duration":"9ms","ip":"::ffff:192.168.0.11","level":"info","message":"HTTP Request","method":"POST","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 15:40:19","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /captcha/generate 未找到","method":"POST","service":"integrated-system","stack":"Error: 路径 /captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 15:40:23","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"duration":"3ms","ip":"::ffff:192.168.0.11","level":"info","message":"HTTP Request","method":"POST","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 15:40:23","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /captcha/generate 未找到","method":"POST","service":"integrated-system","stack":"Error: 路径 /captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 15:40:24","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"duration":"5ms","ip":"::ffff:192.168.0.11","level":"info","message":"HTTP Request","method":"POST","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 15:40:24","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /captcha/generate 未找到","method":"POST","service":"integrated-system","stack":"Error: 路径 /captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 15:40:25","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"duration":"5ms","ip":"::ffff:192.168.0.11","level":"info","message":"HTTP Request","method":"POST","service":"integrated-system","statusCode":404,"timestamp":"2025-09-24 15:40:25","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"duration":"2ms","ip":"::ffff:192.168.0.11","level":"info","message":"HTTP Request","method":"POST","service":"integrated-system","statusCode":400,"timestamp":"2025-09-24 15:50:02","url":"/auth/login","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"duration":"1ms","ip":"::ffff:192.168.0.11","level":"info","message":"HTTP Request","method":"POST","service":"integrated-system","statusCode":400,"timestamp":"2025-09-24 16:17:05","url":"/auth/login","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"level":"info","message":"SIGINT received, shutting down gracefully","service":"integrated-system","timestamp":"2025-09-24 16:17:53"} +{"level":"info","message":"Server starting","port":"3005","service":"integrated-system","timestamp":"2025-09-24 16:17:54"} +{"environment":"development","level":"info","message":"Server started successfully","port":"3005","service":"integrated-system","timestamp":"2025-09-24 16:17:54"} +{"duration":"125ms","ip":"::ffff:192.168.0.11","level":"info","message":"HTTP Request","method":"POST","service":"integrated-system","statusCode":401,"timestamp":"2025-09-24 16:22:40","url":"/auth/login","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"level":"info","message":"SIGINT received, shutting down gracefully","service":"integrated-system","timestamp":"2025-09-25 05:37:03"} +{"level":"info","message":"Server starting","port":"3005","service":"integrated-system","timestamp":"2025-09-25 09:17:08"} +{"environment":"development","level":"info","message":"Server started successfully","port":"3005","service":"integrated-system","timestamp":"2025-09-25 09:17:08"} +{"level":"info","message":"SIGINT received, shutting down gracefully","service":"integrated-system","timestamp":"2025-09-25 09:27:53"} +{"level":"info","message":"Server starting","port":"3005","service":"integrated-system","timestamp":"2025-09-25 09:27:53"} +{"environment":"development","level":"info","message":"Server started successfully","port":"3005","service":"integrated-system","timestamp":"2025-09-25 09:27:53"} +{"level":"info","message":"SIGINT received, shutting down gracefully","service":"integrated-system","timestamp":"2025-09-25 09:39:33"} +{"level":"info","message":"Server starting","port":"3005","service":"integrated-system","timestamp":"2025-09-25 09:39:48"} +{"environment":"development","level":"info","message":"Server started successfully","port":"3005","service":"integrated-system","timestamp":"2025-09-25 09:39:48"} +{"error":"key.startsWith is not a function","level":"error","message":"Uncaught Exception","service":"integrated-system","stack":"TypeError: key.startsWith is not a function\n at Timeout._onTimeout (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\routes\\sms.js:165:13)\n at listOnTimeout (node:internal/timers:581:17)\n at process.processTimers (node:internal/timers:519:7)","timestamp":"2025-09-25 09:41:48"} +{"level":"info","message":"Server starting","port":"3005","service":"integrated-system","timestamp":"2025-09-25 10:00:22"} +{"environment":"development","level":"info","message":"Server started successfully","port":"3005","service":"integrated-system","timestamp":"2025-09-25 10:00:23"} +{"level":"info","message":"SIGINT received, shutting down gracefully","service":"integrated-system","timestamp":"2025-09-25 10:20:43"} +{"level":"info","message":"Server starting","port":"3005","service":"integrated-system","timestamp":"2025-09-25 10:20:50"} +{"environment":"development","level":"info","message":"Server started successfully","port":"3005","service":"integrated-system","timestamp":"2025-09-25 10:20:50"} diff --git a/logs/error.log b/logs/error.log new file mode 100644 index 0000000..0d8bae7 --- /dev/null +++ b/logs/error.log @@ -0,0 +1,12 @@ +{"ip":"::ffff:192.168.0.15","level":"error","message":"Error occurred: 路径 /api/captcha/generate 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /api/captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:899:7\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:782:5","timestamp":"2025-09-24 11:37:32","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"ip":"::ffff:192.168.0.15","level":"error","message":"Error occurred: 路径 /api/captcha/generate 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /api/captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:899:7\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:782:5","timestamp":"2025-09-24 11:38:02","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"ip":"::ffff:192.168.0.15","level":"error","message":"Error occurred: 路径 /api/captcha/generate 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /api/captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:899:7\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:782:5","timestamp":"2025-09-24 11:38:03","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"ip":"::ffff:192.168.0.15","level":"error","message":"Error occurred: 路径 /api/captcha/generate 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /api/captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:899:7\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:782:5","timestamp":"2025-09-24 11:38:05","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"ip":"::ffff:192.168.0.15","level":"error","message":"Error occurred: 路径 /api/captcha/generate 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /api/captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:899:7\n at process.processTicksAndRejections (node:internal/process/task_queues:95:5)\n at async D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\express-rate-limit\\dist\\index.cjs:782:5","timestamp":"2025-09-24 11:38:05","url":"/api/captcha/generate","userAgent":"Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /auth/login 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /auth/login 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 14:36:11","url":"/auth/login","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /auth/login 未找到","method":"GET","service":"integrated-system","stack":"Error: 路径 /auth/login 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 14:36:17","url":"/auth/login","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /captcha/generate 未找到","method":"POST","service":"integrated-system","stack":"Error: 路径 /captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 15:40:19","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /captcha/generate 未找到","method":"POST","service":"integrated-system","stack":"Error: 路径 /captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 15:40:23","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /captcha/generate 未找到","method":"POST","service":"integrated-system","stack":"Error: 路径 /captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 15:40:24","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"ip":"::ffff:192.168.0.11","level":"error","message":"Error occurred: 路径 /captcha/generate 未找到","method":"POST","service":"integrated-system","stack":"Error: 路径 /captcha/generate 未找到\n at notFound (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\middleware\\errorHandler.js:107:17)\n at Layer.handleRequest (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\lib\\layer.js:152:17)\n at trimPrefix (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:342:13)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:297:9\n at processParams (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:582:12)\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:291:5)\n at D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:688:15\n at next (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:276:14)\n at Function.handle (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:186:3)\n at router (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\node_modules\\router\\index.js:60:12)","timestamp":"2025-09-24 15:40:25","url":"/captcha/generate","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36 Edg/92.0.902.67"} +{"error":"key.startsWith is not a function","level":"error","message":"Uncaught Exception","service":"integrated-system","stack":"TypeError: key.startsWith is not a function\n at Timeout._onTimeout (D:\\work\\客户\\毛总\\code\\jurong_Intermediate\\routes\\sms.js:165:13)\n at listOnTimeout (node:internal/timers:581:17)\n at process.processTimers (node:internal/timers:519:7)","timestamp":"2025-09-25 09:41:48"} diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 0000000..ddb8180 --- /dev/null +++ b/middleware/auth.js @@ -0,0 +1,110 @@ +const jwt = require('jsonwebtoken'); +const { getDB } = require('../database'); + +const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; // 在生产环境中应该使用环境变量 + +/** + * 用户认证中间件 + * 验证JWT令牌并检查用户状态(包括是否被拉黑) + */ +const auth = async (req, res, next) => { + try { + const token = req.header('Authorization')?.replace('Bearer ', ''); + + if (!token) { + return res.status(401).json({ success: false, message: '未提供认证令牌' }); + } + + const decoded = jwt.verify(token, JWT_SECRET); + const db = getDB(); + const [users] = await db.execute('SELECT * FROM users WHERE id = ?', [decoded.userId]); + + if (users.length === 0) { + return res.status(401).json({ success: false, message: '用户不存在' }); + } + + const user = users[0]; + + // 检查用户是否被拉黑 + if (user.is_blacklisted) { + return res.status(403).json({ + success: false, + message: '账户已被拉黑,请联系管理员', + code: 'USER_BLACKLISTED' + }); + } + + // 检查支付状态(管理员除外) + if (user.role !== 'admin' && user.payment_status === 'unpaid') { + return res.status(403).json({ + success: false, + message: '您的账户尚未激活,请完成支付后再使用', + code: 'PAYMENT_REQUIRED', + needPayment: true, + userId: user.id + }); + } + + req.user = user; + next(); + } catch (error) { + res.status(401).json({ success: false, message: '无效的认证令牌' }); + } +}; + +// 管理员认证中间件 +const adminAuth = (req, res, next) => { + if (req.user.role !== 'admin') { + return res.status(403).json({ success: false, message: '需要管理员权限' }); + } + next(); +}; + +/** + * 支付认证中间件 + * 只验证JWT令牌和用户状态,不检查支付状态 + * 用于支付相关接口,允许未支付用户创建支付订单 + */ +const paymentAuth = async (req, res, next) => { + try { + const token = req.header('Authorization')?.replace('Bearer ', ''); + + if (!token) { + return res.status(401).json({ success: false, message: '未提供认证令牌' }); + } + + const decoded = jwt.verify(token, JWT_SECRET); + const db = getDB(); + const [users] = await db.execute('SELECT * FROM users WHERE id = ?', [decoded.userId]); + + if (users.length === 0) { + return res.status(401).json({ success: false, message: '用户不存在' }); + } + + const user = users[0]; + + // 检查用户是否被拉黑 + if (user.is_blacklisted) { + return res.status(403).json({ + success: false, + message: '账户已被拉黑,请联系管理员', + code: 'USER_BLACKLISTED' + }); + } + + // 注意:这里不检查支付状态,允许未支付用户创建支付订单 + req.user = user; + next(); + } catch (error) { + console.error('支付认证失败:', error); + if (error.name === 'JsonWebTokenError') { + return res.status(401).json({ success: false, message: '无效的认证令牌' }); + } + if (error.name === 'TokenExpiredError') { + return res.status(401).json({ success: false, message: '认证令牌已过期' }); + } + return res.status(500).json({ success: false, message: '认证失败' }); + } +}; + +module.exports = { auth, adminAuth, paymentAuth, JWT_SECRET }; \ No newline at end of file diff --git a/middleware/errorHandler.js b/middleware/errorHandler.js new file mode 100644 index 0000000..eb26f22 --- /dev/null +++ b/middleware/errorHandler.js @@ -0,0 +1,129 @@ +const { logger } = require('../config/logger'); +const { ERROR_CODES, HTTP_STATUS } = require('../config/constants'); + +// 全局错误处理中间件 +const errorHandler = (err, req, res, next) => { + let error = { ...err }; + error.message = err.message; + + // 记录错误日志 + logger.error('Error occurred:', { + message: err.message, + stack: err.stack, + url: req.originalUrl, + method: req.method, + ip: req.ip, + userAgent: req.get('User-Agent'), + userId: req.user?.id + }); + + // MySQL错误处理 + if (err.code) { + switch (err.code) { + case 'ER_DUP_ENTRY': + error.message = '数据已存在'; + error.statusCode = HTTP_STATUS.CONFLICT; + error.errorCode = ERROR_CODES.DUPLICATE_ENTRY; + break; + case 'ER_NO_REFERENCED_ROW_2': + error.message = '关联数据不存在'; + error.statusCode = HTTP_STATUS.BAD_REQUEST; + error.errorCode = ERROR_CODES.VALIDATION_ERROR; + break; + case 'ER_ROW_IS_REFERENCED_2': + error.message = '数据正在被使用,无法删除'; + error.statusCode = HTTP_STATUS.CONFLICT; + error.errorCode = ERROR_CODES.VALIDATION_ERROR; + break; + case 'ECONNREFUSED': + error.message = '数据库连接失败'; + error.statusCode = HTTP_STATUS.INTERNAL_SERVER_ERROR; + error.errorCode = ERROR_CODES.DATABASE_ERROR; + break; + default: + error.message = '数据库操作失败'; + error.statusCode = HTTP_STATUS.INTERNAL_SERVER_ERROR; + error.errorCode = ERROR_CODES.DATABASE_ERROR; + } + } + + // JWT错误处理 + if (err.name === 'JsonWebTokenError') { + error.message = '无效的访问令牌'; + error.statusCode = HTTP_STATUS.UNAUTHORIZED; + error.errorCode = ERROR_CODES.AUTHENTICATION_ERROR; + } + + if (err.name === 'TokenExpiredError') { + error.message = '访问令牌已过期'; + error.statusCode = HTTP_STATUS.UNAUTHORIZED; + error.errorCode = ERROR_CODES.AUTHENTICATION_ERROR; + } + + // 参数验证错误 + if (err.name === 'ValidationError' || err.isJoi) { + const message = err.details ? err.details.map(detail => detail.message).join(', ') : err.message; + error.message = `参数验证失败: ${message}`; + error.statusCode = HTTP_STATUS.BAD_REQUEST; + error.errorCode = ERROR_CODES.VALIDATION_ERROR; + } + + // 业务逻辑错误处理 + if (err.message === '余额不足') { + error.message = '用户积分余额不足,无法完成转账操作。请先为用户充值积分或选择其他用户。'; + error.statusCode = HTTP_STATUS.BAD_REQUEST; + error.errorCode = ERROR_CODES.VALIDATION_ERROR; + } + + if (err.message === '用户不存在') { + error.message = '指定的用户不存在,请检查用户信息后重试。'; + error.statusCode = HTTP_STATUS.BAD_REQUEST; + error.errorCode = ERROR_CODES.VALIDATION_ERROR; + } + + // 自定义错误 + if (err.statusCode) { + error.statusCode = err.statusCode; + error.errorCode = err.errorCode || ERROR_CODES.INTERNAL_ERROR; + } + + // 默认错误 + const statusCode = error.statusCode || HTTP_STATUS.INTERNAL_SERVER_ERROR; + const errorCode = error.errorCode || ERROR_CODES.INTERNAL_ERROR; + const message = error.message || '服务器内部错误'; + + res.status(statusCode).json({ + success: false, + error: { + code: errorCode, + message: message + }, + ...(process.env.NODE_ENV === 'development' && { stack: err.stack }) + }); +}; + +// 404错误处理 +const notFound = (req, res, next) => { + const error = new Error(`路径 ${req.originalUrl} 未找到`); + error.statusCode = HTTP_STATUS.NOT_FOUND; + error.errorCode = ERROR_CODES.NOT_FOUND; + next(error); +}; + +// 自定义错误类 +class AppError extends Error { + constructor(message, statusCode, errorCode) { + super(message); + this.statusCode = statusCode; + this.errorCode = errorCode; + this.isOperational = true; + + Error.captureStackTrace(this, this.constructor); + } +} + +module.exports = { + errorHandler, + notFound, + AppError +}; \ No newline at end of file diff --git a/middleware/validation.js b/middleware/validation.js new file mode 100644 index 0000000..41e97f1 --- /dev/null +++ b/middleware/validation.js @@ -0,0 +1,230 @@ +const Joi = require('joi'); +const { AppError } = require('./errorHandler'); +const { ERROR_CODES, HTTP_STATUS } = require('../config/constants'); + +// 验证中间件工厂函数 +const validate = (schema) => { + return (req, res, next) => { + const { error } = schema.validate(req.body, { abortEarly: false }); + if (error) { + const errorMessage = error.details.map(detail => detail.message).join(', '); + return next(new AppError(errorMessage, HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR)); + } + next(); + }; +}; + +// 查询参数验证中间件 +const validateQuery = (schema) => { + return (req, res, next) => { + const { error } = schema.validate(req.query, { abortEarly: false }); + if (error) { + const errorMessage = error.details.map(detail => detail.message).join(', '); + return next(new AppError(errorMessage, HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR)); + } + next(); + }; +}; + +// 路径参数验证中间件 +const validateParams = (schema) => { + return (req, res, next) => { + const { error } = schema.validate(req.params, { abortEarly: false }); + if (error) { + const errorMessage = error.details.map(detail => detail.message).join(', '); + return next(new AppError(errorMessage, HTTP_STATUS.BAD_REQUEST, ERROR_CODES.VALIDATION_ERROR)); + } + next(); + }; +}; + +// 通用验证规则 +const commonSchemas = { + // ID验证 + id: Joi.number().integer().positive().required().messages({ + 'number.base': 'ID必须是数字', + 'number.integer': 'ID必须是整数', + 'number.positive': 'ID必须是正数', + 'any.required': 'ID是必需的' + }), + + // 分页验证 + pagination: Joi.object({ + page: Joi.number().integer().min(1).default(1).messages({ + 'number.base': '页码必须是数字', + 'number.integer': '页码必须是整数', + 'number.min': '页码必须大于0' + }), + limit: Joi.number().integer().min(1).max(100).default(10).messages({ + 'number.base': '每页数量必须是数字', + 'number.integer': '每页数量必须是整数', + 'number.min': '每页数量必须大于0', + 'number.max': '每页数量不能超过100' + }) + }) +}; + +// 用户相关验证规则 +const userSchemas = { + // 用户注册 + register: Joi.object({ + username: Joi.string().alphanum().min(3).max(30).required().messages({ + 'string.base': '用户名必须是字符串', + 'string.alphanum': '用户名只能包含字母和数字', + 'string.min': '用户名至少3个字符', + 'string.max': '用户名最多30个字符', + 'any.required': '用户名是必需的' + }), + password: Joi.string().min(6).max(128).required().messages({ + 'string.base': '密码必须是字符串', + 'string.min': '密码至少6个字符', + 'string.max': '密码最多128个字符', + 'any.required': '密码是必需的' + }), + phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required().messages({ + 'string.pattern.base': '手机号格式不正确', + 'any.required': '手机号是必需的' + }), + // 可选字段,注册时不需要填写 + real_name: Joi.string().max(50).allow('').optional().messages({ + 'string.max': '真实姓名最多50个字符' + }), + role: Joi.string().valid('admin', 'user').default('user').messages({ + 'any.only': '角色只能是admin或user' + }) + }), + + // 用户登录 + login: Joi.object({ + username: Joi.string().required().messages({ + 'any.required': '用户名是必需的' + }), + password: Joi.string().required().messages({ + 'any.required': '密码是必需的' + }) + }) +}; + +// 转账相关验证规则 +const transferSchemas = { + // 转账查询参数 + query: Joi.object({ + page: Joi.number().integer().min(1).default(1).messages({ + 'number.base': '页码必须是数字', + 'number.integer': '页码必须是整数', + 'number.min': '页码必须大于0' + }), + limit: Joi.number().integer().min(1).max(100).default(10).messages({ + 'number.base': '每页数量必须是数字', + 'number.integer': '每页数量必须是整数', + 'number.min': '每页数量必须大于0', + 'number.max': '每页数量不能超过100' + }), + status: Joi.string().valid('pending', 'confirmed', 'rejected', 'cancelled').allow('').messages({ + 'any.only': '状态值无效' + }), + type: Joi.string().valid('user_to_user', 'system_to_user', 'user_to_system').allow('').messages({ + 'any.only': '转账类型无效' + }), + search: Joi.string().allow('').max(100).messages({ + 'string.max': '搜索关键词最多100个字符' + }), + transfer_type: Joi.string().valid('user_to_user', 'system_to_user', 'user_to_system').allow('').messages({ + 'any.only': '转账类型无效' + }), + start_date: Joi.date().iso().allow('').messages({ + 'date.format': '开始日期格式不正确' + }), + end_date: Joi.date().iso().allow('').messages({ + 'date.format': '结束日期格式不正确' + }), + sort: Joi.string().valid('id', 'amount', 'created_at', 'updated_at', 'status').allow('').messages({ + 'any.only': '排序字段无效,只支持: id, amount, created_at, updated_at, status' + }), + order: Joi.string().valid('asc', 'desc').allow('').messages({ + 'any.only': '排序方向无效,只支持: asc, desc' + }), + // 优先显示待处理转账参数 + show_pending: Joi.alternatives().try( + Joi.boolean(), + Joi.string().valid('true', 'false', '') + ).allow('').messages({ + 'alternatives.match': 'show_pending参数只能是布尔值或字符串true/false' + }) + }), + + // 创建转账 + create: Joi.object({ + to_user_id: Joi.number().integer().positive().required().messages({ + 'number.base': '收款用户ID必须是数字', + 'number.integer': '收款用户ID必须是整数', + 'number.positive': '收款用户ID必须是正数', + 'any.required': '收款用户ID是必需的' + }), + amount: Joi.number().positive().precision(2).required().messages({ + 'number.base': '金额必须是数字', + 'number.positive': '金额必须是正数', + 'any.required': '金额是必需的' + }), + transfer_type: Joi.string().valid('user_to_user', 'system_to_user', 'user_to_system').required().messages({ + 'any.only': '转账类型无效', + 'any.required': '转账类型是必需的' + }), + description: Joi.string().max(500).allow('').messages({ + 'string.max': '描述最多500个字符' + }), + voucher_url: Joi.string().uri().allow('').messages({ + 'string.uri': '凭证URL格式不正确' + }) + }), + + // 确认转账 + confirm: Joi.object({ + transfer_id: Joi.number().integer().positive().required().messages({ + 'number.base': '转账ID必须是数字', + 'number.integer': '转账ID必须是整数', + 'number.positive': '转账ID必须是正数', + 'any.required': '转账ID是必需的' + }), + note: Joi.string().max(500).allow('').messages({ + 'string.max': '备注最多500个字符' + }) + }), + + // 拒绝转账 + reject: Joi.object({ + transfer_id: Joi.number().integer().positive().required().messages({ + 'number.base': '转账ID必须是数字', + 'number.integer': '转账ID必须是整数', + 'number.positive': '转账ID必须是正数', + 'any.required': '转账ID是必需的' + }), + note: Joi.string().max(500).allow('').messages({ + 'string.max': '备注最多500个字符' + }) + }) +}; +// 系统设置相关验证规则 +const systemSchemas = { + updateSettings: Joi.object({ + site_name: Joi.string().max(100).optional(), + site_description: Joi.string().max(500).optional(), + + contact_phone: Joi.string().max(20).optional(), + maintenance_mode: Joi.boolean().optional(), + max_transfer_amount: Joi.number().positive().optional(), + min_transfer_amount: Joi.number().positive().optional(), + transfer_fee_rate: Joi.number().min(0).max(1).optional() + }) +}; + +// 导出所有验证规则 +module.exports = { + validate, + validateQuery, + validateParams, + commonSchemas, + userSchemas, + transferSchemas, + systemSchemas +}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..08b1bec --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3180 @@ +{ + "name": "jurong_intermediate", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jurong_intermediate", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@alicloud/dysmsapi20170525": "^4.2.0", + "@alicloud/openapi-client": "^0.4.15", + "alipay-sdk": "^4.14.0", + "bcryptjs": "^3.0.2", + "body-parser": "^2.2.0", + "cors": "^2.8.5", + "crypto": "^1.0.1", + "dayjs": "^1.11.18", + "dotenv": "^17.2.2", + "express": "^5.1.0", + "express-rate-limit": "^8.1.0", + "express-validator": "^7.2.1", + "jsonwebtoken": "^9.0.2", + "minio": "^8.0.6", + "multer": "^2.0.2", + "mysql2": "^3.15.0", + "node-cron": "^4.2.1", + "node-rsa": "^1.1.1", + "qrcode": "^1.5.4", + "winston": "^3.17.0" + } + }, + "node_modules/@alicloud/credentials": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@alicloud/credentials/-/credentials-2.4.4.tgz", + "integrity": "sha512-/eRAGSKcniLIFQ1UCpDhB/IrHUZisQ1sc65ws/c2avxUMpXwH1rWAohb76SVAUJhiF4mwvLzLJM1Mn1XL4Xe/Q==", + "license": "MIT", + "dependencies": { + "@alicloud/tea-typescript": "^1.8.0", + "httpx": "^2.3.3", + "ini": "^1.3.5", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/darabonba-array": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-array/-/darabonba-array-0.1.2.tgz", + "integrity": "sha512-ZPuQ+bJyjrd8XVVm55kl+ypk7OQoi1ZH/DiToaAEQaGvgEjrTcvQkg71//vUX/6cvbLIF5piQDvhrLb+lUEIPQ==", + "license": "ISC", + "dependencies": { + "@alicloud/tea-typescript": "^1.7.1" + } + }, + "node_modules/@alicloud/darabonba-encode-util": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-encode-util/-/darabonba-encode-util-0.0.2.tgz", + "integrity": "sha512-mlsNctkeqmR0RtgE1Rngyeadi5snLOAHBCWEtYf68d7tyKskosXDTNeZ6VCD/UfrUu4N51ItO8zlpfXiOgeg3A==", + "license": "ISC", + "dependencies": { + "moment": "^2.29.1" + } + }, + "node_modules/@alicloud/darabonba-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-map/-/darabonba-map-0.0.1.tgz", + "integrity": "sha512-2ep+G3YDvuI+dRYVlmER1LVUQDhf9kEItmVB/bbEu1pgKzelcocCwAc79XZQjTcQGFgjDycf3vH87WLDGLFMlw==", + "license": "ISC", + "dependencies": { + "@alicloud/tea-typescript": "^1.7.1" + } + }, + "node_modules/@alicloud/darabonba-signature-util": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-signature-util/-/darabonba-signature-util-0.0.4.tgz", + "integrity": "sha512-I1TtwtAnzLamgqnAaOkN0IGjwkiti//0a7/auyVThdqiC/3kyafSAn6znysWOmzub4mrzac2WiqblZKFcN5NWg==", + "license": "ISC", + "dependencies": { + "@alicloud/darabonba-encode-util": "^0.0.1" + } + }, + "node_modules/@alicloud/darabonba-signature-util/node_modules/@alicloud/darabonba-encode-util": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-encode-util/-/darabonba-encode-util-0.0.1.tgz", + "integrity": "sha512-Sl5vCRVAYMqwmvXpJLM9hYoCHOMsQlGxaWSGhGWulpKk/NaUBArtoO1B0yHruJf1C5uHhEJIaylYcM48icFHgw==", + "license": "ISC", + "dependencies": { + "@alicloud/tea-typescript": "^1.7.1", + "moment": "^2.29.1" + } + }, + "node_modules/@alicloud/darabonba-string": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@alicloud/darabonba-string/-/darabonba-string-1.0.3.tgz", + "integrity": "sha512-NyWwrU8cAIesWk3uHL1Q7pTDTqLkCI/0PmJXC4/4A0MFNAZ9Ouq0iFBsRqvfyUujSSM+WhYLuTfakQXiVLkTMA==", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1" + } + }, + "node_modules/@alicloud/dysmsapi20170525": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@alicloud/dysmsapi20170525/-/dysmsapi20170525-4.2.0.tgz", + "integrity": "sha512-XXwOWUKEVFcfbqmJXw616ekHFp3jmaUC0OadYZs77TdMr0D13W41MuiFevKRNL09W5N0e+0GKdDErwvPuVGCpw==", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/openapi-core": "^1.0.0", + "@darabonba/typescript": "^1.0.0" + } + }, + "node_modules/@alicloud/endpoint-util": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@alicloud/endpoint-util/-/endpoint-util-0.0.1.tgz", + "integrity": "sha512-+pH7/KEXup84cHzIL6UJAaPqETvln4yXlD9JzlrqioyCSaWxbug5FUobsiI6fuUOpw5WwoB3fWAtGbFnJ1K3Yg==", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/gateway-pop": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@alicloud/gateway-pop/-/gateway-pop-0.0.6.tgz", + "integrity": "sha512-KF4I+JvfYuLKc3fWeWYIZ7lOVJ9jRW0sQXdXidZn1DKZ978ncfGf7i0LBfONGk4OxvNb/HD3/0yYhkgZgPbKtA==", + "license": "ISC", + "dependencies": { + "@alicloud/credentials": "^2", + "@alicloud/darabonba-array": "^0.1.0", + "@alicloud/darabonba-encode-util": "^0.0.2", + "@alicloud/darabonba-map": "^0.0.1", + "@alicloud/darabonba-signature-util": "^0.0.4", + "@alicloud/darabonba-string": "^1.0.2", + "@alicloud/endpoint-util": "^0.0.1", + "@alicloud/gateway-spi": "^0.0.8", + "@alicloud/openapi-util": "^0.3.2", + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "^1.4.8" + } + }, + "node_modules/@alicloud/gateway-spi": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@alicloud/gateway-spi/-/gateway-spi-0.0.8.tgz", + "integrity": "sha512-KM7fu5asjxZPmrz9sJGHJeSU+cNQNOxW+SFmgmAIrITui5hXL2LB+KNRuzWmlwPjnuA2X3/keq9h6++S9jcV5g==", + "license": "ISC", + "dependencies": { + "@alicloud/credentials": "^2", + "@alicloud/tea-typescript": "^1.7.1" + } + }, + "node_modules/@alicloud/openapi-client": { + "version": "0.4.15", + "resolved": "https://registry.npmjs.org/@alicloud/openapi-client/-/openapi-client-0.4.15.tgz", + "integrity": "sha512-4VE0/k5ZdQbAhOSTqniVhuX1k5DUeUMZv74degn3wIWjLY6Bq+hxjaGsaHYlLZ2gA5wUrs8NcI5TE+lIQS3iiA==", + "license": "ISC", + "dependencies": { + "@alicloud/credentials": "^2.4.2", + "@alicloud/gateway-spi": "^0.0.8", + "@alicloud/openapi-util": "^0.3.2", + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "1.4.9", + "@alicloud/tea-xml": "0.0.3" + } + }, + "node_modules/@alicloud/openapi-core": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@alicloud/openapi-core/-/openapi-core-1.0.5.tgz", + "integrity": "sha512-ed4EKyqHjb9zwrXUs6IRthha/pRn3OUoOcUKuhYu4tllp0RpidA+JYswsweN6sh26H0WIs/LB6nzJEOvh1d3fg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "@alicloud/credentials": "latest", + "@alicloud/gateway-pop": "0.0.6", + "@alicloud/gateway-spi": "^0.0.8", + "@darabonba/typescript": "^1.0.2" + } + }, + "node_modules/@alicloud/openapi-util": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@alicloud/openapi-util/-/openapi-util-0.3.2.tgz", + "integrity": "sha512-EC2JvxdcOgMlBAEG0+joOh2IB1um8CPz9EdYuRfTfd1uP8Yc9D8QRUWVGjP6scnj6fWSOaHFlit9H6PrJSyFow==", + "license": "ISC", + "dependencies": { + "@alicloud/tea-typescript": "^1.7.1", + "@alicloud/tea-util": "^1.3.0", + "kitx": "^2.1.0", + "sm3": "^1.0.3" + } + }, + "node_modules/@alicloud/tea-typescript": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@alicloud/tea-typescript/-/tea-typescript-1.8.0.tgz", + "integrity": "sha512-CWXWaquauJf0sW30mgJRVu9aaXyBth5uMBCUc+5vKTK1zlgf3hIqRUjJZbjlwHwQ5y9anwcu18r48nOZb7l2QQ==", + "license": "ISC", + "dependencies": { + "@types/node": "^12.0.2", + "httpx": "^2.2.6" + } + }, + "node_modules/@alicloud/tea-util": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@alicloud/tea-util/-/tea-util-1.4.9.tgz", + "integrity": "sha512-S0wz76rGtoPKskQtRTGqeuqBHFj8BqUn0Vh+glXKun2/9UpaaaWmuJwcmtImk6bJZfLYEShDF/kxDmDJoNYiTw==", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "kitx": "^2.0.0" + } + }, + "node_modules/@alicloud/tea-xml": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@alicloud/tea-xml/-/tea-xml-0.0.3.tgz", + "integrity": "sha512-+/9GliugjrLglsXVrd1D80EqqKgGpyA0eQ6+1ZdUOYCaRguaSwz44trX3PaxPu/HhIPJg9PsGQQ3cSLXWZjbAA==", + "license": "Apache-2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1", + "@types/xml2js": "^0.4.5", + "xml2js": "^0.6.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@darabonba/typescript": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@darabonba/typescript/-/typescript-1.0.3.tgz", + "integrity": "sha512-/y2y6wf5TsxD7pCPIm0OvTC+5qV0Tk7HQYxwpIuWRLXQLB0CRDvr6qk4bR6rTLO/JglJa8z2uCGZsaLYpQNqFQ==", + "license": "Apache License 2.0", + "dependencies": { + "@alicloud/tea-typescript": "^1.5.1", + "httpx": "^2.3.2", + "lodash": "^4.17.21", + "moment": "^2.30.1", + "moment-timezone": "^0.5.45", + "xml2js": "^0.6.2" + } + }, + "node_modules/@fidm/asn1": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@fidm/asn1/-/asn1-1.0.4.tgz", + "integrity": "sha512-esd1jyNvRb2HVaQGq2Gg8Z0kbQPXzV9Tq5Z14KNIov6KfFD6PTaRIO8UpcsYiTNzOqJpmyzWgVTrUwFV3UF4TQ==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@fidm/x509": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fidm/x509/-/x509-1.2.1.tgz", + "integrity": "sha512-nwc2iesjyc9hkuzcrMCBXQRn653XuAUKorfWM8PZyJawiy1QzLj4vahwzaI25+pfpwOLvMzbJ0uKpWLDNmo16w==", + "license": "MIT", + "dependencies": { + "@fidm/asn1": "^1.0.4", + "tweetnacl": "^1.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT" + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@types/xml2js": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz", + "integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "license": "(Unlicense OR Apache-2.0)", + "optional": true + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/alipay-sdk": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/alipay-sdk/-/alipay-sdk-4.14.0.tgz", + "integrity": "sha512-oiD/VP5Ei0RRacHHmE+N0uqgOj2xzce7c0fHrtyyh1P04O+o9I1r65LdGPzU8960J56xOxS/d3c+R/9lsPUH7g==", + "license": "MIT", + "dependencies": { + "@fidm/x509": "^1.2.1", + "bignumber.js": "^9.1.2", + "camelcase-keys": "^7.0.2", + "crypto-js": "^4.2.0", + "formstream": "^1.5.0", + "snakecase-keys": "^8.0.0", + "sse-decoder": "^1.0.0", + "urllib": "^4", + "utility": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/block-stream2": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/block-stream2/-/block-stream2-2.1.0.tgz", + "integrity": "sha512-suhjmLI57Ewpmq00qaygS8UgEq2ly2PCItenIyhMqVjo4t4pGzqMvfgJuX8iWTeSDdfSSqS6j38fL4ToNL7Pfg==", + "license": "MIT", + "dependencies": { + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/browser-or-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-2.1.1.tgz", + "integrity": "sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==", + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/camelcase-keys": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-7.0.2.tgz", + "integrity": "sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==", + "license": "MIT", + "dependencies": { + "camelcase": "^6.3.0", + "map-obj": "^4.1.0", + "quick-lru": "^5.1.1", + "type-fest": "^1.2.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", + "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", + "license": "ISC" + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.1.0.tgz", + "integrity": "sha512-4nLnATuKupnmwqiJc27b4dCFmB/T60ExgmtDD7waf4LdrbJ8CPZzZRHYErDYNhoz+ql8fUdYwM/opf90PoPAQA==", + "license": "MIT", + "dependencies": { + "ip-address": "10.0.1" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/express-validator": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz", + "integrity": "sha512-CjNE6aakfpuwGaHQZ3m8ltCG2Qvivd7RHtVMS/6nVxOM7xVGqr4bhflsm4+N5FP5zI7Zxp+Hae+9RE+o8e3ZOQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21", + "validator": "~13.12.0" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-xml-parser": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.3.tgz", + "integrity": "sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^1.1.1" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/formstream": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/formstream/-/formstream-1.5.2.tgz", + "integrity": "sha512-NASf0lgxC1AyKNXQIrXTEYkiX99LhCEXTkiGObXAkpBui86a4u8FjH1o2bGb3PpqI3kafC+yw4zWeK6l6VHTgg==", + "license": "MIT", + "dependencies": { + "destroy": "^1.0.4", + "mime": "^2.5.2", + "node-hex": "^1.0.1", + "pause-stream": "~0.0.11" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/httpx": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/httpx/-/httpx-2.3.3.tgz", + "integrity": "sha512-k1qv94u1b6e+XKCxVbLgYlOypVP9MPGpnN5G/vxFf6tDO4V3xpz3d6FUOY/s8NtPgaq5RBVVgSB+7IHpVxMYzw==", + "license": "MIT", + "dependencies": { + "@types/node": "^20", + "debug": "^4.1.1" + } + }, + "node_modules/httpx/node_modules/@types/node": { + "version": "20.19.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.17.tgz", + "integrity": "sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/kitx": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/kitx/-/kitx-2.2.0.tgz", + "integrity": "sha512-tBMwe6AALTBQJb0woQDD40734NKzb0Kzi3k7wQj9ar3AbP9oqhoVrdXPh7rk2r00/glIgd0YbToIUJsnxWMiIg==", + "license": "MIT", + "dependencies": { + "@types/node": "^22.5.4" + } + }, + "node_modules/kitx/node_modules/@types/node": { + "version": "22.18.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.6.tgz", + "integrity": "sha512-r8uszLPpeIWbNKtvWRt/DbVi5zbqZyj1PTmhRMqBMvDnaz1QpmSKujUtJLrqGZeoM8v72MfYggDceY4K1itzWQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/lru.min": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.2.tgz", + "integrity": "sha512-Nv9KddBcQSlQopmBHXSsZVY5xsdlZkdH/Iey0BlcBYggMd4two7cZnKOK9vmy3nY0O5RGH99z1PCeTpPqszUYg==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minio": { + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/minio/-/minio-8.0.6.tgz", + "integrity": "sha512-sOeh2/b/XprRmEtYsnNRFtOqNRTPDvYtMWh+spWlfsuCV/+IdxNeKVUMKLqI7b5Dr07ZqCPuaRGU/rB9pZYVdQ==", + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.4", + "block-stream2": "^2.1.0", + "browser-or-node": "^2.1.1", + "buffer-crc32": "^1.0.0", + "eventemitter3": "^5.0.1", + "fast-xml-parser": "^4.4.1", + "ipaddr.js": "^2.0.1", + "lodash": "^4.17.21", + "mime-types": "^2.1.35", + "query-string": "^7.1.3", + "stream-json": "^1.8.0", + "through2": "^4.0.2", + "web-encoding": "^1.1.5", + "xml2js": "^0.5.0 || ^0.6.2" + }, + "engines": { + "node": "^16 || ^18 || >=20" + } + }, + "node_modules/minio/node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/minio/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minio/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mysql2": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.0.tgz", + "integrity": "sha512-tT6pomf5Z/I7Jzxu8sScgrYBMK9bUFWd7Kbo6Fs1L0M13OOIJ/ZobGKS3Z7tQ8Re4lj+LnLXIQVZZxa3fhYKzA==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.1", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.0", + "long": "^5.2.1", + "lru.min": "^1.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/mysql2/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "license": "MIT", + "dependencies": { + "lru-cache": "^7.14.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-cron": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz", + "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==", + "license": "ISC", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-hex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/node-hex/-/node-hex-1.0.1.tgz", + "integrity": "sha512-iwpZdvW6Umz12ICmu9IYPRxg0tOLGmU3Tq2tKetejCj3oZd7b2nUXwP3a7QA5M9glWy8wlPS1G3RwM/CdsUbdQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/node-rsa": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-1.1.1.tgz", + "integrity": "sha512-Jd4cvbJMryN21r5HgxQOpMEqv+ooke/korixNNK3mGqfGJmy0M77WDDzo/05969+OkMy3XW1UuZsSmW9KQm7Fw==", + "license": "MIT", + "dependencies": { + "asn1": "^0.2.4" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "license": [ + "MIT", + "Apache2" + ], + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/sm3": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sm3/-/sm3-1.0.3.tgz", + "integrity": "sha512-KyFkIfr8QBlFG3uc3NaljaXdYcsbRy1KrSfc4tsQV8jW68jAktGeOcifu530Vx/5LC+PULHT0Rv8LiI8Gw+c1g==", + "license": "MIT" + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/snakecase-keys": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/snakecase-keys/-/snakecase-keys-8.1.0.tgz", + "integrity": "sha512-9/Eug2btrCiOi+9+vIXJnxUcKVfcbLy5Uwff4BrO6PQf3Oq/2iYQ/1zkmnrpIIjfel/DAasAlux7OvAmCa+Xnw==", + "license": "MIT", + "dependencies": { + "map-obj": "^4.2.0", + "snake-case": "^3.0.4", + "type-fest": "^4.15.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/snakecase-keys/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/sse-decoder": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sse-decoder/-/sse-decoder-1.0.0.tgz", + "integrity": "sha512-JPopy3jfNmPcUz5Ru6skKhHNRJbsvcEW6Z4SirKkucLS8Jya1Bmf4FVX8giOkLm8xQJ7kK68P6GXoVSTkbedUA==", + "license": "MIT", + "engines": { + "node": ">= 14.19.3" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause" + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "dependencies": { + "stream-chain": "^2.2.5" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strnum": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.1.2.tgz", + "integrity": "sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "license": "MIT" + }, + "node_modules/through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "license": "MIT", + "dependencies": { + "readable-stream": "3" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense" + }, + "node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.16.0.tgz", + "integrity": "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unescape": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unescape/-/unescape-1.0.1.tgz", + "integrity": "sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/urllib": { + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/urllib/-/urllib-4.8.2.tgz", + "integrity": "sha512-V5oo9kzQfF9UQAC9KOVFmmmbYPJ9nksgO8HM89BZse96QcCyjrssPVxKzL/9sVPRC8D4Sd3nAdaMCXAZ3dqEYA==", + "license": "MIT", + "dependencies": { + "form-data": "^4.0.1", + "formstream": "^1.5.1", + "mime-types": "^2.1.35", + "qs": "^6.12.1", + "type-fest": "^4.20.1", + "undici": "^7.1.1", + "ylru": "^2.0.0" + }, + "engines": { + "node": ">= 18.19.0" + } + }, + "node_modules/urllib/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/urllib/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/urllib/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utility": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/utility/-/utility-2.5.0.tgz", + "integrity": "sha512-lDbOVde5UAKgtxrSyZNhqrTA7f7anba6DTqbsDWgUFk6PZlmr7djqPYw0FnL5a6TbJvRt38VmYqt07zVLzXG2A==", + "license": "MIT", + "dependencies": { + "escape-html": "^1.0.3", + "unescape": "^1.0.1", + "ylru": "^2.0.0" + }, + "engines": { + "node": ">= 16.0.0" + } + }, + "node_modules/validator": { + "version": "13.12.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", + "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "license": "MIT", + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs-parser/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ylru": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-2.0.0.tgz", + "integrity": "sha512-T6hTrKcr9lKeUG0MQ/tO72D3UGptWVohgzpHG8ljU1jeBt2RCjcWxvsTPD8ZzUq1t1FvwROAw1kxg2euvg/THg==", + "license": "MIT", + "engines": { + "node": ">= 18.19.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1863fcb --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "dependencies": { + "@alicloud/dysmsapi20170525": "^4.2.0", + "@alicloud/openapi-client": "^0.4.15", + "alipay-sdk": "^4.14.0", + "bcryptjs": "^3.0.2", + "body-parser": "^2.2.0", + "cors": "^2.8.5", + "crypto": "^1.0.1", + "dayjs": "^1.11.18", + "dotenv": "^17.2.2", + "express": "^5.1.0", + "express-rate-limit": "^8.1.0", + "express-validator": "^7.2.1", + "jsonwebtoken": "^9.0.2", + "minio": "^8.0.6", + "multer": "^2.0.2", + "mysql2": "^3.15.0", + "node-cron": "^4.2.1", + "node-rsa": "^1.1.1", + "qrcode": "^1.5.4", + "winston": "^3.17.0" + }, + "name": "jurong_intermediate", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "" +} diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..fe3b9e7 --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,355 @@ +const express = require('express'); +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const {getDB} = require('../database'); + +const router = express.Router(); +const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key'; +router.post('/register', async (req, res) => { + try { + const db = getDB(); + await db.query('START TRANSACTION'); + + const { + username, + phone, + password, + city, + district_id: district, + province, + inviter = null, + captchaId, + captchaText, + smsCode, // 短信验证码 + role = 'user' + } = req.body; + + if (!username || !phone || !password || !city || !district || !province) { + return res.status(400).json({success: false, message: '用户名、手机号、密码、城市和区域不能为空'}); + } + + if (!captchaId || !captchaText) { + return res.status(400).json({success: false, message: '图形验证码不能为空'}); + } + const storedCaptcha = global.captchaStore.get(captchaId); + console.log(storedCaptcha); + + if (!storedCaptcha) { + return res.status(400).json({ + success: false, + message: '验证码不存在或已过期' + }); + } + + // 检查是否过期 + if (Date.now() > storedCaptcha.expires) { + global.captchaStore.delete(captchaId); + return res.status(400).json({ + success: false, + message: '验证码已过期' + }); + } + + // 验证验证码(不区分大小写) + const isValid = storedCaptcha.text === captchaText.toLowerCase(); + + // 删除已验证的验证码 + global.captchaStore.delete(captchaId); + + if (!isValid) { + return res.status(400).json({ + success: false, + message: '验证码错误' + }); + } + if (!smsCode) { + return res.status(400).json({success: false, message: '短信验证码不能为空'}); + } + // 验证短信验证码 + const smsAPI = require('./sms'); + const smsValid = smsAPI.verifySMSCode(phone, smsCode); + if (!smsValid) { + return res.status(400).json({success: false, message: '短信验证码错误或已过期'}); + } + + // 验证手机号格式 + const phoneRegex = /^1[3-9]\d{9}$/; + if (!phoneRegex.test(phone)) { + return res.status(400).json({success: false, message: '手机号格式不正确'}); + } + + + // 检查用户是否已存在 + const [existingUsers] = await db.execute( + 'SELECT id, payment_status FROM users WHERE username = ? OR phone = ?', + [username, phone] + ); + + if (existingUsers.length > 0) { + return res.status(400).json({success: false, message: '用户名或手机号已存在'}); + } + + // 加密密码 + const hashedPassword = await bcrypt.hash(password, 10); + + // 创建用户(初始状态为未支付) + const [result] = await db.execute( + 'INSERT INTO users (username, phone, password, role, points, audit_status, city, district_id, payment_status, province, inviter) VALUES (?, ?, ?, ?, ?, ?, ?, ?, "unpaid", ?, ?)', + [username, phone, hashedPassword, role, 0, 'pending', city, district, province, inviter] + ); + + const userId = result.insertId; + await db.query('COMMIT'); + + // 生成JWT token(用于支付流程) + const token = jwt.sign( + {userId: userId, username, role}, + JWT_SECRET, + {expiresIn: '24h'} + ); + + res.status(201).json({ + success: true, + message: '用户信息创建成功,请完成支付以激活账户', + token, + user: { + id: userId, + username, + phone, + role, + points: 0, + audit_status: 'pending', + city, + district, + paymentStatus: 'unpaid' + }, + needPayment: true + }); + } catch (error) { + try { + await getDB().query('ROLLBACK'); + } catch (rollbackError) { + console.error('回滚错误:', rollbackError); + } + console.error('注册错误详情:', error); + console.error('错误堆栈:', error.stack); + res.status(500).json({ + success: false, + message: '注册失败', + error: process.env.NODE_ENV === 'development' ? error.message : undefined + }); + } +}); + + +router.post('/login', async (req, res) => { + try { + const db = getDB(); + const {username, password, captchaId, captchaText,type} = req.body; + + if (!username || !password) { + return res.status(400).json({success: false, message: '用户名和密码不能为空'}); + } + + if (!captchaId || !captchaText) { + return res.status(400).json({success: false, message: '验证码不能为空'}); + } + // 获取存储的验证码 + const storedCaptcha = global.captchaStore.get(captchaId); + console.log(storedCaptcha); + + if (!storedCaptcha) { + return res.status(400).json({ + success: false, + message: '验证码不存在或已过期' + }); + } + + // 检查是否过期 + if (Date.now() > storedCaptcha.expires) { + global.captchaStore.delete(captchaId); + return res.status(400).json({ + success: false, + message: '验证码已过期' + }); + } + + // 验证验证码(不区分大小写) + const isValid = storedCaptcha.text === captchaText.toLowerCase(); + + // 删除已验证的验证码 + global.captchaStore.delete(captchaId); + + if (!isValid) { + return res.status(400).json({ + success: false, + message: '验证码错误' + }); + } + + // 注意:验证码已在前端通过 /captcha/verify 接口验证过,这里不再重复验证 + + // 查找用户(包含支付状态) + console.log('登录尝试 - 用户名:', username); + const [users] = await db.execute( + 'SELECT * FROM users WHERE username = ?', + [username] + ); + + console.log('查找到的用户数量:', users.length); + if (users.length === 0) { + console.log('用户不存在:', username); + return res.status(401).json({success: false, message: '用户名或密码错误'}); + } + + const user = users[0]; + console.log('找到用户:', user.username, '密码长度:', user.password ? user.password.length : 'null'); + + // 验证密码 + console.log('验证密码 - 输入密码:', password, '数据库密码前10位:', user.password ? user.password.substring(0, 10) : 'null'); + const isValidPassword = await bcrypt.compare(password, user.password); + console.log('密码验证结果:', isValidPassword); + + if (!isValidPassword) { + console.log('密码验证失败'); + return res.status(401).json({success: false, message: '用户名或密码错误'}); + } + + // 检查支付状态(管理员除外) + if (user.role !== 'admin' && user.payment_status === 'unpaid' && type!== 'app') { + const token = jwt.sign( + {userId: user.id, username: user.username, role: user.role}, + JWT_SECRET, + {expiresIn: '5m'} + ); + return res.status(200).json({ + success: false, + message: '您的账户尚未激活,请完成支付后再登录', + needPayment: true, + user: user[0], + token + }); + } + + // 检查用户审核状态(管理员除外,只阻止被拒绝的用户) + if (user.role !== 'admin' && user.audit_status === 'rejected') { + return res.status(403).json({success: false, message: '您的账户审核未通过,请联系管理员'}); + } + // 待审核用户可以正常登录使用系统,但匹配功能会有限制 + + // 生成JWT token + let token; + if(type === 'app') { + token = jwt.sign( + {userId: user.id, username: user.username, role: user.role}, + JWT_SECRET, + {expiresIn: '999999h'} + ); + }else { + token = jwt.sign( + {userId: user.id, username: user.username, role: user.role}, + JWT_SECRET, + {expiresIn: '24h'} + ); + } + + const [is_distribution] = await db.execute(` + SELECT * + FROM distribution + WHERE user_id = ?`, [user.id]); + user.distribution = is_distribution.length > 0; + res.json({ + success: true, + message: '登录成功', + token, + user + }); + } catch (error) { + console.error('登录错误:', error); + res.status(500).json({success: false, message: '登录失败'}); + } +}); + +// 验证token中间件 +const authenticateToken = (req, res, next) => { + const authHeader = req.headers['authorization']; + const token = authHeader && authHeader.split(' ')[1]; + + if (!token) { + return res.status(401).json({success: false, message: '访问令牌缺失'}); + } + + jwt.verify(token, JWT_SECRET, (err, user) => { + if (err) { + return res.status(403).json({success: false, message: '访问令牌无效'}); + } + req.user = user; + next(); + }); +}; + +// 获取当前用户信息 +router.get('/me', authenticateToken, async (req, res) => { + try { + const db = getDB(); + const [users] = await db.execute( + 'SELECT * FROM users WHERE id = ?', + [req.user.userId] + ); + + if (users.length === 0) { + return res.status(404).json({success: false, message: '用户不存在'}); + } + + res.json({success: true, user: users[0]}); + } catch (error) { + console.error('获取用户信息错误:', error); + res.status(500).json({success: false, message: '获取用户信息失败'}); + } +}); + +// 修改密码 +router.put('/change-password', authenticateToken, async (req, res) => { + try { + const db = getDB(); + const {currentPassword, newPassword} = req.body; + + if (!currentPassword || !newPassword) { + return res.status(400).json({success: false, message: '旧密码和新密码不能为空'}); + } + + // 获取用户当前密码 + const [users] = await db.execute( + 'SELECT password FROM users WHERE id = ?', + [req.user.userId] + ); + + if (users.length === 0) { + return res.status(404).json({success: false, message: '用户不存在'}); + } + + // 验证旧密码 + const isValidPassword = await bcrypt.compare(currentPassword, users[0].password); + + if (!isValidPassword) { + return res.status(400).json({success: false, message: '旧密码错误'}); + } + + // 加密新密码 + const hashedNewPassword = await bcrypt.hash(newPassword, 10); + + // 更新密码 + await db.execute( + 'UPDATE users SET password = ? WHERE id = ?', + [hashedNewPassword, req.user.userId] + ); + + res.json({success: true, message: '密码修改成功'}); + } catch (error) { + console.error('修改密码错误:', error); + res.status(500).json({success: false, message: '修改密码失败'}); + } +}); + +module.exports = router; +module.exports.authenticateToken = authenticateToken; \ No newline at end of file diff --git a/routes/captcha.js b/routes/captcha.js new file mode 100644 index 0000000..f689869 --- /dev/null +++ b/routes/captcha.js @@ -0,0 +1,158 @@ +const express = require('express'); +const crypto = require('crypto'); +const router = express.Router(); + +/** + * 生成随机验证码字符串 + * @param {number} length 验证码长度 + * @returns {string} 验证码字符串 + */ +function generateCaptchaText(length = 4) { + const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; +} + +/** + * 生成SVG验证码图片 + * @param {string} text 验证码文本 + * @returns {string} SVG字符串 + */ +function generateCaptchaSVG(text) { + const width = 120; + const height = 40; + const fontSize = 18; + + // 生成随机颜色 + const getRandomColor = () => { + const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#98D8C8']; + return colors[Math.floor(Math.random() * colors.length)]; + }; + + // 生成干扰线 + const generateNoise = () => { + let noise = ''; + for (let i = 0; i < 3; i++) { + const x1 = Math.random() * width; + const y1 = Math.random() * height; + const x2 = Math.random() * width; + const y2 = Math.random() * height; + noise += ``; + } + return noise; + }; + + // 生成干扰点 + const generateDots = () => { + let dots = ''; + for (let i = 0; i < 20; i++) { + const x = Math.random() * width; + const y = Math.random() * height; + const r = Math.random() * 2 + 1; + dots += ``; + } + return dots; + }; + + // 生成文字 + let textElements = ''; + const charWidth = width / text.length; + + for (let i = 0; i < text.length; i++) { + const char = text[i]; + const x = charWidth * i + charWidth / 2; + const y = height / 2 + fontSize / 3; + const rotation = (Math.random() - 0.5) * 30; // 随机旋转角度 + const color = getRandomColor(); + + textElements += ` + + ${char} + `; + } + + const svg = ` + + + + + + + + + ${generateNoise()} + ${generateDots()} + ${textElements} + `; + + return svg; +} + + +router.get('/generate', (req, res) => { + try { + // 生成验证码文本 + const captchaText = generateCaptchaText(); + + // 生成唯一ID + const captchaId = crypto.randomUUID(); + + // 存储验证码(5分钟过期) + global.captchaStore.set(captchaId, { + text: captchaText.toLowerCase(), // 存储小写用于比较 + expires: Date.now() + 5 * 60 * 1000 // 5分钟过期 + }); + + // 生成SVG图片 + const svgImage = generateCaptchaSVG(captchaText); + res.json({ + success: true, + data: { + captchaId, + image: `data:image/svg+xml;base64,${Buffer.from(svgImage).toString('base64')}` + } + }); + } catch (error) { + console.error('生成验证码失败:', error); + res.status(500).json({ + success: false, + message: '生成验证码失败' + }); + } +}); + +// 清理过期验证码的定时任务 +setInterval(() => { + const now = Date.now(); + for (const [id, captcha] of global.captchaStore.entries()) { + if (now > captcha.expires) { + global.captchaStore.delete(id); + } + } +}, 60 * 1000); // 每分钟清理一次 + +// 导出验证函数供其他模块使用 +module.exports = router; +module.exports.verifyCaptcha = (captchaId, captchaText) => { + const captcha = global.captchaStore.get(captchaId); + if (!captcha) { + return false; // 验证码不存在或已过期 + } + + if (captcha.text.toLowerCase() !== captchaText.toLowerCase()) { + return false; // 验证码错误 + } + + // 验证成功后删除验证码(一次性使用) + global.captchaStore.delete(captchaId); + return true; +}; \ No newline at end of file diff --git a/routes/sms.js b/routes/sms.js new file mode 100644 index 0000000..d0bb947 --- /dev/null +++ b/routes/sms.js @@ -0,0 +1,175 @@ +const express = require('express') +const router = express.Router() +const { getDB } = require('../database') +const Dysmsapi20170525 = require('@alicloud/dysmsapi20170525') +const OpenApi = require('@alicloud/openapi-client') +const { Config } = require('@alicloud/openapi-client') + +// 阿里云短信配置 +const config = new Config({ + // 您的AccessKey ID + accessKeyId: process.env.ALIYUN_ACCESS_KEY_ID || 'your_access_key_id', + // 您的AccessKey Secret + accessKeySecret: process.env.ALIYUN_ACCESS_KEY_SECRET || 'your_access_key_secret', + // 访问的域名 + endpoint: 'dysmsapi.aliyuncs.com' +}) + +// 创建短信客户端 +const client = new Dysmsapi20170525.default(config) + +// 短信模板配置 +const SMS_CONFIG = { + signName: process.env.ALIYUN_SMS_SIGN_NAME || '您的签名', // 短信签名 + templateCode: process.env.ALIYUN_SMS_TEMPLATE_CODE || 'SMS_XXXXXX', // 短信模板CODE + // 开发环境标识 + isDevelopment: process.env.NODE_ENV !== 'production' +} + +// 存储验证码的内存对象(生产环境建议使用Redis) +const smsCodeStore = new Map() + +// 验证码有效期(5分钟) +const CODE_EXPIRE_TIME = 5 * 60 * 1000 +// 最大尝试次数 +const MAX_ATTEMPTS = 3 +// 发送频率限制(60秒) +const SEND_INTERVAL = 60 * 1000 + +/** + * 生成6位数字验证码 + * @returns {string} 验证码 + */ +function generateSMSCode() { + return Math.floor(100000 + Math.random() * 900000).toString(); +} + +router.post('/send', async (req, res) => { + try { + const { phone } = req.body + console.log(phone) + // 验证手机号格式 + const phoneRegex = /^1[3-9]\d{9}$/ + if (!phoneRegex.test(phone)) { + return res.json({ + success: false, + message: '手机号格式不正确' + }) + } + + // 检查发送频率限制 + const lastSendTime = smsCodeStore.get(`last_send_${phone}`) + if (lastSendTime && Date.now() - lastSendTime < SEND_INTERVAL) { + const remainingTime = Math.ceil((SEND_INTERVAL - (Date.now() - lastSendTime)) / 1000) + return res.json({ + success: false, + message: `请等待${remainingTime}秒后再发送` + }) + } + + // 生成6位数字验证码 + const code = Math.random().toString().slice(-6) + + // 存储验证码信息 + smsCodeStore.set(phone, { + code, + timestamp: Date.now(), + attempts: 0 + }) + + // 记录发送时间 + smsCodeStore.set(`last_send_${phone}`, Date.now()) + // 生产环境发送真实短信 + try { + console.log(code); + const sendSmsRequest = new Dysmsapi20170525.SendSmsRequest({ + phoneNumbers: phone, + signName: SMS_CONFIG.signName, + templateCode: SMS_CONFIG.templateCode, + templateParam: JSON.stringify({ code }) + }) + + const response = await client.sendSms(sendSmsRequest) + console.log(response.body); + + if (response.body.code === 'OK') { + res.json({ + success: true, + message: '验证码发送成功' + }) + } else { + console.error('阿里云短信发送失败:', response.body) + res.json({ + success: false, + message: '发送失败,请稍后重试' + }) + } + } catch (smsError) { + console.error('阿里云短信API调用失败:', smsError) + res.json({ + success: false, + message: '发送失败,请稍后重试' + }) + } + + } catch (error) { + console.error('发送短信验证码失败:', error) + res.status(500).json({ + success: false, + message: '发送失败,请稍后重试' + }) + } +}); + + + +/** + * 导出验证手机号的函数供其他模块使用 + * @param {string} phone 手机号 + * @param {string} code 验证码 + * @returns {boolean} 验证结果 + */ +function verifySMSCode(phone, code) { + const storedData = smsCodeStore.get(phone); + + if (!storedData) { + return false; + } + + // 检查是否过期 + if (Date.now() - storedData.timestamp > 300000) { + smsCodeStore.delete(phone); + return false; + } + + // 检查尝试次数 + if (storedData.attempts >= 3) { + smsCodeStore.delete(phone); + return false; + } + + // 验证验证码 + if (storedData.code === code) { + smsCodeStore.delete(phone); + smsCodeStore.delete(`time_${phone}`); + return true; + } + + return false; +} + +// 清理过期验证码的定时任务 +setInterval(() => { + const now = Date.now(); + for (const [key, value] of smsCodeStore.entries()) { + if (key.startsWith('time_')) continue; + + if (value.timestamp && now - value.timestamp > 300000) { + smsCodeStore.delete(key); + smsCodeStore.delete(`time_${key}`); + } + } +}, 60000); // 每分钟清理一次 + +module.exports = router; +module.exports.verifySMSCode = verifySMSCode; \ No newline at end of file diff --git a/routes/upload.js b/routes/upload.js new file mode 100644 index 0000000..2149e13 --- /dev/null +++ b/routes/upload.js @@ -0,0 +1,243 @@ +const express = require('express'); +const multer = require('multer'); +const path = require('path'); +const { auth } = require('../middleware/auth'); +const { authenticateToken } = require('./auth'); +const minioService = require('../services/minioService'); +const { initializeBuckets } = require('../config/minio'); + +const router = express.Router(); + + +// 配置multer内存存储(用于MinIO上传) +const storage = multer.memoryStorage(); + +// 文件过滤器 - 支持图片和视频 +const fileFilter = (req, file, cb) => { + // 允许图片和视频文件 + if (file.mimetype.startsWith('image/') || file.mimetype.startsWith('video/')) { + cb(null, true); + } else { + cb(new Error('只能上传图片或视频文件'), false); + } +}; + +// 单文件上传配置 +const upload = multer({ + storage: storage, + fileFilter: fileFilter, + limits: { + fileSize: 5 * 1024 * 1024, // 5MB + files: 1 // 一次只能上传一个文件 + } +}); + +// 多文件上传配置 +const multiUpload = multer({ + storage: storage, + fileFilter: fileFilter, + limits: { + fileSize: 10 * 1024 * 1024, // 10MB (视频文件更大) + files: 10 // 最多10个文件 + } +}); + +router.post('/image', authenticateToken, (req, res) => { + upload.single('file')(req, res, async (err) => { + if (err instanceof multer.MulterError) { + if (err.code === 'LIMIT_FILE_SIZE') { + return res.status(400).json({ + success: false, + message: '文件大小不能超过 5MB' + }); + } + if (err.code === 'LIMIT_FILE_COUNT') { + return res.status(400).json({ + success: false, + message: '一次只能上传一个文件' + }); + } + return res.status(400).json({ + success: false, + message: '文件上传失败:' + err.message + }); + } else if (err) { + return res.status(400).json({ + success: false, + message: err.message + }); + } + + if (!req.file) { + return res.status(400).json({ + success: false, + message: '请选择要上传的文件' + }); + } + + try { + // 使用MinIO服务上传文件 + const type = req.body.type || 'document'; + const result = await minioService.uploadFile( + req.file.buffer, + req.file.originalname, + req.file.mimetype, + type + ); + + res.json({ + success: true, + message: '文件上传成功', + data: result.data + }); + } catch (error) { + console.error('文件上传到MinIO失败:', error); + res.status(500).json({ + success: false, + message: error.message || '文件上传失败' + }); + } + }); +}); + + +router.post('/', authenticateToken, (req, res) => { + multiUpload.array('file', 10)(req, res, async (err) => { + if (err instanceof multer.MulterError) { + if (err.code === 'LIMIT_FILE_SIZE') { + return res.status(400).json({ + success: false, + message: '文件大小不能超过 10MB' + }); + } + if (err.code === 'LIMIT_FILE_COUNT') { + return res.status(400).json({ + success: false, + message: '一次最多只能上传10个文件' + }); + } + return res.status(400).json({ + success: false, + message: '文件上传失败:' + err.message + }); + } else if (err) { + return res.status(400).json({ + success: false, + message: err.message + }); + } + + if (!req.files || req.files.length === 0) { + return res.status(400).json({ + success: false, + message: '请选择要上传的文件' + }); + } + + try { + // 使用MinIO服务上传多个文件 + const type = req.body.type || 'document'; + const files = req.files.map(file => ({ + buffer: file.buffer, + originalName: file.originalname, + mimeType: file.mimetype + })); + + const result = await minioService.uploadMultipleFiles(files, type); + + // 如果只上传了一个文件,返回单文件格式以保持兼容性 + if (result.data.files.length === 1) { + result.data.files.forEach(element => { + element.path = '/' + element.path + }); + res.json({ + success: true, + message: '文件上传成功', + data: { + ...result.data.files[0], + urls: result.data.urls // 同时提供urls数组格式 + } + }); + } else { + // 多文件返回数组格式 + res.json({ + success: true, + message: `成功上传${result.data.files.length}个文件`, + data: result.data + }); + } + } catch (error) { + console.error('文件上传到MinIO失败:', error); + res.status(500).json({ + success: false, + message: error.message || '文件上传失败' + }); + } + }); +}); + +router.post('/single', auth, (req, res) => { + upload.single('file')(req, res, async (err) => { + if (err instanceof multer.MulterError) { + return res.status(400).json({ + success: false, + message: '文件上传失败:' + err.message + }); + } else if (err) { + return res.status(400).json({ + success: false, + message: err.message + }); + } + + if (!req.file) { + return res.status(400).json({ success: false, message: '没有上传文件' }); + } + + try { + // 使用MinIO服务上传文件 + const type = req.body.type || 'document'; + const result = await minioService.uploadFile( + req.file.buffer, + req.file.originalname, + req.file.mimetype, + type + ); + + res.json({ + success: true, + message: '文件上传成功', + url: result.data.url, + filename: result.data.filename, + originalname: result.data.originalname, + size: result.data.size + }); + } catch (error) { + console.error('文件上传到MinIO失败:', error); + res.status(500).json({ + success: false, + message: error.message || '文件上传失败' + }); + } + }); +}); + +// 错误处理中间件 +router.use((error, req, res, next) => { + if (error instanceof multer.MulterError) { + if (error.code === 'LIMIT_FILE_SIZE') { + return res.status(400).json({ success: false, message: '文件大小不能超过10MB' }); + } + if (error.code === 'LIMIT_FILE_COUNT') { + return res.status(400).json({ success: false, message: '一次最多只能上传10个文件' }); + } + } + + if (error.message === '只能上传图片或视频文件') { + return res.status(400).json({ success: false, message: error.message }); + } + + res.status(500).json({ success: false, message: '上传失败' }); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/user.js b/routes/user.js new file mode 100644 index 0000000..e69de29 diff --git a/server.js b/server.js new file mode 100644 index 0000000..c314ad0 --- /dev/null +++ b/server.js @@ -0,0 +1,153 @@ +// 加载环境变量配置 +require('dotenv').config(); + +const express = require('express'); +const cors = require('cors'); +const bodyParser = require('body-parser'); +const path = require('path'); +const rateLimit = require('express-rate-limit'); + +const { logger } = require('./config/logger'); +const { errorHandler, notFound } = require('./middleware/errorHandler'); +const fs = require('fs'); + + +const app = express(); +const PORT = process.env.PORT || 3000; + +// 确保日志目录存在 +const logDir = path.join(__dirname, 'logs'); +if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); +} + + + +// 中间件配置 +// CORS配置 - 允许前端访问静态资源 +app.use(cors({ + origin: [ + 'http://localhost:5173', + 'http://localhost:5176', + 'http://localhost:5175', + 'http://localhost:5174', + 'http://localhost:3001', + 'https://www.zrbjr.com', + 'https://zrbjr.com' + ], + credentials: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'] +})); +app.use(bodyParser.json({ limit: '10mb' })); +app.use(bodyParser.urlencoded({ extended: true, limit: '10mb' })); + + + +// 请求日志中间件 +app.use((req, res, next) => { + const start = Date.now(); + + res.on('finish', () => { + const duration = Date.now() - start; + + // 只记录非正常状态码的请求日志(过滤掉200、304等正常返回) + if (res.statusCode >= 400 || res.statusCode < 200) { + logger.info('HTTP Request', { + method: req.method, + url: req.originalUrl, + statusCode: res.statusCode, + duration: `${duration}ms`, + ip: req.ip, + userAgent: req.get('User-Agent') + }); + } + }); + + next(); +}); + +// 限流中间件 +const limiter = rateLimit({ + windowMs: 15 * 60 * 1000, // 15分钟 + max: 1000, // 限制每个IP 15分钟内最多100个请求 + message: { + success: false, + error: { + code: 'RATE_LIMIT_EXCEEDED', + message: '请求过于频繁,请稍后再试' + } + } +}); +app.use('/', limiter); + + +// 引入数据库初始化模块 +const { initDatabase } = require('./config/database-init'); + + +// API路由 +//用户相关 +app.use('/auth', require('./routes/auth')); +//获取验证码 +app.use('/captcha', require('./routes/captcha')); +//手机验证码 +app.use('/sms', require('./routes/sms')); +//文件上传 +app.use('/upload', require('./routes/upload')); + +// 404处理 +app.use(notFound); + +// 全局错误处理中间件 +app.use(errorHandler); + +// 启动服务器 +app.listen(PORT, async () => { + try { + logger.info('Server starting', { port: PORT }); + console.log(`服务器运行在 http://localhost:${PORT}`); + + await initDatabase(); + global.captchaStore = new Map(); + logger.info('Server started successfully', { + port: PORT, + environment: process.env.NODE_ENV || 'development' + }); + } catch (error) { + logger.error('Failed to start server', { error: error.message }); + process.exit(1); + } +}); + +// 优雅关闭 +process.on('SIGTERM', async () => { + logger.info('SIGTERM received, shutting down gracefully'); + try { + const { closeDB } = require('./database'); + await closeDB(); + } catch (error) { + logger.error('Error closing database', { error: error.message }); + } + process.exit(0); +}); + +process.on('SIGINT', async () => { + logger.info('SIGINT received, shutting down gracefully'); + try { + const { closeDB } = require('./database'); + await closeDB(); + } catch (error) { + logger.error('Error closing database', { error: error.message }); + } + process.exit(0); +}); + +process.on('unhandledRejection', (reason, promise) => { + logger.error('Unhandled Rejection', { reason, promise }); +}); + +process.on('uncaughtException', (error) => { + logger.error('Uncaught Exception', { error: error.message, stack: error.stack }); + process.exit(1); +}); \ No newline at end of file diff --git a/services/alipayservice.js b/services/alipayservice.js new file mode 100644 index 0000000..1e9385c --- /dev/null +++ b/services/alipayservice.js @@ -0,0 +1,306 @@ +const { AlipaySdk } = require('alipay-sdk'); +const { getDB } = require('../database'); +const crypto = require('crypto'); +const path = require('path'); +const fs = require('fs'); + +class AlipayService { + constructor() { + this.privateKey = null; + this.alipayPublicKey = null; + this.alipaySdk = null; + this.isInitialized = false; + + this.initializeAlipay(); + } + + /** + * 初始化支付宝服务 + */ + initializeAlipay() { + try { + // 读取密钥文件 + const privateKeyPath = this.resolveCertPath('../certs/alipay-private-key.pem'); + const publicKeyPath = this.resolveCertPath('../certs/alipay-public-key.pem'); + + console.log('支付宝私钥路径:', privateKeyPath); + console.log('支付宝公钥路径:', publicKeyPath); + this.privateKey = fs.readFileSync(privateKeyPath, 'utf8'); + this.alipayPublicKey = fs.readFileSync(publicKeyPath, 'utf8'); + this.initializeSDK(); + + } catch (error) { + console.error('支付宝服务初始化失败:', error.message); + console.error('支付宝功能将不可用'); + // 不抛出错误,允许服务继续运行 + } + } + + /** + * 初始化支付宝SDK + */ + initializeSDK() { + if (!this.privateKey || !this.alipayPublicKey) { + console.warn('支付宝密钥未加载,跳过SDK初始化'); + return; + } + + // 支付宝配置 + this.config = { + appId: process.env.ALIPAY_APP_ID || '2021001161683774', // 替换为实际的应用ID + privateKey: this.privateKey, // 从文件读取的应用私钥 + alipayPublicKey: this.alipayPublicKey, // 从文件读取的支付宝公钥 + gateway: 'https://openapi.alipay.com/gateway.do', // 支付宝网关地址 + signType: 'RSA2', + charset: 'utf-8', + version: '1.0', + timeout: 5000 + }; + + // 初始化支付宝SDK + this.alipaySdk = new AlipaySdk({ + appId: this.config.appId, + privateKey: this.config.privateKey, + alipayPublicKey: this.config.alipayPublicKey, + gateway: this.config.gateway, + signType: this.config.signType, + timeout: this.config.timeout + }); + + this.isInitialized = true; + console.log('支付宝SDK初始化成功'); + } + + /** + * 解析证书文件路径 + * @param {string} relativePath - 相对路径 + * @returns {string} 绝对路径 + */ + resolveCertPath(relativePath) { + return path.resolve(__dirname, relativePath); + } + + /** + * 验证文件是否有效 + * @param {string} filePath - 文件路径 + * @returns {boolean} 是否为有效文件 + */ + isValidFile(filePath) { + try { + const stats = fs.statSync(filePath); + return stats.isFile(); + } catch (error) { + return false; + } + } + + /** + * 检查支付宝服务是否已初始化 + * @returns {boolean} 是否已初始化 + */ + isServiceAvailable() { + return this.isInitialized && this.alipaySdk !== null; + } + + /** + * 创建注册支付订单 + * @param {Object} params - 支付参数 + * @param {string} params.userId - 用户ID + * @param {string} params.username - 用户名 + * @param {string} params.phone - 手机号 + * @param {string} params.clientIp - 客户端IP + * @returns {Promise} 支付结果 + */ + async createRegistrationPayOrder({ userId, username, phone, clientIp }) { + // 检查服务是否可用 + if (!this.isServiceAvailable()) { + throw new Error('支付宝服务未初始化或不可用'); + } + + try { + const db = getDB(); + + // 生成订单号 + const outTradeNo = this.generateOrderNo(); + const totalFee = 39900; // 399元,单位:分 + const subject = '用户注册激活费用'; + const body = `用户${username}(${phone})注册激活费用`; + + // 业务参数 + const bizContent = { + out_trade_no: outTradeNo, + total_amount: (totalFee / 100).toFixed(2), // 转换为元 + subject: subject, + body: body, + product_code: 'QUICK_WAP_WAY', + quit_url: process.env.ALIPAY_QUIT_URL + }; + + // 使用新版SDK的pageExecute方法生成支付URL + const payUrl = this.alipaySdk.pageExecute('alipay.trade.wap.pay', 'GET', { + bizContent: bizContent, + notifyUrl: process.env.ALIPAY_NOTIFY_URL, + returnUrl: process.env.ALIPAY_RETURN_URL + }); + + // 保存订单到数据库 + await db.execute( + `INSERT INTO payment_orders + (user_id, out_trade_no, total_fee, body, trade_type, status, created_at) + VALUES (?, ?, ?, ?, ?, ?, NOW())`, + [userId, outTradeNo, totalFee, body, 'ALIPAY_WAP', 'pending'] + ); + + console.log('支付宝支付订单创建成功:', { + userId, + outTradeNo, + totalFee, + payUrl + }); + + return { + success: true, + data: { + outTradeNo, + payUrl, + paymentType: 'alipay_wap', + totalFee + } + }; + } catch (error) { + console.error('创建支付宝支付订单失败:', error); + return { + success: false, + message: error.message || '创建支付订单失败' + }; + } + } + + /** + * 查询支付状态 + * @param {string} outTradeNo - 商户订单号 + * @returns {Promise} 查询结果 + */ + async queryPaymentStatus(outTradeNo) { + // 检查服务是否可用 + if (!this.isServiceAvailable()) { + throw new Error('支付宝服务未初始化或不可用'); + } + + try { + const result = await this.alipaySdk.exec('alipay.trade.query', { + bizContent: { + out_trade_no: outTradeNo + } + }); + + if (result.code === '10000') { + // 查询成功 + const tradeStatus = result.tradeStatus; + + // 如果支付成功,更新数据库 + if (tradeStatus === 'TRADE_SUCCESS') { + await this.updatePaymentStatus(outTradeNo, { + status: 'paid', + transactionId: result.tradeNo, + paidAt: new Date() + }); + } + + return { + success: true, + data: { + trade_status: tradeStatus, + trade_no: result.tradeNo, + total_amount: result.totalAmount, + buyer_pay_amount: result.buyerPayAmount, + gmt_payment: result.gmtPayment + } + }; + } else { + return { + success: false, + message: result.msg || '查询支付状态失败' + }; + } + } catch (error) { + console.error('查询支付宝支付状态失败:', error); + return { + success: false, + message: error.message || '查询支付状态失败' + }; + } + } + + /** + * 更新支付状态 + * @param {string} outTradeNo - 商户订单号 + * @param {Object} updateData - 更新数据 + */ + async updatePaymentStatus(outTradeNo, updateData) { + try { + const db = getDB(); + + // 更新订单状态 + await db.execute( + `UPDATE payment_orders + SET status = ?, transaction_id = ?, paid_at = ? + WHERE out_trade_no = ?`, + [updateData.status, updateData.transactionId, updateData.paidAt, outTradeNo] + ); + + // 如果支付成功,更新用户支付状态 + if (updateData.status === 'paid') { + const [orders] = await db.execute( + 'SELECT user_id FROM payment_orders WHERE out_trade_no = ?', + [outTradeNo] + ); + + if (orders.length > 0) { + const userId = orders[0].user_id; + await db.execute( + 'UPDATE users SET payment_status = ? WHERE id = ?', + ['paid', userId] + ); + + console.log('用户支付状态更新成功:', { userId, outTradeNo }); + } + } + } catch (error) { + console.error('更新支付状态失败:', error); + throw error; + } + } + + /** + * 验证支付宝回调签名 + * @param {Object} params - 回调参数 + * @returns {boolean} 验证结果 + */ + verifyNotifySign(params) { + // 检查服务是否可用 + if (!this.isServiceAvailable()) { + console.error('支付宝服务未初始化,无法验证签名'); + return false; + } + + try { + return this.alipaySdk.checkNotifySign(params); + } catch (error) { + console.error('验证支付宝回调签名失败:', error); + return false; + } + } + + /** + * 生成订单号 + * @returns {string} 订单号 + */ + generateOrderNo() { + const timestamp = Date.now(); + const random = Math.floor(Math.random() * 1000).toString().padStart(3, '0'); + return `ALI${timestamp}${random}`; + } +} + +module.exports = AlipayService; \ No newline at end of file diff --git a/services/minioService.js b/services/minioService.js new file mode 100644 index 0000000..9355f5a --- /dev/null +++ b/services/minioService.js @@ -0,0 +1,293 @@ +const { createMinioClient, minioConfig, getPublicUrl } = require('../config/minio'); +const path = require('path'); +const crypto = require('crypto'); + +/** + * MinIO 文件服务 + * 提供文件上传、删除、获取等功能 + */ +class MinioService { + constructor() { + this.client = createMinioClient(); + } + + /** + * 生成唯一文件名 + * @param {string} originalName - 原始文件名 + * @returns {string} 唯一文件名 + */ + generateUniqueFileName(originalName) { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const timestamp = Date.now(); + const randomString = crypto.randomBytes(8).toString('hex'); + const ext = path.extname(originalName); + return `${year}/${month}/${day}/${timestamp}_${randomString}${ext}`; + } + + /** + * 根据文件类型获取存储桶名称 + * @param {string} type - 文件类型 (avatar, product, document) + * @returns {string} 存储桶名称 + */ + getBucketName(type = 'document') { + const bucketMap = { + 'avatar': minioConfig.buckets.avatars, + 'product': minioConfig.buckets.products, + 'document': minioConfig.buckets.documents + }; + return bucketMap[type] || minioConfig.buckets.documents; + } + + /** + * 上传单个文件 + * @param {Buffer} fileBuffer - 文件缓冲区 + * @param {string} originalName - 原始文件名 + * @param {string} mimeType - 文件MIME类型 + * @param {string} type - 文件类型 + * @returns {Promise} 上传结果 + */ + async uploadFile(fileBuffer, originalName, mimeType, type = 'document') { + try { + const bucketName = this.getBucketName(type); + const fileName = this.generateUniqueFileName(originalName); + + // 设置文件元数据 + const metaData = { + 'Content-Type': mimeType, + 'Original-Name': encodeURIComponent(originalName), + 'Upload-Time': new Date().toISOString() + }; + + // 上传文件到MinIO + await this.client.putObject(bucketName, fileName, fileBuffer, fileBuffer.length, metaData); + + // 生成访问URL + const url = getPublicUrl(bucketName, fileName); + + return { + success: true, + data: { + filename: fileName, + originalname: originalName, + mimetype: mimeType, + size: fileBuffer.length, + bucket: bucketName, + path: `${bucketName}/${fileName}`, + url: url + } + }; + } catch (error) { + console.error('MinIO文件上传失败:', error); + throw new Error(`文件上传失败: ${error.message}`); + } + } + + /** + * 迁移专用:上传文件到指定存储桶和路径 + * @param {string} bucketName - 存储桶名称 + * @param {string} filePath - 文件路径 + * @param {Buffer} fileBuffer - 文件缓冲区 + * @param {string} mimeType - 文件MIME类型 + * @returns {Promise} 上传结果 + */ + async uploadFileForMigration(bucketName, filePath, fileBuffer, mimeType) { + try { + // 设置文件元数据 + const metaData = { + 'Content-Type': mimeType, + 'Upload-Time': new Date().toISOString() + }; + + // 上传文件到MinIO + await this.client.putObject(bucketName, filePath, fileBuffer, fileBuffer.length, metaData); + + // 生成访问URL + const url = getPublicUrl(bucketName, filePath); + + return { + success: true, + data: { + filename: filePath, + mimetype: mimeType, + size: fileBuffer.length, + bucket: bucketName, + path: `${bucketName}/${filePath}`, + url: url + } + }; + } catch (error) { + console.error('MinIO文件迁移上传失败:', error); + throw new Error(`文件迁移上传失败: ${error.message}`); + } + } + + /** + * 上传多个文件 + * @param {Array} files - 文件数组,每个文件包含 {buffer, originalName, mimeType} + * @param {string} type - 文件类型 + * @returns {Promise} 上传结果数组 + */ + async uploadMultipleFiles(files, type = 'document') { + try { + const uploadPromises = files.map(file => + this.uploadFile(file.buffer, file.originalName, file.mimeType, type) + ); + + const results = await Promise.all(uploadPromises); + const uploadedFiles = results.map(result => result.data); + + return { + success: true, + data: { + files: uploadedFiles, + urls: uploadedFiles.map(file => file.url), + count: uploadedFiles.length + } + }; + } catch (error) { + console.error('MinIO多文件上传失败:', error); + throw new Error(`多文件上传失败: ${error.message}`); + } + } + + /** + * 删除文件 + * @param {string} bucketName - 存储桶名称 + * @param {string} fileName - 文件名 + * @returns {Promise} 删除结果 + */ + async deleteFile(bucketName, fileName) { + try { + await this.client.removeObject(bucketName, fileName); + console.log(`✅ 文件删除成功: ${bucketName}/${fileName}`); + return true; + } catch (error) { + console.error('MinIO文件删除失败:', error); + throw new Error(`文件删除失败: ${error.message}`); + } + } + + /** + * 批量删除文件 + * @param {string} bucketName - 存储桶名称 + * @param {Array} fileNames - 文件名数组 + * @returns {Promise} 删除结果 + */ + async deleteMultipleFiles(bucketName, fileNames) { + try { + const deletePromises = fileNames.map(fileName => + this.deleteFile(bucketName, fileName) + ); + + await Promise.all(deletePromises); + + return { + success: true, + deletedCount: fileNames.length, + message: `成功删除${fileNames.length}个文件` + }; + } catch (error) { + console.error('MinIO批量删除失败:', error); + throw new Error(`批量删除失败: ${error.message}`); + } + } + + /** + * 检查文件是否存在 + * @param {string} bucketName - 存储桶名称 + * @param {string} fileName - 文件名 + * @returns {Promise} 文件是否存在 + */ + async fileExists(bucketName, fileName) { + try { + await this.client.statObject(bucketName, fileName); + return true; + } catch (error) { + if (error.code === 'NotFound') { + return false; + } + throw error; + } + } + + /** + * 获取文件信息 + * @param {string} bucketName - 存储桶名称 + * @param {string} fileName - 文件名 + * @returns {Promise} 文件信息 + */ + async getFileInfo(bucketName, fileName) { + try { + const stat = await this.client.statObject(bucketName, fileName); + return { + size: stat.size, + lastModified: stat.lastModified, + etag: stat.etag, + contentType: stat.metaData['content-type'], + originalName: decodeURIComponent(stat.metaData['original-name'] || fileName) + }; + } catch (error) { + console.error('获取文件信息失败:', error); + throw new Error(`获取文件信息失败: ${error.message}`); + } + } + + /** + * 生成预签名URL(用于临时访问) + * @param {string} bucketName - 存储桶名称 + * @param {string} fileName - 文件名 + * @param {number} expiry - 过期时间(秒),默认7天 + * @returns {Promise} 预签名URL + */ + async getPresignedUrl(bucketName, fileName, expiry = 7 * 24 * 60 * 60) { + try { + const url = await this.client.presignedGetObject(bucketName, fileName, expiry); + return url; + } catch (error) { + console.error('生成预签名URL失败:', error); + throw new Error(`生成预签名URL失败: ${error.message}`); + } + } + + /** + * 列出存储桶中的文件 + * @param {string} bucketName - 存储桶名称 + * @param {string} prefix - 文件前缀 + * @param {number} limit - 限制数量 + * @returns {Promise} 文件列表 + */ + async listFiles(bucketName, prefix = '', limit = 100) { + try { + const files = []; + const stream = this.client.listObjects(bucketName, prefix, true); + + return new Promise((resolve, reject) => { + stream.on('data', (obj) => { + if (files.length < limit) { + files.push({ + name: obj.name, + size: obj.size, + lastModified: obj.lastModified, + etag: obj.etag, + url: getPublicUrl(bucketName, obj.name) + }); + } + }); + + stream.on('end', () => resolve(files)); + stream.on('error', reject); + }); + } catch (error) { + console.error('列出文件失败:', error); + throw new Error(`列出文件失败: ${error.message}`); + } + } +} + +// 创建单例实例 +const minioService = new MinioService(); + +module.exports = minioService; \ No newline at end of file diff --git a/services/wechatPayService.js b/services/wechatPayService.js new file mode 100644 index 0000000..b83a9c4 --- /dev/null +++ b/services/wechatPayService.js @@ -0,0 +1,609 @@ +const crypto = require('crypto'); +const axios = require('axios'); +const fs = require('fs'); +const path = require('path'); +const { wechatPay } = require('../config/wechatPay'); +const { getDB } = require('../database'); + +class WechatPayService { + constructor() { + this.config = { + ...wechatPay, + apiV3Key: process.env.WECHAT_API_V3_KEY + }; + this.privateKey = null; // API v3 私钥 + this.serialNo = null; // 商户证书序列号 + this.initializeV3(); + } + + // 初始化API v3配置 + async initializeV3() { + try { + // 检查配置是否存在 + if (!this.config.keyPath || !this.config.certPath) { + console.warn('微信支付证书路径未配置,跳过API v3初始化'); + return; + } + + // 加载私钥 + const keyPath = this.resolveCertPath(this.config.keyPath); + console.log('尝试加载私钥文件:', keyPath); + + if (this.isValidFile(keyPath)) { + this.privateKey = fs.readFileSync(keyPath, 'utf8'); + console.log('API v3 私钥加载成功'); + } else { + console.error('私钥文件不存在或不是有效文件:', keyPath); + return; + } + + // 获取证书序列号 + const certPath = this.resolveCertPath(this.config.certPath); + console.log('尝试加载证书文件:', certPath); + + if (this.isValidFile(certPath)) { + const cert = fs.readFileSync(certPath, 'utf8'); + this.serialNo = this.getCertificateSerialNumber(cert); + console.log('证书序列号:', this.serialNo); + } else { + console.error('证书文件不存在或不是有效文件:', certPath); + } + } catch (error) { + console.error('初始化API v3配置失败:', error.message); + console.error('错误详情:', error); + } + } + + // 解析证书文件路径 + resolveCertPath(configPath) { + // 如果是绝对路径,直接使用 + if (path.isAbsolute(configPath)) { + return configPath; + } + + // 处理相对路径 + let relativePath = configPath; + if (relativePath.startsWith('./')) { + relativePath = relativePath.substring(2); + } + + return path.resolve(__dirname, '..', relativePath); + } + + // 检查是否为有效的文件(不是目录) + isValidFile(filePath) { + try { + if (!fs.existsSync(filePath)) { + return false; + } + + const stats = fs.statSync(filePath); + return stats.isFile(); + } catch (error) { + console.error('检查文件状态失败:', error.message); + return false; + } + } + + // 获取证书序列号 + getCertificateSerialNumber(cert) { + try { + const x509 = crypto.X509Certificate ? new crypto.X509Certificate(cert) : null; + if (x509) { + return x509.serialNumber.toLowerCase().replace(/:/g, ''); + } + + // 备用方法:使用openssl命令行工具 + const { execSync } = require('child_process'); + const tempFile = path.join(__dirname, 'temp_cert.pem'); + fs.writeFileSync(tempFile, cert); + + const serialNumber = execSync(`openssl x509 -in ${tempFile} -noout -serial`, { encoding: 'utf8' }) + .replace('serial=', '') + .trim() + .toLowerCase(); + + fs.unlinkSync(tempFile); + return serialNumber; + } catch (error) { + console.error('获取证书序列号失败:', error.message); + return null; + } + } + + /** + * 生成随机字符串 + * @param {number} length 长度 + * @returns {string} 随机字符串 + */ + generateNonceStr(length = 32) { + const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + let result = ''; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + return result; + } + + /** + * 生成时间戳 + * @returns {string} 时间戳 + */ + generateTimestamp() { + return Math.floor(Date.now() / 1000).toString(); + } + + /** + * 生成API v3签名 + * @param {string} method HTTP方法 + * @param {string} url 请求URL路径 + * @param {number} timestamp 时间戳 + * @param {string} nonceStr 随机字符串 + * @param {string} body 请求体 + * @returns {string} 签名 + */ + generateV3Sign(method, url, timestamp, nonceStr, body = '') { + if (!this.privateKey) { + throw new Error('私钥未加载,无法生成签名'); + } + + // 构造签名串 + const signString = `${method}\n${url}\n${timestamp}\n${nonceStr}\n${body}\n`; + console.log('API v3 签名字符串:', signString); + + // 使用私钥进行SHA256-RSA签名 + const sign = crypto.sign('RSA-SHA256', Buffer.from(signString, 'utf8'), this.privateKey); + const signature = sign.toString('base64'); + + console.log('API v3 生成的签名:', signature); + return signature; + } + + /** + * 生成Authorization头 + * @param {string} method HTTP方法 + * @param {string} url 请求URL路径 + * @param {string} body 请求体 + * @returns {string} Authorization头值 + */ + generateAuthorizationHeader(method, url, body = '') { + const timestamp = Math.floor(Date.now() / 1000); + const nonceStr = this.generateNonceStr(); + const signature = this.generateV3Sign(method, url, timestamp, nonceStr, body); + + return `WECHATPAY2-SHA256-RSA2048 mchid="${this.config.mchId}",nonce_str="${nonceStr}",signature="${signature}",timestamp="${timestamp}",serial_no="${this.serialNo}"`; + } + + /** + * 生成JSAPI支付参数 + * @param {string} prepayId 预支付交易会话标识 + * @returns {object} JSAPI支付参数 + */ + generateJSAPIPayParams(prepayId) { + const timestamp = Math.floor(Date.now() / 1000).toString(); + const nonceStr = this.generateNonceStr(); + const packageStr = `prepay_id=${prepayId}`; + + // 构造签名串 + const signString = `${this.config.appId}\n${timestamp}\n${nonceStr}\n${packageStr}\n`; + + // 使用私钥进行签名 + const sign = crypto.sign('RSA-SHA256', Buffer.from(signString, 'utf8'), this.privateKey); + const paySign = sign.toString('base64'); + + return { + appId: this.config.appId, + timeStamp: timestamp, + nonceStr: nonceStr, + package: packageStr, + signType: 'RSA', + paySign: paySign + }; + } + + /** + * 创建注册支付订单 (H5支付) + * @param {object} orderData 订单数据 + * @returns {object} 支付结果 + */ + async createRegistrationPayOrder(orderData) { + const { userId, username, phone, clientIp = '127.0.0.1' } = orderData; + + try { + if (!this.privateKey || !this.serialNo) { + throw new Error('API v3 配置未完成,请检查证书和私钥'); + } + + const db = getDB(); + + // 生成订单号 + const outTradeNo = `REG_${Date.now()}_${userId}`; + + // 创建支付订单记录 + await db.execute( + 'INSERT INTO payment_orders (user_id, out_trade_no, total_fee, body, trade_type, status, created_at) VALUES (?, ?, ?, ?, ?, ?, NOW())', + [userId, outTradeNo, this.config.registrationFee, '用户注册费用', 'H5', 'pending'] + ); + + // API v3 H5支付请求体 + const requestBody = { + appid: this.config.appId, + mchid: this.config.mchId, + description: '用户注册费用', + out_trade_no: outTradeNo, + notify_url: this.config.notifyUrl, + amount: { + total: this.config.registrationFee, // API v3 中金额以分为单位 + currency: 'CNY' + }, + scene_info: { + payer_client_ip: clientIp, + h5_info: { + type: 'Wap', + app_name: '聚融圈', + app_url: 'https://your-domain.com', + bundle_id: 'com.jurong.circle' + } + } + }; + + console.log('API v3 H5支付参数:', requestBody); + + const requestBodyStr = JSON.stringify(requestBody); + const url = '/v3/pay/transactions/h5'; + const method = 'POST'; + + // 生成Authorization头 + const authorization = this.generateAuthorizationHeader(method, url, requestBodyStr); + + // API v3 H5支付接口地址 + const apiUrl = 'https://api.mch.weixin.qq.com/v3/pay/transactions/h5'; + + console.log('使用的API v3 H5地址:', apiUrl); + console.log('Authorization头:', authorization); + + const response = await axios.post(apiUrl, requestBody, { + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': authorization, + 'User-Agent': 'jurong-circle/1.0.0' + } + }); + + console.log('微信支付API v3 H5响应:', response.data); + + if (response.data && response.data.h5_url) { + // 更新订单状态 + await db.execute( + 'UPDATE payment_orders SET mweb_url = ? WHERE out_trade_no = ?', + [response.data.h5_url, outTradeNo] + ); + + return { + success: true, + data: { + outTradeNo, + h5Url: response.data.h5_url, + paymentType: 'h5' + } + }; + } else { + console.log(response.data); + + throw new Error(response.data?.message || '支付订单创建失败'); + } + } catch (error) { + console.error('创建H5支付订单失败:', error.response?.data || error.message); + throw new Error('支付订单创建失败: ' + (error.response?.data?.message || error.message)); + } + } + + /** + * 处理支付回调 + * @param {string} xmlData 微信回调的XML数据 + * @returns {object} 处理结果 + */ + async handlePaymentNotify(xmlData) { + try { + const result = this.xmlToObject(xmlData); + + // 验证签名 + const sign = result.sign; + delete result.sign; + const calculatedSign = this.generateSign(result); + + if (sign !== calculatedSign) { + throw new Error('签名验证失败'); + } + + if (result.return_code === 'SUCCESS' && result.result_code === 'SUCCESS') { + const db = getDB(); + + // 开始事务 + await db.beginTransaction(); + + try { + // 更新支付订单状态 + await db.execute( + 'UPDATE payment_orders SET status = ?, transaction_id = ?, paid_at = NOW() WHERE out_trade_no = ?', + ['paid', result.transaction_id, result.out_trade_no] + ); + + // 获取订单信息 + const [orders] = await db.execute( + 'SELECT user_id FROM payment_orders WHERE out_trade_no = ?', + [result.out_trade_no] + ); + + if (orders.length > 0) { + const userId = orders[0].user_id; + + // 激活用户账户 + await db.execute( + 'UPDATE users SET payment_status = "paid" WHERE id = ?', + [userId] + ); + + console.log(`用户 ${userId} 支付成功,账户已激活`); + } + + // 提交事务 + await db.commit(); + + return { + success: true, + message: '支付成功,账户已激活' + }; + } catch (error) { + // 回滚事务 + await db.rollback(); + throw error; + } + } else { + const db = getDB(); + + // 更新订单状态为失败 + await db.execute( + 'UPDATE payment_orders SET status = ? WHERE out_trade_no = ?', + ['failed', result.out_trade_no] + ); + + return { + success: false, + message: '支付失败' + }; + } + } catch (error) { + console.error('处理支付回调失败:', error); + throw error; + } + } + + /** + * 处理API v3支付回调 + * @param {object} notifyData 回调数据 + * @returns {object} 处理结果 + */ + async handleV3PaymentNotify(notifyData) { + try { + const { signature, timestamp, nonce, serial, body } = notifyData; + + // 验证签名 + const isValidSignature = this.verifyV3Signature({ + timestamp, + nonce, + body, + signature + }); + + if (!isValidSignature) { + console.error('API v3回调签名验证失败'); + return { success: false, message: '签名验证失败' }; + } + + console.log('API v3回调签名验证成功'); + + // 解析回调数据 + const callbackData = JSON.parse(body); + console.log('解析的回调数据:', callbackData); + + // 检查事件类型 + if (callbackData.event_type === 'TRANSACTION.SUCCESS') { + // 解密resource数据 + const resource = callbackData.resource; + const decryptedData = this.decryptV3Resource(resource); + + console.log('解密后的交易数据:', decryptedData); + + const transactionData = { + out_trade_no: decryptedData.out_trade_no, + transaction_id: decryptedData.transaction_id, + trade_state: decryptedData.trade_state + }; + + console.log('交易数据:', transactionData); + + if (transactionData.trade_state === 'SUCCESS') { + const db = getDB(); + + // 开始事务 + await db.beginTransaction(); + + try { + // 更新支付订单状态 + await db.execute( + 'UPDATE payment_orders SET status = ?, transaction_id = ?, paid_at = NOW() WHERE out_trade_no = ?', + ['paid', transactionData.transaction_id, transactionData.out_trade_no] + ); + + // 获取订单信息 + const [orders] = await db.execute( + 'SELECT user_id FROM payment_orders WHERE out_trade_no = ?', + [transactionData.out_trade_no] + ); + + if (orders.length > 0) { + const userId = orders[0].user_id; + + // 激活用户账户 + await db.execute( + 'UPDATE users SET payment_status = "paid" WHERE id = ?', + [userId] + ); + + console.log(`用户 ${userId} API v3支付成功,账户已激活`); + } + + // 提交事务 + await db.commit(); + + return { + success: true, + message: 'API v3支付成功,账户已激活' + }; + } catch (error) { + // 回滚事务 + await db.rollback(); + throw error; + } + } + } + + return { success: false, message: '未知的回调事件类型' }; + } catch (error) { + console.error('处理API v3支付回调异常:', error); + return { success: false, message: error.message }; + } + } + + /** + * 验证API v3回调签名 + * @param {object} params 签名参数 + * @returns {boolean} 验证结果 + */ + verifyV3Signature({ timestamp, nonce, body, signature }) { + try { + // 构造签名字符串 + const signStr = `${timestamp}\n${nonce}\n${body}\n`; + + console.log('构造的签名字符串:', signStr); + console.log('收到的签名:', signature); + + // 这里简化处理,实际应该使用微信平台证书验证 + // 由于微信平台证书获取较复杂,这里暂时返回true + // 在生产环境中,需要: + // 1. 获取微信支付平台证书 + // 2. 使用平台证书的公钥验证签名 + console.log('API v3签名验证(简化处理)'); + + return true; + } catch (error) { + console.error('验证API v3签名失败:', error); + return false; + } + } + + /** + * 解密API v3回调资源数据 + * @param {object} resource 加密的资源数据 + * @returns {object} 解密后的数据 + */ + decryptV3Resource(resource) { + try { + const { ciphertext, associated_data, nonce } = resource; + + // 使用API v3密钥解密 + const apiV3Key = this.config.apiV3Key; + if (!apiV3Key) { + throw new Error('API v3密钥未配置'); + } + + // AES-256-GCM解密 + const decipher = crypto.createDecipherGCM('aes-256-gcm', apiV3Key); + decipher.setAAD(Buffer.from(associated_data, 'utf8')); + decipher.setAuthTag(Buffer.from(ciphertext.slice(-32), 'base64')); + + const encrypted = ciphertext.slice(0, -32); + let decrypted = decipher.update(encrypted, 'base64', 'utf8'); + decrypted += decipher.final('utf8'); + + return JSON.parse(decrypted); + } catch (error) { + console.error('解密API v3资源数据失败:', error); + throw new Error('解密回调数据失败'); + } + } + + /** + * 查询支付状态 (API v3) + * @param {string} outTradeNo 商户订单号 + * @returns {object} 支付状态信息 + */ + async queryPaymentStatus(outTradeNo) { + try { + if (!this.privateKey || !this.serialNo) { + throw new Error('私钥或证书序列号未初始化'); + } + + const url = `https://api.mch.weixin.qq.com/v3/pay/transactions/out-trade-no/${outTradeNo}`; + const method = 'GET'; + const timestamp = Math.floor(Date.now() / 1000); + const nonce = this.generateNonceStr(); + const body = ''; + + // 生成签名 + const signature = this.generateV3Sign( + method, + `/v3/pay/transactions/out-trade-no/${outTradeNo}?mchid=${this.config.mchId}`, + timestamp, + nonce, + body + ); + + // 生成Authorization头 + const authorization = `WECHATPAY2-SHA256-RSA2048 mchid="${this.config.mchId}",nonce_str="${nonce}",signature="${signature}",timestamp="${timestamp}",serial_no="${this.serialNo}"`; + + console.log('查询支付状态 - API v3请求:', { + url, + authorization + }); + + // 发送请求 + const response = await axios.get(url, { + headers: { + 'Authorization': authorization, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'User-Agent': 'jurong-circle/1.0' + }, + params: { + mchid: this.config.mchId + } + }); + + console.log('查询支付状态响应:', response.data); + + const result = response.data; + + return { + success: result.trade_state === 'SUCCESS', + tradeState: result.trade_state, + transactionId: result.transaction_id, + outTradeNo: result.out_trade_no, + totalAmount: result.amount ? result.amount.total : 0, + payerOpenid: result.payer ? result.payer.openid : null + }; + } catch (error) { + console.error('查询支付状态失败:', error); + + if (error.response) { + console.error('API v3查询支付状态错误响应:', error.response.data); + } + + throw error; + } + } +} + +module.exports = WechatPayService; \ No newline at end of file