[0.2.7.4][ci]
All checks were successful
CI / docker-ci (push) Successful in 24s

This commit is contained in:
DSQ
2026-03-17 22:45:56 +08:00
parent 85dd7bc991
commit 71a0723a74
11 changed files with 1191 additions and 75 deletions

View File

@@ -15,13 +15,14 @@
/* 主内容区 */
.main-content { margin-left: 220px; padding: 40px; }
.profile-card { background: #fff; border-radius: 14px; box-shadow: 0 10px 24px rgba(31,35,40,0.08); padding: 30px; margin-bottom: 30px; }
.profile-card { background: #fff; border-radius: 14px; box-shadow: 0 10px 24px rgba(31,35,40,0.08); padding: 30px; margin-bottom: 40px; }
.rc-card { margin-top: 18px; }
.profile-header { display: flex; align-items: center; margin-bottom: 20px; border-bottom: 1px solid #eee; padding-bottom: 20px; }
.profile-info h2 { margin: 0; color: #1e1e2e; }
.profile-info p { margin: 5px 0; color: #666; }
.label { font-weight: bold; color: #333; margin-right: 10px; }
.section-title { font-size: 20px; font-weight: bold; margin-bottom: 20px; color: #1e1e2e; }
.section-title { font-size: 20px; font-weight: bold; margin: 34px 0 24px; color: #1e1e2e; }
.image-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; }
.image-item { background: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.05); transition: transform 0.2s; }
.image-item:hover { transform: translateY(-5px); }
@@ -66,34 +67,13 @@
<div class="profile-details">
<p><span class="label">用户名:</span> {{ profile_user.username }}</p>
<p><span class="label">用户ID:</span> {{ profile_user.user_id }}</p>
<p><span class="label">注册码:</span> {{ profile_user.registration_code|default:"无" }}</p>
<p><span class="label">所属:</span> {{ profile_user.key|join:"、"|default:"未填写" }}</p>
<p><span class="label">可管理级别:</span> {{ profile_user.manage_key|join:"、"|default:"无" }}</p>
<p><span class="label">权限级别:</span> {{ permission_name }}</p>
</div>
</div>
{% if permission_name != "管理员" and not profile_user.manage_key %}
<div class="profile-card">
<div class="profile-header">
<div class="profile-info">
<h2>修改密码</h2>
</div>
</div>
<form id="pwdForm">
<div class="form-group">
<label for="newPassword">新密码</label>
<input type="password" id="newPassword" autocomplete="new-password" required>
</div>
<div class="form-group">
<label for="confirmPassword">确认密码</label>
<input type="password" id="confirmPassword" autocomplete="new-password" required>
</div>
<button type="submit" class="btn">保存</button>
<div id="pwdMsg" class="msg"></div>
</form>
</div>
{% endif %}
<div class="section-title">我的提交</div>
{% if achievements %}
<div class="image-grid">
@@ -116,6 +96,50 @@
<a href="{% url 'elastic:upload_page' %}" style="color: #2d8cf0; text-decoration: none;">去上传第一张图片吧!</a>
</div>
{% endif %}
<div class="profile-card rc-card">
<div class="profile-header">
<div class="profile-info">
<h2>替换注册码</h2>
</div>
</div>
<form id="rcForm">
<div class="form-group">
<label for="newRegCode">新注册码</label>
<input type="text" id="newRegCode" placeholder="输入新注册码后替换原有 key" required>
</div>
<div class="form-group">
<label>预览</label>
<div id="rcPreview" style="background:#f8fafc; border:1px solid #e5e7eb; border-radius:10px; padding:10px 12px; font-size:13px; color:#334155;">
<div style="color:#64748b;">输入注册码后自动显示 key 预览</div>
</div>
</div>
<button type="submit" class="btn">替换</button>
<div id="rcMsg" class="msg"></div>
</form>
</div>
{% if permission_name != "管理员" and not profile_user.manage_key %}
<div class="profile-card">
<div class="profile-header">
<div class="profile-info">
<h2>修改密码</h2>
</div>
</div>
<form id="pwdForm">
<div class="form-group">
<label for="newPassword">新密码</label>
<input type="password" id="newPassword" autocomplete="new-password" required>
</div>
<div class="form-group">
<label for="confirmPassword">确认密码</label>
<input type="password" id="confirmPassword" autocomplete="new-password" required>
</div>
<button type="submit" class="btn">保存</button>
<div id="pwdMsg" class="msg"></div>
</form>
</div>
{% endif %}
</div>
<!-- 图片放大模态框 -->
@@ -205,6 +229,88 @@
}
});
}
const rcForm = document.getElementById('rcForm');
if (rcForm) {
let rcPreviewTimer = null;
let rcPreviewSeq = 0;
const rcInput = document.getElementById('newRegCode');
const rcPreview = document.getElementById('rcPreview');
async function refreshRcPreview(code) {
const seq = ++rcPreviewSeq;
if (!code) {
rcPreview.innerHTML = '<div style="color:#64748b;">输入注册码后自动显示 key 预览</div>';
return;
}
rcPreview.innerHTML = '<div style="color:#64748b;">正在查询...</div>';
try {
const resp = await fetch(`/accounts/profile/registration-code/preview/?code=${encodeURIComponent(code)}`, { method: 'GET', credentials: 'same-origin' });
const data = await resp.json();
if (seq !== rcPreviewSeq) return;
if (!(resp.ok && data && data.ok)) {
const msg = (data && data.message) ? data.message : '查询失败';
rcPreview.innerHTML = `<div style="color:#b91c1c;">${msg}</div>`;
return;
}
const keys = ((data.data || {}).keys || []).map(String).filter(Boolean);
const manageKeys = ((data.data || {}).manage_keys || []).map(String).filter(Boolean);
const keysText = keys.length ? keys.join('、') : '无';
const manageText = manageKeys.length ? manageKeys.join('、') : '无';
rcPreview.innerHTML = `<div><span style="font-weight:700;">key</span>${keysText}</div><div style="margin-top:6px;"><span style="font-weight:700;">manage_key</span>${manageText}</div>`;
} catch (e) {
if (seq !== rcPreviewSeq) return;
rcPreview.innerHTML = '<div style="color:#b91c1c;">查询失败</div>';
}
}
if (rcInput) {
rcInput.addEventListener('input', () => {
const code = (rcInput.value || '').trim();
if (rcPreviewTimer) window.clearTimeout(rcPreviewTimer);
rcPreviewTimer = window.setTimeout(() => refreshRcPreview(code), 300);
});
refreshRcPreview((rcInput.value || '').trim());
}
rcForm.addEventListener('submit', async (e) => {
e.preventDefault();
const msg = document.getElementById('rcMsg');
msg.textContent = '';
msg.className = 'msg';
const code = (document.getElementById('newRegCode').value || '').trim();
if (!code) {
msg.textContent = '请输入注册码';
msg.className = 'msg error';
return;
}
if (!confirm('确定要替换注册码吗?该操作会替换你当前的 key。')) return;
try {
const csrftoken = getCookie('csrftoken');
const resp = await fetch('/accounts/profile/registration-code/replace/', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken || ''
},
body: JSON.stringify({ code })
});
const data = await resp.json();
if (resp.ok && data.ok) {
msg.textContent = '替换成功';
msg.className = 'msg success';
window.location.reload();
} else {
msg.textContent = (data && data.message) ? data.message : '替换失败';
msg.className = 'msg error';
}
} catch (err) {
msg.textContent = '替换失败';
msg.className = 'msg error';
}
});
}
</script>
</body>
</html>

