-
Notifications
You must be signed in to change notification settings - Fork 5
新增Docker部署 #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
新增Docker部署 #1
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| # Build stage for frontend | ||
| FROM node:20-alpine AS frontend-builder | ||
|
|
||
| WORKDIR /app/web | ||
|
|
||
| # Copy frontend package files | ||
| COPY web/MS_OAuth2API_Next_Web/package*.json ./ | ||
|
|
||
| # Install frontend dependencies | ||
| RUN npm install | ||
|
|
||
| # Copy frontend source code | ||
| COPY web/MS_OAuth2API_Next_Web/ ./ | ||
|
|
||
| # Build frontend (skip type-check for faster builds) | ||
| RUN npm run build-only | ||
|
|
||
| # Production stage for backend | ||
| FROM node:20-alpine | ||
|
|
||
| # Install wget for healthcheck | ||
| RUN apk add --no-cache wget | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| # Copy backend package files | ||
| COPY package*.json ./ | ||
|
|
||
| # Install backend dependencies (production only) | ||
| RUN npm install --omit=dev | ||
|
|
||
| # Copy backend source code | ||
| COPY config ./config | ||
| COPY controllers ./controllers | ||
| COPY middlewares ./middlewares | ||
| COPY routes ./routes | ||
| COPY services ./services | ||
| COPY utils ./utils | ||
| COPY main.js ./ | ||
|
|
||
| # Copy built frontend assets to public directory | ||
| RUN mkdir -p public | ||
| COPY --from=frontend-builder /app/web/dist ./public | ||
|
|
||
| # Create logs directory with proper permissions | ||
| RUN mkdir -p logs && chmod 755 logs | ||
|
|
||
| # Set environment defaults | ||
| ENV NODE_ENV=production | ||
| ENV PORT=13000 | ||
|
|
||
| # Expose port | ||
| EXPOSE 13000 | ||
|
|
||
| # Health check | ||
| HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ | ||
| CMD wget --no-verbose --tries=1 --spider http://localhost:13000/ || exit 1 | ||
|
|
||
| # Start command | ||
| CMD ["node", "main.js"] | ||
|
||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,78 @@ | ||||||||
| services: | ||||||||
| app: | ||||||||
| build: . | ||||||||
| ports: | ||||||||
| - "13000:13000" | ||||||||
| environment: | ||||||||
| - NODE_ENV=production | ||||||||
| - PORT=13000 | ||||||||
| - USE_REDIS=1 | ||||||||
| - REDIS_HOST=redis | ||||||||
| - REDIS_PORT=6379 | ||||||||
| ## ===== MySQL Config (uncomment below lines to enable) ===== | ||||||||
| #- DB_HOST=mysql | ||||||||
| #- DB_PORT=3306 | ||||||||
| #- DB_USER=root | ||||||||
| #- DB_PASSWORD=ms_oauth2api_root | ||||||||
| #- DB_DATABASE=ms_oauth2api | ||||||||
| #- DB_WAIT_FOR_CONNECTIONS=true | ||||||||
| #- DB_CONNECTION_LIMIT=10 | ||||||||
|
||||||||
| #- DB_CONNECTION_LIMIT=10 | |
| #- DB_CONNECTION_LIMIT=10 | |
| #- DB_QUEUE_LIMIT=50 |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -29,6 +29,9 @@ class BaseService { | |||||
| * @returns {Promise<Array>} 查询结果 | ||||||
| */ | ||||||
| async query(sql, params = []) { | ||||||
| if (!pool) { | ||||||
| throw new Error('Database not configured. Please set DB_HOST and DB_DATABASE environment variables.'); | ||||||
| } | ||||||
| try { | ||||||
| const [rows] = await pool.query(sql, params); | ||||||
| return rows; | ||||||
|
|
@@ -44,7 +47,7 @@ class BaseService { | |||||
| */ | ||||||
| async findAll(fields = ['*']) { | ||||||
| if (!validateFieldName(fields) && fields[0] !== '*') { | ||||||
| throw new Error('Invalid field names'); | ||||||
| throw new Error('Invalid field names'); | ||||||
| } | ||||||
| const fieldList = fields.join(', '); | ||||||
| return this.query(`SELECT ${fieldList} FROM ??`, [this.tableName]); | ||||||
|
|
@@ -78,15 +81,15 @@ class BaseService { | |||||
| if (!validateFieldName(fields)) { | ||||||
| throw new Error('Invalid field names'); | ||||||
| } | ||||||
|
|
||||||
| const values = fields.map(field => data[field]); | ||||||
| const placeholders = fields.map(() => '?').join(', '); | ||||||
|
|
||||||
| const [result] = await pool.query( | ||||||
| `INSERT INTO ?? (??) VALUES (${placeholders})`, | ||||||
| [this.tableName, fields, ...values] | ||||||
| ); | ||||||
|
Comment on lines
88
to
91
|
||||||
|
|
||||||
| return result.insertId; | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -101,15 +104,15 @@ class BaseService { | |||||
| if (!validateFieldName(fields)) { | ||||||
| throw new Error('Invalid field names'); | ||||||
| } | ||||||
|
|
||||||
| const setClause = fields.map(field => `${field} = ?`).join(', '); | ||||||
| const values = fields.map(field => data[field]); | ||||||
|
|
||||||
| const [result] = await pool.query( | ||||||
| `UPDATE ?? SET ${setClause} WHERE id = ?`, | ||||||
| [this.tableName, ...values, id] | ||||||
| ); | ||||||
|
Comment on lines
111
to
114
|
||||||
|
|
||||||
| return result.affectedRows > 0; | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -139,40 +142,40 @@ class BaseService { | |||||
| offset = 0, | ||||||
| orderBy = 'id ASC' | ||||||
| } = options; | ||||||
|
|
||||||
| if (!validateFieldName(fields) && fields[0] !== '*') { | ||||||
| throw new Error('Invalid field names'); | ||||||
| } | ||||||
|
|
||||||
| const conditionFields = Object.keys(conditions); | ||||||
| if (!validateFieldName(conditionFields)) { | ||||||
| throw new Error('Invalid condition field names'); | ||||||
| } | ||||||
|
|
||||||
| const fieldList = fields.join(', '); | ||||||
| let whereClause = '1 = 1'; | ||||||
| const values = []; | ||||||
|
|
||||||
| if (conditionFields.length > 0) { | ||||||
| whereClause = conditionFields.map(field => { | ||||||
| values.push(conditions[field]); | ||||||
| return `${field} = ?`; | ||||||
| }).join(' AND '); | ||||||
| } | ||||||
|
|
||||||
| // 验证 orderBy | ||||||
| const orderByParts = orderBy.split(' '); | ||||||
| if (orderByParts.length > 2 || (orderByParts[1] && !['ASC', 'DESC'].includes(orderByParts[1].toUpperCase()))) { | ||||||
| throw new Error('Invalid orderBy parameter'); | ||||||
| } | ||||||
|
|
||||||
| const sql = ` | ||||||
| SELECT ${fieldList} FROM ?? | ||||||
| WHERE ${whereClause} | ||||||
| ORDER BY ${orderBy} | ||||||
| LIMIT ? OFFSET ? | ||||||
| `; | ||||||
|
|
||||||
| return this.query(sql, [this.tableName, ...values, limit, offset]); | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -186,22 +189,22 @@ class BaseService { | |||||
| if (conditionFields.length > 0 && !validateFieldName(conditionFields)) { | ||||||
| throw new Error('Invalid condition field names'); | ||||||
| } | ||||||
|
|
||||||
| let whereClause = '1 = 1'; | ||||||
| const values = []; | ||||||
|
|
||||||
| if (conditionFields.length > 0) { | ||||||
| whereClause = conditionFields.map(field => { | ||||||
| values.push(conditions[field]); | ||||||
| return `${field} = ?`; | ||||||
| }).join(' AND '); | ||||||
| } | ||||||
|
|
||||||
| const [result] = await pool.query( | ||||||
|
||||||
| const [result] = await pool.query( | |
| const [result] = await this.query( |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,14 +1,22 @@ | ||||||
| const mysql = require('mysql2/promise'); | ||||||
|
|
||||||
| const pool = mysql.createPool({ | ||||||
| host: process.env.DB_HOST, | ||||||
| port: process.env.DB_PORT, | ||||||
| user: process.env.DB_USER, | ||||||
| password: process.env.DB_PASSWORD, | ||||||
| database: process.env.DB_DATABASE, | ||||||
| waitForConnections: process.env.DB_WAIT_FOR_CONNECTIONS, | ||||||
| connectionLimit: process.env.DB_CONNECTION_LIMIT, | ||||||
| queueLimit: process.env.DB_QUEUE_LIMIT, | ||||||
| }); | ||||||
| let pool = null; | ||||||
|
|
||||||
| // Only create pool if database configuration is provided | ||||||
| if (process.env.DB_HOST && process.env.DB_DATABASE) { | ||||||
| pool = mysql.createPool({ | ||||||
| host: process.env.DB_HOST, | ||||||
| port: process.env.DB_PORT || 3306, | ||||||
|
||||||
| port: process.env.DB_PORT || 3306, | |
| port: parseInt(process.env.DB_PORT) || 3306, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment "skip type-check for faster builds" is misleading because
npm run build-onlyis a standard Next.js/Vite command that naturally skips type checking (unlikenpm run build). Consider removing or clarifying this comment to avoid confusion.