diff --git a/init-db.sql b/init-db.sql index f1d2b79..fa87c33 100644 --- a/init-db.sql +++ b/init-db.sql @@ -1,15 +1,24 @@ -- Weibo-HotSign Database Initialization Script for MySQL --- Create tables according to development document specification +-- 创建数据库和表结构 + +CREATE DATABASE IF NOT EXISTS weibo_hotsign DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +USE weibo_hotsign; -- Users table CREATE TABLE IF NOT EXISTS users ( id CHAR(36) PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, - email VARCHAR(255) UNIQUE NOT NULL, - hashed_password VARCHAR(255) NOT NULL, + email VARCHAR(255) UNIQUE, + hashed_password VARCHAR(255), + wx_openid VARCHAR(64) UNIQUE, + wx_nickname VARCHAR(100), + wx_avatar VARCHAR(500), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - is_active BOOLEAN DEFAULT TRUE -); + is_active BOOLEAN DEFAULT TRUE, + INDEX idx_users_email (email), + INDEX idx_users_username (username), + INDEX idx_users_wx_openid (wx_openid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Accounts table CREATE TABLE IF NOT EXISTS accounts ( @@ -22,8 +31,10 @@ CREATE TABLE IF NOT EXISTS accounts ( status VARCHAR(20) DEFAULT 'pending', last_checked_at TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_accounts_user_id (user_id), + INDEX idx_accounts_status (status), FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE -); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Tasks table CREATE TABLE IF NOT EXISTS tasks ( @@ -32,8 +43,10 @@ CREATE TABLE IF NOT EXISTS tasks ( cron_expression VARCHAR(50) NOT NULL, is_enabled BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_tasks_account_id (account_id), + INDEX idx_tasks_is_enabled (is_enabled), FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE CASCADE -); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Signin logs table CREATE TABLE IF NOT EXISTS signin_logs ( @@ -44,21 +57,8 @@ CREATE TABLE IF NOT EXISTS signin_logs ( reward_info JSON, error_message TEXT, signed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + INDEX idx_signin_logs_account_id (account_id), + INDEX idx_signin_logs_signed_at (signed_at), + INDEX idx_signin_logs_status (status), FOREIGN KEY (account_id) REFERENCES accounts(id) -); - --- Create indexes for better performance -CREATE INDEX idx_users_email ON users(email); -CREATE INDEX idx_users_username ON users(username); -CREATE INDEX idx_accounts_user_id ON accounts(user_id); -CREATE INDEX idx_accounts_status ON accounts(status); -CREATE INDEX idx_tasks_account_id ON tasks(account_id); -CREATE INDEX idx_tasks_is_enabled ON tasks(is_enabled); -CREATE INDEX idx_signin_logs_account_id ON signin_logs(account_id); -CREATE INDEX idx_signin_logs_signed_at ON signin_logs(signed_at); -CREATE INDEX idx_signin_logs_status ON signin_logs(status); - --- Insert sample data for testing (optional) --- Note: UUIDs must be provided by the application --- INSERT INTO users (id, username, email, hashed_password) VALUES --- ('your-uuid-here', 'testuser', 'test@example.com', '$2b$12$hashed_password_here'); +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; diff --git a/qrcode.png b/qrcode.png deleted file mode 100644 index 6a25cce..0000000 Binary files a/qrcode.png and /dev/null differ diff --git a/setup_linux.sh b/setup_linux.sh new file mode 100644 index 0000000..09d5a96 --- /dev/null +++ b/setup_linux.sh @@ -0,0 +1,245 @@ +#!/bin/bash +set -e + +# ============================================================ +# Weibo-HotSign Linux 安装运行脚本 +# 使用方式: chmod +x setup_linux.sh && ./setup_linux.sh +# ============================================================ + +# ===================== 请在此处填写配置 ===================== + +MYSQL_HOST="127.0.0.1" +MYSQL_PORT="3306" +MYSQL_USER="root" +MYSQL_PASSWORD="" # ← 填写你的 MySQL 密码 + +REDIS_HOST="127.0.0.1" +REDIS_PORT="6379" +REDIS_PASSWORD="" # ← 填写你的 Redis 密码(没有密码留空) + +# JWT 密钥(生产环境请改成随机长字符串) +JWT_SECRET="change-me-to-a-random-string-in-production" + +# Cookie 加密密钥(32 字节) +COOKIE_KEY="change-me-to-a-32byte-key-prod!" + +# 微信小程序(可选,暂时留空) +WX_APPID="" +WX_SECRET="" + +# 服务端口 +AUTH_PORT=8001 +API_PORT=8000 +FRONTEND_PORT=5000 + +# ===================== 配置结束 ===================== + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +DB_NAME="weibo_hotsign" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[✓]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } +error() { echo -e "${RED}[✗]${NC} $1"; exit 1; } + +echo "" +echo "========================================" +echo " Weibo-HotSign Linux 安装脚本" +echo "========================================" +echo "" + +# ------ 1. 检查系统依赖 ------ + +echo "--- 检查系统依赖 ---" + +command -v python3 >/dev/null 2>&1 || error "未找到 python3,请先安装 Python 3.8+" +PYTHON_VER=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') +info "Python $PYTHON_VER" + +command -v pip3 >/dev/null 2>&1 || command -v pip >/dev/null 2>&1 || error "未找到 pip,请先安装 pip" +PIP_CMD=$(command -v pip3 || command -v pip) +info "pip: $PIP_CMD" + +# ------ 2. 检查 MySQL ------ + +echo "" +echo "--- 检查 MySQL ---" + +command -v mysql >/dev/null 2>&1 || error "未找到 mysql 客户端,请先安装 mysql-client" + +MYSQL_CMD="mysql -h${MYSQL_HOST} -P${MYSQL_PORT} -u${MYSQL_USER}" +if [ -n "$MYSQL_PASSWORD" ]; then + MYSQL_CMD="$MYSQL_CMD -p${MYSQL_PASSWORD}" +fi + +# 测试连接 +if $MYSQL_CMD -e "SELECT 1" >/dev/null 2>&1; then + info "MySQL 连接成功 (${MYSQL_HOST}:${MYSQL_PORT})" +else + error "MySQL 连接失败,请检查地址、端口和密码" +fi + +# ------ 3. 检查 Redis ------ + +echo "" +echo "--- 检查 Redis ---" + +command -v redis-cli >/dev/null 2>&1 || error "未找到 redis-cli,请先安装 redis-tools" + +REDIS_CLI="redis-cli -h ${REDIS_HOST} -p ${REDIS_PORT}" +if [ -n "$REDIS_PASSWORD" ]; then + REDIS_CLI="$REDIS_CLI -a ${REDIS_PASSWORD}" +fi + +REDIS_PING=$($REDIS_CLI --no-auth-warning PING 2>/dev/null || true) +if [ "$REDIS_PING" = "PONG" ]; then + info "Redis 连接成功 (${REDIS_HOST}:${REDIS_PORT})" +else + error "Redis 连接失败,请检查地址、端口和密码" +fi + +# ------ 4. 初始化 MySQL 数据库 ------ + +echo "" +echo "--- 初始化 MySQL 数据库 ---" + +if $MYSQL_CMD -e "USE ${DB_NAME}" >/dev/null 2>&1; then + # 数据库已存在,检查是否需要加微信字段 + HAS_WX=$($MYSQL_CMD -N -e "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='${DB_NAME}' AND TABLE_NAME='users' AND COLUMN_NAME='wx_openid'" 2>/dev/null) + if [ "$HAS_WX" = "0" ]; then + warn "数据库已存在,正在添加微信登录字段..." + $MYSQL_CMD ${DB_NAME} -e " + ALTER TABLE users ADD COLUMN wx_openid VARCHAR(64) UNIQUE AFTER hashed_password; + ALTER TABLE users ADD COLUMN wx_nickname VARCHAR(100) AFTER wx_openid; + ALTER TABLE users ADD COLUMN wx_avatar VARCHAR(500) AFTER wx_nickname; + ALTER TABLE users MODIFY COLUMN email VARCHAR(255) NULL; + ALTER TABLE users MODIFY COLUMN hashed_password VARCHAR(255) NULL; + " 2>/dev/null || true + info "微信字段已添加" + else + info "数据库 ${DB_NAME} 已存在且结构完整" + fi +else + info "正在创建数据库和表..." + $MYSQL_CMD < "${PROJECT_DIR}/init-db.sql" + info "数据库 ${DB_NAME} 创建完成" +fi + +# 创建测试用户(如果 users 表为空) +USER_COUNT=$($MYSQL_CMD -N -e "SELECT COUNT(*) FROM ${DB_NAME}.users" 2>/dev/null) +if [ "$USER_COUNT" = "0" ]; then + info "正在创建测试用户..." + # 用 python 生成 bcrypt 密码 + HASHED_PW=$(python3 -c "import bcrypt; print(bcrypt.hashpw(b'Admin123!', bcrypt.gensalt(12)).decode())") + USER_ID=$(python3 -c "import uuid; print(str(uuid.uuid4()))") + $MYSQL_CMD ${DB_NAME} -e " + INSERT INTO users (id, username, email, hashed_password, is_active) + VALUES ('${USER_ID}', 'admin', 'admin@example.com', '${HASHED_PW}', 1); + " + info "测试用户: admin / Admin123!" +fi + +# ------ 5. 构建 Redis URL ------ + +if [ -n "$REDIS_PASSWORD" ]; then + REDIS_URL="redis://:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT}/0" +else + REDIS_URL="redis://${REDIS_HOST}:${REDIS_PORT}/0" +fi + +# ------ 6. 构建 MySQL URL ------ + +if [ -n "$MYSQL_PASSWORD" ]; then + MYSQL_URL="mysql+aiomysql://${MYSQL_USER}:${MYSQL_PASSWORD}@${MYSQL_HOST}:${MYSQL_PORT}/${DB_NAME}?charset=utf8mb4" +else + MYSQL_URL="mysql+aiomysql://${MYSQL_USER}@${MYSQL_HOST}:${MYSQL_PORT}/${DB_NAME}?charset=utf8mb4" +fi + +# ------ 7. 生成 .env 文件 ------ + +echo "" +echo "--- 生成配置文件 ---" + +cat > "${PROJECT_DIR}/backend/.env" << EOF +# ===== 由 setup_linux.sh 自动生成 ===== + +# MySQL +DATABASE_URL=${MYSQL_URL} + +# Redis +USE_REDIS=true +REDIS_URL=${REDIS_URL} + +# JWT +JWT_SECRET_KEY=${JWT_SECRET} +JWT_ALGORITHM=HS256 +JWT_EXPIRATION_HOURS=24 + +# Cookie 加密 +COOKIE_ENCRYPTION_KEY=${COOKIE_KEY} + +# 微信小程序 +WX_APPID=${WX_APPID} +WX_SECRET=${WX_SECRET} + +# 环境 +ENVIRONMENT=production +EOF +info "backend/.env 已生成" + +cat > "${PROJECT_DIR}/frontend/.env" << EOF +# ===== 由 setup_linux.sh 自动生成 ===== + +FLASK_ENV=production +FLASK_DEBUG=False +SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))") + +API_BASE_URL=http://127.0.0.1:${API_PORT} +AUTH_BASE_URL=http://127.0.0.1:${AUTH_PORT} + +SESSION_TYPE=filesystem +EOF +info "frontend/.env 已生成" + +# ------ 8. 创建虚拟环境并安装依赖 ------ + +echo "" +echo "--- 安装 Python 依赖 ---" + +# 后端 +if [ ! -d "${PROJECT_DIR}/backend/venv" ]; then + info "创建后端虚拟环境..." + python3 -m venv "${PROJECT_DIR}/backend/venv" +fi +source "${PROJECT_DIR}/backend/venv/bin/activate" +pip install -q --upgrade pip +pip install -q -r "${PROJECT_DIR}/backend/requirements.txt" +deactivate +info "后端依赖安装完成" + +# 前端 +if [ ! -d "${PROJECT_DIR}/frontend/venv" ]; then + info "创建前端虚拟环境..." + python3 -m venv "${PROJECT_DIR}/frontend/venv" +fi +source "${PROJECT_DIR}/frontend/venv/bin/activate" +pip install -q --upgrade pip +pip install -q -r "${PROJECT_DIR}/frontend/requirements.txt" +deactivate +info "前端依赖安装完成" + +echo "" +echo "========================================" +echo " 安装完成!" +echo "========================================" +echo "" +echo " MySQL: ${MYSQL_HOST}:${MYSQL_PORT} / ${DB_NAME}" +echo " Redis: ${REDIS_HOST}:${REDIS_PORT}" +echo "" +echo " 启动服务: ./start_linux.sh" +echo " 停止服务: ./stop_linux.sh" +echo "" diff --git a/start_linux.sh b/start_linux.sh new file mode 100644 index 0000000..32858cd --- /dev/null +++ b/start_linux.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +# ============================================================ +# Weibo-HotSign Linux 启动脚本 +# 使用方式: chmod +x start_linux.sh && ./start_linux.sh +# ============================================================ + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +PID_DIR="${PROJECT_DIR}/.pids" +LOG_DIR="${PROJECT_DIR}/logs" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[✓]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } +error() { echo -e "${RED}[✗]${NC} $1"; exit 1; } + +# 创建目录 +mkdir -p "$PID_DIR" "$LOG_DIR" + +# 检查是否已在运行 +check_running() { + local name=$1 + local pidfile="${PID_DIR}/${name}.pid" + if [ -f "$pidfile" ]; then + local pid=$(cat "$pidfile") + if kill -0 "$pid" 2>/dev/null; then + warn "${name} 已在运行 (PID: ${pid}),跳过" + return 0 + else + rm -f "$pidfile" + fi + fi + return 1 +} + +echo "" +echo "========================================" +echo " Weibo-HotSign 启动服务" +echo "========================================" +echo "" + +# ------ 1. 启动 Auth Service (端口 8001) ------ + +if ! check_running "auth_service"; then + info "启动 Auth Service (端口 8001)..." + source "${PROJECT_DIR}/backend/venv/bin/activate" + cd "${PROJECT_DIR}/backend" + PYTHONPATH="${PROJECT_DIR}/backend" nohup python -m uvicorn auth_service.app.main:app \ + --host 0.0.0.0 --port 8001 \ + > "${LOG_DIR}/auth_service.log" 2>&1 & + echo $! > "${PID_DIR}/auth_service.pid" + deactivate + cd "${PROJECT_DIR}" + sleep 2 + info "Auth Service 已启动 (PID: $(cat ${PID_DIR}/auth_service.pid))" +fi + +# ------ 2. 启动 API Service (端口 8000) ------ + +if ! check_running "api_service"; then + info "启动 API Service (端口 8000)..." + source "${PROJECT_DIR}/backend/venv/bin/activate" + cd "${PROJECT_DIR}/backend" + PYTHONPATH="${PROJECT_DIR}/backend" nohup python -m uvicorn api_service.app.main:app \ + --host 0.0.0.0 --port 8000 \ + > "${LOG_DIR}/api_service.log" 2>&1 & + echo $! > "${PID_DIR}/api_service.pid" + deactivate + cd "${PROJECT_DIR}" + sleep 2 + info "API Service 已启动 (PID: $(cat ${PID_DIR}/api_service.pid))" +fi + +# ------ 3. 启动 Frontend (端口 5000) ------ + +if ! check_running "frontend"; then + info "启动 Frontend (端口 5000)..." + source "${PROJECT_DIR}/frontend/venv/bin/activate" + cd "${PROJECT_DIR}/frontend" + nohup python app.py \ + > "${LOG_DIR}/frontend.log" 2>&1 & + echo $! > "${PID_DIR}/frontend.pid" + deactivate + cd "${PROJECT_DIR}" + sleep 2 + info "Frontend 已启动 (PID: $(cat ${PID_DIR}/frontend.pid))" +fi + +echo "" +echo "========================================" +echo " 所有服务已启动" +echo "========================================" +echo "" +echo " 前端界面: http://localhost:5000" +echo " API Service: http://localhost:8000" +echo " Auth Service: http://localhost:8001" +echo "" +echo " 日志目录: ${LOG_DIR}/" +echo " 停止服务: ./stop_linux.sh" +echo " 查看日志: tail -f logs/auth_service.log" +echo "" diff --git a/stop_linux.sh b/stop_linux.sh new file mode 100644 index 0000000..a31a988 --- /dev/null +++ b/stop_linux.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +# ============================================================ +# Weibo-HotSign Linux 停止脚本 +# 使用方式: chmod +x stop_linux.sh && ./stop_linux.sh +# ============================================================ + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +PID_DIR="${PROJECT_DIR}/.pids" + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' + +info() { echo -e "${GREEN}[✓]${NC} $1"; } +warn() { echo -e "${YELLOW}[!]${NC} $1"; } + +echo "" +echo "========================================" +echo " Weibo-HotSign 停止服务" +echo "========================================" +echo "" + +SERVICES=("auth_service" "api_service" "frontend") +SERVICE_NAMES=("Auth Service" "API Service" "Frontend") + +stopped=0 + +for i in "${!SERVICES[@]}"; do + name="${SERVICES[$i]}" + display="${SERVICE_NAMES[$i]}" + pidfile="${PID_DIR}/${name}.pid" + + if [ -f "$pidfile" ]; then + pid=$(cat "$pidfile") + if kill -0 "$pid" 2>/dev/null; then + kill "$pid" 2>/dev/null + # 等待进程退出(最多 5 秒) + for j in $(seq 1 10); do + if ! kill -0 "$pid" 2>/dev/null; then + break + fi + sleep 0.5 + done + # 如果还没退出,强制杀 + if kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null + fi + info "${display} 已停止 (PID: ${pid})" + stopped=$((stopped + 1)) + else + warn "${display} 进程不存在 (PID: ${pid})" + fi + rm -f "$pidfile" + else + warn "${display} 未在运行" + fi +done + +echo "" +if [ $stopped -gt 0 ]; then + info "已停止 ${stopped} 个服务" +else + warn "没有正在运行的服务" +fi +echo ""