Files
server-core/app/config.py
T
Pine 32ca580a06 feat(auth): 统一四种登录 —— 短信演示码123456 + 微信扫码OAuth + 小程序扫码
- sms stub 固定演示码123456
- 微信扫码(开放平台标准OAuth): /auth/wx-qr/start|wx-callback(GET/POST)|wx-qr/poll
- 小程序扫码: /auth/mp-qr/start|poll|confirm
- /auth/status 返回四 login_modes;WECHAT_OPEN_APPID/SECRET、AUTH_WECHAT_QR、WECHAT_QR_REDIRECT_URI 配置
2026-08-25 22:47:27 +08:00

162 lines
8.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""全局配置:端口、数据目录、令牌有效期、认证开关。
所有路径/开关均可通过环境变量覆盖,便于部署和测试:
PINEAGENTS_DEMO_HOST / PINEAGENTS_DEMO_PORT 监听地址(默认 127.0.0.1:8090
PINEAGENTS_DEMO_DATA_DIR JSON 数据目录(默认 <项目根>/data
PINEAGENTS_DEMO_AUTH_ENABLED 认证开关(默认 true)
"""
from __future__ import annotations
import os
from pathlib import Path
# 启动第一步即加载项目根 .env(含 PINEAGENTS_INTERNAL_TOKEN / DATABASE_URL 等),
# 否则后续 os.environ.get 读到空串(.env 后续才会被 app.park/app.training 各自加载)。
# 必须在下方任何 os.environ.get 之前执行。
try: # python-dotenv 为可选依赖;缺失时仍可用系统环境变量
from dotenv import load_dotenv
load_dotenv(Path(__file__).resolve().parent.parent / ".env")
except Exception: # noqa: BLE001
pass
# 项目根目录 = 本文件的上上级(app/config.py -> <根>
BASE_DIR = Path(__file__).resolve().parent.parent
# ---------------------------------------------------------------------------
# 数据目录(serverdata/ 统一承载所有资源:data/logs/files/keys/prompts/uploads
# ---------------------------------------------------------------------------
SERVERDATA_DIR = Path(
os.environ.get("PINEAGENTS_SERVERDATA_DIR", str(BASE_DIR / "serverdata")),
).resolve()
# 数据子目录(缺失自动创建,start.sh 亦负责初始化)
DATA_DIR = SERVERDATA_DIR / "data"
LOG_DIR = SERVERDATA_DIR / "logs"
FILES_DIR = SERVERDATA_DIR / "files"
KEYS_DIR = SERVERDATA_DIR / "keys"
PROMPTS_DIR = SERVERDATA_DIR / "prompts"
UPLOADS_DIR = SERVERDATA_DIR / "uploads"
for _d in (DATA_DIR, LOG_DIR, FILES_DIR, KEYS_DIR, PROMPTS_DIR, UPLOADS_DIR):
_d.mkdir(parents=True, exist_ok=True)
# 数据库连接(异步)。开发默认 SQLite+aiosqlite;生产用 MySQL+asyncmy(环境变量覆盖)。
# 示例 MySQLPINEAGENTS_DEMO_DATABASE_URL=mysql+asyncmy://user:pass@127.0.0.1:3306/opc?charset=utf8mb4
DATABASE_URL = os.environ.get(
"PINEAGENTS_DEMO_DATABASE_URL",
f"sqlite+aiosqlite:///{DATA_DIR / 'app.db'}",
)
# Redis(缓存/会话/限流/分布式锁),空则降级内存。
REDIS_URL = os.environ.get("REDIS_URL", "redis://127.0.0.1:6379/0")
# OSS 对象存储(aioboto3,兼容阿里云 OSS / MinIO);空则文件落 serverdata/uploads。
OSS_ENDPOINT = os.environ.get("OSS_ENDPOINT", "")
OSS_ACCESS_KEY = os.environ.get("OSS_ACCESS_KEY", "")
OSS_SECRET_KEY = os.environ.get("OSS_SECRET_KEY", "")
OSS_BUCKET = os.environ.get("OSS_BUCKET", "opc-files")
USERS_FILE = DATA_DIR / "users.json"
TOKENS_FILE = DATA_DIR / "tokens.json"
AGENTS_FILE = DATA_DIR / "agents.json"
# 智能体初始化 md 模板(镜像本地 md_files 结构;qa 已统一为 PineAgents 品牌)。
# 目录:<PROMPTS_DIR>/<lang>/*.md(通用)、<PROMPTS_DIR>/qa/<lang>/*.md、
# <PROMPTS_DIR>/local/<lang>/*.md
TEMPLATES_DIR = PROMPTS_DIR
# 首次启动时用于初始化 data/ 的种子数据(提交到仓库,数据可读)。
SEED_DIR = BASE_DIR / "data" / "seed"
SEED_USERS_FILE = SEED_DIR / "demo_users.json"
# ---------------------------------------------------------------------------
# 服务监听
# ---------------------------------------------------------------------------
HOST = os.environ.get("PINEAGENTS_DEMO_HOST", "127.0.0.1")
PORT = int(os.environ.get("PINEAGENTS_DEMO_PORT", "8090"))
# ---------------------------------------------------------------------------
# 认证与令牌
# ---------------------------------------------------------------------------
# 演示服务默认开启认证(PineAgents 主后端把 /auth/* 转发到本服务)。
AUTH_ENABLED = (
os.environ.get("PINEAGENTS_DEMO_AUTH_ENABLED", "true").strip().lower()
in ("true", "1", "yes")
)
# 令牌有效期(秒):默认 7 天;-1/0 表示永久(按 100 年封顶)。
DEFAULT_TOKEN_EXPIRY_SECONDS = 7 * 24 * 3600
PERMANENT_TOKEN_SECONDS = 100 * 365 * 24 * 3600
TOKEN_EXPIRY_MAX = PERMANENT_TOKEN_SECONDS
# ---------------------------------------------------------------------------
# JWT
# ---------------------------------------------------------------------------
JWT_SECRET = os.environ.get(
"PINEAGENTS_DEMO_JWT_SECRET",
"dev-insecure-change-me-0123456789abcdef",
)
JWT_ALGORITHM = os.environ.get("PINEAGENTS_DEMO_JWT_ALGORITHM", "HS256")
JWT_ACCESS_EXPIRY_SECONDS = DEFAULT_TOKEN_EXPIRY_SECONDS
# ---------------------------------------------------------------------------
# 算力引擎(compute-engine / new-api)管理接入(loopback,仅服务)
# 模型调用与额度/令牌/流水均经此引擎计量;前端不直连引擎。
# ---------------------------------------------------------------------------
COMPUTE_BASE_URL = os.environ.get(
"PINEAGENTS_COMPUTE_BASE_URL", "http://127.0.0.1:3000",
)
# 受信任内部令牌:server-core 作为唯一入口,无权限限制访问引擎管理/root 端点。
# 与引擎侧 PINEAGENTS_INTERNAL_TOKEN 保持一致;回退兼容旧名 PINEAGENTS_COMPUTE_ADMIN_TOKEN。
COMPUTE_ADMIN_TOKEN = os.environ.get("PINEAGENTS_INTERNAL_TOKEN") or os.environ.get("PINEAGENTS_COMPUTE_ADMIN_TOKEN", "")
# /v1 模型中继转发(relay.py)用的引擎消费令牌:需是引擎 /v1 能识别的真实用户/渠道令牌,
# 与聚合 root 的 COMPUTE_ADMIN_TOKEN 区分开(后者仅用于管理/root 端点)。回退 COMPUTE_ADMIN_TOKEN。
COMPUTE_RELAY_TOKEN = os.environ.get("PINEAGENTS_COMPUTE_RELAY_TOKEN", "")
# 用户端算力中心「基础 API 地址」:对外公网地址(如 https://opc.pinesound.cn),供用户其它工具接入。
# 未设置时回落请求的 base_url。经代理时 request.base_url 可能为内部地址,故用此配置给公网地址。
COMPUTE_PUBLIC_BASE = os.environ.get("PINEAGENTS_COMPUTE_PUBLIC_BASE", "").rstrip("/")
COMPUTE_TIMEOUT = float(os.environ.get("PINEAGENTS_COMPUTE_TIMEOUT", "10"))
COMPUTE_RETRIES = int(os.environ.get("PINEAGENTS_COMPUTE_RETRIES", "2"))
# ---------------------------------------------------------------------------
# 短信验证码(统一登录)
# ---------------------------------------------------------------------------
# stub = 仅打印验证码不真正发送(默认,便于联调);生产接阿里云/腾讯云短信。
SMS_PROVIDER = os.environ.get("PINEAGENTS_SMS_PROVIDER", "stub").strip().lower()
SMS_CODE_TTL_SECONDS = int(os.environ.get("PINEAGENTS_SMS_CODE_TTL_SECONDS", "300"))
SMS_RATE_LIMIT = int(os.environ.get("PINEAGENTS_SMS_RATE_LIMIT", "5"))
# ---------------------------------------------------------------------------
# 微信登录(小程序 code2session + 开放平台网站应用扫码登录)
# 未配置 appid/secret 时走「测试直通」:扫码登录把 code 当 openid 返回,便于无真实凭据联调。
# WECHAT_APPID/SECRET 为小程序;WECHAT_OPEN_APPID/SECRET 为开放平台「网站应用」(扫码登录),缺省回退前者。
# ---------------------------------------------------------------------------
WECHAT_APPID = os.environ.get("PINEAGENTS_WECHAT_APPID", "")
WECHAT_SECRET = os.environ.get("PINEAGENTS_WECHAT_SECRET", "")
WECHAT_OPEN_APPID = os.environ.get("PINEAGENTS_WECHAT_OPEN_APPID", "") or WECHAT_APPID
WECHAT_OPEN_SECRET = os.environ.get("PINEAGENTS_WECHAT_OPEN_SECRET", "") or WECHAT_SECRET
# 扫码登录授权后的回跳地址(须在开放平台后台配置为该 URL):默认服务端 /auth/wx-callback。
# 未设置时由前端 request.base_url 推导。
WECHAT_QR_REDIRECT_URI = os.environ.get("PINEAGENTS_WECHAT_QR_REDIRECT_URI", "")
# 扫码登录开关(true 才返回可用的 qr_start;false 时前端隐藏「微信扫码」入口)
AUTH_WECHAT_QR = os.environ.get("PINEAGENTS_WECHAT_QR", "true").strip().lower() in ("true", "1", "yes")
# 本期统一登录仅两模式(账号密码/手机号验证码);微信扫码预留后期,未开放(默认 False)。
AUTH_WECHAT_LOGIN = os.environ.get("PINEAGENTS_WECHAT_LOGIN", "false").strip().lower() in ("true", "1", "yes")
def resolve_token_expiry(expires_in: int | None) -> int:
"""把前端传来的 ``expires_in`` 归一化为秒数。
- None → 默认 7 天
- <= 0(含 -1/0)→ 永久(100 年)
- 其他 → 按最大值封顶
"""
if expires_in is None:
return DEFAULT_TOKEN_EXPIRY_SECONDS
if expires_in <= 0:
return PERMANENT_TOKEN_SECONDS
return min(expires_in, TOKEN_EXPIRY_MAX)