feat: 阶段 1 最小闭环 - 建表 SQL、/wechat 事件、/auth 接口、首次关注免费授权

- sql/schema.sql: users/authorizations/auth_scenes/usage_logs + sessions 建表
- db.py: aiomysql 连接池,lifespan 内初始化与释放
- wechat_api.py: access_token 缓存 + 临时二维码创建
- auth.py: /auth/create_scene、/auth/status,handle_scan 事务内幂等处理扫码
- wechat.py: 接入 DB 生命周期,处理 subscribe/SCAN 事件;移除多余的 openid query 参数
- 首次关注赠送 7 天免费授权,has_claimed_free 条件更新保证幂等
- config.py/.env.example: 新增 FREE_AUTH_DAYS/SCENE_TTL_SECONDS/SESSION_TTL_HOURS
This commit is contained in:
gjm committed 2026-09-26 22:25:02 +08:00
1 parent a2185104ad
commit 3790821ce0
10 files changed
+915 -32

No files matched your search

+58
View File
@@ -0,0 +1,58 @@
"""
MySQL 连接池管理(aiomysql)
用法:
await init_pool() # 应用启动时调用
async with acquire() as conn:
...
await close_pool() # 应用关闭时调用
连接池开启 autocommit,单条语句自动提交;需要事务时显式调用 conn.begin() / conn.commit()。
"""
from contextlib import asynccontextmanager
from typing import Optional
import aiomysql
from config import MYSQL_DB, MYSQL_HOST, MYSQL_PASSWORD, MYSQL_PORT, MYSQL_USER
_pool: Optional[aiomysql.Pool] = None
async def init_pool() -> None:
"""创建全局连接池"""
global _pool
_pool = await aiomysql.create_pool(
host=MYSQL_HOST,
port=MYSQL_PORT,
user=MYSQL_USER,
password=MYSQL_PASSWORD,
db=MYSQL_DB,
charset="utf8mb4",
autocommit=True,
minsize=1,
maxsize=10,
)
async def close_pool() -> None:
"""关闭连接池"""
global _pool
if _pool is not None:
_pool.close()
await _pool.wait_closed()
_pool = None
def get_pool() -> aiomysql.Pool:
if _pool is None:
raise RuntimeError("MySQL 连接池未初始化,请先调用 init_pool()")
return _pool
@asynccontextmanager
async def acquire():
"""从连接池借出一个连接"""
async with get_pool().acquire() as conn:
yield conn