lrf 7 місяців тому
батько
коміт
8962dcd03a
64 змінених файлів з 4105 додано та 296 видалено
  1. 2891 0
      importData/exportConfig.json
  2. 235 6
      package-lock.json
  3. 3 0
      package.json
  4. 5 0
      src/config/config.local.ts
  5. 5 0
      src/config/config.prod.ts
  6. 6 1
      src/config/config.self.ts
  7. 2 0
      src/configuration.ts
  8. 37 0
      src/consumer/mission.consumer.ts
  9. 44 0
      src/controller/asyncExport.controller.ts
  10. 52 0
      src/controller/exportConfig.controller.ts
  11. 11 61
      src/controller/home.controller.ts
  12. 21 0
      src/entity/exportConfig.entity.ts
  13. 20 0
      src/entity/exportMission.entity.ts
  14. 2 2
      src/entity/platform/achievement.entity.ts
  15. 1 1
      src/entity/platform/collection.entity.ts
  16. 1 1
      src/entity/platform/demand.entity.ts
  17. 1 1
      src/entity/platform/design.entity.ts
  18. 1 1
      src/entity/platform/directory.entity.ts
  19. 1 1
      src/entity/platform/footplate.entity.ts
  20. 1 1
      src/entity/platform/friend.entity.ts
  21. 1 1
      src/entity/platform/journal.entity.ts
  22. 1 1
      src/entity/platform/match.entity.ts
  23. 1 1
      src/entity/platform/matchPath.entity.ts
  24. 1 1
      src/entity/platform/news.entity.ts
  25. 1 1
      src/entity/platform/notes.entity.ts
  26. 1 1
      src/entity/platform/project.entity.ts
  27. 1 2
      src/entity/platform/score.entity.ts
  28. 1 1
      src/entity/platform/sector.entity.ts
  29. 1 1
      src/entity/platform/sign.entity.ts
  30. 1 1
      src/entity/platform/supply.entity.ts
  31. 1 1
      src/entity/platform/support.entity.ts
  32. 1 1
      src/entity/system/admin.entity.ts
  33. 1 1
      src/entity/system/dept.entity.ts
  34. 1 1
      src/entity/system/dictData.entity.ts
  35. 1 1
      src/entity/system/dictType.entity.ts
  36. 1 1
      src/entity/system/menus.entity.ts
  37. 1 1
      src/entity/system/message.entity.ts
  38. 1 1
      src/entity/system/region.entity.ts
  39. 1 1
      src/entity/system/role.entity.ts
  40. 1 1
      src/entity/system/tags.entity.ts
  41. 1 1
      src/entity/system/user.entity.ts
  42. 1 1
      src/entity/system/userMenus.entity.ts
  43. 1 1
      src/entity/users/applyCompany.entity.ts
  44. 1 1
      src/entity/users/association.entity.ts
  45. 1 1
      src/entity/users/cirelation.entity.ts
  46. 1 1
      src/entity/users/company.entity.ts
  47. 1 1
      src/entity/users/companyYear.entity.ts
  48. 1 1
      src/entity/users/competition.entity.ts
  49. 1 1
      src/entity/users/contactApply.entity.ts
  50. 1 1
      src/entity/users/expert.entity.ts
  51. 1 1
      src/entity/users/incubator.entity.ts
  52. 1 1
      src/entity/users/incubatorYear.entity.ts
  53. 1 1
      src/entity/users/investment.entity.ts
  54. 1 1
      src/entity/users/school.entity.ts
  55. 1 1
      src/entity/users/state.entity.ts
  56. 1 1
      src/entity/users/unit.entity.ts
  57. 6 2
      src/error/service.error.ts
  58. 5 180
      src/frame/BaseServiceV2.ts
  59. 180 0
      src/frame/conditionBuilder.ts
  60. 368 0
      src/service/asyncExport.service.ts
  61. 131 0
      src/service/exportConfig.service.ts
  62. 29 0
      src/service/exportMission.service.ts
  63. 8 0
      src/service/initData/initSystemData.service.ts
  64. 2 1
      src/service/platform/match.service.ts

Різницю між файлами не показано, бо вона завелика
+ 2891 - 0
importData/exportConfig.json


+ 235 - 6
package-lock.json

@@ -16,11 +16,13 @@
         "@midwayjs/jwt": "^3.16.1",
         "@midwayjs/koa": "^3.12.0",
         "@midwayjs/logger": "^3.1.0",
+        "@midwayjs/rabbitmq": "^3.17.1",
         "@midwayjs/redis": "^3.16.0",
         "@midwayjs/swagger": "^3.16.1",
         "@midwayjs/typeorm": "^3.16.0",
         "@midwayjs/upload": "^3.16.8",
         "@midwayjs/validate": "^3.12.0",
+        "amqplib": "^0.10.4",
         "axios": "^1.7.4",
         "bcryptjs": "^2.4.3",
         "crypto-js": "^4.2.0",
@@ -32,6 +34,7 @@
       },
       "devDependencies": {
         "@midwayjs/mock": "^3.12.0",
+        "@types/amqplib": "^0.10.5",
         "@types/crypto-js": "^4.2.2",
         "@types/jest": "^29.2.0",
         "@types/lodash": "^4.17.4",
@@ -48,6 +51,24 @@
         "node": ">=12.0.0"
       }
     },
+    "node_modules/@acuminous/bitsyntax": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz",
+      "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==",
+      "dependencies": {
+        "buffer-more-ints": "~1.0.0",
+        "debug": "^4.3.4",
+        "safe-buffer": "~5.1.2"
+      },
+      "engines": {
+        "node": ">=0.8"
+      }
+    },
+    "node_modules/@acuminous/bitsyntax/node_modules/safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
     "node_modules/@ampproject/remapping": {
       "version": "2.3.0",
       "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -1310,9 +1331,9 @@
       }
     },
     "node_modules/@midwayjs/core": {
-      "version": "3.16.2",
-      "resolved": "https://registry.npmmirror.com/@midwayjs/core/-/core-3.16.2.tgz",
-      "integrity": "sha512-ucJzCjL3kvTW4iLmPbYWPeDAaIrbU0G63Lw4nz4AOz4+3NIUCmD/Ezgj8m4K0VKJ0usqM72Vdg6ySYXgreYkfg==",
+      "version": "3.17.1",
+      "resolved": "https://registry.npmmirror.com/@midwayjs/core/-/core-3.17.1.tgz",
+      "integrity": "sha512-kdUeX7/BjgP88Q1ry/80kqW8BG75OwJfmOqgTgVvYI/YIl56W5FlPXwOWYWGEYEfTRuE/8Oupk5fWmLEEmhLmw==",
       "dependencies": {
         "@midwayjs/glob": "^1.0.2",
         "class-transformer": "0.5.1",
@@ -1421,6 +1442,22 @@
         "node": ">=12"
       }
     },
+    "node_modules/@midwayjs/rabbitmq": {
+      "version": "3.17.1",
+      "resolved": "https://registry.npmmirror.com/@midwayjs/rabbitmq/-/rabbitmq-3.17.1.tgz",
+      "integrity": "sha512-aBxfie3Hse8KGsKDh6LmNxgi5rjHKqs2Ul05BImKdz00Av9mKGcWoqcFrUPu4LUkzaV0GE+2hWkBVgKGDzeTcw==",
+      "dependencies": {
+        "@midwayjs/core": "^3.17.1",
+        "amqp-connection-manager": "4.1.14"
+      },
+      "engines": {
+        "node": ">=12"
+      },
+      "peerDependencies": {
+        "@types/amqplib": "*",
+        "amqplib": "*"
+      }
+    },
     "node_modules/@midwayjs/redis": {
       "version": "3.16.0",
       "resolved": "https://registry.npmmirror.com/@midwayjs/redis/-/redis-3.16.0.tgz",
@@ -1608,6 +1645,14 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/amqplib": {
+      "version": "0.10.5",
+      "resolved": "https://registry.npmmirror.com/@types/amqplib/-/amqplib-0.10.5.tgz",
+      "integrity": "sha512-/cSykxROY7BWwDoi4Y4/jLAuZTshZxd8Ey1QYa/VaXriMotBDoou7V/twJiOSHzU6t1Kp1AHAUXGCgqq+6DNeg==",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
     "node_modules/@types/babel__core": {
       "version": "7.20.5",
       "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -2187,6 +2232,56 @@
         "url": "https://github.com/sponsors/epoberezkin"
       }
     },
+    "node_modules/amqp-connection-manager": {
+      "version": "4.1.14",
+      "resolved": "https://registry.npmmirror.com/amqp-connection-manager/-/amqp-connection-manager-4.1.14.tgz",
+      "integrity": "sha512-1km47dIvEr0HhMUazqovSvNwIlSvDX2APdUpULaINtHpiki1O+cLRaTeXb/jav4OLtH+k6GBXx5gsKOT9kcGKQ==",
+      "dependencies": {
+        "promise-breaker": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10.0.0",
+        "npm": ">5.0.0"
+      },
+      "peerDependencies": {
+        "amqplib": "*"
+      }
+    },
+    "node_modules/amqplib": {
+      "version": "0.10.4",
+      "resolved": "https://registry.npmmirror.com/amqplib/-/amqplib-0.10.4.tgz",
+      "integrity": "sha512-DMZ4eCEjAVdX1II2TfIUpJhfKAuoCeDIo/YyETbfAqehHTXxxs7WOOd+N1Xxr4cKhx12y23zk8/os98FxlZHrw==",
+      "dependencies": {
+        "@acuminous/bitsyntax": "^0.1.2",
+        "buffer-more-ints": "~1.0.0",
+        "readable-stream": "1.x >=1.1.9",
+        "url-parse": "~1.5.10"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/amqplib/node_modules/isarray": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz",
+      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
+    },
+    "node_modules/amqplib/node_modules/readable-stream": {
+      "version": "1.1.14",
+      "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-1.1.14.tgz",
+      "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
+      "dependencies": {
+        "core-util-is": "~1.0.0",
+        "inherits": "~2.0.1",
+        "isarray": "0.0.1",
+        "string_decoder": "~0.10.x"
+      }
+    },
+    "node_modules/amqplib/node_modules/string_decoder": {
+      "version": "0.10.31",
+      "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-0.10.31.tgz",
+      "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
+    },
     "node_modules/ansi-align": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/ansi-align/-/ansi-align-3.0.1.tgz",
@@ -2767,6 +2862,11 @@
         "node": ">=0.10"
       }
     },
