This commit is contained in:
2026-07-20 10:04:57 +08:00
parent 22cbe5bb4a
commit 9658015972
4 changed files with 95 additions and 5 deletions
+67 -1
View File
@@ -1,14 +1,16 @@
from contextlib import asynccontextmanager
from pathlib import Path
import logging
import asyncio
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from starlette.requests import Request
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from backend.database import init_db
from backend.database import init_db, async_session
from backend.routers import api, admin
from backend.config import get_settings
from backend.bootstrap import ensure_self_signed_cert
@@ -25,12 +27,75 @@ from backend.routers.admin import (
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("certcenter")
scheduler = AsyncIOScheduler()
async def auto_renew_job():
"""定时任务:自动续签即将过期的证书"""
from backend.routers.admin import _get_domain_acme_config
from backend.models import Domain, AcmeLog
from backend.acme_service import AcmeService
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from datetime import datetime
logger.info("[定时任务] 开始检查证书续签...")
try:
async with async_session() as db:
settings = get_settings()
result = await db.execute(select(Domain).options(selectinload(Domain.acme_config)))
domains = result.scalars().all()
renewed = 0
for d in domains:
try:
config = await _get_domain_acme_config(d, db)
except Exception:
continue
service = AcmeService(config, settings.cert_dir)
need_renew, days_left = service.check_expiry(d.domain)
if not need_renew:
continue
logger.info(f"[定时任务] {d.domain} 需要续签(剩余 {days_left} 天)")
log = AcmeLog(domain_id=d.id, action="renew", status="pending", message=f"自动续签 {d.domain}")
db.add(log)
await db.flush()
try:
success, msg = service.renew_certificate(d.domain)
log.status = "success" if success else "failed"
log.message = msg
if success:
info = service.get_cert_info(d.domain)
if info:
d.cert_not_after = datetime.fromisoformat(info["not_after"])
d.version = str(int(d.version or "0") + 1)
renewed += 1
logger.info(f"[定时任务] {d.domain}: {'成功' if success else '失败'} - {msg}")
except Exception as e:
log.status = "failed"
log.message = str(e)
logger.error(f"[定时任务] {d.domain} 续签异常: {e}")
await db.commit()
logger.info(f"[定时任务] 检查完成,续签 {renewed} 个证书")
except Exception as e:
logger.error(f"[定时任务] 执行异常: {e}")
@asynccontextmanager
async def lifespan(app: FastAPI):
await init_db()
settings = get_settings()
ensure_self_signed_cert(settings.base_url, settings.cert_dir)
# 启动定时续签任务(每天凌晨3点执行)
scheduler.add_job(auto_renew_job, "cron", hour=3, minute=0, id="auto_renew", replace_existing=True)
scheduler.start()
logger.info("=== 定时续签任务已启动(每天 03:00) ===")
logger.info("=== 服务启动完成 ===")
# 打印所有已注册的路由
for route in app.routes:
@@ -41,6 +106,7 @@ async def lifespan(app: FastAPI):
else:
logger.info(f" [included router] {type(route).__name__}")
yield
scheduler.shutdown()
app = FastAPI(title="CertCenter", version="1.0.0", lifespan=lifespan)