wok-server 0.4.12 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,11 +6,15 @@ const config_1 = require("../config");
6
6
  const validation_1 = require("../validation");
7
7
  const level_1 = require("./level");
8
8
  const envConfig = (0, config_1.registerConfig)({
9
+ console: true,
10
+ format: 'text',
9
11
  file: false,
10
12
  fileDir: 'logs',
11
13
  fileMaxDays: 30,
12
14
  level: 'INFO'
13
15
  }, 'LOG', {
16
+ console: [(0, validation_1.notNull)()],
17
+ format: [(0, validation_1.notNull)()],
14
18
  file: [(0, validation_1.notNull)()],
15
19
  fileDir: [(0, validation_1.notBlank)()],
16
20
  fileMaxDays: [(0, validation_1.min)(1)],
@@ -25,5 +29,7 @@ exports.config = Object.freeze({
25
29
  file: envConfig.file,
26
30
  fileDir,
27
31
  fileMaxDays: envConfig.fileMaxDays,
28
- level: (0, level_1.parseLogLevel)(level)
32
+ level: (0, level_1.parseLogLevel)(level),
33
+ console: envConfig.console,
34
+ format: envConfig.format
29
35
  });
package/dist/log/file.js CHANGED
@@ -1,72 +1,198 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fileStore = void 0;
3
+ exports.flushLogsToFile = exports.fileStore = void 0;
4
4
  const fs_1 = require("fs");
5
5
  const promises_1 = require("fs/promises");
6
6
  const os_1 = require("os");
7
7
  const path_1 = require("path");
8
- const task_1 = require("../task");
9
8
  const config_1 = require("./config");
10
- let QUEUE = [];
9
+ const log_1 = require("./log");
10
+ // 日志队列
11
+ let LOG_QUEUE = [];
12
+ // 最大缓冲数量
13
+ const MAX_QUEUE_SIZE = 1024;
14
+ // 写入定时器
15
+ let WRITE_TIMER = null;
16
+ // 延迟写入时间(毫秒)
17
+ const WRITE_DELAY = 100;
18
+ // 是否已经安排了清理任务
19
+ let CLEANUP_SCHEDULED = false;
20
+ // 清理任务定时器
21
+ let CLEANUP_TIMER = null;
11
22
  /**
12
23
  * 文件存储.
13
- * @param log
24
+ * @param log 日志对象
25
+ * @param logConfig 日志配置
14
26
  */
15
- function fileStore(log) {
16
- QUEUE.push(log);
17
- setTimeout(() => write().catch(e => console.error('Writing log file failed', e)), 0);
27
+ function fileStore(log, logConfig) {
28
+ // 将日志添加到队列
29
+ LOG_QUEUE.push(log);
30
+ // 如果队列超过最大缓冲数量,立即写入
31
+ if (LOG_QUEUE.length >= MAX_QUEUE_SIZE) {
32
+ writeLogs(logConfig).catch(e => console.error('Writing log file failed', e));
33
+ // 在写入前检查是否需要安排清理任务
34
+ if (!CLEANUP_SCHEDULED) {
35
+ scheduleCleanupTask(logConfig);
36
+ }
37
+ return;
38
+ }
39
+ // 安排延迟写入
40
+ if (!WRITE_TIMER) {
41
+ WRITE_TIMER = setTimeout(() => {
42
+ WRITE_TIMER = null;
43
+ writeLogs(logConfig).catch(e => console.error('Writing log file failed', e));
44
+ }, WRITE_DELAY);
45
+ }
46
+ // 如果启用了文件存储并且还没有安排清理任务,则安排清理任务
47
+ if (!CLEANUP_SCHEDULED) {
48
+ scheduleCleanupTask(logConfig);
49
+ }
18
50
  }
19
51
  exports.fileStore = fileStore;
20
- function buildFilePath() {
21
- const date = new Date();
22
- let fileName = `${date.getFullYear()}-`;
23
- const month = date.getMonth() + 1;
24
- fileName += month.toFixed(0).padStart(2, '0');
25
- fileName += '-';
52
+ /**
53
+ * 根据日期构建日志文件路径
54
+ * @param logConfig 日志配置
55
+ * @param dateKey 数字键 (格式:年*10000 + 月*100 + 日)
56
+ * @returns 日志文件路径
57
+ */
58
+ function buildFilePathByDate(logConfig, dateKey) {
59
+ const fileName = `${dateKey}.log`;
60
+ // 确保目录是绝对路径
61
+ let fileDir = logConfig.fileDir;
62
+ if (!(0, path_1.isAbsolute)(fileDir)) {
63
+ fileDir = (0, path_1.resolve)(process.cwd(), fileDir);
64
+ }
65
+ return (0, path_1.resolve)(fileDir, fileName);
66
+ }
67
+ /**
68
+ * 根据日期对象计算数字键
69
+ * @param date 日期对象
70
+ * @returns 数字键 (格式:年*10000 + 月*100 + 日)
71
+ */
72
+ function calculateDateKey(date) {
73
+ const year = date.getFullYear();
74
+ const month = date.getMonth() + 1; // 转换为1-12
26
75
  const day = date.getDate();
27
- fileName += day.toFixed(0).padStart(2, '0');
28
- fileName += '.log';
29
- return (0, path_1.resolve)(config_1.config.fileDir, fileName);
76
+ return year * 10000 + month * 100 + day;
30
77
  }
31
- async function write() {
32
- if (!QUEUE.length) {
78
+ /**
79
+ * 写入日志到文件
80
+ * @param logConfig 日志配置
81
+ */
82
+ async function writeLogs(logConfig) {
83
+ if (!LOG_QUEUE.length || !logConfig.file) {
33
84
  return;
34
85
  }
35
- const path = buildFilePath();
36
- const dir = (0, path_1.dirname)(path);
37
- if (!(0, fs_1.existsSync)(dir)) {
38
- await (0, promises_1.mkdir)(dir, { recursive: true });
86
+ // 复制队列并清空原始队列
87
+ const logsToWrite = [...LOG_QUEUE];
88
+ LOG_QUEUE = [];
89
+ // 按日期对日志进行分组 - 使用数字键提升性能
90
+ const logsByDate = new Map();
91
+ logsToWrite.forEach(log => {
92
+ const logDate = new Date(log.time);
93
+ // 直接使用日期对象计算数字键
94
+ const dateKey = calculateDateKey(logDate);
95
+ const dateLogs = logsByDate.get(dateKey);
96
+ if (dateLogs) {
97
+ dateLogs.push(log);
98
+ }
99
+ else {
100
+ logsByDate.set(dateKey, [log]);
101
+ }
102
+ });
103
+ // 为每个日期组写入对应的日志文件
104
+ for (const [dateKey, dateLogs] of logsByDate.entries()) {
105
+ // 直接使用数字键构建文件路径
106
+ const filePath = buildFilePathByDate(logConfig, dateKey);
107
+ const dir = (0, path_1.dirname)(filePath);
108
+ // 确保目录存在
109
+ if (!(0, fs_1.existsSync)(dir)) {
110
+ await (0, promises_1.mkdir)(dir, { recursive: true });
111
+ }
112
+ // 格式化并写入该日期的所有日志
113
+ const lines = dateLogs
114
+ .map(log => (logConfig.format === 'json' ? (0, log_1.formatLogJson)(log) : (0, log_1.formatLogText)(log)))
115
+ .join(os_1.EOL);
116
+ try {
117
+ await (0, promises_1.appendFile)(filePath, lines);
118
+ }
119
+ catch (error) {
120
+ console.error(`Failed to write logs for date ${dateKey}:`, error);
121
+ // 继续处理下一个日期的日志,不中断整个写入过程
122
+ }
39
123
  }
40
- const lines = QUEUE.join(os_1.EOL);
41
- QUEUE = [];
42
- await (0, promises_1.appendFile)(path, lines);
43
124
  }
44
- if (config_1.config.file) {
45
- /**
46
- * 清理任务.
47
- */
48
- (0, task_1.scheduleDailyTask)(3, 0, {
49
- name: 'Log files clear',
50
- async run() {
51
- const files = await (0, promises_1.readdir)(config_1.config.fileDir);
52
- const now = new Date().getTime();
53
- for (const file of files) {
54
- const dotIdx = file.indexOf('.');
55
- if (dotIdx === -1) {
56
- console.warn(`Unable to process the log file: ${file}`);
57
- return;
58
- }
59
- const dateStr = file.substring(0, dotIdx);
60
- const timestamp = Date.parse(dateStr);
61
- if (isNaN(timestamp)) {
62
- console.warn(`Unable to process the log file: ${file}`);
63
- return;
64
- }
65
- if (timestamp + config_1.config.fileMaxDays * 24 * 3600 * 1000 < now) {
125
+ /**
126
+ * 执行日志清理任务
127
+ * @param logConfig 日志配置
128
+ */
129
+ async function performCleanupTask(logConfig) {
130
+ try {
131
+ let dir = logConfig.fileDir;
132
+ if (!(0, path_1.isAbsolute)(dir)) {
133
+ dir = (0, path_1.resolve)(process.cwd(), dir);
134
+ }
135
+ // 确保目录存在
136
+ if (!(0, fs_1.existsSync)(dir)) {
137
+ return;
138
+ }
139
+ const files = await (0, promises_1.readdir)(dir);
140
+ const now = new Date().getTime();
141
+ const maxAge = logConfig.fileMaxDays * 24 * 3600 * 1000;
142
+ for (const file of files) {
143
+ try {
144
+ // 获取文件的完整路径
145
+ const filePath = (0, path_1.resolve)(dir, file);
146
+ // 获取文件的最后修改时间
147
+ const fileStats = (0, fs_1.statSync)(filePath);
148
+ const lastModifiedTime = fileStats.mtime.getTime();
149
+ // 检查文件是否过期(最后修改时间早于最大保留天数)
150
+ if (lastModifiedTime + maxAge < now) {
66
151
  console.warn(`Remove log file: ${file}`);
67
- await (0, promises_1.rm)((0, path_1.resolve)(config_1.config.fileDir, file));
152
+ await (0, promises_1.rm)(filePath);
68
153
  }
69
154
  }
155
+ catch (error) {
156
+ console.error(`Failed to process log file ${file}:`, error);
157
+ // 继续处理下一个文件,不中断清理过程
158
+ continue;
159
+ }
70
160
  }
71
- });
161
+ }
162
+ catch (error) {
163
+ console.error('Error during log cleanup:', error);
164
+ }
165
+ finally {
166
+ // 清理完成后,重置清理任务安排状态
167
+ CLEANUP_SCHEDULED = false;
168
+ }
169
+ }
170
+ /**
171
+ * 安排日志清理任务
172
+ * @param logConfig 日志配置
173
+ */
174
+ function scheduleCleanupTask(logConfig) {
175
+ CLEANUP_SCHEDULED = true;
176
+ // 清除现有的清理定时器
177
+ if (CLEANUP_TIMER) {
178
+ clearTimeout(CLEANUP_TIMER);
179
+ }
180
+ // 设置清理任务在一天后执行
181
+ const delay = 24 * 60 * 60 * 1000;
182
+ CLEANUP_TIMER = setTimeout(() => {
183
+ CLEANUP_TIMER = null;
184
+ performCleanupTask(logConfig).catch(e => console.error('Cleanup task failed', e));
185
+ }, delay);
186
+ }
187
+ /**
188
+ * 确保所有日志都被写入文件
189
+ * 可以在应用程序关闭时调用
190
+ */
191
+ async function flushLogsToFile() {
192
+ if (WRITE_TIMER) {
193
+ clearTimeout(WRITE_TIMER);
194
+ WRITE_TIMER = null;
195
+ }
196
+ await writeLogs(config_1.config);
72
197
  }
198
+ exports.flushLogsToFile = flushLogsToFile;
package/dist/log/index.js CHANGED
@@ -2,11 +2,10 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setLogStore = exports.getLogger = void 0;
4
4
  const tslib_1 = require("tslib");
5
- const os_1 = require("os");
6
5
  const config_1 = require("./config");
7
- const date_1 = require("./date");
8
6
  const file_1 = require("./file");
9
7
  const level_1 = require("./level");
8
+ const log_1 = require("./log");
10
9
  const store_1 = require("./store");
11
10
  /**
12
11
  * 文件存储
@@ -14,92 +13,123 @@ const store_1 = require("./store");
14
13
  if (config_1.config.file) {
15
14
  (0, store_1.setLogStore)(file_1.fileStore);
16
15
  }
17
- /**
18
- * 输出日志
19
- * @param level
20
- * @param message
21
- * @param error
22
- */
23
- function log(level, message, error) {
24
- if (level < config_1.config.level) {
25
- return;
16
+ class Logger {
17
+ prefix;
18
+ constructor(prefix) {
19
+ this.prefix = prefix;
26
20
  }
27
- const date = new Date();
28
- // 控制台输出日志
29
- let msg = `[${(0, date_1.formatDateTime)(date)}][${level_1.LogLevel[level]}]${message}`;
30
- console.log(msg);
31
- if (error) {
32
- console.log(error);
33
- }
34
- // 存储中输出日志
35
- const store = (0, store_1.getLogStore)();
36
- if (store) {
37
- if (error) {
38
- if (error.stack) {
39
- msg += os_1.EOL + error.stack;
40
- }
41
- else if (error.message) {
42
- msg += os_1.EOL + error.message;
21
+ /**
22
+ * 输出日志
23
+ * @param level
24
+ * @param message
25
+ * @param error
26
+ */
27
+ log(level, message, error) {
28
+ if (level < config_1.config.level) {
29
+ return;
30
+ }
31
+ const log = {
32
+ level,
33
+ content: message,
34
+ time: new Date(),
35
+ prefix: this.prefix,
36
+ error
37
+ };
38
+ // 控制台输出日志
39
+ if (config_1.config.console) {
40
+ // const msg = config.format === 'text' ? formatLogText(log, true) : formatLogJson(log, true)
41
+ // 控制台强制使用 text 格式,json 格式只在文件中输出
42
+ const msg = (0, log_1.formatLogText)(log, true);
43
+ switch (level) {
44
+ case level_1.LogLevel.DEBUG:
45
+ console.debug(msg);
46
+ break;
47
+ case level_1.LogLevel.INFO:
48
+ console.info(msg);
49
+ break;
50
+ case level_1.LogLevel.WARN:
51
+ console.warn(msg);
52
+ break;
53
+ case level_1.LogLevel.ERROR:
54
+ console.error(msg);
55
+ break;
43
56
  }
44
- else {
45
- msg += os_1.EOL + error;
57
+ if (error) {
58
+ console.error(error);
46
59
  }
47
60
  }
48
- store(msg);
61
+ // 自定义存储中输出日志
62
+ const store = (0, store_1.getLogStore)();
63
+ if (store) {
64
+ store(log, config_1.config);
65
+ }
49
66
  }
50
- }
51
- const logger = Object.freeze({
52
67
  /**
53
68
  * debug 日志
54
69
  */
55
70
  debug(message) {
56
- log(level_1.LogLevel.DEBUG, message);
57
- },
71
+ this.log(level_1.LogLevel.DEBUG, message);
72
+ }
58
73
  isDebugEnabled() {
59
74
  return level_1.LogLevel.DEBUG >= config_1.config.level;
60
- },
75
+ }
61
76
  /**
62
77
  * info 日志
63
78
  * @param message
64
79
  */
65
80
  info(message) {
66
- log(level_1.LogLevel.INFO, message);
67
- },
81
+ this.log(level_1.LogLevel.INFO, message);
82
+ }
68
83
  isInfoEnabled() {
69
84
  return level_1.LogLevel.INFO >= config_1.config.level;
70
- },
85
+ }
71
86
  /**
72
87
  * 警告日志
73
88
  * @param message
74
89
  * @param error
75
90
  */
76
91
  warn(message, error) {
77
- log(level_1.LogLevel.WARN, message, error);
78
- },
92
+ this.log(level_1.LogLevel.WARN, message, error);
93
+ }
94
+ /**
95
+ * 等同于 warn
96
+ * @param message
97
+ * @param error
98
+ */
99
+ warning(message, error) {
100
+ this.log(level_1.LogLevel.WARN, message, error);
101
+ }
79
102
  isWarnEnabled() {
80
103
  return level_1.LogLevel.WARN >= config_1.config.level;
81
- },
104
+ }
82
105
  /**
83
106
  * 错误日志
84
107
  * @param message
85
108
  * @param error
86
109
  */
87
110
  error(message, error) {
88
- log(level_1.LogLevel.ERROR, message, error);
89
- },
111
+ this.log(level_1.LogLevel.ERROR, message, error);
112
+ }
90
113
  isErrorEnabled() {
91
114
  return level_1.LogLevel.ERROR >= config_1.config.level;
92
115
  }
93
- });
116
+ }
117
+ const defaultLogger = new Logger();
94
118
  /**
95
119
  * 获取日志对象.
120
+ *
121
+ * @param prefix 日志前缀,如果有值,每条日志前都会加上前缀信息
96
122
  * @returns
97
123
  */
98
- function getLogger() {
99
- return logger;
124
+ function getLogger(prefix) {
125
+ if (prefix) {
126
+ return new Logger(prefix);
127
+ }
128
+ return defaultLogger;
100
129
  }
101
130
  exports.getLogger = getLogger;
102
131
  tslib_1.__exportStar(require("./config"), exports);
103
132
  tslib_1.__exportStar(require("./level"), exports);
133
+ tslib_1.__exportStar(require("./log"), exports);
104
134
  var store_2 = require("./store");
105
135
  Object.defineProperty(exports, "setLogStore", { enumerable: true, get: function () { return store_2.setLogStore; } });
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatLogJson = exports.formatLogText = void 0;
4
+ const os_1 = require("os");
5
+ const date_1 = require("./date");
6
+ const level_1 = require("./level");
7
+ /**
8
+ * 将日志格式化为简单的文本
9
+ * @param log
10
+ * @param ignoreError 忽略异常信息
11
+ * @returns
12
+ */
13
+ function formatLogText(log, ignoreError = false) {
14
+ let str = `[${(0, date_1.formatDateTime)(log.time)}][${level_1.LogLevel[log.level]}]${log.prefix ? `[${log.prefix}]` : ''}${log.content}`;
15
+ if (log.error && !ignoreError) {
16
+ if (log.error.stack) {
17
+ str += os_1.EOL + log.error.stack;
18
+ }
19
+ else if (log.error.message) {
20
+ str += os_1.EOL + log.error.message;
21
+ }
22
+ else {
23
+ str += os_1.EOL + log.error;
24
+ }
25
+ }
26
+ return str;
27
+ }
28
+ exports.formatLogText = formatLogText;
29
+ /**
30
+ * 将日志格式化为 json
31
+ * @param log
32
+ * @param ignoreError 忽略异常信息
33
+ * @returns
34
+ */
35
+ function formatLogJson(log, ignoreError = false) {
36
+ let error;
37
+ if (log.error && !ignoreError) {
38
+ error = '';
39
+ if (log.error.stack) {
40
+ error += log.error.stack;
41
+ }
42
+ else if (log.error.message) {
43
+ error += log.error.message;
44
+ }
45
+ else {
46
+ error += log.error;
47
+ }
48
+ }
49
+ const json = { ...log };
50
+ delete json.error;
51
+ if (error) {
52
+ json.error = error;
53
+ }
54
+ return JSON.stringify(json);
55
+ }
56
+ exports.formatLogJson = formatLogJson;
package/dist/log/store.js CHANGED
@@ -1,6 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getLogStore = exports.setLogStore = void 0;
4
+ /**
5
+ * 日志存储函数
6
+ */
4
7
  let STORE;
5
8
  /**
6
9
  * 设置日志存储.
@@ -192,6 +192,14 @@ class BaseMysqlManager {
192
192
  find(opts) {
193
193
  return this.queryWithConnection(conn => (0, ops_1.find)(this.opts.config, conn, opts));
194
194
  }
195
+ /**
196
+ * 指定字段条件查询
197
+ * @param opts
198
+ * @returns
199
+ */
200
+ findSelect(opts) {
201
+ return this.queryWithConnection(conn => (0, ops_1.findSelect)(this.opts.config, conn, opts));
202
+ }
195
203
  /**
196
204
  * 指定条件查询数量
197
205
  * @param table 表信息
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.findFirst = exports.findByIdIn = exports.find = exports.findAll = exports.findById = void 0;
3
+ exports.findFirst = exports.findByIdIn = exports.findSelect = exports.find = exports.findAll = exports.findById = void 0;
4
4
  const criteria_1 = require("./criteria");
5
5
  const utils_1 = require("../utils");
6
6
  /**
@@ -73,6 +73,45 @@ async function find(config, conn, opts) {
73
73
  return res;
74
74
  }
75
75
  exports.find = find;
76
+ /**
77
+ * 查询指定的字段,返回值类型与选择的字段有关
78
+ * @param onfig
79
+ * @param conn
80
+ * @param opts
81
+ */
82
+ async function findSelect(config, conn, opts) {
83
+ let query = opts.criteria ? (0, criteria_1.buildQuery)(opts.criteria) : undefined;
84
+ const values = [];
85
+ let sql = `select ${opts.select.map(() => '??').join(',')} from ?? `;
86
+ values.push(...opts.select, opts.table.tableName);
87
+ if (query) {
88
+ sql += ` where ${query.sql} `;
89
+ values.push(...query.values);
90
+ }
91
+ // 排序
92
+ if (opts.orderBy && opts.orderBy.length) {
93
+ opts.orderBy.forEach((orderBy, idx) => {
94
+ const [field, sort] = orderBy;
95
+ if (idx == 0) {
96
+ sql += ` order by ?? ${sort} `;
97
+ }
98
+ else {
99
+ sql += ` , ?? ${sort} `;
100
+ }
101
+ values.push(field);
102
+ });
103
+ }
104
+ // 数量限制
105
+ if (opts.limit) {
106
+ sql += ` limit ${opts.limit} `;
107
+ if (opts.offset) {
108
+ sql += ` offset ${opts.offset}`;
109
+ }
110
+ }
111
+ const res = await (0, utils_1.promiseQuery)(config, conn, sql, values);
112
+ return res;
113
+ }
114
+ exports.findSelect = findSelect;
76
115
  /**
77
116
  * 根据 id 列表查询记录
78
117
  * @param connection
@@ -84,6 +84,9 @@ class MysqlStrictTxSession extends tx_1.MysqlTxSession {
84
84
  find(opts) {
85
85
  throw new exception_1.MysqlException('Prohibited to use find in a strict transaction.');
86
86
  }
87
+ findSelect(opts) {
88
+ throw new exception_1.MysqlException('Prohibited to use findSelect in a strict transaction.');
89
+ }
87
90
  count(table, criteria) {
88
91
  throw new exception_1.MysqlException('Prohibited to use count in a strict transaction.');
89
92
  }
@@ -17,6 +17,10 @@ function regexp(pattern, msg) {
17
17
  if (typeof val !== 'string') {
18
18
  return { ok: false, validator, message: (0, i18n_1.getI18n)().buildMsg('validate-err-string') };
19
19
  }
20
+ // 跳过空串
21
+ if (!val) {
22
+ return { ok: true };
23
+ }
20
24
  if (!pattern.test(val)) {
21
25
  return {
22
26
  ok: false,
@@ -204,7 +204,7 @@ user/create-user.ts 示例:
204
204
 
205
205
  ```ts
206
206
  import { createJsonHandler, getMysqlManager, notBlank, length } from 'wok-server'
207
- import { User, tableUser } from './user.ts'
207
+ import { User, tableUser } from './user'
208
208
 
209
209
  /**
210
210
  * 请求发送的表单信息
@@ -4,12 +4,15 @@
4
4
 
5
5
  ## 环境变量
6
6
 
7
- | 环境变量名称 | 默认值 | 说明 |
8
- | :---------------- | :----- | :----------------------------------------------------------------------- |
9
- | LOG_LEVEL | info | 日志级别,低于设定级别的日志将不会输出,取值:DEBUG,INFO,WARN,ERROR |
10
- | LOG_FILE | false | 取值 true 或 false ,表示是否开启文件 |
11
- | LOG_FILE_MAX_DAYS | 30 | 数文件的保留天数 |
12
- | LOG_FILE_DIR | logs | 日志文件存储路径,支持相对路径和绝对路径,相对路径是相对于进程执行目录的 |
7
+ | 环境变量名称 | 默认值 | 说明 |
8
+ | :---------------- | :----- | :-------------------------------------------------------------------------------- |
9
+ | LOG_LEVEL | info | 日志级别,低于设定级别的日志将不会输出,取值:DEBUG,INFO,WARN,ERROR |
10
+ | LOG_FILE | false | 取值 true 或 false ,表示是否开启文件 |
11
+ | LOG_FILE_MAX_DAYS | 30 | 数文件的保留天数 |
12
+ | LOG_FILE_DIR | logs | 日志文件存储路径,支持相对路径和绝对路径,相对路径是相对于进程执行目录的 |
13
+ | LOG_CONSOLE | true | 是否输出日志到控制台,0.5 版本新增加 |
14
+ | LOG_FORMAT | text | 输出日志的格式,可设置为 json 或 text, 0.5 版本新增加,注意控制台是强制输出文本的 |
15
+
13
16
 
14
17
  ## 使用
15
18
 
@@ -24,17 +27,87 @@ const err = new Error('错误信息测试')
24
27
  logger.error('错误日志输出信息', err)
25
28
 
26
29
  if (logger.isDebugEnabled()) {
27
- logger.debug('调试日志输出', JSON.stringify(args))
30
+ logger.debug(`调试日志输出, args: ${JSON.stringify(args)}`)
31
+ }
32
+ ```
33
+
34
+ ## 判定是否支持某个日志级别
35
+
36
+ 日志对象提供了 isDebugEnabled、isInfoEnabled、isWarnEnabled、isErrorEnabled 方法来判定是否支持某个日志级别。
37
+
38
+ ```ts
39
+ if (logger.isDebugEnabled()) {
40
+ logger.debug('调试日志输出')
28
41
  }
29
42
  ```
30
43
 
44
+ 这些方法的作用是判断当前是否支持某个级别,然后再做处理,如果不支持就不构建日志内容。
45
+ 一些情况下,可以避免构建日志信息所带来的不必要的开销。
46
+
47
+ ## 给日志增加前缀
48
+
49
+ 0.5 版本开始,getLogger 函数支持增加前缀,比如:
50
+
51
+ ```ts
52
+ const logger = getLogger('my-module')
53
+ ```
54
+
55
+ 这样输出的日志会增加前缀 `[my-module]`,方便区分不同模块的日志。
56
+
57
+ 例如,默认无前缀的日志是这样的:
58
+
59
+ ```
60
+ [2024/08/19 16:27:18.214][INFO]Mysql migration
61
+ ```
62
+
63
+ 增加前缀后是这样的:
64
+
65
+ ```
66
+ [2024/08/19 16:27:18.214][INFO][my-module]Mysql migration
67
+ ```
68
+
69
+
31
70
  ## 自定义日志存储
32
71
 
33
72
  通过函数 setLogStore 可以自定义日志的存储,一旦设置,将会覆盖掉文件存储,即使设置了开启文件,日志也不会输出到文件中。
34
73
 
74
+ 从 0.5 版本开始,日志做了结构化,存储函数的参数是日志对象和配置信息,而不是一个字符串了。
75
+
76
+
35
77
  ```ts
36
- setLogStore(log => {
78
+ setLogStore((log: Log, config: LogConfig) => {
79
+ // log 的类型是 Log
80
+ // config 的类型是 LogConfig
37
81
  // 可以根据需要将日志内容放入消息队列或独立的文件存储系统中
38
82
  messageQueue.push(log)
39
83
  })
40
84
  ```
85
+
86
+ Log 的定义如下:
87
+
88
+ ```ts
89
+ export interface Log {
90
+ /**
91
+ * 日志的时间
92
+ */
93
+ time: Date
94
+ /**
95
+ * 日志的等级
96
+ */
97
+ level: LogLevel
98
+ /**
99
+ * 日志的内容
100
+ */
101
+ content: string
102
+ /**
103
+ * 异常信息
104
+ */
105
+ error?: any
106
+ /**
107
+ * 前缀信息
108
+ */
109
+ prefix?: string
110
+ }
111
+ ```
112
+
113
+ LogConfig 类型则是包含了前面的环境变量配置信息,用于获取配置信息来决定如何处理日志。
@@ -29,7 +29,7 @@ mysql 组件基于 [mysql2](https://www.npmjs.com/package/mysql2) 封装,提
29
29
  | MYSQL_SLOW_SQL_MS | 慢 sql 毫秒数,默认 200 |
30
30
  | MYSQL_TRANSACTION_TIMEOUT | 事务超时时间,单位毫秒,默认 5000 |
31
31
  | MYSQL_TRANSACTION_STRICT | 事务严格模式,默认 true,设置为 false 可关闭严格模式 |
32
- | MYSQL_MAX_OPS_IN_STRICT_TX | 严格事务中可以执行的操作次数,默认 10 |
32
+ | MYSQL_MAX_OPS_IN_STRICT_TX | 严格事务中可以执行的操作次数,默认 10 |
33
33
 
34
34
  ## 初始化
35
35
 
@@ -48,7 +48,7 @@ await enableMysql()
48
48
  await enableMysql('d2')
49
49
  ```
50
50
 
51
- 上传的操作要激活 mysql  时,会自动映射以 `D2_` 为前缀的环境变量。
51
+ 执行上面的操作后,会自动映射以 `D2_` 为前缀的环境变量。
52
52
 
53
53
  下面是多数据源的环境变量示例:
54
54
 
@@ -284,27 +284,28 @@ await manager.modify(`update user set nickname='无名' where nickname='佚名'`
284
284
 
285
285
  ### 所有操作方法
286
286
 
287
- | 方法 | 功能说明 |
288
- | :------------ | :-------------------------------------------------------------------------- |
289
- | findById | 按 id 查询 |
290
- | findByIdIn | 按 id 列表查询多条记录 |
291
- | existsBy | 判定指定的条件是否存在记录 |
292
- | existsById | 判定 id 是否存在 |
293
- | deleteById | 按 id 删除 |
294
- | deleteMany | 按指定条件删除,危险操作,建议尽可能设置 limit 参数来限制数量 |
295
- | findAll | 查询表下所有记录,危险操作,建议只对数据量非常小的表使用 |
296
- | findFirst | 查询符合条件的第一条记录 |
297
- | insert | 插入记录 |
298
- | insertMany | 一次性插入多条记录 |
299
- | update | 更新记录,需要完整信息 |
300
- | partialUpdate | 局部更新,只提供 id 和需要更新的字段信息 |
301
- | updateOne | 只更新指定条件的第一条记录,必须是相等条件,不支持范围条件 |
302
- | updateMany | 更新所有符合条件的记录,危险操作,建议对条件严加限制,控制受影响的范围 |
303
- | find | 按条件查询所有符合条件的记录,危险操作,建议尽可能设置 limit 参数来限制数量 |
304
- | count | 统计符合条件的记录数量,危险操作,建议严格限制条件,注意索引的利用 |
305
- | paginate | 分页查询 ,危险操作,基于 find 和 count |
306
- | query | 自定义 sql 查询,返回记录列表,支持预编译 sql |
307
- | modify | 执行自定义 sql,返回操作记录数 ,支持预编译 sql |
287
+ | 方法 | 功能说明 |
288
+ | :------------ | :---------------------------------------------------------------------------------- |
289
+ | findById | 按 id 查询 |
290
+ | findByIdIn | 按 id 列表查询多条记录 |
291
+ | existsBy | 判定指定的条件是否存在记录 |
292
+ | existsById | 判定 id 是否存在 |
293
+ | deleteById | 按 id 删除 |
294
+ | deleteMany | 按指定条件删除,危险操作,建议尽可能设置 limit 参数来限制数量 |
295
+ | findAll | 查询表下所有记录,危险操作,建议只对数据量非常小的表使用 |
296
+ | findFirst | 查询符合条件的第一条记录 |
297
+ | insert | 插入记录 |
298
+ | insertMany | 一次性插入多条记录 |
299
+ | update | 更新记录,需要完整信息 |
300
+ | partialUpdate | 局部更新,只提供 id 和需要更新的字段信息 |
301
+ | updateOne | 只更新指定条件的第一条记录,必须是相等条件,不支持范围条件 |
302
+ | updateMany | 更新所有符合条件的记录,危险操作,建议对条件严加限制,控制受影响的范围 |
303
+ | find | 按条件查询所有符合条件的记录,危险操作,建议尽可能设置 limit 参数来限制数量 |
304
+ | findSelect | 指定字段进行条件查询,与 find 唯一的不同的是多一个参数 select 可以用来指定要返回的列 |
305
+ | count | 统计符合条件的记录数量,危险操作,建议严格限制条件,注意索引的利用 |
306
+ | paginate | 分页查询 ,危险操作,基于 find 和 count |
307
+ | query | 自定义 sql 查询,返回记录列表,支持预编译 sql |
308
+ | modify | 执行自定义 sql,返回操作记录数 ,支持预编译 sql |
308
309
 
309
310
  ### json 类型
310
311
 
@@ -20,8 +20,8 @@ validate(
20
20
 
21
21
  | 函数 | 作用 |
22
22
  | :---------- | :--------------------------------------------------- |
23
- | notNull | 非空校验,不能是 null 或 undefinded |
24
- | notBlank | 字符非空校验,不能是 null 或 undefinded 或空白字符串 |
23
+ | notNull | 非空校验,不能是 null 或 undefined |
24
+ | notBlank | 字符非空校验,不能是 null 或 undefined 或空白字符串 |
25
25
  | min | 校验数字最小值 |
26
26
  | max | 校验数字最大值 |
27
27
  | length | 校验长度,适用于字符串和数组 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wok-server",
3
- "version": "0.4.12",
3
+ "version": "0.5.0",
4
4
  "packageManager": "pnpm@8.9.0",
5
5
  "description": "一个基于 NodeJs 和 TypeScript 的后端框架,轻量级、克制、简洁。A lightweight, restrained, and concise backend framework based on Node.js and TypeScript.",
6
6
  "scripts": {
@@ -3,6 +3,14 @@ import { LogLevel } from './level';
3
3
  * 日志配置的定义
4
4
  */
5
5
  interface EnvConfig {
6
+ /**
7
+ * 是否输出日志到控制台
8
+ */
9
+ console: boolean;
10
+ /**
11
+ * 输出日志的格式
12
+ */
13
+ format: 'text' | 'json';
6
14
  /**
7
15
  * 是否输出到文件,如果为 true ,则会在根目录下生成 logs 目录存放日志文件.
8
16
  */
@@ -1,5 +1,13 @@
1
+ import { LogConfig } from './config';
2
+ import { Log } from './log';
1
3
  /**
2
4
  * 文件存储.
3
- * @param log
5
+ * @param log 日志对象
6
+ * @param logConfig 日志配置
4
7
  */
5
- export declare function fileStore(log: string): void;
8
+ export declare function fileStore(log: Log, logConfig: LogConfig): void;
9
+ /**
10
+ * 确保所有日志都被写入文件
11
+ * 可以在应用程序关闭时调用
12
+ */
13
+ export declare function flushLogsToFile(): Promise<void>;
@@ -1,8 +1,13 @@
1
- /**
2
- * 获取日志对象.
3
- * @returns
4
- */
5
- export declare function getLogger(): Readonly<{
1
+ declare class Logger {
2
+ private prefix?;
3
+ constructor(prefix?: string | undefined);
4
+ /**
5
+ * 输出日志
6
+ * @param level
7
+ * @param message
8
+ * @param error
9
+ */
10
+ private log;
6
11
  /**
7
12
  * debug 日志
8
13
  */
@@ -20,6 +25,12 @@ export declare function getLogger(): Readonly<{
20
25
  * @param error
21
26
  */
22
27
  warn(message: string, error?: any): void;
28
+ /**
29
+ * 等同于 warn
30
+ * @param message
31
+ * @param error
32
+ */
33
+ warning(message: string, error?: any): void;
23
34
  isWarnEnabled(): boolean;
24
35
  /**
25
36
  * 错误日志
@@ -28,7 +39,15 @@ export declare function getLogger(): Readonly<{
28
39
  */
29
40
  error(message: string, error?: any): void;
30
41
  isErrorEnabled(): boolean;
31
- }>;
42
+ }
43
+ /**
44
+ * 获取日志对象.
45
+ *
46
+ * @param prefix 日志前缀,如果有值,每条日志前都会加上前缀信息
47
+ * @returns
48
+ */
49
+ export declare function getLogger(prefix?: string): Logger;
32
50
  export * from './config';
33
51
  export * from './level';
52
+ export * from './log';
34
53
  export { setLogStore } from './store';
@@ -0,0 +1,40 @@
1
+ import { LogLevel } from './level';
2
+ /**
3
+ * 单条日志的信息
4
+ */
5
+ export interface Log {
6
+ /**
7
+ * 日志的时间
8
+ */
9
+ time: Date;
10
+ /**
11
+ * 日志的等级
12
+ */
13
+ level: LogLevel;
14
+ /**
15
+ * 日志的内容
16
+ */
17
+ content: string;
18
+ /**
19
+ * 异常信息
20
+ */
21
+ error?: any;
22
+ /**
23
+ * 前缀信息
24
+ */
25
+ prefix?: string;
26
+ }
27
+ /**
28
+ * 将日志格式化为简单的文本
29
+ * @param log
30
+ * @param ignoreError 忽略异常信息
31
+ * @returns
32
+ */
33
+ export declare function formatLogText(log: Log, ignoreError?: boolean): string;
34
+ /**
35
+ * 将日志格式化为 json
36
+ * @param log
37
+ * @param ignoreError 忽略异常信息
38
+ * @returns
39
+ */
40
+ export declare function formatLogJson(log: Log, ignoreError?: boolean): string;
@@ -1,8 +1,15 @@
1
+ import { LogConfig } from './config';
2
+ import { Log } from './log';
1
3
  /**
2
4
  * 日志存储函数
3
5
  */
4
6
  export interface LogStore {
5
- (log: string): void;
7
+ /**
8
+ * 日志存储函数
9
+ * @param log 日志
10
+ * @param config 日志配置
11
+ */
12
+ (log: Log, config: LogConfig): void;
6
13
  }
7
14
  /**
8
15
  * 设置日志存储.
@@ -1,7 +1,7 @@
1
1
  import { Pool, PoolConnection } from 'mysql2';
2
2
  import { MysqlConfig } from '../config';
3
3
  import { Table } from '../table-info';
4
- import { DeleteManyOpts, FindOpts, MixCriteria, MysqlPage, MysqlPaginateOpts, UpdateOpts, Updater } from './ops';
4
+ import { DeleteManyOpts, FindOpts, MixCriteria, MysqlPage, MysqlPaginateOpts, UpdateOpts, Updater, FindSelectOpts } from './ops';
5
5
  /**
6
6
  * mysql 管理器基类,提供基础的操作方法.
7
7
  */
@@ -129,6 +129,12 @@ export declare abstract class BaseMysqlManager {
129
129
  * @returns
130
130
  */
131
131
  find<T>(opts: FindOpts<T>): Promise<T[]>;
132
+ /**
133
+ * 指定字段条件查询
134
+ * @param opts
135
+ * @returns
136
+ */
137
+ findSelect<T, K extends keyof T>(opts: FindSelectOpts<T, K>): Promise<Pick<T, K>[]>;
132
138
  /**
133
139
  * 指定条件查询数量
134
140
  * @param table 表信息
@@ -48,6 +48,22 @@ export interface FindOpts<T> {
48
48
  * @returns
49
49
  */
50
50
  export declare function find<T>(config: MysqlConfig, conn: PoolConnection, opts: FindOpts<T>): Promise<T[]>;
51
+ /**
52
+ * 指定字段条件查询选项
53
+ */
54
+ export interface FindSelectOpts<T, K extends keyof T> extends FindOpts<T> {
55
+ /**
56
+ * 要查询的字段
57
+ */
58
+ select: K[];
59
+ }
60
+ /**
61
+ * 查询指定的字段,返回值类型与选择的字段有关
62
+ * @param onfig
63
+ * @param conn
64
+ * @param opts
65
+ */
66
+ export declare function findSelect<T, K extends keyof T>(config: MysqlConfig, conn: PoolConnection, opts: FindSelectOpts<T, K>): Promise<Array<Pick<T, K>>>;
51
67
  /**
52
68
  * 根据 id 列表查询记录
53
69
  * @param connection
@@ -2,7 +2,7 @@ import { PoolConnection } from 'mysql2';
2
2
  import { MysqlConfig } from '../config';
3
3
  import { MysqlTxSession } from './tx';
4
4
  import { Table } from '../table-info';
5
- import { DeleteManyOpts, FindOpts, MixCriteria, MysqlPage, MysqlPaginateOpts, UpdateOpts, Updater } from './ops';
5
+ import { DeleteManyOpts, FindOpts, FindSelectOpts, MixCriteria, MysqlPage, MysqlPaginateOpts, UpdateOpts, Updater } from './ops';
6
6
  /**
7
7
  * 严格 mysql 事务会话,会禁用一些操作.
8
8
  */
@@ -28,6 +28,7 @@ export declare class MysqlStrictTxSession extends MysqlTxSession {
28
28
  }>): Promise<boolean>;
29
29
  partialUpdate<T>(table: Table<T>, data: Updater<T>): Promise<boolean>;
30
30
  find<T>(opts: FindOpts<T>): Promise<T[]>;
31
+ findSelect<T, K extends keyof T>(opts: FindSelectOpts<T, K>): Promise<Pick<T, K>[]>;
31
32
  count<T>(table: Table<T>, criteria?: MixCriteria<T> | undefined): Promise<number>;
32
33
  paginate<T>(opts: MysqlPaginateOpts<T>): Promise<MysqlPage<T>>;
33
34
  query<T>(sql: string, values?: any[] | undefined): Promise<T[]>;