chanjs 2.0.18 → 2.0.19

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.
@@ -1,6 +1,10 @@
1
+
2
+ import {formatTime} from "../helper/time.js";
3
+
1
4
  export default class Controller {
2
5
  constructor() {}
3
- success(data) {
6
+
7
+ success(data){
4
8
  return {
5
9
  success: true,
6
10
  msg: "操作成功",
package/core/service.js CHANGED
@@ -8,18 +8,20 @@ class Service {
8
8
  /**
9
9
  * 构造函数
10
10
  * @param {Object} knex - Knex实例
11
- * @param {string} model - 表名/模型名
11
+ * @param {string} tableName - 表名
12
12
  */
13
- constructor(knex, model) {
14
- if (!knex || !model) {
15
- throw new Error("Service: knex instance and model name are required");
13
+ constructor(knex, tableName) {
14
+ if (!knex || !tableName) {
15
+ throw new Error("Service: knex instance and tableName are required");
16
16
  }
17
17
 
18
- this.knex = knex;
19
- this.model = model;
18
+ this.db = knex;
19
+ this.tableName = tableName;
20
20
  this.pageSize = 100;
21
21
  }
22
22
 
23
+
24
+
23
25
  /**
24
26
  * 查询表所有记录(慎用)
25
27
  * @param {Object} query - 查询条件
@@ -27,28 +29,58 @@ class Service {
27
29
  */
28
30
  async all(query = {}) {
29
31
  try {
30
- let dbQuery = this.knex(this.model);
31
- if (Object.keys(query).length > 0) {
32
- dbQuery = dbQuery.where(query);
33
- }
34
- const res = await dbQuery.select();
32
+ let _query = this.db(this.tableName);
33
+ if (Object.keys(query).length) _query = _query.where(query);
34
+ const res = await _query.select();
35
35
  return success(res);
36
36
  } catch (err) {
37
37
  return error(err);
38
38
  }
39
39
  }
40
40
 
41
+ /**
42
+ * 查询所有文档(仅支持对象模式排序)
43
+ * @param {Object} query - 查询条件,可包含 sort: { field: 'asc|desc' }
44
+ * @returns {Promise} 查询结果
45
+ */
46
+ async find(query = {}) {
47
+ try {
48
+ let _query = this.db(this.tableName);
49
+
50
+ // 解构出排序参数,剩余作为 where 条件
51
+ const { sort, ...whereConditions } = query;
52
+
53
+ // 应用 where 条件
54
+ if (Object.keys(whereConditions).length > 0) {
55
+ _query = _query.where(whereConditions);
56
+ }
57
+
58
+ // 应用排序(仅支持对象模式)
59
+ if (sort && typeof sort === 'object') {
60
+ for (const [field, dir] of Object.entries(sort)) {
61
+ const direction = ['asc', 'desc'].includes(dir.toLowerCase())
62
+ ? dir.toLowerCase()
63
+ : 'asc'; // 默认升序
64
+ _query = _query.orderBy(field, direction);
65
+ }
66
+ }
67
+
68
+ const res = await _query.select();
69
+ return success(res);
70
+ } catch (err) {
71
+ return error(err);
72
+ }
73
+ }
74
+
41
75
  /**
42
76
  * 获取单个记录
43
77
  * @param {Object} query - 查询条件
44
78
  * @returns {Promise} 查询结果
45
79
  */
46
- async one(query = {}) {
80
+ async findOne(query = {}) {
47
81
  try {
48
- let dbQuery = this.knex(this.model);
49
- if (Object.keys(query).length > 0) {
50
- dbQuery = dbQuery.where(query);
51
- }
82
+ let dbQuery = this.db(this.tableName);
83
+ if (Object.keys(query).length) dbQuery = dbQuery.where(query);
52
84
  const res = await dbQuery.first();
53
85
  return success(res);
54
86
  } catch (err) {
@@ -60,35 +92,30 @@ class Service {
60
92
  * 根据ID查询记录
61
93
  * @param {Object} options - 查询选项
62
94
  * @param {Object} options.query - 查询条件
63
- * @param {Array} options.field - 返回字段
64
- * @param {number} options.len - 返回数量
95
+ * @param {Array} options.field - 返回字段
65
96
  */
66
- async findById({ query, field = [], len = 1 }) {
97
+ async findById({ query, field = [] }) {
67
98
  try {
68
- let dataQuery = this.knex(this.model).where(query);
69
- if (field.length > 0) dataQuery = dataQuery.select(field);
70
- if (len === 1) dataQuery = dataQuery.first();
71
- else if (len > 1) dataQuery = dataQuery.limit(len);
72
-
73
- const res = await dataQuery;
74
- return success(res || (len === 1 ? {} : []));
99
+ let _query = this.db(this.tableName).where(query);
100
+ if (field.length) _query = _query.select(field);
101
+ const res = await _query.first();
102
+ return success(res);
75
103
  } catch (err) {
76
104
  return error(err);
77
105
  }
78
106
  }
79
107
 
108
+
80
109
  /**
81
110
  * 创建新记录
82
- * @param {Object} data - 包含要插入的数据对象
111
+ * @param {Object} data - 要插入的数据
83
112
  * @returns {Promise} 操作结果
84
113
  */
85
114
  async insert(data = {}) {
86
115
  try {
87
- if (Object.keys(data).length === 0) {
88
- return fail(CODE[2002], { code: 2002 });
89
- }
90
- const result = await this.knex(this.model).insert(data);
91
- return success(result?.length > 0 || !!result);
116
+ if (!Object.keys(data).length) return fail(CODE[2002], { code: 2002 });
117
+ const result = await this.db(this.tableName).insert(data);
118
+ return success(result);
92
119
  } catch (err) {
93
120
  return error(err);
94
121
  }
@@ -96,15 +123,13 @@ class Service {
96
123
 
97
124
  /**
98
125
  * 插入多条记录
99
- * @param {Array} records - 包含要插入的数据对象数组
126
+ * @param {Array} records - 数据数组
100
127
  * @returns {Promise} 操作结果
101
128
  */
102
129
  async insertMany(records = []) {
103
130
  try {
104
- if (records.length === 0) {
105
- return fail(CODE[2002], { code: 2002 });
106
- }
107
- const result = await this.knex(this.model).insert(records);
131
+ if (!records.length) return fail(CODE[2002], { code: 2002 });
132
+ const result = await this.db(this.tableName).insert(records);
108
133
  return success(result);
109
134
  } catch (err) {
110
135
  return error(err);
@@ -112,16 +137,14 @@ class Service {
112
137
  }
113
138
 
114
139
  /**
115
- * 根据指定条件删除记录
116
- * @param {Object} query - 包含查询条件的对象
140
+ * 根据条件删除记录
141
+ * @param {Object} query - 查询条件
117
142
  * @returns {Promise} 操作结果
118
143
  */
119
144
  async delete(query = {}) {
120
145
  try {
121
- if (Object.keys(query).length === 0) {
122
- return fail(CODE[2002], { code: 2002 });
123
- }
124
- const affectedRows = await this.knex(this.model).where(query).del();
146
+ if (!Object.keys(query).length) return fail(CODE[2002], { code: 2002 });
147
+ const affectedRows = await this.db(this.tableName).where(query).del();
125
148
  return success(affectedRows > 0);
126
149
  } catch (err) {
127
150
  return error(err);
@@ -129,7 +152,7 @@ class Service {
129
152
  }
130
153
 
131
154
  /**
132
- * 根据指定条件更新记录
155
+ * 根据条件更新记录
133
156
  * @param {Object} options - 更新选项
134
157
  * @param {Object} options.query - 查询条件
135
158
  * @param {Object} options.params - 更新数据
@@ -137,10 +160,10 @@ class Service {
137
160
  */
138
161
  async update({ query, params } = {}) {
139
162
  try {
140
- if (!query || !params || Object.keys(query).length === 0) {
163
+ if (!query || !params || !Object.keys(query).length) {
141
164
  return fail(CODE[2001], { code: 2001 });
142
165
  }
143
- const result = await this.knex(this.model).where(query).update(params);
166
+ const result = await this.db(this.tableName).where(query).update(params);
144
167
  return success(!!result);
145
168
  } catch (err) {
146
169
  return error(err);
@@ -148,21 +171,27 @@ class Service {
148
171
  }
149
172
 
150
173
  /**
151
- * 批量更新多条记录
152
- * @param {Array} updates - 更新操作数组
174
+ * 批量更新多条记录(事务)
175
+ * @param {Array} updates - [{ query, params }, ...]
153
176
  * @returns {Promise} 操作结果
154
177
  */
155
178
  async updateMany(updates = []) {
156
- if (!Array.isArray(updates) || updates.length === 0) {
179
+ if (!Array.isArray(updates) || !updates.length) {
157
180
  return fail(CODE[2002], { code: 2002 });
158
181
  }
159
182
 
160
- const trx = await this.knex.transaction();
183
+ const trx = await this.db.transaction();
161
184
  try {
162
185
  for (const { query, params } of updates) {
163
- const result = await trx(this.model).where(query).update(params);
186
+ // FIX-4 防止空条件全表更新
187
+ if (!query || !Object.keys(query).length) {
188
+ await trx.rollback();
189
+ return fail('批量更新不允许空条件');
190
+ }
191
+ const result = await trx(this.tableName).where(query).update(params);
164
192
  if (result === 0) {
165
193
  await trx.rollback();
194
+ console.warn('[updateMany] 未匹配到行, query=%j', query); // FIX-3 日志
166
195
  return fail(`更新失败: ${JSON.stringify(query)}`);
167
196
  }
168
197
  }
@@ -174,37 +203,41 @@ class Service {
174
203
  }
175
204
  }
176
205
 
206
+
177
207
  /**
178
208
  * 分页查询
179
209
  * @param {Object} options - 查询选项
180
- * @param {number} options.current - 当前页码
181
- * @param {number} options.pageSize - 每页大小
182
- * @param {Object} options.query - 查询条件
183
- * @param {Array} options.field - 返回字段
210
+ * @param {number} options.current - 当前页
211
+ * @param {number} options.pageSize - 每页条数
212
+ * @param {Object} options.query - 查询条件
213
+ * @param {Array} options.field - 返回字段
184
214
  * @returns {Promise} 查询结果
185
215
  */
186
216
  async query({ current = 1, pageSize = 10, query = {}, field = [] }) {
187
217
  try {
188
- const offset = (current - 1) * pageSize;
189
- let countQuery = this.knex(this.model).count("* as total");
190
- let dataQuery = this.knex(this.model);
191
-
192
- if (Object.keys(query).length > 0) {
193
- Object.entries(query).forEach(([key, value]) => {
194
- countQuery = countQuery.where(key, value);
195
- dataQuery = dataQuery.where(key, value);
218
+ const size = pageSize; // 如需要可再做非空校验
219
+ const offset = (current - 1) * size;
220
+
221
+ let countQuery = this.db(this.tableName).count("* as total");
222
+ let dataQuery = this.db(this.tableName);
223
+
224
+ if (Object.keys(query).length) {
225
+ Object.entries(query).forEach(([k, v]) => {
226
+ countQuery = countQuery.where(k, v);
227
+ dataQuery = dataQuery.where(k, v);
196
228
  });
197
229
  }
198
230
 
199
- if (field.length > 0) dataQuery = dataQuery.select(field);
231
+ if (field && field.length) dataQuery = dataQuery.select(field);
200
232
 
201
233
  const [totalResult, list] = await Promise.all([
202
234
  countQuery.first(),
203
- dataQuery.offset(offset).limit(pageSize),
235
+ dataQuery.offset(offset).limit(size),
204
236
  ]);
205
237
 
206
- const total = totalResult?.total || 0;
207
- return success({ list, total, current, pageSize });
238
+ // FIX-1 显式转数字,防止字符串 "0"
239
+ const total = Number(totalResult?.total ?? 0);
240
+ return success({ list, total, current, pageSize: size });
208
241
  } catch (err) {
209
242
  return error(err);
210
243
  }
@@ -212,21 +245,51 @@ class Service {
212
245
 
213
246
  /**
214
247
  * 计数查询
215
- * @param {Array} query - 查询条件数组
248
+ * @param {Object} query - 查询条件
216
249
  * @returns {Promise} 查询结果
217
250
  */
218
- async count(query = []) {
251
+ async count(query = {}) {
219
252
  try {
220
- let dataQuery = this.knex(this.model);
221
- if (query.length > 0) {
222
- query.forEach((condition) => (dataQuery = dataQuery.where(condition)));
223
- }
253
+ let dataQuery = this.db(this.tableName);
254
+ if (Object.keys(query).length) dataQuery = dataQuery.where(query);
224
255
  const result = await dataQuery.count("* as total").first();
225
- return success(Number(result?.total) || 0);
256
+ return success(Number(result?.total ?? 0));
257
+ } catch (err) {
258
+ return error(err);
259
+ }
260
+ }
261
+
262
+ /**
263
+ * 多表关联查询
264
+ * @param {Object} query - 关联配置
265
+ * @returns {Promise} 查询结果
266
+ */
267
+ async join(query) {
268
+ try {
269
+ const {
270
+ joinTable,
271
+ localField,
272
+ foreignField,
273
+ select = "*",
274
+ where = {},
275
+ } = query;
276
+
277
+ let _query = this.db(this.tableName)
278
+ .join(
279
+ joinTable,
280
+ `${this.tableName}.${localField}`,
281
+ "=",
282
+ `${joinTable}.${foreignField}`
283
+ )
284
+ .select(select);
285
+
286
+ if (Object.keys(where).length) _query = _query.where(where);
287
+ const res = await _query;
288
+ return success(res);
226
289
  } catch (err) {
227
290
  return error(err);
228
291
  }
229
292
  }
230
293
  }
231
294
 
232
- export default Service;
295
+ export default Service;
package/helper/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { db } from "./db.js";
2
2
  import { loaderSort, loadConfig } from "./loader.js";
3
+ import { formatTime, formatDateFields } from "./time.js";
3
4
 
4
- export { db, loaderSort, loadConfig };
5
+ export { db, loaderSort, loadConfig, formatTime, formatDateFields };
package/helper/loader.js CHANGED
@@ -48,6 +48,26 @@ export const loadConfig = async function () {
48
48
  return config;
49
49
  };
50
50
 
51
+ /**
52
+ * 绑定已实例化对象的方法
53
+ * @param {Object} instance - 已实例化的对象
54
+ * @returns {Object} 绑定方法后的对象
55
+ */
56
+ export const bindInstance = function (instance) {
57
+ Object.getOwnPropertyNames(Object.getPrototypeOf(instance)).forEach(
58
+ (methodName) => {
59
+ if (
60
+ methodName !== "constructor" &&
61
+ typeof instance[methodName] === "function"
62
+ ) {
63
+ instance[methodName] = instance[methodName].bind(instance);
64
+ }
65
+ }
66
+ );
67
+ return instance;
68
+ };
69
+
70
+
51
71
  /**
52
72
  * 加载指定模块名下的所有控制器文件
53
73
  * @param {string} moduleName - 模块名称
@@ -67,17 +87,15 @@ export const loadController = async function (moduleName) {
67
87
 
68
88
  for (const file of files) {
69
89
  const filePath = path.join(dir, file);
70
- const name = file.replace(/\.js$/i, ""); // 安全处理 .js 后缀
90
+ const name = file.replace(/\.js$/i, "");
71
91
 
72
92
  try {
73
93
  const module = await importFile(filePath);
74
- let obj = module.default || module;
75
-
76
- controller[name] = obj;
94
+ let obj = module?.default || module;
95
+ // 使用 bindClass 确保方法绑定了正确的 this
96
+ controller[name] = bindInstance(obj);
77
97
  } catch (e) {
78
98
  console.error(`加载控制器失败: ${filePath}`, e);
79
- // 可选:抛出错误或继续加载其他文件
80
- // throw e;
81
99
  }
82
100
  }
83
101
 
package/helper/time.js CHANGED
@@ -24,5 +24,51 @@ export const formatDay = (data, time = true, format = "YYYY-MM-DD") => {
24
24
  };
25
25
 
26
26
  export const formatTime = (data, format = "YYYY-MM-DD HH:mm:ss") => {
27
- return dayjs(data).format("YYYY-MM-DD HH:mm:ss");
27
+ return dayjs(data).format(format);
28
28
  };
29
+
30
+ /**
31
+ * 格式化对象或数组中的日期字段
32
+ * @param {Object|Array} data - 要格式化的数据
33
+ * @param {Object} options - 配置选项
34
+ * @param {Array} options.fields - 要格式化的字段名,默认 ['createdAt', 'updatedAt']
35
+ * @param {String} options.format - 日期格式,默认 'YYYY-MM-DD HH:mm:ss'
36
+ * @returns {Object|Array} - 格式化后的数据
37
+ */
38
+ export const formatDateFields = (data, options = {}) => {
39
+ const {
40
+ fields = ['createdAt', 'updatedAt'],
41
+ format = 'YYYY-MM-DD HH:mm:ss',
42
+ } = options;
43
+
44
+ const isPlainObject = (val) =>
45
+ Object.prototype.toString.call(val) === '[object Object]';
46
+
47
+ if (isPlainObject(data)) {
48
+ const result = { ...data };
49
+ fields.forEach(field => {
50
+ const value = result[field];
51
+ if (value != null) {
52
+ const type = typeof value;
53
+ if (type === 'string' || type === 'number' || value instanceof Date) {
54
+ try {
55
+ result[field] = formatTime(value, format);
56
+ } catch (e) {
57
+ console.warn(`格式化字段 ${field} 失败:`, e.message);
58
+ }
59
+ }
60
+ }
61
+ });
62
+ return result;
63
+ }
64
+
65
+ if (Array.isArray(data)) {
66
+ return data.map(item =>
67
+ typeof item === 'object' && item !== null
68
+ ? formatDateFields(item, options)
69
+ : item
70
+ );
71
+ }
72
+
73
+ return data;
74
+ };;
package/index.js CHANGED
@@ -29,7 +29,7 @@ class Chan {
29
29
  static Controller = Controller; //控制器
30
30
  static extend = {}; //组件扩展
31
31
  static middleware = {}; //中间件
32
-
32
+ static modules = {}; //模块
33
33
 
34
34
  constructor() {
35
35
  this.app = express();
@@ -165,7 +165,6 @@ class Chan {
165
165
  methods: Object.keys(r.route.methods),
166
166
  }));
167
167
 
168
- console.log(routes);
169
168
  }
170
169
 
171
170
  run(cb) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "chanjs",
4
- "version": "2.0.18",
4
+ "version": "2.0.19",
5
5
  "description": "chanjs基于express5 纯js研发的轻量级mvc框架。",
6
6
  "main": "index.js",
7
7
  "module": "index.js",