mirror of
https://gitee.com/houhuan/TrendRadar.git
synced 2025-12-21 15:47: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:
|
on:
|
||||||
push:
|
push:
|
||||||
tags: ["v*"]
|
tags:
|
||||||
|
- "v*" # 主项目版本
|
||||||
|
- "mcp-v*" # MCP 版本
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
image:
|
||||||
|
description: "选择要构建的镜像"
|
||||||
|
required: true
|
||||||
|
default: "all"
|
||||||
|
type: choice
|
||||||
|
options:
|
||||||
|
- all
|
||||||
|
- crawler
|
||||||
|
- mcp
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: docker.io
|
REGISTRY: docker.io
|
||||||
IMAGE_NAME: wantcat/trendradar
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build-crawler:
|
||||||
runs-on: ubuntu-latest
|
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:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@ -35,12 +51,11 @@ jobs:
|
|||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
with:
|
with:
|
||||||
images: ${{ env.IMAGE_NAME }}
|
images: wantcat/trendradar
|
||||||
tags: |
|
tags: |
|
||||||
type=ref,event=branch
|
|
||||||
type=semver,pattern={{version}}
|
type=semver,pattern={{version}}
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=raw,value=latest,enable={{is_default_branch}}
|
type=raw,value=latest
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
@ -55,5 +70,65 @@ jobs:
|
|||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
cache-to: type=gha,mode=max
|
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 # 排名高亮阈值
|
rank_threshold: 5 # 排名高亮阈值
|
||||||
sort_by_position_first: false # 排序优先级:true=先按配置位置排序,false=先按热点条数排序
|
sort_by_position_first: false # 排序优先级:true=先按配置位置排序,false=先按热点条数排序
|
||||||
max_news_per_keyword: 0 # 每个关键词最大显示数量,0=不限制
|
max_news_per_keyword: 0 # 每个关键词最大显示数量,0=不限制
|
||||||
|
reverse_content_order: false # 内容顺序:false=热点词汇统计在前,true=新增热点新闻在前
|
||||||
|
|
||||||
notification:
|
notification:
|
||||||
enable_notification: true # 是否启用通知功能,如果 false,则不发送手机通知
|
enable_notification: true # 是否启用通知功能,如果 false,则不发送手机通知
|
||||||
@ -39,6 +40,7 @@ notification:
|
|||||||
slack_batch_size: 4000 # Slack消息分批大小(字节)
|
slack_batch_size: 4000 # Slack消息分批大小(字节)
|
||||||
batch_send_interval: 3 # 批次发送间隔(秒)
|
batch_send_interval: 3 # 批次发送间隔(秒)
|
||||||
feishu_message_separator: "━━━━━━━━━━━━━━━━━━━" # feishu 消息分割线
|
feishu_message_separator: "━━━━━━━━━━━━━━━━━━━" # feishu 消息分割线
|
||||||
|
max_accounts_per_channel: 3 # 每个渠道最大账号数量,建议不超过 3
|
||||||
|
|
||||||
# 🕐 推送时间窗口控制(可选功能)
|
# 🕐 推送时间窗口控制(可选功能)
|
||||||
# 用途:限制推送的时间范围,避免非工作时间打扰
|
# 用途:限制推送的时间范围,避免非工作时间打扰
|
||||||
@ -71,23 +73,39 @@ notification:
|
|||||||
# - Minor: Spam notifications flooding your devices
|
# - Minor: Spam notifications flooding your devices
|
||||||
# - Severe: Webhook abuse leading to security incidents (malicious messages, phishing links, etc.)
|
# - 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:
|
webhooks:
|
||||||
feishu_url: "" # 飞书机器人的 webhook URL
|
feishu_url: "" # 飞书机器人的 webhook URL(多账号用 ; 分隔)
|
||||||
dingtalk_url: "" # 钉钉机器人的 webhook URL
|
dingtalk_url: "" # 钉钉机器人的 webhook URL(多账号用 ; 分隔)
|
||||||
wework_url: "" # 企业微信机器人的 webhook URL
|
wework_url: "" # 企业微信机器人的 webhook URL(多账号用 ; 分隔)
|
||||||
wework_msg_type: "markdown" # 企业微信消息类型:markdown(群机器人) 或 text(个人微信应用)
|
wework_msg_type: "markdown" # 企业微信消息类型:markdown(群机器人) 或 text(个人微信应用)
|
||||||
telegram_bot_token: "" # Telegram Bot Token
|
telegram_bot_token: "" # Telegram Bot Token(多账号用 ; 分隔,需与 chat_id 数量一致)
|
||||||
telegram_chat_id: "" # Telegram Chat ID
|
telegram_chat_id: "" # Telegram Chat ID(多账号用 ; 分隔,需与 bot_token 数量一致)
|
||||||
email_from: "" # 发件人邮箱地址
|
email_from: "" # 发件人邮箱地址
|
||||||
email_password: "" # 发件人邮箱密码或授权码
|
email_password: "" # 发件人邮箱密码或授权码
|
||||||
email_to: "" # 收件人邮箱地址,多个收件人用逗号分隔
|
email_to: "" # 收件人邮箱地址,多个收件人用逗号分隔
|
||||||
email_smtp_server: "" # SMTP服务器地址(可选,留空自动识别)
|
email_smtp_server: "" # SMTP服务器地址(可选,留空自动识别)
|
||||||
email_smtp_port: "" # SMTP端口(可选,留空自动识别)
|
email_smtp_port: "" # SMTP端口(可选,留空自动识别)
|
||||||
ntfy_server_url: "https://ntfy.sh" # ntfy服务器地址,默认使用公共服务,可改为自托管地址
|
ntfy_server_url: "https://ntfy.sh" # ntfy服务器地址,默认使用公共服务,可改为自托管地址
|
||||||
ntfy_topic: "" # ntfy主题名称
|
ntfy_topic: "" # ntfy主题名称(多账号用 ; 分隔)
|
||||||
ntfy_token: "" # ntfy访问令牌(可选,用于私有主题)
|
ntfy_token: "" # ntfy访问令牌(可选,用于私有主题,多账号用 ; 分隔)
|
||||||
bark_url: "" # Bark推送URL(格式:https://api.day.app/your_device_key 或自建服务器地址)
|
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)
|
slack_webhook_url: "" # Slack Incoming Webhook URL(多账号用 ; 分隔,格式:https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX)
|
||||||
|
|
||||||
# 用于让关注度更高的新闻在更前面显示,即用算法重新组合不同平台的热搜排序形成你侧重的热搜,合起来是 1 就行
|
# 用于让关注度更高的新闻在更前面显示,即用算法重新组合不同平台的热搜排序形成你侧重的热搜,合起来是 1 就行
|
||||||
weight:
|
weight:
|
||||||
|
|||||||
44
docker/.env
44
docker/.env
@ -12,6 +12,21 @@ REPORT_MODE=
|
|||||||
SORT_BY_POSITION_FIRST=
|
SORT_BY_POSITION_FIRST=
|
||||||
# 每个关键词最大显示数量 (0=不限制,>0=限制数量)
|
# 每个关键词最大显示数量 (0=不限制,>0=限制数量)
|
||||||
MAX_NEWS_PER_KEYWORD=
|
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=
|
PUSH_WINDOW_RETENTION_DAYS=
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
# 通知渠道配置
|
# 多账号配置
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
# 推送配置
|
# 每个渠道最大账号数量(建议不超过 3,避免fork用户触发账号风险)
|
||||||
|
MAX_ACCOUNTS_PER_CHANNEL=
|
||||||
|
|
||||||
|
# ============================================
|
||||||
|
# 通知渠道配置(多账号用 ; 分隔)
|
||||||
|
# ============================================
|
||||||
|
|
||||||
|
# 飞书机器人 webhook URL(多账号用 ; 分隔)
|
||||||
FEISHU_WEBHOOK_URL=
|
FEISHU_WEBHOOK_URL=
|
||||||
|
# Telegram Bot Token(多账号用 ; 分隔,需与 chat_id 数量一致)
|
||||||
TELEGRAM_BOT_TOKEN=
|
TELEGRAM_BOT_TOKEN=
|
||||||
|
# Telegram Chat ID(多账号用 ; 分隔,需与 bot_token 数量一致)
|
||||||
TELEGRAM_CHAT_ID=
|
TELEGRAM_CHAT_ID=
|
||||||
|
# 钉钉机器人 webhook URL(多账号用 ; 分隔)
|
||||||
DINGTALK_WEBHOOK_URL=
|
DINGTALK_WEBHOOK_URL=
|
||||||
|
# 企业微信机器人 webhook URL(多账号用 ; 分隔)
|
||||||
WEWORK_WEBHOOK_URL=
|
WEWORK_WEBHOOK_URL=
|
||||||
|
# 企业微信消息类型(markdown 或 text)
|
||||||
WEWORK_MSG_TYPE=
|
WEWORK_MSG_TYPE=
|
||||||
|
|
||||||
|
# 邮件配置(邮箱已支持多收件人,逗号分隔)
|
||||||
EMAIL_FROM=
|
EMAIL_FROM=
|
||||||
EMAIL_PASSWORD=
|
EMAIL_PASSWORD=
|
||||||
EMAIL_TO=
|
EMAIL_TO=
|
||||||
EMAIL_SMTP_SERVER=
|
EMAIL_SMTP_SERVER=
|
||||||
EMAIL_SMTP_PORT=
|
EMAIL_SMTP_PORT=
|
||||||
|
|
||||||
# ntfy 推送配置
|
# ntfy 推送配置(多账号用 ; 分隔,topic 和 token 数量需一致)
|
||||||
NTFY_SERVER_URL=https://ntfy.sh
|
NTFY_SERVER_URL=https://ntfy.sh
|
||||||
# ntfy主题名称
|
# ntfy主题名称(多账号用 ; 分隔)
|
||||||
NTFY_TOPIC=
|
NTFY_TOPIC=
|
||||||
# 可选:访问令牌(用于私有主题)
|
# 可选:访问令牌(用于私有主题,多账号用 ; 分隔,无令牌的留空占位如 ";token2")
|
||||||
NTFY_TOKEN=
|
NTFY_TOKEN=
|
||||||
|
|
||||||
# Bark 推送配置
|
# Bark 推送配置(多账号用 ; 分隔)
|
||||||
# Bark推送URL(格式:https://api.day.app/your_device_key 或自建服务器地址)
|
|
||||||
BARK_URL=
|
BARK_URL=
|
||||||
|
|
||||||
# Slack 推送配置
|
# Slack 推送配置(多账号用 ; 分隔)
|
||||||
# Slack Incoming Webhook URL(格式:https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX)
|
|
||||||
SLACK_WEBHOOK_URL=
|
SLACK_WEBHOOK_URL=
|
||||||
|
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|||||||
@ -2,9 +2,9 @@ FROM python:3.10-slim
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# https://github.com/aptible/supercronic
|
# Latest releases available at https://github.com/aptible/supercronic/releases
|
||||||
ARG TARGETARCH
|
ARG TARGETARCH
|
||||||
ENV SUPERCRONIC_VERSION=v0.2.34
|
ENV SUPERCRONIC_VERSION=v0.2.39
|
||||||
|
|
||||||
RUN set -ex && \
|
RUN set -ex && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
@ -12,12 +12,12 @@ RUN set -ex && \
|
|||||||
case ${TARGETARCH} in \
|
case ${TARGETARCH} in \
|
||||||
amd64) \
|
amd64) \
|
||||||
export SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/${SUPERCRONIC_VERSION}/supercronic-linux-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; \
|
export SUPERCRONIC=supercronic-linux-amd64; \
|
||||||
;; \
|
;; \
|
||||||
arm64) \
|
arm64) \
|
||||||
export SUPERCRONIC_URL=https://github.com/aptible/supercronic/releases/download/${SUPERCRONIC_VERSION}/supercronic-linux-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; \
|
export SUPERCRONIC=supercronic-linux-arm64; \
|
||||||
;; \
|
;; \
|
||||||
*) \
|
*) \
|
||||||
@ -26,26 +26,25 @@ RUN set -ex && \
|
|||||||
;; \
|
;; \
|
||||||
esac && \
|
esac && \
|
||||||
echo "Downloading supercronic for ${TARGETARCH} from ${SUPERCRONIC_URL}" && \
|
echo "Downloading supercronic for ${TARGETARCH} from ${SUPERCRONIC_URL}" && \
|
||||||
# 添加重试机制和超时设置
|
# 重试机制:最多3次,每次超时30秒
|
||||||
for i in 1 2 3 4 5; do \
|
for i in 1 2 3; do \
|
||||||
echo "Download attempt $i/5"; \
|
echo "Download attempt $i/3"; \
|
||||||
if curl --fail --silent --show-error --location --retry 3 --retry-delay 2 --connect-timeout 30 --max-time 120 -o "$SUPERCRONIC" "$SUPERCRONIC_URL"; then \
|
if curl -fsSL --connect-timeout 30 --max-time 60 -o "$SUPERCRONIC" "$SUPERCRONIC_URL"; then \
|
||||||
echo "Download successful"; \
|
echo "Download successful"; \
|
||||||
break; \
|
break; \
|
||||||
else \
|
else \
|
||||||
echo "Download attempt $i failed, exit code: $?"; \
|
echo "Download attempt $i failed"; \
|
||||||
if [ $i -eq 5 ]; then \
|
if [ $i -eq 3 ]; then \
|
||||||
echo "All download attempts failed"; \
|
echo "All download attempts failed"; \
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi; \
|
fi; \
|
||||||
sleep $((i * 2)); \
|
sleep 2; \
|
||||||
fi; \
|
fi; \
|
||||||
done && \
|
done && \
|
||||||
echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - && \
|
echo "${SUPERCRONIC_SHA1SUM} ${SUPERCRONIC}" | sha1sum -c - && \
|
||||||
chmod +x "$SUPERCRONIC" && \
|
chmod +x "$SUPERCRONIC" && \
|
||||||
mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" && \
|
mv "$SUPERCRONIC" "/usr/local/bin/${SUPERCRONIC}" && \
|
||||||
ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic && \
|
ln -s "/usr/local/bin/${SUPERCRONIC}" /usr/local/bin/supercronic && \
|
||||||
# 验证安装
|
|
||||||
supercronic -version && \
|
supercronic -version && \
|
||||||
apt-get remove -y curl && \
|
apt-get remove -y curl && \
|
||||||
apt-get clean && \
|
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
|
container_name: trend-radar
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:${WEBSERVER_PORT:-8080}:${WEBSERVER_PORT:-8080}"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ../config:/app/config:ro
|
- ../config:/app/config:ro
|
||||||
- ../output:/app/output
|
- ../output:/app/output
|
||||||
@ -18,6 +21,12 @@ services:
|
|||||||
- REPORT_MODE=${REPORT_MODE:-}
|
- REPORT_MODE=${REPORT_MODE:-}
|
||||||
- SORT_BY_POSITION_FIRST=${SORT_BY_POSITION_FIRST:-}
|
- SORT_BY_POSITION_FIRST=${SORT_BY_POSITION_FIRST:-}
|
||||||
- MAX_NEWS_PER_KEYWORD=${MAX_NEWS_PER_KEYWORD:-}
|
- 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_ENABLED=${PUSH_WINDOW_ENABLED:-}
|
||||||
- PUSH_WINDOW_START=${PUSH_WINDOW_START:-}
|
- PUSH_WINDOW_START=${PUSH_WINDOW_START:-}
|
||||||
@ -49,3 +58,20 @@ services:
|
|||||||
- CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
|
- CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
|
||||||
- RUN_MODE=${RUN_MODE:-cron}
|
- RUN_MODE=${RUN_MODE:-cron}
|
||||||
- IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}
|
- 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
|
container_name: trend-radar
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:${WEBSERVER_PORT:-8080}:${WEBSERVER_PORT:-8080}"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- ../config:/app/config:ro
|
- ../config:/app/config:ro
|
||||||
- ../output:/app/output
|
- ../output:/app/output
|
||||||
@ -16,6 +19,12 @@ services:
|
|||||||
- REPORT_MODE=${REPORT_MODE:-}
|
- REPORT_MODE=${REPORT_MODE:-}
|
||||||
- SORT_BY_POSITION_FIRST=${SORT_BY_POSITION_FIRST:-}
|
- SORT_BY_POSITION_FIRST=${SORT_BY_POSITION_FIRST:-}
|
||||||
- MAX_NEWS_PER_KEYWORD=${MAX_NEWS_PER_KEYWORD:-}
|
- 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_ENABLED=${PUSH_WINDOW_ENABLED:-}
|
||||||
- PUSH_WINDOW_START=${PUSH_WINDOW_START:-}
|
- PUSH_WINDOW_START=${PUSH_WINDOW_START:-}
|
||||||
@ -47,3 +56,18 @@ services:
|
|||||||
- CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
|
- CRON_SCHEDULE=${CRON_SCHEDULE:-*/5 * * * *}
|
||||||
- RUN_MODE=${RUN_MODE:-cron}
|
- RUN_MODE=${RUN_MODE:-cron}
|
||||||
- IMMEDIATE_RUN=${IMMEDIATE_RUN:-true}
|
- 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,6 +33,12 @@ case "${RUN_MODE:-cron}" in
|
|||||||
/usr/local/bin/python main.py
|
/usr/local/bin/python main.py
|
||||||
fi
|
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: ${CRON_SCHEDULE:-*/30 * * * *}"
|
||||||
echo "🎯 supercronic 将作为 PID 1 运行"
|
echo "🎯 supercronic 将作为 PID 1 运行"
|
||||||
|
|
||||||
|
|||||||
153
docker/manage.py
153
docker/manage.py
@ -8,8 +8,14 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
import signal
|
||||||
from pathlib import Path
|
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):
|
def run_command(cmd, shell=True, capture_output=True):
|
||||||
"""执行系统命令"""
|
"""执行系统命令"""
|
||||||
@ -394,6 +400,139 @@ def restart_supercronic():
|
|||||||
print(" 💡 建议重启容器: docker restart trend-radar")
|
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():
|
def show_help():
|
||||||
"""显示帮助信息"""
|
"""显示帮助信息"""
|
||||||
help_text = """
|
help_text = """
|
||||||
@ -406,6 +545,9 @@ def show_help():
|
|||||||
files - 显示输出文件
|
files - 显示输出文件
|
||||||
logs - 实时查看日志
|
logs - 实时查看日志
|
||||||
restart - 重启说明
|
restart - 重启说明
|
||||||
|
start_webserver - 启动 Web 服务器托管 output 目录
|
||||||
|
stop_webserver - 停止 Web 服务器
|
||||||
|
webserver_status - 查看 Web 服务器状态
|
||||||
help - 显示此帮助
|
help - 显示此帮助
|
||||||
|
|
||||||
📖 使用示例:
|
📖 使用示例:
|
||||||
@ -413,10 +555,12 @@ def show_help():
|
|||||||
python manage.py run
|
python manage.py run
|
||||||
python manage.py status
|
python manage.py status
|
||||||
python manage.py logs
|
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 run
|
||||||
docker exec -it trend-radar python manage.py status
|
docker exec -it trend-radar python manage.py status
|
||||||
|
docker exec -it trend-radar python manage.py start_webserver
|
||||||
docker logs trend-radar
|
docker logs trend-radar
|
||||||
|
|
||||||
💡 常用操作指南:
|
💡 常用操作指南:
|
||||||
@ -436,6 +580,12 @@ def show_help():
|
|||||||
4. 重启服务: restart
|
4. 重启服务: restart
|
||||||
- 由于 supercronic 是 PID 1,需要重启整个容器
|
- 由于 supercronic 是 PID 1,需要重启整个容器
|
||||||
- 使用: docker restart trend-radar
|
- 使用: docker restart trend-radar
|
||||||
|
|
||||||
|
5. Web 服务器管理:
|
||||||
|
- 启动: start_webserver
|
||||||
|
- 停止: stop_webserver
|
||||||
|
- 状态: webserver_status
|
||||||
|
- 访问: http://localhost:8080
|
||||||
"""
|
"""
|
||||||
print(help_text)
|
print(help_text)
|
||||||
|
|
||||||
@ -453,6 +603,9 @@ def main():
|
|||||||
"files": show_files,
|
"files": show_files,
|
||||||
"logs": show_logs,
|
"logs": show_logs,
|
||||||
"restart": restart_supercronic,
|
"restart": restart_supercronic,
|
||||||
|
"start_webserver": start_webserver,
|
||||||
|
"stop_webserver": stop_webserver,
|
||||||
|
"webserver_status": webserver_status,
|
||||||
"help": show_help,
|
"help": show_help,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user