Skip to content

Commit 579bc37

Browse files
committed
v2.3.7a
1 parent 4563d2a commit 579bc37

35 files changed

Lines changed: 335 additions & 660 deletions

backend/app/configs/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class Settings(BaseSettings):
3535
GITEE_WEBHOOK_SECRET: str | None = None
3636
SEND_BOOT_EMAIL: bool = True
3737
ADMIN_EMAIL: str = ""
38+
FEISHU_WEBHOOK_URL: str = ""
3839

3940
model_config = SettingsConfigDict(
4041
env_file=get_env_file_path(),
@@ -60,3 +61,4 @@ def get_settings() -> Settings:
6061
print(get_env_file_path())
6162
print(f"SEND_BOOT_EMAIL: {get_settings().SEND_BOOT_EMAIL}")
6263
print(f"ADMIN_EMAIL: {get_settings().ADMIN_EMAIL}")
64+
print(f"FEISHU_WEBHOOK_URL: {get_settings().FEISHU_WEBHOOK_URL}")

backend/app/configs/logger.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@
1919
)
2020

2121
base = Path(__file__).parent.parent
22-
base = base.parent
22+
base: Path = base.parent
2323

24-
log_path_env = os.getenv("LOG_PATH")
24+
log_path_env: str | None = os.getenv("LOG_PATH")
2525
if log_path_env:
2626
log_path = Path(log_path_env)
2727
else:
2828
log_dir = Path(os.getenv("LOG_DIR", base / "logs"))
29-
log_path = log_dir / "app.log"
29+
log_path: Path = log_dir / "app.log"
3030

3131
with contextlib.suppress(FileNotFoundError):
3232
log_path.parent.mkdir(parents=True, exist_ok=True)
3333

34-
info_log_path = log_path.with_stem(f"{log_path.stem}_info")
35-
error_log_path = log_path.with_stem(f"{log_path.stem}_error")
34+
info_log_path: Path = log_path.with_stem(f"{log_path.stem}_info")
35+
error_log_path: Path = log_path.with_stem(f"{log_path.stem}_error")
3636

3737
# INFO <= 级别 < ERROR 写入 app_info.log
3838
logger.add(

backend/app/main.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
users,
3838
weread,
3939
)
40-
from app.tasks import send_bootstrap_emails
40+
from app.tasks import send_bootstrap_emails, send_feishu_message
4141
from app.utils.cache import close_cache_redis
4242

4343

@@ -64,18 +64,20 @@ async def lifespan(app: FastAPI):
6464
logger.debug(f"Settings:{get_settings().model_dump()}")
6565
logger.info("FastAPI started successfully.")
6666

67-
# 发送引导邮件
67+
# 发送引导邮件和飞书消息
6868
admin_email: str = get_settings().ADMIN_EMAIL
6969
bootstrap_key: str = f"bootstrap_email_sent:{admin_email}"
7070
if app.state.redis is not None and get_settings().SEND_BOOT_EMAIL:
7171
lock_acquired = await app.state.redis.set(
7272
name=bootstrap_key, value="1", ex=600, nx=True
7373
)
7474
if lock_acquired:
75+
# 发送飞书消息
76+
await send_feishu_message.kiq()
7577
await send_bootstrap_emails.kiq(admin_email=admin_email)
76-
app_logger.info(f"引导邮件任务已添加到队列: {admin_email}")
78+
app_logger.info("✅启动通知任务已添加到队列")
7779
else:
78-
app_logger.info(f"引导邮件已在24小时内发送过,跳过: {admin_email}")
80+
app_logger.info("✅引导邮件已发送")
7981

8082
yield
8183

backend/app/routers/admin.py

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from app.schemas import VisitorData
2929
from app.schemas.response import APIResponse
3030
from app.schemas.schemas import BlogPostIn, BlogPostUpdate
31+
from app.tasks import send_feishu_message
3132
from app.utils import redis_cache
3233

