加入了登录注册页面(ptstation-register文件夹),需启动react前端和创建虚拟环境并安装requirements.txt并运行run_server.py

Change-Id: I6abba3854c517286245d118a591e00761ac98685
diff --git a/ptstation-register/backend/backend.py b/ptstation-register/backend/backend.py
new file mode 100644
index 0000000..f3d6aba
--- /dev/null
+++ b/ptstation-register/backend/backend.py
@@ -0,0 +1,334 @@
+from flask import Flask, jsonify, request, current_app
+from flask_cors import CORS
+import traceback
+import hashlib
+import requests
+from bs4 import BeautifulSoup, MarkupResemblesLocatorWarning
+import warnings
+import config
+import pymysql.cursors  # 确保导入了 pymysql 的 cursors
+from pyngrok import ngrok
+from flask import Flask, request, jsonify
+from flask_cors import CORS
+from smtplib import SMTP_SSL
+from email.mime.text import MIMEText
+import random, datetime
+import bcrypt
+import mysql.connector
+
+
+app = Flask(__name__)
+CORS(app, supports_credentials=True)  # 支持 cookies(可选)
+
+# 商户ID和密钥
+merchant_id = 4058
+merchant_key = '2c9e609b503698c6f1dba301725ec631'
+base_url = 'https://api.ltrhcj.cn/api/paySubmit'  # 请求网关
+
+def generate_md5_signature(*args):
+    """根据传入的参数生成MD5签名"""
+    sign_str = ''.join(args)
+    return hashlib.md5(sign_str.encode('utf-8')).hexdigest()
+
+@app.route('/paySuccess', methods=['POST', 'GET'])
+def pay_success():
+    data = request.form if request.method == 'POST' else request.args
+    orderid = data.get('orderid')
+    state = data.get('state')
+    amount = data.get('amount')
+    payment = data.get('payment')
+    rate = data.get('rate')
+    sign = data.get('sign')
+
+    if not orderid or not state or not amount or not payment or not sign:
+        return "Missing parameter", 400
+
+    try:
+        print("Received paySuccess notification:")
+        print(f"orderid: {orderid}")
+        print(f"state: {state}")
+        print(f"amount: {amount}")
+        print(f"payment: {payment}")
+        print(f"rate: {rate}")
+        print(f"sign: {sign}")
+
+        # 获取数据库连接
+        connection = config.get_db_connection()
+        cursor = connection.cursor(pymysql.cursors.DictCursor)
+
+        # 查询该订单对应的邮箱和密码
+        cursor.execute("SELECT user_name, password FROM `order` WHERE orderid = %s", (orderid,))
+        result = cursor.fetchone()
+        print("result内容:", result)
+
+        if result:
+            email = result["user_name"]
+            raw_password = result["password"]
+
+            # 查询 sys_user 是否已存在该 email
+            cursor.execute("SELECT user_id FROM sys_user WHERE email = %s", (email,))
+            user_exists = cursor.fetchone()
+
+            if not user_exists:
+                # 加密密码(如果已经加密过,可以略过这一步)
+
+                cursor.execute("""
+                    INSERT INTO sys_user (
+                        user_name, nick_name, password, email, create_time, del_flag, status, user_type
+                    ) VALUES (%s, %s, %s, %s, NOW(), '0', '0', '00')
+                """, (email, email, raw_password, email))
+            else:
+                cursor.execute("UPDATE sys_user SET status = '0' WHERE email = %s", (email,))
+
+            # 更新订单状态为已支付
+            cursor.execute("UPDATE `order` SET state = %s WHERE orderid = %s", ('已支付', orderid))
+
+            connection.commit()
+
+        cursor.close()
+        connection.close()
+
+        return "success", 200
+
+    except Exception as e:
+        import traceback
+        current_app.logger.error(f'发生异常: {e}')
+        traceback.print_exc()
+        return "Internal server error", 500
+
+
+
+
+@app.route('/user/createOrder', methods=['POST'])
+def create_order():
+    data = request.json
+    print(data)
+    orderid = data.get('orderid')
+    paytype = data.get('paytype')
+    money = data.get('money')
+    notifyUrl = data.get('notifyUrl', '')
+    returnUrl = data.get('returnUrl', '')
+    returnmsg = 2
+    user_name = data.get('user_name')
+    password = data.get('password')  # 👈 获取前端传来的加密密码
+
+    print(data)
+    if not orderid or not paytype or not money or not user_name:
+        return jsonify({'success': False, 'message': '缺少必传参数'}), 400
+
+    try:
+        sign = generate_md5_signature(str(merchant_id), orderid, str(paytype), str(money), merchant_key)
+        payload = {
+            'appid': merchant_id,
+            'orderid': orderid,
+            'paytype': paytype,
+            'money': money,
+            'sign': sign,
+            'notifyUrl': notifyUrl,
+            'returnUrl': returnUrl,
+            'returnmsg': returnmsg
+        }
+
+        # 发送POST请求到外部 API
+        response = requests.post(base_url, data=payload)
+        current_app.logger.debug(f'请求URL: {base_url}')
+        current_app.logger.debug(f'请求数据: {payload}')
+
+        if response.status_code == 200:
+            # 将订单信息插入到数据库的 order 表中
+            connection = config.get_db_connection()
+            cursor = connection.cursor()
+            cursor.execute(
+                "INSERT INTO `order` (orderid, user_name, password, time, amount, state) VALUES (%s, %s, %s, NOW(), %s, %s)",
+                (orderid, user_name, password, money, 'pending')
+            )
+
+            connection.commit()
+            cursor.close()
+            connection.close()
+
+            return response.text, 200
+        else:
+            return jsonify({'success': False, 'message': f"外部 API 错误: {response.text}"}), response.status_code
+    except Exception as e:
+        current_app.logger.error(f'发生异常: {e}')
+        traceback.print_exc()
+        return jsonify({'success': False, 'message': f"Error: {e}"}), 530
+
+
+
+
+
+
+
+# 邮箱配置
+SMTP_SERVER = 'smtp.qq.com'
+EMAIL_ADDRESS = '3534185780@qq.com'
+EMAIL_AUTH_CODE = 'slcsbwtrwitbcjic'
+
+# 数据库配置
+DB_CONFIG = {
+    'host': '49.233.215.144',
+    'port': 3306,
+    'user': 'sy',
+    'password': 'sy_password',
+    'database': 'pt_station'
+}
+
+
+# === 发送验证码接口 ===
+@app.route('/send-code', methods=['POST'])
+def send_code():
+    data = request.get_json()
+    to_email = data.get('email')
+
+    if not to_email:
+        return jsonify({"success": False, "message": "缺少邮箱"}), 400
+
+    code = str(random.randint(100000, 999999))
+    msg = MIMEText(f"欢迎注册PTStation,您的验证码是:{code}(有效期5分钟)")
+    msg['Subject'] = '您的验证码'
+    msg['From'] = EMAIL_ADDRESS
+    msg['To'] = to_email
+
+    try:
+        # 发送邮件
+        with SMTP_SSL(SMTP_SERVER, 465) as smtp:
+            smtp.login(EMAIL_ADDRESS, EMAIL_AUTH_CODE)
+            smtp.send_message(msg)
+            smtp.quit()
+        # 存入数据库
+        conn = mysql.connector.connect(**DB_CONFIG)
+        cursor = conn.cursor()
+        sql = "INSERT INTO email_verification (email, code, created_at) VALUES (%s, %s, NOW())"
+        cursor.execute(sql, (to_email, code))
+        conn.commit()
+        cursor.close()
+        conn.close()
+
+        return jsonify({"success": True})
+    except Exception as e:
+        print("发送失败:", e)
+        return jsonify({"success": False, "message": f"邮件发送失败:{e}"}), 500
+
+
+# === 注册接口 ===
+@app.route('/register', methods=['POST'])
+def register():
+    data = request.get_json()
+    email = data.get('email')
+    password = data.get('password')  # 可以保留做哈希后传给支付回调存库
+    code = data.get('code')
+
+    if not all([email, password, code]):
+        return jsonify({"success": False, "message": "缺少参数"}), 400
+
+    try:
+        conn = mysql.connector.connect(**DB_CONFIG)
+        cursor = conn.cursor(dictionary=True)
+
+        # 1. 验证验证码是否有效(5分钟内)
+        cursor.execute("""
+            SELECT * FROM email_verification 
+            WHERE email = %s AND code = %s AND created_at > NOW() - INTERVAL 5 MINUTE
+            ORDER BY created_at DESC LIMIT 1
+        """, (email, code))
+        result = cursor.fetchone()
+
+        if not result:
+            return jsonify({"success": False, "message": "验证码无效或已过期"}), 400
+
+        # 2. 检查邮箱是否已注册
+        cursor.execute("SELECT user_id FROM sys_user WHERE email = %s", (email,))
+        if cursor.fetchone():
+            return jsonify({"success": False, "message": "该邮箱已注册"}), 400
+
+        # ⚠️ 不再插入数据库,只返回验证通过
+        return jsonify({"success": True, "message": "验证通过"})
+
+    except Exception as e:
+        print("注册失败:", e)
+        return jsonify({"success": False, "message": f"注册失败:{e}"}), 500
+    finally:
+        cursor.close()
+        conn.close()
+
+
+
+@app.route('/reset-password', methods=['POST'])
+def reset_password():
+    data = request.get_json()
+    email = data.get('email')
+    code = data.get('code')
+    new_password = data.get('newPassword')
+
+    if not all([email, code, new_password]):
+        return jsonify({"success": False, "message": "参数缺失"}), 400
+
+    try:
+        conn = mysql.connector.connect(**DB_CONFIG)
+        cursor = conn.cursor()
+
+        # 验证验证码是否正确且在有效期内
+        cursor.execute("""
+            SELECT * FROM email_verification 
+            WHERE email = %s AND code = %s 
+              AND created_at > NOW() - INTERVAL 5 MINUTE
+            ORDER BY created_at DESC LIMIT 1
+        """, (email, code))
+        result = cursor.fetchone()
+
+        if not result:
+            return jsonify({"success": False, "message": "验证码错误或已过期"}), 400
+
+        # 加密密码
+        import bcrypt
+        hashed_pwd = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()).decode()
+
+        # 检查用户是否存在
+        cursor.execute("SELECT user_id FROM sys_user WHERE email = %s", (email,))
+        user = cursor.fetchone()
+        if not user:
+            return jsonify({"success": False, "message": "用户不存在"}), 404
+
+        # 更新密码
+        cursor.execute("""
+            UPDATE sys_user 
+            SET password = %s 
+            WHERE email = %s
+        """, (hashed_pwd, email))
+
+        conn.commit()
+        return jsonify({"success": True, "message": "密码已重置"})
+
+    except Exception as e:
+        print("重置失败:", e)
+        return jsonify({"success": False, "message": f"服务器异常:{e}"}), 500
+
+    finally:
+        if cursor:
+            cursor.close()
+        if conn:
+            conn.close()
+
+from flask import send_file, jsonify
+
+@app.route('/ngrok-url', methods=['GET'])
+def ngrok_url():
+    try:
+        with open("ngrok_url.txt", "r", encoding="utf-8") as f:
+            url = f.read().strip()
+            print(url)
+        return jsonify({"backUrl": f"{url}"})
+    except FileNotFoundError:
+        # 回退到本地,避免前端报错
+        return jsonify({"backUrl": "http://localhost:6001"})
+
+if __name__ == '__main__':
+    try:
+        conn = config.get_db_connection()
+        print("数据库连接成功!")
+        conn.close()
+    except Exception as e:
+        print(f"数据库连接失败: {e}")   
+    app.run(host='0.0.0.0', port=6001,debug =True)
diff --git a/ptstation-register/backend/config.py b/ptstation-register/backend/config.py
new file mode 100644
index 0000000..f035308
--- /dev/null
+++ b/ptstation-register/backend/config.py
@@ -0,0 +1,14 @@
+import pymysql
+
+DB_CONFIG = {
+    'host': '49.233.215.144',
+    'port': 3306,
+    'user': 'sy',
+    'password': 'sy_password',
+    'database': 'pt_station',
+    'charset': 'utf8mb4',
+    'cursorclass': pymysql.cursors.DictCursor
+}
+
+def get_db_connection():
+    return pymysql.connect(**DB_CONFIG)
diff --git a/ptstation-register/backend/ngrok.exe b/ptstation-register/backend/ngrok.exe
new file mode 100644
index 0000000..68b587f
--- /dev/null
+++ b/ptstation-register/backend/ngrok.exe
Binary files differ
diff --git a/ptstation-register/backend/ngrok_url.txt b/ptstation-register/backend/ngrok_url.txt
new file mode 100644
index 0000000..364e437
--- /dev/null
+++ b/ptstation-register/backend/ngrok_url.txt
@@ -0,0 +1 @@
+https://f2f9-183-241-154-140.ngrok-free.app
\ No newline at end of file
diff --git a/ptstation-register/backend/requirements.txt b/ptstation-register/backend/requirements.txt
new file mode 100644
index 0000000..914b8c0
--- /dev/null
+++ b/ptstation-register/backend/requirements.txt
@@ -0,0 +1,22 @@
+bcrypt==4.3.0
+beautifulsoup4==4.13.4
+blinker==1.9.0
+certifi==2025.4.26
+charset-normalizer==3.4.2
+click==8.2.1
+colorama==0.4.6
+Flask==3.1.1
+flask-cors==6.0.0
+idna==3.10
+itsdangerous==2.2.0
+Jinja2==3.1.6
+MarkupSafe==3.0.2
+mysql-connector-python==9.3.0
+PyMySQL==1.1.1
+pyngrok==7.2.11
+PyYAML==6.0.2
+requests==2.32.3
+soupsieve==2.7
+typing_extensions==4.14.0
+urllib3==2.4.0
+Werkzeug==3.1.3
diff --git a/ptstation-register/backend/run_server.py b/ptstation-register/backend/run_server.py
new file mode 100644
index 0000000..22f2292
--- /dev/null
+++ b/ptstation-register/backend/run_server.py
@@ -0,0 +1,69 @@
+import subprocess
+import time
+import requests
+import os
+import signal
+from flask import Flask
+from flask_cors import CORS
+
+
+
+NGROK_PORT = 6001
+NGROK_API = 'http://127.0.0.1:4040/api/tunnels'
+NGROK_URL_FILE = 'ngrok_url.txt'
+
+def start_backend():
+    print("🚀 启动 Flask 后端...")
+    return subprocess.Popen(
+        ["venv\\Scripts\\python.exe", "backend.py"],
+        creationflags=subprocess.CREATE_NEW_CONSOLE
+    )
+
+def start_ngrok():
+    print("🌐 启动 Ngrok 隧道(窗口隐藏)...")
+    startupinfo = subprocess.STARTUPINFO()
+    startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
+
+    return subprocess.Popen(
+        ["ngrok", "http", str(NGROK_PORT)],
+        stdout=subprocess.DEVNULL,
+        stderr=subprocess.DEVNULL,
+        startupinfo=startupinfo
+    )
+
+
+def fetch_ngrok_url(retries=10):
+    print("⏳ 正在获取 Ngrok 公网地址...")
+    for _ in range(retries):
+        try:
+            res = requests.get(NGROK_API).json()
+            tunnels = res.get("tunnels")
+            if tunnels:
+                public_url = tunnels[0]["public_url"]
+                with open(NGROK_URL_FILE, "w", encoding="utf-8") as f:
+                    f.write(public_url)
+                print(f"✅ Ngrok 地址: {public_url} 已写入 {NGROK_URL_FILE}")
+                return public_url
+        except Exception:
+            pass
+        time.sleep(1)
+    print("❌ 获取 Ngrok 地址失败!")
+    return None
+
+if __name__ == "__main__":
+    backend_proc = start_backend()
+    time.sleep(3)  # 等后端稍微先启动
+
+    ngrok_proc = start_ngrok()
+    time.sleep(3)  # 等 ngrok 启动
+
+    try:
+        fetch_ngrok_url()
+        print("\n✅ 所有服务已启动,按 Ctrl+C 退出。")
+        while True:
+            time.sleep(1)
+    except KeyboardInterrupt:
+        print("\n🛑 正在关闭服务...")
+        backend_proc.terminate()
+        ngrok_proc.terminate()
+        print("✅ 已关闭")
diff --git a/ptstation-register/backend/send_code_server.py b/ptstation-register/backend/send_code_server.py
new file mode 100644
index 0000000..c45859d
--- /dev/null
+++ b/ptstation-register/backend/send_code_server.py
@@ -0,0 +1,166 @@
+from flask import Flask, request, jsonify
+from flask_cors import CORS
+from smtplib import SMTP_SSL
+from email.mime.text import MIMEText
+import random, datetime
+import bcrypt
+import mysql.connector
+
+# Flask & CORS
+app = Flask(__name__)
+CORS(app)
+
+# 邮箱配置
+SMTP_SERVER = 'smtp.qq.com'
+EMAIL_ADDRESS = '3534185780@qq.com'
+EMAIL_AUTH_CODE = 'slcsbwtrwitbcjic'
+
+# 数据库配置
+DB_CONFIG = {
+    'host': '49.233.215.144',
+    'port': 3306,
+    'user': 'sy',
+    'password': 'sy_password',
+    'database': 'pt_station'
+}
+
+
+# === 发送验证码接口 ===
+@app.route('/send-code', methods=['POST'])
+def send_code():
+    data = request.get_json()
+    to_email = data.get('email')
+
+    if not to_email:
+        return jsonify({"success": False, "message": "缺少邮箱"}), 400
+
+    code = str(random.randint(100000, 999999))
+    msg = MIMEText(f"欢迎注册PTStation,您的验证码是:{code}(有效期5分钟)")
+    msg['Subject'] = '您的验证码'
+    msg['From'] = EMAIL_ADDRESS
+    msg['To'] = to_email
+
+    try:
+        # 发送邮件
+        with SMTP_SSL(SMTP_SERVER, 465) as smtp:
+            smtp.login(EMAIL_ADDRESS, EMAIL_AUTH_CODE)
+            smtp.send_message(msg)
+            smtp.quit()
+        # 存入数据库
+        conn = mysql.connector.connect(**DB_CONFIG)
+        cursor = conn.cursor()
+        sql = "INSERT INTO email_verification (email, code, created_at) VALUES (%s, %s, NOW())"
+        cursor.execute(sql, (to_email, code))
+        conn.commit()
+        cursor.close()
+        conn.close()
+
+        return jsonify({"success": True})
+    except Exception as e:
+        print("发送失败:", e)
+        return jsonify({"success": False, "message": f"邮件发送失败:{e}"}), 500
+
+
+# === 注册接口 ===
+@app.route('/register', methods=['POST'])
+def register():
+    data = request.get_json()
+    email = data.get('email')
+    password = data.get('password')  # 可以保留做哈希后传给支付回调存库
+    code = data.get('code')
+
+    if not all([email, password, code]):
+        return jsonify({"success": False, "message": "缺少参数"}), 400
+
+    try:
+        conn = mysql.connector.connect(**DB_CONFIG)
+        cursor = conn.cursor(dictionary=True)
+
+        # 1. 验证验证码是否有效(5分钟内)
+        cursor.execute("""
+            SELECT * FROM email_verification 
+            WHERE email = %s AND code = %s AND created_at > NOW() - INTERVAL 5 MINUTE
+            ORDER BY created_at DESC LIMIT 1
+        """, (email, code))
+        result = cursor.fetchone()
+
+        if not result:
+            return jsonify({"success": False, "message": "验证码无效或已过期"}), 400
+
+        # 2. 检查邮箱是否已注册
+        cursor.execute("SELECT user_id FROM sys_user WHERE email = %s", (email,))
+        if cursor.fetchone():
+            return jsonify({"success": False, "message": "该邮箱已注册"}), 400
+
+        # ⚠️ 不再插入数据库,只返回验证通过
+        return jsonify({"success": True, "message": "验证通过"})
+
+    except Exception as e:
+        print("注册失败:", e)
+        return jsonify({"success": False, "message": f"注册失败:{e}"}), 500
+    finally:
+        cursor.close()
+        conn.close()
+
+
+
+@app.route('/reset-password', methods=['POST'])
+def reset_password():
+    data = request.get_json()
+    email = data.get('email')
+    code = data.get('code')
+    new_password = data.get('newPassword')
+
+    if not all([email, code, new_password]):
+        return jsonify({"success": False, "message": "参数缺失"}), 400
+
+    try:
+        conn = mysql.connector.connect(**DB_CONFIG)
+        cursor = conn.cursor()
+
+        # 验证验证码是否正确且在有效期内
+        cursor.execute("""
+            SELECT * FROM email_verification 
+            WHERE email = %s AND code = %s 
+              AND created_at > NOW() - INTERVAL 5 MINUTE
+            ORDER BY created_at DESC LIMIT 1
+        """, (email, code))
+        result = cursor.fetchone()
+
+        if not result:
+            return jsonify({"success": False, "message": "验证码错误或已过期"}), 400
+
+        # 加密密码
+        import bcrypt
+        hashed_pwd = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()).decode()
+
+        # 检查用户是否存在
+        cursor.execute("SELECT user_id FROM sys_user WHERE email = %s", (email,))
+        user = cursor.fetchone()
+        if not user:
+            return jsonify({"success": False, "message": "用户不存在"}), 404
+
+        # 更新密码
+        cursor.execute("""
+            UPDATE sys_user 
+            SET password = %s 
+            WHERE email = %s
+        """, (hashed_pwd, email))
+
+        conn.commit()
+        return jsonify({"success": True, "message": "密码已重置"})
+
+    except Exception as e:
+        print("重置失败:", e)
+        return jsonify({"success": False, "message": f"服务器异常:{e}"}), 500
+
+    finally:
+        if cursor:
+            cursor.close()
+        if conn:
+            conn.close()
+
+
+
+if __name__ == '__main__':
+    app.run(port=3001, debug=True)
diff --git a/ptstation-register/backend/test.py b/ptstation-register/backend/test.py
new file mode 100644
index 0000000..b27d10c
--- /dev/null
+++ b/ptstation-register/backend/test.py
@@ -0,0 +1,51 @@
+import pymysql
+import bcrypt
+import config  # 使用你已有的 config.get_db_connection()
+
+def insert_test_user():
+    email = '22301110@bjtu.edu.cn'
+    password = '123456'
+    hashed_pwd = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
+
+    try:
+        conn = config.get_db_connection()
+        cursor = conn.cursor()
+
+        cursor.execute("""
+            INSERT INTO sys_user (
+                user_name, nick_name, password, email, create_time, del_flag, status, user_type
+            ) VALUES (%s, %s, %s, %s, NOW(), '0', '0', '00')
+        """, (email, email, hashed_pwd, email))
+
+        conn.commit()
+        print("✅ 用户插入成功")
+    except Exception as e:
+        print("❌ 插入失败:", e)
+    finally:
+        cursor.close()
+        conn.close()
+
+
+def delete_test_user():
+    email = '22301110@bjtu.edu.cn'
+
+    try:
+        conn = config.get_db_connection()
+        cursor = conn.cursor()
+
+        cursor.execute("DELETE FROM sys_user WHERE email = %s", (email,))
+        conn.commit()
+        print("🗑️ 用户删除成功")
+    except Exception as e:
+        print("❌ 删除失败:", e)
+    finally:
+        cursor.close()
+        conn.close()
+
+
+if __name__ == '__main__':
+    # 测试:插入
+    #insert_test_user()
+
+    # 测试:删除
+    delete_test_user()