View File

@@ -13,4 +13,10 @@ urlpatterns = [
path("register/submit/", views.register_submit, name="register_submit"),
path("email/send-code/", views.send_email_code, name="send_email_code"),
path("profile/", views.profile_page, name="profile"),
]
path("profile/registration-code/replace/", views.replace_registration_code_view, name="replace_registration_code"),
path("profile/registration-code/preview/", views.registration_code_preview_view, name="registration_code_preview"),
path("registration-code/request/submit/", views.submit_registration_code_request_view, name="submit_registration_code_request"),
path("registration-code/requests/", views.registration_code_requests_page, name="registration_code_requests_page"),
path("registration-code/requests/list/", views.list_registration_code_requests_view, name="list_registration_code_requests"),
path("registration-code/requests/decide/", views.decide_registration_code_request_view, name="decide_registration_code_request"),
]

View File

@@ -15,7 +15,7 @@ from django.conf import settings
from .es_client import get_user_by_username
from .crypto import get_public_key_spki_b64, rsa_oaep_decrypt_b64, aes_gcm_decrypt_b64, verify_password, generate_rsa_private_pem_b64, public_spki_b64_from_private_pem_b64, rsa_oaep_decrypt_b64_with_private_pem
from elastic.es_connect import get_registration_code, get_user_by_username as es_get_user_by_username, get_all_users as es_get_all_users, write_user_data, update_user_by_id, get_user_by_id
from elastic.es_connect import get_registration_code, get_user_by_username as es_get_user_by_username, get_all_users as es_get_all_users, write_user_data, update_user_by_id, get_user_by_id, create_registration_code_manage_request, find_pending_registration_code_manage_request, list_registration_code_manage_requests, decide_registration_code_manage_request, get_registration_code_manage_request
@require_http_methods(["GET"])
@@ -259,6 +259,7 @@ def register_submit(request):
"email": email,
"key": (rc.get("keys") if rc else []) or [],
"manage_key": (rc.get("manage_keys") if rc else []) or [],
"registration_code": (rc.get("code") if rc else None),
})
if not ok:
return JsonResponse({"ok": False, "message": "注册失败"}, status=500)
@@ -269,6 +270,169 @@ def register_submit(request):
pass
return JsonResponse({"ok": True, "redirect_url": "/accounts/login/"})
@require_http_methods(["POST"])
@csrf_protect
def replace_registration_code_view(request):
session_user_id = request.session.get("user_id")
if session_user_id is None:
return JsonResponse({"ok": False, "message": "未登录"}, status=401)
try:
payload = json.loads(request.body.decode("utf-8"))
except json.JSONDecodeError:
return HttpResponseBadRequest("Invalid JSON")
code = (payload.get("code") or "").strip()
if not code:
return JsonResponse({"ok": False, "message": "请输入注册码"}, status=400)
rc = get_registration_code(code)
if not rc:
return JsonResponse({"ok": False, "message": "注册码无效"}, status=400)
try:
exp = rc.get("expires_at")
now = __import__("datetime").datetime.now(__import__("datetime").timezone.utc)
if hasattr(exp, 'isoformat'):
exp_dt = exp
else:
exp_dt = __import__("datetime").datetime.fromisoformat(str(exp))
if exp_dt <= now:
return JsonResponse({"ok": False, "message": "注册码已过期"}, status=400)
except Exception:
pass
keys = list(rc.get("keys") or [])
manage_keys = list(rc.get("manage_keys") or [])
ok = update_user_by_id(session_user_id, key=keys, manage_key=manage_keys, registration_code=code)
if not ok:
return JsonResponse({"ok": False, "message": "替换失败"}, status=500)
return JsonResponse({"ok": True})
@require_http_methods(["GET"])
def registration_code_preview_view(request):
session_user_id = request.session.get("user_id")
if session_user_id is None:
return JsonResponse({"ok": False, "message": "未登录"}, status=401)
code = (request.GET.get("code") or "").strip()
if not code:
return JsonResponse({"ok": False, "message": "请输入注册码"}, status=400)
rc = get_registration_code(code)
if not rc:
return JsonResponse({"ok": False, "message": "注册码无效"}, status=400)
try:
exp = rc.get("expires_at")
now = __import__("datetime").datetime.now(__import__("datetime").timezone.utc)
if hasattr(exp, 'isoformat'):
exp_dt = exp
else:
exp_dt = __import__("datetime").datetime.fromisoformat(str(exp))
if exp_dt <= now:
return JsonResponse({"ok": False, "message": "注册码已过期"}, status=400)
except Exception:
pass
return JsonResponse(
{
"ok": True,
"data": {
"code": rc.get("code"),
"keys": list(rc.get("keys") or []),
"manage_keys": list(rc.get("manage_keys") or []),
"expires_at": rc.get("expires_at"),
},
}
)
@require_http_methods(["POST"])
@csrf_protect
def submit_registration_code_request_view(request):
session_user_id = request.session.get("user_id")
if session_user_id is None:
return JsonResponse({"ok": False, "message": "未登录"}, status=401)
try:
perm = int(request.session.get("permission", 1))
except Exception:
perm = 1
if perm == 0:
return JsonResponse({"ok": False, "message": "无权限"}, status=403)
me = get_user_by_id(session_user_id) or {}
if (me.get("manage_key") or []) or int(me.get("can_manage_registration_codes") or 0) == 1:
return JsonResponse({"ok": False, "message": "无需申请"}, status=400)
if str(me.get("registration_code") or "").strip():
return JsonResponse({"ok": False, "message": "已有注册码,无法申请"}, status=400)
try:
payload = json.loads(request.body.decode("utf-8"))
except json.JSONDecodeError:
return HttpResponseBadRequest("Invalid JSON")
reason = (payload.get("reason") or "").strip()
if not reason:
return JsonResponse({"ok": False, "message": "请填写申请理由"}, status=400)
pending = find_pending_registration_code_manage_request(session_user_id)
if pending:
return JsonResponse({"ok": True, "message": "已提交申请"})
rid = create_registration_code_manage_request(session_user_id, me.get("username"), reason)
if not rid:
return JsonResponse({"ok": False, "message": "提交失败"}, status=500)
return JsonResponse({"ok": True})
@require_http_methods(["GET"])
@ensure_csrf_cookie
def registration_code_requests_page(request):
session_user_id = request.session.get("user_id")
if session_user_id is None:
return redirect("/accounts/login/")
try:
perm = int(request.session.get("permission", 1))
except Exception:
perm = 1
if perm != 0:
return redirect("/main/home/")
me = get_user_by_id(session_user_id) or {}
return render(request, "accounts/registration_code_requests.html", {"username": me.get("username")})
@require_http_methods(["GET"])
def list_registration_code_requests_view(request):
session_user_id = request.session.get("user_id")
if session_user_id is None:
return JsonResponse({"ok": False, "message": "未登录"}, status=401)
try:
perm = int(request.session.get("permission", 1))
except Exception:
perm = 1
if perm != 0:
return JsonResponse({"ok": False, "message": "无权限"}, status=403)
status = (request.GET.get("status") or "").strip() or None
data = list_registration_code_manage_requests(status=status)
return JsonResponse({"ok": True, "data": data})
@require_http_methods(["POST"])
@csrf_protect
def decide_registration_code_request_view(request):
session_user_id = request.session.get("user_id")
if session_user_id is None:
return JsonResponse({"ok": False, "message": "未登录"}, status=401)
try:
perm = int(request.session.get("permission", 1))
except Exception:
perm = 1
if perm != 0:
return JsonResponse({"ok": False, "message": "无权限"}, status=403)
try:
payload = json.loads(request.body.decode("utf-8"))
except json.JSONDecodeError:
return HttpResponseBadRequest("Invalid JSON")
request_id = (payload.get("request_id") or "").strip()
action = (payload.get("action") or "").strip().lower()
note = (payload.get("note") or "").strip()
if not request_id or action not in ("approve", "reject"):
return JsonResponse({"ok": False, "message": "参数错误"}, status=400)
req = get_registration_code_manage_request(request_id)
if not req:
return JsonResponse({"ok": False, "message": "申请不存在"}, status=404)
status = "approved" if action == "approve" else "rejected"
ok = decide_registration_code_manage_request(request_id, status=status, reviewed_by=session_user_id, reviewer_note=note)
if not ok:
return JsonResponse({"ok": False, "message": "操作失败"}, status=500)
if status == "approved":
uid = req.get("user_id")
update_user_by_id(uid, can_manage_registration_codes=1, registration_manage_keys=[])
return JsonResponse({"ok": True})
@require_http_methods(["POST"])
@csrf_protect
def send_email_code(request):
@@ -327,4 +491,4 @@ def _send_smtp_email(to_email: str, code: str):
pass
return True, ""
except Exception as e:
return False, str(e)
return False, str(e)