+    "node_modules/buffer-more-ints": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz",
+      "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg=="
+    },
     "node_modules/buffers": {
       "version": "0.1.1",
       "resolved": "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz",
@@ -7591,6 +7691,11 @@
         "node": ">=0.4.0"
       }
     },
+    "node_modules/promise-breaker": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/promise-breaker/-/promise-breaker-6.0.0.tgz",
+      "integrity": "sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA=="
+    },
     "node_modules/prompts": {
       "version": "2.4.2",
       "resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz",
@@ -7670,6 +7775,11 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/querystringify": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz",
+      "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
+    },
     "node_modules/queue-lit": {
       "version": "1.5.2",
       "resolved": "https://registry.npmmirror.com/queue-lit/-/queue-lit-1.5.2.tgz",
@@ -7992,6 +8102,11 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
+    },
     "node_modules/resolve": {
       "version": "1.22.8",
       "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz",
@@ -9285,6 +9400,15 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/url-parse": {
+      "version": "1.5.10",
+      "resolved": "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz",
+      "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+      "dependencies": {
+        "querystringify": "^2.1.1",
+        "requires-port": "^1.0.0"
+      }
+    },
     "node_modules/url-parse-lax": {
       "version": "3.0.0",
       "resolved": "https://registry.npmmirror.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
@@ -9571,6 +9695,23 @@
     }
   },
   "dependencies": {
+    "@acuminous/bitsyntax": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmmirror.com/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz",
+      "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==",
+      "requires": {
+        "buffer-more-ints": "~1.0.0",
+        "debug": "^4.3.4",
+        "safe-buffer": "~5.1.2"
+      },
+      "dependencies": {
+        "safe-buffer": {
+          "version": "5.1.2",
+          "resolved": "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.1.2.tgz",
+          "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        }
+      }
+    },
     "@ampproject/remapping": {
       "version": "2.3.0",
       "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -10541,9 +10682,9 @@
       }
     },
     "@midwayjs/core": {
-      "version": "3.16.2",
-      "resolved": "https://registry.npmmirror.com/@midwayjs/core/-/core-3.16.2.tgz",
-      "integrity": "sha512-ucJzCjL3kvTW4iLmPbYWPeDAaIrbU0G63Lw4nz4AOz4+3NIUCmD/Ezgj8m4K0VKJ0usqM72Vdg6ySYXgreYkfg==",
+      "version": "3.17.1",
+      "resolved": "https://registry.npmmirror.com/@midwayjs/core/-/core-3.17.1.tgz",
+      "integrity": "sha512-kdUeX7/BjgP88Q1ry/80kqW8BG75OwJfmOqgTgVvYI/YIl56W5FlPXwOWYWGEYEfTRuE/8Oupk5fWmLEEmhLmw==",
       "requires": {
         "@midwayjs/glob": "^1.0.2",
         "class-transformer": "0.5.1",
@@ -10628,6 +10769,15 @@
         "supertest": "6.3.3"
       }
     },
+    "@midwayjs/rabbitmq": {
+      "version": "3.17.1",
+      "resolved": "https://registry.npmmirror.com/@midwayjs/rabbitmq/-/rabbitmq-3.17.1.tgz",
+      "integrity": "sha512-aBxfie3Hse8KGsKDh6LmNxgi5rjHKqs2Ul05BImKdz00Av9mKGcWoqcFrUPu4LUkzaV0GE+2hWkBVgKGDzeTcw==",
+      "requires": {
+        "@midwayjs/core": "^3.17.1",
+        "amqp-connection-manager": "4.1.14"
+      }
+    },
     "@midwayjs/redis": {
       "version": "3.16.0",
       "resolved": "https://registry.npmmirror.com/@midwayjs/redis/-/redis-3.16.0.tgz",
@@ -10779,6 +10929,14 @@
         "@types/node": "*"
       }
     },
+    "@types/amqplib": {
+      "version": "0.10.5",
+      "resolved": "https://registry.npmmirror.com/@types/amqplib/-/amqplib-0.10.5.tgz",
+      "integrity": "sha512-/cSykxROY7BWwDoi4Y4/jLAuZTshZxd8Ey1QYa/VaXriMotBDoou7V/twJiOSHzU6t1Kp1AHAUXGCgqq+6DNeg==",
+      "requires": {
+        "@types/node": "*"
+      }
+    },
     "@types/babel__core": {
       "version": "7.20.5",
       "resolved": "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz",
@@ -11242,6 +11400,48 @@
         "uri-js": "^4.2.2"
       }
     },
+    "amqp-connection-manager": {
+      "version": "4.1.14",
+      "resolved": "https://registry.npmmirror.com/amqp-connection-manager/-/amqp-connection-manager-4.1.14.tgz",
+      "integrity": "sha512-1km47dIvEr0HhMUazqovSvNwIlSvDX2APdUpULaINtHpiki1O+cLRaTeXb/jav4OLtH+k6GBXx5gsKOT9kcGKQ==",
+      "requires": {
+        "promise-breaker": "^6.0.0"
+      }
+    },
+    "amqplib": {
+      "version": "0.10.4",
+      "resolved": "https://registry.npmmirror.com/amqplib/-/amqplib-0.10.4.tgz",
+      "integrity": "sha512-DMZ4eCEjAVdX1II2TfIUpJhfKAuoCeDIo/YyETbfAqehHTXxxs7WOOd+N1Xxr4cKhx12y23zk8/os98FxlZHrw==",
+      "requires": {
+        "@acuminous/bitsyntax": "^0.1.2",
+        "buffer-more-ints": "~1.0.0",
+        "readable-stream": "1.x >=1.1.9",
+        "url-parse": "~1.5.10"
+      },
+      "dependencies": {
+        "isarray": {
+          "version": "0.0.1",
+          "resolved": "https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz",
+          "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
+        },
+        "readable-stream": {
+          "version": "1.1.14",
+          "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-1.1.14.tgz",
+          "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
+          "requires": {
+            "core-util-is": "~1.0.0",
+            "inherits": "~2.0.1",
+            "isarray": "0.0.1",
+            "string_decoder": "~0.10.x"
+          }
+        },
+        "string_decoder": {
+          "version": "0.10.31",
+          "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-0.10.31.tgz",
+          "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
+        }
+      }
+    },
     "ansi-align": {
       "version": "3.0.1",
       "resolved": "https://registry.npmmirror.com/ansi-align/-/ansi-align-3.0.1.tgz",
@@ -11663,6 +11863,11 @@
       "resolved": "https://registry.npmmirror.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz",
       "integrity": "sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A=="
     },
+    "buffer-more-ints": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz",
+      "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg=="
+    },
     "buffers": {
       "version": "0.1.1",
       "resolved": "https://registry.npmmirror.com/buffers/-/buffers-0.1.1.tgz",
@@ -15287,6 +15492,11 @@
       "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
       "dev": true
     },
+    "promise-breaker": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/promise-breaker/-/promise-breaker-6.0.0.tgz",
+      "integrity": "sha512-BthzO9yTPswGf7etOBiHCVuugs2N01/Q/94dIPls48z2zCmrnDptUUZzfIb+41xq0MnYZ/BzmOd6ikDR4ibNZA=="
+    },
     "prompts": {
       "version": "2.4.2",
       "resolved": "https://registry.npmmirror.com/prompts/-/prompts-2.4.2.tgz",
@@ -15341,6 +15551,11 @@
         "side-channel": "^1.0.6"
       }
     },
+    "querystringify": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmmirror.com/querystringify/-/querystringify-2.2.0.tgz",
+      "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
+    },
     "queue-lit": {
       "version": "1.5.2",
       "resolved": "https://registry.npmmirror.com/queue-lit/-/queue-lit-1.5.2.tgz",
@@ -15581,6 +15796,11 @@
       "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
       "dev": true
     },
+    "requires-port": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/requires-port/-/requires-port-1.0.0.tgz",
+      "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
+    },
     "resolve": {
       "version": "1.22.8",
       "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.8.tgz",
@@ -16456,6 +16676,15 @@
         "punycode": "^2.1.0"
       }
     },
+    "url-parse": {
+      "version": "1.5.10",
+      "resolved": "https://registry.npmmirror.com/url-parse/-/url-parse-1.5.10.tgz",
+      "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+      "requires": {
+        "querystringify": "^2.1.1",
+        "requires-port": "^1.0.0"
+      }
+    },
     "url-parse-lax": {
       "version": "3.0.0",
       "resolved": "https://registry.npmmirror.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz",

+ 3 - 0
package.json

@@ -11,11 +11,13 @@
     "@midwayjs/jwt": "^3.16.1",
     "@midwayjs/koa": "^3.12.0",
     "@midwayjs/logger": "^3.1.0",
+    "@midwayjs/rabbitmq": "^3.17.1",
     "@midwayjs/redis": "^3.16.0",
     "@midwayjs/swagger": "^3.16.1",
     "@midwayjs/typeorm": "^3.16.0",
     "@midwayjs/upload": "^3.16.8",
     "@midwayjs/validate": "^3.12.0",
+    "amqplib": "^0.10.4",
     "axios": "^1.7.4",
     "bcryptjs": "^2.4.3",
     "crypto-js": "^4.2.0",
@@ -27,6 +29,7 @@
   },
   "devDependencies": {
     "@midwayjs/mock": "^3.12.0",
+    "@types/amqplib": "^0.10.5",
     "@types/crypto-js": "^4.2.2",
     "@types/jest": "^29.2.0",
     "@types/lodash": "^4.17.4",

+ 5 - 0
src/config/config.local.ts

@@ -17,10 +17,15 @@ const redisPwd = '1234qwer!@#$';
 const redisDB = 0;
 /**redis 记录登录的key */
 const loginSign = 'cxyy';
+/**mq设置*/
+const mqSetting = {
+  url: 'amqp://huaxin:1234qwerasdf@10.120.114.6/hxmsg',
+};
 export default {
   // use for cookie sign key, should change to your own and keep security
   keys: '1697684406848_4978',
   loginSign,
+  rabbitmq: mqSetting,
   // 请求记录在redis留存时间,超过时间.数据变化将不会记录.以秒为单位--5分钟
   requestTimeLimit: 300,
   jwt: {

+ 5 - 0
src/config/config.prod.ts

@@ -18,10 +18,15 @@ const redisPwd = '1234qwer!@#$';
 const redisDB = 0;
 /**redis 记录登录的key */
 const loginSign = 'cxyy';
+/**mq设置*/
+const mqSetting = {
+  url: 'amqp://huaxin:1234qwerasdf@10.120.114.6/hxmsg',
+};
 export default {
   // use for cookie sign key, should change to your own and keep security
   keys: '1697684406848_4978',
   loginSign,
+  rabbitmq: mqSetting,
   // 请求记录在redis留存时间,超过时间.数据变化将不会记录.以秒为单位--5分钟
   requestTimeLimit: 300,
   jwt: {

+ 6 - 1
src/config/config.self.ts

@@ -18,10 +18,15 @@ const redisPwd = '1234qwer!@#$';
 const redisDB = 0;
 /**redis 记录登录的key */
 const loginSign = 'cxyy';
+/**mq设置*/
+const mqSetting = {
+  url: 'amqp://huaxin:1234qwerasdf@127.0.0.1/hxmsg',
+};
 export default {
   // use for cookie sign key, should change to your own and keep security
   keys: '1697684406848_4978',
   loginSign,
+  rabbitmq: mqSetting,
   // 请求记录在redis留存时间,超过时间.数据变化将不会记录.以秒为单位--5分钟
   requestTimeLimit: 300,
   jwt: {
@@ -46,7 +51,7 @@ export default {
         port: 54321,
         entities: ['./entity'],
         type: 'postgres',
-        synchronize: false, // 如果第一次使用,不存在表,有同步的需求可以写 true,注意会丢数据
+        synchronize: true, // 如果第一次使用,不存在表,有同步的需求可以写 true,注意会丢数据
         logging: false,
         subscribers: [DbSubscriber],
       },

+ 2 - 0
src/configuration.ts

@@ -14,6 +14,7 @@ import { CheckTokenMiddleware } from './middleware/checkToken.middleware';
 import { ResponseMiddleware } from './middleware/response.middleware';
 import * as axios from '@midwayjs/axios';
 import * as upload from '@midwayjs/upload';
+import * as rabbitmq from '@midwayjs/rabbitmq';
 @Configuration({
   imports: [
     koa,
@@ -23,6 +24,7 @@ import * as upload from '@midwayjs/upload';
     orm,
     axios,
     upload,
+    rabbitmq,
     {
       component: info,
       enabledEnvironment: ['local'],

+ 37 - 0
src/consumer/mission.consumer.ts

@@ -0,0 +1,37 @@
+import { Consumer, MSListenerType, RabbitMQListener, Inject } from '@midwayjs/core';
+import { Context } from '@midwayjs/rabbitmq';
+import { ConsumeMessage } from 'amqplib';
+import { AsyncExportService } from '../service/asyncExport.service';
+
+@Consumer(MSListenerType.RABBITMQ)
+export class MissionConsumer {
+  @Inject()
+  ctx: Context;
+  // @Inject()
+  // aeService: AsyncExportService;
+
+  @RabbitMQListener('mission', {
+    exchange: 'missionEx',
+    exchangeOptions: {
+      type: 'direct',
+    },
+    consumeOptions: {
+      noAck: true,
+    },
+  })
+  async gotData(msg: ConsumeMessage) {
+    try {
+      const id = msg.content.toString('utf8');
+      const asService = await this.ctx.requestContext.getAsync(AsyncExportService);
+      // 接 AsyncExportController
+      // 3.消费者消费消息,并开始执行任务
+      try {
+        await asService.execute(id);
+      } catch (error) {
+        console.error(error);
+      }
+    } catch (error) {
+      console.log(error);
+    }
+  }
+}

+ 44 - 0
src/controller/asyncExport.controller.ts

@@ -0,0 +1,44 @@
+import { Controller, Post, Body, Inject } from '@midwayjs/core';
+import { Context } from '@midwayjs/koa';
+import { get, omit } from 'lodash';
+import { ErrorCode, ServiceError } from '../error/service.error';
+import { ExportMissionService } from '../service/exportMission.service';
+import { AsyncExportService } from '../service/asyncExport.service';
+
+@Controller('/asyncExport')
+export class AsyncExportController {
+  @Inject()
+  ctx: Context;
+  @Inject()
+  missionService: ExportMissionService;
+  @Inject()
+  aeService: AsyncExportService;
+
+  @Post('/')
+  async index(@Body() body: object) {
+    const user = get(this.ctx, 'user');
+    if (!user || !get(user, 'id')) throw new ServiceError(ErrorCode.NOT_LOGIN);
+    const table = get(body, 'table');
+    const config = get(body, 'config');
+    if (!table || !config) throw new ServiceError(ErrorCode.BODY_ERROR);
+    const query = omit(body, ['table', 'config']);
+    // 检查 查询范围内有没有数据,没有数据直接返回没数据: 无法导出会抛异常中断程序
+    await this.aeService.checkHaveDataInQuery(table, query);
+    // 下达任务,1.创建任务数据; 2.创建mq任务; 3.消费mq任务进行导出; 4.更新任务数据
+    // 1.创建任务数据
+    const role = get(user, 'role', []);
+    const isAdmin = role.find(f => f === 'Admin');
+    let user_type = '';
+    if (isAdmin) user_type = 'ADMIN';
+    else user_type = 'USER';
+    const data = {
+      user: get(user, 'id'),
+      user_type,
+      config: { table, config, query },
+      progress: 0,
+    };
+    const res = await this.missionService.create(data);
+    // 2.创建mq任务
+    await this.missionService.toSendMq(get(res, 'id'));
+  }
+}

+ 52 - 0
src/controller/exportConfig.controller.ts

@@ -0,0 +1,52 @@
+import { Body, Controller, Get, Inject, Param, Post } from '@midwayjs/core';
+import { ExportConfigService } from '../service/exportConfig.service';
+import { get, pick, upperFirst } from 'lodash';
+
+@Controller('/ec')
+export class ExportConfigController {
+  @Inject()
+  ecService: ExportConfigService;
+
+  @Get('/user/:table')
+  async userFetch(@Param('table') table: string) {
+    table = upperFirst(table);
+    let data: any = await this.ecService.fetch({ table });
+    if (!data) {
+      const filters = ['id', 'created_time', , 'update_time', '__v'];
+      const cols = await this.ecService.getModelColumns(table, filters);
+      data = { config: cols };
+    }
+    let config = get(data, 'config', []);
+    config = config.map(i => pick(i, ['column', 'zh']));
+    data.config = config;
+    return data;
+  }
+
+  @Get('/:table')
+  async fetch(@Param('table') table: string) {
+    table = upperFirst(table);
+    let data: any = await this.ecService.fetch({ table });
+    if (!data) {
+      const filters = ['id', 'created_time', , 'update_time', '__v'];
+      const cols = await this.ecService.getModelColumns(table, filters);
+      data = { config: cols };
+    }
+    return data;
+  }
+
+  /**修改,根据表名修改 */
+  @Post('/:table')
+  async udpate(@Param('table') table: string, @Body() body: object) {
+    table = upperFirst(table);
+    const res = await this.ecService.update({ table }, body);
+    return res;
+  }
+
+  @Get('/dict')
+  async getDict() {
+    const tableList = this.ecService.getDict_tables();
+    const typeList = this.ecService.getDict_type();
+    const sourceList = this.ecService.getDict_source();
+    return { tableList, typeList, sourceList };
+  }
+}

+ 11 - 61
src/controller/home.controller.ts

@@ -1,75 +1,25 @@
-import { Config, Controller, Get, Inject, Query } from '@midwayjs/core';
-import { InitTwoService } from '../service/initData/initTwo.service';
-import { InitRegionService } from '../service/initData/initRegion.service';
-import { InitOneService } from '../service/initData/initOne.service';
-import { QichachaService } from '../service/thirdParty/qichacha.service';
-import { InitThreeService } from '../service/initData/initThree.service';
-import { DataDealService } from '../service/initData/dataDeal.service';
-import { TestsService } from '../service/tests.service';
-import { ESService } from '../service/elasticsearch/es.service';
+import { Controller, Get, Inject, Query } from '@midwayjs/core';
 import { Opera } from '../frame/dbOpera';
 import { InitSystemDataService } from '../service/initData/initSystemData.service';
+import { ExportConfigService } from '../service/exportConfig.service';
+import { ExportMissionService } from '../service/exportMission.service';
+import { AsyncExportService } from '../service/asyncExport.service';
 @Controller('/')
 export class HomeController {
   @Inject()
-  threeService: InitThreeService;
+  ec: ExportConfigService;
   @Inject()
-  twoService: InitTwoService;
-  @Inject()
-  oneService: InitOneService;
-  @Inject()
-  regionService: InitRegionService;
-  @Inject()
-  qichachaService: QichachaService;
-  @Inject()
-  dataDealService: DataDealService;
-  @Inject()
-  testService: TestsService;
-  @Inject()
-  esService: ESService;
-  @Inject()
-  initSystemDataService: InitSystemDataService;
+  missionService: ExportMissionService;
 
+  @Inject()
+  aeService: AsyncExportService;
   Opera = Opera;
 
   @Get('/')
   async home(@Query() query: object): Promise<any> {
-    // await this.initSystemDataService.initSystemMenus();
-    // await this.initSystemDataService.initRoleData();
-    // await this.initSystemDataService.initRoleMenus();
-    // await this.dataDealService.correctImportData();
-    // await this.esService.initES()
-    // await this.dataDealService.initUserMenus();
-    // await this.dataDealService.initRoleMenus();
-    // await this.oneService.addTags();
-    // await this.oneService.addImportDataTags();
-    // await this.twoService.addTags();
-    // await this.twoService.addImportDataTags();
-    // await this.oneService.dataToUse();
-    // await this.twoService.dataToUse();
-    // await this.threeService.dataToUse();
-    // const data = await this.qichachaService.searchByName('长春市福瑞科技');
-    // await this.threeService.import2021Company();
-    // await this.threeService.import2024Company();
-    // await this.threeService.importMatching();
-    // await this.dataDealService.JsonbAddDefault();
-    // const sq = { 'props.hobby': '睡觉1' };
-    // const operas = { 'props.hobby': Opera.JsonObject };
-    // const res = await this.testService.query(sq, null, operas);
-    // const body = {
-    //   nick_name: '测试20240806_1',
-    //   account: '测试20240806_1',
-    //   age: 20,
-    //   hobby: ['上班'],
-    //   time: '2024-09-01',
-    //   tags: ['黑奴'],
-    // };
-    // const res = await this.testService.create(body);
-    // const id = 9;
-    // const res = await this.testService.update({ id }, { age: 21 });
-    // const res = await this.testService.fetch({ id });
-    // const res = await this.testService.delete({ id });
-    // return res;
+    // await this.ec.initData()
+    // await this.missionService.toSendMq(1);
+    await this.aeService.execute(11)
     return 'starting...';
   }
   // @Get('/:method')

+ 21 - 0
src/entity/exportConfig.entity.ts

@@ -0,0 +1,21 @@
+import { Entity, Column } from 'typeorm';
+import { BaseModel } from '../frame/BaseModel';
+class Config {
+  column: string; // 字段
+  zh: string; //中文
+  type: string; // 数据类型: 字符串(string);数字(integer);json(jsonb,先不做内部处理);时间(date)
+  source: string; //数据来源: 手写(无特殊处理);字典(需要填写dict和dict_value);关联
+
+  from: string;// 数据来源为 字典表:则填写字典类型; 关联表:则填写表名(小驼峰)
+  from_column: string; // 数据来源为 字典表:填写作为值的字段,默认为value,可以使用label; 关联表:取出哪个字段
+
+  is_use: string; //是否使用: 0使用;1禁用
+}
+/**导出设置 */
+@Entity('exportConfig')
+export class ExportConfig extends BaseModel {
+  @Column({ type: 'character varying', nullable: true, comment: '表名' })
+  table: string;
+  @Column({ type: 'jsonb', nullable: true, comment: '设置', default: [] })
+  config: Array<Config>;
+}

+ 20 - 0
src/entity/exportMission.entity.ts

@@ -0,0 +1,20 @@
+import { Entity, Column } from 'typeorm';
+import { BaseModel } from '../frame/BaseModel';
+class config {
+  table: string;
+  config: Array<any>;
+  query: object;
+}
+@Entity('exportMission', { comment: '导出任务' })
+export class ExportMission extends BaseModel {
+  @Column({ type: 'integer', nullable: true, comment: '用户' })
+  user: number;
+  @Column({ type: 'character varying', nullable: true, comment: '用户类型' })
+  user_type: string; // ADMIN/USER
+  @Column({ type: 'jsonb', nullable: true, comment: '导出内容', default: {} })
+  config: object;
+  @Column({ type: 'integer', nullable: true, comment: '进度' })
+  progress: number;
+  @Column({ type: 'character varying', nullable: true, comment: '下载地址' })
+  uri: string;
+}

+ 2 - 2
src/entity/platform/achievement.entity.ts

@@ -3,9 +3,9 @@ import { BaseModel } from '../../frame/BaseModel';
 import * as dayjs from 'dayjs';
 // 成果需要与用户连接,否则就只能属于平台的了
 // 成果
-@Entity('achievement')
+@Entity('achievement', { comment: '成果数据' })
 export class Achievement extends BaseModel {
-  @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
+  @Column({ type: 'integer', nullable: true, comment: '平台用户' })
   user: number;
   @Column({ type: 'character varying', nullable: true, comment: '所属产业' })
   industry: string;

+ 1 - 1
src/entity/platform/collection.entity.ts

@@ -2,7 +2,7 @@ import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 import * as dayjs from 'dayjs';
 // 收藏
-@Entity('collection')
+@Entity('collection', { comment: '收藏数据' })
 export class Collection extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/platform/demand.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 需求
-@Entity('demand')
+@Entity('demand', { comment: '需求数据' })
 export class Demand extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/platform/design.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 平台设置
-@Entity('design')
+@Entity('design', { comment: '平台设置' })
 export class Design extends BaseModel {
   @Column({ type: 'character varying', nullable: true, comment: '标题' })
   zhTitle: string;

+ 1 - 1
src/entity/platform/directory.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 目录
-@Entity('directory')
+@Entity('directory', { comment: '产研行研目录' })
 export class Directory extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '所属行研产研' })
   journal: number;

+ 1 - 1
src/entity/platform/footplate.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 中试平台
-@Entity('footplate')
+@Entity('footplate', { comment: '中试平台' })
 export class Footplate extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/platform/friend.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 
-@Entity('friend')
+@Entity('friend', { comment: '合作伙伴' })
 export class Friend extends BaseModel {
   @Column({ type: 'character varying', nullable: true, comment: '编码' })
   code: string;

+ 1 - 1
src/entity/platform/journal.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 行研产研
-@Entity('journal')
+@Entity('journal', { comment: '行研产研' })
 export class Journal extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/platform/match.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 //赛事
-@Entity('match')
+@Entity('match', { comment: '双创赛事' })
 export class Match extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/platform/matchPath.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 //赛事流程
-@Entity('matchPath')
+@Entity('matchPath', { comment: '赛事流程' })
 export class MatchPath extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '赛事id' })
   match: number;

+ 1 - 1
src/entity/platform/news.entity.ts

@@ -2,7 +2,7 @@ import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 import * as dayjs from 'dayjs';
 // 新闻
-@Entity('news')
+@Entity('news', { comment: '政策、新闻、行业动态' })
 export class News extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/platform/notes.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 期刊
-@Entity('notes')
+@Entity('notes', { comment: '期刊数据' })
 export class Notes extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '所属期刊' })
   journal: number;

+ 1 - 1
src/entity/platform/project.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 //项目
-@Entity('project')
+@Entity('project', { comment: '项目数据' })
 export class Project extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 2
src/entity/platform/score.entity.ts

@@ -1,7 +1,6 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
-//项目
-@Entity('score')
+@Entity('score', { comment: '赛事分数' })
 export class Score extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/platform/sector.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 行业
-@Entity('sector')
+@Entity('sector', { comment: '行业数据' })
 export class Sector extends BaseModel {
   @Column({ type: 'character varying', nullable: true, comment: '标题' })
   title: string;

+ 1 - 1
src/entity/platform/sign.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 //赛事报名
-@Entity('sign')
+@Entity('sign', { comment: '赛事报名' })
 export class Sign extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/platform/supply.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 供给
-@Entity('supply')
+@Entity('supply', { comment: '供给数据' })
 export class Supply extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/platform/support.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 服务
-@Entity('support')
+@Entity('support', { comment: '服务数据' })
 export class Support extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/system/admin.entity.ts

@@ -2,7 +2,7 @@ import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 import * as bcrypt from 'bcryptjs';
 // 管理员
-@Entity('admin')
+@Entity('admin', { comment: '管理员用户' })
 export class Admin extends BaseModel {
   @Column({ type: 'character varying', nullable: true, comment: '管理员名称' })
   nick_name: string;

+ 1 - 1
src/entity/system/dept.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 部门
-@Entity('dept', { comment: '部门' })
+@Entity('dept', { comment: '部门数据' })
 export class Dept extends BaseModel {
   @Column({ type: 'character varying', comment: '部门名称' })
   name: string;

+ 1 - 1
src/entity/system/dictData.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 字典数据表
-@Entity('dictData')
+@Entity('dictData', { comment: '字典数据' })
 export class DictData extends BaseModel {
   @Column({ type: 'character varying', comment: '字典类型编码' })
   code: string;

+ 1 - 1
src/entity/system/dictType.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 字典类型表
-@Entity('dictType')
+@Entity('dictType', { comment: '字典目录' })
 export class DictType extends BaseModel {
   @Column({ type: 'character varying', comment: '字典类型名称' })
   title: string;

+ 1 - 1
src/entity/system/menus.entity.ts

@@ -7,7 +7,7 @@ import { BaseModel } from '../../frame/BaseModel';
 //   controller_code: '接口函数名路径';
 // }
 // 菜单表
-@Entity('menus')
+@Entity('menus', { comment: '管理端目录' })
 export class Menus extends BaseModel {
   @Column({ type: 'character varying', nullable: true, comment: '目录名称' })
   name: string;

+ 1 - 1
src/entity/system/message.entity.ts

@@ -9,7 +9,7 @@ class toModel {
   is_read: string; // 是否阅读: 0否;1是
 }
 
-@Entity('message')
+@Entity('message', { comment: '消息数据' })
 export class Message extends BaseModel {
   @Column({ type: 'text', nullable: true, comment: '消息内容' })
   content: string;

+ 1 - 1
src/entity/system/region.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 
-@Entity('region')
+@Entity('region', { comment: '地区数据' })
 export class Region extends BaseModel {
   @Column({ type: 'character varying', nullable: true, comment: '编码' })
   code: string;

+ 1 - 1
src/entity/system/role.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 角色表
-@Entity('role')
+@Entity('role', { comment: '角色数据' })
 export class Role extends BaseModel {
   @Column({ type: 'character varying', comment: '角色名称' })
   name: string;

+ 1 - 1
src/entity/system/tags.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 标签
-@Entity('tags')
+@Entity('tags', { comment: '标签数据' })
 export class Tags extends BaseModel {
   @Column({ type: 'character varying', nullable: true, comment: '标题' })
   title: string;

+ 1 - 1
src/entity/system/user.entity.ts

@@ -2,7 +2,7 @@ import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 import * as bcrypt from 'bcryptjs';
 // 用户表
-@Entity('user')
+@Entity('user', { comment: '用户数据' })
 export class User extends BaseModel {
   @Column({ type: 'character varying', comment: 'openid', nullable: true })
   openid: string;

+ 1 - 1
src/entity/system/userMenus.entity.ts

@@ -7,7 +7,7 @@ import { BaseModel } from '../../frame/BaseModel';
 //   controller_code: '接口函数名路径';
 // }
 // 菜单表
-@Entity('userMenus')
+@Entity('userMenus', { comment: '用户目录' })
 export class UserMenus extends BaseModel {
   @Column({ type: 'character varying', comment: '目录名称' })
   name: string;

+ 1 - 1
src/entity/users/applyCompany.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 企业申请
-@Entity('applyCompany')
+@Entity('applyCompany', { comment: '企业认领申请' })
 export class ApplyCompany extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/association.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 商协会
-@Entity('association')
+@Entity('association', { comment: '商协会数据' })
 export class Association extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/cirelation.entity.ts

@@ -1,7 +1,7 @@
 import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 企业孵化器关联
-@Entity('cirelation')
+@Entity('cirelation', { comment: '企业孵化器关联' })
 export class Cirelation extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/company.entity.ts

@@ -2,7 +2,7 @@ import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 import * as dayjs from 'dayjs';
 // 企业
-@Entity('company')
+@Entity('company', { comment: '企业数据' })
 export class Company extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/companyYear.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 //企业年度信息
-@Entity('companyYear')
+@Entity('companyYear', { comment: '企业年度数据' })
 export class CompanyYear extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/competition.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 赛事管理人
-@Entity('competition')
+@Entity('competition', { comment: '赛事管理人员数据' })
 export class Competition extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/contactApply.entity.ts

@@ -2,7 +2,7 @@ import { Entity, Column } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 
 /**联系方式申请表 */
-@Entity('contactApply')
+@Entity('contactApply', { comment: '联系方式申请数据' })
 export class ContactApply extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '申请用户id' })
   apply_user: number;

+ 1 - 1
src/entity/users/expert.entity.ts

@@ -3,7 +3,7 @@ import { BaseModel } from '../../frame/BaseModel';
 import dayjs = require('dayjs');
 // 导入的数据没有归属,只能不加权限的查询,也无法作为用户的证明
 // 专家
-@Entity('expert')
+@Entity('expert', { comment: '专家数据' })
 export class Expert extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/incubator.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 //孵化基地
-@Entity('incubator')
+@Entity('incubator', { comment: '孵化基地数据' })
 export class Incubator extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/incubatorYear.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 //孵化基地年度信息
-@Entity('incubatorYear')
+@Entity('incubatorYear', { comment: '孵化基地年度数据' })
 export class IncubatorYear extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/investment.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 投资人
-@Entity('investment')
+@Entity('investment', { comment: '投资人数据' })
 export class Investment extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/school.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 院校
-@Entity('school')
+@Entity('school', { comment: '院校数据' })
 export class School extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/state.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 政府部门
-@Entity('state')
+@Entity('state', { comment: '政府部门数据' })
 export class State extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 1 - 1
src/entity/users/unit.entity.ts

@@ -1,7 +1,7 @@
 import { Column, Entity } from 'typeorm';
 import { BaseModel } from '../../frame/BaseModel';
 // 科研机构
-@Entity('unit')
+@Entity('unit', { comment: '科研机构数据' })
 export class Unit extends BaseModel {
   @Column({ type: 'integer', nullable: true, comment: '平台用户id' })
   user: number;

+ 6 - 2
src/error/service.error.ts

@@ -35,11 +35,15 @@ export enum ErrorCode {
   CONTACTAPPLY_NO_PERMISSION = 'CONTACTAPPLY_NO_PERMISSION',
   CONTACTAPPLY_NO_KEYWORD = 'CONTACTAPPLY_NO_KEYWORD',
   CONTACTAPPLY_MATCHING_NOT_ENOUGH = 'CONTACTAPPLY_MATCHING_NOT_ENOUGH',
-  CONTACTAPPLY_IS_THIS_USER_DATA='CONTACTAPPLY_IS_THIS_USER_DATA',
-  CONTACTAPPLY_DEPT_NO_ADMIN='CONTACTAPPLY_DEPT_NO_ADMIN',
+  CONTACTAPPLY_IS_THIS_USER_DATA = 'CONTACTAPPLY_IS_THIS_USER_DATA',
+  CONTACTAPPLY_DEPT_NO_ADMIN = 'CONTACTAPPLY_DEPT_NO_ADMIN',
   SERVICE_APPLY = 'SERVICE_APPLY',
 
   DATA_NOT_FOUND = 'DATA_NOT_FOUND',
+
+  // export
+  NO_EXPORT_SETTING = 'NO_EXPORT_SETTING',
+  NO_DATA_IN_EXPORT_QUERY = 'NO_DATA_IN_EXPORT_QUERY',
 }
 export class ServiceError extends Error {
   constructor(errcode: string) {

+ 5 - 180
src/frame/BaseServiceV2.ts

@@ -7,6 +7,7 @@ import dayjs = require('dayjs');
 import { InjectEntityModel } from '@midwayjs/typeorm';
 import { Repository } from 'typeorm';
 import { Tags } from '../entity/system/tags.entity';
+import { completeBuilderCondition } from './conditionBuilder';
 /**
  * query默认的查询方式(哪个字段 是 = 还是 IN 还是 LIKE)的设置函数为getQueryColumnsOpera,如果有需要重写即可,返回object
  * {
@@ -65,7 +66,7 @@ export abstract class BaseServiceV2 {
     } else orderObject = { id: 'DESC' };
     // 没有传如何查询,就获取query查询设置的默认查询方式
     if (!operas) operas = this.getQueryColumnsOpera();
-    this.completeBuilderCondition(builder, query, operas);
+    completeBuilderCondition(builder, query, operas, this.model);
     // 分页
     if (isString(skip)) {
       skip = parseInt(skip);
@@ -86,170 +87,6 @@ export abstract class BaseServiceV2 {
     return { data, total };
   }
 
-  /**
-   *
-   * @param builder model的createQueryBuilder,只有到最后要查数据的时候才是异步的
-   * @param {object} query 查询条件
-   * @param {object} operas 指定查询方式
-   */
-  completeBuilderCondition(builder, query = {}, operas = {}) {
-    // 组织查询条件
-    if (!query) return;
-    const searchColumns = Object.keys(query);
-    if (searchColumns.length <= 0) return;
-    for (let i = 0; i < searchColumns.length; i++) {
-      const key = searchColumns[i];
-      const value = query[key];
-      if (!value) continue;
-      /**该字段的查询方式 */
-      const opera = get(operas, key);
-      /**builder的使用函数名 */
-      let method = 'where';
-      if (i === 0) method = 'where';
-      else method = 'andWhere';
-      let str;
-      let params;
-      // 需要给变量位置重命名,否则多个条件叠加后,都会使用第一个参数
-      const valueStr = `value${i}`;
-      let valueArr = [];
-      const strArr = [];
-      switch (opera) {
-        case this.Opera.Between:
-          str = `"${key}" Between :${valueStr}_1 AND :${valueStr}_2`;
-          params = { [`${valueStr}_1`]: head(value), [`${valueStr}_2`]: last(value) };
-          break;
-        case this.Opera.Not:
-          str = `"${key}" != :${valueStr}`;
-          params = { [`${valueStr}`]: value };
-          break;
-        case this.Opera.Like:
-          str = `"${key}" Like :${valueStr}`;
-          params = { [`${valueStr}`]: `%${value}%` };
-          break;
-        case this.Opera.LikeLeft:
-          str = `"${key}" Like :${valueStr}`;
-          params = { [`${valueStr}`]: `%${value}` };
-          break;
-        case this.Opera.LikeRight:
-          str = `"${key}" Like :${valueStr}`;
-          params = { [`${valueStr}`]: `${value}%` };
-          break;
-        case this.Opera.ILike:
-          str = `"${key}" Not Like :${valueStr}`;
-          params = { [`${valueStr}`]: `%${value}%` };
-          break;
-        case this.Opera.ILikeLeft:
-          str = `"${key}" Not Like :${valueStr}`;
-          params = { [`${valueStr}`]: `%${value}` };
-          break;
-        case this.Opera.ILikeRight:
-          str = `"${key}" Not Like :${valueStr}`;
-          params = { [`${valueStr}`]: `${value}%` };
-          break;
-        case this.Opera.LessThan:
-          str = `"${key}" < :${valueStr}`;
-          params = { [`${valueStr}`]: value };
-          break;
-        case this.Opera.LessThanOrEqual:
-          str = `"${key}" <= :${valueStr}`;
-          params = { [`${valueStr}`]: value };
-          break;
-        case this.Opera.MoreThan:
-          str = `"${key}" > :${valueStr}`;
-          params = { [`${valueStr}`]: value };
-          break;
-        case this.Opera.MoreThanOrEqual:
-          str = `"${key}" >= :${valueStr}`;
-          params = { [`${valueStr}`]: value };
-          break;
-        case this.Opera.In:
-          if (!isArray(value)) str = `"${key}" IN (:${valueStr})`;
-          else str = `"${key}" IN (:...${valueStr})`;
-          params = { [`${valueStr}`]: value };
-          break;
-        case this.Opera.IsNull:
-          str = `"${key}" IS NULL`;
-          break;
-        case this.Opera.IsNotNull:
-          str = `"${key}" IS NOT NULL`;
-          params = { [`${valueStr}`]: value };
-          break;
-        case this.Opera.Json:
-          params = {};
-          if (isArray(value)) valueArr = value;
-          else valueArr = [value];
-          for (let vi = 0; vi < valueArr.length; vi++) {
-            const v = valueArr[vi];
-            const mvalKey = `${valueStr}${vi}`;
-            const mstr = `JSONB_EXISTS("${key}", :${mvalKey})`;
-            strArr.push(mstr);
-            params[mvalKey] = v;
-          }
-          str = `(${strArr.join(' OR ')})`;
-          break;
-        case this.Opera.JsonObject:
-          const jokeys = key.split('.');
-          const jorootCol = head(jokeys);
-          const jolastKey = last(jokeys);
-          const jopath = jokeys.filter(f => f !== jorootCol && f !== jolastKey);
-          str = `"${jorootCol}" `;
-          for (const jok of jopath) {
-            str = `${str} -> ${jok}`;
-          }
-          str = `${str} ->> '${jolastKey}' = :${valueStr}`;
-          params = { [`${valueStr}`]: value };
-          break;
-        case this.Opera.JsonArrayObject:
-          /**
-           * 1.分割key,过来的属性默认以 x.y.z... 形式
-           * x:根子段;后面,数组中依次往下的属性名
-           */
-          const keys = key.split('.');
-          let numberValue;
-          if (isFinite(parseInt(value))) numberValue = parseInt(value);
-          let rootCol = head(keys);
-          let lastKey = last(keys);
-          let path = keys.filter(f => f !== rootCol && f !== lastKey);
-          const getObject = (path, lastKey, value) => {
-            let obj = {};
-            let mid = obj;
-            for (const k of path) {
-              mid[k] = {};
-              mid = mid[k];
-            }
-            mid[lastKey] = value;
-            return obj;
-          };
-          const obj = getObject(path, lastKey, value);
-          let newVal = JSON.stringify([obj]);
-          str = `"${rootCol}" @> :${valueStr}`;
-          params = { [`${valueStr}`]: newVal };
-          if (numberValue) {
-            const numObj = getObject(path, lastKey, numberValue);
-            let numVal = JSON.stringify([numObj]);
-            const valueStrNum = `${valueStr}Num`;
-            str = `(${str} OR "${rootCol}" @> :${valueStrNum})`;
-            params[valueStrNum] = numVal;
-          }
-          break;
-        case this.Opera.Equal:
-        default:
-          const isString = this.columnIsString(key);
-          if (isString) {
-            // 字符串默认使用模糊查询
-            str = `"${key}" Like :${valueStr}`;
-            params = { [`${valueStr}`]: `%${value}%` };
-          } else {
-            str = `"${key}" = :${valueStr}`;
-            params = { [`${valueStr}`]: value };
-          }
-          break;
-      }
-      if (!str) continue;
-      builder[method](str, params);
-    }
-  }
-
   /**
    * 单查询,不止用id还可以根据别的条件,默认全等,可以在调用时,进行查询方式设置
    * @param {object} query 查询条件
@@ -257,7 +94,7 @@ export abstract class BaseServiceV2 {
    */
   async fetch(query: object, operas = {}) {
     const builder = this.model.createQueryBuilder();
-    this.completeBuilderCondition(builder, query, operas);
+    completeBuilderCondition(builder, query, operas, this.model);
     const result = await builder.getOne();
     return result;
   }
@@ -310,7 +147,7 @@ export abstract class BaseServiceV2 {
     }
     // 找到原数据
     const originDataBuilder = this.model.createQueryBuilder();
-    this.completeBuilderCondition(originDataBuilder, query);
+    completeBuilderCondition(originDataBuilder, query, {}, this.model);
     const origin_data = await originDataBuilder.getMany(query);
     if (origin_data.length <= 0) return;
     await this.model.update(query, updateData);
@@ -334,7 +171,7 @@ export abstract class BaseServiceV2 {
     if (query && Object.keys(query).length <= 0) return;
     // 删除前,先查出来数据, 找到原数据
     const originDataBuilder = this.model.createQueryBuilder();
-    this.completeBuilderCondition(originDataBuilder, query);
+    completeBuilderCondition(originDataBuilder, query, {}, this.model);
     const origin_data = await originDataBuilder.getMany(query);
     if (origin_data.length <= 0) return;
     const result = await this.model.delete(query);
@@ -453,16 +290,4 @@ export abstract class BaseServiceV2 {
     const has = columns.find(f => get(f, 'propertyName') === columnName);
     return !isUndefined(has);
   }
-  /**
-   * 检查字段是否是字符串字段
-   * @param {string} columnName 字段名
-   */
-  private columnIsString(columnName: string) {
-    const columns = this.model.metadata.columns;
-    const colSetting = columns.find(f => get(f, 'propertyName') === columnName);
-    if (!colSetting) return false;
-    const type = get(colSetting, 'type');
-    if (!type) return false;
-    return type === 'character varying' || type === 'text';
-  }
 }

+ 180 - 0
src/frame/conditionBuilder.ts

@@ -0,0 +1,180 @@
+import { get, head, last, isArray, isFunction } from 'lodash';
+import { Opera } from './dbOpera';
+/**
+ *
+ * @param builder model的createQueryBuilder,只有到最后要查数据的时候才是异步的
+ * @param {object} query 查询条件
+ * @param {object} operas 指定查询方式
+ * @param {any} model 存在的情况,default会调用columnIsString 判断是否是字符串字段的函数或其他任何形式的参数
+ */
+export const completeBuilderCondition = (builder, query = {}, operas = {}, model?) => {
+  // 组织查询条件
+  if (!query) return;
+  const searchColumns = Object.keys(query);
+  if (searchColumns.length <= 0) return;
+  for (let i = 0; i < searchColumns.length; i++) {
+    const key = searchColumns[i];
+    const value = query[key];
+    if (!value) continue;
+    /**该字段的查询方式 */
+    const opera = get(operas, key);
+    /**builder的使用函数名 */
+    let method = 'where';
+    if (i === 0) method = 'where';
+    else method = 'andWhere';
+    let str;
+    let params;
+    // 需要给变量位置重命名,否则多个条件叠加后,都会使用第一个参数
+    const valueStr = `value${i}`;
+    let valueArr = [];
+    const strArr = [];
+    switch (opera) {
+      case Opera.Between:
+        str = `"${key}" Between :${valueStr}_1 AND :${valueStr}_2`;
+        params = { [`${valueStr}_1`]: head(value), [`${valueStr}_2`]: last(value) };
+        break;
+      case Opera.Not:
+        str = `"${key}" != :${valueStr}`;
+        params = { [`${valueStr}`]: value };
+        break;
+      case Opera.Like:
+        str = `"${key}" Like :${valueStr}`;
+        params = { [`${valueStr}`]: `%${value}%` };
+        break;
+      case Opera.LikeLeft:
+        str = `"${key}" Like :${valueStr}`;
+        params = { [`${valueStr}`]: `%${value}` };
+        break;
+      case Opera.LikeRight:
+        str = `"${key}" Like :${valueStr}`;
+        params = { [`${valueStr}`]: `${value}%` };
+        break;
+      case Opera.ILike:
+        str = `"${key}" Not Like :${valueStr}`;
+        params = { [`${valueStr}`]: `%${value}%` };
+        break;
+      case Opera.ILikeLeft:
+        str = `"${key}" Not Like :${valueStr}`;
+        params = { [`${valueStr}`]: `%${value}` };
+        break;
+      case Opera.ILikeRight:
+        str = `"${key}" Not Like :${valueStr}`;
+        params = { [`${valueStr}`]: `${value}%` };
+        break;
+      case Opera.LessThan:
+        str = `"${key}" < :${valueStr}`;
+        params = { [`${valueStr}`]: value };
+        break;
+      case Opera.LessThanOrEqual:
+        str = `"${key}" <= :${valueStr}`;
+        params = { [`${valueStr}`]: value };
+        break;
+      case Opera.MoreThan:
+        str = `"${key}" > :${valueStr}`;
+        params = { [`${valueStr}`]: value };
+        break;
+      case Opera.MoreThanOrEqual:
+        str = `"${key}" >= :${valueStr}`;
+        params = { [`${valueStr}`]: value };
+        break;
+      case Opera.In:
+        if (!isArray(value)) str = `"${key}" IN (:${valueStr})`;
+        else str = `"${key}" IN (:...${valueStr})`;
+        params = { [`${valueStr}`]: value };
+        break;
+      case Opera.IsNull:
+        str = `"${key}" IS NULL`;
+        break;
+      case Opera.IsNotNull:
+        str = `"${key}" IS NOT NULL`;
+        params = { [`${valueStr}`]: value };
+        break;
+      case Opera.Json:
+        params = {};
+        if (isArray(value)) valueArr = value;
+        else valueArr = [value];
+        for (let vi = 0; vi < valueArr.length; vi++) {
+          const v = valueArr[vi];
+          const mvalKey = `${valueStr}${vi}`;
+          const mstr = `JSONB_EXISTS("${key}", :${mvalKey})`;
+          strArr.push(mstr);
+          params[mvalKey] = v;
+        }
+        str = `(${strArr.join(' OR ')})`;
+        break;
+      case Opera.JsonObject:
+        const jokeys = key.split('.');
+        const jorootCol = head(jokeys);
+        const jolastKey = last(jokeys);
+        const jopath = jokeys.filter(f => f !== jorootCol && f !== jolastKey);
+        str = `"${jorootCol}" `;
+        for (const jok of jopath) {
+          str = `${str} -> ${jok}`;
+        }
+        str = `${str} ->> '${jolastKey}' = :${valueStr}`;
+        params = { [`${valueStr}`]: value };
+        break;
+      case Opera.JsonArrayObject:
+        /**
+         * 1.分割key,过来的属性默认以 x.y.z... 形式
+         * x:根子段;后面,数组中依次往下的属性名
+         */
+        const keys = key.split('.');
+        let numberValue;
+        if (isFinite(parseInt(value))) numberValue = parseInt(value);
+        let rootCol = head(keys);
+        let lastKey = last(keys);
+        let path = keys.filter(f => f !== rootCol && f !== lastKey);
+        const getObject = (path, lastKey, value) => {
+          let obj = {};
+          let mid = obj;
+          for (const k of path) {
+            mid[k] = {};
+            mid = mid[k];
+          }
+          mid[lastKey] = value;
+          return obj;
+        };
+        const obj = getObject(path, lastKey, value);
+        let newVal = JSON.stringify([obj]);
+        str = `"${rootCol}" @> :${valueStr}`;
+        params = { [`${valueStr}`]: newVal };
+        if (numberValue) {
+          const numObj = getObject(path, lastKey, numberValue);
+          let numVal = JSON.stringify([numObj]);
+          const valueStrNum = `${valueStr}Num`;
+          str = `(${str} OR "${rootCol}" @> :${valueStrNum})`;
+          params[valueStrNum] = numVal;
+        }
+        break;
+      case Opera.Equal:
+        str = `"${key}" = :${valueStr}`;
+        params = { [`${valueStr}`]: value };
+        break;
+      default:
+        let isString = false;
+        if (model) isString = columnIsString(key, model);
+        if (isString) {
+          // 字符串默认使用模糊查询
+          str = `"${key}" Like :${valueStr}`;
+          params = { [`${valueStr}`]: `%${value}%` };
+        } else {
+          str = `"${key}" = :${valueStr}`;
+          params = { [`${valueStr}`]: value };
+        }
+        break;
+    }
+    if (!str) continue;
+    builder[method](str, params);
+  }
+};
+
+const columnIsString = (columnName: string, model?) => {
+  if (model) return false;
+  const columns = model.metadata.columns;
+  const colSetting = columns.find(f => get(f, 'propertyName') === columnName);
+  if (!colSetting) return false;
+  const type = get(colSetting, 'type');
+  if (!type) return false;
+  return type === 'character varying' || type === 'text';
+};

+ 368 - 0
src/service/asyncExport.service.ts

@@ -0,0 +1,368 @@
+import { Config, Inject, Provide } from '@midwayjs/core';
+import { Demand } from '../entity/platform/demand.entity';
+import { Admin } from '../entity/system/admin.entity';
+import { Dept } from '../entity/system/dept.entity';
+import { DictData } from '../entity/system/dictData.entity';
+import { DictType } from '../entity/system/dictType.entity';
+import { Menus } from '../entity/system/menus.entity';
+import { Message } from '../entity/system/message.entity';
+import { Region } from '../entity/system/region.entity';
+import { Role } from '../entity/system/role.entity';
+import { Tags } from '../entity/system/tags.entity';
+import { User } from '../entity/system/user.entity';
+import { UserMenus } from '../entity/system/userMenus.entity';
+import { ApplyCompany } from '../entity/users/applyCompany.entity';
+import { Association } from '../entity/users/association.entity';
+import { Cirelation } from '../entity/users/cirelation.entity';
+import { Company } from '../entity/users/company.entity';
+import { CompanyYear } from '../entity/users/companyYear.entity';
+import { Competition } from '../entity/users/competition.entity';
+import { ContactApply } from '../entity/users/contactApply.entity';
+import { Expert } from '../entity/users/expert.entity';
+import { Incubator } from '../entity/users/incubator.entity';
+import { IncubatorYear } from '../entity/users/incubatorYear.entity';
+import { Investment } from '../entity/users/investment.entity';
+import { School } from '../entity/users/school.entity';
+import { State } from '../entity/users/state.entity';
+import { Unit } from '../entity/users/unit.entity';
+import { Achievement } from '../entity/platform/achievement.entity';
+import { Collection } from '../entity/platform/collection.entity';
+import { Design } from '../entity/platform/design.entity';
+import { Directory } from '../entity/platform/directory.entity';
+import { Footplate } from '../entity/platform/footplate.entity';
+import { Friend } from '../entity/platform/friend.entity';
+import { Journal } from '../entity/platform/journal.entity';
+import { Match } from '../entity/platform/match.entity';
+import { MatchPath } from '../entity/platform/matchPath.entity';
+import { News } from '../entity/platform/news.entity';
+import { Notes } from '../entity/platform/notes.entity';
+import { Project } from '../entity/platform/project.entity';
+import { Score } from '../entity/platform/score.entity';
+import { Sector } from '../entity/platform/sector.entity';
+import { Sign } from '../entity/platform/sign.entity';
+import { Supply } from '../entity/platform/supply.entity';
+import { Support } from '../entity/platform/support.entity';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { ExportMission } from '../entity/exportMission.entity';
+import { divide, get, isFinite, isObject, isString, upperFirst } from 'lodash';
+import { ExportConfigService } from './exportConfig.service';
+import { completeBuilderCondition } from '../frame/conditionBuilder';
+import * as dayjs from 'dayjs';
+import * as Excel from 'exceljs';
+import * as Path from 'path';
+import * as fs from 'fs';
+import { ErrorCode, ServiceError } from '../error/service.error';
+import { ExportConfig } from '../entity/exportConfig.entity';
+
+@Provide()
+export class AsyncExportService {
+  @InjectEntityModel(ExportMission)
+  model: Repository<ExportMission>;
+  @InjectEntityModel(ExportConfig)
+  ecService: Repository<ExportConfig>;
+  //数据步进值
+  limit = 50;
+
+  async checkHaveDataInQuery(table, query = {}) {
+    const model: any = get(this, `_model_${table}`);
+    // 抛出异常: 该数据未经过导出设置,无法导出
+    if (!model) throw new ServiceError(ErrorCode.NO_EXPORT_SETTING);
+    const builder = model.createQueryBuilder();
+    completeBuilderCondition(builder, query);
+    const total = await builder.getCount();
+    // 抛出异常: 未查询到可导出的数据
+    if (total <= 0) throw new ServiceError(ErrorCode.NO_DATA_IN_EXPORT_QUERY);
+  }
+
+  async execute(id) {
+    const mission = await this.model.createQueryBuilder().where(`"id" = :id`, { id }).getOne();
+    // 没有任务,不需要执行
+    if (!mission) return false;
+    // 没有配置,不需要执行
+    const config = get(mission, 'config');
+    if (!config) return;
+    /**表名 */
+    let table: string = get(config, 'table');
+    // 没有表,不需要执行
+    if (!table) return;
+    table = upperFirst(table);
+    /**用户选择的字段 */
+    const cc: Array<string> = get(config, 'config');
+    // 没有导出配置,不需要执行
+    if (!cc) return;
+    /**根据表名找到完整的配置 */
+    const configData = await this.ecService.createQueryBuilder().where(`"table" =:table`, { table }).getOne();
+    // 需要通过cc换正常的配置
+    // 未找到配置,不需要执行
+    if (!configData) return;
+    const colConfigs: Array<any> = get(configData, 'config');
+    if (!colConfigs) return;
+    /**任务存储的导出字段完整配置 */
+    const selectCols = colConfigs.filter(f => cc.includes(f.column));
+    // 计算数据总量
+    /**查询范围 */
+    const query = get(config, 'query', {});
+    const model: any = get(this, `_model_${table}`);
+    // 没找到model,不需要执行
+    if (!model) return;
+    const builder = model.createQueryBuilder();
+    completeBuilderCondition(builder, query);
+    /**数据总数,计算进度用 */
+    const total = await builder.getCount();
+    // 找数据,处理数据,写入数据
+    let skip = 0;
+    let fileName = `${table}-${dayjs().format('YYYYMMDDHHmmss')}.xlsx`;
+    let downloadPath;
+    /**循环标志 */
+    let whileContinue = true;
+    const head = selectCols.map(i => i.zh);
+    while (whileContinue) {
+      const writeInExcel = [];
+      if (skip === 0) writeInExcel.push(head);
+      const list = await builder.skip(skip).take(this.limit).getMany();
+      if (list.length <= 0) {
+        // 没有数据就不需要继续循环了
+        whileContinue = false;
+        break;
+      }
+      skip = skip + this.limit;
+      /**处理数据 */
+      const excelData = await this.dealData(list, selectCols);
+      writeInExcel.push(...excelData);
+
+      /**写入数据 */
+      downloadPath = await this.writeExcel(fileName, writeInExcel);
+      try {
+        // 计算,更新进度
+        let per = Math.ceil(divide(skip, total) * 100);
+        if (per > 100) per = 100;
+        this.model.update({ id }, { progress: per });
+      } catch (error) {
+        console.log(`mission id:${id}; skip:${skip}`);
+      }
+    }
+    await this.model.update({ id }, { progress: 100, uri: downloadPath });
+  }
+
+  async dealData(list, cols) {
+    const returnData = [];
+    for (const i of list) {
+      const excelData = [];
+      for (const c of cols) {
+        const column = get(c, 'column');
+        // 取值
+        let value = get(i, column);
+        // 空值下一个
+        if (!value || value === null) {
+          // 空字符串站位,否则列对不上
+          excelData.push('');
+          continue;
+        }
+        // 数据类型验证
+        const type = get(c, 'type');
+        value = this.checkValueType(value, type);
+        // 验证失败或空字符串,直接下一个
+        if (value === '') {
+          excelData.push(value);
+          continue;
+        }
+        const source = get(c, 'source');
+        const from = get(c, 'from');
+        const from_column = get(c, 'from_column');
+        value = await this.getValueFromSource(value, source, from, from_column);
+        excelData.push(value);
+      }
+      returnData.push(excelData);
+    }
+    return returnData;
+  }
+  /**
+   * 验证数据是否匹配数据类型,不匹配的返回空字符串
+   * @param value 数据库中的值
+   * @param type 数据类型
+   * @returns 空字符串 ''
+   */
+  checkValueType(value, type) {
+    let returnData = '';
+    switch (type) {
+      case 'string':
+        if (isString(value)) returnData = value;
+        break;
+      case 'integer':
+        if (isFinite(value)) returnData = value;
+        break;
+      case 'text':
+        if (isString(value)) returnData = value;
+        break;
+      case 'date':
+        if (dayjs(value).isValid()) returnData = value;
+        break;
+      case 'jsonb':
+        if (isObject(value)) returnData = JSON.stringify(value);
+        break;
+      default:
+        break;
+    }
+    return returnData;
+  }
+
+  /**
+   * 根据来源,转换值
+   * @param value 数据库中的值
+   * @param source 数据来源
+   * @param from 字典/关联表
+   * @param from_column 具体使用的字段
+   */
+  async getValueFromSource(value, source, from, from_column) {
+    if (source === 'dict') {
+      // 查出字典数据
+      const dictData = await this._model_DictData.createQueryBuilder().where('"code" =:code', { code: from }).getMany();
+      // 没有字典数据,直接返回原值
+      if (dictData.length <= 0) return value;
+      let dict_column = from_column;
+      if (!dict_column) dict_column = 'value';
+      const dict = dictData.find(f => get(f, dict_column) === value);
+      // 没找到,返回原值
+      if (!dict) return value;
+      // 找到了,返回label
+      return get(dict, 'label');
+    } else if (source === 'relation') {
+      const table = upperFirst(from);
+      const model = this[`_model_${table}`];
+      if (!model) return value;
+      const relationData = await model.createQueryBuilder().where(`"id" =:id`, { id: value }).getOne();
+      // 没找到关联的数据
+      if (!relationData) return value;
+      // 找到了,就取指定字段
+      return get(relationData, from_column);
+    } else return value;
+  }
+
+  @Config('PathConfig.path')
+  path;
+
+  async writeExcel(fileName, data) {
+    const path = this.path;
+    if (!path) {
+      throw new ServiceError('服务端没有设置存储路径');
+    }
+    if (!fs.existsSync(path)) {
+      // 如果不存在文件夹,就创建
+      this.mkdir(path);
+    }
+    const fullPath = Path.resolve(path, fileName);
+    const has_file = fs.existsSync(fullPath);
+    let workbook = new Excel.Workbook();
+    let sheet;
+    if (!has_file) {
+      // 没有文件,第一次,需要生成
+      sheet = workbook.addWorksheet('sheet');
+    } else {
+      await workbook.xlsx.readFile(fullPath);
+      sheet = workbook.getWorksheet('sheet');
+    }
+    sheet.addRows(data);
+    await workbook.xlsx.writeFile(fullPath);
+    return `/files/cxyy/export/${fileName}`;
+  }
+
+  // 创建文件夹
+  mkdir(dirname) {
+    if (fs.existsSync(dirname)) {
+      return true;
+    }
+    if (this.mkdir(Path.dirname(dirname))) {
+      fs.mkdirSync(dirname);
+      return true;
+    }
+  }
+
+  @InjectEntityModel(Admin)
+  _model_Admin: Repository<Admin>;
+  @InjectEntityModel(Dept)
+  _model_Dept: Repository<Dept>;
+  @InjectEntityModel(DictData)
+  _model_DictData: Repository<DictData>;
+  @InjectEntityModel(DictType)
+  _model_DictType: Repository<DictType>;
+  @InjectEntityModel(Menus)
+  _model_Menus: Repository<Menus>;
+  @InjectEntityModel(Message)
+  _model_Message: Repository<Message>;
+  @InjectEntityModel(Region)
+  _model_Region: Repository<Region>;
+  @InjectEntityModel(Role)
+  _model_Role: Repository<Role>;
+  @InjectEntityModel(Tags)
+  _model_Tags: Repository<Tags>;
+  @InjectEntityModel(User)
+  _model_User: Repository<User>;
+  @InjectEntityModel(UserMenus)
+  _model_UserMenus: Repository<UserMenus>;
+  @InjectEntityModel(ApplyCompany)
+  _model_ApplyCompany: Repository<ApplyCompany>;
+  @InjectEntityModel(Association)
+  _model_Association: Repository<Association>;
+  @InjectEntityModel(Cirelation)
+  _model_Cirelation: Repository<Cirelation>;
+  @InjectEntityModel(Company)
+  _model_Company: Repository<Company>;
+  @InjectEntityModel(CompanyYear)
+  _model_CompanyYear: Repository<CompanyYear>;
+  @InjectEntityModel(Competition)
+  _model_Competition: Repository<Competition>;
+  @InjectEntityModel(ContactApply)
+  _model_ContactApply: Repository<ContactApply>;
+  @InjectEntityModel(Expert)
+  _model_Expert: Repository<Expert>;
+  @InjectEntityModel(Incubator)
+  _model_Incubator: Repository<Incubator>;
+  @InjectEntityModel(IncubatorYear)
+  _model_IncubatorYear: Repository<IncubatorYear>;
+
+  @InjectEntityModel(Investment)
+  _model_Investment: Repository<Investment>;
+  @InjectEntityModel(School)
+  _model_School: Repository<School>;
+  @InjectEntityModel(State)
+  _model_State: Repository<State>;
+  @InjectEntityModel(Unit)
+  _model_Unit: Repository<Unit>;
+  @InjectEntityModel(Achievement)
+  _model_Achievement: Repository<Achievement>;
+  @InjectEntityModel(Collection)
+  _model_Collection: Repository<Collection>;
+  @InjectEntityModel(Demand)
+  _model_Demand: Repository<Demand>;
+  @InjectEntityModel(Design)
+  _model_Design: Repository<Design>;
+  @InjectEntityModel(Directory)
+  _model_Directory: Repository<Directory>;
+  @InjectEntityModel(Footplate)
+  _model_Footplate: Repository<Footplate>;
+  @InjectEntityModel(Friend)
+  _model_Friend: Repository<Friend>;
+  @InjectEntityModel(Journal)
+  _model_Journal: Repository<Journal>;
+  @InjectEntityModel(Match)
+  _model_Match: Repository<Match>;
+  @InjectEntityModel(MatchPath)
+  _model_MatchPath: Repository<MatchPath>;
+  @InjectEntityModel(News)
+  _model_News: Repository<News>;
+  @InjectEntityModel(Notes)
+  _model_Notes: Repository<Notes>;
+  @InjectEntityModel(Project)
+  _model_Project: Repository<Project>;
+  @InjectEntityModel(Score)
+  _model_Score: Repository<Score>;
+  @InjectEntityModel(Sector)
+  _model_Sector: Repository<Sector>;
+  @InjectEntityModel(Sign)
+  _model_Sign: Repository<Sign>;
+  @InjectEntityModel(Supply)
+  _model_Supply: Repository<Supply>;
+  @InjectEntityModel(Support)
+  _model_Support: Repository<Support>;
+}

+ 131 - 0
src/service/exportConfig.service.ts

@@ -0,0 +1,131 @@
+import { InjectDataSource, InjectEntityModel } from '@midwayjs/typeorm';
+import { DataSource, Repository } from 'typeorm';
+import { Inject, Provide } from '@midwayjs/core';
+import { ExportConfig } from '../entity/exportConfig.entity';
+import { cloneDeep, get } from 'lodash';
+import * as fs from 'fs';
+import * as path from 'path';
+import { AsyncExportService } from './asyncExport.service';
+@Provide()
+export class ExportConfigService {
+  @InjectEntityModel(ExportConfig)
+  model: Repository<ExportConfig>;
+  @Inject()
+  aeService: AsyncExportService;
+  /**创建 */
+  async create(data: object) {
+    // 设置 创建数据的人
+    const result = await this.model.insert(data);
+    const id = get(result, 'identifiers.0.id');
+    // 没有id估计是出错了
+    if (!id) return;
+    const createData = await this.fetch({ id });
+    // 没有查出数据,也是有问题
+    if (!createData) return;
+    return createData;
+  }
+  async fetch(query: object) {
+    const builder = this.model.createQueryBuilder();
+    if (get(query, 'id')) builder.where(`"id"=:id`, { id: get(query, 'id') });
+    else if (get(query, 'table')) builder.where(`"table"=:table`, { table: get(query, 'table') });
+    const result = await builder.getOne();
+    return result;
+  }
+
+  /**修改,单修改/多修改是统一修改为 */
+  async update(query: object, data: object) {
+    const result = await this.model.update(query, data);
+    return result;
+  }
+
+  /**删除,单删多删都行 */
+  async delete(query: object) {
+    const result = await this.model.delete(query);
+    return result;
+  }
+
+  /**
+   * 导出设置字典
+   */
+  getDict_type() {
+    const typeList = [
+      { label: '字符串', value: 'string', dbType: 'character varying' },
+      { label: '数字', value: 'integer', dbType: 'integer' },
+      { label: '时间', value: 'date', dbType: 'timestamp without time zone' },
+      { label: '文本', value: 'text', dbType: 'text' },
+      { label: 'json', value: 'jsonb', dbType: 'jsonb' },
+    ];
+    return typeList;
+  }
+
+  getDict_source() {
+    const sourceList = [
+      { label: '填写', value: 'write' },
+      { label: '字典', value: 'dict' },
+      { label: '关联表', value: 'relation' },
+    ];
+    return sourceList;
+  }
+  getDict_tables() {
+    const keys = Object.keys(this.aeService).filter(f => f.includes('_model_'));
+    const tableList = keys.map(i => {
+      const model = this[i];
+      const label = get(model, 'metadata.comment');
+      const table = get(model, 'metadata.name');
+      return { label, table };
+    });
+    return tableList;
+  }
+  getModelColumns(modelName, filters = []) {
+    if (!modelName.includes('_model_')) {
+      modelName = `_model_${modelName}`;
+    }
+    const model = this.aeService[modelName];
+    // model
+    const typeList = this.getDict_type();
+    const column = model.metadata.columns;
+    const ncols = [];
+    for (const i of column) {
+      const table = get(i, 'propertyName');
+      const inFilter = filters.find(f => f === table);
+      if (inFilter) continue;
+      let type = get(i, 'type');
+      const useType = typeList.find(f => f.dbType === type);
+      if (useType && get(useType, 'value')) type = get(useType, 'value');
+      const zh = get(i, 'comment');
+      ncols.push({ type, column: table, zh });
+    }
+    return ncols;
+  }
+
+  async initData() {
+    const p = path.resolve(__dirname, '../../importData', 'exportConfig.json');
+    const file = fs.readFileSync(p, { encoding: 'utf8' });
+    if (!file) return;
+    await this.clearTable('exportConfig');
+    let list = JSON.parse(file);
+    list = list.map(i => {
+      const obj = cloneDeep(i);
+      obj.config = obj.config.map(c => {
+        const co = cloneDeep(c);
+        co.is_use = '0';
+        const source = get(co, 'source');
+        if (!source) co.source = 'write';
+        return co;
+      });
+      return obj;
+    });
+    await this.model.insert(list);
+  }
+
+  @InjectDataSource('default')
+  defaultDataSource: DataSource;
+  /**
+   * 清空表与id序列
+   * @param tableName 表名 小驼峰
+   */
+  private async clearTable(tableName: string) {
+    await this.defaultDataSource.query(`TRUNCATE TABLE "public"."${tableName}" RESTART IDENTITY RESTRICT;`);
+    await this.defaultDataSource.query(`SELECT setval('"${tableName}_id_seq"', (SELECT max(id) FROM "${tableName}"));`);
+  }
+}

+ 29 - 0
src/service/exportMission.service.ts

@@ -0,0 +1,29 @@
+import { Config, Provide } from '@midwayjs/core';
+import { InjectEntityModel } from '@midwayjs/typeorm';
+import { Repository } from 'typeorm';
+import { ExportMission } from '../entity/exportMission.entity';
+import { BaseServiceV2 } from '../frame/BaseServiceV2';
+import Axios from 'axios';
+
+@Provide()
+export class ExportMissionService extends BaseServiceV2 {
+  @InjectEntityModel(ExportMission)
+  model: Repository<ExportMission>;
+
+  @Config('modulesConfig.mq')
+  mqServiceHttpPrefix: any;
+
+  /**
+   * 发送mq消息
+   * @param id 任务id
+   */
+  async toSendMq(id) {
+    const url = `${this.mqServiceHttpPrefix}/mission`;
+    const body = { id };
+    try {
+      await Axios.post(url, body, { responseType: 'json' });
+    } catch (error) {
+      console.log(error);
+    }
+  }
+}

+ 8 - 0
src/service/initData/initSystemData.service.ts

@@ -395,6 +395,14 @@ export class InitSystemDataService {
             is_use: '0',
             route_name: 'system_region',
           },
+          {
+            name: '导出设置',
+            component: 'export-config',
+            type: '2',
+            is_default: '0',
+            is_use: '0',
+            route_name: 'system_export_config',
+          },
           {
             name: '系统功能重置',
             component: 'system-func',

+ 2 - 1
src/service/platform/match.service.ts

@@ -8,6 +8,7 @@ import { MatchPathService } from './matchPath.service';
 import dayjs = require('dayjs');
 import { ScoreService } from './score.service';
 import { SignService } from './sign.service';
+import { completeBuilderCondition } from '../../frame/conditionBuilder';
 @Provide()
 export class MatchService extends BaseServiceV2 {
   @InjectEntityModel(Match)
@@ -34,7 +35,7 @@ export class MatchService extends BaseServiceV2 {
   // 赛事进行中第一部创建报名分数
   async updateProject({ id }, data) {
     const builder = this.model.createQueryBuilder();
-    this.completeBuilderCondition({ id });
+    completeBuilderCondition({ id });
     const match = await builder.getOne();
     if (match && match.match_status === '2') {
       // 如果赛事改为进行中将赛事流程按排序进行查询