diff --git a/soul-admin/src/pages/find-partner/tabs/CKBStatsTab.tsx b/soul-admin/src/pages/find-partner/tabs/CKBStatsTab.tsx deleted file mode 100644 index c5ca6de2..00000000 --- a/soul-admin/src/pages/find-partner/tabs/CKBStatsTab.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import { useState, useEffect, useCallback } from 'react' -import { useNavigate } from 'react-router-dom' -import { Card, CardContent } from '@/components/ui/card' -import { - Users, Zap, Link2, ExternalLink, -} from 'lucide-react' -import { get } from '@/api/client' - -interface MatchStats { - totalMatches: number - todayMatches: number - byType: { matchType: string; count: number }[] - uniqueUsers: number - matchRevenue?: number - paidMatchCount?: number -} - -interface CKBPlanStats { - ckbTotal: number - withContact: number - byType: { matchType: string; total: number }[] - ckbApiKey: string - ckbApiUrl: string -} - -const typeLabels: Record = { partner: '找伙伴', investor: '资源对接', mentor: '导师顾问', team: '团队招募', join: '加入', match: '匹配' } -const typeIcons: Record = { partner: '⭐', investor: '👥', mentor: '❤️', team: '🎮', join: '📋', match: '🔗' } - -interface Props { - onSwitchTab?: (tabId: string) => void - onOpenCKB?: (tab?: string) => void -} - -export function CKBStatsTab({ onSwitchTab, onOpenCKB }: Props = {}) { - const navigate = useNavigate() - const [stats, setStats] = useState(null) - const [ckbStats, setCkbStats] = useState(null) - const [isLoading, setIsLoading] = useState(true) - - const loadStats = useCallback(async () => { - setIsLoading(true) - try { - const [statsRes, ckbRes] = await Promise.allSettled([ - get<{ success?: boolean; data?: MatchStats }>('/api/db/match-records?stats=true'), - get<{ success?: boolean; data?: CKBPlanStats }>('/api/db/ckb-plan-stats'), - ]) - - if (statsRes.status === 'fulfilled' && statsRes.value?.success && statsRes.value.data) { - let result = statsRes.value.data - if (result.totalMatches > 0 && (!result.uniqueUsers || result.uniqueUsers === 0)) { - try { - const allRec = await get<{ success?: boolean; records?: { userId: string }[]; total?: number }>('/api/db/match-records?page=1&pageSize=200') - if (allRec?.success && allRec.records) { - const userSet = new Set(allRec.records.map(r => r.userId).filter(Boolean)) - result = { ...result, uniqueUsers: userSet.size } - } - } catch { /* fallback */ } - } - setStats(result) - } - - if (ckbRes.status === 'fulfilled' && ckbRes.value?.success && ckbRes.value.data) { - setCkbStats(ckbRes.value.data) - } - } catch (e) { console.error('加载统计失败:', e) } - finally { setIsLoading(false) } - }, []) - - useEffect(() => { loadStats() }, [loadStats]) - - const v = (n: number | undefined) => isLoading ? '—' : String(n ?? 0) - const avgMatch = stats?.uniqueUsers ? (stats.totalMatches / stats.uniqueUsers).toFixed(1) : '0' - - return ( -
- {/* 核心指标:一行紧凑卡片 */} -
- onSwitchTab?.('partner')}> - -

总匹配

-

{v(stats?.totalMatches)}

-

查看记录

-
-
- - -

今日

-

{v(stats?.todayMatches)}

-

实时

-
-
- navigate('/users')}> - -

用户数

-

{v(stats?.uniqueUsers)}

-

查看用户

-
-
- - -

人均匹配

-

{isLoading ? '—' : avgMatch}

-
-
- - -

付费匹配

-

{v(stats?.paidMatchCount)}

-
-
-
- - {/* 类型分布 + AI 获客:并排两列 */} -
- {/* 左列:匹配类型分布 */} - - -

匹配类型分布

- {stats?.byType && stats.byType.length > 0 ? ( -
- {stats.byType.map(item => { - const pct = stats.totalMatches > 0 ? ((item.count / stats.totalMatches) * 100) : 0 - return ( -
- {typeIcons[item.matchType] || '📊'} -
-
- {typeLabels[item.matchType] || item.matchType} - {item.count} ({pct.toFixed(0)}%) -
-
-
-
-
-
- ) - })} -
- ) : ( -

暂无数据

- )} - - - - {/* 右列:AI 获客概览 */} - - -

- AI 获客 -

-
-
onOpenCKB?.('submitted')}> -

已提交线索

-

{isLoading ? '—' : (ckbStats?.ckbTotal ?? 0)}

-
-
onOpenCKB?.('contact')}> -

有联系方式

-

{isLoading ? '—' : (ckbStats?.withContact ?? 0)}

