V3.2 送达者=用户 + 用户邮箱必填

- users 表新增 email 列:所有用户(含 admin)必须有邮箱,新建/编辑用户强制校验
- 项目送达者从'邮箱字符串'改为'绑定用户(deliver_user_id)':创建项目必须选择平台用户
- 送达通知实时读取用户最新邮箱:用户改邮箱 → 其送达的所有项目自动跟随,无需改项目
- 项目进行中可随时更换送达者用户(编辑项目 → 送达者下拉)
- 迁移:存量用户自动补 username@tphai.com 默认邮箱;旧项目按 deliver_email 匹配回填 deliver_user_id
- 新增 /api/users 轻量用户列表接口(登录即可,供选送达者);deliver_email 降级为快照缓存
- 前端:项目表单送达者改为用户下拉选择;用户管理增加邮箱列;交付页显示送达者用户名+邮箱
This commit is contained in:
2026-08-13 16:21:35 +08:00
parent 1f37344d03
commit 8be01b5af7
6 changed files with 173 additions and 57 deletions
+19 -11
View File
@@ -110,13 +110,15 @@ def is_admin(username):
return role_of(username) == 'admin'
def create_local_user(username, password, display_name='', role='member'):
def create_local_user(username, password, display_name='', role='member', email=''):
if get_user(username):
return None, '用户已存在'
if not email:
return None, '必填:用户邮箱(用于项目送达/通知)'
uid = db.w(
'INSERT INTO users (username, password_hash, display_name, role, source, status, created_at) '
'VALUES (?,?,?,?,?,?,?)',
(username, db.hash_password(password), display_name or username, role, 'local', 'active', db.now()))
'INSERT INTO users (username, password_hash, display_name, email, role, source, status, created_at) '
'VALUES (?,?,?,?,?,?,?,?)',
(username, db.hash_password(password), display_name or username, email, role, 'local', 'active', db.now()))
return uid, None
@@ -228,14 +230,17 @@ def sso_login(userinfo):
return None, '用户未开通(自动开通未启用),请联系管理员'
role = 'admin' if cfg.get('oidc_admin_group') and cfg['oidc_admin_group'] in groups else 'member'
db.w(
'INSERT INTO users (username, password_hash, display_name, role, source, status, created_at) '
'VALUES (?,?,?,?,?,?,?)',
(username, '', display, role, 'oidc', 'active', db.now()))
'INSERT INTO users (username, password_hash, display_name, email, role, source, status, created_at) '
'VALUES (?,?,?,?,?,?,?,?)',
(username, '', display, email or f'{username}@sso.local', role, 'oidc', 'active', db.now()))
u = get_user(username)
elif u['status'] != 'active':
return None, '账号已停用'
elif u['source'] != 'oidc':
return None, f'用户名 {username} 已被本地账号占用'
# SSO 邮箱变更时同步(送达通知用最新邮箱)
if email and u.get('email') != email:
db.w('UPDATE users SET email=? WHERE id=?', (email, u['id']))
db.w('UPDATE users SET last_login_at=? WHERE id=?', (db.now(), u['id']))
return u, None
@@ -276,12 +281,15 @@ def ldap_login(username, password):
if sso_config().get('sso_auto_provision') != '1':
return None, '用户未开通,请联系管理员'
db.w(
'INSERT INTO users (username, password_hash, display_name, role, source, status, created_at) '
'VALUES (?,?,?,?,?,?,?)',
(username, '', info.get('display') or username, 'member', 'ldap', 'active', db.now()))
'INSERT INTO users (username, password_hash, display_name, email, role, source, status, created_at) '
'VALUES (?,?,?,?,?,?,?,?)',
(username, '', info.get('display') or username, info.get('email') or f'{username}@ldap.local',
'member', 'ldap', 'active', db.now()))
u = get_user(username)
elif u['status'] != 'active':
return None, '账号已停用'
if info.get('email') and u.get('email') != info['email']:
db.w('UPDATE users SET email=? WHERE id=?', (info['email'], u['id']))
db.w('UPDATE users SET last_login_at=? WHERE id=?', (db.now(), u['id']))
return u, None
@@ -296,7 +304,7 @@ def export_all():
'eval_results', 'templates', 'users', 'audit_logs',
'project_deliverables', 'user_projects', 'user_workers']
out = {'exported_at': time.strftime('%Y-%m-%d %H:%M:%S'),
'platform': 'ai-worker-platform', 'version': 'v3.1.0'}
'platform': 'ai-worker-platform', 'version': 'v3.2.0'}
for t in tables:
try:
out[t] = db.q(f'SELECT * FROM {t}')