API/CS3.1.py

399 lines
14 KiB
Python
Raw Permalink Normal View History

2025-06-02 11:43:53 +08:00
import threading
import json
from flask import Flask, jsonify, request
import sqlite3
import socket
2025-06-06 19:10:31 +08:00
import secrets
import time
2025-06-13 20:19:55 +08:00
import os
2025-06-13 20:48:07 +08:00
import logging
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
2025-06-13 20:19:55 +08:00
2025-06-02 11:43:53 +08:00
app = Flask(__name__)
socket_server = socket.socket()
socket_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
2025-06-13 20:19:55 +08:00
# 修改数据结构:使用用户名作为主键
2025-06-13 20:48:07 +08:00
active_connections = {} # {username: {'conn': conn, 'ip': ip, 'last_active': timestamp}}
2025-06-13 20:19:55 +08:00
chat_connections = [] # 所有活跃连接列表
tokens = {} # 令牌管理
2025-06-02 11:43:53 +08:00
def get_db_connection():
conn = sqlite3.connect("usr.db")
conn.row_factory = sqlite3.Row
return conn
2025-06-02 11:43:53 +08:00
def isuserxist(name):
cn = get_db_connection()
csr = cn.cursor()
csr.execute('SELECT * FROM users WHERE name = ?', (name,))
rst = csr.fetchone()
cn.close()
2025-06-13 20:19:55 +08:00
return rst is not None
2025-06-02 11:43:53 +08:00
2025-06-13 20:19:55 +08:00
def ispsswdright(name, passwd):
2025-06-02 11:43:53 +08:00
cn = get_db_connection()
csr = cn.cursor()
2025-06-13 20:19:55 +08:00
csr.execute("SELECT passwd FROM users WHERE name = ?", (name,))
result = csr.fetchone()
cn.close()
return result and result[0] == passwd
def get_avatar(username):
cn = get_db_connection()
csr = cn.cursor()
csr.execute("SELECT avatar FROM users WHERE name = ?", (username,))
result = csr.fetchone()
cn.close()
return result[0] if result else "default_avatar.png"
2025-06-13 20:19:55 +08:00
def register_user(usr, pwd, avatar="default_avatar.png"):
2025-06-02 11:43:53 +08:00
conn = get_db_connection()
2025-06-13 20:19:55 +08:00
try:
cursor = conn.cursor()
cursor.execute("INSERT INTO users (name, passwd, avatar) VALUES (?, ?, ?)",
(usr, pwd, avatar))
conn.commit()
return {"type": "register_1", "success": True, "message": "User registered successfully"}
except sqlite3.IntegrityError:
return {"type": "register_0", "success": False, "message": "Username already exists"}
2025-06-13 20:19:55 +08:00
except sqlite3.Error as e:
return {"type": "register_0", "success": False, "message": str(e)}
finally:
conn.close()
2025-06-06 19:10:31 +08:00
def generate_token(username):
token = secrets.token_hex(16)
tokens[token] = {'username': username, 'timestamp': time.time()}
return token
def validate_token(token):
if token in tokens:
if time.time() - tokens[token]['timestamp'] < 3600:
tokens[token]['timestamp'] = time.time()
return tokens[token]['username']
return None
@app.route("/api/register", methods=['POST'])
def register1():
vl = request.get_json()
usr = vl.get('username')
pwd = vl.get('password')
2025-06-13 20:48:07 +08:00
avatar = vl.get('avatar', 'default_avatar.png')
2025-06-13 20:19:55 +08:00
if avatar and not (avatar.endswith('.png') or avatar.endswith('.jpg')):
return jsonify({
"type": "register_0",
"success": False,
"message": "Invalid avatar format. Only .png or .jpg allowed"
}), 400
result = register_user(usr, pwd, avatar)
if result['success']:
return jsonify(result)
else:
return jsonify(result), 403 if result['message'] == "Username already exists" else 500
2025-06-06 19:10:31 +08:00
@app.route("/api/login", methods=['POST'])
def login():
data = request.get_json()
2025-06-13 20:19:55 +08:00
username = data['username']
# 检查账号是否已登录
if username in active_connections:
2025-06-13 20:48:07 +08:00
# 检查连接是否仍然活跃
conn_info = active_connections[username]
try:
# 发送测试消息检查连接是否有效
conn_info['conn'].sendall(json.dumps({"type": "ping"}).encode('utf-8'))
logger.info(f"用户 {username} 的连接仍然活跃")
return jsonify({
"type": "login_0",
"status": "error",
"message": "Account already logged in"
}), 409
except:
# 连接已失效,清理旧连接
logger.warning(f"清理无效连接: {username}")
if username in active_connections:
del active_connections[username]
if conn_info['conn'] in chat_connections:
chat_connections.remove(conn_info['conn'])
2025-06-13 20:19:55 +08:00
if isuserxist(username) and ispsswdright(username, data['password']):
token = generate_token(username)
avatar = get_avatar(username)
return jsonify({
"type": "login_1",
"status": "success",
"token": token,
2025-06-13 20:48:07 +08:00
"avatar": avatar
2025-06-13 20:19:55 +08:00
})
return jsonify({
"type": "login_0",
"status": "error",
"message": "Invalid credentials"
}), 401
@app.route("/api/avatar/<username>", methods=['GET'])
def get_user_avatar(username):
avatar = get_avatar(username)
return jsonify({"username": username, "avatar": avatar})
2025-06-06 19:10:31 +08:00
@app.route("/api/chat", methods=['POST'])
def chat():
token = request.headers.get('Authorization')
username = validate_token(token)
if not username:
return jsonify({"type": "chat", "status": "error"}), 401
2025-06-13 20:19:55 +08:00
2025-06-06 19:10:31 +08:00
data = request.get_json()
message = {
"type": "chat",
"user": username,
2025-06-13 20:19:55 +08:00
"message": data['message'],
2025-06-13 20:48:07 +08:00
"avatar": get_avatar(username)
2025-06-06 19:10:31 +08:00
}
broadcast_message(message)
return jsonify({"type": "chat", "status": "success"})
def broadcast_message(message, sender=None):
2025-06-13 20:48:07 +08:00
for conn in chat_connections[:]: # 使用副本迭代
try:
2025-06-13 20:32:24 +08:00
conn.sendall(json.dumps(message).encode('utf-8'))
except:
2025-06-13 20:19:55 +08:00
# 连接异常时移除
for uname, info in list(active_connections.items()):
if info['conn'] == conn:
2025-06-13 20:48:07 +08:00
logger.warning(f"广播时移除无效连接: {uname}")
2025-06-13 20:19:55 +08:00
del active_connections[uname]
break
if conn in chat_connections:
chat_connections.remove(conn)
def handle_socket_message(data, addr, conn):
2025-06-02 11:43:53 +08:00
try:
action = data.get('type')
if action == 'register':
2025-06-13 20:19:55 +08:00
avatar = data.get('avatar', 'default_avatar.png')
result = register_user(data.get('username'), data.get('password'), avatar)
2025-06-13 20:32:24 +08:00
conn.sendall(json.dumps(result).encode('utf-8'))
2025-06-13 20:19:55 +08:00
return result
2025-06-02 11:43:53 +08:00
elif action == 'login':
2025-06-13 20:19:55 +08:00
username = data['username']
password = data['password']
2025-06-13 20:48:07 +08:00
# 检查账号是否已登录且连接有效
2025-06-13 20:19:55 +08:00
if username in active_connections:
2025-06-13 20:48:07 +08:00
conn_info = active_connections[username]
try:
# 测试连接是否仍然有效
conn_info['conn'].sendall(json.dumps({"type": "ping"}).encode('utf-8'))
logger.info(f"用户 {username} 尝试登录但已有活跃连接")
response = {
"type": "login_0",
"status": "error",
"message": "Account already logged in"
}
conn.sendall(json.dumps(response).encode('utf-8'))
return response
except:
# 连接已失效,清理旧连接
logger.warning(f"清理无效连接后允许登录: {username}")
if username in active_connections:
del active_connections[username]
if conn_info['conn'] in chat_connections:
chat_connections.remove(conn_info['conn'])
2025-06-13 20:19:55 +08:00
if isuserxist(username) and ispsswdright(username, password):
# 添加新连接
2025-06-13 20:48:07 +08:00
active_connections[username] = {'conn': conn, 'ip': addr[0], 'last_active': time.time()}
2025-06-13 20:19:55 +08:00
if conn not in chat_connections:
chat_connections.append(conn)
2025-06-13 20:19:55 +08:00
token = generate_token(username)
avatar = get_avatar(username)
response = {
"type": "login_1",
"status": "success",
"message": "Login successful",
"token": token,
"username": username,
2025-06-13 20:48:07 +08:00
"avatar": avatar
2025-06-13 20:19:55 +08:00
}
2025-06-13 20:32:24 +08:00
conn.sendall(json.dumps(response).encode('utf-8'))
2025-06-13 20:48:07 +08:00
logger.info(f"用户 {username} 登录成功")
2025-06-13 20:19:55 +08:00
return response
else:
response = {
"type": "login_0",
"status": "error",
"message": "Invalid credentials"
}
2025-06-13 20:32:24 +08:00
conn.sendall(json.dumps(response).encode('utf-8'))
2025-06-13 20:19:55 +08:00
return response
elif action == 'chat':
2025-06-13 20:32:24 +08:00
token = data.get('token')
if not token:
response = {
"type": "chat",
"status": "error",
"message": "Missing token"
}
conn.sendall(json.dumps(response).encode('utf-8'))
return response
username = validate_token(token)
2025-06-13 20:19:55 +08:00
if not username:
response = {
"type": "chat",
2025-06-13 20:19:55 +08:00
"status": "error",
2025-06-13 20:32:24 +08:00
"message": "Invalid token"
}
2025-06-13 20:32:24 +08:00
conn.sendall(json.dumps(response).encode('utf-8'))
2025-06-13 20:19:55 +08:00
return response
if username not in active_connections:
response = {
"type": "chat",
"status": "error",
"message": "Not logged in"
}
2025-06-13 20:32:24 +08:00
conn.sendall(json.dumps(response).encode('utf-8'))
2025-06-13 20:19:55 +08:00
return response
2025-06-13 20:48:07 +08:00
# 更新最后活跃时间
active_connections[username]['last_active'] = time.time()
2025-06-13 20:19:55 +08:00
message = {
"type": "chat",
"user": username,
"message": data['message'],
2025-06-13 20:48:07 +08:00
"avatar": get_avatar(username)
2025-06-13 20:19:55 +08:00
}
broadcast_message(message)
2025-06-13 20:32:24 +08:00
response = {"type": "chat", "status": "success"}
conn.sendall(json.dumps(response).encode('utf-8'))
return response
2025-06-13 20:19:55 +08:00
2025-06-13 20:48:07 +08:00
elif action == 'heartbeat':
# 心跳检测
token = data.get('token')
if token:
username = validate_token(token)
if username and username in active_connections:
# 更新最后活跃时间
active_connections[username]['last_active'] = time.time()
response = {"type": "heartbeat", "status": "success"}
conn.sendall(json.dumps(response).encode('utf-8'))
return response
return {"type": "heartbeat", "status": "error"}
2025-06-02 11:43:53 +08:00
except Exception as e:
2025-06-13 20:48:07 +08:00
logger.error(f"处理消息时出错: {str(e)}")
2025-06-13 20:32:24 +08:00
response = {
"status": "error",
"message": str(e)
}
2025-06-13 20:48:07 +08:00
try:
conn.sendall(json.dumps(response).encode('utf-8'))
except:
pass
2025-06-13 20:32:24 +08:00
return response
2025-06-13 20:48:07 +08:00
def check_inactive_connections():
"""定期检查不活跃的连接并清理"""
while True:
time.sleep(60) # 每分钟检查一次
current_time = time.time()
inactive_users = []
for username, info in list(active_connections.items()):
# 5分钟无活动视为不活跃
if current_time - info['last_active'] > 300:
logger.warning(f"检测到不活跃用户: {username}, 最后活跃: {current_time - info['last_active']}秒前")
inactive_users.append(username)
for username in inactive_users:
info = active_connections[username]
try:
info['conn'].close()
except:
pass
if username in active_connections:
del active_connections[username]
if info['conn'] in chat_connections:
chat_connections.remove(info['conn'])
logger.info(f"已清理不活跃用户: {username}")
2025-06-02 11:43:53 +08:00
def run_socket_server():
2025-06-13 20:48:07 +08:00
socket_server.bind(("0.0.0.0", 8889))
2025-06-02 11:43:53 +08:00
socket_server.listen()
2025-06-13 20:48:07 +08:00
logger.info("Socket server running on port 8889")
# 启动连接检查线程
threading.Thread(target=check_inactive_connections, daemon=True).start()
2025-06-02 11:43:53 +08:00
while True:
conn, addr = socket_server.accept()
2025-06-13 20:48:07 +08:00
logger.info(f"Socket client connected: {addr}")
2025-06-02 11:43:53 +08:00
try:
while True:
data = conn.recv(1024)
if not data:
break
2025-06-02 11:43:53 +08:00
try:
2025-06-13 20:32:24 +08:00
decoded_data = data.decode('utf-8', errors='ignore')
json_data = json.loads(decoded_data)
response = handle_socket_message(json_data, addr, conn)
2025-06-02 11:43:53 +08:00
except json.JSONDecodeError:
2025-06-13 20:19:55 +08:00
response = {
"type": "error",
"status": "error",
"message": "Invalid JSON"
}
2025-06-13 20:32:24 +08:00
conn.sendall(json.dumps(response).encode('utf-8'))
except Exception as e:
2025-06-13 20:48:07 +08:00
logger.error(f"处理数据时出错: {str(e)}")
2025-06-13 20:32:24 +08:00
response = {
"type": "error",
"status": "error",
"message": str(e)
}
conn.sendall(json.dumps(response).encode('utf-8'))
2025-06-13 20:19:55 +08:00
except (ConnectionResetError, BrokenPipeError):
2025-06-13 20:48:07 +08:00
logger.warning(f"Client {addr} disconnected abruptly")
2025-06-02 11:43:53 +08:00
finally:
2025-06-13 20:19:55 +08:00
# 清理断开的连接
for username, info in list(active_connections.items()):
if info['conn'] == conn:
del active_connections[username]
2025-06-13 20:48:07 +08:00
logger.info(f"用户 {username} 断开连接")
2025-06-13 20:19:55 +08:00
break
if conn in chat_connections:
chat_connections.remove(conn)
2025-06-13 20:19:55 +08:00
try:
conn.close()
except:
pass
2025-06-13 20:48:07 +08:00
logger.info(f"Connection closed for {addr}")
2025-06-02 11:43:53 +08:00
if __name__ == '__main__':
with get_db_connection() as conn:
conn.execute('''CREATE TABLE IF NOT EXISTS users
2025-06-13 20:19:55 +08:00
(name TEXT PRIMARY KEY,
passwd TEXT,
2025-06-13 20:32:24 +08:00
avatar TEXT DEFAULT 'default_avatar.png')''')
2025-06-13 20:19:55 +08:00
threading.Thread(target=run_socket_server, daemon=True).start()
2025-06-13 20:48:07 +08:00
app.run(port=5001, host='0.0.0.0')