From d25a4c6862564a4adb9032779a438b9b7dd585eb Mon Sep 17 00:00:00 2001 From: dekun Date: Sat, 17 Oct 2026 19:05:41 +0800 Subject: [PATCH] Allow changing username and password in settings; admin can delete duplicate accounts. Co-authored-by: Cursor --- app/db.py | 67 ++++++++++++++++++++++++++++++++++--- app/routes.py | 45 +++++++++++++++++++++++++ app/templates/accounts.html | 10 +++++- app/templates/login.html | 2 +- app/templates/settings.html | 23 ++++++++----- docs/usage.md | 10 +++--- 6 files changed, 137 insertions(+), 20 deletions(-) diff --git a/app/db.py b/app/db.py index 292f052..00191fa 100644 --- a/app/db.py +++ b/app/db.py @@ -387,20 +387,77 @@ def verify_login(username: str, password: str) -> Optional[dict]: def change_user_password(user_id: int, old_password: str, new_password: str) -> None: + change_user_credentials(user_id, old_password, new_username=None, new_password=new_password) + + +def change_user_credentials( + user_id: int, + old_password: str, + new_username: Optional[str] = None, + new_password: Optional[str] = None, +) -> dict: + """校验当前密码后,可改用户名和/或密码。返回更新后的用户信息。""" from werkzeug.security import check_password_hash, generate_password_hash - if len(new_password) < 6: + new_username = (new_username or "").strip() or None + new_password = new_password if new_password else None + if new_password is not None and len(new_password) < 6: raise ValueError("新密码至少 6 位") + if new_username is not None and len(new_username) < 2: + raise ValueError("用户名至少 2 位") + with get_db() as conn: user = get_user(conn, user_id) if not user: raise ValueError("用户不存在") if not check_password_hash(user["password_hash"], old_password): raise ValueError("当前密码不正确") - conn.execute( - "UPDATE users SET password_hash=? WHERE id=?", - (generate_password_hash(new_password), user_id), - ) + + username_changed = bool(new_username and new_username != user["username"]) + password_changed = new_password is not None + if not username_changed and not password_changed: + raise ValueError("请修改用户名或填写新密码") + + if username_changed: + taken = conn.execute( + "SELECT id FROM users WHERE username=? AND id!=?", + (new_username, user_id), + ).fetchone() + if taken: + raise ValueError("用户名已被占用") + conn.execute("UPDATE users SET username=? WHERE id=?", (new_username, user_id)) + + if password_changed: + conn.execute( + "UPDATE users SET password_hash=? WHERE id=?", + (generate_password_hash(new_password), user_id), + ) + + # 同步旧 settings 键(兼容) + updated = get_user(conn, user_id) + if updated and updated.get("role") == "admin": + set_setting(conn, "admin_user", updated["username"]) + if password_changed: + set_setting(conn, "admin_password_hash", updated["password_hash"]) + return updated or user + + +def delete_account(conn: sqlite3.Connection, account_id: int) -> None: + """删除交易账户;若仍被用户绑定则拒绝。""" + bound = conn.execute( + "SELECT id, username FROM users WHERE account_id=?", (account_id,) + ).fetchone() + if bound: + raise ValueError(f"账户已绑定用户 {bound['username']},请先调整用户绑定") + pending = conn.execute( + "SELECT COUNT(*) AS c FROM activations WHERE account_id=? AND status='pending'", + (account_id,), + ).fetchone()["c"] + if pending: + raise ValueError("该账户仍有未使用的激活码,请先作废或用完后再删") + if not conn.execute("SELECT id FROM accounts WHERE id=?", (account_id,)).fetchone(): + raise ValueError("账户不存在") + conn.execute("DELETE FROM accounts WHERE id=?", (account_id,)) def create_user( diff --git a/app/routes.py b/app/routes.py index baf2700..25ce4f6 100644 --- a/app/routes.py +++ b/app/routes.py @@ -95,9 +95,43 @@ def register_routes(app: Flask) -> None: def settings_page(): return render_template("settings.html") + @app.post("/settings/profile") + @login_required + def settings_profile(): + old_pwd = request.form.get("old_password") or "" + new_username = (request.form.get("username") or "").strip() + new_pwd = request.form.get("new_password") or "" + new_pwd2 = request.form.get("new_password2") or "" + if new_pwd or new_pwd2: + if new_pwd != new_pwd2: + flash("两次输入的新密码不一致", "error") + return redirect(url_for("settings_page")) + else: + new_pwd = None + try: + prev = session.get("username") or "" + updated = database.change_user_credentials( + int(session["user_id"]), + old_pwd, + new_username=new_username, + new_password=new_pwd, + ) + session["username"] = updated["username"] + session["user"] = updated["username"] + parts = [] + if updated["username"] != prev: + parts.append("用户名") + if new_pwd: + parts.append("密码") + flash(("、".join(parts) or "资料") + "已更新", "ok") + except ValueError as exc: + flash(str(exc), "error") + return redirect(url_for("settings_page")) + @app.post("/settings/password") @login_required def settings_password(): + """兼容旧表单:仅改密码。""" old_pwd = request.form.get("old_password") or "" new_pwd = request.form.get("new_password") or "" new_pwd2 = request.form.get("new_password2") or "" @@ -216,6 +250,17 @@ def register_routes(app: Flask) -> None: flash("标注已保存", "ok") return redirect(url_for("accounts_page")) + @app.post("/accounts//delete") + @admin_required + def accounts_delete(aid: int): + try: + with database.get_db() as conn: + database.delete_account(conn, aid) + flash("账户已删除", "ok") + except ValueError as exc: + flash(str(exc), "error") + return redirect(url_for("accounts_page")) + @app.get("/machines") @login_required def machines_page(): diff --git a/app/templates/accounts.html b/app/templates/accounts.html index 6ec1959..16435ab 100644 --- a/app/templates/accounts.html +++ b/app/templates/accounts.html @@ -39,7 +39,15 @@ - #{{ a.id }} + + #{{ a.id }} + {% if can_create %} +
+ +
+ {% endif %} + {% endfor %} diff --git a/app/templates/login.html b/app/templates/login.html index aa29f1c..081e436 100644 --- a/app/templates/login.html +++ b/app/templates/login.html @@ -27,7 +27,7 @@
- +
diff --git a/app/templates/settings.html b/app/templates/settings.html index 4b57882..7e19e11 100644 --- a/app/templates/settings.html +++ b/app/templates/settings.html @@ -4,29 +4,34 @@

