q-koa 13.8.57 → 13.8.59

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
@@ -458,154 +458,88 @@ class APP {
458
458
  })
459
459
  dirList = dirList.filter((item) => includeList.some((i) => i.app === item))
460
460
 
461
- let dbFlag = false
462
- for (const appName of dirList) {
463
- console.log(
464
- chalk.green(`==================== 启动 ${appName} ====================`)
465
- )
466
- const routerConfig = {
467
- prefix: `/${appName}`,
468
- }
469
- const router = new Router(routerConfig)
470
- Serve(
471
- path.resolve(
472
- __dirname,
473
- `${rootDirectory}/${APP_DIR}/${appName}/public`
474
- ),
475
- router
476
- )
477
-
478
- const { push } = require('./file/plugins/log/service')
461
+ const { push } = require('./file/plugins/log/service')
462
+
463
+ // 所有 app 共用同一份 redis 配置,全局只连接一对 client+subscriber 供各 app 复用,
464
+ // 避免每个 app 各自建一套连接(2N 条连接 -> 2 条)
465
+ let sharedRedisClient = null
466
+ let sharedSubscriber = null
467
+ let redisError = null
468
+ if (
469
+ this.config.redis &&
470
+ this.config.redis.host &&
471
+ this.config.redis.port
472
+ ) {
479
473
  try {
480
- const target = includeList.find((i) => i.app === appName)
481
- const dbName =
482
- (target ? target.db : '') || this.config.db.name || appName
483
- const db = connectDatabase(dbName)(this.config.db)
484
- if (!is_dev) {
485
- if (!dbFlag) {
486
- console.log(chalk.green(`${appName}检查数据库${dbName}`))
487
- await db.authenticate()
488
- dbFlag = true
489
- }
490
- } else {
491
- console.log(chalk.green(`${appName}检查数据库${dbName}`))
492
- await db.authenticate()
493
- }
494
- const configPath = `${rootDirectory}/${APP_DIR}/${appName}/config.js`
495
- const configFile = path.resolve(__dirname, configPath)
496
- const configExist = fs.existsSync(configFile)
497
-
498
- if (
499
- this.config.redis &&
500
- this.config.redis.host &&
501
- this.config.redis.port
502
- ) {
503
- let subscriber
504
- let client
505
- try {
506
- client = redis.createClient({
507
- socket: {
508
- host: this.config.redis.host,
509
- port: this.config.redis.port,
510
- reconnectStrategy: false,
511
- },
512
- ...(this.config.redis.database
513
- ? {
514
- database: this.config.redis.database,
515
- }
516
- : {}),
517
- })
518
-
519
- client.on('error', (err) => console.log('Redis Client Error', err))
520
-
521
- await client.connect()
522
-
523
- subscriber = client.duplicate()
524
- await subscriber.connect()
525
-
526
- if (!is_dev) {
527
- push({
528
- appName,
529
- message: `${appName}连接redis成功,${this.config.ip}:${this.port}`,
530
- })
531
- }
532
- } catch (e) {
533
- client = null
534
-
535
- push({
536
- appName,
537
- message: `${appName}连接redis失败,${e.message}`,
538
- })
539
- }
540
-
541
- this.app[appName] = {
542
- sequelize: db,
543
- ...(client
544
- ? {
545
- redisClient: client,
546
- subscriber,
547
- }
548
- : {}),
549
- appConfig: _.defaultsDeep(
550
- configExist ? require(path.resolve(__dirname, configPath)) : {},
551
- this.config.app,
552
- { appName, ip: this.config.ip, port: this.port }
553
- ),
554
- }
555
- } else {
556
- this.app[appName] = {
557
- sequelize: db,
558
- redisClient: redisVirtual,
559
- appConfig: _.defaultsDeep(
560
- configExist ? require(path.resolve(__dirname, configPath)) : {},
561
- this.config.app,
562
- { appName, ip: this.config.ip, port: this.port }
563
- ),
564
- }
565
- }
474
+ sharedRedisClient = redis.createClient({
475
+ socket: {
476
+ host: this.config.redis.host,
477
+ port: this.config.redis.port,
478
+ reconnectStrategy: false,
479
+ },
480
+ ...(this.config.redis.database
481
+ ? {
482
+ database: this.config.redis.database,
483
+ }
484
+ : {}),
485
+ })
566
486
 
567
- // const proxyConfig = _.get(this.app[appName], 'appConfig.proxy');
487
+ sharedRedisClient.on('error', (err) =>
488
+ console.log('Redis Client Error', err)
489
+ )
568
490
 
569
- // if (proxyConfig) {
570
- // this.app.use(proxy(`/${appName}`, proxyConfig));
571
- // }
491
+ await sharedRedisClient.connect()
572
492
 
573
- const initFileConfig = _.get(
574
- this.app[appName],
575
- 'appConfig.initFile',
576
- false
577
- )
578
- if (initFileConfig) {
579
- this.initFile(appName)
580
- }
581
- await this.initPlugin(appName)
493
+ sharedSubscriber = sharedRedisClient.duplicate()
494
+ await sharedSubscriber.connect()
495
+ } catch (e) {
496
+ sharedRedisClient = null
497
+ sharedSubscriber = null
498
+ redisError = e
499
+ }
500
+ }
582
501
 
583
- const initEvent = _.get(this.app[appName], 'appConfig.initEvent', false)
584
- if (initEvent) {
585
- this.app[appName].event = new EventEmitter()
502
+ // 数据库连接校验:生产只校验一次(保持原有行为);开发环境按 dbName 去重,同库只校验一次
503
+ let prodAuthPromise = null
504
+ const dbAuthCache = new Map()
505
+ const authenticateDb = (db, dbName, appName) => {
506
+ if (!is_dev) {
507
+ if (!prodAuthPromise) {
508
+ console.log(chalk.green(`${appName}检查数据库${dbName}`))
509
+ prodAuthPromise = db.authenticate()
586
510
  }
511
+ return prodAuthPromise
512
+ }
513
+ if (!dbAuthCache.has(dbName)) {
514
+ console.log(chalk.green(`${appName}检查数据库${dbName}`))
515
+ dbAuthCache.set(dbName, db.authenticate())
516
+ }
517
+ return dbAuthCache.get(dbName)
518
+ }
587
519
 
588
- const cacheConfig = _.get(this.app[appName], 'appConfig.cache', {
589
- max: 100,
590
- maxAge: 1000 * 60 * 60 * 24,
591
- })
592
-
593
- this.app[appName].cache = new LRUCache({
594
- max: cacheConfig.max,
595
- ttl: cacheConfig.maxAge,
520
+ const routers = new Map()
521
+
522
+ // 各 app 的重初始化(数据库校验/插件/模型等)互相独立,并行执行
523
+ const results = await Promise.allSettled(
524
+ dirList.map((appName) =>
525
+ this._initSingleApp(appName, includeList, {
526
+ push,
527
+ routers,
528
+ sharedRedisClient,
529
+ sharedSubscriber,
530
+ redisError,
531
+ authenticateDb,
596
532
  })
533
+ )
534
+ )
535
+ const failed = results.find((r) => r.status === 'rejected')
536
+ if (failed) {
537
+ throw failed.reason
538
+ }
597
539
 
598
- await this.initModel(appName)
599
- // const models = Object.keys(this.app[appName].model).join(' / ')
600
- } catch (e) {
601
- push({
602
- appName,
603
- message: `${appName}数据库连接失败`,
604
- })
605
- console.log(`${appName}数据库连接失败`)
606
- console.log(e)
607
- throw new Error(e.message)
608
- }
540
+ // 按原有顺序挂载路由,保证中间件注册顺序确定
541
+ for (const appName of dirList) {
542
+ const router = routers.get(appName)
609
543
  if (
610
544
  this.app[appName] &&
611
545
  this.app[appName].appConfig &&
@@ -654,10 +588,121 @@ class APP {
654
588
  starter.emit('loadAll')
655
589
  }
656
590
 
657
- initFile(appName) {
591
+ async _initSingleApp(appName, includeList, ctx) {
592
+ const {
593
+ push,
594
+ routers,
595
+ sharedRedisClient,
596
+ sharedSubscriber,
597
+ redisError,
598
+ authenticateDb,
599
+ } = ctx
600
+ console.log(
601
+ chalk.green(`==================== 启动 ${appName} ====================`)
602
+ )
603
+ const routerConfig = {
604
+ prefix: `/${appName}`,
605
+ }
606
+ const router = new Router(routerConfig)
607
+ Serve(
608
+ path.resolve(__dirname, `${rootDirectory}/${APP_DIR}/${appName}/public`),
609
+ router
610
+ )
611
+ routers.set(appName, router)
612
+
613
+ try {
614
+ const target = includeList.find((i) => i.app === appName)
615
+ const dbName = (target ? target.db : '') || this.config.db.name || appName
616
+ const db = connectDatabase(dbName)(this.config.db)
617
+ await authenticateDb(db, dbName, appName)
618
+
619
+ const configPath = `${rootDirectory}/${APP_DIR}/${appName}/config.js`
620
+ const configFile = path.resolve(__dirname, configPath)
621
+ const configExist = fs.existsSync(configFile)
622
+ const appConfig = _.defaultsDeep(
623
+ configExist ? require(configFile) : {},
624
+ this.config.app,
625
+ { appName, ip: this.config.ip, port: this.port }
626
+ )
627
+
628
+ if (sharedRedisClient) {
629
+ if (!is_dev) {
630
+ push({
631
+ appName,
632
+ message: `${appName}连接redis成功,${this.config.ip}:${this.port}`,
633
+ })
634
+ }
635
+ this.app[appName] = {
636
+ sequelize: db,
637
+ redisClient: sharedRedisClient,
638
+ subscriber: sharedSubscriber,
639
+ appConfig,
640
+ }
641
+ } else if (
642
+ this.config.redis &&
643
+ this.config.redis.host &&
644
+ this.config.redis.port
645
+ ) {
646
+ // redis 配置了但连接失败:保持原有行为(不挂 redisClient),仅记录日志
647
+ push({
648
+ appName,
649
+ message: `${appName}连接redis失败,${
650
+ redisError ? redisError.message : '未知错误'
651
+ }`,
652
+ })
653
+ this.app[appName] = {
654
+ sequelize: db,
655
+ appConfig,
656
+ }
657
+ } else {
658
+ this.app[appName] = {
659
+ sequelize: db,
660
+ redisClient: redisVirtual,
661
+ appConfig,
662
+ }
663
+ }
664
+
665
+ const initFileConfig = _.get(
666
+ this.app[appName],
667
+ 'appConfig.initFile',
668
+ false
669
+ )
670
+ if (initFileConfig) {
671
+ await this.initFile(appName)
672
+ }
673
+ await this.initPlugin(appName)
674
+
675
+ const initEvent = _.get(this.app[appName], 'appConfig.initEvent', false)
676
+ if (initEvent) {
677
+ this.app[appName].event = new EventEmitter()
678
+ }
679
+
680
+ const cacheConfig = _.get(this.app[appName], 'appConfig.cache', {
681
+ max: 100,
682
+ maxAge: 1000 * 60 * 60 * 24,
683
+ })
684
+
685
+ this.app[appName].cache = new LRUCache({
686
+ max: cacheConfig.max,
687
+ ttl: cacheConfig.maxAge,
688
+ })
689
+
690
+ await this.initModel(appName)
691
+ } catch (e) {
692
+ push({
693
+ appName,
694
+ message: `${appName}数据库连接失败`,
695
+ })
696
+ console.log(`${appName}数据库连接失败`)
697
+ console.log(e)
698
+ throw new Error(e.message)
699
+ }
700
+ }
701
+
702
+ async initFile(appName) {
658
703
  const dirExist = fsExtra.existsSync(path.resolve(__dirname, './file'))
659
704
  if (dirExist) {
660
- fsExtra.copySync(
705
+ await fsExtra.copy(
661
706
  path.resolve(__dirname, './file'),
662
707
  path.resolve(__dirname, `${rootDirectory}/${APP_DIR}/${appName}`),
663
708
  {
@@ -672,285 +717,265 @@ class APP {
672
717
  __dirname,
673
718
  `${rootDirectory}/${APP_DIR}/${appName}/${PLUGINS_DIR}`
674
719
  )
675
- const publicExist = fs.existsSync(pluginDir)
676
- if (!publicExist) await fsPromise.mkdir(pluginDir)
720
+ await fsPromise.mkdir(pluginDir, { recursive: true })
677
721
  const excludesModel = _.get(
678
722
  this.app[appName],
679
723
  'appConfig.excludesModel',
680
724
  []
681
725
  )
682
- const pluginDirList = await fsPromise.readdir(pluginDir)
683
- const pluginDirResult = pluginDirList.filter(
684
- (item) => !excludesModel.includes(item)
726
+ // withFileTypes 直接带出类型,省掉每个文件夹一次同步 lstatSync
727
+ const pluginEntries = await fsPromise.readdir(pluginDir, {
728
+ withFileTypes: true,
729
+ })
730
+ const folders = pluginEntries
731
+ .filter(
732
+ (item) => item.isDirectory() && !excludesModel.includes(item.name)
733
+ )
734
+ .map((item) => item.name)
735
+
736
+ // 各插件目录的 readdir 互相独立,并行读取
737
+ const folderFileList = await Promise.all(
738
+ folders.map(async (folder) => [
739
+ folder,
740
+ await fsPromise.readdir(path.resolve(pluginDir, folder)),
741
+ ])
685
742
  )
686
743
 
687
- for (const folder of pluginDirResult) {
688
- const isFolder = fs
689
- .lstatSync(path.resolve(pluginDir, folder))
690
- .isDirectory()
691
- if (isFolder) {
692
- const folderResult = await fsPromise.readdir(
693
- path.resolve(pluginDir, folder)
694
- )
695
- for (const filename of folderResult) {
696
- const extname = path.extname(filename)
697
- if (extname === '.js') {
698
- const n = path.basename(filename, extname)
699
- if (
700
- [
701
- 'model',
702
- 'controller',
703
- 'service',
704
- 'validate',
705
- 'config',
706
- 'beforeValidate',
707
- 'afterValidate',
708
- 'afterExecute',
709
- ].includes(n)
710
- ) {
711
- if (n === 'model') {
712
- const defaultModelPath = path.resolve(
713
- __dirname,
714
- `./file/${PLUGINS_DIR}/${folder}`,
715
- `${filename}`
716
- )
717
- const defaultModelExist = fs.existsSync(defaultModelPath)
718
- const currentAttributes = require(path.resolve(
719
- pluginDir,
720
- folder,
721
- filename
722
- ))
723
- const _attributes =
724
- Object.keys(currentAttributes).length === 0 &&
725
- defaultModelExist
726
- ? require(defaultModelPath)
727
- : currentAttributes
728
-
729
- const belongs = _.get(
730
- this.app[appName].config[folder],
731
- 'belongs',
732
- 'page'
733
- )
744
+ // O(1) 直接挂载,替代对整个 app 对象反复 defaultsDeep(O(n²))
745
+ const mount = (n, folder, value) => {
746
+ const target = this.app[appName]
747
+ target[n] = target[n] || {}
748
+ target[n][folder] = value
749
+ }
734
750
 
735
- const idType = _.get(
736
- this.app[appName].config[folder],
737
- 'idType',
738
- belongs === 'setting' ? 'TINYINT' : 'MEDIUMINT'
739
- )
751
+ for (const [folder, folderResult] of folderFileList) {
752
+ for (const filename of folderResult) {
753
+ const extname = path.extname(filename)
754
+ if (extname === '.js') {
755
+ const n = path.basename(filename, extname)
756
+ if (
757
+ [
758
+ 'model',
759
+ 'controller',
760
+ 'service',
761
+ 'validate',
762
+ 'config',
763
+ 'beforeValidate',
764
+ 'afterValidate',
765
+ 'afterExecute',
766
+ ].includes(n)
767
+ ) {
768
+ if (n === 'model') {
769
+ const defaultModelPath = path.resolve(
770
+ __dirname,
771
+ `./file/${PLUGINS_DIR}/${folder}`,
772
+ `${filename}`
773
+ )
774
+ const defaultModelExist = fs.existsSync(defaultModelPath)
775
+ const currentAttributes = require(path.resolve(
776
+ pluginDir,
777
+ folder,
778
+ filename
779
+ ))
780
+ const _attributes =
781
+ Object.keys(currentAttributes).length === 0 &&
782
+ defaultModelExist
783
+ ? require(defaultModelPath)
784
+ : currentAttributes
785
+
786
+ const belongs = _.get(
787
+ this.app[appName].config[folder],
788
+ 'belongs',
789
+ 'page'
790
+ )
740
791
 
741
- const _id = {
742
- id: {
743
- type: Sequelize[idType].UNSIGNED,
744
- allowNull: false,
745
- primaryKey: true,
746
- autoIncrement: true,
747
- },
748
- }
792
+ const idType = _.get(
793
+ this.app[appName].config[folder],
794
+ 'idType',
795
+ belongs === 'setting' ? 'TINYINT' : 'MEDIUMINT'
796
+ )
749
797
 
750
- const attributes = _.defaultsDeep(_.cloneDeep(_attributes), {
751
- ..._id,
752
- createdid: {
753
- type: Sequelize.MEDIUMINT.UNSIGNED,
754
- allowNull: false,
755
- defaultValue: 0,
756
- },
757
- created_at: {
758
- type: Sequelize.DATE,
759
- allowNull: false,
760
- // defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
761
- comment: '生成时间',
762
- },
763
- updated_at: {
764
- type: Sequelize.DATE,
765
- allowNull: false,
766
- // defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
767
- comment: '修改时间',
768
- },
769
- })
798
+ const _id = {
799
+ id: {
800
+ type: Sequelize[idType].UNSIGNED,
801
+ allowNull: false,
802
+ primaryKey: true,
803
+ autoIncrement: true,
804
+ },
805
+ }
770
806
 
771
- this.app[appName].attributes = {
772
- ...this.app[appName].attributes,
773
- [folder]: _attributes,
774
- }
775
- const indexes = _.cloneDeep(
776
- _.get(this.app[appName].config[folder], 'indexList', [])
777
- )
778
- let hookList = _.cloneDeep(
779
- _.get(this.app[appName].config[folder], 'hookList', [])
780
- )
781
- if (
782
- hookList.some((h) =>
783
- ['afterDestroy', 'beforeDestroy'].includes(h)
784
- ) &&
785
- !hookList.includes('beforeBulkDestroy')
786
- ) {
787
- hookList = [...hookList, 'beforeBulkDestroy']
807
+ const attributes = _.defaultsDeep(_.cloneDeep(_attributes), {
808
+ ..._id,
809
+ createdid: {
810
+ type: Sequelize.MEDIUMINT.UNSIGNED,
811
+ allowNull: false,
812
+ defaultValue: 0,
813
+ },
814
+ created_at: {
815
+ type: Sequelize.DATE,
816
+ allowNull: false,
817
+ // defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
818
+ comment: '生成时间',
819
+ },
820
+ updated_at: {
821
+ type: Sequelize.DATE,
822
+ allowNull: false,
823
+ // defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'),
824
+ comment: '修改时间',
825
+ },
826
+ })
827
+
828
+ this.app[appName].attributes = {
829
+ ...this.app[appName].attributes,
830
+ [folder]: _attributes,
831
+ }
832
+ const indexes = _.cloneDeep(
833
+ _.get(this.app[appName].config[folder], 'indexList', [])
834
+ )
835
+ let hookList = _.cloneDeep(
836
+ _.get(this.app[appName].config[folder], 'hookList', [])
837
+ )
838
+ if (
839
+ hookList.some((h) =>
840
+ ['afterDestroy', 'beforeDestroy'].includes(h)
841
+ ) &&
842
+ !hookList.includes('beforeBulkDestroy')
843
+ ) {
844
+ hookList = [...hookList, 'beforeBulkDestroy']
845
+ }
846
+ let reduceHook = {}
847
+ for (const hook of hookList) {
848
+ reduceHook = {
849
+ ...reduceHook,
850
+ [hook]: (target, t) => {
851
+ if (['beforeBulkDestroy'].includes(hook)) {
852
+ target.individualHooks = true
853
+ }
854
+ try {
855
+ this.app[appName].service &&
856
+ this.app[appName].service[folder] &&
857
+ this.app[appName].service[folder][hook] &&
858
+ this.app[appName].service[folder][hook]({
859
+ app: this.app[appName],
860
+ model: folder,
861
+ hook,
862
+ ...(target.toJSON
863
+ ? {
864
+ target: target.toJSON(),
865
+ }
866
+ : {
867
+ target,
868
+ }),
869
+ ...t,
870
+ })
871
+ } catch (e) {
872
+ console.log(e)
873
+ }
874
+
875
+ if (['beforeBulkDestroy'].includes(hook)) {
876
+ return target
877
+ }
878
+ },
788
879
  }
789
- let reduceHook = {}
790
- for (const hook of hookList) {
791
- reduceHook = {
880
+ }
881
+ const instance = this.app[appName].sequelize.define(
882
+ folder,
883
+ attributes,
884
+ {
885
+ freezeTableName: true,
886
+ comment: folder,
887
+ // timestamps: !modelConfig.belongs === 'setting',
888
+ createdAt: 'created_at',
889
+ updatedAt: 'updated_at',
890
+ name: {
891
+ singular: folder,
892
+ plural: `${folder}s`,
893
+ },
894
+ hooks: {
792
895
  ...reduceHook,
793
- [hook]: (target, t) => {
794
- if (['beforeBulkDestroy'].includes(hook)) {
795
- target.individualHooks = true
796
- }
896
+ beforeCreate: (res, t) => {
797
897
  try {
798
- this.app[appName].service &&
799
- this.app[appName].service[folder] &&
800
- this.app[appName].service[folder][hook] &&
801
- this.app[appName].service[folder][hook]({
802
- app: this.app[appName],
803
- model: folder,
804
- hook,
805
- ...(target.toJSON
806
- ? {
807
- target: target.toJSON(),
808
- }
809
- : {
810
- target,
811
- }),
812
- ...t,
813
- })
814
- } catch (e) {
815
- console.log(e)
816
- }
817
-
818
- if (['beforeBulkDestroy'].includes(hook)) {
819
- return target
820
- }
821
- },
822
- }
823
- }
824
- const instance = this.app[appName].sequelize.define(
825
- folder,
826
- attributes,
827
- {
828
- freezeTableName: true,
829
- comment: folder,
830
- // timestamps: !modelConfig.belongs === 'setting',
831
- createdAt: 'created_at',
832
- updatedAt: 'updated_at',
833
- name: {
834
- singular: folder,
835
- plural: `${folder}s`,
836
- },
837
- hooks: {
838
- ...reduceHook,
839
- beforeCreate: (res, t) => {
840
- try {
841
- if (folder === 'log') {
842
- if (this.is_dev && !this.app[appName].service.log) {
843
- console.error('缺少log/service.js,')
844
- }
845
- this.app[appName].service.log &&
846
- this.app[appName].service.log.push({
847
- app: this.app[appName],
848
- appName,
849
- message: res.toJSON().message,
850
- url: res.toJSON().url,
851
- })
898
+ if (folder === 'log') {
899
+ if (this.is_dev && !this.app[appName].service.log) {
900
+ console.error('缺少log/service.js,')
852
901
  }
853
- if (res.toJSON().user_id) {
854
- res.createdid = res.toJSON().user_id
855
- }
856
- } catch (e) {
857
- console.log(e)
902
+ this.app[appName].service.log &&
903
+ this.app[appName].service.log.push({
904
+ app: this.app[appName],
905
+ appName,
906
+ message: res.toJSON().message,
907
+ url: res.toJSON().url,
908
+ })
858
909
  }
859
- },
860
- },
861
- ...(indexes.length > 0
862
- ? {
863
- indexes,
910
+ if (res.toJSON().user_id) {
911
+ res.createdid = res.toJSON().user_id
864
912
  }
865
- : {}),
866
- }
867
- )
868
- instance.getName = function () {
869
- return folder
870
- }
871
- this.app[appName] = _.defaultsDeep(
872
- {
873
- [n]: {
874
- [folder]: instance,
913
+ } catch (e) {
914
+ console.log(e)
915
+ }
875
916
  },
876
917
  },
877
- this.app[appName]
878
- )
879
- if (is_dev) {
880
- const mockObj = _.pickBy(
881
- _attributes,
882
- (item) => item.mock && typeof item.mock === 'function'
883
- )
884
- this.app[appName] = _.defaultsDeep(
885
- {
886
- mock: {
887
- [folder]: () =>
888
- _.zipObject(
889
- Object.keys(mockObj),
890
- Object.keys(mockObj).map((key) =>
891
- _.result(mockObj, `${key}.mock`)
892
- )
893
- ),
894
- },
895
- },
896
- this.app[appName]
897
- )
918
+ ...(indexes.length > 0
919
+ ? {
920
+ indexes,
921
+ }
922
+ : {}),
898
923
  }
899
- } else if (
900
- [
901
- 'config',
902
- 'controller',
903
- 'validate',
904
- 'afterExecute',
905
- 'service',
906
- ].includes(n)
907
- ) {
908
- const defaultPath = path.resolve(
909
- __dirname,
910
- `./file/${PLUGINS_DIR}/${folder}`,
911
- `${filename}`
912
- )
913
- const defaultExist = fs.existsSync(defaultPath)
914
- const defaultN = !defaultExist
915
- ? Object.create(null)
916
- : {
917
- [n]: {
918
- [folder]: defaultExist
919
- ? require(path.resolve(
920
- __dirname,
921
- `./file/${PLUGINS_DIR}/${folder}`,
922
- `${filename}`
923
- ))
924
- : null,
925
- },
926
- }
927
- this.app[appName] = _.defaultsDeep(
928
- {
929
- [n]: {
930
- [folder]: require(path.resolve(
931
- pluginDir,
932
- `${folder}`,
933
- `${filename}`
934
- )),
935
- },
936
- },
937
- defaultN,
938
- this.app[appName]
924
+ )
925
+ instance.getName = function () {
926
+ return folder
927
+ }
928
+ mount(n, folder, instance)
929
+ if (is_dev) {
930
+ const mockObj = _.pickBy(
931
+ _attributes,
932
+ (item) => item.mock && typeof item.mock === 'function'
939
933
  )
940
- } else {
941
- this.app[appName] = _.defaultsDeep(
942
- {
943
- [n]: {
944
- [folder]: require(path.resolve(
945
- pluginDir,
946
- `${folder}`,
947
- `${filename}`
948
- )),
949
- },
950
- },
951
- this.app[appName]
934
+ mount(
935
+ 'mock',
936
+ folder,
937
+ () =>
938
+ _.zipObject(
939
+ Object.keys(mockObj),
940
+ Object.keys(mockObj).map((key) =>
941
+ _.result(mockObj, `${key}.mock`)
942
+ )
943
+ )
952
944
  )
953
945
  }
946
+ } else if (
947
+ [
948
+ 'config',
949
+ 'controller',
950
+ 'validate',
951
+ 'afterExecute',
952
+ 'service',
953
+ ].includes(n)
954
+ ) {
955
+ const defaultPath = path.resolve(
956
+ __dirname,
957
+ `./file/${PLUGINS_DIR}/${folder}`,
958
+ `${filename}`
959
+ )
960
+ const userModule = require(path.resolve(
961
+ pluginDir,
962
+ `${folder}`,
963
+ `${filename}`
964
+ ))
965
+ // 用户模块缺失的方法/配置由框架内置同名模块补齐(仅合并当前模块,不波及整个 app)
966
+ if (
967
+ fs.existsSync(defaultPath) &&
968
+ _.isPlainObject(userModule)
969
+ ) {
970
+ _.defaultsDeep(userModule, require(defaultPath))
971
+ }
972
+ mount(n, folder, userModule)
973
+ } else {
974
+ mount(
975
+ n,
976
+ folder,
977
+ require(path.resolve(pluginDir, `${folder}`, `${filename}`))
978
+ )
954
979
  }
955
980
  }
956
981
  }
@@ -984,12 +1009,8 @@ class APP {
984
1009
  a[b] = []
985
1010
  return a
986
1011
  }, {})
987
- this.app[appName] = _.defaultsDeep(
988
- {
989
- include,
990
- },
991
- this.app[appName]
992
- )
1012
+ // 直接赋值,避免对整个 app 对象做 defaultsDeep 深合并(且保持对象引用不变)
1013
+ this.app[appName].include = include
993
1014
  for (const model of modelList) {
994
1015
 
995
1016
  // if (!this.app[appName].include || !this.app[appName].include[model]) {
@@ -1673,7 +1673,12 @@ exports.union_notify = async (ctx) => {
1673
1673
  }
1674
1674
 
1675
1675
  // 通联回调报文字段为全小写 signtype;兼容大小写两种 + 配置 union_signType;末位回退 RSA
1676
- const rawSignType = (notifyData.signtype || notifyData.signType || union_signType || 'RSA').toUpperCase()
1676
+ const rawSignType = (
1677
+ notifyData.signtype ||
1678
+ notifyData.signType ||
1679
+ union_signType ||
1680
+ 'RSA'
1681
+ ).toUpperCase()
1677
1682
  const hasSign = notifyData.sign !== undefined && notifyData.sign !== ''
1678
1683
  const trxstatus = notifyData.trxstatus
1679
1684
 
@@ -1698,22 +1703,17 @@ exports.union_notify = async (ctx) => {
1698
1703
  if (!signOk) {
1699
1704
  app.service.log.push({
1700
1705
  app,
1701
- message: `通联回调验签失败(type=${rawSignType}): ${JSON.stringify(notifyData)}`,
1706
+ message: `通联回调验签失败(type=${rawSignType}): ${JSON.stringify(
1707
+ notifyData
1708
+ )}`,
1702
1709
  })
1703
1710
  ctx.body = 'FAIL'
1704
1711
  return
1705
1712
  }
1706
1713
  }
1707
1714
 
1708
- const {
1709
- trxcode,
1710
- cusorderid,
1711
- acct,
1712
- trxid,
1713
- trxamt,
1714
- srctrxid,
1715
- trxreserved,
1716
- } = notifyData
1715
+ const { trxcode, cusorderid, acct, trxid, trxamt, srctrxid, trxreserved } =
1716
+ notifyData
1717
1717
 
1718
1718
  if (trxstatus === '0000') {
1719
1719
  if (['VSP501', 'VSP681'].includes(trxcode)) {
@@ -2439,6 +2439,7 @@ exports.union_checkPay = async (ctx) => {
2439
2439
  const payResult = await allinpay.checkOrder(out_trade_no)
2440
2440
 
2441
2441
  if (payResult) {
2442
+ console.log(payResult)
2442
2443
  if (payResult.trxstatus === '0000' && payResult.trxcode === 'VSP501') {
2443
2444
  const order_price = Number(payResult.trxamt) / 100
2444
2445
 
@@ -2477,12 +2478,8 @@ exports.union_check_balance = async (ctx) => {
2477
2478
  } = ctx.request.body
2478
2479
 
2479
2480
  const appConfig = getConfig(app)
2480
- const {
2481
- union_mchId,
2482
- union_appid,
2483
- union_privateKey,
2484
- union_publicKey,
2485
- } = await appConfig.getObject(pay_config)
2481
+ const { union_mchId, union_appid, union_privateKey, union_publicKey } =
2482
+ await appConfig.getObject(pay_config)
2486
2483
 
2487
2484
  const allinpay = new AllinPay({
2488
2485
  cusid: mch_id || union_mchId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "q-koa",
3
- "version": "13.8.57",
3
+ "version": "13.8.59",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {