@gadmin2n/schematics 0.0.63 → 0.0.65

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.
Files changed (223) hide show
  1. package/dist/lib/application/files/gadmin2-game-angle-demo/.dockerignore +2 -2
  2. package/dist/lib/application/files/gadmin2-game-angle-demo/Dockerfile +40 -26
  3. package/dist/lib/application/files/gadmin2-game-angle-demo/Jenkinsfile +30 -4
  4. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/example.prisma +33 -0
  5. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/system.prisma +163 -0
  6. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Event.ts +70 -0
  7. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Game.ts +6 -6
  8. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/ITActivityDay.ts +70 -0
  9. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Log.ts +2 -2
  10. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Role.ts +2 -2
  11. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env +20 -9
  12. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.env.local +1 -0
  13. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.eslintrc.js +1 -0
  14. package/dist/lib/application/files/gadmin2-game-angle-demo/server/.prettierignore +1 -0
  15. package/dist/lib/application/files/gadmin2-game-angle-demo/server/README.md +2 -18
  16. package/dist/lib/application/files/gadmin2-game-angle-demo/server/gadmin-cli.json +1 -1
  17. package/dist/lib/application/files/gadmin2-game-angle-demo/server/migrate-between-pg-schemas.js +1232 -0
  18. package/dist/lib/application/files/gadmin2-game-angle-demo/server/package.json +65 -38
  19. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma/.generator.prisma +4 -3
  20. package/dist/lib/application/files/gadmin2-game-angle-demo/server/prisma.config.ts +16 -0
  21. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/games.ts +1 -71
  22. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/index.ts +17 -21
  23. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/permissions.ts +278 -0
  24. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/seedDataMngtPages.ts +258 -0
  25. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/users.ts +7 -0
  26. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/alias.config.ts +7 -0
  27. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.controller.ts +151 -11
  28. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.module.ts +29 -13
  29. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/app.service.ts +151 -12
  30. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/auth.guard.ts +87 -41
  31. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/http-cache.interceptor.ts +21 -0
  32. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/logger.ts +19 -0
  33. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/safe-log.util.ts +176 -0
  34. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/{yufuid.ts → taihu.ts} +49 -34
  35. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/tracing.ts +174 -0
  36. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/trim.pipe.ts +51 -0
  37. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/utils.ts +91 -0
  38. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/lib/woaAuth.ts +25 -12
  39. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/main.ts +22 -12
  40. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.spec.ts +20 -0
  41. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.controller.ts +190 -0
  42. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.module.ts +10 -0
  43. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.spec.ts +338 -0
  44. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/audit/audit.service.ts +83 -0
  45. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.spec.ts +20 -0
  46. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.controller.ts +188 -0
  47. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.module.ts +10 -0
  48. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.spec.ts +18 -0
  49. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/game/game.service.ts +83 -0
  50. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.spec.ts +20 -0
  51. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.controller.ts +250 -0
  52. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.module.ts +10 -0
  53. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.spec.ts +18 -0
  54. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/page/page.service.ts +1051 -0
  55. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.spec.ts +20 -0
  56. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.controller.ts +196 -0
  57. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.module.ts +13 -0
  58. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.spec.ts +18 -0
  59. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/pageResource/pageResource.service.ts +219 -0
  60. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.spec.ts +20 -0
  61. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.controller.ts +196 -0
  62. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.module.ts +10 -0
  63. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.spec.ts +18 -0
  64. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/resource/resource.service.ts +199 -0
  65. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.spec.ts +20 -0
  66. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.controller.ts +210 -0
  67. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.module.ts +12 -0
  68. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.spec.ts +18 -0
  69. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/role.service.ts +849 -0
  70. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/role/roles-refresher.service.ts +133 -0
  71. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.spec.ts +20 -0
  72. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.controller.ts +196 -0
  73. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.module.ts +10 -0
  74. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.spec.ts +18 -0
  75. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/rolePages/rolePages.service.ts +201 -0
  76. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.spec.ts +20 -0
  77. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.controller.ts +196 -0
  78. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.module.ts +10 -0
  79. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.spec.ts +18 -0
  80. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/roleResource/roleResource.service.ts +216 -0
  81. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.spec.ts +20 -0
  82. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.controller.ts +198 -0
  83. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.module.ts +10 -0
  84. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.spec.ts +18 -0
  85. package/dist/lib/application/files/gadmin2-game-angle-demo/server/src/modules/user/user.service.ts +104 -0
  86. package/dist/lib/application/files/gadmin2-game-angle-demo/server/start-prod.sh +130 -0
  87. package/dist/lib/application/files/gadmin2-game-angle-demo/server/tsconfig.json +18 -3
  88. package/dist/lib/application/files/gadmin2-game-angle-demo/web/index.html +19 -0
  89. package/dist/lib/application/files/gadmin2-game-angle-demo/web/package.json +34 -42
  90. package/dist/lib/application/files/gadmin2-game-angle-demo/web/postcss.config.cjs +6 -0
  91. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/App.tsx +111 -185
  92. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/auditLogProvider.ts +5 -5
  93. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/contexts/color-mode/index.tsx +49 -51
  94. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/custom-avatar.tsx +38 -0
  95. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/index.ts +4 -0
  96. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/{header/index.tsx → header.tsx} +22 -31
  97. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/index.ts +3 -1
  98. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/layout.tsx +32 -0
  99. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/logo.tsx +19 -0
  100. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/sider.tsx +331 -166
  101. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/title.tsx +61 -0
  102. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/pagination-total.tsx +21 -0
  103. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/tags/index.ts +1 -0
  104. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/tags/role-tag.tsx +44 -0
  105. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/text.tsx +74 -0
  106. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/config/routeRegistry.tsx +258 -0
  107. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/constants/layout.ts +16 -0
  108. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/enums/audit-log.enum.ts +13 -0
  109. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/enums/index.ts +1 -0
  110. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/form.tsx +2 -0
  111. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/helpers/login.ts +22 -4
  112. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useDynamicResources.tsx +211 -0
  113. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useFetchData.ts +33 -0
  114. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useRoles.ts +30 -0
  115. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/hooks/useUserPageAccess.ts +339 -0
  116. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/i18n.ts +8 -4
  117. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/index.tsx +3 -11
  118. package/dist/lib/application/files/gadmin2-game-angle-demo/web/{public → src}/locales/en/common.json +1 -1
  119. package/dist/lib/application/files/gadmin2-game-angle-demo/web/{public → src}/locales/zh_CN/common.json +1 -1
  120. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.css +3 -0
  121. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/components/action-cell.tsx +134 -0
  122. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/create.tsx +113 -0
  123. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/edit.tsx +122 -0
  124. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.ts +8 -0
  125. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/index.tsx +6 -0
  126. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/list.tsx +213 -0
  127. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/audit/show.tsx +61 -0
  128. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/AssignRolesModal.tsx +168 -0
  129. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/CreatePageModal.tsx +42 -0
  130. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/EditPageModal.tsx +42 -0
  131. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageDetailDrawer.tsx +101 -0
  132. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/Components/PageFormModal.tsx +731 -0
  133. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/create.tsx +113 -0
  134. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/edit.tsx +122 -0
  135. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/hooks/usePageManagement.ts +36 -0
  136. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.ts +8 -0
  137. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/index.tsx +6 -0
  138. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/list.tsx +1113 -0
  139. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/queries.ts +17 -0
  140. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/show.tsx +61 -0
  141. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/page/types.ts +44 -0
  142. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/create.tsx +113 -0
  143. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/edit.tsx +122 -0
  144. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/index.tsx +6 -0
  145. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/list.tsx +243 -0
  146. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/pageResource/show.tsx +61 -0
  147. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/permission-readme/index.tsx +1088 -0
  148. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/CreateModal.tsx +25 -0
  149. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/EditModal.tsx +28 -0
  150. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/ResourceDetailDrawer.tsx +160 -0
  151. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/Components/modal.tsx +202 -0
  152. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/create.tsx +113 -0
  153. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/edit.tsx +122 -0
  154. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.ts +9 -0
  155. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/index.tsx +6 -0
  156. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/list.tsx +184 -0
  157. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/queries.ts +10 -0
  158. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/show.tsx +61 -0
  159. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/resource/types.ts +9 -0
  160. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/CreateModal.tsx +30 -0
  161. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/EditModal.tsx +47 -0
  162. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/RoleDetailDrawer.tsx +56 -0
  163. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/Components/modal.tsx +302 -0
  164. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/create.tsx +113 -0
  165. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/edit.tsx +122 -0
  166. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/hooks/useRolePage.ts +35 -0
  167. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.ts +8 -0
  168. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/index.tsx +6 -0
  169. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/list.tsx +382 -0
  170. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/queries.ts +8 -0
  171. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/show.tsx +61 -0
  172. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/role/types.ts +8 -0
  173. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/create.tsx +113 -0
  174. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/edit.tsx +122 -0
  175. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/index.tsx +6 -0
  176. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/list.tsx +243 -0
  177. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/rolePages/show.tsx +61 -0
  178. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/create.tsx +113 -0
  179. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/edit.tsx +122 -0
  180. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/index.tsx +6 -0
  181. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/list.tsx +243 -0
  182. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/roleResource/show.tsx +61 -0
  183. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/create-modal.tsx +17 -0
  184. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/edit-modal.tsx +19 -0
  185. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/form-modal.tsx +188 -0
  186. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/index.ts +5 -0
  187. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/role-tag.tsx +48 -0
  188. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/components/show-drawer.tsx +140 -0
  189. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/create.tsx +113 -0
  190. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/edit.tsx +122 -0
  191. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.ts +8 -0
  192. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/index.tsx +6 -0
  193. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/list.tsx +342 -0
  194. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/queries.ts +14 -0
  195. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/routes/user/show.tsx +61 -0
  196. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.css +132 -0
  197. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/fc.css +58 -0
  198. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/index.css +128 -0
  199. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/show-drawer.module.css +18 -0
  200. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/show-page.module.css +21 -0
  201. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/audit-log.ts +1 -0
  202. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/index.ts +3 -0
  203. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/role.ts +7 -0
  204. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/types/user.ts +1 -0
  205. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/get-name-initials.ts +8 -0
  206. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/get-random-color.ts +27 -0
  207. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/index.ts +2 -0
  208. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/utilities/utils.tsx +5 -0
  209. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/vite-env.d.ts +1 -0
  210. package/dist/lib/application/files/gadmin2-game-angle-demo/web/tsconfig.json +3 -3
  211. package/dist/lib/application/files/gadmin2-game-angle-demo/web/vite.config.ts +31 -0
  212. package/package.json +1 -1
  213. package/dist/lib/application/files/gadmin2-game-angle-demo/config/prisma/sample.prisma +0 -65
  214. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Source.ts +0 -76
  215. package/dist/lib/application/files/gadmin2-game-angle-demo/config/ui/Tasklog.ts +0 -76
  216. package/dist/lib/application/files/gadmin2-game-angle-demo/server/seed/roles.ts +0 -4
  217. package/dist/lib/application/files/gadmin2-game-angle-demo/web/craco.config.js +0 -27
  218. package/dist/lib/application/files/gadmin2-game-angle-demo/web/public/index.html +0 -53
  219. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/layout/styles.ts +0 -10
  220. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/react-app-env.d.ts +0 -1
  221. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/reportWebVitals.ts +0 -15
  222. package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/styles/antd.less +0 -79
  223. /package/dist/lib/application/files/gadmin2-game-angle-demo/web/src/components/VanillaJSONEditor/{index.js → index.jsx} +0 -0
@@ -0,0 +1,1232 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * PostgreSQL Schema 间数据迁移脚本
5
+ *
6
+ * 功能:
7
+ * - 迁移表数据(支持跨数据库和同数据库不同schema)
8
+ * - 迁移视图定义(自动处理schema引用和依赖关系)
9
+ *
10
+ * 使用方法:
11
+ * yarn add pg
12
+ * node migrate-between-pg-schemas.js
13
+ *
14
+ * 环境变量配置:
15
+ *
16
+ * 源数据库配置:
17
+ * PG_HOST - PostgreSQL 主机地址
18
+ * PG_PORT - PostgreSQL 端口 (默认: 5432)
19
+ * PG_USER - PostgreSQL 用户名
20
+ * PG_PASSWORD - PostgreSQL 密码
21
+ * PG_DATABASE - PostgreSQL 数据库名
22
+ * DATABASE_URL - 源数据库连接URL(可选,优先级高于单独配置)
23
+ * PG_SCHEMA - 源 schema 名称 (默认: public)
24
+ *
25
+ * 目标数据库配置(可选,如果不配置则在同一数据库的不同schema间迁移):
26
+ * PG_HOST_TARGET - 目标 PostgreSQL 主机地址
27
+ * PG_PORT_TARGET - 目标 PostgreSQL 端口 (默认: 5432)
28
+ * PG_USER_TARGET - 目标 PostgreSQL 用户名
29
+ * PG_PASSWORD_TARGET - 目标 PostgreSQL 密码
30
+ * PG_DATABASE_TARGET - 目标 PostgreSQL 数据库名
31
+ * DATABASE_URL_TARGET - 目标数据库连接URL(可选,优先级高于单独配置)
32
+ * PG_SCHEMA_TARGET - 目标 schema 名称 (必填)
33
+ */
34
+
35
+ const { Pool } = require('pg');
36
+
37
+ // 批量插入大小
38
+ const BATCH_SIZE = 1000;
39
+ const MAX_ROWS_TO_MIGRATE = 5000;
40
+
41
+ // 解析源数据库配置
42
+ let sourcePgConfig = null;
43
+ let sourceSchema = process.env.PG_SCHEMA || 'public';
44
+
45
+ if (process.env.DATABASE_URL) {
46
+ const url = new URL(process.env.DATABASE_URL);
47
+ sourcePgConfig = {
48
+ host: url.hostname,
49
+ port: Number(url.port),
50
+ user: url.username,
51
+ password: url.password,
52
+ database: url.pathname.slice(1),
53
+ };
54
+ if (!process.env.PG_SCHEMA) {
55
+ sourceSchema = url.searchParams.get('schema') || 'public';
56
+ }
57
+ } else {
58
+ sourcePgConfig = {
59
+ host: process.env.PG_HOST || 'localhost',
60
+ port: Number(process.env.PG_PORT || 5432),
61
+ user: process.env.PG_USER || 'kavenma',
62
+ password: process.env.PG_PASSWORD || 'kavenma',
63
+ database: process.env.PG_DATABASE || 'kavenma',
64
+ };
65
+ }
66
+
67
+ // 解析目标数据库配置
68
+ let targetPgConfig = null;
69
+ let targetSchema = process.env.PG_SCHEMA_TARGET;
70
+ let isCrossDatabase = false; // 是否跨数据库迁移
71
+
72
+ // 检查是否配置了目标数据库
73
+ const hasTargetDatabaseConfig =
74
+ process.env.DATABASE_URL_TARGET ||
75
+ process.env.PG_HOST_TARGET ||
76
+ process.env.PG_DATABASE_TARGET;
77
+
78
+ if (hasTargetDatabaseConfig) {
79
+ // 配置了目标数据库,进行跨数据库迁移
80
+ isCrossDatabase = true;
81
+
82
+ if (process.env.DATABASE_URL_TARGET) {
83
+ const url = new URL(process.env.DATABASE_URL_TARGET);
84
+ targetPgConfig = {
85
+ host: url.hostname,
86
+ port: Number(url.port),
87
+ user: url.username,
88
+ password: url.password,
89
+ database: url.pathname.slice(1),
90
+ };
91
+ if (!process.env.PG_SCHEMA_TARGET) {
92
+ targetSchema = url.searchParams.get('schema') || 'public';
93
+ }
94
+ } else {
95
+ targetPgConfig = {
96
+ host: process.env.PG_HOST_TARGET || sourcePgConfig.host,
97
+ port: Number(process.env.PG_PORT_TARGET || process.env.PG_PORT || 5432),
98
+ user: process.env.PG_USER_TARGET || sourcePgConfig.user,
99
+ password: process.env.PG_PASSWORD_TARGET || sourcePgConfig.password,
100
+ database: process.env.PG_DATABASE_TARGET || sourcePgConfig.database,
101
+ };
102
+ }
103
+ } else {
104
+ // 未配置目标数据库,使用源数据库配置(同数据库不同schema迁移)
105
+ targetPgConfig = sourcePgConfig;
106
+ }
107
+
108
+ /**
109
+ * 获取源 schema 中所有表名
110
+ */
111
+ async function getSourceTables(pgPool) {
112
+ const result = await pgPool.query(
113
+ `SELECT table_name
114
+ FROM information_schema.tables
115
+ WHERE table_schema = $1
116
+ AND table_type = 'BASE TABLE'
117
+ ORDER BY table_name`,
118
+ [sourceSchema]
119
+ );
120
+ return result.rows.map(row => row.table_name);
121
+ }
122
+
123
+ /**
124
+ * 获取源 schema 中所有视图名
125
+ */
126
+ async function getSourceViews(pgPool) {
127
+ const result = await pgPool.query(
128
+ `SELECT table_name
129
+ FROM information_schema.views
130
+ WHERE table_schema = $1
131
+ ORDER BY table_name`,
132
+ [sourceSchema]
133
+ );
134
+ return result.rows.map(row => row.table_name);
135
+ }
136
+
137
+ /**
138
+ * 获取指定 schema 中的外键约束信息
139
+ * @param {Pool} pgPool - 数据库连接池
140
+ * @param {string} schema - schema 名称
141
+ */
142
+ async function getForeignKeyConstraints(pgPool, schema) {
143
+ const result = await pgPool.query(
144
+ `SELECT
145
+ tc.table_name,
146
+ ccu.table_name AS referenced_table_name
147
+ FROM information_schema.table_constraints AS tc
148
+ JOIN information_schema.key_column_usage AS kcu
149
+ ON tc.constraint_name = kcu.constraint_name
150
+ AND tc.table_schema = kcu.table_schema
151
+ JOIN information_schema.constraint_column_usage AS ccu
152
+ ON ccu.constraint_name = tc.constraint_name
153
+ AND ccu.table_schema = tc.table_schema
154
+ WHERE tc.constraint_type = 'FOREIGN KEY'
155
+ AND tc.table_schema = $1
156
+ AND ccu.table_schema = $1
157
+ GROUP BY tc.table_name, ccu.table_name`,
158
+ [schema]
159
+ );
160
+ return result.rows.map(row => ({
161
+ TABLE_NAME: row.table_name,
162
+ REFERENCED_TABLE_NAME: row.referenced_table_name
163
+ }));
164
+ }
165
+
166
+ /**
167
+ * 获取源 schema 表的列信息
168
+ */
169
+ async function getSourceTableColumns(pgPool, tableName) {
170
+ const result = await pgPool.query(
171
+ `SELECT column_name, data_type, is_nullable, column_default
172
+ FROM information_schema.columns
173
+ WHERE table_schema = $1 AND table_name = $2
174
+ ORDER BY ordinal_position`,
175
+ [sourceSchema, tableName]
176
+ );
177
+ return result.rows.map(row => ({
178
+ COLUMN_NAME: row.column_name,
179
+ DATA_TYPE: row.data_type,
180
+ IS_NULLABLE: row.is_nullable,
181
+ COLUMN_DEFAULT: row.column_default
182
+ }));
183
+ }
184
+
185
+ /**
186
+ * 获取目标 schema 表的列信息
187
+ */
188
+ async function getTargetTableColumns(targetPool, tableName) {
189
+ const result = await targetPool.query(
190
+ `SELECT column_name, data_type, is_nullable, column_default
191
+ FROM information_schema.columns
192
+ WHERE table_schema = $1 AND table_name = $2
193
+ ORDER BY ordinal_position`,
194
+ [targetSchema, tableName]
195
+ );
196
+ return result.rows;
197
+ }
198
+
199
+ /**
200
+ * 获取源 schema 表的总行数
201
+ */
202
+ async function getTableRowCount(pgPool, tableName) {
203
+ const result = await pgPool.query(
204
+ `SELECT COUNT(*) as count FROM ${escapeIdentifier(sourceSchema)}.${escapeIdentifier(tableName)}`
205
+ );
206
+ return parseInt(result.rows[0].count, 10);
207
+ }
208
+
209
+ /**
210
+ * 获取表的主键列名
211
+ * @returns {Array<string>} 主键列名数组,如果没有主键返回空数组
212
+ */
213
+ async function getPrimaryKeyColumns(pgPool, tableName) {
214
+ const result = await pgPool.query(
215
+ `SELECT column_name
216
+ FROM information_schema.key_column_usage
217
+ WHERE table_schema = $1
218
+ AND table_name = $2
219
+ AND constraint_name IN (
220
+ SELECT constraint_name
221
+ FROM information_schema.table_constraints
222
+ WHERE table_schema = $1
223
+ AND table_name = $2
224
+ AND constraint_type = 'PRIMARY KEY'
225
+ )
226
+ ORDER BY ordinal_position`,
227
+ [sourceSchema, tableName]
228
+ );
229
+ return result.rows.map(row => row.column_name);
230
+ }
231
+
232
+ /**
233
+ * 转义标识符(用于 SQL 查询)
234
+ */
235
+ function escapeIdentifier(identifier) {
236
+ return `"${identifier.replace(/"/g, '""')}"`;
237
+ }
238
+
239
+ /**
240
+ * 转换值(PostgreSQL 内部迁移,通常不需要特殊转换)
241
+ * @param {*} value - 要转换的值
242
+ * @param {string} sourceDataType - 源列的数据类型
243
+ * @param {string} targetDataType - 目标列的数据类型(可选)
244
+ */
245
+ function convertValue(value, sourceDataType, targetDataType) {
246
+ if (value === null || value === undefined) {
247
+ return null;
248
+ }
249
+
250
+ // 处理日期时间类型
251
+ if (value instanceof Date) {
252
+ return value;
253
+ }
254
+
255
+ // 确定目标数据类型(优先使用目标类型,如果没有则使用源类型)
256
+ const dataType = targetDataType || sourceDataType;
257
+
258
+ // 处理 JSON 类型
259
+ if (dataType === 'json' || dataType === 'jsonb') {
260
+ // 关键修复:对于 JSON/JSONB 字段,我们需要确保值被正确序列化
261
+ // PostgreSQL 的 pg 库在读取 JSONB 时会自动解析为对象,但插入时可能无法正确序列化
262
+ // 问题根源:pg 库在序列化 JavaScript 对象时,可能产生不符合 PostgreSQL 严格 JSON 要求的格式
263
+ // 解决方案:直接返回序列化后的 JSON 字符串,让 PostgreSQL 自己处理类型转换
264
+
265
+ // 如果值已经是对象或数组,序列化为 JSON 字符串
266
+ if (typeof value === 'object' && value !== null && !(value instanceof Date) && !(value instanceof Buffer)) {
267
+ try {
268
+ // 清理对象:移除 undefined 值,确保所有值都可以被 JSON.stringify 正确处理
269
+ const cleanedValue = cleanObjectForJSON(value);
270
+
271
+ // 序列化为 JSON 字符串
272
+ const serialized = JSON.stringify(cleanedValue);
273
+
274
+ // 验证序列化后的字符串可以重新解析(确保是有效的 JSON)
275
+ JSON.parse(serialized);
276
+
277
+ // 直接返回序列化后的字符串,而不是对象
278
+ // 这样 PostgreSQL 可以直接使用这个字符串,而不需要 pg 库再次序列化
279
+ return serialized;
280
+ } catch (e) {
281
+ // 如果无法序列化或解析,返回 null
282
+ return null;
283
+ }
284
+ }
285
+
286
+ // 如果值是字符串,验证是否为有效的 JSON
287
+ if (typeof value === 'string') {
288
+ // 空字符串或空白字符串,返回 null
289
+ const trimmed = value.trim();
290
+ if (trimmed === '') {
291
+ return null;
292
+ }
293
+
294
+ // 检查是否是有效的 JSON 字符串格式(快速检查)
295
+ // JSON 必须以 {, [, ", 数字, true, false, null 开头
296
+ const firstChar = trimmed[0];
297
+ if (!['{', '[', '"', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 't', 'f', 'n'].includes(firstChar)) {
298
+ return null;
299
+ }
300
+
301
+ try {
302
+ // 验证字符串是有效的 JSON(尝试解析)
303
+ const parsed = JSON.parse(trimmed);
304
+ // 验证可以重新序列化(确保格式正确)
305
+ JSON.stringify(parsed);
306
+ // 返回原始字符串(已经是有效的 JSON 字符串)
307
+ return trimmed;
308
+ } catch (e) {
309
+ // 如果解析失败,返回 null
310
+ return null;
311
+ }
312
+ }
313
+
314
+ // 其他类型(数字、布尔等),序列化为 JSON 字符串
315
+ if (typeof value === 'number' || typeof value === 'boolean') {
316
+ try {
317
+ // 直接序列化为 JSON 字符串
318
+ return JSON.stringify(value);
319
+ } catch (e) {
320
+ return null;
321
+ }
322
+ }
323
+
324
+ // 未知类型,返回 null
325
+ return null;
326
+ }
327
+
328
+ return value;
329
+ }
330
+
331
+ /**
332
+ * 清理对象,移除 undefined 值,确保可以正确序列化为 JSON
333
+ * @param {*} obj - 要清理的对象
334
+ */
335
+ function cleanObjectForJSON(obj) {
336
+ if (obj === null || obj === undefined) {
337
+ return null;
338
+ }
339
+
340
+ if (Array.isArray(obj)) {
341
+ return obj.map(item => cleanObjectForJSON(item));
342
+ }
343
+
344
+ if (typeof obj === 'object' && obj.constructor === Object) {
345
+ const cleaned = {};
346
+ for (const key in obj) {
347
+ if (obj.hasOwnProperty(key)) {
348
+ const value = obj[key];
349
+ // 跳过 undefined 值
350
+ if (value !== undefined) {
351
+ cleaned[key] = cleanObjectForJSON(value);
352
+ }
353
+ }
354
+ }
355
+ return cleaned;
356
+ }
357
+
358
+ // 对于其他类型(字符串、数字、布尔等),直接返回
359
+ return obj;
360
+ }
361
+
362
+ /**
363
+ * 拓扑排序:根据外键依赖关系确定表的迁移顺序
364
+ * @param {Array<string>} tables - 所有表名
365
+ * @param {Array} constraints - 外键约束信息
366
+ * @returns {Array<string>} 排序后的表名数组
367
+ */
368
+ function topologicalSort(tables, constraints) {
369
+ // 构建依赖图:table -> [依赖的表列表]
370
+ const dependencies = new Map();
371
+ const inDegree = new Map();
372
+
373
+ // 初始化
374
+ tables.forEach(table => {
375
+ dependencies.set(table, []);
376
+ inDegree.set(table, 0);
377
+ });
378
+
379
+ // 构建依赖关系
380
+ constraints.forEach(constraint => {
381
+ const table = constraint.TABLE_NAME;
382
+ const referencedTable = constraint.REFERENCED_TABLE_NAME;
383
+
384
+ // 只处理在表列表中的表
385
+ if (tables.includes(table) && tables.includes(referencedTable)) {
386
+ dependencies.get(referencedTable).push(table);
387
+ inDegree.set(table, (inDegree.get(table) || 0) + 1);
388
+ }
389
+ });
390
+
391
+ // 拓扑排序
392
+ const queue = [];
393
+ const result = [];
394
+
395
+ // 找到所有入度为 0 的节点(没有依赖的表)
396
+ inDegree.forEach((degree, table) => {
397
+ if (degree === 0) {
398
+ queue.push(table);
399
+ }
400
+ });
401
+
402
+ while (queue.length > 0) {
403
+ const current = queue.shift();
404
+ result.push(current);
405
+
406
+ // 减少依赖当前表的其他表的入度
407
+ dependencies.get(current).forEach(dependent => {
408
+ const newDegree = inDegree.get(dependent) - 1;
409
+ inDegree.set(dependent, newDegree);
410
+ if (newDegree === 0) {
411
+ queue.push(dependent);
412
+ }
413
+ });
414
+ }
415
+
416
+ // 如果存在循环依赖,将剩余的表添加到结果中
417
+ inDegree.forEach((degree, table) => {
418
+ if (degree > 0 && !result.includes(table)) {
419
+ console.log(` 警告: 表 ${table} 可能存在循环依赖,将在最后迁移`);
420
+ result.push(table);
421
+ }
422
+ });
423
+
424
+ return result;
425
+ }
426
+
427
+ /**
428
+ * 获取视图的定义
429
+ */
430
+ async function getViewDefinition(pgPool, viewName) {
431
+ const result = await pgPool.query(
432
+ `SELECT pg_get_viewdef($1::regclass, true) as definition`,
433
+ [`${sourceSchema}.${viewName}`]
434
+ );
435
+ return result.rows[0]?.definition || null;
436
+ }
437
+
438
+ /**
439
+ * 替换视图定义中的schema引用
440
+ * 将源schema的引用替换为目标schema
441
+ */
442
+ function replaceSchemaInViewDefinition(viewDefinition, sourceSchema, targetSchema) {
443
+ if (!viewDefinition) {
444
+ return viewDefinition;
445
+ }
446
+
447
+ // 转义schema名称用于正则表达式
448
+ const escapedSourceSchema = sourceSchema.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
449
+ const targetSchemaEscaped = escapeIdentifier(targetSchema);
450
+
451
+ let replaced = viewDefinition;
452
+
453
+ // 替换 "schema"."table" 格式(带引号的schema和表名)
454
+ const quotedBothPattern = new RegExp(
455
+ `"${escapedSourceSchema}"\\s*\\.\\s*"([^"]+)"`,
456
+ 'gi'
457
+ );
458
+ replaced = replaced.replace(quotedBothPattern, `${targetSchemaEscaped}."$1"`);
459
+
460
+ // 替换 "schema".table 格式(带引号的schema,不带引号的表名)
461
+ const quotedSchemaOnlyPattern = new RegExp(
462
+ `"${escapedSourceSchema}"\\s*\\.\\s*([a-zA-Z_][a-zA-Z0-9_]*)`,
463
+ 'gi'
464
+ );
465
+ replaced = replaced.replace(quotedSchemaOnlyPattern, (match, tableName) => {
466
+ return `${targetSchemaEscaped}.${escapeIdentifier(tableName)}`;
467
+ });
468
+
469
+ // 替换 schema."table" 格式(不带引号的schema,带引号的表名)
470
+ const quotedTableOnlyPattern = new RegExp(
471
+ `\\b${escapedSourceSchema}\\s*\\.\\s*"([^"]+)"`,
472
+ 'gi'
473
+ );
474
+ replaced = replaced.replace(quotedTableOnlyPattern, `${targetSchemaEscaped}."$1"`);
475
+
476
+ // 替换 schema.table 格式(都不带引号)
477
+ const unquotedPattern = new RegExp(
478
+ `\\b${escapedSourceSchema}\\s*\\.\\s*([a-zA-Z_][a-zA-Z0-9_]*)`,
479
+ 'gi'
480
+ );
481
+ replaced = replaced.replace(unquotedPattern, (match, tableName) => {
482
+ return `${targetSchemaEscaped}.${escapeIdentifier(tableName)}`;
483
+ });
484
+
485
+ return replaced;
486
+ }
487
+
488
+ /**
489
+ * 获取视图的依赖关系(视图依赖的其他视图或表)
490
+ */
491
+ async function getViewDependencies(pgPool, viewName) {
492
+ const result = await pgPool.query(
493
+ `SELECT DISTINCT
494
+ dependent_ns.nspname as dependent_schema,
495
+ dependent_view.relname as dependent_view,
496
+ source_ns.nspname as source_schema,
497
+ source_table.relname as source_table
498
+ FROM pg_depend
499
+ JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
500
+ JOIN pg_class as dependent_view ON pg_rewrite.ev_class = dependent_view.oid
501
+ JOIN pg_class as source_table ON pg_depend.refobjid = source_table.oid
502
+ JOIN pg_namespace dependent_ns ON dependent_view.relnamespace = dependent_ns.oid
503
+ JOIN pg_namespace source_ns ON source_table.relnamespace = source_ns.oid
504
+ WHERE dependent_ns.nspname = $1
505
+ AND dependent_view.relname = $2
506
+ AND dependent_view.relkind = 'v'
507
+ AND source_table.relkind IN ('r', 'v')
508
+ ORDER BY source_schema, source_table`,
509
+ [sourceSchema, viewName]
510
+ );
511
+ return result.rows.map(row => ({
512
+ dependent_schema: row.dependent_schema,
513
+ dependent_view: row.dependent_view,
514
+ source_schema: row.source_schema,
515
+ source_table: row.source_table
516
+ }));
517
+ }
518
+
519
+ /**
520
+ * 迁移单个视图
521
+ */
522
+ async function migrateView(sourcePool, targetPool, viewName) {
523
+ const migrationType = isCrossDatabase
524
+ ? `跨数据库迁移 (${sourcePgConfig.database}.${sourceSchema} -> ${targetPgConfig.database}.${targetSchema})`
525
+ : `同数据库迁移 (${sourceSchema} -> ${targetSchema})`;
526
+ console.log(`\n开始迁移视图: ${viewName} [${migrationType}]`);
527
+
528
+ try {
529
+ // 检查目标 schema 中是否已存在该视图
530
+ const targetViewCheck = await targetPool.query(
531
+ `SELECT EXISTS (
532
+ SELECT FROM information_schema.views
533
+ WHERE table_schema = $1
534
+ AND table_name = $2
535
+ )`,
536
+ [targetSchema, viewName]
537
+ );
538
+
539
+ if (targetViewCheck.rows[0].exists) {
540
+ console.log(` 提示: 目标 schema ${targetSchema} 中已存在视图 ${viewName},跳过迁移`);
541
+ return;
542
+ }
543
+
544
+ // 获取视图定义
545
+ const viewDefinition = await getViewDefinition(sourcePool, viewName);
546
+
547
+ if (!viewDefinition) {
548
+ console.log(` 警告: 无法获取视图 ${viewName} 的定义,跳过`);
549
+ return;
550
+ }
551
+
552
+ console.log(` 获取视图定义成功`);
553
+
554
+ // 获取视图依赖关系
555
+ const dependencies = await getViewDependencies(sourcePool, viewName);
556
+ if (dependencies.length > 0) {
557
+ console.log(` 视图依赖: ${dependencies.map(d => `${d.source_schema}.${d.source_table}`).join(', ')}`);
558
+ }
559
+
560
+ // 替换视图定义中的schema引用
561
+ let modifiedDefinition = replaceSchemaInViewDefinition(viewDefinition, sourceSchema, targetSchema);
562
+
563
+ // 如果目标schema与源schema不同,还需要处理可能的其他schema引用
564
+ // 这里假设视图主要引用源schema中的对象,如果需要引用其他schema,可能需要额外处理
565
+
566
+ // 构建CREATE VIEW语句
567
+ const targetViewName = escapeIdentifier(targetSchema) + '.' + escapeIdentifier(viewName);
568
+ const createViewQuery = `CREATE OR REPLACE VIEW ${targetViewName} AS ${modifiedDefinition}`;
569
+
570
+ // 在目标schema中创建视图
571
+ const client = await targetPool.connect();
572
+ try {
573
+ await client.query('BEGIN');
574
+ await client.query(createViewQuery);
575
+ await client.query('COMMIT');
576
+ console.log(` ✓ 视图 ${viewName} 迁移完成`);
577
+ } catch (error) {
578
+ await client.query('ROLLBACK');
579
+
580
+ // 检查是否是依赖相关的错误
581
+ const isDependencyError = error.message && (
582
+ error.message.includes('does not exist') ||
583
+ error.message.includes('relation') ||
584
+ error.message.includes('不存在')
585
+ );
586
+
587
+ if (isDependencyError) {
588
+ console.log(` 警告: 视图 ${viewName} 迁移失败,可能是依赖的对象不存在: ${error.message}`);
589
+ console.log(` 提示: 请确保依赖的表或视图已迁移到目标schema`);
590
+ } else {
591
+ throw error;
592
+ }
593
+ } finally {
594
+ client.release();
595
+ }
596
+ } catch (error) {
597
+ console.error(` ✗ 视图 ${viewName} 迁移失败:`, error.message);
598
+ console.log(` 跳过视图 ${viewName},继续迁移其他视图`);
599
+ }
600
+ }
601
+
602
+ /**
603
+ * 重置表的自增序列
604
+ * 在数据迁移后,将序列值设置为当前最大 id + 1
605
+ */
606
+ async function resetSequences(targetPool, tableName, primaryKeyColumns) {
607
+ if (primaryKeyColumns.length === 0) {
608
+ return;
609
+ }
610
+
611
+ const tableRef = `${escapeIdentifier(targetSchema)}.${escapeIdentifier(tableName)}`;
612
+
613
+ for (const pkColumn of primaryKeyColumns) {
614
+ try {
615
+ // 获取序列名称
616
+ const seqResult = await targetPool.query(
617
+ `SELECT pg_get_serial_sequence($1, $2) as seq_name`,
618
+ [`${targetSchema}.${tableName}`, pkColumn]
619
+ );
620
+
621
+ const seqName = seqResult.rows[0]?.seq_name;
622
+ if (!seqName) {
623
+ // 该列没有关联的序列(可能不是自增列)
624
+ continue;
625
+ }
626
+
627
+ // 获取当前最大值并重置序列
628
+ const resetQuery = `
629
+ SELECT setval(
630
+ $1,
631
+ COALESCE((SELECT MAX(${escapeIdentifier(pkColumn)}) FROM ${tableRef}), 0) + 1,
632
+ false
633
+ )
634
+ `;
635
+ const resetResult = await targetPool.query(resetQuery, [seqName]);
636
+ const newSeqValue = resetResult.rows[0]?.setval;
637
+
638
+ console.log(` ✓ 已重置序列 ${seqName} 到 ${newSeqValue}`);
639
+ } catch (error) {
640
+ // 忽略序列重置错误(可能是非自增主键)
641
+ console.log(` 提示: 无法重置列 ${pkColumn} 的序列: ${error.message}`);
642
+ }
643
+ }
644
+ }
645
+
646
+ /**
647
+ * 迁移单个表的数据
648
+ */
649
+ async function migrateTable(sourcePool, targetPool, tableName) {
650
+ const migrationType = isCrossDatabase
651
+ ? `跨数据库迁移 (${sourcePgConfig.database}.${sourceSchema} -> ${targetPgConfig.database}.${targetSchema})`
652
+ : `同数据库迁移 (${sourceSchema} -> ${targetSchema})`;
653
+ console.log(`\n开始迁移表: ${tableName} [${migrationType}]`);
654
+
655
+ try {
656
+ // 获取源 schema 列信息
657
+ const sourceColumns = await getSourceTableColumns(sourcePool, tableName);
658
+ const sourceColumnNames = sourceColumns.map(col => col.COLUMN_NAME);
659
+
660
+ // 获取主键信息
661
+ const primaryKeyColumns = await getPrimaryKeyColumns(sourcePool, tableName);
662
+ const hasPrimaryKey = primaryKeyColumns.length > 0;
663
+
664
+ // 获取总行数
665
+ const totalRows = await getTableRowCount(sourcePool, tableName);
666
+ console.log(` 总行数: ${totalRows}`);
667
+
668
+ if (hasPrimaryKey) {
669
+ console.log(` 主键列: ${primaryKeyColumns.join(', ')}`);
670
+ console.log(` 将仅迁移最新的 ${MAX_ROWS_TO_MIGRATE} 条数据(按主键降序)`);
671
+ }
672
+
673
+ if (totalRows === 0) {
674
+ console.log(` 表 ${tableName} 为空,跳过`);
675
+ return;
676
+ }
677
+
678
+ // 限制迁移行数
679
+ const actualRowsToMigrate = Math.min(totalRows, MAX_ROWS_TO_MIGRATE);
680
+
681
+ // 检查目标 schema 表是否存在
682
+ const targetTableCheck = await targetPool.query(
683
+ `SELECT EXISTS (
684
+ SELECT FROM information_schema.tables
685
+ WHERE table_schema = $1
686
+ AND table_name = $2
687
+ )`,
688
+ [targetSchema, tableName]
689
+ );
690
+
691
+ if (!targetTableCheck.rows[0].exists) {
692
+ console.log(` 警告: 目标 schema ${targetSchema} 中不存在表 ${tableName},跳过`);
693
+ return;
694
+ }
695
+
696
+ // 检查目标表是否已有数据
697
+ const targetRowCountResult = await targetPool.query(
698
+ `SELECT COUNT(*) as count FROM ${escapeIdentifier(targetSchema)}.${escapeIdentifier(tableName)}`
699
+ );
700
+ const targetRowCount = parseInt(targetRowCountResult.rows[0].count, 10);
701
+
702
+ if (targetRowCount > 0) {
703
+ console.log(` 提示: 目标表 ${tableName} 已有 ${targetRowCount} 条数据,跳过迁移`);
704
+ return;
705
+ }
706
+
707
+ // 获取目标 schema 列信息
708
+ const targetColumns = await getTargetTableColumns(targetPool, tableName);
709
+ const targetColumnNames = targetColumns.map(col => col.column_name);
710
+
711
+ // 找出两个表都存在的共同字段(忽略大小写差异)
712
+ const commonColumns = sourceColumnNames.filter(sourceCol => {
713
+ return targetColumnNames.some(targetCol => targetCol.toLowerCase() === sourceCol.toLowerCase());
714
+ });
715
+
716
+ if (commonColumns.length === 0) {
717
+ console.log(` 警告: 表 ${tableName} 没有共同字段,跳过`);
718
+ return;
719
+ }
720
+
721
+ // 记录字段差异
722
+ const missingInTarget = sourceColumnNames.filter(col =>
723
+ !targetColumnNames.some(targetCol => targetCol.toLowerCase() === col.toLowerCase())
724
+ );
725
+ const missingInSource = targetColumnNames.filter(col =>
726
+ !sourceColumnNames.some(sourceCol => sourceCol.toLowerCase() === col.toLowerCase())
727
+ );
728
+
729
+ if (missingInTarget.length > 0) {
730
+ console.log(` 提示: 源 schema 中存在但目标 schema 中不存在的字段: ${missingInTarget.join(', ')}`);
731
+ }
732
+ if (missingInSource.length > 0) {
733
+ console.log(` 提示: 目标 schema 中存在但源 schema 中不存在的字段: ${missingInSource.join(', ')}`);
734
+ }
735
+ console.log(` 将迁移 ${commonColumns.length} 个共同字段: ${commonColumns.join(', ')}`);
736
+
737
+ // 清空目标表(可选,如果需要增量迁移可以注释掉)
738
+ // await targetPool.query(`TRUNCATE TABLE ${escapeIdentifier(targetSchema)}.${escapeIdentifier(tableName)} CASCADE`);
739
+
740
+ let offset = 0;
741
+ let migratedRows = 0;
742
+ let errorCount = 0;
743
+
744
+ // 构建查询语句
745
+ const sourceTableRef = `${escapeIdentifier(sourceSchema)}.${escapeIdentifier(tableName)}`;
746
+ const targetTableRef = `${escapeIdentifier(targetSchema)}.${escapeIdentifier(tableName)}`;
747
+
748
+ // 分批读取和插入数据
749
+ while (offset < actualRowsToMigrate) {
750
+ let query;
751
+
752
+ if (hasPrimaryKey && primaryKeyColumns.length > 0) {
753
+ // 对于有主键的表,按主键降序排序,直接限制为MAX_ROWS_TO_MIGRATE条
754
+ const orderByClause = primaryKeyColumns
755
+ .map(col => `${escapeIdentifier(col)} DESC`)
756
+ .join(', ');
757
+ const limit = Math.min(BATCH_SIZE, actualRowsToMigrate - offset);
758
+ query = `SELECT * FROM ${sourceTableRef} ORDER BY ${orderByClause} LIMIT ${limit} OFFSET ${offset}`;
759
+ } else {
760
+ // 对于没有主键的表,使用普通查询
761
+ query = `SELECT * FROM ${sourceTableRef} LIMIT ${BATCH_SIZE} OFFSET ${offset}`;
762
+ }
763
+
764
+ const result = await sourcePool.query(query);
765
+ const rows = result.rows;
766
+
767
+ if (rows.length === 0) {
768
+ break;
769
+ }
770
+
771
+ // 批量插入到目标 schema
772
+ const client = await targetPool.connect();
773
+ try {
774
+ await client.query('BEGIN');
775
+
776
+ // 准备批量插入的值(只使用共同字段)
777
+ const allValues = rows.map(row => {
778
+ return commonColumns.map(colName => {
779
+ const sourceColumn = sourceColumns.find(c => c.COLUMN_NAME === colName);
780
+ const targetColumn = targetColumns.find(c => c.column_name.toLowerCase() === colName.toLowerCase());
781
+ return convertValue(row[colName], sourceColumn?.DATA_TYPE, targetColumn?.data_type);
782
+ });
783
+ });
784
+
785
+ // 使用批量插入(构建多行 VALUES)
786
+ if (allValues.length > 0) {
787
+ // 构建 VALUES 占位符,对于 JSON/JSONB 字段使用类型转换
788
+ const valuesPlaceholders = allValues.map((_, rowIndex) => {
789
+ const startParam = rowIndex * commonColumns.length + 1;
790
+ return `(${commonColumns.map((_, colIndex) => {
791
+ const colName = commonColumns[colIndex];
792
+ const targetColumn = targetColumns.find(c => c.column_name.toLowerCase() === colName.toLowerCase());
793
+ const isJsonType = targetColumn && (targetColumn.data_type === 'json' || targetColumn.data_type === 'jsonb');
794
+ const paramIndex = startParam + colIndex;
795
+
796
+ // 对于 JSON/JSONB 字段,使用类型转换确保格式正确
797
+ // 注意:现在 convertValue 返回的是序列化后的字符串,所以需要类型转换
798
+ if (isJsonType) {
799
+ return `$${paramIndex}::${targetColumn.data_type}`;
800
+ }
801
+ return `$${paramIndex}`;
802
+ }).join(', ')})`;
803
+ }).join(', ');
804
+
805
+ // 使用目标 schema 中实际的列名(处理大小写)
806
+ const targetColumnNamesForInsert = commonColumns.map(col => {
807
+ const targetCol = targetColumnNames.find(targetCol => targetCol.toLowerCase() === col.toLowerCase());
808
+ return escapeIdentifier(targetCol || col);
809
+ });
810
+
811
+ const batchInsertQuery = `INSERT INTO ${targetTableRef} (${targetColumnNamesForInsert.join(', ')}) VALUES ${valuesPlaceholders}`;
812
+ const flatValues = allValues.flat();
813
+
814
+ await client.query(batchInsertQuery, flatValues);
815
+ }
816
+
817
+ await client.query('COMMIT');
818
+ migratedRows += rows.length;
819
+ const progressTotal = hasPrimaryKey ? actualRowsToMigrate : totalRows;
820
+ console.log(` 已迁移: ${migratedRows}/${progressTotal} 行`);
821
+ } catch (error) {
822
+ await client.query('ROLLBACK');
823
+
824
+ // 检查是否是 JSON 格式错误
825
+ const isJsonError = error.message && (
826
+ error.message.includes('invalid input syntax for type json') ||
827
+ error.message.includes('invalid input syntax for type jsonb') ||
828
+ (error.message.includes('invalid input syntax') && error.message.includes('json'))
829
+ );
830
+
831
+ // 检查是否是字段相关的错误
832
+ const isColumnError = error.message && (
833
+ error.message.includes('column') ||
834
+ error.message.includes('字段') ||
835
+ error.message.includes('does not exist') ||
836
+ error.message.includes('unknown column')
837
+ );
838
+
839
+ // 检查是否是外键约束错误
840
+ const isForeignKeyError = error.message && (
841
+ error.message.includes('foreign key') ||
842
+ error.message.includes('外键') ||
843
+ error.message.includes('violates foreign key constraint') ||
844
+ error.message.includes('referential integrity')
845
+ );
846
+
847
+ if (isJsonError) {
848
+ // 对于 JSON 错误,尝试逐行插入以跳过有问题的行
849
+ console.log(` 警告: 批量插入遇到 JSON 格式错误,尝试逐行插入以跳过有问题的行...`);
850
+ let rowInserted = 0;
851
+ let rowSkipped = 0;
852
+
853
+ // 使用目标 schema 中实际的列名(处理大小写)
854
+ const targetColumnNamesForInsert = commonColumns.map(col => {
855
+ const targetCol = targetColumnNames.find(targetCol => targetCol.toLowerCase() === col.toLowerCase());
856
+ return escapeIdentifier(targetCol || col);
857
+ });
858
+
859
+ for (let i = 0; i < rows.length; i++) {
860
+ const row = rows[i];
861
+ try {
862
+ await client.query('BEGIN');
863
+
864
+ // 准备单行插入的值
865
+ const rowValues = commonColumns.map(colName => {
866
+ const sourceColumn = sourceColumns.find(c => c.COLUMN_NAME === colName);
867
+ const targetColumn = targetColumns.find(c => c.column_name.toLowerCase() === colName.toLowerCase());
868
+ return convertValue(row[colName], sourceColumn?.DATA_TYPE, targetColumn?.data_type);
869
+ });
870
+
871
+ // 构建单行插入语句,对于 JSON/JSONB 字段使用类型转换
872
+ const placeholders = commonColumns.map((colName, index) => {
873
+ const targetColumn = targetColumns.find(c => c.column_name.toLowerCase() === colName.toLowerCase());
874
+ const isJsonType = targetColumn && (targetColumn.data_type === 'json' || targetColumn.data_type === 'jsonb');
875
+
876
+ // 对于 JSON/JSONB 字段,使用类型转换确保格式正确
877
+ // 注意:现在 convertValue 返回的是序列化后的字符串,所以需要类型转换
878
+ if (isJsonType) {
879
+ return `$${index + 1}::${targetColumn.data_type}`;
880
+ }
881
+ return `$${index + 1}`;
882
+ }).join(', ');
883
+ const singleInsertQuery = `INSERT INTO ${targetTableRef} (${targetColumnNamesForInsert.join(', ')}) VALUES (${placeholders})`;
884
+
885
+ await client.query(singleInsertQuery, rowValues);
886
+ await client.query('COMMIT');
887
+ rowInserted++;
888
+ } catch (rowError) {
889
+ await client.query('ROLLBACK');
890
+
891
+ // 检查是否是 JSON 错误
892
+ const isRowJsonError = rowError.message && (
893
+ rowError.message.includes('invalid input syntax for type json') ||
894
+ rowError.message.includes('invalid input syntax for type jsonb') ||
895
+ (rowError.message.includes('invalid input syntax') && rowError.message.includes('json'))
896
+ );
897
+
898
+ if (isRowJsonError) {
899
+ rowSkipped++;
900
+ // 尝试找出有问题的 JSON 字段
901
+ const problematicFields = [];
902
+ for (const colName of commonColumns) {
903
+ const column = sourceColumns.find(c => c.COLUMN_NAME === colName);
904
+ const targetColumn = targetColumns.find(c => c.column_name.toLowerCase() === colName.toLowerCase());
905
+ if (column && (column.DATA_TYPE === 'json' || column.DATA_TYPE === 'jsonb') ||
906
+ targetColumn && (targetColumn.data_type === 'json' || targetColumn.data_type === 'jsonb')) {
907
+ const value = row[colName];
908
+ if (value !== null && value !== undefined) {
909
+ try {
910
+ if (typeof value === 'string') {
911
+ JSON.parse(value);
912
+ } else if (typeof value === 'object') {
913
+ JSON.stringify(value);
914
+ }
915
+ } catch (e) {
916
+ problematicFields.push(`${colName}(${typeof value})`);
917
+ }
918
+ }
919
+ }
920
+ }
921
+ if (problematicFields.length > 0) {
922
+ console.log(` 跳过行 ${i + 1}(JSON 字段问题: ${problematicFields.join(', ')})`);
923
+ } else {
924
+ console.log(` 跳过行 ${i + 1}(JSON 格式错误: ${rowError.message.substring(0, 150)})`);
925
+ }
926
+ } else {
927
+ // 其他错误,也跳过这一行,但输出详细错误信息
928
+ rowSkipped++;
929
+ console.log(` 跳过行 ${i + 1}(错误: ${rowError.message.substring(0, 200)})`);
930
+ }
931
+ }
932
+ }
933
+
934
+ migratedRows += rowInserted;
935
+ errorCount += rowSkipped;
936
+ if (rowSkipped > 0) {
937
+ console.log(` 逐行插入完成: 成功 ${rowInserted} 行,跳过 ${rowSkipped} 行`);
938
+ }
939
+ } else if (isColumnError) {
940
+ errorCount++;
941
+ console.log(` 警告: 字段差异错误(已忽略): ${error.message}`);
942
+ // 继续处理下一批数据
943
+ migratedRows += rows.length;
944
+ } else if (isForeignKeyError) {
945
+ errorCount++;
946
+ console.log(` 警告: 外键约束错误(已忽略,可能因为依赖表数据未完全迁移): ${error.message}`);
947
+ // 继续处理下一批数据
948
+ migratedRows += rows.length;
949
+ } else {
950
+ // 非字段/外键/JSON错误,抛出异常
951
+ throw error;
952
+ }
953
+ } finally {
954
+ client.release();
955
+ }
956
+
957
+ // 更新偏移量
958
+ offset += BATCH_SIZE;
959
+
960
+ // 如果获取的数据少于批次大小,说明已经获取完所有数据
961
+ if (rows.length < BATCH_SIZE) {
962
+ break;
963
+ }
964
+ }
965
+
966
+ if (migratedRows < totalRows) {
967
+ console.log(` 提示: 表 ${tableName} 共有 ${totalRows} 行,仅迁移了最新的 ${migratedRows} 行`);
968
+ }
969
+
970
+ if (errorCount > 0) {
971
+ console.log(` ⚠ 表 ${tableName} 迁移完成,共 ${migratedRows} 行,遇到 ${errorCount} 个字段差异错误(已忽略)`);
972
+ } else {
973
+ console.log(` ✓ 表 ${tableName} 迁移完成,共 ${migratedRows} 行`);
974
+ }
975
+
976
+ // 重置自增序列,避免后续插入时 id 冲突
977
+ if (migratedRows > 0 && hasPrimaryKey) {
978
+ await resetSequences(targetPool, tableName, primaryKeyColumns);
979
+ }
980
+ } catch (error) {
981
+ console.error(` ✗ 表 ${tableName} 迁移失败:`, error.message);
982
+ // 不再抛出错误,继续迁移下一个表
983
+ console.log(` 跳过表 ${tableName},继续迁移其他表`);
984
+ }
985
+ }
986
+
987
+ /**
988
+ * 主函数
989
+ */
990
+ async function main() {
991
+ console.log('PostgreSQL Schema 间数据迁移工具');
992
+ console.log('=====================================\n');
993
+
994
+ if (isCrossDatabase) {
995
+ console.log('迁移模式: 跨数据库迁移');
996
+ console.log(`源数据库: ${sourcePgConfig.host}:${sourcePgConfig.port}/${sourcePgConfig.database}`);
997
+ console.log(`源 Schema: ${sourceSchema}`);
998
+ console.log(`目标数据库: ${targetPgConfig.host}:${targetPgConfig.port}/${targetPgConfig.database}`);
999
+ console.log(`目标 Schema: ${targetSchema}\n`);
1000
+ } else {
1001
+ console.log('迁移模式: 同数据库不同 Schema 迁移');
1002
+ console.log(`数据库: ${sourcePgConfig.host}:${sourcePgConfig.port}/${sourcePgConfig.database}`);
1003
+ console.log(`源 Schema: ${sourceSchema}`);
1004
+ console.log(`目标 Schema: ${targetSchema}\n`);
1005
+ }
1006
+
1007
+ // 验证配置
1008
+ if (!sourcePgConfig.database) {
1009
+ console.error('错误: 请设置源数据库配置 (PG_DATABASE 或 DATABASE_URL)');
1010
+ process.exit(1);
1011
+ }
1012
+ if (!sourceSchema) {
1013
+ console.error('错误: 请设置 PG_SCHEMA 环境变量');
1014
+ process.exit(1);
1015
+ }
1016
+ if (!targetSchema) {
1017
+ console.error('错误: 请设置 PG_SCHEMA_TARGET 环境变量');
1018
+ process.exit(1);
1019
+ }
1020
+ if (isCrossDatabase && !targetPgConfig.database) {
1021
+ console.error('错误: 请设置目标数据库配置 (PG_DATABASE_TARGET 或 DATABASE_URL_TARGET)');
1022
+ process.exit(1);
1023
+ }
1024
+ if (sourceSchema === targetSchema && !isCrossDatabase) {
1025
+ console.error('错误: 源 schema 和目标 schema 不能相同(同数据库迁移时)');
1026
+ process.exit(1);
1027
+ }
1028
+ if (isCrossDatabase &&
1029
+ sourcePgConfig.host === targetPgConfig.host &&
1030
+ sourcePgConfig.port === targetPgConfig.port &&
1031
+ sourcePgConfig.database === targetPgConfig.database &&
1032
+ sourceSchema === targetSchema) {
1033
+ console.error('错误: 源和目标配置完全相同');
1034
+ process.exit(1);
1035
+ }
1036
+
1037
+ let sourcePool = null;
1038
+ let targetPool = null;
1039
+
1040
+ try {
1041
+ // 连接源 PostgreSQL
1042
+ console.log('正在连接源 PostgreSQL...');
1043
+ sourcePool = new Pool(sourcePgConfig);
1044
+ await sourcePool.query('SELECT 1');
1045
+ console.log(`✓ 源 PostgreSQL 连接成功 (${sourcePgConfig.database})\n`);
1046
+
1047
+ // 连接目标 PostgreSQL(如果跨数据库)
1048
+ if (isCrossDatabase) {
1049
+ console.log('正在连接目标 PostgreSQL...');
1050
+ targetPool = new Pool(targetPgConfig);
1051
+ await targetPool.query('SELECT 1');
1052
+ console.log(`✓ 目标 PostgreSQL 连接成功 (${targetPgConfig.database})\n`);
1053
+ } else {
1054
+ // 同数据库迁移,使用同一个连接池
1055
+ targetPool = sourcePool;
1056
+ }
1057
+
1058
+ // 检查源 schema 是否存在
1059
+ const sourceSchemaCheck = await sourcePool.query(
1060
+ `SELECT EXISTS (
1061
+ SELECT FROM information_schema.schemata
1062
+ WHERE schema_name = $1
1063
+ )`,
1064
+ [sourceSchema]
1065
+ );
1066
+ if (!sourceSchemaCheck.rows[0].exists) {
1067
+ console.error(`错误: 源 schema ${sourceSchema} 不存在`);
1068
+ process.exit(1);
1069
+ }
1070
+
1071
+ // 检查目标 schema 是否存在
1072
+ const targetSchemaCheck = await targetPool.query(
1073
+ `SELECT EXISTS (
1074
+ SELECT FROM information_schema.schemata
1075
+ WHERE schema_name = $1
1076
+ )`,
1077
+ [targetSchema]
1078
+ );
1079
+ if (!targetSchemaCheck.rows[0].exists) {
1080
+ console.error(`错误: 目标 schema ${targetSchema} 不存在`);
1081
+ process.exit(1);
1082
+ }
1083
+
1084
+ // 获取所有表
1085
+ console.log(`正在获取源 schema ${sourceSchema} 中的表列表...`);
1086
+ const tables = await getSourceTables(sourcePool);
1087
+ console.log(`找到 ${tables.length} 个表: ${tables.join(', ')}\n`);
1088
+
1089
+ if (tables.length === 0) {
1090
+ console.log('没有找到任何表,退出');
1091
+ return;
1092
+ }
1093
+
1094
+ // 获取外键约束信息(从目标数据库获取,因为目标数据库可能有更严格的外键约束)
1095
+ console.log('正在分析外键依赖关系(基于目标数据库)...');
1096
+ const constraints = await getForeignKeyConstraints(targetPool, targetSchema);
1097
+ console.log(`找到 ${constraints.length} 个外键约束`);
1098
+
1099
+ if (constraints.length > 0) {
1100
+ console.log('外键依赖关系:');
1101
+ constraints.forEach(constraint => {
1102
+ console.log(` ${constraint.TABLE_NAME} -> ${constraint.REFERENCED_TABLE_NAME}`);
1103
+ });
1104
+ }
1105
+ console.log('');
1106
+
1107
+ // 根据外键依赖关系排序表
1108
+ const sortedTables = topologicalSort(tables, constraints);
1109
+
1110
+ if (sortedTables.length !== tables.length) {
1111
+ console.log('警告: 排序后的表数量与原始表数量不一致');
1112
+ }
1113
+
1114
+ console.log('迁移顺序(考虑外键依赖):');
1115
+ sortedTables.forEach((table, index) => {
1116
+ const deps = constraints
1117
+ .filter(c => c.TABLE_NAME === table)
1118
+ .map(c => c.REFERENCED_TABLE_NAME);
1119
+ if (deps.length > 0) {
1120
+ console.log(` ${index + 1}. ${table} (依赖: ${deps.join(', ')})`);
1121
+ } else {
1122
+ console.log(` ${index + 1}. ${table}`);
1123
+ }
1124
+ });
1125
+ console.log('');
1126
+
1127
+ // 按照排序后的顺序迁移每个表
1128
+ for (const tableName of sortedTables) {
1129
+ try {
1130
+ await migrateTable(sourcePool, targetPool, tableName);
1131
+ } catch (error) {
1132
+ console.error(` 表 ${tableName} 迁移出错,继续处理下一个表:`, error.message);
1133
+ // 继续迁移下一个表,不中断整个流程
1134
+ }
1135
+ }
1136
+
1137
+ console.log('\n=====================================');
1138
+ console.log('✓ 所有表迁移完成!\n');
1139
+
1140
+ // 迁移视图(在表迁移完成后进行,因为视图可能依赖于表)
1141
+ console.log('=====================================');
1142
+ console.log('开始迁移视图...');
1143
+ console.log('=====================================\n');
1144
+
1145
+ // 获取所有视图
1146
+ console.log(`正在获取源 schema ${sourceSchema} 中的视图列表...`);
1147
+ const views = await getSourceViews(sourcePool);
1148
+ console.log(`找到 ${views.length} 个视图: ${views.join(', ')}\n`);
1149
+
1150
+ if (views.length > 0) {
1151
+ // 获取视图依赖关系(视图之间的依赖)
1152
+ console.log('正在分析视图依赖关系...');
1153
+ const viewDependencies = [];
1154
+ for (const viewName of views) {
1155
+ const deps = await getViewDependencies(sourcePool, viewName);
1156
+ // 只记录视图之间的依赖(不包括对表的依赖)
1157
+ for (const dep of deps) {
1158
+ if (dep.source_table && views.includes(dep.source_table) && dep.source_schema === sourceSchema) {
1159
+ viewDependencies.push({
1160
+ TABLE_NAME: viewName,
1161
+ REFERENCED_TABLE_NAME: dep.source_table
1162
+ });
1163
+ }
1164
+ }
1165
+ }
1166
+
1167
+ if (viewDependencies.length > 0) {
1168
+ console.log(`找到 ${viewDependencies.length} 个视图依赖关系:`);
1169
+ viewDependencies.forEach(dep => {
1170
+ console.log(` ${dep.TABLE_NAME} -> ${dep.REFERENCED_TABLE_NAME}`);
1171
+ });
1172
+ console.log('');
1173
+ }
1174
+
1175
+ // 根据依赖关系排序视图
1176
+ const sortedViews = topologicalSort(views, viewDependencies);
1177
+
1178
+ console.log('视图迁移顺序(考虑依赖关系):');
1179
+ sortedViews.forEach((view, index) => {
1180
+ const deps = viewDependencies
1181
+ .filter(d => d.TABLE_NAME === view)
1182
+ .map(d => d.REFERENCED_TABLE_NAME);
1183
+ if (deps.length > 0) {
1184
+ console.log(` ${index + 1}. ${view} (依赖: ${deps.join(', ')})`);
1185
+ } else {
1186
+ console.log(` ${index + 1}. ${view}`);
1187
+ }
1188
+ });
1189
+ console.log('');
1190
+
1191
+ // 按照排序后的顺序迁移每个视图
1192
+ for (const viewName of sortedViews) {
1193
+ try {
1194
+ await migrateView(sourcePool, targetPool, viewName);
1195
+ } catch (error) {
1196
+ console.error(` 视图 ${viewName} 迁移出错,继续处理下一个视图:`, error.message);
1197
+ // 继续迁移下一个视图,不中断整个流程
1198
+ }
1199
+ }
1200
+
1201
+ console.log('\n=====================================');
1202
+ console.log('✓ 所有视图迁移完成!');
1203
+ } else {
1204
+ console.log('没有找到任何视图');
1205
+ }
1206
+ } catch (error) {
1207
+ console.error('\n迁移过程中发生错误:', error);
1208
+ process.exit(1);
1209
+ } finally {
1210
+ // 关闭连接
1211
+ if (sourcePool) {
1212
+ await sourcePool.end();
1213
+ console.log('\n源 PostgreSQL 连接已关闭');
1214
+ }
1215
+ if (targetPool && isCrossDatabase && targetPool !== sourcePool) {
1216
+ await targetPool.end();
1217
+ console.log('目标 PostgreSQL 连接已关闭');
1218
+ }
1219
+ }
1220
+ }
1221
+
1222
+ // 运行主函数
1223
+ if (require.main === module) {
1224
+ main().catch(error => {
1225
+ console.error('未处理的错误:', error);
1226
+ process.exit(1);
1227
+ });
1228
+ }
1229
+
1230
+ module.exports = { main };
1231
+
1232
+