从零构建安全文件上传系统:FastAPI + JWT + 密码哈希 + Streamlit 前端 + SQLite

【学习记录】从零构建安全文件上传系统:FastAPI + JWT + 密码哈希 + Streamlit 前端 + SQLite

文件上传是 Web 应用中的常见需求,但一个生产级别的上传系统不仅要能接收文件,还需要考虑用户认证、数据隔离、密码安全、文件存储安全等关键问题。本文使用 FastAPI、JWT、Passlib(密码哈希)、Streamlit 和 SQLite,从零构建一个完整的文件上传系统。用户需要注册/登录,每个用户只能管理自己的上传记录,所有敏感操作需携带 JWT Token。文章涵盖技术原理、代码逐段解析、安全实践以及数据库命令行查看方法,适合作为内部工具或知识库上传组件的原型。


📌 目录

  1. 系统概览与技术栈
  2. 核心功能与流程
  3. 数据库设计
  4. 后端代码详解
    • 4.1 密码哈希与用户注册/登录
    • 4.2 JWT 签发与验证
    • 4.3 文件上传与记录存储
    • 4.4 查询用户记录
  5. [前端实现:Streamlit 会话管理与 API 调用](#前端实现:Streamlit 会话管理与 API 调用)
  6. 安全实践要点
  7. 运行与测试指南
  8. [使用 SQLite 命令行查看数据](#使用 SQLite 命令行查看数据)
  9. 扩展方向
  10. 总结

一、系统概览与技术栈

组件 技术 职责
后端框架 FastAPI 提供 REST API,处理认证、上传、查询
认证方式 JWT(JSON Web Token) 无状态认证,Token 包含用户标识和过期时间
密码哈希 Passlib (pbkdf2_sha256) 安全存储用户密码,防数据库泄露
前端界面 Streamlit 快速构建交互式 UI,管理登录状态
数据库 SQLite + aiosqlite 存储用户信息和上传记录,支持异步操作
文件存储 本地磁盘(UUID 重命名) 防止路径遍历,避免文件名冲突

核心安全特性

  • 密码从不明文存储,只保存哈希值。
  • JWT Token 有效期 24 小时,验证失败返回 401。
  • 文件重命名(UUID)彻底杜绝用户可控路径。
  • 文件大小限制(50 MB),避免资源耗尽。

二、核心功能与流程

认证流程

  1. 注册 :用户提供用户名和密码,后端使用 pbkdf2_sha256 加密后存入 users 表。
  2. 登录:验证用户名和密码,若成功则生成 JWT Token(有效期 24 小时)并返回。
  3. Token 使用 :前端将 Token 保存在 st.session_state 中,后续请求携带 Authorization: Bearer <token>

文件上传与记录流程

  1. 用户选择文件,点击上传。
  2. 前端发送带 Token 的 POST 请求到 /upload
  3. 后端验证 Token,获取 user_id
  4. 检查文件大小,生成 UUID 文件名,保存到磁盘。
  5. 将记录(原始名、保存名、路径、大小、用户 ID、时间)写入 upload_records 表。
  6. 返回成功响应。

查询记录流程

  1. 用户点击"查看我的上传记录"。
  2. 前端发送带 Token 的 GET 请求到 /records
  3. 后端验证 Token,获取 user_id,查询该用户的所有记录(按时间倒序)。
  4. 返回 JSON 数组,前端展示。

三、数据库设计

用户表 users

字段 类型 说明
id INTEGER 自增主键
username TEXT UNIQUE 用户名,唯一索引
password_hash TEXT 哈希后的密码
created_at TIMESTAMP 注册时间

上传记录表 upload_records

字段 类型 说明
id INTEGER 自增主键
original_filename TEXT 原始文件名
saved_filename TEXT 实际保存的文件名(UUID + 扩展名)
saved_path TEXT 文件完整路径
file_size INTEGER 字节数
user_id TEXT 上传者的用户名(来自 JWT)
upload_time TIMESTAMP 上传时间,默认当前时间

外键关系upload_records.user_id 对应 users.username(字符串关联,未启用外键约束,但业务逻辑保证一致性)。


四、后端代码详解

4.1 密码哈希与用户注册/登录

使用 passlib 提供安全的密码哈希(默认 pbkdf2_sha256,可配置为 bcrypt 等)。

python 复制代码
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")

# 注册
@app.post("/register")
async def register(username: str, password: str):
    password_hash = pwd_context.hash(password)
    async with aiosqlite.connect(DATABASE_PATH) as db:
        try:
            await db.execute("INSERT INTO users (username, password_hash) VALUES (?, ?)", (username, password_hash))
            await db.commit()
        except aiosqlite.IntegrityError:
            raise HTTPException(status_code=400, detail="用户名已存在")
    return {"message": "注册成功"}

# 登录
@app.post("/login")
async def login(username: str, password: str):
    async with aiosqlite.connect(DATABASE_PATH) as db:
        cursor = await db.execute("SELECT id, password_hash FROM users WHERE username = ?", (username,))
        row = await cursor.fetchone()
    if not row or not pwd_context.verify(password, row[1]):
        raise HTTPException(status_code=401, detail="用户名或密码错误")
    token = create_access_token(username)
    return {"access_token": token, "token_type": "bearer", "user_id": username}
  • 哈希函数pwd_context.hash(password) 生成不可逆的哈希值,每次调用会随机加盐。
  • 验证pwd_context.verify(password, hash) 在常数时间内比较。
  • 注册时 检查 IntegrityError 处理重复用户名。

4.2 JWT 签发与验证

python 复制代码
import jwt
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials

JWT_SECRET = "replace_with_your_secret_key"
JWT_ALGORITHM = "HS256"

def create_access_token(user_id: str):
    payload = {
        "sub": user_id,
        "exp": datetime.utcnow() + timedelta(hours=24)
    }
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)

security = HTTPBearer()

async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
    token = credentials.credentials
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
        user_id = payload.get("sub")
        if not user_id:
            raise HTTPException(401, "无效Token")
        return user_id
    except jwt.ExpiredSignatureError:
        raise HTTPException(401, "Token已过期")
    except jwt.InvalidTokenError:
        raise HTTPException(401, "Token无效")
  • HTTPBearer 自动从请求头提取 Bearer <token>
  • get_current_user 作为依赖项,可被任何需要认证的路由复用。
  • 有效期 24 小时,过期需重新登录。

4.3 文件上传与记录存储

python 复制代码
@app.post("/upload")
async def upload_document(
    file: UploadFile = File(...),
    current_user: str = Depends(get_current_user)
):
    # 大小检查(通过指针移动)
    file.file.seek(0, 2)
    size = file.file.tell()
    if size > MAX_FILE_SIZE:
        raise HTTPException(413, detail="文件过大")
    file.file.seek(0)

    # 生成唯一文件名
    ext = os.path.splitext(file.filename)[1]
    unique_filename = f"{uuid.uuid4().hex}{ext}"
    save_path = os.path.join(UPLOAD_DIR, unique_filename)

    # 保存文件
    with open(save_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)

    # 写入数据库
    await insert_upload_record(file.filename, unique_filename, save_path, size, current_user)

    return {"status": "success", "saved_filename": unique_filename, ...}
  • 大小检查seek(0, 2) 移动指针到末尾获取总长度,然后 seek(0) 复位。
  • 异步数据库操作 :使用 aiosqlite 避免阻塞事件循环。
  • shutil.copyfileobj 以块形式写入,内存友好。

4.4 查询用户记录

python 复制代码
@app.get("/records")
async def get_records(current_user: str = Depends(get_current_user), limit: int = 50):
    async with aiosqlite.connect(DATABASE_PATH) as db:
        cursor = await db.execute("""
            SELECT * FROM upload_records
            WHERE user_id = ? ORDER BY upload_time DESC LIMIT ?
        """, (current_user, limit))
        rows = await cursor.fetchall()
        columns = [desc[0] for desc in cursor.description]
        return [dict(zip(columns, row)) for row in rows]
  • 只返回当前用户自己的记录,天然数据隔离。
  • 支持 limit 参数,默认 50 条。

五、前端实现:Streamlit 会话管理与 API 调用

Streamlit 前端主要管理 st.session_state 存储 Token,并提供注册、登录、上传、查询四个界面。

注册与登录

python 复制代码
# 注册独立组件(不依赖登录状态)
st.subheader("用户注册")
reg_username = st.text_input("用户名(注册)", key="reg_username")
reg_password = st.text_input("密码", type="password", key="reg_password")
if st.button("注册"):
    resp = requests.post(REGISTER_URL, params={"username": reg_username, "password": reg_password})
    # 处理响应...

# 未登录时显示登录表单
if st.session_state.token is None:
    username = st.text_input("用户名", key="login_username")
    password = st.text_input("密码", type="password", key="login_password")
    if st.button("登录"):
        resp = requests.post(LOGIN_URL, params={"username": username, "password": password})
        if resp.status_code == 200:
            st.session_state.token = resp.json()["access_token"]
            st.session_state.user_id = resp.json()["user_id"]
            st.rerun()
    st.stop()   # 阻止后续内容渲染

上传文件

python 复制代码
headers = {"Authorization": f"Bearer {st.session_state.token}"}
uploaded_file = st.file_uploader("选择文件")
if uploaded_file and st.button("上传文件"):
    files = {"file": (uploaded_file.name, uploaded_file.getvalue(), uploaded_file.type)}
    resp = requests.post(UPLOAD_URL, files=files, headers=headers)
    st.json(resp.json())

查询记录

python 复制代码
if st.button("查看我的上传记录"):
    resp = requests.get(RECORDS_URL, headers=headers)
    records = resp.json()
    for rec in records:
        st.markdown(f"**原文件名:** {rec['original_filename']} ...")

退出登录 :清空 st.session_state.tokenuser_id,并调用 st.rerun()


六、安全实践要点

威胁 防护措施
密码泄露 使用 pbkdf2_sha256 加盐哈希,绝不存储明文
Token 伪造 JWT 使用强密钥签名,验证签名和过期时间
文件路径遍历 使用 uuid.uuid4().hex 完全重命名,忽略用户输入的文件名
超大文件上传 限制 MAX_FILE_SIZE(50 MB),检查 Content-Length 或流式读取
数据库注入 使用参数化查询(? 占位符),SQLite 驱动自动转义
跨域攻击 开发环境 allow_origins=["*"],生产环境限制具体域名

七、运行与测试指南

1. 安装依赖

创建 requirements.txt

复制代码
fastapi
uvicorn
streamlit
requests
python-multipart
aiosqlite
PyJWT
passlib

安装:

bash 复制代码
pip install -r requirements.txt

2. 启动后端

保存后端代码为 backend.py,并修改 JWT_SECRET 为强随机字符串(如 openssl rand -hex 32)。

bash 复制代码
python backend.py

默认运行在 http://0.0.0.0:6006(代码中端口 6006)。

3. 启动前端

保存前端代码为 frontend.py,确保 BASE_URL 与后端一致。

bash 复制代码
streamlit run frontend.py --server.address 0.0.0.0 --server.port 8501

浏览器打开 http://localhost:8501

4. 测试步骤

  1. 注册一个新用户(如 alice / 123456)。
  2. 登录,页面自动切换到已登录状态。
  3. 选择一个文件上传,看到成功提示。
  4. 点击"查看我的上传记录",显示刚才上传的文件信息。
  5. 可以注册另一个用户 bob,上传文件后只能看到自己的记录,验证数据隔离。
  6. 退出登录,尝试直接访问上传或记录接口(使用工具如 curl)应返回 401。

八、使用 SQLite 命令行查看数据

进入项目目录(与 backend.py 同级),执行:

bash 复制代码
sqlite3 upload_records.db

常用命令:

  • .tables -- 查看所有表
  • .schema users -- 查看 users 表结构
  • .schema upload_records -- 查看上传记录表结构

查询用户

sql 复制代码
.headers on
.mode column
SELECT id, username, created_at FROM users;

查询上传记录

sql 复制代码
SELECT id, original_filename, saved_filename, file_size, user_id, upload_time
FROM upload_records
ORDER BY id DESC
LIMIT 10;

退出:.quit 或按 Ctrl+D


代码汇总

backend.py

python 复制代码
import os
import shutil
import uuid
import sqlite3
from datetime import datetime, timedelta

import aiosqlite
from fastapi import FastAPI, File, UploadFile, HTTPException, Depends
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
import jwt
from passlib.context import CryptContext

# =====================================================
# 配置
# =====================================================
UPLOAD_DIR = "./uploaded_files"
DATABASE_PATH = "./upload_records.db"
MAX_FILE_SIZE = 50 * 1024 * 1024  # 50 MB
JWT_SECRET = "replace_with_your_secret_key"
JWT_ALGORITHM = "HS256"

os.makedirs(UPLOAD_DIR, exist_ok=True)

pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto")
security = HTTPBearer()

# =====================================================
# 初始化数据库
# =====================================================
def init_db():
    conn = sqlite3.connect(DATABASE_PATH)
    cursor = conn.cursor()
    # 上传记录表
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS upload_records (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            original_filename TEXT NOT NULL,
            saved_filename TEXT NOT NULL,
            saved_path TEXT NOT NULL,
            file_size INTEGER NOT NULL,
            user_id TEXT NOT NULL,
            upload_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    # 用户表
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            username TEXT UNIQUE NOT NULL,
            password_hash TEXT NOT NULL,
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    conn.commit()
    conn.close()

# =====================================================
# JWT 工具
# =====================================================
def create_access_token(user_id: str):
    payload = {
        "sub": user_id,
        "exp": datetime.utcnow() + timedelta(hours=24)
    }
    return jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)

async def get_current_user(credentials: HTTPAuthorizationCredentials = Depends(security)):
    token = credentials.credentials
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
        user_id = payload.get("sub")
        if not user_id:
            raise HTTPException(status_code=401, detail="无效Token")
        return user_id
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token已过期")
    except jwt.InvalidTokenError:
        raise HTTPException(status_code=401, detail="Token无效")

# =====================================================
# 数据库操作
# =====================================================
async def insert_upload_record(original_filename, saved_filename, saved_path, file_size, user_id):
    async with aiosqlite.connect(DATABASE_PATH) as db:
        await db.execute("""
            INSERT INTO upload_records
            (original_filename, saved_filename, saved_path, file_size, user_id, upload_time)
            VALUES (?, ?, ?, ?, ?, ?)
        """, (original_filename, saved_filename, saved_path, file_size, user_id, datetime.now()))
        await db.commit()

# =====================================================
# FastAPI 应用
# =====================================================
app = FastAPI(title="文档上传系统")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.on_event("startup")
async def startup():
    init_db()

@app.get("/")
async def root():
    return {"message": "服务运行正常"}

# =====================================================
# 用户注册/登录
# =====================================================
@app.post("/register")
async def register(username: str, password: str):
    password_hash = pwd_context.hash(password)
    async with aiosqlite.connect(DATABASE_PATH) as db:
        try:
            await db.execute("INSERT INTO users (username, password_hash) VALUES (?, ?)", (username, password_hash))
            await db.commit()
        except aiosqlite.IntegrityError:
            raise HTTPException(status_code=400, detail="用户名已存在")
    return {"message": "注册成功"}

@app.post("/login")
async def login(username: str, password: str):
    async with aiosqlite.connect(DATABASE_PATH) as db:
        cursor = await db.execute("SELECT id, password_hash FROM users WHERE username = ?", (username,))
        row = await cursor.fetchone()
    if not row:
        raise HTTPException(status_code=401, detail="用户名或密码错误")
    user_id, password_hash = row
    if not pwd_context.verify(password, password_hash):
        raise HTTPException(status_code=401, detail="用户名或密码错误")
    token = create_access_token(username)
    return {"access_token": token, "token_type": "bearer", "user_id": username}

# =====================================================
# 文件上传
# =====================================================
@app.post("/upload")
async def upload_document(file: UploadFile = File(...), current_user: str = Depends(get_current_user)):
    file.file.seek(0, 2)
    size = file.file.tell()
    if size > MAX_FILE_SIZE:
        raise HTTPException(status_code=413, detail=f"文件超过 {MAX_FILE_SIZE // (1024*1024)}MB")
    file.file.seek(0)

    original_filename = file.filename
    ext = os.path.splitext(original_filename)[1]
    unique_filename = f"{uuid.uuid4().hex}{ext}"
    save_path = os.path.join(UPLOAD_DIR, unique_filename)

    try:
        with open(save_path, "wb") as buffer:
            shutil.copyfileobj(file.file, buffer)
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"保存失败: {str(e)}")

    await insert_upload_record(original_filename, unique_filename, save_path, size, current_user)

    return JSONResponse(content={
        "status": "success",
        "message": "上传成功",
        "user_id": current_user,
        "saved_filename": unique_filename,
        "file_size": size
    })

# =====================================================
# 查询用户上传记录
# =====================================================
@app.get("/records")
async def get_records(current_user: str = Depends(get_current_user), limit: int = 50):
    async with aiosqlite.connect(DATABASE_PATH) as db:
        cursor = await db.execute("""
            SELECT *
            FROM upload_records
            WHERE user_id = ?
            ORDER BY upload_time DESC
            LIMIT ?
        """, (current_user, limit))
        rows = await cursor.fetchall()
        columns = [desc[0] for desc in cursor.description]
        return [dict(zip(columns, row)) for row in rows]

# =====================================================
# 启动
# =====================================================
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=6006)

frontend.py

python 复制代码
import streamlit as st
import requests

BASE_URL = "http://127.0.0.1:6006"
REGISTER_URL = f"{BASE_URL}/register"
LOGIN_URL = f"{BASE_URL}/login"
UPLOAD_URL = f"{BASE_URL}/upload"
RECORDS_URL = f"{BASE_URL}/records"

st.set_page_config(page_title="文档上传系统", page_icon="📄")

# ----------------- Session -----------------
if "token" not in st.session_state:
    st.session_state.token = None
if "user_id" not in st.session_state:
    st.session_state.user_id = None

# ----------------- 注册 -----------------
st.subheader("用户注册")
reg_username = st.text_input("用户名(注册)", key="reg_username")
reg_password = st.text_input("密码", type="password", key="reg_password")
if st.button("注册"):
    try:
        resp = requests.post(
            REGISTER_URL,
            params={
                "username": reg_username,
                "password": reg_password
            }
        )

        st.write("状态码:", resp.status_code)
        st.write("返回内容:")
        st.code(resp.text)

        if resp.status_code == 200:
            st.success("注册成功")
        else:
            st.error("注册失败")

    except Exception as e:
        st.exception(e)

# ----------------- 登录 -----------------
if st.session_state.token is None:
    st.subheader("用户登录")
    username = st.text_input("用户名", key="login_username")
    password = st.text_input("密码", type="password", key="login_password")
    if st.button("登录"):
        try:
            resp = requests.post(LOGIN_URL, params={"username": username, "password": password})
            if resp.status_code == 200:
                data = resp.json()
                st.session_state.token = data["access_token"]
                st.session_state.user_id = data["user_id"]
                st.success("登录成功")
                st.rerun()
            else:
                st.error(resp.json()["detail"])
        except Exception as e:
            st.error(str(e))
    st.stop()

# ----------------- 已登录 -----------------
st.success(f"当前用户:{st.session_state.user_id}")
headers = {"Authorization": f"Bearer {st.session_state.token}"}

# ----------------- 上传文件 -----------------
uploaded_file = st.file_uploader("选择文件")

if uploaded_file is not None:

    st.write("文件名:", uploaded_file.name)
    st.write("文件大小:", uploaded_file.size)

    if st.button("上传文件"):

        files = {
            "file": (
                uploaded_file.name,
                uploaded_file.getvalue(),
                uploaded_file.type
            )
        }

        try:

            with st.spinner("上传中..."):

                resp = requests.post(
                    UPLOAD_URL,
                    files=files,
                    headers=headers
                )

            if resp.status_code == 200:

                st.success("上传成功")

                st.json(resp.json())

            else:

                try:
                    st.error(resp.json()["detail"])
                except:
                    st.error(resp.text)

        except Exception as e:

            st.error(str(e))

# ----------------- 查询记录 -----------------
st.markdown("---")

if st.button("查看我的上传记录"):

    try:

        resp = requests.get(
            RECORDS_URL,
            headers=headers
        )

        if resp.status_code == 200:

            records = resp.json()

            if len(records) == 0:

                st.info("暂无上传记录")

            else:

                st.subheader("上传历史")

                for rec in records:

                    st.markdown(
                        f"""
**原文件名:** {rec['original_filename']}

**保存文件名:** `{rec['saved_filename']}`

**上传时间:** {rec['upload_time']}

**文件大小:** {rec['file_size']} 字节

---
"""
                    )

        else:

            try:
                st.error(resp.json()["detail"])
            except:
                st.error(resp.text)

    except Exception as e:

        st.error(str(e))

# ----------------- 退出登录 -----------------
st.markdown("---")

if st.button("退出登录"):

    st.session_state.token = None
    st.session_state.user_id = None

    st.rerun()

九、扩展方向

  • 文件类型限制 :检查 file.content_type 或扩展名白名单。
  • 文件下载接口:为已上传文件生成临时 URL 或直接读取返回。
  • 分片上传:支持大文件断点续传。
  • 密码强度校验:增加密码复杂度要求。
  • 刷新 Token:实现 refresh token 机制,避免频繁登录。
  • 云存储集成:将文件保存到阿里云 OSS、MinIO 或 AWS S3。

十、总结

本文构建了一个具备完整认证和授权机制的文件上传系统。主要亮点包括:

  • ✅ 用户注册/登录,密码安全哈希存储。
  • ✅ JWT 无状态认证,Token 有效期控制。
  • ✅ 文件上传与记录持久化,每个用户数据隔离。
  • ✅ Streamlit 前端提供友好的交互界面,会话管理简单。
  • ✅ 安全性考虑:文件大小限制、UUID 重命名、参数化查询、密钥环境变量等。

该系统可以作为文档管理、RAG 知识库上传组件的坚实基础。你可以在此基础上添加更多功能,打造企业级应用。

相关推荐
是发财不是旺财1 小时前
Hermes 网关四层权限控制方案:让 AI Agent 安全地查数据库
数据库·安全·agent·openclaw·hermes
持敬chijing1 小时前
Web渗透之前后端漏洞-XSS漏洞原理攻击防御全流程
前端·安全·web安全·网络安全·网络攻击模型·安全威胁分析·xss
ZeroNews内网穿透1 小时前
NAS部署Hermes AI Agent + 零讯内网穿透,实现远程可管理的AI助手
人工智能·安全·ai·内网穿透
持敬chijing2 小时前
Web渗透之SQL注入总结
sql·安全·web安全·网络安全·网络攻击模型·web
kishu_iOS&AI2 小时前
LLM —— 安全和合规性
安全·大模型·agent
m0_738120722 小时前
HVV应急溯源基础——Linux 系统安全加固配置指南(一)
linux·运维·服务器·安全·网络安全·系统安全
guyuyiqi3 小时前
糖精钠检测技术科普
科技·安全·制造
布朗克1684 小时前
26 多线程基础——Thread、Runnable与线程安全
java·安全·多线程
lulu12165440784 小时前
大模型API聚合平台技术架构深度对比:六大平台协议转换、路由调度与安全治理全解析 - 微元算力(weytoken)
java·人工智能·安全·架构·ai编程