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
|
||||
import io
|
||||
|
||||
# 创建图片
|
||||
width, height = 120, 40
|
||||
# 创建图片 - 增大尺寸以便显示更大的字体
|
||||
width, height = 160, 60
|
||||
image = Image.new('RGB', (width, height), color=(255, 255, 255))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
# 添加干扰线
|
||||
for _ in range(5):
|
||||
for _ in range(6):
|
||||
x1 = random.randint(0, width)
|
||||
y1 = random.randint(0, height)
|
||||
x2 = random.randint(0, width)
|
||||
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)
|
||||
y = random.randint(0, height)
|
||||
draw.point((x, y), fill=(random.randint(0, 200), random.randint(0, 200), random.randint(0, 200)))
|
||||
|
||||
# 绘制验证码文字
|
||||
# 绘制验证码文字 - 增大字体
|
||||
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:
|
||||
font = ImageFont.load_default()
|
||||
|
||||
for i, char in enumerate(code):
|
||||
x = 10 + i * 25 + random.randint(-3, 3)
|
||||
y = random.randint(2, 8)
|
||||
x = 12 + i * 35 + random.randint(-3, 3)
|
||||
y = random.randint(5, 12)
|
||||
color = (random.randint(0, 150), random.randint(0, 150), random.randint(0, 150))
|
||||
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:
|
||||
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
|
||||
email_service.send_task_complete_email_async(
|
||||
user_id=user_id,
|
||||
@@ -2538,7 +2539,8 @@ def take_screenshot_for_account(user_id, account_id, browse_type="应读", sourc
|
||||
# 发送任务完成邮件通知
|
||||
try:
|
||||
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
|
||||
if result and result.get('success') and result.get('filename'):
|
||||
screenshot_path = os.path.join(SCREENSHOTS_DIR, result['filename'])
|
||||
@@ -2967,6 +2969,27 @@ def unbind_user_email():
|
||||
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'])
|
||||
@login_required
|
||||
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:
|
||||
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('''
|
||||
UPDATE users
|
||||
SET email = ?, email_verified = ?
|
||||
@@ -843,6 +850,41 @@ def update_user_email(user_id, email, verified=False):
|
||||
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():
|
||||
"""获取所有用户"""
|
||||
with db_pool.get_db() as conn:
|
||||
|
||||
@@ -100,7 +100,7 @@ def init_email_tables():
|
||||
password TEXT DEFAULT '',
|
||||
use_ssl INTEGER DEFAULT 1,
|
||||
use_tls INTEGER DEFAULT 0,
|
||||
sender_name TEXT DEFAULT '知识管理平台',
|
||||
sender_name TEXT DEFAULT '自动化学习',
|
||||
sender_email TEXT DEFAULT '',
|
||||
daily_limit INTEGER DEFAULT 0,
|
||||
daily_sent INTEGER DEFAULT 0,
|
||||
@@ -411,7 +411,7 @@ def create_smtp_config(data: Dict[str, Any]) -> int:
|
||||
password,
|
||||
int(data.get('use_ssl', True)),
|
||||
int(data.get('use_tls', False)),
|
||||
data.get('sender_name', '知识管理平台'),
|
||||
data.get('sender_name', '自动化学习'),
|
||||
data.get('sender_email', ''),
|
||||
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.send(
|
||||
test_email,
|
||||
'知识管理平台 - SMTP配置测试',
|
||||
'自动化学习 - SMTP配置测试',
|
||||
f'这是一封测试邮件。\n\n配置名称: {config["name"]}\nSMTP服务器: {config["host"]}:{config["port"]}\n\n如果您收到此邮件,说明SMTP配置正确。',
|
||||
None,
|
||||
None
|
||||
@@ -1211,7 +1211,7 @@ def send_register_verification_email(
|
||||
text_body = f"""
|
||||
您好,{username}!
|
||||
|
||||
感谢您注册知识管理平台。请点击下面的链接验证您的邮箱地址:
|
||||
感谢您注册自动化学习。请点击下面的链接验证您的邮箱地址:
|
||||
|
||||
{verify_url}
|
||||
|
||||
@@ -1223,7 +1223,7 @@ def send_register_verification_email(
|
||||
# 发送邮件
|
||||
result = send_email(
|
||||
to_email=email,
|
||||
subject='【知识管理平台】邮箱验证',
|
||||
subject='【自动化学习】邮箱验证',
|
||||
body=text_body,
|
||||
html_body=html_body,
|
||||
email_type=EMAIL_TYPE_REGISTER,
|
||||
@@ -1355,7 +1355,7 @@ def send_password_reset_email(
|
||||
# 发送邮件
|
||||
result = send_email(
|
||||
to_email=email,
|
||||
subject='【知识管理平台】密码重置',
|
||||
subject='【自动化学习】密码重置',
|
||||
body=text_body,
|
||||
html_body=html_body,
|
||||
email_type=EMAIL_TYPE_RESET,
|
||||
@@ -1515,7 +1515,7 @@ def send_bind_email_verification(
|
||||
# 发送邮件
|
||||
result = send_email(
|
||||
to_email=email,
|
||||
subject='【知识管理平台】邮箱绑定验证',
|
||||
subject='【自动化学习】邮箱绑定验证',
|
||||
body=text_body,
|
||||
html_body=html_body,
|
||||
email_type=EMAIL_TYPE_BIND,
|
||||
@@ -1832,7 +1832,7 @@ def send_task_complete_email(
|
||||
|
||||
result = send_email(
|
||||
to_email=email,
|
||||
subject=f'【知识管理平台】任务完成 - {account_name}',
|
||||
subject=f'【自动化学习】任务完成 - {account_name}',
|
||||
body=text_body,
|
||||
html_body=html_body,
|
||||
email_type=EMAIL_TYPE_TASK_COMPLETE,
|
||||
@@ -1851,7 +1851,7 @@ def send_task_complete_email(
|
||||
attachment = [{'filename': screenshot_filename, 'data': screenshot_data}]
|
||||
result2 = send_email(
|
||||
to_email=email,
|
||||
subject=f'【知识管理平台】任务截图 - {account_name}',
|
||||
subject=f'【自动化学习】任务截图 - {account_name}',
|
||||
body=f'这是 {account_name} 的任务截图。',
|
||||
attachments=attachment,
|
||||
email_type=EMAIL_TYPE_TASK_COMPLETE,
|
||||
@@ -1898,7 +1898,7 @@ def send_task_complete_email(
|
||||
|
||||
result = send_email(
|
||||
to_email=email,
|
||||
subject=f'【知识管理平台】任务完成 - {account_name}',
|
||||
subject=f'【自动化学习】任务完成 - {account_name}',
|
||||
body=text_body,
|
||||
html_body=html_body,
|
||||
attachments=attachments,
|
||||
|
||||
@@ -768,6 +768,20 @@
|
||||
<button class="btn btn-text" onclick="unbindEmail()" style="color: #e74c3c;">解绑</button>
|
||||
</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>
|
||||
|
||||
<!-- 修改密码 -->
|
||||
@@ -2081,6 +2095,7 @@
|
||||
.then(data => {
|
||||
const bindSection = document.getElementById('emailBindSection');
|
||||
const boundSection = document.getElementById('emailBoundSection');
|
||||
const notifySection = document.getElementById('emailNotifySection');
|
||||
const statusSpan = document.getElementById('emailStatus');
|
||||
const boundEmail = document.getElementById('boundEmail');
|
||||
const bindInput = document.getElementById('bindEmail');
|
||||
@@ -2088,16 +2103,21 @@
|
||||
if (data.email && data.email_verified) {
|
||||
bindSection.style.display = 'none';
|
||||
boundSection.style.display = 'block';
|
||||
notifySection.style.display = 'block';
|
||||
boundEmail.textContent = data.email;
|
||||
statusSpan.innerHTML = '<span style="color: #27ae60;">已验证</span>';
|
||||
// 加载邮件通知偏好
|
||||
loadEmailNotify();
|
||||
} else if (data.email) {
|
||||
bindSection.style.display = 'block';
|
||||
boundSection.style.display = 'none';
|
||||
notifySection.style.display = 'none';
|
||||
bindInput.value = data.email;
|
||||
statusSpan.innerHTML = '<span style="color: #f39c12;">待验证</span>';
|
||||
} else {
|
||||
bindSection.style.display = 'block';
|
||||
boundSection.style.display = 'none';
|
||||
notifySection.style.display = 'none';
|
||||
bindInput.value = '';
|
||||
statusSpan.innerHTML = '<span style="color: #999;">未绑定</span>';
|
||||
}
|
||||
@@ -2105,6 +2125,63 @@
|
||||
.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() {
|
||||
const email = document.getElementById('bindEmail').value.trim();
|
||||
if (!email) {
|
||||
|
||||
Reference in New Issue
Block a user