注册码生成以及用户注册
This commit is contained in:
@@ -42,6 +42,10 @@
|
||||
<button id="loginBtn" type="submit">登录</button>
|
||||
<div id="error" class="error"></div>
|
||||
</form>
|
||||
<div class="hint" style="text-align:center; margin-top:12px;">
|
||||
还没有账号?
|
||||
<a href="/accounts/register/" style="color:#2d8cf0; text-decoration:none;">去注册</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="{% static 'accounts/login.js' %}"></script>
|
||||
|
||||
62
accounts/templates/accounts/register.html
Normal file
62
accounts/templates/accounts/register.html
Normal file
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>用户注册</title>
|
||||
<style>
|
||||
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background: #f5f6fa; }
|
||||
.container { max-width: 400px; margin: 10vh auto; padding: 24px; background: #fff; border-radius: 10px; box-shadow: 0 8px 24px rgba(0,0,0,0.08); }
|
||||
h1 { font-size: 20px; margin: 0 0 16px; }
|
||||
label { display:block; margin: 12px 0 6px; color:#333; }
|
||||
input { width:100%; padding:10px 12px; border:1px solid #dcdde1; border-radius:6px; }
|
||||
button { width:100%; margin-top:16px; padding:10px 12px; background:#2d8cf0; color:#fff; border:none; border-radius:6px; cursor:pointer; }
|
||||
button:disabled { background:#9bbcf0; cursor:not-allowed; }
|
||||
.error { color:#d93025; margin-top:10px; min-height:20px; }
|
||||
.hint { color:#888; font-size:12px; margin-top:10px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>注册新用户</h1>
|
||||
<form id="regForm">
|
||||
{% csrf_token %}
|
||||
<label for="code">注册码</label>
|
||||
<input id="code" name="code" type="text" required />
|
||||
<label for="email">邮箱</label>
|
||||
<input id="email" name="email" type="email" required />
|
||||
<label for="username">用户名</label>
|
||||
<input id="username" name="username" type="text" required />
|
||||
<label for="password">密码</label>
|
||||
<input id="password" name="password" type="password" required />
|
||||
<label for="confirm">确认密码</label>
|
||||
<input id="confirm" name="confirm" type="password" required />
|
||||
<button id="regBtn" type="submit">注册</button>
|
||||
<div id="error" class="error"></div>
|
||||
</form>
|
||||
<div class="hint">仅允许持有管理员提供注册码的学生注册</div>
|
||||
</div>
|
||||
<script>
|
||||
function getCookie(name){const v=`; ${document.cookie}`;const p=v.split(`; ${name}=`);if(p.length===2) return p.pop().split(';').shift();}
|
||||
document.getElementById('regForm').addEventListener('submit',async(e)=>{
|
||||
e.preventDefault();
|
||||
const err=document.getElementById('error'); err.textContent='';
|
||||
const code=(document.getElementById('code').value||'').trim();
|
||||
const email=(document.getElementById('email').value||'').trim();
|
||||
const username=(document.getElementById('username').value||'').trim();
|
||||
const password=document.getElementById('password').value||'';
|
||||
const confirm=document.getElementById('confirm').value||'';
|
||||
if(!code||!email||!username||!password){err.textContent='请填写所有字段';return;}
|
||||
if(password!==confirm){err.textContent='两次密码不一致';return;}
|
||||
const btn=document.getElementById('regBtn'); btn.disabled=true;
|
||||
try{
|
||||
const csrftoken=getCookie('csrftoken');
|
||||
const resp=await fetch('/accounts/register/submit/',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-CSRFToken':csrftoken||''},body:JSON.stringify({code,email,username,password})});
|
||||
const data=await resp.json();
|
||||
if(!resp.ok||!data.ok){throw new Error(data.message||'注册失败');}
|
||||
window.location.href=data.redirect_url;
|
||||
}catch(e){err.textContent=e.message||'发生错误';}
|
||||
finally{btn.disabled=false;}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -9,4 +9,6 @@ urlpatterns = [
|
||||
path("session-key/", views.set_session_key, name="set_session_key"),
|
||||
path("login/secure-submit/", views.secure_login_submit, name="secure_login_submit"),
|
||||
path("logout/", views.logout, name="logout"),
|
||||
path("register/", views.register_page, name="register"),
|
||||
path("register/submit/", views.register_submit, name="register_submit"),
|
||||
]
|
||||
@@ -13,6 +13,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
|
||||
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
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@@ -157,4 +158,54 @@ def logout(request):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return resp
|
||||
return resp
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@ensure_csrf_cookie
|
||||
def register_page(request):
|
||||
return render(request, "accounts/register.html")
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@csrf_protect
|
||||
def register_submit(request):
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
except json.JSONDecodeError:
|
||||
return HttpResponseBadRequest("Invalid JSON")
|
||||
code = (payload.get("code") or "").strip()
|
||||
email = (payload.get("email") or "").strip()
|
||||
username = (payload.get("username") or "").strip()
|
||||
password = (payload.get("password") or "")
|
||||
if not code or not email or not username or not password:
|
||||
return HttpResponseBadRequest("Missing fields")
|
||||
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
|
||||
existing = es_get_user_by_username(username)
|
||||
if existing:
|
||||
return JsonResponse({"ok": False, "message": "用户名已存在"}, status=409)
|
||||
users = es_get_all_users()
|
||||
next_id = (max([int(u.get("user_id", 0)) for u in users]) + 1) if users else 1
|
||||
ok = write_user_data({
|
||||
"user_id": next_id,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"permission": 1,
|
||||
"email": email,
|
||||
"key": rc.get("keys") or [],
|
||||
"manage_key": rc.get("manage_keys") or [],
|
||||
})
|
||||
if not ok:
|
||||
return JsonResponse({"ok": False, "message": "注册失败"}, status=500)
|
||||
return JsonResponse({"ok": True, "redirect_url": "/accounts/login/"})
|
||||
@@ -34,13 +34,14 @@ class UserDocument(Document):
|
||||
"""用户数据文档映射"""
|
||||
user_id = fields.LongField()
|
||||
username = fields.KeywordField()
|
||||
email = fields.KeywordField()
|
||||
password_hash = fields.KeywordField()
|
||||
password_salt = fields.KeywordField()
|
||||
permission = fields.IntegerField() # 还是2种权限,0为管理员,1为用户(区别在于0有全部权限,1在数据管理页面有搜索框,但是索引到的录入信息要根据其用户id查询其key,若其中之一与用户的manage_key字段匹配就显示否则不显示)
|
||||
key = fields.IntegerField() #表示该用户的关键字,举个例子:学生A的key为"2024届人工智能1班","2024届","计算机与人工智能学院" 班导师B的key为"计算机与人工智能学院"
|
||||
manage_key = fields.IntegerField() #表示该用户管理的关键字(非管理员)班导师B的manage_key为"2024届人工智能1班"
|
||||
key = fields.KeywordField(multi=True) #表示该用户的关键字,举个例子:学生A的key为"2024届人工智能1班","2024届","计算机与人工智能学院" 班导师B的key为"计算机与人工智能学院"
|
||||
manage_key = fields.KeywordField(multi=True) #表示该用户管理的关键字(非管理员)班导师B的manage_key为"2024届人工智能1班"
|
||||
#那么学生A就可以在数据管理页面搜索到自己的获奖数据,而班导师B就可以在数据管理页面搜索到所有人工智能1班的获奖数据。也就是说学生A和班导师B都其实只有用户权限
|
||||
|
||||
|
||||
class Django:
|
||||
model = User
|
||||
# fields列表应该只包含需要特殊处理的字段,或者可以完全省略
|
||||
@@ -49,6 +50,17 @@ class UserDocument(Document):
|
||||
@GLOBAL_INDEX.doc_type
|
||||
class GlobalDocument(Document):
|
||||
type_list = fields.KeywordField()
|
||||
keys_list = fields.KeywordField(multi=True)
|
||||
|
||||
class Django:
|
||||
model = ElasticNews
|
||||
|
||||
@GLOBAL_INDEX.doc_type
|
||||
class RegistrationCodeDocument(Document):
|
||||
code = fields.KeywordField() #具体值
|
||||
keys = fields.KeywordField(multi=True) #对应的key
|
||||
manage_keys = fields.KeywordField(multi=True) #对应的manage_key
|
||||
expires_at = fields.DateField() #过期时间
|
||||
created_by = fields.LongField() #创建者id
|
||||
class Django:
|
||||
model = ElasticNews
|
||||
|
||||
@@ -5,12 +5,13 @@ Django版本的ES连接和操作模块
|
||||
from elasticsearch import Elasticsearch
|
||||
from elasticsearch_dsl import connections
|
||||
import os
|
||||
from .documents import AchievementDocument, UserDocument, GlobalDocument
|
||||
from .documents import AchievementDocument, UserDocument, GlobalDocument, RegistrationCodeDocument
|
||||
from accounts.crypto import hash_password_random_salt
|
||||
from .indexes import ACHIEVEMENT_INDEX_NAME, USER_INDEX_NAME, GLOBAL_INDEX_NAME
|
||||
import hashlib
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timezone, timedelta
|
||||
import uuid
|
||||
import json
|
||||
|
||||
# 使用环境变量配置ES连接,默认为本机
|
||||
@@ -115,6 +116,87 @@ def ensure_type_in_list(type_name: str):
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def get_keys_list():
|
||||
try:
|
||||
try:
|
||||
doc = GlobalDocument.get(id='keys')
|
||||
cur = list(doc.keys_list or [])
|
||||
except Exception:
|
||||
cur = []
|
||||
doc = GlobalDocument(keys_list=cur)
|
||||
doc.meta.id = 'keys'
|
||||
doc.save()
|
||||
return [str(t).strip().strip(';') for t in cur]
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
def ensure_key_in_list(key_name: str):
|
||||
if not key_name:
|
||||
return False
|
||||
norm = str(key_name).strip().strip(';')
|
||||
try:
|
||||
try:
|
||||
doc = GlobalDocument.get(id='keys')
|
||||
cur = list(doc.keys_list or [])
|
||||
except Exception:
|
||||
cur = []
|
||||
doc = GlobalDocument(keys_list=cur)
|
||||
doc.meta.id = 'keys'
|
||||
cur_sanitized = {str(t).strip().strip(';') for t in cur}
|
||||
if norm not in cur_sanitized:
|
||||
cur.append(norm)
|
||||
doc.keys_list = cur
|
||||
doc.save()
|
||||
return True
|
||||
return False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def generate_registration_code(keys=None, manage_keys=None, expires_in_days: int = 30, created_by: int = None):
|
||||
try:
|
||||
keys = list(keys or [])
|
||||
manage_keys = list(manage_keys or [])
|
||||
for k in list(keys):
|
||||
ensure_key_in_list(k)
|
||||
for mk in list(manage_keys):
|
||||
ensure_key_in_list(mk)
|
||||
code = uuid.uuid4().hex + str(int(time.time()))[-6:]
|
||||
now = datetime.now(timezone.utc)
|
||||
expires = now + timedelta(days=max(1, int(expires_in_days or 30)))
|
||||
doc = RegistrationCodeDocument(
|
||||
code=code,
|
||||
keys=keys,
|
||||
manage_keys=manage_keys,
|
||||
created_at=now.isoformat(),
|
||||
expires_at=expires.isoformat(),
|
||||
created_by=created_by,
|
||||
)
|
||||
doc.meta.id = code
|
||||
doc.save()
|
||||
return {
|
||||
"code": code,
|
||||
"keys": keys,
|
||||
"manage_keys": manage_keys,
|
||||
"created_at": now.isoformat(),
|
||||
"expires_at": expires.isoformat(),
|
||||
}
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
def get_registration_code(code: str):
|
||||
try:
|
||||
doc = RegistrationCodeDocument.get(id=str(code))
|
||||
return {
|
||||
"code": getattr(doc, 'code', str(code)),
|
||||
"keys": list(getattr(doc, 'keys', []) or []),
|
||||
"manage_keys": list(getattr(doc, 'manage_keys', []) or []),
|
||||
"created_at": getattr(doc, 'created_at', None),
|
||||
"expires_at": getattr(doc, 'expires_at', None),
|
||||
"created_by": getattr(doc, 'created_by', None),
|
||||
}
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_doc_id(data):
|
||||
"""
|
||||
根据数据内容生成唯一ID(用于去重)
|
||||
@@ -527,7 +609,10 @@ def write_user_data(user_data):
|
||||
username=user_data.get('username'),
|
||||
password_hash=pwd_hash_b64,
|
||||
password_salt=pwd_salt_b64,
|
||||
permission=perm_val
|
||||
permission=perm_val,
|
||||
email=user_data.get('email'),
|
||||
key=list(user_data.get('key') or []),
|
||||
manage_key=list(user_data.get('manage_key') or []),
|
||||
)
|
||||
user.save()
|
||||
print(f"用户数据写入成功: {user_data.get('username')}")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
INDEX_NAME = "wordsearch2666661"
|
||||
USER_NAME = "users1111166"
|
||||
USER_NAME = "users11111666789"
|
||||
ACHIEVEMENT_INDEX_NAME = INDEX_NAME
|
||||
USER_INDEX_NAME = USER_NAME
|
||||
GLOBAL_INDEX_NAME = "global1111111121"
|
||||
GLOBAL_INDEX_NAME = "global11111111"
|
||||
|
||||
119
elastic/templates/elastic/registration_codes.html
Normal file
119
elastic/templates/elastic/registration_codes.html
Normal file
@@ -0,0 +1,119 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>注册码管理</title>
|
||||
<style>
|
||||
body { margin:0; font-family: system-ui,-apple-system, Segoe UI, Roboto, sans-serif; background:#fafafa; }
|
||||
.sidebar { position:fixed; top:0; left:0; width:180px; height:100vh; background:#1e1e2e; color:#fff; padding:20px; box-shadow:2px 0 5px rgba(0,0,0,0.1); z-index:1000; display:flex; flex-direction:column; align-items:center; }
|
||||
.sidebar h3 { margin:0; font-size:18px; color:#add8e6; text-align:center; }
|
||||
.navigation-links { width:100%; margin-top:60px; }
|
||||
.sidebar a, .sidebar button { display:block; color:#8be9fd; text-decoration:none; margin:10px 0; font-size:16px; padding:15px; border-radius:4px; background:transparent; border:none; cursor:pointer; width:calc(100% - 40px); text-align:left; transition:all .2s ease; }
|
||||
.sidebar a:hover, .sidebar button:hover { color:#ff79c6; background-color:rgba(139,233,253,.2); }
|
||||
.main { margin-left:200px; padding:20px; color:#333; }
|
||||
.card { background:#fff; border-radius:14px; box-shadow:0 10px 24px rgba(31,35,40,.08); padding:20px; margin-bottom:20px; }
|
||||
.row { display:flex; gap:16px; }
|
||||
.col { flex:1; }
|
||||
label { display:block; margin-bottom:6px; font-weight:600; }
|
||||
input[type=text], input[type=number], select { width:100%; padding:8px 12px; border:1px solid #d1d5db; border-radius:6px; box-sizing:border-box; }
|
||||
.btn { padding:8px 12px; border:none; border-radius:8px; cursor:pointer; margin:0 4px; }
|
||||
.btn-primary { background:#4f46e5; color:#fff; }
|
||||
.btn-secondary { background:#64748b; color:#fff; }
|
||||
.notice { padding:10px; border-radius:6px; margin-top:10px; display:none; }
|
||||
.notice.success { background:#d4edda; color:#155724; border:1px solid #c3e6cb; }
|
||||
.notice.error { background:#f8d7da; color:#721c24; border:1px solid #f5c6cb; }
|
||||
.code-box { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; padding:12px; border:1px solid #e5e7eb; border-radius:8px; background:#fafafa; margin-top:10px; }
|
||||
</style>
|
||||
{% csrf_token %}
|
||||
<script>
|
||||
function getCookie(name){const v=`; ${document.cookie}`;const p=v.split(`; ${name}=`);if(p.length===2) return p.pop().split(';').shift();}
|
||||
async function loadKeys(){
|
||||
const resp=await fetch('/elastic/registration-codes/keys/');
|
||||
const data=await resp.json();
|
||||
const opts=(data.data||[]);
|
||||
const keySel=document.getElementById('keys');
|
||||
const mkeySel=document.getElementById('manageKeys');
|
||||
keySel.innerHTML=''; mkeySel.innerHTML='';
|
||||
opts.forEach(k=>{
|
||||
const o=document.createElement('option'); o.value=k; o.textContent=k; keySel.appendChild(o);
|
||||
const o2=document.createElement('option'); o2.value=k; o2.textContent=k; mkeySel.appendChild(o2);
|
||||
});
|
||||
}
|
||||
async function addKey(){
|
||||
const keyName=(document.getElementById('newKey').value||'').trim();
|
||||
if(!keyName) return;
|
||||
const csrftoken=getCookie('csrftoken');
|
||||
const resp=await fetch('/elastic/registration-codes/keys/add/',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-CSRFToken':csrftoken||''},body:JSON.stringify({key:keyName})});
|
||||
const data=await resp.json();
|
||||
const msg=document.getElementById('msg');
|
||||
if(resp.ok && data.status==='success'){msg.textContent='新增key成功'; msg.className='notice success'; msg.style.display='block'; document.getElementById('newKey').value=''; loadKeys();}
|
||||
else{msg.textContent=data.message||'新增失败'; msg.className='notice error'; msg.style.display='block';}
|
||||
}
|
||||
function selectedValues(sel){return Array.from(sel.selectedOptions).map(o=>o.value);}
|
||||
function enableToggleSelect(sel){ sel.addEventListener('mousedown',function(e){ if(e.target && e.target.tagName==='OPTION'){ e.preventDefault(); const op=e.target; op.selected=!op.selected; this.dispatchEvent(new Event('change',{bubbles:true})); } }); }
|
||||
function clearSelection(id){ const sel=document.getElementById(id); Array.from(sel.options).forEach(o=>o.selected=false); }
|
||||
async function generateCode(){
|
||||
const csrftoken=getCookie('csrftoken');
|
||||
const keys=selectedValues(document.getElementById('keys'));
|
||||
const manageKeys=selectedValues(document.getElementById('manageKeys'));
|
||||
const mode=document.getElementById('expireMode').value;
|
||||
let days=30; if(mode==='month') days=30; else if(mode==='fouryears') days=1460; else { const d=parseInt(document.getElementById('customDays').value||'30'); days=isNaN(d)?30:Math.max(1,d);}
|
||||
const resp=await fetch('/elastic/registration-codes/generate/',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-CSRFToken':csrftoken||''},body:JSON.stringify({keys,manage_keys:manageKeys,expires_in_days:days})});
|
||||
const data=await resp.json();
|
||||
const out=document.getElementById('codeOut');
|
||||
const msg=document.getElementById('msg');
|
||||
if(resp.ok && data.status==='success'){out.textContent=data.data.code; msg.textContent='生成成功'; msg.className='notice success'; msg.style.display='block';}
|
||||
else{msg.textContent=data.message||'生成失败'; msg.className='notice error'; msg.style.display='block';}
|
||||
}
|
||||
document.addEventListener('DOMContentLoaded',()=>{loadKeys(); enableToggleSelect(document.getElementById('keys')); enableToggleSelect(document.getElementById('manageKeys'));});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<h3>用户ID:{{ user_id }}</h3>
|
||||
<div class="navigation-links">
|
||||
<a href="{% url 'main:home' %}">主页</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main">
|
||||
<div class="card">
|
||||
<h2>管理注册码</h2>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<label>新增key</label>
|
||||
<input id="newKey" type="text" placeholder="输入新的key" />
|
||||
<button class="btn btn-secondary" onclick="addKey()">新增</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top:12px;">
|
||||
<div class="col">
|
||||
<label>选择keys</label>
|
||||
<select id="keys" multiple size="10"></select>
|
||||
<div style="margin-top:8px;"><button class="btn btn-secondary" onclick="clearSelection('keys')">清空选择</button></div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<label>选择manage_keys</label>
|
||||
<select id="manageKeys" multiple size="10"></select>
|
||||
<div style="margin-top:8px;"><button class="btn btn-secondary" onclick="clearSelection('manageKeys')">清空选择</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top:12px;">
|
||||
<div class="col">
|
||||
<label>有效期</label>
|
||||
<select id="expireMode">
|
||||
<option value="month">一个月</option>
|
||||
<option value="fouryears">四年</option>
|
||||
<option value="custom">自定义天数</option>
|
||||
</select>
|
||||
<input id="customDays" type="number" min="1" placeholder="自定义天数" />
|
||||
</div>
|
||||
<div class="col" style="display:flex; align-items:flex-end;">
|
||||
<button class="btn btn-primary" onclick="generateCode()">生成注册码</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="msg" class="notice"></div>
|
||||
<div class="code-box" id="codeOut"></div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -32,6 +32,10 @@ urlpatterns = [
|
||||
# 管理页面
|
||||
path('manage/', views.manage_page, name='manage_page'),
|
||||
path('user_manage/', views.user_manage, name='user_manage'),
|
||||
path('registration-codes/manage/', views.registration_code_manage_page, name='registration_code_manage_page'),
|
||||
path('registration-codes/keys/', views.get_keys_list_view, name='get_keys_list'),
|
||||
path('registration-codes/keys/add/', views.add_key_view, name='add_key'),
|
||||
path('registration-codes/generate/', views.generate_registration_code_view, name='generate_registration_code'),
|
||||
|
||||
# 分析接口
|
||||
path('analytics/trend/', views.analytics_trend_view, name='analytics_trend'),
|
||||
|
||||
@@ -604,3 +604,67 @@ def user_manage(request):
|
||||
user_id_qs = request.GET.get("user_id")
|
||||
context = {"user_id": user_id_qs or session_user_id}
|
||||
return render(request, "elastic/users.html", context)
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@ensure_csrf_cookie
|
||||
def registration_code_manage_page(request):
|
||||
session_user_id = request.session.get("user_id")
|
||||
if session_user_id is None:
|
||||
from django.shortcuts import redirect
|
||||
return redirect("/accounts/login/")
|
||||
if int(request.session.get("permission", 1)) != 0:
|
||||
from django.shortcuts import redirect
|
||||
return redirect("/main/home/")
|
||||
user_id_qs = request.GET.get("user_id")
|
||||
context = {"user_id": user_id_qs or session_user_id}
|
||||
return render(request, "elastic/registration_codes.html", context)
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
def get_keys_list_view(request):
|
||||
if request.session.get("user_id") is None:
|
||||
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
|
||||
if int(request.session.get("permission", 1)) != 0:
|
||||
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
|
||||
lst = get_keys_list()
|
||||
return JsonResponse({"status": "success", "data": lst})
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@csrf_protect
|
||||
def add_key_view(request):
|
||||
if request.session.get("user_id") is None:
|
||||
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
|
||||
if int(request.session.get("permission", 1)) != 0:
|
||||
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
except Exception:
|
||||
return JsonResponse({"status": "error", "message": "JSON无效"}, status=400)
|
||||
key_name = (payload.get("key") or "").strip()
|
||||
if not key_name:
|
||||
return JsonResponse({"status": "error", "message": "key不能为空"}, status=400)
|
||||
ok = ensure_key_in_list(key_name)
|
||||
if not ok:
|
||||
return JsonResponse({"status": "error", "message": "key已存在或写入失败"}, status=409)
|
||||
return JsonResponse({"status": "success"})
|
||||
|
||||
@require_http_methods(["POST"])
|
||||
@csrf_protect
|
||||
def generate_registration_code_view(request):
|
||||
if request.session.get("user_id") is None:
|
||||
return JsonResponse({"status": "error", "message": "未登录"}, status=401)
|
||||
if int(request.session.get("permission", 1)) != 0:
|
||||
return JsonResponse({"status": "error", "message": "无权限"}, status=403)
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8"))
|
||||
except Exception:
|
||||
return JsonResponse({"status": "error", "message": "JSON无效"}, status=400)
|
||||
keys = list(payload.get("keys") or [])
|
||||
manage_keys = list(payload.get("manage_keys") or [])
|
||||
try:
|
||||
days = int(payload.get("expires_in_days", 30))
|
||||
except Exception:
|
||||
days = 30
|
||||
result = generate_registration_code(keys=keys, manage_keys=manage_keys, expires_in_days=days, created_by=request.session.get("user_id"))
|
||||
if not result:
|
||||
return JsonResponse({"status": "error", "message": "生成失败"}, status=500)
|
||||
return JsonResponse({"status": "success", "data": result})
|
||||
|
||||
@@ -145,6 +145,7 @@
|
||||
<a href="{% url 'elastic:manage_page' %}" onclick="return handleNavClick(this, '/elastic/manage/');">数据管理</a>
|
||||
{% if is_admin %}
|
||||
<a href="{% url 'elastic:user_manage' %}" onclick="return handleNavClick(this, '/elastic/user_manage/');">用户管理</a>
|
||||
<a href="{% url 'elastic:registration_code_manage_page' %}" onclick="return handleNavClick(this, '/elastic/registration-codes/manage/');">注册码管理</a>
|
||||
{% endif %}
|
||||
<button id="logoutBtn">退出登录</button>
|
||||
<div id="logoutMsg"></div>
|
||||
|
||||
Reference in New Issue
Block a user