Initial commit

This commit is contained in:
liumangmang
2026-05-12 17:51:53 +08:00
commit b564ca4797
55 changed files with 6407 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# utils package
+74
View File
@@ -0,0 +1,74 @@
from datetime import datetime, timedelta, timezone
from typing import Optional
from jose import JWTError, jwt
import bcrypt
from fastapi import Depends, HTTPException, Query, Request, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from app.config import get_settings
from app.database import get_db
from app.models.admin_user import AdminUser
ALGORITHM = "HS256"
bearer_scheme = HTTPBearer(auto_error=False)
def hash_password(password: str) -> str:
pw = password.encode("utf-8")[:72]
return bcrypt.hashpw(pw, bcrypt.gensalt()).decode("utf-8")
def verify_password(plain: str, hashed: str) -> bool:
pw = plain.encode("utf-8")[:72]
return bcrypt.checkpw(pw, hashed.encode("utf-8"))
def create_access_token(email: str, expires_hours: Optional[int] = None) -> str:
settings = get_settings()
hours = expires_hours or settings.jwt_expire_hours
expire = datetime.now(timezone.utc) + timedelta(hours=hours)
data = {"sub": email, "exp": expire}
return jwt.encode(data, settings.jwt_secret, algorithm=ALGORITHM)
def decode_token(token: str) -> Optional[str]:
settings = get_settings()
try:
payload = jwt.decode(token, settings.jwt_secret, algorithms=[ALGORITHM])
return payload.get("sub")
except JWTError:
return None
def get_current_user(
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
db: Session = Depends(get_db),
) -> AdminUser:
token = credentials.credentials if credentials else None
if not token:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
email = decode_token(token)
if not email:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
user = db.query(AdminUser).filter(AdminUser.email == email).first()
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
return user
def get_user_from_token_param(
token: Optional[str] = Query(default=None),
credentials: HTTPAuthorizationCredentials = Depends(bearer_scheme),
db: Session = Depends(get_db),
) -> AdminUser:
"""Accept JWT from ?token= query param (for iframe src) OR Authorization header."""
raw = token or (credentials.credentials if credentials else None)
if not raw:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Not authenticated")
email = decode_token(raw)
if not email:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token")
user = db.query(AdminUser).filter(AdminUser.email == email).first()
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="User not found")
return user
+59
View File
@@ -0,0 +1,59 @@
"""DingTalk webhook signing and message formatting (ported from monitor script)."""
import base64
import hashlib
import hmac
import json
import time
from typing import Any
from urllib.parse import quote_plus
def dingtalk_signed_url(webhook_url: str, secret: str) -> str:
timestamp = str(int(time.time() * 1000))
string_to_sign = f"{timestamp}\n{secret}".encode("utf-8")
digest = hmac.new(secret.encode("utf-8"), string_to_sign, hashlib.sha256).digest()
sign = quote_plus(base64.b64encode(digest).decode("utf-8"))
sep = "&" if "?" in webhook_url else "?"
return f"{webhook_url}{sep}timestamp={timestamp}&sign={sign}"
def format_dingtalk_rate_changed(upstream_name: str, changed_at: str, changes: list[dict[str, Any]]) -> dict[str, Any]:
lines = [
f"### 📊 {upstream_name} 分组倍率变更",
"",
f"- **时间**{changed_at}",
f"- **变化数量**{len(changes)}",
"",
]
for ch in changes:
name = ch.get("group_name") or ch.get("group_id") or "unknown"
platform = ch.get("platform") or "-"
old = ch.get("old_rate")
new = ch.get("new_rate")
lines.append(f"- `{name}` ({platform})`{old}` → `{new}`")
return {
"msgtype": "markdown",
"markdown": {
"title": f"{upstream_name} 分组倍率变更",
"text": "\n".join(lines),
},
}
def format_dingtalk_status(upstream_name: str, event: str, changed_at: str, error: str = "") -> dict[str, Any]:
emoji = "🔴" if event == "upstream_unhealthy" else "🟢"
label = "服务异常" if event == "upstream_unhealthy" else "服务恢复"
lines = [
f"### {emoji} {upstream_name} {label}",
"",
f"- **时间**{changed_at}",
]
if error:
lines.append(f"- **错误**{error}")
return {
"msgtype": "markdown",
"markdown": {
"title": f"{upstream_name} {label}",
"text": "\n".join(lines),
},
}