3334
router = APIRouter(prefix="/admin", tags=["admin"])
@@ -448,8 +449,10 @@ async def track_visitor(
448449
async def run_deployment() -> None:
449450
"""异步执行部署脚本不阻塞HTTP请求"""
450451
try:
451-
logger.info("Starting deployment process...")
452+
logger.info("启动自动化部署...")
453+
import time
452454

455+
start_time = time.time()
453456
# 部署脚本路径,根据你的实际情况修改
454457
deploy_script = "/home/kano/blog/backend/deploy.sh"
455458

@@ -468,12 +471,12 @@ async def run_deployment() -> None:
468471
stdout, stderr = await process.communicate()
469472

470473
if process.returncode == 0:
471-
logger.info("Deployment completed successfully!")
472-
logger.debug(f"Deploy output: {stdout.decode()}")
474+
end_time: float = time.time()
475+
elapsed_time: float = end_time - start_time
476+
logger.info(f"✅自动化部署完成,耗时 {elapsed_time:.2f} 秒")
477+
logger.debug(f"✅Deploy output: {stdout.decode()}")
473478
else:
474-
logger.error(
475-
f"Deployment failed with exit code {process.returncode}"
476-
)
479+
logger.error(f"❌自动化部署失败,退出码: {process.returncode}")
477480
logger.error(f"STDOUT: {stdout.decode()}")
478481
logger.error(f"STDERR: {stderr.decode()}")
479482

@@ -490,7 +493,7 @@ async def webhook_deploy(
490493
Gitee Webhook 自动部署接口
491494
"""
492495
# 从环境变量获取webhook密钥,需要在.env中配置
493-
webhook_secret = get_settings().GITEE_WEBHOOK_SECRET
496+
webhook_secret: str | None = get_settings().GITEE_WEBHOOK_SECRET
494497
if not webhook_secret:
495498
logger.error(
496499
"GITEE_WEBHOOK_SECRET is not set in environment variables"
@@ -557,9 +560,29 @@ async def webhook_deploy(
557560
f"Deployment triggered by webhook from {get_remote_address(request)}"
558561
)
559562

560-
# 使用asyncio.create_task在后台执行部署,不阻塞请求
561-
asyncio.create_task(run_deployment()) # noqa: RUF006
562-
563-
return APIResponse.ok(
564-
message="Deployment triggered successfully", data={"status": "pending"}
563+
lock_acquired = await redis.set(
564+
name="deploy_lock", value="1", ex=300, nx=True
565565
)
566+
if not lock_acquired:
567+
logger.info("Deployment already in progress, skipping")
568+
return APIResponse.ok(
569+
message="Deployment already in progress",
570+
data={"status": "in_progress"},
571+
)
572+
try:
573+
logger.info(
574+
f"Deployment triggered by webhook from {get_remote_address(request)}"
575+
)
576+
await send_feishu_message.kiq("API服务正在部署中,请稍候...")
577+
asyncio.create_task(run_deployment()) # noqa: RUF006
578+
return APIResponse.ok(
579+
message="Deployment triggered successfully",
580+
data={"status": "pending"},
581+
)
582+
except Exception as e:
583+
await redis.delete("deploy_lock")
584+
logger.error(f"Failed to trigger deployment: {e!s}")
585+
return APIResponse.error(
586+
message="Failed to trigger deployment",
587+
code=status.HTTP_500_INTERNAL_SERVER_ERROR,
588+
)

backend/app/tasks/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
save_to_mongo,
77
send_bootstrap_emails,
88
send_code,
9+
send_feishu_message,
910
)
1011

1112
__all__ = [
@@ -17,4 +18,5 @@
1718
"scheduler",
1819
"send_bootstrap_emails",
1920
"send_code",
21+
"send_feishu_message",
2022
]

backend/app/tasks/broker.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from taskiq.middlewares import SmartRetryMiddleware
12
from taskiq_redis import RedisAsyncResultBackend, RedisStreamBroker
23

34
from app.configs.logger import logger
@@ -7,8 +8,20 @@
78
result_ex_time=86400,
89
)
910

10-
broker: RedisStreamBroker = RedisStreamBroker(
11-
url="redis://localhost:6379/3",
12-
).with_result_backend(result_backend=result_backend)
11+
broker: RedisStreamBroker = (
12+
RedisStreamBroker(
13+
url="redis://localhost:6379/3",
14+
)
15+
.with_result_backend(result_backend=result_backend)
16+
.with_middlewares(
17+
SmartRetryMiddleware(
18+
default_retry_count=5,
19+
default_delay=10,
20+
use_jitter=True,
21+
use_delay_exponent=True,
22+
max_delay_exponent=120,
23+
)
24+
)
25+
)
1326

14-
logger.info("[Taskiq] ✅ Broker initialized successfully")
27+
logger.info("[Taskiq]✅Broker初始化成功")

backend/app/tasks/task.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from datetime import UTC, datetime
22

3+
import httpx
34
from beanie import init_beanie
45
from email_validator import EmailNotValidError, ValidatedEmail, validate_email
56
from fastapi_mail import FastMail, MessageSchema, MessageType
@@ -216,3 +217,33 @@ async def save_cache_to_redis(
216217
await context.state.redis.set(key, value, ex=expire)
217218
except Exception as e:
218219
logger.error(f"❌Failed to save cache to Redis: {e!r}")
220+
221+
222+
class FeishuMessageContent(BaseModel):
223+
"""飞书消息内容模型"""
224+
225+
msg_type: str = "text"
226+
content: dict
227+
228+
229+
@broker.task
230+
async def send_feishu_message(message: str | None = None):
231+
"""发送飞书消息"""
232+
url: str = get_settings().FEISHU_WEBHOOK_URL
233+
now: str = datetime.now().strftime(format="%Y-%m-%d %H:%M:%S")
234+
if message is None:
235+
message = f"KUROOME BLOG API 已成功启动!时间:{now}"
236+
message = message.strip()
237+
if not url:
238+
return
239+
payload = FeishuMessageContent(
240+
msg_type="text",
241+
content={"text": message},
242+
)
243+
try:
244+
async with httpx.AsyncClient() as client:
245+
response = await client.post(url, json=payload)
246+
response.raise_for_status()
247+
logger.info("飞书消息已发送")
248+
except Exception as e:
249+
logger.error(f"发送飞书消息失败: {e!s}")

backend/app/utils/__init__.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
11
from app.utils.cache import get_redis_cache, redis_cache
2+
from app.utils.mailservice import send_bootstrap_emails, send_feishu_message
23

3-
__all__ = ["get_redis_cache", "redis_cache"]
4+
__all__ = [
5+
"get_redis_cache",
6+
"redis_cache",
7+
"send_bootstrap_emails",
8+
"send_feishu_message",
9+
]

backend/app/utils/mailservice.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import datetime
22

3+
import httpx
34
from email_validator import EmailNotValidError, ValidatedEmail, validate_email
45
from fastapi_mail import FastMail, MessageSchema, MessageType
56
from pydantic import BaseModel, EmailStr
@@ -61,3 +62,21 @@ async def send_bootstrap_emails(admin_email: str):
6162
logger.error(f"发送引导邮件失败: {e!s}")
6263
# print(f"[red]发送引导邮件失败: {e!s}[/red]")
6364
raise e
65+
66+
67+
async def send_feishu_message():
68+
"""发送飞书消息"""
69+
url = get_settings().FEISHU_WEBHOOK_URL
70+
if not url:
71+
return
72+
payload = {
73+
"msg_type": "text",
74+
"content": {"text": "KUROOME BLOG API 已成功启动!"},
75+
}
76+
try:
77+
async with httpx.AsyncClient() as client:
78+
response = await client.post(url, json=payload)
79+
response.raise_for_status()
80+
logger.info("飞书消息已发送")
81+
except Exception as e:
82+
logger.error(f"发送飞书消息失败: {e!s}")

frontend/src/components/article/ArticleDetailLayout.vue

Lines changed: 11 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,13 @@ const goBack = () => {
4747
};
4848
4949
const displayDate = computed(() => {
50-
if (
51-
props.publishedDate &&
52-
props.updatedDate &&
53-
props.updatedDate !== props.publishedDate
54-
) {
50+
if (props.publishedDate && props.updatedDate && props.updatedDate !== props.publishedDate) {
5551
return formatDate(props.updatedDate);
5652
}
5753
return props.publishedDate ? formatDate(props.publishedDate) : "";
5854
});
5955
60-
const formattedDate = computed(() =>
61-
props.publishedDate ? formatDate(props.publishedDate) : "",
62-
);
56+
const formattedDate = computed(() => (props.publishedDate ? formatDate(props.publishedDate) : ""));
6357
</script>
6458

6559
<template>
@@ -70,48 +64,30 @@ const formattedDate = computed(() =>
7064
:style="titleStyle"
7165
>
7266
<!-- Loading State -->
73-
<div
74-
v-if="isLoading"
75-
class="flex min-h-[60vh] flex-col items-center justify-center"
76-
>
67+
<div v-if="isLoading" class="flex min-h-[60vh] flex-col items-center justify-center">
7768
<div
7869
class="h-12 w-12 animate-spin rounded-full border-4 border-blue-200 border-t-blue-600"
7970
></div>
8071
<p class="mt-4 text-gray-600 dark:text-gray-400">加载中...</p>
8172
</div>
8273

8374
<div v-else>
84-
<h1
85-
class="max-w-6xl text-center font-serif text-7xl text-gray-50 max-sm:text-3xl"
86-
>
75+
<h1 class="max-w-6xl text-center font-serif text-7xl text-gray-50 max-sm:text-3xl">
8776
{{ title }}
8877
</h1>
8978
<!-- Author and Date Info -->
90-
<div
91-
class="flex flex-wrap items-center justify-center gap-4 text-sm text-gray-400"
92-
>
79+
<div class="flex flex-wrap items-center justify-center gap-4 text-sm text-gray-400">
9380
<div class="flex items-center gap-2">
9481
<div
9582
class="flex h-10 w-10 items-center justify-center rounded-full bg-linear-to-br from-blue-500 to-sky-600 text-lg font-bold text-white"
9683
>
97-
{{
98-
author?.charAt(0).toUpperCase() ||
99-
authorInitial?.charAt(0).toUpperCase() ||
100-
"K"
101-
}}
84+
{{ author?.charAt(0).toUpperCase() || authorInitial?.charAt(0).toUpperCase() || "K" }}
10285
</div>
103-
<span class="font-medium text-gray-200"
104-
>@{{ author || "Kurroome" }}</span
105-
>
86+
<span class="font-medium text-gray-200">@{{ author || "Kurroome" }}</span>
10687
</div>
10788
<span class="text-gray-50">·</span>
10889
<div class="flex items-center gap-1">
109-
<svg
110-
class="h-4 w-4 text-gray-50"
111-
fill="none"
112-
stroke="currentColor"
113-
viewBox="0 0 24 24"
114-
>
90+
<svg class="h-4 w-4 text-gray-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
11591
<path
11692
stroke-linecap="round"
11793
stroke-linejoin="round"
@@ -124,12 +100,7 @@ const formattedDate = computed(() =>
124100
</span>
125101
</div>
126102
<span v-if="showCategory && category" class="flex items-center gap-1">
127-
<svg
128-
class="h-4 w-4 text-gray-50"
129-
fill="none"
130-
stroke="currentColor"
131-
viewBox="0 0 24 24"
132-
>
103+
<svg class="h-4 w-4 text-gray-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
133104
<path
134105
stroke-linecap="round"
135106
stroke-linejoin="round"
@@ -153,7 +124,7 @@ const formattedDate = computed(() =>
153124
<!-- 背景层 -->
154125
<div
155126
:style="sectionStyle"
156-
class="absolute inset-y-0 left-1/2 -z-5 -translate-x-1/2 rounded-t-[40px] bg-blue-50 backdrop-blur-sm dark:bg-slate-900"
127+
class="absolute inset-y-0 left-1/2 -z-5 -translate-x-1/2 rounded-t-[40px] bg-blue-50 dark:bg-slate-900"
157128
/>
158129

159130
<div class="mx-auto mt-20 max-w-4xl">
@@ -223,9 +194,7 @@ const formattedDate = computed(() =>
223194
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"
224195
/>
225196
</svg>
226-
<p class="text-lg font-medium text-red-600 dark:text-red-400">
227-
加载失败
228-
</p>
197+
<p class="text-lg font-medium text-red-600 dark:text-red-400">加载失败</p>
229198
<p class="mt-1 text-sm text-red-500">{{ errorMessage }}</p>
230199
<button
231200
class="mt-4 cursor-pointer rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700"

0 commit comments

Comments
 (0)