q-koa 11.3.4 → 11.4.1

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.
package/core/app.js CHANGED
@@ -853,6 +853,7 @@ class APP {
853
853
  !_.get(app, `${appName}.model.${controller}`) ||
854
854
  !_.get(app, `${appName}.controller.${controller}`)
855
855
  ) {
856
+ console.error(`${controller}`)
856
857
  throw new Error('路由model定义缺失')
857
858
  }
858
859
  await next()
@@ -870,6 +871,7 @@ class APP {
870
871
  !_.get(app, `${appName}.model.${controller}.${fn}`) &&
871
872
  !_.get(app, `${appName}.controller.${controller}.${fn}`)
872
873
  ) {
874
+ console.error(`${controller}.${fn}`)
873
875
  throw new Error('路由fn定义缺失')
874
876
  }
875
877
 
@@ -45,3 +45,599 @@ exports.initModel = async (ctx) => {
45
45
  return ctx.ERROR(`app.model没有${model}`)
46
46
  }
47
47
  }
48
+
49
+ exports.customRouter = async (ctx) => {
50
+ // const {
51
+ // app,
52
+ // appName,
53
+ // } = getAppByCtx(ctx);
54
+
55
+ // const list = [{
56
+ // path: 'jiangong_statis',
57
+ // name: '数据面板'
58
+ // }]
59
+ const list = []
60
+ ctx.SUCCESS(list)
61
+ return list
62
+ }
63
+
64
+ exports.showTables = async (ctx) => {
65
+ const { app, appName } = getAppByCtx(ctx)
66
+
67
+ if (app.cache && app.cache.get('showTables')) {
68
+ ctx.SUCCESS(app.cache.get('showTables'))
69
+ return app.cache.get('showTables')
70
+ }
71
+
72
+ const pluginDir = path.resolve(
73
+ __dirname,
74
+ `${process.cwd()}/app/${appName}/plugins`
75
+ )
76
+ let aliasModelList = []
77
+ const allList = await fsPromise.readdir(pluginDir)
78
+ allList.forEach((folder) => {
79
+ const isFolder = fs.lstatSync(path.resolve(pluginDir, folder)).isDirectory()
80
+ const hasConfig = fs.existsSync(
81
+ path.resolve(
82
+ __dirname,
83
+ `${process.cwd()}/app/${appName}/plugins/${folder}/config.js`
84
+ )
85
+ )
86
+ let hasModel = false
87
+ if (hasConfig) {
88
+ const folderConfig = require(`${process.cwd()}/app/${appName}/plugins/${folder}/config.js`)
89
+ if (folderConfig.model) {
90
+ hasModel = true
91
+ }
92
+ }
93
+
94
+ if (
95
+ isFolder &&
96
+ hasConfig &&
97
+ hasModel &&
98
+ !Object.keys(app.model).includes(folder)
99
+ ) {
100
+ aliasModelList = [...aliasModelList, folder]
101
+ }
102
+ })
103
+ const modelList = [...Object.keys(app.model), ...aliasModelList]
104
+ const models = modelList.map((_model) => {
105
+ const defaultExcludes = [
106
+ 'id',
107
+ 'created_at',
108
+ 'updated_at',
109
+ 'deleted_at',
110
+ 'createdid',
111
+ ]
112
+ const mock = !lodash.isEmpty(
113
+ app.mock && app.mock[_model] && app.mock[_model]()
114
+ )
115
+ const defaultConfigPath = path.resolve(__dirname, `../${_model}/config.js`)
116
+ const defaultConfigExist = fs.existsSync(defaultConfigPath)
117
+ const target =
118
+ app.config[_model] &&
119
+ Object.keys(app.config[_model]).length === 0 &&
120
+ defaultConfigExist
121
+ ? require(defaultConfigPath)
122
+ : app.config[_model]
123
+ // const target = Object.keys(app.config[model])
124
+
125
+ if (!target) throw new Error(`检查是否有${_model}/config.js`)
126
+ const model = target.model ? target.model : _model
127
+ return {
128
+ modelName: _model,
129
+ model,
130
+ name: target ? target.name : '',
131
+ // 属于哪个分类
132
+ belongs: lodash.get(target, 'belongs', 'page'),
133
+ // list切换tab显示 ex lanuage
134
+ limit: lodash.get(target, 'limit', 20),
135
+ excludeAuth: lodash.get(target, 'excludeAuth', []),
136
+ // 可排序字段
137
+ order: lodash.get(target, 'order', []),
138
+ // 默认排序
139
+ defaultOrder: lodash.get(target, 'defaultOrder', []),
140
+ // 下拉筛选
141
+ select: lodash.get(target, 'select', []),
142
+ // 是否可以选择批量删除
143
+ multiple: lodash.get(target, 'multiple', true),
144
+ // 是否后台拆分sql
145
+ is_split: lodash.get(target, 'is_split', false),
146
+ // 是否后台拆分count
147
+ is_split_count: lodash.get(target, 'is_split_count', false),
148
+ // 是否后台显示虚拟字段
149
+ show_virtual: lodash.get(target, 'show_virtual', false),
150
+ // 忽略字段
151
+ excludes:
152
+ _model === 'setting'
153
+ ? lodash.get(target, 'excludes', [])
154
+ : lodash.difference(
155
+ lodash.uniq([
156
+ ...defaultExcludes,
157
+ ...lodash.get(target, 'excludes', []),
158
+ ]),
159
+ lodash.get(target, 'order', [])
160
+ ),
161
+ // reference admin components
162
+ reference: lodash.get(target, 'reference', []),
163
+ // reference admin components
164
+ personal: lodash.get(target, 'personal', []),
165
+ // sortOrder router
166
+ sortOrder: lodash.get(target, 'sortOrder', 1),
167
+
168
+ attributes: lodash.mapValues(
169
+ app.attributes[model],
170
+ (item) => item.comment
171
+ ),
172
+
173
+ // include for admin
174
+ include: lodash.get(target, 'include', ''),
175
+ // only include exsit, and for custom like user_distribute has two user model
176
+ relate: lodash.get(target, 'relate', ''),
177
+ // can be mock boolean
178
+ mock,
179
+ // reference model query
180
+ referenceSelect: lodash.get(target, 'referenceSelect', []),
181
+ // 是否可以选择批量删除
182
+ availableSort: lodash.get(target, 'availableSort', false),
183
+ // 用于后台管理不要查询太多数据,常用于表多的model
184
+ autoData: lodash.get(target, 'autoData', null),
185
+ // 有些model需要前置查询其它model数据
186
+ initList: lodash.get(target, 'initList', []),
187
+ // 有些table需要前置查询数据
188
+ initTableList: lodash.get(target, 'initTableList', []),
189
+ // 局部更新列表
190
+ editInline: lodash.get(target, 'editInline', {}),
191
+ // 有些model需要前置查询其它model数据
192
+ comment: lodash.get(target, 'comment', {}),
193
+ // 默认查询,覆盖之前
194
+ modelQuery: lodash.get(target, 'modelQuery', {}),
195
+ // 默认更新,覆盖之前
196
+ modelUpsert: lodash.get(target, 'modelUpsert', {}),
197
+ // 软删除比如is_on:false,就upsert
198
+ modelDelete: lodash.get(target, 'modelDelete', {}),
199
+ // 删除时检查是不是含有
200
+ deleteCheckList: lodash.get(target, 'deleteCheckList', []),
201
+ // 批量更新字段
202
+ bulkCreateList: lodash.get(target, 'bulkCreateList', []),
203
+
204
+ fn: app.controller[_model] ? Object.keys(app.controller[_model]) : [],
205
+ }
206
+ })
207
+
208
+ ctx.SUCCESS(models)
209
+ app.cache && app.cache.set('showTables', models)
210
+ return models
211
+ }
212
+
213
+ exports.getTable = async (ctx) => {
214
+ const { app } = getAppByCtx(ctx)
215
+
216
+ const {
217
+ model: _model,
218
+ show_virtual = false,
219
+ is_cache = true,
220
+ } = ctx.request.body
221
+ if (_model === 'page') return ctx.SUCCESS({})
222
+ if (is_cache && app.cache && app.cache.get(`${_model}-table`)) {
223
+ return ctx.SUCCESS(app.cache.get(`${_model}-table`))
224
+ }
225
+
226
+ const model = app.config[_model].model || _model
227
+
228
+ const _result = await app.sequelize.getQueryInterface().describeTable(model)
229
+
230
+ const modelAttributes = lodash.cloneDeep(app.attributes[model])
231
+ const result = lodash.pick(_result, [
232
+ ...Object.keys(modelAttributes),
233
+ 'id',
234
+ 'createdid',
235
+ 'updated_at',
236
+ 'created_at',
237
+ ])
238
+
239
+ const obj = {}
240
+ if (show_virtual) {
241
+ Array.from(
242
+ new Set([...Object.keys(result), ...Object.keys(modelAttributes)])
243
+ ).forEach((attr) => {
244
+ obj[attr] = lodash.merge(
245
+ modelAttributes[attr],
246
+ lodash.omitBy(result[attr], lodash.isNull),
247
+ app.config[_model].comment && app.config[_model].comment
248
+ ? app.config[_model].comment[attr]
249
+ : {}
250
+ )
251
+ })
252
+ } else {
253
+ Object.keys(result).forEach((attr) => {
254
+ obj[attr] = lodash.merge(
255
+ modelAttributes[attr],
256
+ lodash.omitBy(result[attr], lodash.isNull),
257
+ app.config[_model].comment && app.config[_model].comment
258
+ ? app.config[_model].comment[attr]
259
+ : {}
260
+ )
261
+ })
262
+ }
263
+
264
+ app.cache && app.cache.set(`${_model}-table`, obj)
265
+ return ctx.SUCCESS(obj)
266
+ }
267
+
268
+ exports.dropModel = async (ctx) => {
269
+ const { app } = getAppByCtx(ctx)
270
+
271
+ const { model } = ctx.request.body
272
+ const result = await app.model[model].drop()
273
+ return ctx.SUCCESS(result)
274
+ }
275
+
276
+ exports.getImage = async (ctx) => {
277
+ const { app } = getAppByCtx(ctx)
278
+
279
+ const appConfig = getConfig(app)
280
+
281
+ const { site_host } = await appConfig.getObject('base')
282
+ // const { dir, upload } = app.config.static;
283
+ const targetDir = `${path.resolve(
284
+ __dirname,
285
+ `${process.cwd()}/public/upload`
286
+ )}`
287
+ const files = await fsPromise.readdir(targetDir)
288
+ const images = files.filter((file) =>
289
+ /\w(\.gif|\.jpeg|\.png|\.jpg|\.bmp)/i.test(file)
290
+ )
291
+ const result = []
292
+ for (let i = 0; i < images.length; i++) {
293
+ const image = `${path.resolve(
294
+ __dirname,
295
+ `${process.cwd()}/public/upload/${images[i]}`
296
+ )}`
297
+
298
+ result.push({
299
+ img: `http://${site_host || 'api.kuashou.com'}/upload/${images[i]}`,
300
+ name: images[i],
301
+ ...fs.statSync(image),
302
+ })
303
+ }
304
+
305
+ ctx.SUCCESS(result)
306
+ }
307
+
308
+ exports.mock = async (ctx) => {
309
+ const { app } = getAppByCtx(ctx)
310
+
311
+ const { model } = ctx.request.body
312
+ const result = app.mock && app.mock[model] && app.mock[model]()
313
+ return ctx.SUCCESS(!lodash.isEmpty(result))
314
+ }
315
+
316
+ exports.table = async (ctx) => {
317
+ const { app } = getAppByCtx(ctx)
318
+ const { model } = ctx.request.body
319
+ if (!model) {
320
+ const result = Object.keys(app.model).map((m) => ({
321
+ [m]: lodash.mapValues(app.attributes[m], (item) => item.comment),
322
+ }))
323
+ ctx.SUCCESS(result)
324
+ } else {
325
+ const result = app.attributes[model]
326
+ ctx.SUCCESS(result)
327
+ }
328
+ }
329
+
330
+ exports.showAllSchemas = async (ctx) => {
331
+ const { app } = getAppByCtx(ctx)
332
+
333
+ const { model } = ctx.request.body
334
+ const result = await app.sequelize.getQueryInterface().describeTable(model)
335
+ return ctx.SUCCESS(result)
336
+ }
337
+
338
+ exports.initDataWithCache = exports.initData = async (ctx) => {
339
+ const { app } = getAppByCtx(ctx)
340
+ const appConfig = getConfig(app)
341
+
342
+ const { is_cache } = await appConfig.getObject('base')
343
+ const { includes, excludes = [] } = ctx.request.body
344
+
345
+ const cacheTarget = JSON.stringify(ctx.request.body)
346
+
347
+ let result = null
348
+ if (is_cache && cache.get(cacheTarget)) {
349
+ result = cache.get(cacheTarget)
350
+ } else {
351
+ result = await app.service.system.initData({
352
+ app,
353
+ includes,
354
+ excludes,
355
+ ctx,
356
+ })
357
+ if (is_cache && cacheTarget.includes('setting/findAll')) {
358
+ cache.set(cacheTarget, result)
359
+ }
360
+ }
361
+ return ctx.SUCCESS(result)
362
+ }
363
+
364
+ exports.recentlyError = async (ctx) => {
365
+ // const {
366
+ // app,
367
+ // appName,
368
+ // } = getAppByCtx(ctx);
369
+
370
+ if (ctx.ws) {
371
+ const ws = await ctx.ws()
372
+ const send = (data) => ws.send(JSON.stringify(data))
373
+ ctx.app.on('error', async (err) => {
374
+ console.log(err)
375
+ try {
376
+ send({
377
+ type: 'error',
378
+ payload: err ? err.errorData : {},
379
+ })
380
+ } catch (e) {
381
+ console.log(e)
382
+ }
383
+ })
384
+ } else {
385
+ throw new Error('这是一个websocket url')
386
+ }
387
+ }
388
+
389
+ exports.sandbox = async (ctx) => {
390
+ const code = `
391
+ module.exports = async (callback)=>{
392
+ callback({
393
+ name:1
394
+ })
395
+ }
396
+ `
397
+ // const { code } = ctx.request.body;
398
+ const vm = new VM({
399
+ sandbox: {
400
+ axios,
401
+ cheerio,
402
+ module,
403
+ setTimeout,
404
+ sleep: (time) =>
405
+ new Promise((resolve) => setTimeout(resolve, time * 1000)),
406
+ },
407
+ })
408
+ vm.run(code)(ctx.SUCCESS)
409
+ }
410
+
411
+ exports.onlineChat = async (ctx) => {
412
+ if (ctx.ws) {
413
+ const ws = await ctx.ws()
414
+ const send = (data) => ws.send(JSON.stringify(data))
415
+ ws.on('message', async (code) => {
416
+ const vm = new VM({
417
+ sandbox: {
418
+ send: (data) => ws.send(JSON.stringify(data)),
419
+ axios,
420
+ cheerio,
421
+ },
422
+ })
423
+ vm.run(code)
424
+ const unhandledRejections = {}
425
+
426
+ process.on('unhandledRejection', (reason) => {
427
+ if (unhandledRejections[reason]) {
428
+ console.log('====?', reason)
429
+ } else {
430
+ send({
431
+ type: 'error',
432
+ payload: `${reason}`,
433
+ })
434
+ unhandledRejections[reason] = true
435
+ }
436
+ })
437
+ })
438
+ } else {
439
+ throw new Error('这是一个websocket url')
440
+ }
441
+ }
442
+
443
+ exports.chat = async (ctx) => {
444
+ const { app } = getAppByCtx(ctx)
445
+ if (ctx.ws) {
446
+ const ws = await ctx.ws()
447
+ const send = (data) => ws.send(JSON.stringify(data))
448
+ ws.on('message', async (payload) => {
449
+ const message = JSON.parse(payload)
450
+ const result = await app.model.ask.create({
451
+ user_id: message.payload.user_id,
452
+ content: message.payload.content,
453
+ is_ask: Boolean(message.payload.user_id),
454
+ })
455
+
456
+ send({
457
+ type: 'message',
458
+ payload: result,
459
+ })
460
+ })
461
+ } else {
462
+ throw new Error('这是一个websocket url')
463
+ }
464
+ }
465
+
466
+ exports.task = async (ctx) => {
467
+ const { app } = getAppByCtx(ctx)
468
+
469
+ const { task } = ctx.request.body
470
+
471
+ if (app.task && app.task[task]) {
472
+ await app.task[task](app)
473
+ }
474
+ ctx.SUCCESS(task)
475
+ }
476
+
477
+ exports.dashboard = async (ctx) => {
478
+ // const {
479
+ // app,
480
+ // } = getAppByCtx(ctx);
481
+
482
+ const result = [
483
+ // {
484
+ // name: '用户',
485
+ // number: 5,
486
+ // route: 'page/user'
487
+ // }
488
+ ]
489
+ ctx.SUCCESS(result)
490
+ }
491
+
492
+ exports.showAllModel = async (ctx) => {
493
+ const { app, appName } = getAppByCtx(ctx)
494
+ const { is_count = false } = ctx.request.body
495
+ const result = await app.sequelize.showAllSchemas()
496
+ const list = result.map(
497
+ (item) => item[`Tables_in_${app.sequelize.config.database}`]
498
+ )
499
+ let tableList = []
500
+ for (let i = 0; i < list.length; i++) {
501
+ if (!app.model[list[i]]) continue
502
+ const defaultConfigPath = path.resolve(__dirname, `../${list[i]}/config.js`)
503
+ const defaultConfigExist = fs.existsSync(defaultConfigPath)
504
+ const target =
505
+ app.config[list[i]] &&
506
+ Object.keys(app.config[list[i]]).length === 0 &&
507
+ defaultConfigExist
508
+ ? require(defaultConfigPath)
509
+ : app.config[list[i]]
510
+ let count = 0
511
+ if (is_count) {
512
+ count = await app.model[list[i]].count()
513
+ }
514
+ tableList = [
515
+ ...tableList,
516
+ {
517
+ count,
518
+ name: target.name,
519
+ model: list[i],
520
+ count,
521
+ },
522
+ ]
523
+ }
524
+ return ctx.SUCCESS(
525
+ tableList.map((item) => lodash.omit(item, is_count ? [] : ['count']))
526
+ )
527
+ }
528
+
529
+ exports.drop = async (ctx) => {
530
+ const { app, appName } = getAppByCtx(ctx)
531
+
532
+ const result = await app.sequelize.showAllSchemas()
533
+ const list = result.map(
534
+ (item) => item[`Tables_in_${app.sequelize.config.database}`]
535
+ )
536
+ const filterList = list.filter((item) => !app.model[item])
537
+ await Promise.all(
538
+ filterList.map((folder) => {
539
+ const model = app.sequelize.define(
540
+ folder,
541
+ {
542
+ id: {
543
+ type: Sequelize.INTEGER(11),
544
+ allowNull: false,
545
+ primaryKey: true,
546
+ autoIncrement: true,
547
+ },
548
+ },
549
+ {
550
+ freezeTableName: true,
551
+ comment: folder,
552
+ createdAt: 'created_at',
553
+ updatedAt: 'updated_at',
554
+ name: {
555
+ singular: folder,
556
+ plural: `${folder}s`,
557
+ },
558
+ }
559
+ )
560
+ return model.drop()
561
+ })
562
+ )
563
+
564
+ ctx.SUCCESS(`删除${filterList.length}个无用表`)
565
+ }
566
+
567
+ exports.mockPay = async (ctx) => {
568
+ const { app } = getAppByCtx(ctx)
569
+ const { order_id, order_price, prefix, ...rest } = ctx.request.body
570
+ const model = prefix ? `${prefix}_order` : 'order'
571
+ console.log(model, 'model')
572
+ if (!(app.service[model] && app.service[model].notify)) {
573
+ throw new Error(`没配置${model}支付回调notify`)
574
+ }
575
+ await app.service[model].notify({
576
+ app,
577
+ order_id,
578
+ order: model,
579
+ order_price,
580
+ ...rest,
581
+ })
582
+ ctx.SUCCESS('ok')
583
+ }
584
+
585
+ exports.copyOther = async (ctx) => {
586
+ const { app, appName } = getAppByCtx(ctx)
587
+
588
+ const { model, source } = ctx.request.body
589
+ if (!model) return ctx.ERROR('model?')
590
+ if (!source) return ctx.ERROR('source? https://www.kuashou.com/kuashou')
591
+ let list = []
592
+ if (Array.isArray(model)) {
593
+ list = model
594
+ } else {
595
+ if (model !== 'all') {
596
+ list = [model]
597
+ } else {
598
+ const result = await app.sequelize.showAllSchemas()
599
+ list = result.map(
600
+ (item) => item[`Tables_in_${app.sequelize.config.database}`]
601
+ )
602
+ }
603
+ }
604
+
605
+ const prefix = `${source}`
606
+ let successList = []
607
+ let failList = []
608
+ for (let i = 0; i < list.length; i++) {
609
+ const url = `${prefix}/${list[i]}/findAll`
610
+
611
+ const result = await axios
612
+ .post(
613
+ url,
614
+ {},
615
+ {
616
+ headers: {
617
+ 'Client-Type': 0,
618
+ },
619
+ }
620
+ )
621
+ .then((res) => res.data)
622
+ if (result.code === 200) {
623
+ await app.model[list[i]].sync({
624
+ force: true,
625
+ })
626
+ try {
627
+ await app.model[list[i]].bulkCreate(
628
+ result.data.map((i) => lodash.omitBy(i, lodash.isNull)),
629
+ {}
630
+ )
631
+ successList = [...successList, list[i]]
632
+ } catch (e) {
633
+ console.log(e.message)
634
+ failList = [...failList, list[i]]
635
+ }
636
+ } else {
637
+ console.log(url, result.data)
638
+ failList = [...failList, list[i]]
639
+ continue
640
+ }
641
+ }
642
+ ctx.SUCCESS({ successList, failList })
643
+ }
@@ -1352,7 +1352,13 @@ exports.refund_notify = async (ctx) => {
1352
1352
  app_key: key.length > 10 ? key : partner_key,
1353
1353
  })
