mirror of
https://gitee.com/houhuan/TrendRadar.git
synced 2025-12-21 14:17:16 +08:00
v3.5.0: 新增了一些功能,详细见【更新日志】
This commit is contained in:
parent
6266751bae
commit
e6b95ada77
93
.github/workflows/docker.yml
vendored
93
.github/workflows/docker.yml
vendored
@ -1,17 +1,33 @@
|
||||
name: Build and Push Multi-Arch Docker Images
|
||||
name: Build and Push Docker Images
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
tags:
|
||||
- "v*" # 主项目版本
|
||||
- "mcp-v*" # MCP 版本
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
image:
|
||||
description: "选择要构建的镜像"
|
||||
required: true
|
||||
default: "all"
|
||||
type: choice
|
||||
options:
|
||||
- all
|
||||
- crawler
|
||||
- mcp
|
||||
|
||||
env:
|
||||
REGISTRY: docker.io
|
||||
IMAGE_NAME: wantcat/trendradar
|
||||
|
||||
jobs:
|
||||
build:
|
||||
build-crawler:
|
||||
runs-on: ubuntu-latest
|
||||
# 条件:v* 标签(排除 mcp-v*)或手动触发选择 all/crawler
|
||||
if: |
|
||||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') && !startsWith(github.ref, 'refs/tags/mcp-v')) ||
|
||||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.image == 'all' || github.event.inputs.image == 'crawler'))
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -35,12 +51,11 @@ jobs:
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.IMAGE_NAME }}
|
||||
images: wantcat/trendradar
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
@ -55,5 +70,65 @@ jobs:
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-args: |
|
||||
BUILDKIT_INLINE_CACHE=1
|
||||
|
||||
build-mcp:
|
||||
runs-on: ubuntu-latest
|
||||
# 条件:mcp-v* 标签 或手动触发选择 all/mcp
|
||||
if: |
|
||||
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/mcp-v')) ||
|
||||
(github.event_name == 'workflow_dispatch' && (github.event.inputs.image == 'all' || github.event.inputs.image == 'mcp'))
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
driver-opts: |
|
||||
network=host
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
run: |
|
||||
if [[ "${{ github.ref }}" == refs/tags/mcp-v* ]]; then
|
||||
VERSION="${GITHUB_REF#refs/tags/mcp-v}"
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
echo "major_minor=$(echo $VERSION | cut -d. -f1,2)" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "version=latest" >> $GITHUB_OUTPUT
|
||||
echo "major_minor=latest" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: wantcat/trendradar-mcp
|
||||
tags: |
|
||||
type=raw,value=${{ steps.version.outputs.version }}
|
||||
type=raw,value=${{ steps.version.outputs.major_minor }}
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
env:
|
||||
BUILDKIT_PROGRESS: plain
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile.mcp
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
933
README-EN.md
933
README-EN.md
File diff suppressed because it is too large
Load Diff
@ -29,6 +29,7 @@ report:
|
||||
rank_threshold: 5 # 排名高亮阈值
|
||||
sort_by_position_first: false # 排序优先级:true=先按配置位置排序,false=先按热点条数排序
|
||||
max_news_per_keyword: 0 # 每个关键词最大显示数量,0=不限制
|
||||
reverse_content_order: false # 内容顺序:false=热点词汇统计在前,true=新增热点新闻在前
|
||||
|
||||
notification:
|
||||
enable_notification: true # 是否启用通知功能,如果 false,则不发送手机通知
|
||||
@ -39,6 +40,7 @@ notification:
|
||||
slack_batch_size: 4000 # Slack消息分批大小(字节)
|
||||
batch_send_interval: 3 # 批次发送间隔(秒)
|
||||
feishu_message_separator: "━━━━━━━━━━━━━━━━━━━" # feishu 消息分割线
|
||||
max_accounts_per_channel: 3 # 每个渠道最大账号数量,建议不超过 3
|
||||
|
||||
# 🕐 推送时间窗口控制(可选功能)
|
||||
# 用途:限制推送的时间范围,避免非工作时间打扰
|
||||
@ -71,23 +73,39 @@ notification:
|
||||
# - Minor: Spam notifications flooding your devices
|
||||
# - Severe: Webhook abuse leading to security incidents (malicious messages, phishing links, etc.)
|
||||
#
|
||||
# ⚠️⚠️⚠️ 多账号推送说明 / MULTI-ACCOUNT PUSH NOTICE ⚠️⚠️⚠️
|
||||
#
|
||||
# 🔸 多账号支持:
|
||||
# • 请使用分号(;)分隔多个账号,如:"url1;url2;url3"
|
||||
# • 示例:telegram_bot_token: "token1;token2" 对应 telegram_chat_id: "id1;id2"
|
||||
# • 对于需要配对的配置(如 Telegram 的 token 和 chat_id),数量必须一致
|
||||
# • 每个渠道最多支持 max_accounts_per_channel 个账号(见上方配置)
|
||||
# • 邮箱已支持多收件人(逗号分隔),保持不变
|
||||
#
|
||||
# 🔸 Multi-Account Support:
|
||||
# • Use semicolon(;) to separate multiple accounts, e.g., "url1;url2;url3"
|
||||
# • Example: telegram_bot_token: "token1;token2" with telegram_chat_id: "id1;id2"
|
||||
# • For paired configs (e.g., Telegram token and chat_id), quantities must match
|
||||
# • Each channel supports up to max_accounts_per_channel accounts (see above config)
|
||||
# • Email already supports multiple recipients (comma-separated), unchanged
|
||||
#
|
||||
webhooks:
|
||||
feishu_url: "" # 飞书机器人的 webhook URL
|
||||
dingtalk_url: "" # 钉钉机器人的 webhook URL
|
||||
wework_url: "" # 企业微信机器人的 webhook URL
|
||||
feishu_url: "" # 飞书机器人的 webhook URL(多账号用 ; 分隔)
|
||||
dingtalk_url: "" # 钉钉机器人的 webhook URL(多账号用 ; 分隔)
|
||||
wework_url: "" # 企业微信机器人的 webhook URL(多账号用 ; 分隔)
|
||||
wework_msg_type: "markdown" # 企业微信消息类型:markdown(群机器人) 或 text(个人微信应用)
|
||||
telegram_bot_token: "" # Telegram Bot Token
|
||||
telegram_chat_id: "" # Telegram Chat ID
|
||||
telegram_bot_token: "" # Telegram Bot Token(多账号用 ; 分隔,需与 chat_id 数量一致)
|
||||
telegram_chat_id: "" # Telegram Chat ID(多账号用 ; 分隔,需与 bot_token 数量一致)
|
||||
email_from: "" # 发件人邮箱地址
|
||||
email_password: "" # 发件人邮箱密码或授权码
|
||||
email_to: "" # 收件人邮箱地址,多个收件人用逗号分隔
|
||||
email_smtp_server: "" # SMTP服务器地址(可选,留空自动识别)
|
||||
email_smtp_port: "" # SMTP端口(可选,留空自动识别)
|
||||
ntfy_server_url: "https://ntfy.sh" # ntfy服务器地址,默认使用公共服务,可改为自托管地址
|
||||
ntfy_topic: "" # ntfy主题名称
|
||||
ntfy_token: "" # ntfy访问令牌(可选,用于私有主题)
|
||||
bark_url: "" # Bark推送URL(格式:https://api.day.app/your_device_key 或自建服务器地址)
|
||||
slack_webhook_url: "" # Slack Incoming Webhook URL(格式:https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX)
|
||||
ntfy_topic: "" # ntfy主题名称(多账号用 ; 分隔)
|
||||
ntfy_token: "" # ntfy访问令牌(可选,用于私有主题,多账号用 ; 分隔)
|
||||
bark_url: "" # Bark推送URL(多账号用 ; 分隔,格式:https://api.day.app/your_device_key 或自建服务器地址)
|
||||
slack_webhook_url: "" # Slack Incoming Webhook URL(多账号用 ; 分隔,格式:https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX)
|
||||
|
||||
# 用于让关注度更高的新闻在更前面显示,即用算法重新组合不同平台的热搜排序形成你侧重的热搜,合起来是 1 就行
|
||||
weight:
|
||||
|
||||
44
docker/.env
44
docker/.env
@ -12,6 +12,21 @@ REPORT_MODE=
|
||||
SORT_BY_POSITION_FIRST=
|
||||
# 每个关键词最大显示数量 (0=不限制,>0=限制数量)
|
||||
MAX_NEWS_PER_KEYWORD=
|
||||
# 内容顺序:false=热点词汇统计在前,true=新增热点新闻在前
|
||||
REVERSE_CONTENT_ORDER=
|
||||
|
||||
# ============================================
|
||||
# Web 服务器配置
|
||||
# ============================================
|
||||
|
||||
# 是否自动启动 Web 服务器托管 output 目录 (true/false)
|
||||
# 启用后可通过 http://localhost:{WEBSERVER_PORT} 访问生成的报告
|
||||
# 手动控制:docker exec -it trend-radar python manage.py start_webserver
|
||||
ENABLE_WEBSERVER=false
|
||||
|
||||
# Web 服务器端口(默认 8080,可自定义避免冲突)
|
||||
# 注意:修改后需要重启容器生效
|
||||
WEBSERVER_PORT=8080
|
||||
|
||||
# ============================================
|
||||
# 推送时间窗口配置
|
||||
@ -29,36 +44,47 @@ PUSH_WINDOW_ONCE_PER_DAY=
|
||||
PUSH_WINDOW_RETENTION_DAYS=
|
||||
|
||||
# ============================================
|
||||
# 通知渠道配置
|
||||
# 多账号配置
|
||||
# ============================================
|
||||
|
||||
# 推送配置
|
||||
# 每个渠道最大账号数量(建议不超过 3,避免fork用户触发账号风险)
|
||||
MAX_ACCOUNTS_PER_CHANNEL=
|
||||
|
||||
# ============================================
|
||||
# 通知渠道配置(多账号用 ; 分隔)
|
||||
# ============================================
|
||||
|
||||
# 飞书机器人 webhook URL(多账号用 ; 分隔)
|
||||
FEISHU_WEBHOOK_URL=
|
||||
# Telegram Bot Token(多账号用 ; 分隔,需与 chat_id 数量一致)
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
# Telegram Chat ID(多账号用 ; 分隔,需与 bot_token 数量一致)
|
||||
TELEGRAM_CHAT_ID=
|
||||
# 钉钉机器人 webhook URL(多账号用 ; 分隔)
|
||||
DINGTALK_WEBHOOK_URL=
|
||||
# 企业微信机器人 webhook URL(多账号用 ; 分隔)
|
||||
WEWORK_WEBHOOK_URL=
|
||||
# 企业微信消息类型(markdown 或 text)
|
||||
WEWORK_MSG_TYPE=
|
||||
|
||||
# 邮件配置(邮箱已支持多收件人,逗号分隔)
|
||||
EMAIL_FROM=
|
||||
EMAIL_PASSWORD=
|
||||
EMAIL_TO=
|
||||
EMAIL_SMTP_SERVER=
|
||||
EMAIL_SMTP_PORT=
|
||||
|
||||
# ntfy 推送配置
|
||||
# ntfy 推送配置(多账号用 ; 分隔,topic 和 token 数量需一致)
|
||||
NTFY_SERVER_URL=https://ntfy.sh
|
||||
# ntfy主题名称
|
||||
# ntfy主题名称(多账号用 ; 分隔)
|
||||
NTFY_TOPIC=
|
||||
# 可选:访问令牌(用于私有主题)
|
||||
# 可选:访问令牌(用于私有主题,多账号用 ; 分隔,无令牌的留空占位如 ";token2")
|
||||
NTFY_TOKEN=
|
||||
|
||||
# Bark 推送配置
|
||||
# Bark推送URL(格式:https://api.day.app/your_device_key 或自建服务器地址)
|
||||
# Bark 推送配置(多账号用 ; 分隔)
|
||||
BARK_URL=
|
||||
|
||||
# Slack 推送配置
|
||||
# Slack Incoming Webhook URL(格式:https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX)
|
||||
# Slack 推送配置(多账号用 ; 分隔)
|
||||
SLACK_WEBHOOK_URL=
|
||||
|
||||
# ============================================
|
||||
|
||||
@ -2,9 +2,9 @@ FROM python:3.10-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# https://github.com/aptible/supercronic
|
||||
# Latest releases available at https://github.com/aptible/supercronic/releases
|
||||
ARG TARGETARCH
|
||||
ENV SUPERCRONIC_VERSION=v0.2.34
|
||||
ENV SUPERCRONIC_VERSION=v0.2.39
|
||||
|
||||
RUN set -ex && \
|
||||
apt-get update && \
|
||||
@ -12,12 +12,12 @@ RUN set -ex && \
|
||||
case ${TARGETARCH} in \
|
||||
amd64) \
|
||||
export SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/${SUPERCRONIC_VERSION}/supercronic-linux-amd64; \
|
||||
export SUPERCRONIC_SHA1SUM=e8631edc1775000d119b70fd40339a7238eece14; \
|
||||
export SUPERCRONIC_SHA1SUM=c98bbf82c5f648aaac8708c182cc83046fe48423; \
|
||||
export SUPERCRONIC=supercronic-linux-amd64; \
|
||||
;; \
|
||||
arm64) \
|
||||
export SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/${SUPERCRONIC_VERSION}/supercronic-linux-arm64; \
|
||||
export SUPERCRONIC_SHA1SUM=4ab6343b52bf9da592e8b4bb7ae6eb5a8e21b71e; \
|
||||
export SUPERCRONIC_SHA1SUM=5ef4ccc3d43f12d0f6c3763758bc37cc4e5af76e; \
|
||||
export SUPERCRONIC=supercronic-linux-arm64; \
|
||||
;; \
|
||||
*) \
|
||||
@ -26,26 +26,25 @@ RUN set -ex && \
|
||||
;; \
|
||||
esac && \
|
||||
echo "Downloading supercronic for ${TARGETARCH} from ${SUPERCRONIC_URL}" && \
|
||||
# 添加重试机制和超时设置
|
||||
for i in 1 2 3 4 5; do \
|
||||
echo "Download attempt $i/5"; \
|
||||
if curl --fail --silent --show-error --location --retry 3 --retry-delay 2 --connect-timeout 30 --max-time 120 -o "$SUPERCRONIC" "$SUPERCRONIC_URL"; then \
|
||||
echo "Download successful"; \
|
||||
break; \
|
||||
else \
|
||||
echo "Download attempt $i failed, exit code: $?"; \
|
||||
if [ $i -eq 5 ]; then \
|
||||
echo "All download attempts failed"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
sleep $((i * 2)); \
|
||||
fi; \
|
||||
# 重试机制:最多3次,每次超时30秒
|
||||
for i in 1 2 3; do \
|
||||
echo "Download attempt $i/3"; \
|
||||
if curl -fsSL --connect-timeout 30 --max-time 60 -o "$SUPERCRONIC" "$SUPERCRONIC_URL"; then \
|
||||
echo "Download successful"; \
|
||||
break; \
|
||||
else \
|
||||
echo "Download attempt $i failed"; \
|
||||
if [ $i -eq 3 ]; then \
|
||||
echo "All download attempts failed"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
sleep 2; \
|
||||
fi; \
|
||||
done && \
|
||||
echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - && \
|
||||
chmod +x "$SUPERCRONIC" && \
|
||||
mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" && \
|
||||
ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic && \
|
||||
# 验证安装
|
||||
supercronic -version && \
|
||||
apt-get remove -y curl && \
|
||||
apt-get clean && \
|
||||
|
||||
23
docker/Dockerfile.mcp
Normal file
23
docker/Dockerfile.mcp
Normal file
@ -0,0 +1,23 @@
|
||||
FROM python:3.10-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装依赖
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 复制 MCP 服务器代码
|
||||
COPY mcp_server/ ./mcp_server/
|
||||
|
||||
# 创建必要目录
|
||||
RUN mkdir -p /app/config /app/output
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
CONFIG_PATH=/app/config/config.yaml \
|
||||
FREQUENCY_WORDS_PATH=/app/config/frequency_words.txt
|
||||
|
||||
# MCP HTTP 服务端口
|
||||
EXPOSE 3333
|
||||
|
||||
# 启动 MCP 服务器(HTTP 模式)
|
||||
CMD ["python", "-m", "mcp_server.server", "--transport", "http", "--host", "0.0.0.0", "--port", "3333"]
|
||||
@ -6,6 +6,9 @@ services:
|
||||
container_name: trend-radar
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
- "127.0.0.1:${WEBSERVER_PORT:-8080}:${WEBSERVER_PORT:-8080}"
|
||||
|
||||
volumes:
|
||||
- ../config:/app/config:ro
|
||||
- ../output:/app/output
|
||||
@ -18,6 +21,12 @@ services:
|
||||
- REPORT_MODE=${REPORT_MODE:-}
|
||||
- SORT_BY_POSITION_FIRST=${SORT_BY_POSITION_FIRST:-}
|
||||
- MAX_NEWS_PER_KEYWORD=${MAX_NEWS_PER_KEYWORD:-}
|
||||
- REVERSE_CONTENT_ORDER=${REVERSE_CONTENT_ORDER:-}
|
||||
# Web 服务器
|
||||
- ENABLE_WEBSERVER=${ENABLE_WEBSERVER:-false}
|
||||
- WEBSERVER_PORT=${WEBSERVER_PORT:-8080}
|
||||
# 多账号配置
|
||||
- MAX_ACCOUNTS_PER_CHANNEL=${MAX_ACCOUNTS_PER_CHANNEL:-}
|
||||
# 推送时间窗口
|
||||
- PUSH_WINDOW_ENABLED=${PUSH_WINDOW_ENABLED:-}
|
||||
- PUSH_WINDOW_START=${PUSH_WINDOW_START:-}
|
||||
@ -49,3 +58,20 @@ services:
|
||||
- CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
|
||||
- RUN_MODE=${RUN_MODE:-cron}
|
||||
- IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}
|
||||
|
||||
trend-radar-mcp:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile.mcp
|
||||
container_name: trend-radar-mcp
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
- "127.0.0.1:3333:3333"
|
||||
|
||||
volumes:
|
||||
- ../config:/app/config:ro
|
||||
- ../output:/app/output:ro
|
||||
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
|
||||
@ -4,6 +4,9 @@ services:
|
||||
container_name: trend-radar
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
- "127.0.0.1:${WEBSERVER_PORT:-8080}:${WEBSERVER_PORT:-8080}"
|
||||
|
||||
volumes:
|
||||
- ../config:/app/config:ro
|
||||
- ../output:/app/output
|
||||
@ -16,6 +19,12 @@ services:
|
||||
- REPORT_MODE=${REPORT_MODE:-}
|
||||
- SORT_BY_POSITION_FIRST=${SORT_BY_POSITION_FIRST:-}
|
||||
- MAX_NEWS_PER_KEYWORD=${MAX_NEWS_PER_KEYWORD:-}
|
||||
- REVERSE_CONTENT_ORDER=${REVERSE_CONTENT_ORDER:-}
|
||||
# Web 服务器
|
||||
- ENABLE_WEBSERVER=${ENABLE_WEBSERVER:-false}
|
||||
- WEBSERVER_PORT=${WEBSERVER_PORT:-8080}
|
||||
# 多账号配置
|
||||
- MAX_ACCOUNTS_PER_CHANNEL=${MAX_ACCOUNTS_PER_CHANNEL:-}
|
||||
# 推送时间窗口
|
||||
- PUSH_WINDOW_ENABLED=${PUSH_WINDOW_ENABLED:-}
|
||||
- PUSH_WINDOW_START=${PUSH_WINDOW_START:-}
|
||||
@ -47,3 +56,18 @@ services:
|
||||
- CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
|
||||
- RUN_MODE=${RUN_MODE:-cron}
|
||||
- IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}
|
||||
|
||||
trend-radar-mcp:
|
||||
image: wantcat/trendradar-mcp:latest
|
||||
container_name: trend-radar-mcp
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
- "127.0.0.1:3333:3333"
|
||||
|
||||
volumes:
|
||||
- ../config:/app/config:ro
|
||||
- ../output:/app/output:ro
|
||||
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
|
||||
@ -33,9 +33,15 @@ case "${RUN_MODE:-cron}" in
|
||||
/usr/local/bin/python main.py
|
||||
fi
|
||||
|
||||
# 启动 Web 服务器(如果配置了)
|
||||
if [ "${ENABLE_WEBSERVER:-false}" = "true" ]; then
|
||||
echo "🌐 启动 Web 服务器..."
|
||||
/usr/local/bin/python manage.py start_webserver
|
||||
fi
|
||||
|
||||
echo "⏰ 启动supercronic: ${CRON_SCHEDULE:-*/30 * * * *}"
|
||||
echo "🎯 supercronic 将作为 PID 1 运行"
|
||||
|
||||
|
||||
exec /usr/local/bin/supercronic -passthrough-logs /tmp/crontab
|
||||
;;
|
||||
*)
|
||||
|
||||
181
docker/manage.py
181
docker/manage.py
@ -8,8 +8,14 @@ import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
import signal
|
||||
from pathlib import Path
|
||||
|
||||
# Web 服务器配置
|
||||
WEBSERVER_PORT = int(os.environ.get("WEBSERVER_PORT", "8080"))
|
||||
WEBSERVER_DIR = "/app/output"
|
||||
WEBSERVER_PID_FILE = "/tmp/webserver.pid"
|
||||
|
||||
|
||||
def run_command(cmd, shell=True, capture_output=True):
|
||||
"""执行系统命令"""
|
||||
@ -374,13 +380,13 @@ def restart_supercronic():
|
||||
"""重启supercronic进程"""
|
||||
print("🔄 重启supercronic...")
|
||||
print("⚠️ 注意: supercronic 是 PID 1,无法直接重启")
|
||||
|
||||
|
||||
# 检查当前 PID 1
|
||||
try:
|
||||
with open('/proc/1/cmdline', 'r') as f:
|
||||
pid1_cmdline = f.read().replace('\x00', ' ').strip()
|
||||
print(f" 🔍 当前 PID 1: {pid1_cmdline}")
|
||||
|
||||
|
||||
if "supercronic" in pid1_cmdline.lower():
|
||||
print(" ✅ PID 1 是 supercronic")
|
||||
print(" 💡 要重启 supercronic,需要重启整个容器:")
|
||||
@ -394,29 +400,167 @@ def restart_supercronic():
|
||||
print(" 💡 建议重启容器: docker restart trend-radar")
|
||||
|
||||
|
||||
def start_webserver():
|
||||
"""启动 Web 服务器托管 output 目录"""
|
||||
print(f"🌐 启动 Web 服务器 (端口: {WEBSERVER_PORT})...")
|
||||
print(f" 🔒 安全提示:仅提供静态文件访问,限制在 {WEBSERVER_DIR} 目录")
|
||||
|
||||
# 检查是否已经运行
|
||||
if Path(WEBSERVER_PID_FILE).exists():
|
||||
try:
|
||||
with open(WEBSERVER_PID_FILE, 'r') as f:
|
||||
old_pid = int(f.read().strip())
|
||||
try:
|
||||
os.kill(old_pid, 0) # 检查进程是否存在
|
||||
print(f" ⚠️ Web 服务器已在运行 (PID: {old_pid})")
|
||||
print(f" 💡 访问: http://localhost:{WEBSERVER_PORT}")
|
||||
print(" 💡 停止服务: python manage.py stop_webserver")
|
||||
return
|
||||
except OSError:
|
||||
# 进程不存在,删除旧的 PID 文件
|
||||
os.remove(WEBSERVER_PID_FILE)
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 清理旧的 PID 文件: {e}")
|
||||
try:
|
||||
os.remove(WEBSERVER_PID_FILE)
|
||||
except:
|
||||
pass
|
||||
|
||||
# 检查目录是否存在
|
||||
if not Path(WEBSERVER_DIR).exists():
|
||||
print(f" ❌ 目录不存在: {WEBSERVER_DIR}")
|
||||
return
|
||||
|
||||
try:
|
||||
# 启动 HTTP 服务器
|
||||
# 使用 --bind 绑定到 0.0.0.0 使容器内部可访问
|
||||
# 工作目录限制在 WEBSERVER_DIR,防止访问其他目录
|
||||
process = subprocess.Popen(
|
||||
[sys.executable, '-m', 'http.server', str(WEBSERVER_PORT), '--bind', '0.0.0.0'],
|
||||
cwd=WEBSERVER_DIR,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
start_new_session=True
|
||||
)
|
||||
|
||||
# 等待一下确保服务器启动
|
||||
time.sleep(1)
|
||||
|
||||
# 检查进程是否还在运行
|
||||
if process.poll() is None:
|
||||
# 保存 PID
|
||||
with open(WEBSERVER_PID_FILE, 'w') as f:
|
||||
f.write(str(process.pid))
|
||||
|
||||
print(f" ✅ Web 服务器已启动 (PID: {process.pid})")
|
||||
print(f" 📁 服务目录: {WEBSERVER_DIR} (只读,仅静态文件)")
|
||||
print(f" 🌐 访问地址: http://localhost:{WEBSERVER_PORT}")
|
||||
print(f" 📄 首页: http://localhost:{WEBSERVER_PORT}/index.html")
|
||||
print(" 💡 停止服务: python manage.py stop_webserver")
|
||||
else:
|
||||
print(f" ❌ Web 服务器启动失败")
|
||||
except Exception as e:
|
||||
print(f" ❌ 启动失败: {e}")
|
||||
|
||||
|
||||
def stop_webserver():
|
||||
"""停止 Web 服务器"""
|
||||
print("🛑 停止 Web 服务器...")
|
||||
|
||||
if not Path(WEBSERVER_PID_FILE).exists():
|
||||
print(" ℹ️ Web 服务器未运行")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(WEBSERVER_PID_FILE, 'r') as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
try:
|
||||
# 尝试终止进程
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
time.sleep(0.5)
|
||||
|
||||
# 检查进程是否已终止
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
# 进程还在,强制杀死
|
||||
os.kill(pid, signal.SIGKILL)
|
||||
print(f" ⚠️ 强制停止 Web 服务器 (PID: {pid})")
|
||||
except OSError:
|
||||
print(f" ✅ Web 服务器已停止 (PID: {pid})")
|
||||
except OSError as e:
|
||||
if e.errno == 3: # No such process
|
||||
print(f" ℹ️ 进程已不存在 (PID: {pid})")
|
||||
else:
|
||||
raise
|
||||
|
||||
# 删除 PID 文件
|
||||
os.remove(WEBSERVER_PID_FILE)
|
||||
except Exception as e:
|
||||
print(f" ❌ 停止失败: {e}")
|
||||
# 尝试清理 PID 文件
|
||||
try:
|
||||
os.remove(WEBSERVER_PID_FILE)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def webserver_status():
|
||||
"""查看 Web 服务器状态"""
|
||||
print("🌐 Web 服务器状态:")
|
||||
|
||||
if not Path(WEBSERVER_PID_FILE).exists():
|
||||
print(" ⭕ 未运行")
|
||||
print(f" 💡 启动服务: python manage.py start_webserver")
|
||||
return
|
||||
|
||||
try:
|
||||
with open(WEBSERVER_PID_FILE, 'r') as f:
|
||||
pid = int(f.read().strip())
|
||||
|
||||
try:
|
||||
os.kill(pid, 0) # 检查进程是否存在
|
||||
print(f" ✅ 运行中 (PID: {pid})")
|
||||
print(f" 📁 服务目录: {WEBSERVER_DIR}")
|
||||
print(f" 🌐 访问地址: http://localhost:{WEBSERVER_PORT}")
|
||||
print(f" 📄 首页: http://localhost:{WEBSERVER_PORT}/index.html")
|
||||
print(" 💡 停止服务: python manage.py stop_webserver")
|
||||
except OSError:
|
||||
print(f" ⭕ 未运行 (PID 文件存在但进程不存在)")
|
||||
os.remove(WEBSERVER_PID_FILE)
|
||||
print(" 💡 启动服务: python manage.py start_webserver")
|
||||
except Exception as e:
|
||||
print(f" ❌ 状态检查失败: {e}")
|
||||
|
||||
|
||||
def show_help():
|
||||
"""显示帮助信息"""
|
||||
help_text = """
|
||||
🐳 TrendRadar 容器管理工具
|
||||
|
||||
📋 命令列表:
|
||||
run - 手动执行一次爬虫
|
||||
status - 显示容器运行状态
|
||||
config - 显示当前配置
|
||||
files - 显示输出文件
|
||||
logs - 实时查看日志
|
||||
restart - 重启说明
|
||||
help - 显示此帮助
|
||||
run - 手动执行一次爬虫
|
||||
status - 显示容器运行状态
|
||||
config - 显示当前配置
|
||||
files - 显示输出文件
|
||||
logs - 实时查看日志
|
||||
restart - 重启说明
|
||||
start_webserver - 启动 Web 服务器托管 output 目录
|
||||
stop_webserver - 停止 Web 服务器
|
||||
webserver_status - 查看 Web 服务器状态
|
||||
help - 显示此帮助
|
||||
|
||||
📖 使用示例:
|
||||
# 在容器中执行
|
||||
python manage.py run
|
||||
python manage.py status
|
||||
python manage.py logs
|
||||
|
||||
python manage.py start_webserver
|
||||
|
||||
# 在宿主机执行
|
||||
docker exec -it trend-radar python manage.py run
|
||||
docker exec -it trend-radar python manage.py status
|
||||
docker exec -it trend-radar python manage.py start_webserver
|
||||
docker logs trend-radar
|
||||
|
||||
💡 常用操作指南:
|
||||
@ -424,18 +568,24 @@ def show_help():
|
||||
- 查看 supercronic 是否为 PID 1
|
||||
- 检查配置文件和关键文件
|
||||
- 查看 cron 调度设置
|
||||
|
||||
2. 手动执行测试: run
|
||||
|
||||
2. 手动执行测试: run
|
||||
- 立即执行一次新闻爬取
|
||||
- 测试程序是否正常工作
|
||||
|
||||
|
||||
3. 查看日志: logs
|
||||
- 实时监控运行情况
|
||||
- 也可使用: docker logs trend-radar
|
||||
|
||||
|
||||
4. 重启服务: restart
|
||||
- 由于 supercronic 是 PID 1,需要重启整个容器
|
||||
- 使用: docker restart trend-radar
|
||||
|
||||
5. Web 服务器管理:
|
||||
- 启动: start_webserver
|
||||
- 停止: stop_webserver
|
||||
- 状态: webserver_status
|
||||
- 访问: http://localhost:8080
|
||||
"""
|
||||
print(help_text)
|
||||
|
||||
@ -453,6 +603,9 @@ def main():
|
||||
"files": show_files,
|
||||
"logs": show_logs,
|
||||
"restart": restart_supercronic,
|
||||
"start_webserver": start_webserver,
|
||||
"stop_webserver": stop_webserver,
|
||||
"webserver_status": webserver_status,
|
||||
"help": show_help,
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user