Initial commit: Office 365 Self Service - 兑换码自助开通系统
This commit is contained in:
247
office365_self_service/routes.py
Normal file
247
office365_self_service/routes.py
Normal 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())
|
||||
Reference in New Issue
Block a user