From bb5fad47cbbdf668371a47a12c30a7382ef3ca70 Mon Sep 17 00:00:00 2001 From: 521141 <183057865+521141@users.noreply.github.com> Date: Thu, 17 Jul 2025 19:44:01 +0800 Subject: [PATCH] feat(database): add MySQL database support (#201) --- docker-compose.local.yml | 28 ++++++++++++++++++ docker-compose.yml | 28 ++++++++++++++++++ example.env.server | 10 ++++++- scripts/init-mysql.sql | 33 ++++++++++++++++++++++ server/database/cache.ts | 60 ++++++++++++++++++++++++++++++++------- server/database/user.ts | 61 ++++++++++++++++++++++++++++++---------- 6 files changed, 194 insertions(+), 26 deletions(-) create mode 100644 scripts/init-mysql.sql diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 7463ad4..94d30d1 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -13,7 +13,35 @@ services: - JWT_SECRET= - INIT_TABLE=true - ENABLE_CACHE=true + # MySQL Database Configuration (uncomment to use MySQL instead of SQLite) + # - MYSQL_HOST=mysql + # - MYSQL_PORT=3306 + # - MYSQL_USER=newsnow + # - MYSQL_PASSWORD=your_password + # - MYSQL_DATABASE=newsnow + # - MYSQL_SSL=false + # Uncomment the following lines to use MySQL database + # depends_on: + # - mysql + + # Uncomment the following service to use MySQL database + # mysql: + # image: mysql:8.0 + # container_name: newsnow_mysql + # restart: always + # environment: + # MYSQL_ROOT_PASSWORD: root_password + # MYSQL_DATABASE: newsnow + # MYSQL_USER: newsnow + # MYSQL_PASSWORD: your_password + # ports: + # - "3306:3306" + # volumes: + # - mysql_data:/var/lib/mysql + # command: --default-authentication-plugin=mysql_native_password volumes: newsnow_data: name: newsnow_data + # mysql_data: + # name: newsnow_mysql_data diff --git a/docker-compose.yml b/docker-compose.yml index ab4f8da..caaf4f9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,35 @@ services: - JWT_SECRET= - INIT_TABLE=true - ENABLE_CACHE=true + # MySQL Database Configuration (uncomment to use MySQL instead of SQLite) + # - MYSQL_HOST=mysql + # - MYSQL_PORT=3306 + # - MYSQL_USER=newsnow + # - MYSQL_PASSWORD=your_password + # - MYSQL_DATABASE=newsnow + # - MYSQL_SSL=false + # Uncomment the following lines to use MySQL database + # depends_on: + # - mysql + + # Uncomment the following service to use MySQL database + # mysql: + # image: mysql:8.0 + # container_name: newsnow_mysql + # restart: always + # environment: + # MYSQL_ROOT_PASSWORD: root_password + # MYSQL_DATABASE: newsnow + # MYSQL_USER: newsnow + # MYSQL_PASSWORD: your_password + # ports: + # - "3306:3306" + # volumes: + # - mysql_data:/var/lib/mysql + # command: --default-authentication-plugin=mysql_native_password volumes: newsnow_data: name: newsnow_data + # mysql_data: + # name: newsnow_mysql_data diff --git a/example.env.server b/example.env.server index 8c920c5..ff1de4b 100644 --- a/example.env.server +++ b/example.env.server @@ -2,4 +2,12 @@ G_CLIENT_ID= G_CLIENT_SECRET= JWT_SECRET= INIT_TABLE=true -ENABLE_CACHE=true \ No newline at end of file +ENABLE_CACHE=true +# MySQL Database Configuration +# Uncomment and configure these variables to use MySQL instead of SQLite +# MYSQL_HOST=localhost +# MYSQL_PORT=3306 +# MYSQL_USER=newsnow +# MYSQL_PASSWORD=your_password +# MYSQL_DATABASE=newsnow +# MYSQL_SSL=false \ No newline at end of file diff --git a/scripts/init-mysql.sql b/scripts/init-mysql.sql new file mode 100644 index 0000000..febb2dc --- /dev/null +++ b/scripts/init-mysql.sql @@ -0,0 +1,33 @@ +-- MySQL Database Initialization Script for NewsNow +-- Run this script to create the required database and tables + +-- Create database if not exists +CREATE DATABASE IF NOT EXISTS newsnow CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- Use the database +USE newsnow; + +-- Create cache table +CREATE TABLE IF NOT EXISTS cache ( + id VARCHAR(255) PRIMARY KEY, + updated BIGINT, + data TEXT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Create user table +CREATE TABLE IF NOT EXISTS user ( + id VARCHAR(255) PRIMARY KEY, + email VARCHAR(255), + data TEXT, + type VARCHAR(50), + created BIGINT, + updated BIGINT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +-- Create index for user table +CREATE INDEX IF NOT EXISTS idx_user_id ON user(id); + +-- Create MySQL user (optional - you can create this manually) +-- CREATE USER IF NOT EXISTS 'newsnow'@'%' IDENTIFIED BY 'your_password'; +-- GRANT ALL PRIVILEGES ON newsnow.* TO 'newsnow'@'%'; +-- FLUSH PRIVILEGES; \ No newline at end of file diff --git a/server/database/cache.ts b/server/database/cache.ts index 8d0a2f5..ce87a9e 100644 --- a/server/database/cache.ts +++ b/server/database/cache.ts @@ -3,28 +3,68 @@ import type { NewsItem } from "@shared/types" import type { Database } from "db0" import type { CacheInfo, CacheRow } from "../types" +// 改进的数据库类型检测函数 +function isMySQLDatabase(): boolean { + const hasMySQLConfig = process.env.MYSQL_HOST && + process.env.MYSQL_USER && + process.env.MYSQL_PASSWORD && + process.env.MYSQL_DATABASE + + if (hasMySQLConfig) { + console.log('🔍 检测到 MySQL 配置,使用 MySQL 数据库') + return true + } else { + console.log('🔍 未检测到 MySQL 配置,使用 SQLite 数据库') + return false + } +} + export class Cache { private db + private isMySQL: boolean + constructor(db: Database) { this.db = db + this.isMySQL = isMySQLDatabase() } async init() { - await this.db.prepare(` - CREATE TABLE IF NOT EXISTS cache ( - id TEXT PRIMARY KEY, - updated INTEGER, - data TEXT - ); - `).run() + if (this.isMySQL) { + // MySQL syntax + await this.db.prepare(` + CREATE TABLE IF NOT EXISTS cache ( + id VARCHAR(255) PRIMARY KEY, + updated BIGINT, + data TEXT + ); + `).run() + } else { + // SQLite syntax + await this.db.prepare(` + CREATE TABLE IF NOT EXISTS cache ( + id TEXT PRIMARY KEY, + updated INTEGER, + data TEXT + ); + `).run() + } logger.success(`init cache table`) } async set(key: string, value: NewsItem[]) { const now = Date.now() - await this.db.prepare( - `INSERT OR REPLACE INTO cache (id, data, updated) VALUES (?, ?, ?)`, - ).run(key, JSON.stringify(value), now) + + if (this.isMySQL) { + // MySQL syntax - use ON DUPLICATE KEY UPDATE + await this.db.prepare( + `INSERT INTO cache (id, data, updated) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE data = VALUES(data), updated = VALUES(updated)`, + ).run(key, JSON.stringify(value), now) + } else { + // SQLite syntax - use INSERT OR REPLACE + await this.db.prepare( + `INSERT OR REPLACE INTO cache (id, data, updated) VALUES (?, ?, ?)`, + ).run(key, JSON.stringify(value), now) + } logger.success(`set ${key} cache`) } diff --git a/server/database/user.ts b/server/database/user.ts index 8a35039..0c35e0c 100644 --- a/server/database/user.ts +++ b/server/database/user.ts @@ -1,3 +1,4 @@ +import process from "node:process" import type { Database } from "db0" import type { UserInfo } from "#/types" @@ -8,28 +9,58 @@ export class UserTable { } async init() { - await this.db.prepare(` - CREATE TABLE IF NOT EXISTS user ( - id TEXT PRIMARY KEY, - email TEXT, - data TEXT, - type TEXT, - created INTEGER, - updated INTEGER - ); - `).run() - await this.db.prepare(` - CREATE INDEX IF NOT EXISTS idx_user_id ON user(id); - `).run() + // Check if we're using MySQL by looking for MySQL environment variables + const isMySQL = process.env.MYSQL_HOST && process.env.MYSQL_USER && process.env.MYSQL_PASSWORD && process.env.MYSQL_DATABASE + + if (isMySQL) { + // MySQL syntax + await this.db.prepare(` + CREATE TABLE IF NOT EXISTS user ( + id VARCHAR(255) PRIMARY KEY, + email VARCHAR(255), + data TEXT, + type VARCHAR(50), + created BIGINT, + updated BIGINT + ); + `).run() + await this.db.prepare(` + CREATE INDEX IF NOT EXISTS idx_user_id ON user(id); + `).run() + } else { + // SQLite syntax + await this.db.prepare(` + CREATE TABLE IF NOT EXISTS user ( + id TEXT PRIMARY KEY, + email TEXT, + data TEXT, + type TEXT, + created INTEGER, + updated INTEGER + ); + `).run() + await this.db.prepare(` + CREATE INDEX IF NOT EXISTS idx_user_id ON user(id); + `).run() + } logger.success(`init user table`) } async addUser(id: string, email: string, type: "github") { const u = await this.getUser(id) const now = Date.now() + const isMySQL = process.env.MYSQL_HOST && process.env.MYSQL_USER && process.env.MYSQL_PASSWORD && process.env.MYSQL_DATABASE + if (!u) { - await this.db.prepare(`INSERT INTO user (id, email, data, type, created, updated) VALUES (?, ?, ?, ?, ?, ?)`) - .run(id, email, "", type, now, now) + if (isMySQL) { + // MySQL syntax - use ON DUPLICATE KEY UPDATE + await this.db.prepare(`INSERT INTO user (id, email, data, type, created, updated) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE email = VALUES(email), updated = VALUES(updated)`) + .run(id, email, "", type, now, now) + } else { + // SQLite syntax + await this.db.prepare(`INSERT INTO user (id, email, data, type, created, updated) VALUES (?, ?, ?, ?, ?, ?)`) + .run(id, email, "", type, now, now) + } logger.success(`add user ${id}`) } else if (u.email !== email && u.type !== type) { await this.db.prepare(`UPDATE user SET email = ?, updated = ? WHERE id = ?`).run(email, now, id)