Initial commit: Office 365 Self Service - 兑换码自助开通系统

This commit is contained in:
youbin
2026-03-28 00:32:30 +08:00
commit 8f5a643ed3
16 changed files with 1219 additions and 0 deletions

View File

@@ -0,0 +1,247 @@
from __future__ import annotations
import secrets
from datetime import datetime, timezone, timedelta
from functools import wraps
from flask import Blueprint, current_app, jsonify, render_template, request, session
from sqlalchemy import func
from . import db
from .models import RedemptionCode
from .services import Office365Service, ServiceConfigurationError, ServiceOperationError
bp_admin = Blueprint("admin", __name__, url_prefix="/admin")
bp_user = Blueprint("user", __name__)
def _settings():
return current_app.config["SETTINGS"]
def _service() -> Office365Service:
return current_app.extensions["office365_service"]
def _success(data=None, message: str = "ok", status: int = 200):
return jsonify({"success": True, "message": message, "data": data}), status
def _error(message: str, status: int = 400, details=None):
payload = {"success": False, "message": message}
if details is not None:
payload["details"] = details
return jsonify(payload), status
def _authenticated() -> bool:
settings = _settings()
if not settings.effective_auth_enabled:
return True
return bool(session.get("authenticated"))
def require_auth(view_func):
@wraps(view_func)
def wrapped(*args, **kwargs):
if not _authenticated():
return _error("请先登录后台管理平台。", status=401)
return view_func(*args, **kwargs)
return wrapped
def _handle_service_call(callback):
try:
return callback()
except ServiceConfigurationError as exc:
return _error(str(exc), status=503)
except ServiceOperationError as exc:
return _error(exc.message, status=exc.status_code, details=exc.details)
except ValueError as exc:
return _error(str(exc), status=400)
def _json_payload() -> dict:
return request.get_json(silent=True) or {}
@bp_admin.get("/")
def admin_index():
if not _authenticated():
return render_template("admin_login.html", settings=_settings())
return render_template("admin_dashboard.html", settings=_settings())
@bp_admin.get("/api/health")
def health():
settings = _settings()
return _success(
{
"platform": settings.to_public_dict(),
"authenticated": _authenticated(),
}
)
@bp_admin.get("/api/session")
def session_info():
return _success(
{
"authenticated": _authenticated(),
"authEnabled": _settings().effective_auth_enabled,
}
)
@bp_admin.post("/api/login")
def login():
settings = _settings()
if not settings.effective_auth_enabled:
session["authenticated"] = True
session.permanent = True
return _success({"authenticated": True}, message="当前平台未启用登录保护。")
payload = _json_payload()
username = str(payload.get("username", "")).strip()
password = str(payload.get("password", "")).strip()
if username == settings.admin_username and password == settings.admin_password:
session["authenticated"] = True
session.permanent = True
return _success({"authenticated": True}, message="登录成功。")
return _error("用户名或密码错误。", status=401)
@bp_admin.post("/api/logout")
def logout():
session.clear()
return _success({"authenticated": False}, message="已退出登录。")
@bp_admin.get("/api/config")
@require_auth
def config_info():
return _success(_settings().to_public_dict())
@bp_admin.get("/api/codes")
@require_auth
def list_codes():
status = request.args.get("status")
query = db.select(RedemptionCode)
if status == "available":
query = query.where(RedemptionCode.status == "available")
elif status == "used":
query = query.where(RedemptionCode.status == "used")
result = db.session.execute(query.order_by(RedemptionCode.created_at.desc())).scalars().all()
codes = [code.to_dict() for code in result]
return _success({"codes": codes, "total": len(codes)})
@bp_admin.post("/api/codes/generate")
@require_auth
def generate_codes():
payload = _json_payload()
count = payload.get("count", 1)
if count < 1:
count = 1
if count > 100:
count = 100
codes = []
for _ in range(count):
code = secrets.token_urlsafe(12)
while RedemptionCode.query.filter_by(code=code).first():
code = secrets.token_urlsafe(12)
redemption_code = RedemptionCode(code=code)
db.session.add(redemption_code)
codes.append(code)
db.session.commit()
return _success({"codes": codes, "count": len(codes)}, f"成功生成 {count} 个兑换码。")
@bp_admin.delete("/api/codes/<code>")
@require_auth
def delete_code(code: str):
redemption_code = RedemptionCode.query.filter_by(code=code).first()
if not redemption_code:
return _error("兑换码不存在。", status=404)
db.session.delete(redemption_code)
db.session.commit()
return _success(message="兑换码已删除。")
@bp_admin.get("/api/records")
@require_auth
def list_records():
page = int(request.args.get("page", "1"))
page_size = int(request.args.get("pageSize", "25"))
query = db.select(RedemptionCode).where(RedemptionCode.status == "used")
result = db.session.execute(query.order_by(RedemptionCode.used_at.desc())).scalars().all()
total = len(result)
start = (page - 1) * page_size
end = start + page_size
records = result[start:end]
return _success({
"records": [code.to_dict() for code in records],
"page": page,
"pageSize": page_size,
"total": total,
})
@bp_user.get("/")
def index():
return render_template("user_redemption.html", settings=_settings())
@bp_user.post("/api/redeem")
def redeem():
payload = _json_payload()
code = str(payload.get("code", "")).strip().upper()
username = str(payload.get("username", "")).strip().lower()
if not code:
return _error("请输入兑换码。", status=400)
if not username:
return _error("请输入用户名。", status=400)
redemption_code = RedemptionCode.query.filter(
func.lower(RedemptionCode.code) == code.lower(),
RedemptionCode.status == "available"
).first()
if not redemption_code:
return _error("兑换码无效或已被使用。", status=404)
try:
user_result = _service().create_user(username=username)
except ServiceOperationError as exc:
return _error(str(exc), status=500)
except Exception as exc:
return _error(f"创建账号失败: {exc}", status=500)
redemption_code.status = "used"
redemption_code.used_at = datetime.now(timezone.utc)
redemption_code.used_by_username = username
redemption_code.used_by_principal_name = user_result.get("userPrincipalName")
db.session.commit()
return _success({
"userPrincipalName": user_result.get("userPrincipalName"),
"temporaryPassword": user_result.get("temporaryPassword"),
}, "账号开通成功!", status=201)
@bp_user.get("/api/config")
def config():
return _success(_settings().to_public_dict())