Kaynağa Gözat

🔧 chore(database): 从MySQL迁移到PostgreSQL数据库

- 更新环境变量配置文件,将数据库端口从3306改为5432,用户从root改为postgres
- 修改docker-compose.yml,替换mysql服务为postgres服务
- 替换phpmyadmin为pgadmin数据库管理工具
- 更新数据库连接字符串,从mysql://改为postgresql://
- 添加pg依赖包并更新typeorm配置以支持PostgreSQL
- 修改data-source.ts中的数据库类型和连接参数

✨ feat(database): 添加PostgreSQL数据库支持

- 配置PostgreSQL数据库服务及数据卷挂载
- 设置pgadmin管理界面访问端口为38091
- 配置PostgreSQL默认数据库和用户认证信息
- 添加PostgreSQL Node.js客户端依赖(pg@8.16.3)
yourname 2 ay önce
ebeveyn
işleme
82aba99ba2
6 değiştirilmiş dosya ile 136 ekleme ve 25 silme
  1. 2 2
      .env.example
  2. 1 1
      .env.test.example
  3. 15 16
      docker-compose.yml
  4. 1 0
      package.json
  5. 113 2
      pnpm-lock.yaml
  6. 4 4
      src/server/data-source.ts

+ 2 - 2
.env.example

@@ -12,9 +12,9 @@ BASE=/
 
 # 数据库配置
 DB_HOST=localhost
-DB_PORT=3306
+DB_PORT=5432
 DB_NAME=d8dai
-DB_USER=root
+DB_USER=postgres
 DB_PASSWORD=your_password_here
 
 # Redis配置

+ 1 - 1
.env.test.example

@@ -2,7 +2,7 @@
 NODE_ENV=test
 
 # Database
-DATABASE_URL=mysql://root:test_password@localhost:3306/test_d8dai
+DATABASE_URL=postgresql://postgres:test_password@localhost:5432/test_d8dai
 
 # Server
 PORT=8080

+ 15 - 16
docker-compose.yml

@@ -12,16 +12,18 @@ services:
       - '8080:8080'
     command: 'sleep infinity'
 
-  mysql:
-    image: 'docker.1ms.run/mysql:8.0.36'
-    container_name: mysql
+  postgres:
+    image: 'docker.1ms.run/postgres:latest'
+    container_name: postgres
     restart: always
     environment:
-      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
+      POSTGRES_DB: d8dai
+      POSTGRES_USER: postgres
+      POSTGRES_PASSWORD: postgres
     volumes:
-      - /mnt/mysql-data:/var/lib/mysql
+      - /mnt/postgres-data:/var/lib/postgresql/data
     ports:
-      - '3306:3306'
+      - '5432:5432'
 
   redis:
     image: 'docker.1ms.run/redis:7.0.4'
@@ -32,20 +34,17 @@ services:
     ports:
       - '6379:6379'
 
-  phpmyadmin:
-    image: 'docker.1ms.run/phpmyadmin:latest'
-    container_name: phpmyadmin
+  pgadmin:
+    image: 'docker.1ms.run/elestio/pgadmin:latest'
+    container_name: pgadmin
     restart: always
     environment:
-      APACHE_PORT: 80  # 容器内端口,通常为80
-      PMA_ABSOLUTE_URI: 'http://localhost:38090/'
-      PMA_USER: 'root'
-      PMA_HOST: mysql  # 修正拼写错误: 从myssql改为mysql
-      PMA_PORT: 3306   # 整数或字符串均可,但建议保持一致
+      PGADMIN_DEFAULT_EMAIL: admin@example.com
+      PGADMIN_DEFAULT_PASSWORD: admin
     ports:
-      - '38090:80'     # 宿主机端口:容器内端口
+      - '38091:80'
     depends_on:
-      - mysql
+      - postgres
 
   minio:
     image: 'docker.1ms.run/minio:latest'

+ 1 - 0
package.json

@@ -81,6 +81,7 @@
     "lucide-react": "^0.536.0",
     "mysql2": "^3.14.2",
     "next-themes": "^0.4.6",
+    "pg": "^8.16.3",
     "react": "^19.1.0",
     "react-day-picker": "^9.8.1",
     "react-dom": "^19.1.0",

+ 113 - 2
pnpm-lock.yaml

@@ -161,6 +161,9 @@ importers:
       next-themes:
         specifier: ^0.4.6
         version: 0.4.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+      pg:
+        specifier: ^8.16.3
+        version: 8.16.3
       react:
         specifier: ^19.1.0
         version: 19.1.0
@@ -202,7 +205,7 @@ importers:
         version: 1.3.6
       typeorm:
         specifier: ^0.3.25
-        version: 0.3.25(mysql2@3.14.2)(reflect-metadata@0.2.2)
+        version: 0.3.25(mysql2@3.14.2)(pg@8.16.3)(reflect-metadata@0.2.2)
       vaul:
         specifier: ^1.1.2
         version: 1.1.2(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -3137,6 +3140,40 @@ packages:
     resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==}
     engines: {node: '>= 14.16'}
 
+  pg-cloudflare@1.2.7:
+    resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==}
+
+  pg-connection-string@2.9.1:
+    resolution: {integrity: sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==}
+
+  pg-int8@1.0.1:
+    resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
+    engines: {node: '>=4.0.0'}
+
+  pg-pool@3.10.1:
+    resolution: {integrity: sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==}
+    peerDependencies:
+      pg: '>=8.0'
+
+  pg-protocol@1.10.3:
+    resolution: {integrity: sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==}
+
+  pg-types@2.2.0:
+    resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
+    engines: {node: '>=4'}
+
+  pg@8.16.3:
+    resolution: {integrity: sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==}
+    engines: {node: '>= 16.0.0'}
+    peerDependencies:
+      pg-native: '>=3.0.1'
+    peerDependenciesMeta:
+      pg-native:
+        optional: true
+
+  pgpass@1.0.5:
+    resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
+
   picocolors@1.1.1:
     resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
 
@@ -3166,6 +3203,22 @@ packages:
     resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
     engines: {node: ^10 || ^12 || >=14}
 
+  postgres-array@2.0.0:
+    resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
+    engines: {node: '>=4'}
+
+  postgres-bytea@1.0.0:
+    resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
+    engines: {node: '>=0.10.0'}
+
+  postgres-date@1.0.7:
+    resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
+    engines: {node: '>=0.10.0'}
+
+  postgres-interval@1.2.0:
+    resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
+    engines: {node: '>=0.10.0'}
+
   prelude-ls@1.2.1:
     resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
     engines: {node: '>= 0.8.0'}
@@ -3449,6 +3502,10 @@ packages:
     resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
     engines: {node: '>=0.10.0'}
 
+  split2@4.2.0:
+    resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+    engines: {node: '>= 10.x'}
+
   sql-highlight@6.1.0:
     resolution: {integrity: sha512-ed7OK4e9ywpE7pgRMkMQmZDPKSVdm0oX5IEtZiKnFucSF0zu6c80GZBe38UqHuVhTWJ9xsKgSMjCG2bml86KvA==}
     engines: {node: '>=14'}
@@ -3910,6 +3967,10 @@ packages:
   xmlchars@2.2.0:
     resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
 
+  xtend@4.0.2:
+    resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
+    engines: {node: '>=0.4'}
+
   y18n@5.0.8:
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     engines: {node: '>=10'}
@@ -6826,6 +6887,41 @@ snapshots:
 
   pathval@2.0.1: {}
 
+  pg-cloudflare@1.2.7:
+    optional: true
+
+  pg-connection-string@2.9.1: {}
+
+  pg-int8@1.0.1: {}
+
+  pg-pool@3.10.1(pg@8.16.3):
+    dependencies:
+      pg: 8.16.3
+
+  pg-protocol@1.10.3: {}
+
+  pg-types@2.2.0:
+    dependencies:
+      pg-int8: 1.0.1
+      postgres-array: 2.0.0
+      postgres-bytea: 1.0.0
+      postgres-date: 1.0.7
+      postgres-interval: 1.2.0
+
+  pg@8.16.3:
+    dependencies:
+      pg-connection-string: 2.9.1
+      pg-pool: 3.10.1(pg@8.16.3)
+      pg-protocol: 1.10.3
+      pg-types: 2.2.0
+      pgpass: 1.0.5
+    optionalDependencies:
+      pg-cloudflare: 1.2.7
+
+  pgpass@1.0.5:
+    dependencies:
+      split2: 4.2.0
+
   picocolors@1.1.1: {}
 
   picomatch@2.3.1: {}
@@ -6848,6 +6944,16 @@ snapshots:
       picocolors: 1.1.1
       source-map-js: 1.2.1
 
+  postgres-array@2.0.0: {}
+
+  postgres-bytea@1.0.0: {}
+
+  postgres-date@1.0.7: {}
+
+  postgres-interval@1.2.0:
+    dependencies:
+      xtend: 4.0.2
+
   prelude-ls@1.2.1: {}
 
   pretty-format@27.5.1:
@@ -7174,6 +7280,8 @@ snapshots:
   source-map@0.6.1:
     optional: true
 
+  split2@4.2.0: {}
+
   sql-highlight@6.1.0: {}
 
   sqlstring@2.3.3: {}
@@ -7385,7 +7493,7 @@ snapshots:
       possible-typed-array-names: 1.1.0
       reflect.getprototypeof: 1.0.10
 
-  typeorm@0.3.25(mysql2@3.14.2)(reflect-metadata@0.2.2):
+  typeorm@0.3.25(mysql2@3.14.2)(pg@8.16.3)(reflect-metadata@0.2.2):
     dependencies:
       '@sqltools/formatter': 1.2.5
       ansis: 3.17.0
@@ -7404,6 +7512,7 @@ snapshots:
       yargs: 17.7.2
     optionalDependencies:
       mysql2: 3.14.2
+      pg: 8.16.3
     transitivePeerDependencies:
       - babel-plugin-macros
       - supports-color
@@ -7667,6 +7776,8 @@ snapshots:
   xmlchars@2.2.0:
     optional: true
 
+  xtend@4.0.2: {}
+
   y18n@5.0.8: {}
 
   yallist@5.0.0: {}

+ 4 - 4
src/server/data-source.ts

@@ -7,12 +7,12 @@ import { UserEntity as User } from "./modules/users/user.entity"
 import { Role } from "./modules/users/role.entity"
 
 export const AppDataSource = new DataSource({
-  type: "mysql",
+  type: "postgres",
   host: process.env.DB_HOST || "localhost",
-  port: parseInt(process.env.DB_PORT || "3306"),
-  username: process.env.DB_USERNAME || "root",
+  port: parseInt(process.env.DB_PORT || "5432"),
+  username: process.env.DB_USERNAME || "postgres",
   password: process.env.DB_PASSWORD || "",
-  database: process.env.DB_DATABASE || "d8dai",
+  database: process.env.DB_DATABASE || "postgres",
   entities: [
     User, Role, 
   ],