a
This commit is contained in:
+123
@@ -0,0 +1,123 @@
|
||||
"""Nancode Portal 后端 — SQLite 持久化链接 + SSO Cookie 认证"""
|
||||
import os, sqlite3
|
||||
from contextlib import contextmanager
|
||||
from fastapi import FastAPI, Request, HTTPException, Depends
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
from jose import jwt, JWTError
|
||||
|
||||
# ── 配置 ─────────────────────────────────
|
||||
DB_PATH = os.path.join(os.path.dirname(__file__), "portal.db")
|
||||
SSO_SECRET_KEY = "your-secret-key-change-in-production" # 与 SSO 中心一致
|
||||
ALGORITHM = "HS256"
|
||||
|
||||
# ── 数据库 ───────────────────────────────
|
||||
@contextmanager
|
||||
def get_db():
|
||||
conn = sqlite3.connect(DB_PATH)
|
||||
conn.row_factory = sqlite3.Row
|
||||
try:
|
||||
yield conn
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def init_db():
|
||||
with get_db() as conn:
|
||||
conn.execute("""CREATE TABLE IF NOT EXISTS links (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
desc TEXT DEFAULT '',
|
||||
icon TEXT DEFAULT '',
|
||||
sort_order INTEGER DEFAULT 0
|
||||
)""")
|
||||
# 首次启动:如果表为空则写入默认数据
|
||||
count = conn.execute("SELECT COUNT(*) FROM links").fetchone()[0]
|
||||
if count == 0:
|
||||
defaults = [
|
||||
('1','个人博客','http://129.211.173.87:8888','Next.js技术博客,个人技术文章与生活记录','book-open',1),
|
||||
('2','相册查看器','http://129.211.173.87:8892','图片相册浏览与管理工具','image',2),
|
||||
('3','快乐游戏','http://129.211.173.87:8895','Flask小游戏集合,休闲益智','gamepad-2',3),
|
||||
('4','网游引擎','http://129.211.173.87:8900','网络游戏引擎服务平台','server',4),
|
||||
('5','有声书播放器','http://129.211.173.87:8899','在线有声书播放与TTS服务','headphones',5),
|
||||
('6','WanAndroid','http://129.211.173.87:8893','Material Design风格的Web应用','smartphone',6),
|
||||
('7','FumaMX CRM','http://129.211.173.87:10000','客户关系管理系统','briefcase',7),
|
||||
('8','AI聊天服务','http://129.211.173.87:8890','AI对话机器人服务','bot',8),
|
||||
('9','新媒体平台','http://129.211.173.87:18888','CMS内容管理系统与网站后台','layout-dashboard',9),
|
||||
('10','微信小程序桥接','http://129.211.173.87:5000','微信-Nanobot消息桥接服务','message-circle',10),
|
||||
('11','音频录制工具','http://129.211.173.87:8901','在线音频录制与处理工具','mic',11),
|
||||
('12','粒子实验室','http://129.211.173.87:8902','粒子特效与图形渲染实验','sparkles',12),
|
||||
('13','WebGPU演示','http://129.211.173.87:8902','WebGPU与PlayCanvas 3D演示','globe',13),
|
||||
('14','Hermes网关','http://129.211.173.87:8894','AI网关与代理服务','hexagon',14),
|
||||
]
|
||||
conn.executemany("INSERT INTO links (id,name,url,desc,icon,sort_order) VALUES (?,?,?,?,?,?)", defaults)
|
||||
conn.commit()
|
||||
|
||||
init_db()
|
||||
|
||||
# ── SSO 认证 ─────────────────────────────
|
||||
def require_sso(request: Request) -> str:
|
||||
"""从 Cookie 验签,返回 username;失败抛 401"""
|
||||
token = request.cookies.get("access_token")
|
||||
if not token:
|
||||
raise HTTPException(401, "未登录")
|
||||
try:
|
||||
payload = jwt.decode(token, SSO_SECRET_KEY, algorithms=[ALGORITHM])
|
||||
username = payload.get("sub")
|
||||
if not username:
|
||||
raise HTTPException(401, "无效令牌")
|
||||
return username
|
||||
except JWTError:
|
||||
raise HTTPException(401, "令牌已过期或无效")
|
||||
|
||||
# ── FastAPI ──────────────────────────────
|
||||
app = FastAPI(title="Nancode Portal API")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[
|
||||
"http://129.211.173.87",
|
||||
"http://129.211.173.87:8887",
|
||||
"http://129.211.173.87:8888",
|
||||
"http://129.211.173.87:9000",
|
||||
],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
class LinkCreate(BaseModel):
|
||||
name: str
|
||||
url: str
|
||||
desc: str = ""
|
||||
icon: str = ""
|
||||
|
||||
@app.get("/api/links")
|
||||
def list_links():
|
||||
with get_db() as conn:
|
||||
rows = conn.execute("SELECT id,name,url,desc,icon FROM links ORDER BY sort_order, id").fetchall()
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
@app.post("/api/links")
|
||||
def add_link(link: LinkCreate, username: str = Depends(require_sso)):
|
||||
import time, random
|
||||
lid = f"{int(time.time()):x}{random.randint(0,0xffffff):06x}"
|
||||
with get_db() as conn:
|
||||
max_sort = conn.execute("SELECT COALESCE(MAX(sort_order),0) FROM links").fetchone()[0]
|
||||
conn.execute("INSERT INTO links (id,name,url,desc,icon,sort_order) VALUES (?,?,?,?,?,?)",
|
||||
(lid, link.name, link.url, link.desc, link.icon, max_sort + 1))
|
||||
conn.commit()
|
||||
return {"id": lid, "name": link.name, "url": link.url, "desc": link.desc, "icon": link.icon}
|
||||
|
||||
@app.delete("/api/links/{link_id}")
|
||||
def delete_link(link_id: str, username: str = Depends(require_sso)):
|
||||
with get_db() as conn:
|
||||
cur = conn.execute("DELETE FROM links WHERE id=?", (link_id,))
|
||||
conn.commit()
|
||||
if cur.rowcount == 0:
|
||||
raise HTTPException(404, "链接不存在")
|
||||
return {"success": True}
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8887)
|
||||
Reference in New Issue
Block a user