mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-02 20:47:35 +08:00
修复bug
This commit is contained in:
parent
cea4e04bf0
commit
cbd3ee64c0
41
README.md
41
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
|
- **用户名**:`admin`
|
||||||
DEFAULT_AI_MODEL=qwen-plus
|
- **默认密码**:`admin123`
|
||||||
DEFAULT_AI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
- **初始化机制**:首次创建数据库时自动创建admin用户
|
||||||
|
|
||||||
# 自动发货配置
|
#### 修改密码方式
|
||||||
AUTO_DELIVERY_ENABLED=true
|
|
||||||
AUTO_DELIVERY_TIMEOUT=30
|
**方式一:Web界面修改(推荐)**
|
||||||
```
|
1. 使用默认密码登录系统
|
||||||
|
2. 进入系统设置页面
|
||||||
|
3. 在"修改密码"区域输入当前密码和新密码
|
||||||
|
4. 点击"修改密码"按钮完成修改
|
||||||
|
|
||||||
|
|
||||||
|
**密码管理机制**:
|
||||||
|
- 数据库初始化时创建admin用户,密码为 `admin123`
|
||||||
|
- 重启时如果用户表已存在,不重新初始化
|
||||||
|
- 所有用户(包括admin)统一使用用户表验证
|
||||||
|
- 密码修改后立即生效,无需重启
|
||||||
|
|
||||||
### 全局配置文件
|
### 全局配置文件
|
||||||
`global_config.yml` 包含详细的系统配置,支持:
|
`global_config.yml` 包含详细的系统配置,支持:
|
||||||
|
@ -298,18 +298,24 @@ class DBManager:
|
|||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
# 插入默认系统设置
|
# 插入默认系统设置(不包括管理员密码,由reply_server.py初始化)
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT OR IGNORE INTO system_settings (key, value, description) VALUES
|
INSERT OR IGNORE INTO system_settings (key, value, description) VALUES
|
||||||
('admin_password_hash', ?, '管理员密码哈希'),
|
|
||||||
('theme_color', 'blue', '主题颜色')
|
('theme_color', 'blue', '主题颜色')
|
||||||
''', (hashlib.sha256("admin123".encode()).hexdigest(),))
|
''')
|
||||||
|
|
||||||
# 创建默认admin用户
|
# 创建默认admin用户(只在首次初始化时创建)
|
||||||
cursor.execute('''
|
cursor.execute('SELECT COUNT(*) FROM users WHERE username = ?', ('admin',))
|
||||||
INSERT OR IGNORE INTO users (username, email, password_hash) VALUES
|
admin_exists = cursor.fetchone()[0] > 0
|
||||||
('admin', 'admin@localhost', ?)
|
|
||||||
''', (hashlib.sha256("admin123".encode()).hexdigest(),))
|
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,用于历史数据绑定
|
# 获取admin用户ID,用于历史数据绑定
|
||||||
cursor.execute("SELECT id FROM users WHERE username = 'admin'")
|
cursor.execute("SELECT id FROM users WHERE username = 'admin'")
|
||||||
@ -1214,19 +1220,7 @@ class DBManager:
|
|||||||
logger.error(f"获取所有系统设置失败: {e}")
|
logger.error(f"获取所有系统设置失败: {e}")
|
||||||
return {}
|
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()
|
password_hash = hashlib.sha256(password.encode()).hexdigest()
|
||||||
return user['password_hash'] == password_hash and user['is_active']
|
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:
|
def generate_verification_code(self) -> str:
|
||||||
"""生成6位数字验证码"""
|
"""生成6位数字验证码"""
|
||||||
return ''.join(random.choices(string.digits, k=6))
|
return ''.join(random.choices(string.digits, k=6))
|
||||||
|
103
reply_server.py
103
reply_server.py
@ -17,19 +17,22 @@ import cookie_manager
|
|||||||
from db_manager import db_manager
|
from db_manager import db_manager
|
||||||
from file_log_collector import setup_file_logging, get_file_log_collector
|
from file_log_collector import setup_file_logging, get_file_log_collector
|
||||||
from ai_reply_engine import ai_reply_engine
|
from ai_reply_engine import ai_reply_engine
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
# 关键字文件路径
|
# 关键字文件路径
|
||||||
KEYWORDS_FILE = Path(__file__).parent / "回复关键字.txt"
|
KEYWORDS_FILE = Path(__file__).parent / "回复关键字.txt"
|
||||||
|
|
||||||
# 简单的用户认证配置
|
# 简单的用户认证配置
|
||||||
ADMIN_USERNAME = "admin"
|
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}}
|
SESSION_TOKENS = {} # 存储会话token: {token: {'user_id': int, 'username': str, 'timestamp': float}}
|
||||||
TOKEN_EXPIRE_TIME = 24 * 60 * 60 # token过期时间:24小时
|
TOKEN_EXPIRE_TIME = 24 * 60 * 60 # token过期时间:24小时
|
||||||
|
|
||||||
# HTTP Bearer认证
|
# HTTP Bearer认证
|
||||||
security = HTTPBearer(auto_error=False)
|
security = HTTPBearer(auto_error=False)
|
||||||
|
|
||||||
|
# 不再需要单独的密码初始化,由数据库初始化时处理
|
||||||
|
|
||||||
|
|
||||||
def load_keywords() -> List[Tuple[str, str]]:
|
def load_keywords() -> List[Tuple[str, str]]:
|
||||||
"""读取关键字→回复映射表
|
"""读取关键字→回复映射表
|
||||||
@ -79,6 +82,11 @@ class LoginResponse(BaseModel):
|
|||||||
user_id: Optional[int] = None
|
user_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ChangePasswordRequest(BaseModel):
|
||||||
|
current_password: str
|
||||||
|
new_password: str
|
||||||
|
|
||||||
|
|
||||||
class RegisterRequest(BaseModel):
|
class RegisterRequest(BaseModel):
|
||||||
username: str
|
username: str
|
||||||
email: str
|
email: str
|
||||||
@ -147,6 +155,19 @@ def verify_token(credentials: Optional[HTTPAuthorizationCredentials] = Depends(s
|
|||||||
return token_data
|
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)):
|
def require_auth(user_info: Optional[Dict[str, Any]] = Depends(verify_token)):
|
||||||
"""需要认证的依赖,返回用户信息"""
|
"""需要认证的依赖,返回用户信息"""
|
||||||
if not user_info:
|
if not user_info:
|
||||||
@ -422,33 +443,7 @@ async def login(request: LoginRequest):
|
|||||||
# 用户名/密码登录
|
# 用户名/密码登录
|
||||||
logger.info(f"【{request.username}】尝试用户名登录")
|
logger.info(f"【{request.username}】尝试用户名登录")
|
||||||
|
|
||||||
# 首先检查是否是admin用户(向后兼容)
|
# 统一使用用户表验证(包括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
|
|
||||||
)
|
|
||||||
|
|
||||||
# 检查普通用户
|
|
||||||
if db_manager.verify_user_password(request.username, request.password):
|
if db_manager.verify_user_password(request.username, request.password):
|
||||||
user = db_manager.get_user_by_username(request.username)
|
user = db_manager.get_user_by_username(request.username)
|
||||||
if user:
|
if user:
|
||||||
@ -460,7 +455,11 @@ async def login(request: LoginRequest):
|
|||||||
'timestamp': time.time()
|
'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(
|
return LoginResponse(
|
||||||
success=True,
|
success=True,
|
||||||
@ -569,6 +568,30 @@ async def logout(credentials: Optional[HTTPAuthorizationCredentials] = Depends(s
|
|||||||
return {"message": "已登出"}
|
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')
|
@app.post('/generate-captcha')
|
||||||
async def generate_captcha(request: CaptchaRequest):
|
async def generate_captcha(request: CaptchaRequest):
|
||||||
@ -829,9 +852,7 @@ class SystemSettingIn(BaseModel):
|
|||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
class PasswordUpdateIn(BaseModel):
|
|
||||||
current_password: str
|
|
||||||
new_password: str
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/cookies")
|
@app.get("/cookies")
|
||||||
@ -1228,25 +1249,7 @@ def get_system_settings(_: None = Depends(require_auth)):
|
|||||||
raise HTTPException(status_code=500, detail=str(e))
|
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}')
|
@app.put('/system-settings/{key}')
|
||||||
|
@ -5797,8 +5797,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${apiBase}/system-settings/password`, {
|
const response = await fetch(`${apiBase}/change-admin-password`, {
|
||||||
method: 'PUT',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${authToken}`,
|
'Authorization': `Bearer ${authToken}`,
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@ -5810,13 +5810,18 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showToast('密码更新成功,请重新登录', 'success');
|
const result = await response.json();
|
||||||
passwordForm.reset();
|
if (result.success) {
|
||||||
// 3秒后跳转到登录页面
|
showToast('密码更新成功,请重新登录', 'success');
|
||||||
setTimeout(() => {
|
passwordForm.reset();
|
||||||
localStorage.removeItem('auth_token');
|
// 3秒后跳转到登录页面
|
||||||
window.location.href = '/login.html';
|
setTimeout(() => {
|
||||||
}, 3000);
|
localStorage.removeItem('auth_token');
|
||||||
|
window.location.href = '/login.html';
|
||||||
|
}, 3000);
|
||||||
|
} else {
|
||||||
|
showToast(`密码更新失败: ${result.message}`, 'danger');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const error = await response.text();
|
const error = await response.text();
|
||||||
showToast(`密码更新失败: ${error}`, 'danger');
|
showToast(`密码更新失败: ${error}`, 'danger');
|
||||||
|
Loading…
x
Reference in New Issue
Block a user