From cbd3ee64c010e8198e7a47e813acad272e030791 Mon Sep 17 00:00:00 2001 From: zhinianboke <115088296+zhinianboke@users.noreply.github.com> Date: Sat, 26 Jul 2025 15:16:14 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 41 +++++++++--------- db_manager.py | 61 +++++++++++++++++---------- reply_server.py | 103 ++++++++++++++++++++++++---------------------- static/index.html | 23 +++++++---- 4 files changed, 127 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index f16ea20..74a3715 100644 --- a/README.md +++ b/README.md @@ -195,31 +195,30 @@ xianyu-auto-reply/ ## ⚙️ 配置说明 -### 环境变量配置 -系统支持通过环境变量或 `.env` 文件进行配置: -```bash -# 基础配置 -WEB_PORT=8080 -ADMIN_USERNAME=admin -ADMIN_PASSWORD=admin123 -JWT_SECRET_KEY=your-secret-key +### 管理员密码配置 -# 多用户系统 -MULTIUSER_ENABLED=true -USER_REGISTRATION_ENABLED=true -EMAIL_VERIFICATION_ENABLED=true -CAPTCHA_ENABLED=true +**重要**:为了系统安全,强烈建议修改默认管理员密码! -# AI回复配置 -AI_REPLY_ENABLED=false -DEFAULT_AI_MODEL=qwen-plus -DEFAULT_AI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 +#### 默认密码 +- **用户名**:`admin` +- **默认密码**:`admin123` +- **初始化机制**:首次创建数据库时自动创建admin用户 -# 自动发货配置 -AUTO_DELIVERY_ENABLED=true -AUTO_DELIVERY_TIMEOUT=30 -``` +#### 修改密码方式 + +**方式一:Web界面修改(推荐)** +1. 使用默认密码登录系统 +2. 进入系统设置页面 +3. 在"修改密码"区域输入当前密码和新密码 +4. 点击"修改密码"按钮完成修改 + + +**密码管理机制**: +- 数据库初始化时创建admin用户,密码为 `admin123` +- 重启时如果用户表已存在,不重新初始化 +- 所有用户(包括admin)统一使用用户表验证 +- 密码修改后立即生效,无需重启 ### 全局配置文件 `global_config.yml` 包含详细的系统配置,支持: diff --git a/db_manager.py b/db_manager.py index 2865649..da02ed2 100644 --- a/db_manager.py +++ b/db_manager.py @@ -298,18 +298,24 @@ class DBManager: ) ''') - # 插入默认系统设置 + # 插入默认系统设置(不包括管理员密码,由reply_server.py初始化) cursor.execute(''' INSERT OR IGNORE INTO system_settings (key, value, description) VALUES - ('admin_password_hash', ?, '管理员密码哈希'), ('theme_color', 'blue', '主题颜色') - ''', (hashlib.sha256("admin123".encode()).hexdigest(),)) + ''') - # 创建默认admin用户 - cursor.execute(''' - INSERT OR IGNORE INTO users (username, email, password_hash) VALUES - ('admin', 'admin@localhost', ?) - ''', (hashlib.sha256("admin123".encode()).hexdigest(),)) + # 创建默认admin用户(只在首次初始化时创建) + cursor.execute('SELECT COUNT(*) FROM users WHERE username = ?', ('admin',)) + admin_exists = cursor.fetchone()[0] > 0 + + if not admin_exists: + # 首次创建admin用户,设置默认密码 + default_password_hash = hashlib.sha256("admin123".encode()).hexdigest() + cursor.execute(''' + INSERT INTO users (username, email, password_hash) VALUES + ('admin', 'admin@localhost', ?) + ''', (default_password_hash,)) + logger.info("创建默认admin用户,密码: admin123") # 获取admin用户ID,用于历史数据绑定 cursor.execute("SELECT id FROM users WHERE username = 'admin'") @@ -1214,19 +1220,7 @@ class DBManager: logger.error(f"获取所有系统设置失败: {e}") return {} - def verify_admin_password(self, password: str) -> bool: - """验证管理员密码""" - stored_hash = self.get_system_setting('admin_password_hash') - if not stored_hash: - return False - - password_hash = hashlib.sha256(password.encode()).hexdigest() - return password_hash == stored_hash - - def update_admin_password(self, new_password: str) -> bool: - """更新管理员密码""" - password_hash = hashlib.sha256(new_password.encode()).hexdigest() - return self.set_system_setting('admin_password_hash', password_hash, '管理员密码哈希') + # 管理员密码现在统一使用用户表管理,不再需要单独的方法 # ==================== 用户管理方法 ==================== @@ -1315,6 +1309,31 @@ class DBManager: password_hash = hashlib.sha256(password.encode()).hexdigest() return user['password_hash'] == password_hash and user['is_active'] + def update_user_password(self, username: str, new_password: str) -> bool: + """更新用户密码""" + with self.lock: + try: + cursor = self.conn.cursor() + password_hash = hashlib.sha256(new_password.encode()).hexdigest() + + cursor.execute(''' + UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP + WHERE username = ? + ''', (password_hash, username)) + + if cursor.rowcount > 0: + self.conn.commit() + logger.info(f"用户 {username} 密码更新成功") + return True + else: + logger.warning(f"用户 {username} 不存在,密码更新失败") + return False + + except Exception as e: + logger.error(f"更新用户密码失败: {e}") + self.conn.rollback() + return False + def generate_verification_code(self) -> str: """生成6位数字验证码""" return ''.join(random.choices(string.digits, k=6)) diff --git a/reply_server.py b/reply_server.py index f647df6..bc28d8d 100644 --- a/reply_server.py +++ b/reply_server.py @@ -17,19 +17,22 @@ import cookie_manager from db_manager import db_manager from file_log_collector import setup_file_logging, get_file_log_collector from ai_reply_engine import ai_reply_engine +from loguru import logger # 关键字文件路径 KEYWORDS_FILE = Path(__file__).parent / "回复关键字.txt" # 简单的用户认证配置 ADMIN_USERNAME = "admin" -ADMIN_PASSWORD_HASH = hashlib.sha256("admin123".encode()).hexdigest() # 默认密码: admin123 +DEFAULT_ADMIN_PASSWORD = "admin123" # 系统初始化时的默认密码 SESSION_TOKENS = {} # 存储会话token: {token: {'user_id': int, 'username': str, 'timestamp': float}} TOKEN_EXPIRE_TIME = 24 * 60 * 60 # token过期时间:24小时 # HTTP Bearer认证 security = HTTPBearer(auto_error=False) +# 不再需要单独的密码初始化,由数据库初始化时处理 + def load_keywords() -> List[Tuple[str, str]]: """读取关键字→回复映射表 @@ -79,6 +82,11 @@ class LoginResponse(BaseModel): user_id: Optional[int] = None +class ChangePasswordRequest(BaseModel): + current_password: str + new_password: str + + class RegisterRequest(BaseModel): username: str email: str @@ -147,6 +155,19 @@ def verify_token(credentials: Optional[HTTPAuthorizationCredentials] = Depends(s return token_data +def verify_admin_token(credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)) -> Dict[str, Any]: + """验证管理员token""" + user_info = verify_token(credentials) + if not user_info: + raise HTTPException(status_code=401, detail="未授权访问") + + # 检查是否是管理员 + if user_info['username'] != ADMIN_USERNAME: + raise HTTPException(status_code=403, detail="需要管理员权限") + + return user_info + + def require_auth(user_info: Optional[Dict[str, Any]] = Depends(verify_token)): """需要认证的依赖,返回用户信息""" if not user_info: @@ -422,33 +443,7 @@ async def login(request: LoginRequest): # 用户名/密码登录 logger.info(f"【{request.username}】尝试用户名登录") - # 首先检查是否是admin用户(向后兼容) - if request.username == ADMIN_USERNAME and db_manager.verify_admin_password(request.password): - # 获取admin用户信息 - admin_user = db_manager.get_user_by_username('admin') - if admin_user: - user_id = admin_user['id'] - else: - user_id = 1 # 默认admin用户ID - - # 生成token - token = generate_token() - SESSION_TOKENS[token] = { - 'user_id': user_id, - 'username': 'admin', - 'timestamp': time.time() - } - - logger.info(f"【admin#{user_id}】登录成功(管理员)") - - return LoginResponse( - success=True, - token=token, - message="登录成功", - user_id=user_id - ) - - # 检查普通用户 + # 统一使用用户表验证(包括admin用户) if db_manager.verify_user_password(request.username, request.password): user = db_manager.get_user_by_username(request.username) if user: @@ -460,7 +455,11 @@ async def login(request: LoginRequest): 'timestamp': time.time() } - logger.info(f"【{user['username']}#{user['id']}】登录成功") + # 区分管理员和普通用户的日志 + if user['username'] == ADMIN_USERNAME: + logger.info(f"【{user['username']}#{user['id']}】登录成功(管理员)") + else: + logger.info(f"【{user['username']}#{user['id']}】登录成功") return LoginResponse( success=True, @@ -569,6 +568,30 @@ async def logout(credentials: Optional[HTTPAuthorizationCredentials] = Depends(s return {"message": "已登出"} +# 修改管理员密码接口 +@app.post('/change-admin-password') +async def change_admin_password(request: ChangePasswordRequest, admin_user: Dict[str, Any] = Depends(verify_admin_token)): + from db_manager import db_manager + + try: + # 验证当前密码(使用用户表验证) + if not db_manager.verify_user_password('admin', request.current_password): + return {"success": False, "message": "当前密码错误"} + + # 更新密码(使用用户表更新) + success = db_manager.update_user_password('admin', request.new_password) + + if success: + logger.info(f"【admin#{admin_user['user_id']}】管理员密码修改成功") + return {"success": True, "message": "密码修改成功"} + else: + return {"success": False, "message": "密码修改失败"} + + except Exception as e: + logger.error(f"修改管理员密码异常: {e}") + return {"success": False, "message": "系统错误"} + + # 生成图形验证码接口 @app.post('/generate-captcha') async def generate_captcha(request: CaptchaRequest): @@ -829,9 +852,7 @@ class SystemSettingIn(BaseModel): description: Optional[str] = None -class PasswordUpdateIn(BaseModel): - current_password: str - new_password: str + @app.get("/cookies") @@ -1228,25 +1249,7 @@ def get_system_settings(_: None = Depends(require_auth)): raise HTTPException(status_code=500, detail=str(e)) -@app.put('/system-settings/password') -def update_admin_password(password_data: PasswordUpdateIn, _: None = Depends(require_auth)): - """更新管理员密码""" - from db_manager import db_manager - try: - # 验证当前密码 - if not db_manager.verify_admin_password(password_data.current_password): - raise HTTPException(status_code=400, detail='当前密码错误') - # 更新密码 - success = db_manager.update_admin_password(password_data.new_password) - if success: - return {'msg': 'password updated'} - else: - raise HTTPException(status_code=400, detail='密码更新失败') - except HTTPException: - raise - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) @app.put('/system-settings/{key}') diff --git a/static/index.html b/static/index.html index cbe9de0..6ef1898 100644 --- a/static/index.html +++ b/static/index.html @@ -5797,8 +5797,8 @@ } try { - const response = await fetch(`${apiBase}/system-settings/password`, { - method: 'PUT', + const response = await fetch(`${apiBase}/change-admin-password`, { + method: 'POST', headers: { 'Authorization': `Bearer ${authToken}`, 'Content-Type': 'application/json' @@ -5810,13 +5810,18 @@ }); if (response.ok) { - showToast('密码更新成功,请重新登录', 'success'); - passwordForm.reset(); - // 3秒后跳转到登录页面 - setTimeout(() => { - localStorage.removeItem('auth_token'); - window.location.href = '/login.html'; - }, 3000); + const result = await response.json(); + if (result.success) { + showToast('密码更新成功,请重新登录', 'success'); + passwordForm.reset(); + // 3秒后跳转到登录页面 + setTimeout(() => { + localStorage.removeItem('auth_token'); + window.location.href = '/login.html'; + }, 3000); + } else { + showToast(`密码更新失败: ${result.message}`, 'danger'); + } } else { const error = await response.text(); showToast(`密码更新失败: ${error}`, 'danger');