1354
1354
 
1355
- const { out_refund_no, out_trade_no, total_fee, refund_fee } = refundResult
1355
+ const {
1356
+ out_refund_no,
1357
+ out_trade_no,
1358
+ total_fee,
1359
+ refund_fee,
1360
+ refund_desc,
1361
+ } = refundResult
1356
1362
 
1357
1363
  const refund_price = Number(refund_fee) / 100
1358
1364
 
@@ -1367,9 +1373,7 @@ exports.refund_notify = async (ctx) => {
1367
1373
  : Number(out_refund_no)
1368
1374
  const type = out_refund_no.includes('_') ? out_refund_no.split('_')[1] : ''
1369
1375
  const model = prefix ? `${prefix}_order` : 'order'
1370
- // console.log(
1371
- // `微信退款回调 prefix:${prefix}/model:${model}/order_id:${order_id}/type:${type}`
1372
- // )
1376
+ console.log(`微信退款回调 ${refund_desc}`)
1373
1377
  if (app.service[model] && app.service[model].refund_notify) {
1374
1378
  await app.service[model].refund_notify({
1375
1379
  app,
@@ -1377,6 +1381,7 @@ exports.refund_notify = async (ctx) => {
1377
1381
  order: model,
1378
1382
  refund_price,
1379
1383
  type,
1384
+ out_refund_no,
1380
1385
  })
1381
1386
  }
1382
1387
 
@@ -1706,7 +1711,7 @@ exports.setMsgJumpPath = async (ctx) => {
1706
1711
  const { app, appName } = getAppByCtx(ctx)
1707
1712
  const appConfig = getConfig(app)
1708
1713
 
1709
- const {config = 'weixin_mp' ,path='pages/index/index'} = ctx.request.body
1714
+ const { config = 'weixin_mp', path = 'pages/index/index' } = ctx.request.body
1710
1715
  const { app_id, app_secrect } = await appConfig.getObject(config)
1711
1716
 
1712
1717
  const weixinMp = new WeixinMp({
@@ -1721,4 +1726,4 @@ exports.setMsgJumpPath = async (ctx) => {
1721
1726
  })
1722
1727
 
1723
1728
  ctx.SUCCESS('ok')
1724
- }
1729
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "q-koa",
3
- "version": "11.3.4",
3
+ "version": "11.4.1",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {