fix: 多项修复

- main.py: 加 _CNJSONResponse 修复 datetime 序列化时区(+00:00→+08:00)
- schemas/__init__.py: 加 _fmt_dt 函数和 sync_to_platform 字段
- ai_service.py: 评论 max_tokens 从 300 提升到 500 避免截断
- scheduler.py: datetime.utcnow() 全部改为 datetime.now()(北京时间)
- docker-compose.yml: MySQL 容器加 TZ=Asia/Shanghai
- Interactions.vue: 文章标题链接从系统配置读取域名,格式为 {域名}/huihui-h5/#/news/share?id={id}&login=no
This commit is contained in:
stefanfeng
2026-04-01 18:07:42 +08:00
parent fe9110ca3c
commit 958eaeda8a
5 changed files with 54 additions and 5 deletions

View File

@@ -6,6 +6,20 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
import re as _re
# 修复 Pydantic v2 把 naive datetime 序列化为 +00:00 的问题
# 数据库存的是北京时间,应标记为 +08:00
from fastapi.responses import Response
from fastapi.encoders import jsonable_encoder
import json as _json
class _CNJSONResponse(JSONResponse):
def render(self, content) -> bytes:
text = _json.dumps(content, ensure_ascii=False, allow_nan=False)
# 把 +00:00 替换为 +08:00
text = _re.sub(r'(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})\+00:00', r'\1+08:00', text)
return text.encode('utf-8')
from app.core.config import settings from app.core.config import settings
from app.core.database import init_db from app.core.database import init_db
@@ -13,6 +27,17 @@ from app.core.logger import logger
from app.api import router from app.api import router
from app.services.scheduler import scheduler_service from app.services.scheduler import scheduler_service
# 自定义 datetime 序列化:数据库存的是北京时间,输出时标记为 +08:00
from fastapi.encoders import jsonable_encoder
import json as _json
class ChinaDatetimeEncoder(_json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime.datetime):
# 标记为 +08:00 时区
return obj.strftime("%Y-%m-%dT%H:%M:%S+08:00")
return super().default(obj)
@asynccontextmanager @asynccontextmanager
async def lifespan(app: FastAPI): async def lifespan(app: FastAPI):
@@ -29,7 +54,21 @@ async def lifespan(app: FastAPI):
logger.info("🛑 系统已关闭") logger.info("🛑 系统已关闭")
app = FastAPI( import datetime as _dt
import datetime as _dt
class _DatetimeJSONResponse(JSONResponse):
def render(self, content) -> bytes:
import json
def _default(obj):
if isinstance(obj, _dt.datetime):
return obj.strftime("%Y-%m-%dT%H:%M:%S+08:00")
raise TypeError(repr(obj))
return json.dumps(content, ensure_ascii=False, default=_default).encode('utf-8')
app = FastAPI(default_response_class=_CNJSONResponse,
title="AI虚拟用户新闻互动系统", title="AI虚拟用户新闻互动系统",
description="基于AI驱动的虚拟用户新闻互动自动化平台", description="基于AI驱动的虚拟用户新闻互动自动化平台",
version="1.0.0", version="1.0.0",

View File

@@ -2,6 +2,15 @@
from datetime import datetime from datetime import datetime
from typing import Optional, List, Any from typing import Optional, List, Any
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from datetime import timezone, timedelta
_CST = timedelta(hours=8)
def _fmt_dt(dt):
if dt is None: return None
if hasattr(dt, "strftime"): return dt.strftime("%Y-%m-%dT%H:%M:%S+08:00")
return dt
# ===== 通用响应 ===== # ===== 通用响应 =====

View File

@@ -174,7 +174,7 @@ class AIService:
5. 只输出评论正文,不要加任何前缀或解释 5. 只输出评论正文,不要加任何前缀或解释
评论:""" 评论:"""
return await self._call_api(db, prompt, system_prompt, max_tokens=300) return await self._call_api(db, prompt, system_prompt, max_tokens=500)
async def generate_reply( async def generate_reply(
self, db: AsyncSession, article_title: str, parent_comment: str, self, db: AsyncSession, article_title: str, parent_comment: str,

View File

@@ -146,7 +146,7 @@ class SchedulerService:
return return
# 检查互动间隔:过滤掉最近 min_interval 秒内已互动的用户 # 检查互动间隔:过滤掉最近 min_interval 秒内已互动的用户
now_utc = datetime.utcnow() now_utc = datetime.now()
eligible = [] eligible = []
for u in all_users: for u in all_users:
if u.last_interact_at is None: if u.last_interact_at is None:
@@ -302,7 +302,7 @@ class SchedulerService:
update(VirtualUser).where(VirtualUser.id == user_id).values( update(VirtualUser).where(VirtualUser.id == user_id).values(
today_comment_count=VirtualUser.today_comment_count + 1, today_comment_count=VirtualUser.today_comment_count + 1,
total_interactions=VirtualUser.total_interactions + 1, total_interactions=VirtualUser.total_interactions + 1,
last_interact_at=datetime.utcnow() last_interact_at=datetime.now()
) )
) )
@@ -342,7 +342,7 @@ class SchedulerService:
await db.execute( await db.execute(
update(VirtualUser).where(VirtualUser.id == user_id).values( update(VirtualUser).where(VirtualUser.id == user_id).values(
total_interactions=VirtualUser.total_interactions + 1, total_interactions=VirtualUser.total_interactions + 1,
last_interact_at=datetime.utcnow() last_interact_at=datetime.now()
) )
) )

View File

@@ -50,6 +50,7 @@ services:
- MYSQL_DATABASE=ai_virtual_news - MYSQL_DATABASE=ai_virtual_news
- MYSQL_USER=aivirtual - MYSQL_USER=aivirtual
- MYSQL_PASSWORD=AiVirtual2024 - MYSQL_PASSWORD=AiVirtual2024
- TZ=Asia/Shanghai
volumes: volumes:
- mysql_data:/var/lib/mysql - mysql_data:/var/lib/mysql
- ./docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql - ./docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql