fix: 修复邮箱绑定验证错误及多项改进
1. 修复email_verified字段缺失导致的500错误 2. 将邮件主题从"知识管理平台"改为"自动化学习" 3. 增大验证码字体(28->42)和图片尺寸(120x40->160x60) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
45
app.py
45
app.py
@@ -1045,34 +1045,34 @@ def generate_captcha():
|
|||||||
from PIL import Image, ImageDraw, ImageFont
|
from PIL import Image, ImageDraw, ImageFont
|
||||||
import io
|
import io
|
||||||
|
|
||||||
# 创建图片
|
# 创建图片 - 增大尺寸以便显示更大的字体
|
||||||
width, height = 120, 40
|
width, height = 160, 60
|
||||||
image = Image.new('RGB', (width, height), color=(255, 255, 255))
|
image = Image.new('RGB', (width, height), color=(255, 255, 255))
|
||||||
draw = ImageDraw.Draw(image)
|
draw = ImageDraw.Draw(image)
|
||||||
|
|
||||||
# 添加干扰线
|
# 添加干扰线
|
||||||
for _ in range(5):
|
for _ in range(6):
|
||||||
x1 = random.randint(0, width)
|
x1 = random.randint(0, width)
|
||||||
y1 = random.randint(0, height)
|
y1 = random.randint(0, height)
|
||||||
x2 = random.randint(0, width)
|
x2 = random.randint(0, width)
|
||||||
y2 = random.randint(0, height)
|
y2 = random.randint(0, height)
|
||||||
draw.line([(x1, y1), (x2, y2)], fill=(random.randint(0, 200), random.randint(0, 200), random.randint(0, 200)))
|
draw.line([(x1, y1), (x2, y2)], fill=(random.randint(0, 200), random.randint(0, 200), random.randint(0, 200)), width=1)
|
||||||
|
|
||||||
# 添加干扰点
|
# 添加干扰点
|
||||||
for _ in range(50):
|
for _ in range(80):
|
||||||
x = random.randint(0, width)
|
x = random.randint(0, width)
|
||||||
y = random.randint(0, height)
|
y = random.randint(0, height)
|
||||||
draw.point((x, y), fill=(random.randint(0, 200), random.randint(0, 200), random.randint(0, 200)))
|
draw.point((x, y), fill=(random.randint(0, 200), random.randint(0, 200), random.randint(0, 200)))
|
||||||
|
|
||||||
# 绘制验证码文字
|
# 绘制验证码文字 - 增大字体
|
||||||
try:
|
try:
|
||||||
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 28)
|
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 42)
|
||||||
except:
|
except:
|
||||||
font = ImageFont.load_default()
|
font = ImageFont.load_default()
|
||||||
|
|
||||||
for i, char in enumerate(code):
|
for i, char in enumerate(code):
|
||||||
x = 10 + i * 25 + random.randint(-3, 3)
|
x = 12 + i * 35 + random.randint(-3, 3)
|
||||||
y = random.randint(2, 8)
|
y = random.randint(5, 12)
|
||||||
color = (random.randint(0, 150), random.randint(0, 150), random.randint(0, 150))
|
color = (random.randint(0, 150), random.randint(0, 150), random.randint(0, 150))
|
||||||
draw.text((x, y), char, font=font, fill=color)
|
draw.text((x, y), char, font=font, fill=color)
|
||||||
|
|
||||||
@@ -2169,7 +2169,8 @@ def run_task(user_id, account_id, browse_type, enable_screenshot=True, source="m
|
|||||||
# 发送任务完成邮件通知(不截图时在此发送)
|
# 发送任务完成邮件通知(不截图时在此发送)
|
||||||
try:
|
try:
|
||||||
user_info = database.get_user_by_id(user_id)
|
user_info = database.get_user_by_id(user_id)
|
||||||
if user_info and user_info.get('email'):
|
# 检查用户是否开启了邮件通知
|
||||||
|
if user_info and user_info.get('email') and database.get_user_email_notify(user_id):
|
||||||
account_name = account.remark if account.remark else account.username
|
account_name = account.remark if account.remark else account.username
|
||||||
email_service.send_task_complete_email_async(
|
email_service.send_task_complete_email_async(
|
||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
@@ -2538,7 +2539,8 @@ def take_screenshot_for_account(user_id, account_id, browse_type="应读", sourc
|
|||||||
# 发送任务完成邮件通知
|
# 发送任务完成邮件通知
|
||||||
try:
|
try:
|
||||||
user_info = database.get_user_by_id(user_id)
|
user_info = database.get_user_by_id(user_id)
|
||||||
if user_info and user_info.get('email'):
|
# 检查用户是否开启了邮件通知
|
||||||
|
if user_info and user_info.get('email') and database.get_user_email_notify(user_id):
|
||||||
screenshot_path = None
|
screenshot_path = None
|
||||||
if result and result.get('success') and result.get('filename'):
|
if result and result.get('success') and result.get('filename'):
|
||||||
screenshot_path = os.path.join(SCREENSHOTS_DIR, result['filename'])
|
screenshot_path = os.path.join(SCREENSHOTS_DIR, result['filename'])
|
||||||
@@ -2967,6 +2969,27 @@ def unbind_user_email():
|
|||||||
return jsonify({"error": "解绑失败"}), 500
|
return jsonify({"error": "解绑失败"}), 500
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/user/email-notify', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_user_email_notify():
|
||||||
|
"""获取用户邮件通知偏好"""
|
||||||
|
enabled = database.get_user_email_notify(current_user.id)
|
||||||
|
return jsonify({"enabled": enabled})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/api/user/email-notify', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def update_user_email_notify():
|
||||||
|
"""更新用户邮件通知偏好"""
|
||||||
|
data = request.get_json()
|
||||||
|
enabled = data.get('enabled', True)
|
||||||
|
|
||||||
|
if database.update_user_email_notify(current_user.id, enabled):
|
||||||
|
return jsonify({"success": True})
|
||||||
|
else:
|
||||||
|
return jsonify({"error": "更新失败"}), 500
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/run_stats', methods=['GET'])
|
@app.route('/api/run_stats', methods=['GET'])
|
||||||
@login_required
|
@login_required
|
||||||
def get_run_stats():
|
def get_run_stats():
|
||||||
|
|||||||
42
database.py
42
database.py
@@ -834,6 +834,13 @@ def update_user_email(user_id, email, verified=False):
|
|||||||
"""更新用户邮箱"""
|
"""更新用户邮箱"""
|
||||||
with db_pool.get_db() as conn:
|
with db_pool.get_db() as conn:
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
# 先检查email_verified字段是否存在,不存在则添加
|
||||||
|
try:
|
||||||
|
cursor.execute('SELECT email_verified FROM users LIMIT 1')
|
||||||
|
except:
|
||||||
|
cursor.execute('ALTER TABLE users ADD COLUMN email_verified INTEGER DEFAULT 0')
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
UPDATE users
|
UPDATE users
|
||||||
SET email = ?, email_verified = ?
|
SET email = ?, email_verified = ?
|
||||||
@@ -843,6 +850,41 @@ def update_user_email(user_id, email, verified=False):
|
|||||||
return cursor.rowcount > 0
|
return cursor.rowcount > 0
|
||||||
|
|
||||||
|
|
||||||
|
def update_user_email_notify(user_id, enabled):
|
||||||
|
"""更新用户邮件通知偏好"""
|
||||||
|
with db_pool.get_db() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
# 先检查字段是否存在
|
||||||
|
try:
|
||||||
|
cursor.execute('SELECT email_notify_enabled FROM users LIMIT 1')
|
||||||
|
except:
|
||||||
|
cursor.execute('ALTER TABLE users ADD COLUMN email_notify_enabled INTEGER DEFAULT 1')
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
cursor.execute('''
|
||||||
|
UPDATE users
|
||||||
|
SET email_notify_enabled = ?
|
||||||
|
WHERE id = ?
|
||||||
|
''', (int(enabled), user_id))
|
||||||
|
conn.commit()
|
||||||
|
return cursor.rowcount > 0
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_email_notify(user_id):
|
||||||
|
"""获取用户邮件通知偏好(默认开启)"""
|
||||||
|
with db_pool.get_db() as conn:
|
||||||
|
cursor = conn.cursor()
|
||||||
|
# 先检查字段是否存在
|
||||||
|
try:
|
||||||
|
cursor.execute('SELECT email_notify_enabled FROM users WHERE id = ?', (user_id,))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
if row is None:
|
||||||
|
return True
|
||||||
|
return bool(row[0]) if row[0] is not None else True
|
||||||
|
except:
|
||||||
|
return True # 字段不存在时默认开启
|
||||||
|
|
||||||
|
|
||||||
def get_all_users():
|
def get_all_users():
|
||||||
"""获取所有用户"""
|
"""获取所有用户"""
|
||||||
with db_pool.get_db() as conn:
|
with db_pool.get_db() as conn:
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ def init_email_tables():
|
|||||||
password TEXT DEFAULT '',
|
password TEXT DEFAULT '',
|
||||||
use_ssl INTEGER DEFAULT 1,
|
use_ssl INTEGER DEFAULT 1,
|
||||||
use_tls INTEGER DEFAULT 0,
|
use_tls INTEGER DEFAULT 0,
|
||||||
sender_name TEXT DEFAULT '知识管理平台',
|
sender_name TEXT DEFAULT '自动化学习',
|
||||||
sender_email TEXT DEFAULT '',
|
sender_email TEXT DEFAULT '',
|
||||||
daily_limit INTEGER DEFAULT 0,
|
daily_limit INTEGER DEFAULT 0,
|
||||||
daily_sent INTEGER DEFAULT 0,
|
daily_sent INTEGER DEFAULT 0,
|
||||||
@@ -411,7 +411,7 @@ def create_smtp_config(data: Dict[str, Any]) -> int:
|
|||||||
password,
|
password,
|
||||||
int(data.get('use_ssl', True)),
|
int(data.get('use_ssl', True)),
|
||||||
int(data.get('use_tls', False)),
|
int(data.get('use_tls', False)),
|
||||||
data.get('sender_name', '知识管理平台'),
|
data.get('sender_name', '自动化学习'),
|
||||||
data.get('sender_email', ''),
|
data.get('sender_email', ''),
|
||||||
data.get('daily_limit', 0)
|
data.get('daily_limit', 0)
|
||||||
))
|
))
|
||||||
@@ -829,7 +829,7 @@ def test_smtp_config(config_id: int, test_email: str) -> Dict[str, Any]:
|
|||||||
sender.connect()
|
sender.connect()
|
||||||
sender.send(
|
sender.send(
|
||||||
test_email,
|
test_email,
|
||||||
'知识管理平台 - SMTP配置测试',
|
'自动化学习 - SMTP配置测试',
|
||||||
f'这是一封测试邮件。\n\n配置名称: {config["name"]}\nSMTP服务器: {config["host"]}:{config["port"]}\n\n如果您收到此邮件,说明SMTP配置正确。',
|
f'这是一封测试邮件。\n\n配置名称: {config["name"]}\nSMTP服务器: {config["host"]}:{config["port"]}\n\n如果您收到此邮件,说明SMTP配置正确。',
|
||||||
None,
|
None,
|
||||||
None
|
None
|
||||||
@@ -1211,7 +1211,7 @@ def send_register_verification_email(
|
|||||||
text_body = f"""
|
text_body = f"""
|
||||||
您好,{username}!
|
您好,{username}!
|
||||||
|
|
||||||
感谢您注册知识管理平台。请点击下面的链接验证您的邮箱地址:
|
感谢您注册自动化学习。请点击下面的链接验证您的邮箱地址:
|
||||||
|
|
||||||
{verify_url}
|
{verify_url}
|
||||||
|
|
||||||
@@ -1223,7 +1223,7 @@ def send_register_verification_email(
|
|||||||
# 发送邮件
|
# 发送邮件
|
||||||
result = send_email(
|
result = send_email(
|
||||||
to_email=email,
|
to_email=email,
|
||||||
subject='【知识管理平台】邮箱验证',
|
subject='【自动化学习】邮箱验证',
|
||||||
body=text_body,
|
body=text_body,
|
||||||
html_body=html_body,
|
html_body=html_body,
|
||||||
email_type=EMAIL_TYPE_REGISTER,
|
email_type=EMAIL_TYPE_REGISTER,
|
||||||
@@ -1355,7 +1355,7 @@ def send_password_reset_email(
|
|||||||
# 发送邮件
|
# 发送邮件
|
||||||
result = send_email(
|
result = send_email(
|
||||||
to_email=email,
|
to_email=email,
|
||||||
subject='【知识管理平台】密码重置',
|
subject='【自动化学习】密码重置',
|
||||||
body=text_body,
|
body=text_body,
|
||||||
html_body=html_body,
|
html_body=html_body,
|
||||||
email_type=EMAIL_TYPE_RESET,
|
email_type=EMAIL_TYPE_RESET,
|
||||||
@@ -1515,7 +1515,7 @@ def send_bind_email_verification(
|
|||||||
# 发送邮件
|
# 发送邮件
|
||||||
result = send_email(
|
result = send_email(
|
||||||
to_email=email,
|
to_email=email,
|
||||||
subject='【知识管理平台】邮箱绑定验证',
|
subject='【自动化学习】邮箱绑定验证',
|
||||||
body=text_body,
|
body=text_body,
|
||||||
html_body=html_body,
|
html_body=html_body,
|
||||||
email_type=EMAIL_TYPE_BIND,
|
email_type=EMAIL_TYPE_BIND,
|
||||||
@@ -1832,7 +1832,7 @@ def send_task_complete_email(
|
|||||||
|
|
||||||
result = send_email(
|
result = send_email(
|
||||||
to_email=email,
|
to_email=email,
|
||||||
subject=f'【知识管理平台】任务完成 - {account_name}',
|
subject=f'【自动化学习】任务完成 - {account_name}',
|
||||||
body=text_body,
|
body=text_body,
|
||||||
html_body=html_body,
|
html_body=html_body,
|
||||||
email_type=EMAIL_TYPE_TASK_COMPLETE,
|
email_type=EMAIL_TYPE_TASK_COMPLETE,
|
||||||
@@ -1851,7 +1851,7 @@ def send_task_complete_email(
|
|||||||
attachment = [{'filename': screenshot_filename, 'data': screenshot_data}]
|
attachment = [{'filename': screenshot_filename, 'data': screenshot_data}]
|
||||||
result2 = send_email(
|
result2 = send_email(
|
||||||
to_email=email,
|
to_email=email,
|
||||||
subject=f'【知识管理平台】任务截图 - {account_name}',
|
subject=f'【自动化学习】任务截图 - {account_name}',
|
||||||
body=f'这是 {account_name} 的任务截图。',
|
body=f'这是 {account_name} 的任务截图。',
|
||||||
attachments=attachment,
|
attachments=attachment,
|
||||||
email_type=EMAIL_TYPE_TASK_COMPLETE,
|
email_type=EMAIL_TYPE_TASK_COMPLETE,
|
||||||
@@ -1898,7 +1898,7 @@ def send_task_complete_email(
|
|||||||
|
|
||||||
result = send_email(
|
result = send_email(
|
||||||
to_email=email,
|
to_email=email,
|
||||||
subject=f'【知识管理平台】任务完成 - {account_name}',
|
subject=f'【自动化学习】任务完成 - {account_name}',
|
||||||
body=text_body,
|
body=text_body,
|
||||||
html_body=html_body,
|
html_body=html_body,
|
||||||
attachments=attachments,
|
attachments=attachments,
|
||||||
|
|||||||
@@ -768,6 +768,20 @@
|
|||||||
<button class="btn btn-text" onclick="unbindEmail()" style="color: #e74c3c;">解绑</button>
|
<button class="btn btn-text" onclick="unbindEmail()" style="color: #e74c3c;">解绑</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 邮件通知开关 -->
|
||||||
|
<div id="emailNotifySection" style="margin-top: 15px; padding-top: 15px; border-top: 1px dashed #ddd; display: none;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<div>
|
||||||
|
<span style="color: #333;">任务完成通知</span>
|
||||||
|
<div style="font-size: 12px; color: #666; margin-top: 3px;">定时任务完成后发送邮件</div>
|
||||||
|
</div>
|
||||||
|
<label style="position: relative; display: inline-block; width: 50px; height: 26px;">
|
||||||
|
<input type="checkbox" id="emailNotifySwitch" onchange="toggleEmailNotify()" style="opacity: 0; width: 0; height: 0;">
|
||||||
|
<span style="position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: .3s; border-radius: 26px;"></span>
|
||||||
|
<span id="emailNotifySlider" style="position: absolute; content: ''; height: 20px; width: 20px; left: 3px; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%;"></span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 修改密码 -->
|
<!-- 修改密码 -->
|
||||||
@@ -2081,6 +2095,7 @@
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
const bindSection = document.getElementById('emailBindSection');
|
const bindSection = document.getElementById('emailBindSection');
|
||||||
const boundSection = document.getElementById('emailBoundSection');
|
const boundSection = document.getElementById('emailBoundSection');
|
||||||
|
const notifySection = document.getElementById('emailNotifySection');
|
||||||
const statusSpan = document.getElementById('emailStatus');
|
const statusSpan = document.getElementById('emailStatus');
|
||||||
const boundEmail = document.getElementById('boundEmail');
|
const boundEmail = document.getElementById('boundEmail');
|
||||||
const bindInput = document.getElementById('bindEmail');
|
const bindInput = document.getElementById('bindEmail');
|
||||||
@@ -2088,16 +2103,21 @@
|
|||||||
if (data.email && data.email_verified) {
|
if (data.email && data.email_verified) {
|
||||||
bindSection.style.display = 'none';
|
bindSection.style.display = 'none';
|
||||||
boundSection.style.display = 'block';
|
boundSection.style.display = 'block';
|
||||||
|
notifySection.style.display = 'block';
|
||||||
boundEmail.textContent = data.email;
|
boundEmail.textContent = data.email;
|
||||||
statusSpan.innerHTML = '<span style="color: #27ae60;">已验证</span>';
|
statusSpan.innerHTML = '<span style="color: #27ae60;">已验证</span>';
|
||||||
|
// 加载邮件通知偏好
|
||||||
|
loadEmailNotify();
|
||||||
} else if (data.email) {
|
} else if (data.email) {
|
||||||
bindSection.style.display = 'block';
|
bindSection.style.display = 'block';
|
||||||
boundSection.style.display = 'none';
|
boundSection.style.display = 'none';
|
||||||
|
notifySection.style.display = 'none';
|
||||||
bindInput.value = data.email;
|
bindInput.value = data.email;
|
||||||
statusSpan.innerHTML = '<span style="color: #f39c12;">待验证</span>';
|
statusSpan.innerHTML = '<span style="color: #f39c12;">待验证</span>';
|
||||||
} else {
|
} else {
|
||||||
bindSection.style.display = 'block';
|
bindSection.style.display = 'block';
|
||||||
boundSection.style.display = 'none';
|
boundSection.style.display = 'none';
|
||||||
|
notifySection.style.display = 'none';
|
||||||
bindInput.value = '';
|
bindInput.value = '';
|
||||||
statusSpan.innerHTML = '<span style="color: #999;">未绑定</span>';
|
statusSpan.innerHTML = '<span style="color: #999;">未绑定</span>';
|
||||||
}
|
}
|
||||||
@@ -2105,6 +2125,63 @@
|
|||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadEmailNotify() {
|
||||||
|
fetch('/api/user/email-notify')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
const checkbox = document.getElementById('emailNotifySwitch');
|
||||||
|
const slider = document.getElementById('emailNotifySlider');
|
||||||
|
const bg = checkbox.nextElementSibling;
|
||||||
|
checkbox.checked = data.enabled;
|
||||||
|
if (data.enabled) {
|
||||||
|
bg.style.backgroundColor = '#27ae60';
|
||||||
|
slider.style.transform = 'translateX(24px)';
|
||||||
|
} else {
|
||||||
|
bg.style.backgroundColor = '#ccc';
|
||||||
|
slider.style.transform = 'translateX(0)';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleEmailNotify() {
|
||||||
|
const checkbox = document.getElementById('emailNotifySwitch');
|
||||||
|
const slider = document.getElementById('emailNotifySlider');
|
||||||
|
const bg = checkbox.nextElementSibling;
|
||||||
|
const enabled = checkbox.checked;
|
||||||
|
|
||||||
|
// 立即更新UI
|
||||||
|
if (enabled) {
|
||||||
|
bg.style.backgroundColor = '#27ae60';
|
||||||
|
slider.style.transform = 'translateX(24px)';
|
||||||
|
} else {
|
||||||
|
bg.style.backgroundColor = '#ccc';
|
||||||
|
slider.style.transform = 'translateX(0)';
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('/api/user/email-notify', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ enabled: enabled })
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showToast(enabled ? '已开启邮件通知' : '已关闭邮件通知', 'success');
|
||||||
|
} else {
|
||||||
|
// 恢复状态
|
||||||
|
checkbox.checked = !enabled;
|
||||||
|
loadEmailNotify();
|
||||||
|
showToast('设置失败', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
checkbox.checked = !enabled;
|
||||||
|
loadEmailNotify();
|
||||||
|
showToast('网络错误', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function bindEmail() {
|
function bindEmail() {
|
||||||
const email = document.getElementById('bindEmail').value.trim();
|
const email = document.getElementById('bindEmail').value.trim();
|
||||||
if (!email) {
|
if (!email) {
|
||||||
|
|||||||
Reference in New Issue
Block a user