系统设置

-

修改登录密码

-

修改当前登录用户密码,立即生效。管理员请在「用户」页创建普通用户。

- +

修改登录账号

+

可改用户名和/或密码;须验证当前密码。立即生效。

+ +
- + + +
+
+
- - + +
- +
- +

服务信息

对外地址:{{ public_base }}

端口:5800

-

当前登录用户:{{ session.get('user') }}

+

当前登录用户:{{ current_user.username }}({{ current_user.role }})

通用 exe 放置目录:releases/黄金市价辅助.exe

diff --git a/docs/usage.md b/docs/usage.md index b0b3c56..cdb8511 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -22,13 +22,15 @@ - **账户 / 用户 / 机器**:管理员可见;普通用户仅「我的账户」 - **系统设置**:修改当前登录用户密码 -### 修改密码 +### 修改用户名 / 密码 1. 登录后打开 **系统设置** -2. 输入当前密码与新密码(至少 6 位)→ 保存 -3. 下次登录使用新密码 +2. 可改 **用户名**;改密码则填写新密码(至少 6 位,不改留空) +3. 须填写 **当前密码** 验证 → 保存 +4. 下次用新用户名/密码登录 -> 初始管理员密码来自部署时的 `.env`(默认 admin123);修改后以系统设置为准。 +> 初始管理员来自部署 `.env`(默认 `admin` / `admin123`);改过后以系统设置为准。 +> 早期版本会种子创建 1 个示例交易账户「模拟盘-主」;现已不再自动创建。重复项可在账户页删除。 ## 2. 管理员:建账户与用户