-
-
- {ckbStats?.byType && ckbStats.byType.length > 0 && ( -
- {ckbStats.byType.map(item => ( -
- {typeIcons[item.matchType] || '📋'} - {typeLabels[item.matchType] || item.matchType} - {item.total} -
- ))} -
- )} - -
-
-
-
- ) -} diff --git a/soul-admin/src/pages/find-partner/tabs/MatchRecordsTab.tsx b/soul-admin/src/pages/find-partner/tabs/MatchRecordsTab.tsx deleted file mode 100644 index 659356d9..00000000 --- a/soul-admin/src/pages/find-partner/tabs/MatchRecordsTab.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { useState, useEffect } from 'react' -import { normalizeImageUrl } from '@/lib/utils' -import { Card, CardContent } from '@/components/ui/card' -import { - Table, TableBody, TableCell, TableHead, TableHeader, TableRow, -} from '@/components/ui/table' -import { Badge } from '@/components/ui/badge' -import { RefreshCw } from 'lucide-react' -import { Pagination } from '@/components/ui/Pagination' -import { UserDetailModal } from '@/components/modules/user/UserDetailModal' -import { get } from '@/api/client' - -interface MatchRecord { - id: string; userId: string; matchedUserId: string; matchType: string - phone?: string; wechatId?: string; userNickname?: string; matchedNickname?: string - userAvatar?: string; matchedUserAvatar?: string; matchScore?: number; createdAt: string -} - -const matchTypeLabels: Record = { - partner: '找伙伴', investor: '资源对接', mentor: '导师顾问', team: '团队招募', -} - -export function MatchRecordsTab() { - const [records, setRecords] = useState([]) - const [total, setTotal] = useState(0) - const [page, setPage] = useState(1) - const [pageSize, setPageSize] = useState(10) - const [matchTypeFilter, setMatchTypeFilter] = useState('') - const [isLoading, setIsLoading] = useState(true) - const [error, setError] = useState(null) - const [detailUserId, setDetailUserId] = useState(null) - - async function loadRecords() { - setIsLoading(true); setError(null) - try { - const params = new URLSearchParams({ page: String(page), pageSize: String(pageSize) }) - if (matchTypeFilter) params.set('matchType', matchTypeFilter) - const data = await get<{ success?: boolean; records?: MatchRecord[]; total?: number }>(`/api/db/match-records?${params}`) - if (data?.success) { setRecords(data.records || []); setTotal(data.total ?? 0) } - else setError('加载匹配记录失败') - } catch { setError('加载失败,请检查网络后重试') } - finally { setIsLoading(false) } - } - - useEffect(() => { loadRecords() }, [page, matchTypeFilter]) - - const totalPages = Math.ceil(total / pageSize) || 1 - - const UserCell = ({ userId, nickname, avatar }: { userId: string; nickname?: string; avatar?: string }) => ( -
setDetailUserId(userId)}> -
- {avatar ? { (e.currentTarget as HTMLImageElement).style.display = 'none' }} /> : null} - {(nickname || userId || '?').charAt(0)} -
-
-
{nickname || userId}
-
{userId?.slice(0, 16)}{userId?.length > 16 ? '...' : ''}
-
-
- ) - - return ( -
- {error && ( -
- {error} - -
- )} -
-

共 {total} 条匹配记录 · 点击用户名查看详情

-
- - -
-
- - - - {isLoading ? ( -
加载中...
- ) : ( - <> - - - - 发起人 - 匹配到 - 类型 - 联系方式 - 匹配时间 - - - - {records.map(r => ( - - - - - - {r.matchedUserId ? ( - - ) : ( - - )} - - {matchTypeLabels[r.matchType] || r.matchType} - - {r.phone &&
📱 {r.phone}
} - {r.wechatId &&
💬 {r.wechatId}
} - {!r.phone && !r.wechatId && -} -
- {r.createdAt ? new Date(r.createdAt).toLocaleString() : '-'} -
- ))} - {records.length === 0 && 暂无匹配记录} -
-
- { setPageSize(n); setPage(1) }} /> - - )} -
-
- - setDetailUserId(null)} - userId={detailUserId} - onUserUpdated={loadRecords} - /> -
- ) -} diff --git a/soul-api/deploy/Dockerfile.local b/soul-api/deploy/Dockerfile.local deleted file mode 100644 index 4ff573b8..00000000 --- a/soul-api/deploy/Dockerfile.local +++ /dev/null @@ -1,26 +0,0 @@ -# Dockerfile.local - 使用本地 Go 编译后的二进制,无需拉取 golang 镜像 -# 使用方式:deploy.py --mode docker --local-go -# 依赖:先由本地 go build 生成 soul-api 可执行文件 - -# 使用标准引用,配合 --pull=false 使用本地缓存的 alpine:3.19 -FROM alpine:3.19 - -RUN apk add --no-cache ca-certificates tzdata wget -ENV TZ=Asia/Shanghai - -RUN adduser -D -g '' appuser -WORKDIR /app - -COPY soul-api . -# 微信支付等证书(须存在于构建上下文,勿在 .dockerignore 中排除) -COPY certs/ /app/certs/ - -# 由 devloy 传入 --build-arg ENV_FILE=(如 .env / .env.development) -ARG ENV_FILE=.env.production -COPY ${ENV_FILE} /app/.env - -RUN mkdir -p /app/uploads && chown -R appuser:appuser /app -USER appuser - -EXPOSE 8080 -ENTRYPOINT ["./soul-api"] diff --git a/soul-api/deploy/Dockerfile.runner b/soul-api/deploy/Dockerfile.runner deleted file mode 100644 index 1324d2be..00000000 --- a/soul-api/deploy/Dockerfile.runner +++ /dev/null @@ -1,28 +0,0 @@ -# soul-api Runner 容器 -# 红蓝切换在容器内完成,宝塔固定 proxy_pass 到 127.0.0.1:9001 -# 使用 network_mode: host,无需端口映射,避免 iptables 问题 - -FROM alpine:3.19 - -RUN apk add --no-cache \ - ca-certificates tzdata wget nginx redis \ - psmisc \ - && rm -rf /var/cache/apk/* - -ENV TZ=Asia/Shanghai - -WORKDIR /app - -# 复制 Runner 脚本与 nginx 模板(context 为 soul-api 根目录) -COPY deploy/runner/entrypoint.sh /app/ -COPY deploy/runner/deploy.sh /app/ -COPY deploy/runner/nginx.conf.template /app/ - -RUN chmod +x /app/entrypoint.sh /app/deploy.sh - -# 创建目录(blue/green 由 deploy.sh 创建) -RUN mkdir -p /app/uploads /app/blue /app/green - -EXPOSE 9001 - -ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/soul-api/deploy/deploy-runner-remote.sh b/soul-api/deploy/deploy-runner-remote.sh deleted file mode 100644 index e02e1aac..00000000 --- a/soul-api/deploy/deploy-runner-remote.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -# soul-api Runner 部署脚本(在宿主机执行) -# 用法:./deploy-runner-remote.sh [path-to-deploy.tar.gz] -# 默认 tar 路径:${DEPLOY_DOCKER_PATH:-/www/wwwroot/self/soul-dev}/soul_api_deploy.tar.gz -# 仅负责:将 tar 拷入容器并触发容器内 deploy.sh,不涉及宝塔/Nginx 配置 - -set -e -CONTAINER="${DEPLOY_RUNNER_CONTAINER:-soul-api-runner}" -DEPLOY_PATH="${DEPLOY_DOCKER_PATH:-/www/wwwroot/self/soul-dev}" -TAR="${1:-$DEPLOY_PATH/soul_api_deploy.tar.gz}" - -if [ -z "$TAR" ] || [ ! -f "$TAR" ]; then - echo "[ERROR] 用法: $0 [path-to-deploy.tar.gz]" - echo " 默认: $DEPLOY_PATH/soul_api_deploy.tar.gz" - exit 1 -fi - -echo "[1/2] 拷贝部署包到容器 ..." -docker cp "$TAR" "$CONTAINER:/tmp/incoming.tar.gz" - -echo "[2/2] 执行容器内红蓝切换 ..." -docker exec "$CONTAINER" /app/deploy.sh /tmp/incoming.tar.gz - -rm -f "$TAR" -echo "" -echo "[SUCCESS] 部署完成,宝塔代理 9001 无需修改" diff --git a/soul-api/deploy/docker-compose.bluegreen.yml b/soul-api/deploy/docker-compose.bluegreen.yml deleted file mode 100644 index 1f210d53..00000000 --- a/soul-api/deploy/docker-compose.bluegreen.yml +++ /dev/null @@ -1,62 +0,0 @@ -# soul-api 蓝绿部署 - 支持无缝切换 -# blue=9001, green=9002,部署时先启新实例,健康检查通过后切换 Nginx,再停旧实例 -# 用法:见 deploy.py --mode docker - -services: - soul-api-blue: - image: soul-api:latest - container_name: soul-api-blue - restart: "no" - environment: - - REDIS_URL=redis://:soul-docker-redis@redis:6379/0 - - GIN_MODE=release - - APP_ENV=production - # 测试/预发布环境可设 SKIP_PROD_SECRET_CHECK=staging,正式生产请使用真实密钥并移除此项 - - SKIP_PROD_SECRET_CHECK=staging - ports: - - "9001:8080" - volumes: - - soul_uploads:/app/uploads - depends_on: - - redis - healthcheck: - test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/health"] - interval: 5s - timeout: 5s - retries: 10 - start_period: 15s - - soul-api-green: - image: soul-api:latest - container_name: soul-api-green - restart: "no" - environment: - - REDIS_URL=redis://:soul-docker-redis@redis:6379/0 - - GIN_MODE=release - - APP_ENV=production - - SKIP_PROD_SECRET_CHECK=staging - ports: - - "9002:8080" - volumes: - - soul_uploads:/app/uploads - depends_on: - - redis - healthcheck: - test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/health"] - interval: 5s - timeout: 5s - retries: 10 - start_period: 15s - - redis: - # 与 soul-api 一并打包上传,使用本地 DaoCloud 镜像名(与 pack 中 docker save 一致) - image: docker.m.daocloud.io/library/redis:7-alpine - container_name: soul-redis - command: redis-server --appendonly yes --requirepass "soul-docker-redis" - volumes: - - redis_data:/data - restart: unless-stopped - -volumes: - redis_data: - soul_uploads: diff --git a/soul-api/deploy/docker-compose.production.yml b/soul-api/deploy/docker-compose.production.yml deleted file mode 100644 index e772f308..00000000 --- a/soul-api/deploy/docker-compose.production.yml +++ /dev/null @@ -1,42 +0,0 @@ -# soul-api 生产环境 Docker 部署 -# 用法:在 soul-api 根目录执行 -# docker compose -f deploy/docker-compose.production.yml up -d -# -# Redis 7-alpine:与宝塔已有 Redis 隔离,仅容器内网使用 - -services: - soul-api: - build: - context: .. - dockerfile: deploy/Dockerfile - image: soul-api:latest - container_name: soul-api - restart: unless-stopped - environment: - - REDIS_URL=redis://:soul-docker-redis@redis:6379/0 - - GIN_MODE=release - - APP_ENV=production - ports: - - "8080:8080" - volumes: - - soul_uploads:/app/uploads - depends_on: - - redis - healthcheck: - test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:8080/health"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 10s - - redis: - image: redis:7-alpine - container_name: soul-redis - command: redis-server --appendonly yes --requirepass "soul-docker-redis" - volumes: - - redis_data:/data - restart: unless-stopped - -volumes: - redis_data: - soul_uploads: diff --git a/soul-api/deploy/docker-compose.runner.standalone.yml b/soul-api/deploy/docker-compose.runner.standalone.yml deleted file mode 100644 index ba73ac54..00000000 --- a/soul-api/deploy/docker-compose.runner.standalone.yml +++ /dev/null @@ -1,14 +0,0 @@ -# soul-api Runner 部署(仅用已加载镜像,无 build) -# 用于 devloy.py --init-runner 推送镜像后启动 - -services: - soul-api-runner: - image: soul-api-runner:latest - container_name: soul-api-runner - network_mode: host - volumes: - - soul_runner_data:/app - restart: unless-stopped - -volumes: - soul_runner_data: diff --git a/soul-api/deploy/docker-compose.runner.yml b/soul-api/deploy/docker-compose.runner.yml deleted file mode 100644 index d27b3882..00000000 --- a/soul-api/deploy/docker-compose.runner.yml +++ /dev/null @@ -1,21 +0,0 @@ -# soul-api Runner 部署 -# 红蓝切换在容器内完成,宝塔固定 proxy_pass 到 127.0.0.1:9001 -# 使用 network_mode: host,无需端口映射,避免 iptables DOCKER 链问题 -# -# 首次启动:docker compose -f docker-compose.runner.yml up -d -# 部署新版本:上传 tar 后执行 deploy-runner-remote.sh - -services: - soul-api-runner: - build: - context: .. - dockerfile: deploy/Dockerfile.runner - image: soul-api-runner:latest - container_name: soul-api-runner - network_mode: host - volumes: - - soul_runner_data:/app - restart: unless-stopped - -volumes: - soul_runner_data: diff --git a/soul-api/deploy/docker-deploy-remote.sh b/soul-api/deploy/docker-deploy-remote.sh deleted file mode 100644 index 3613d0ab..00000000 --- a/soul-api/deploy/docker-deploy-remote.sh +++ /dev/null @@ -1,130 +0,0 @@ -#!/bin/bash -# soul-api Docker 蓝绿部署脚本(在服务器上执行) -# 用法:./docker-deploy-remote.sh /tmp/soul_api_image.tar.gz [--skip-nginx] -# --skip-nginx:跳过 Nginx 切换,由宝塔 API 在本地执行 - -set -e -PROJECT_ROOT="${DEPLOY_DOCKER_PATH:-/www/wwwroot/self/soul-dev}" -ACTIVE_FILE="$PROJECT_ROOT/.active" -NGINX_CONF="${DEPLOY_NGINX_CONF:-}" -IMAGE_TAR="${1:-}" -SKIP_NGINX="" -if [ "${2:-}" = "--skip-nginx" ]; then - SKIP_NGINX=1 -fi - -if [ -z "$IMAGE_TAR" ] || [ ! -f "$IMAGE_TAR" ]; then - echo "[ERROR] usage: $0 [--skip-nginx]" - exit 1 -fi - -cd "$PROJECT_ROOT" - -# 兼容 docker-compose / docker compose(不同系统安装不一致) -dc() { - if command -v docker-compose >/dev/null 2>&1; then - docker-compose "$@" - else - docker compose "$@" - fi -} - -# 兼容 curl / wget(健康检查工具不一定都有) -health_ok() { - url="$1" - if command -v curl >/dev/null 2>&1; then - curl -sf "$url" >/dev/null 2>&1 - else - wget -qO- "$url" >/dev/null 2>&1 - fi -} - -# 加载新镜像 -echo "[1/5] 加载 Docker 镜像 ..." -gunzip -c "$IMAGE_TAR" | docker load -rm -f "$IMAGE_TAR" - -# 确定当前活跃实例与待启动实例 -CURRENT="blue" -if [ -f "$ACTIVE_FILE" ]; then - CURRENT=$(cat "$ACTIVE_FILE") -fi -if [ "$CURRENT" = "blue" ]; then - NEW="green" - OLD_PORT=9001 - NEW_PORT=9002 -else - NEW="blue" - OLD_PORT=9002 - NEW_PORT=9001 -fi - -echo "[2/5] 当前活跃: $CURRENT ($OLD_PORT),将启动: $NEW ($NEW_PORT)" - -# 启动新实例 -echo "[3/5] 启动 soul-api-$NEW ..." -# --no-deps:线上 Redis 已在跑,不再让 compose 拉起/重建依赖 -dc -f docker-compose.bluegreen.yml up -d --no-deps "soul-api-$NEW" - -# 等待健康检查(镜像已从 tar.gz 加载,无需联网拉取,最多 120 秒) -echo "[4/5] 等待健康检查 ..." -sleep 5 -for i in $(seq 1 58); do - if health_ok "http://127.0.0.1:$NEW_PORT/health"; then - echo " 健康检查通过 ($((5 + i * 2))s)" - break - fi - sleep 2 - if [ $i -eq 58 ]; then - echo "[ERROR] 健康检查超时(120s),新实例未就绪。可查看: docker-compose -f docker-compose.bluegreen.yml logs soul-api-$NEW" - dc -f docker-compose.bluegreen.yml stop "soul-api-$NEW" - exit 1 - fi -done - -# 切换 Nginx(若配置了 NGINX_CONF):将 proxy_pass 中的端口改为 NEW_PORT -if [ -z "$SKIP_NGINX" ]; then - CONF_TO_EDIT="$NGINX_CONF" - # 自动兜底:如果未传入 DEPLOY_NGINX_CONF,则尝试在宝塔默认目录中定位 vhost 配置文件 - if [ -z "$CONF_TO_EDIT" ] || [ ! -f "$CONF_TO_EDIT" ]; then - CONF_DIR="${DEPLOY_NGINX_CONF_DIR:-/www/server/panel/vhost/nginx}" - if [ -d "$CONF_DIR" ]; then - # 优先匹配旧/新端口对应的 proxy_pass,尽量减少误命中 - for p in "$OLD_PORT" "$NEW_PORT"; do - # proxy_pass 前可能带空格;用正则增强匹配容错 - match="$(grep -rlE "proxy_pass[[:space:]]+http://(127\\.0\\.0\\.1|localhost|0\\.0\\.0\\.0):${p}" "$CONF_DIR" 2>/dev/null | sed -n '1p')" - if [ -n "$match" ]; then - CONF_TO_EDIT="$match" - break - fi - done - # 如果仍未匹配,尝试按域名关键字(可选:DEPLOY_DOMAIN) - if [ -z "$CONF_TO_EDIT" ] && [ -n "${DEPLOY_DOMAIN:-}" ]; then - match="$(grep -rl "${DEPLOY_DOMAIN}" "$CONF_DIR" 2>/dev/null | head -n 1)" - if [ -n "$match" ]; then - CONF_TO_EDIT="$match" - fi - fi - fi - fi - - if [ -n "$CONF_TO_EDIT" ] && [ -f "$CONF_TO_EDIT" ]; then - echo "[5/5] 切换 Nginx 到 $NEW_PORT ...(编辑: $CONF_TO_EDIT)" - # 只在同一个 vhost 配置里替换 proxy_pass 上游端口 - sed -i.bak "s|proxy_pass http://127.0.0.1:[0-9]*|proxy_pass http://127.0.0.1:$NEW_PORT|g" "$CONF_TO_EDIT" - sed -i.bak "s|proxy_pass http://localhost:[0-9]*|proxy_pass http://127.0.0.1:$NEW_PORT|g" "$CONF_TO_EDIT" - sed -i.bak "s|proxy_pass http://0.0.0.0:[0-9]*|proxy_pass http://127.0.0.1:$NEW_PORT|g" "$CONF_TO_EDIT" - nginx -t && nginx -s reload - echo " Nginx 已重载" - else - echo "[5/5] 未找到可编辑的 nginx 配置文件,跳过 Nginx 切换。请手动将 proxy_pass 改为 127.0.0.1:$NEW_PORT" - fi -else - echo "[5/5] 已跳过 Nginx 切换(--skip-nginx)" -fi - -# 停止旧实例(首次部署时可能不存在,忽略错误) -dc -f docker-compose.bluegreen.yml stop "soul-api-$CURRENT" 2>/dev/null || true -echo "$NEW" > "$ACTIVE_FILE" -echo "" -echo "[SUCCESS] 部署完成,当前活跃: $NEW (端口 $NEW_PORT)" diff --git a/soul-api/deploy/runner-init.sh b/soul-api/deploy/runner-init.sh deleted file mode 100644 index d6ef22da..00000000 --- a/soul-api/deploy/runner-init.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash -# soul-api Runner 首次初始化(在宿主机执行) -# 构建并启动 Runner 容器,之后用 devloy.py --mode runner 部署 -# -# 用法:在 soul-api 根目录执行 -# cd /path/to/soul-api -# bash deploy/runner-init.sh - -set -e -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" -cd "$ROOT" - -echo "=== soul-api Runner 初始化 ===" -echo " 项目目录: $ROOT" -echo "" - -# 兼容 docker-compose / docker compose -dc() { - if command -v docker-compose >/dev/null 2>&1; then - docker-compose "$@" - else - docker compose "$@" - fi -} - -echo "[1/2] 构建 Runner 镜像 ..." -docker build -f deploy/Dockerfile.runner -t soul-api-runner:latest . - -echo "[2/2] 启动 Runner 容器 ..." -dc -f deploy/docker-compose.runner.yml up -d - -echo "" -echo "[SUCCESS] Runner 已启动" -echo " 宝塔反向代理保持 proxy_pass http://127.0.0.1:9001" -echo " 首次部署: python devloy.py --mode runner" -echo "" diff --git a/soul-api/deploy/runner/deploy.sh b/soul-api/deploy/runner/deploy.sh deleted file mode 100644 index 75041dbb..00000000 --- a/soul-api/deploy/runner/deploy.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/bin/bash -# soul-api Runner 容器内红蓝切换脚本 -# 用法: /app/deploy.sh /tmp/incoming.tar.gz -# 将新版本解压到非活跃目录,健康检查通过后切换 nginx 并停旧实例 - -set -e -INCOMING="${1:-}" -APP_ROOT="/app" -BLUE="$APP_ROOT/blue" -GREEN="$APP_ROOT/green" -ACTIVE_FILE="$APP_ROOT/.active" -NGINX_CONF="$APP_ROOT/nginx.conf" -NGINX_PID="/tmp/nginx.pid" -REDIS_PASS="soul-docker-redis" - -health_ok() { - local url="$1" - if command -v wget >/dev/null 2>&1; then - wget -qO- "$url" >/dev/null 2>&1 - else - [ -x /usr/bin/wget ] && /usr/bin/wget -qO- "$url" >/dev/null 2>&1 - fi -} - -if [ -z "$INCOMING" ] || [ ! -f "$INCOMING" ]; then - echo "[ERROR] 用法: $0 " - exit 1 -fi - -# 确定当前活跃与待部署目录 -CURRENT="blue" -[ -f "$ACTIVE_FILE" ] && CURRENT=$(cat "$ACTIVE_FILE") -[ "$CURRENT" != "blue" ] && [ "$CURRENT" != "green" ] && CURRENT="blue" - -if [ "$CURRENT" = "blue" ]; then - NEW="green" - NEW_PORT=18082 - OLD_PORT=18081 -else - NEW="blue" - NEW_PORT=18081 - OLD_PORT=18082 -fi - -NEW_DIR="$APP_ROOT/$NEW" -echo "[1/5] 当前活跃: $CURRENT ($OLD_PORT),将部署到: $NEW ($NEW_PORT)" - -# 解压到新目录 -echo "[2/5] 解压到 $NEW_DIR ..." -rm -rf "$NEW_DIR" -mkdir -p "$NEW_DIR" -tar -xzf "$INCOMING" -C "$NEW_DIR" -rm -f "$INCOMING" - -# 设置 PORT 和 REDIS_URL -ENV_FILE="$NEW_DIR/.env" -if [ -f "$ENV_FILE" ]; then - sed -i "s/^PORT=.*/PORT=$NEW_PORT/" "$ENV_FILE" - grep -q "^REDIS_URL=" "$ENV_FILE" || echo "REDIS_URL=redis://:${REDIS_PASS}@127.0.0.1:6379/0" >> "$ENV_FILE" - sed -i "s|^REDIS_URL=.*|REDIS_URL=redis://:${REDIS_PASS}@127.0.0.1:6379/0|" "$ENV_FILE" -fi -chmod +x "$NEW_DIR/soul-api" 2>/dev/null || true - -# 启动新实例 -echo "[3/5] 启动 soul-api-$NEW (端口 $NEW_PORT) ..." -cd "$NEW_DIR" -export PORT=$NEW_PORT -export REDIS_URL="redis://:${REDIS_PASS}@127.0.0.1:6379/0" -nohup ./soul-api >> soul-api.log 2>&1 & -NEW_PID=$! -echo $NEW_PID > "$APP_ROOT/.pid.$NEW" -cd - >/dev/null - -# 等待健康检查(最多 120 秒) -echo "[4/5] 等待健康检查 ..." -sleep 5 -for i in $(seq 1 58); do - if health_ok "http://127.0.0.1:$NEW_PORT/health"; then - echo " 健康检查通过" - break - fi - sleep 2 - if [ $i -eq 58 ]; then - echo "[ERROR] 健康检查超时,新实例未就绪" - kill $NEW_PID 2>/dev/null || true - exit 1 - fi -done - -# 更新 nginx 配置并重载 -echo "[5/5] 切换 nginx 到 $NEW_PORT ..." -sed "s/__BACKEND_PORT__/$NEW_PORT/g" "$APP_ROOT/nginx.conf.template" > "$NGINX_CONF" -nginx -s reload 2>/dev/null || nginx -c "$NGINX_CONF" 2>/dev/null || true - -# 停止旧实例(通过 PID 文件或端口) -OLD_PID_FILE="$APP_ROOT/.pid.$CURRENT" -if [ -f "$OLD_PID_FILE" ]; then - OLD_PID=$(cat "$OLD_PID_FILE") - if kill -0 "$OLD_PID" 2>/dev/null; then - echo " 停止旧实例 (PID $OLD_PID)" - kill "$OLD_PID" 2>/dev/null || true - sleep 2 - fi - rm -f "$OLD_PID_FILE" -fi -# 兜底:通过端口杀进程(Alpine 可用 fuser 或 ss) -if command -v fuser >/dev/null 2>&1; then - fuser -k "$OLD_PORT/tcp" 2>/dev/null || true -fi - -echo "$NEW" > "$ACTIVE_FILE" -echo "" -echo "[SUCCESS] 部署完成,当前活跃: $NEW (端口 $NEW_PORT)" diff --git a/soul-api/deploy/runner/entrypoint.sh b/soul-api/deploy/runner/entrypoint.sh deleted file mode 100644 index d1e91290..00000000 --- a/soul-api/deploy/runner/entrypoint.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -# soul-api Runner 容器入口 -# 启动 Redis、Nginx,首次部署时需外部调用 deploy.sh - -set -e -APP_ROOT="/app" -REDIS_PASS="soul-docker-redis" - -# 启动 Redis(后台) -if ! pgrep -x redis-server >/dev/null 2>&1; then - redis-server --requirepass "$REDIS_PASS" --daemonize yes -fi - -# 生成初始 nginx 配置(默认指向 blue 18081,若 blue 未部署则 18082) -BACKEND=18081 -[ -f "$APP_ROOT/.active" ] && [ "$(cat $APP_ROOT/.active)" = "green" ] && BACKEND=18082 -sed "s/__BACKEND_PORT__/$BACKEND/g" "$APP_ROOT/nginx.conf.template" > "$APP_ROOT/nginx.conf" - -# 若已有活跃实例,启动它 -if [ -f "$APP_ROOT/.active" ]; then - ACTIVE=$(cat "$APP_ROOT/.active") - ACTIVE_DIR="$APP_ROOT/$ACTIVE" - if [ -d "$ACTIVE_DIR" ] && [ -x "$ACTIVE_DIR/soul-api" ]; then - PORT=18081 - [ "$ACTIVE" = "green" ] && PORT=18082 - cd "$ACTIVE_DIR" - export PORT=$PORT - export REDIS_URL="redis://:${REDIS_PASS}@127.0.0.1:6379/0" - nohup ./soul-api >> soul-api.log 2>&1 & - echo $! > "$APP_ROOT/.pid.$ACTIVE" - cd - >/dev/null - fi -fi - -# 启动 Nginx(前台,保持容器运行) -exec nginx -c "$APP_ROOT/nginx.conf" -g "daemon off;" diff --git a/soul-api/deploy/runner/nginx.conf.template b/soul-api/deploy/runner/nginx.conf.template deleted file mode 100644 index aec815d3..00000000 --- a/soul-api/deploy/runner/nginx.conf.template +++ /dev/null @@ -1,31 +0,0 @@ -# soul-api Runner - Nginx 反向代理 -# 监听 9001,代理到当前活跃实例(blue=18081, green=18082) -# 宝塔固定 proxy_pass 到 127.0.0.1:9001,无需改配置 - -worker_processes 1; -error_log /dev/stderr warn; -pid /tmp/nginx.pid; - -events { worker_connections 64; } - -http { - access_log /dev/stdout; - include /etc/nginx/mime.types; - default_type application/octet-stream; - - server { - listen 9001; - server_name _; - - location / { - proxy_pass http://127.0.0.1:__BACKEND_PORT__; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_connect_timeout 5s; - proxy_read_timeout 60s; - } - } -} diff --git a/static/mbti-avatars-16personalities-female/ENFJ.svg b/static/mbti-avatars-16personalities-female/ENFJ.svg deleted file mode 100644 index 9153fc1a..00000000 --- a/static/mbti-avatars-16personalities-female/ENFJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Protagonist (ENFJ) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ENFP.svg b/static/mbti-avatars-16personalities-female/ENFP.svg deleted file mode 100644 index e55fd639..00000000 --- a/static/mbti-avatars-16personalities-female/ENFP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Campaigner (ENFP) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ENTJ.svg b/static/mbti-avatars-16personalities-female/ENTJ.svg deleted file mode 100644 index d2c03f9a..00000000 --- a/static/mbti-avatars-16personalities-female/ENTJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Commander (ENTJ) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ENTP.svg b/static/mbti-avatars-16personalities-female/ENTP.svg deleted file mode 100644 index f8ad6b9a..00000000 --- a/static/mbti-avatars-16personalities-female/ENTP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Debater (ENTP) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ESFJ.svg b/static/mbti-avatars-16personalities-female/ESFJ.svg deleted file mode 100644 index 98c9f6e8..00000000 --- a/static/mbti-avatars-16personalities-female/ESFJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Consul (ESFJ) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ESFP.svg b/static/mbti-avatars-16personalities-female/ESFP.svg deleted file mode 100644 index 9a3738e7..00000000 --- a/static/mbti-avatars-16personalities-female/ESFP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Entertainer (ESFP) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ESTJ.svg b/static/mbti-avatars-16personalities-female/ESTJ.svg deleted file mode 100644 index ce1fd041..00000000 --- a/static/mbti-avatars-16personalities-female/ESTJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Executive (ESTJ) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ESTP.svg b/static/mbti-avatars-16personalities-female/ESTP.svg deleted file mode 100644 index b820f5d7..00000000 --- a/static/mbti-avatars-16personalities-female/ESTP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Entrepreneur (ESTP) personality - - - diff --git a/static/mbti-avatars-16personalities-female/INFJ.svg b/static/mbti-avatars-16personalities-female/INFJ.svg deleted file mode 100644 index 757df260..00000000 --- a/static/mbti-avatars-16personalities-female/INFJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Advocate (INFJ) personality - - - diff --git a/static/mbti-avatars-16personalities-female/INFP.svg b/static/mbti-avatars-16personalities-female/INFP.svg deleted file mode 100644 index edbaf724..00000000 --- a/static/mbti-avatars-16personalities-female/INFP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Mediator (INFP) personality - - - diff --git a/static/mbti-avatars-16personalities-female/INTJ.svg b/static/mbti-avatars-16personalities-female/INTJ.svg deleted file mode 100644 index c2d0fc2c..00000000 --- a/static/mbti-avatars-16personalities-female/INTJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Architect (INTJ) personality - - - diff --git a/static/mbti-avatars-16personalities-female/INTP.svg b/static/mbti-avatars-16personalities-female/INTP.svg deleted file mode 100644 index b48fd129..00000000 --- a/static/mbti-avatars-16personalities-female/INTP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Logician (INTP) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ISFJ.svg b/static/mbti-avatars-16personalities-female/ISFJ.svg deleted file mode 100644 index ec689440..00000000 --- a/static/mbti-avatars-16personalities-female/ISFJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Defender (ISFJ) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ISFP.svg b/static/mbti-avatars-16personalities-female/ISFP.svg deleted file mode 100644 index ec6adf4a..00000000 --- a/static/mbti-avatars-16personalities-female/ISFP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Adventurer (ISFP) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ISTJ.svg b/static/mbti-avatars-16personalities-female/ISTJ.svg deleted file mode 100644 index b6030a10..00000000 --- a/static/mbti-avatars-16personalities-female/ISTJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Logistician (ISTJ) personality - - - diff --git a/static/mbti-avatars-16personalities-female/ISTP.svg b/static/mbti-avatars-16personalities-female/ISTP.svg deleted file mode 100644 index d6786267..00000000 --- a/static/mbti-avatars-16personalities-female/ISTP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Virtuoso (ISTP) personality - - - diff --git a/static/mbti-avatars-16personalities-female/README.txt b/static/mbti-avatars-16personalities-female/README.txt deleted file mode 100644 index 80ac456f..00000000 --- a/static/mbti-avatars-16personalities-female/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -16Personalities 彩色小人 SVG(官方静态站) - -版权归属:16personalities.com / NERIS Analytics Limited。 -请勿在未取得授权的情况下用于商业产品对外分发;上架前请改用自有素材或书面许可。 - -文件名:{TYPE}.svg 便于与后台 MBTI key 对应;内容来自官网角色英文名路径。 diff --git a/static/mbti-avatars-16personalities-male/ENFJ.svg b/static/mbti-avatars-16personalities-male/ENFJ.svg deleted file mode 100644 index 6ad8882c..00000000 --- a/static/mbti-avatars-16personalities-male/ENFJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Protagonist (ENFJ) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ENFP.svg b/static/mbti-avatars-16personalities-male/ENFP.svg deleted file mode 100644 index 7a933d24..00000000 --- a/static/mbti-avatars-16personalities-male/ENFP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Campaigner (ENFP) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ENTJ.svg b/static/mbti-avatars-16personalities-male/ENTJ.svg deleted file mode 100644 index b003c3de..00000000 --- a/static/mbti-avatars-16personalities-male/ENTJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Commander (ENTJ) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ENTP.svg b/static/mbti-avatars-16personalities-male/ENTP.svg deleted file mode 100644 index 3ada6c8a..00000000 --- a/static/mbti-avatars-16personalities-male/ENTP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Debater (ENTP) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ESFJ.svg b/static/mbti-avatars-16personalities-male/ESFJ.svg deleted file mode 100644 index a5a0c9b0..00000000 --- a/static/mbti-avatars-16personalities-male/ESFJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Consul (ESFJ) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ESFP.svg b/static/mbti-avatars-16personalities-male/ESFP.svg deleted file mode 100644 index a3e1ea64..00000000 --- a/static/mbti-avatars-16personalities-male/ESFP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Entertainer (ESFP) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ESTJ.svg b/static/mbti-avatars-16personalities-male/ESTJ.svg deleted file mode 100644 index 444b6490..00000000 --- a/static/mbti-avatars-16personalities-male/ESTJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Executive (ESTJ) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ESTP.svg b/static/mbti-avatars-16personalities-male/ESTP.svg deleted file mode 100644 index dd7a6351..00000000 --- a/static/mbti-avatars-16personalities-male/ESTP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Entrepreneur (ESTP) personality - - - diff --git a/static/mbti-avatars-16personalities-male/INFJ.svg b/static/mbti-avatars-16personalities-male/INFJ.svg deleted file mode 100644 index a1de60c2..00000000 --- a/static/mbti-avatars-16personalities-male/INFJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Advocate (INFJ) personality - - - diff --git a/static/mbti-avatars-16personalities-male/INFP.svg b/static/mbti-avatars-16personalities-male/INFP.svg deleted file mode 100644 index e9c45bfe..00000000 --- a/static/mbti-avatars-16personalities-male/INFP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Mediator (INFP) personality - - - diff --git a/static/mbti-avatars-16personalities-male/INTJ.svg b/static/mbti-avatars-16personalities-male/INTJ.svg deleted file mode 100644 index 9319a82a..00000000 --- a/static/mbti-avatars-16personalities-male/INTJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Architect (INTJ) personality - - - diff --git a/static/mbti-avatars-16personalities-male/INTP.svg b/static/mbti-avatars-16personalities-male/INTP.svg deleted file mode 100644 index 0c6cea43..00000000 --- a/static/mbti-avatars-16personalities-male/INTP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Logician (INTP) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ISFJ.svg b/static/mbti-avatars-16personalities-male/ISFJ.svg deleted file mode 100644 index 17372f37..00000000 --- a/static/mbti-avatars-16personalities-male/ISFJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Defender (ISFJ) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ISFP.svg b/static/mbti-avatars-16personalities-male/ISFP.svg deleted file mode 100644 index 34518945..00000000 --- a/static/mbti-avatars-16personalities-male/ISFP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Adventurer (ISFP) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ISTJ.svg b/static/mbti-avatars-16personalities-male/ISTJ.svg deleted file mode 100644 index 54ea465a..00000000 --- a/static/mbti-avatars-16personalities-male/ISTJ.svg +++ /dev/null @@ -1,4 +0,0 @@ -Logistician (ISTJ) personality - - - diff --git a/static/mbti-avatars-16personalities-male/ISTP.svg b/static/mbti-avatars-16personalities-male/ISTP.svg deleted file mode 100644 index 4742bd1a..00000000 --- a/static/mbti-avatars-16personalities-male/ISTP.svg +++ /dev/null @@ -1,4 +0,0 @@ -Virtuoso (ISTP) personality - - - diff --git a/static/mbti-avatars-16personalities-male/README.txt b/static/mbti-avatars-16personalities-male/README.txt deleted file mode 100644 index 80ac456f..00000000 --- a/static/mbti-avatars-16personalities-male/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -16Personalities 彩色小人 SVG(官方静态站) - -版权归属:16personalities.com / NERIS Analytics Limited。 -请勿在未取得授权的情况下用于商业产品对外分发;上架前请改用自有素材或书面许可。 - -文件名:{TYPE}.svg 便于与后台 MBTI key 对应;内容来自官网角色英文名路径。