feat:docker新增统计的微服务
This commit is contained in:
parent
3ec096ecd9
commit
280b4bbee0
@ -33,7 +33,8 @@ func (r *SeasonRepository) GetActiveSeason() (*models.Season, error) {
|
|||||||
|
|
||||||
func (r *SeasonRepository) GetEndedSeasons() ([]*models.Season, error) {
|
func (r *SeasonRepository) GetEndedSeasons() ([]*models.Season, error) {
|
||||||
var seasons []*models.Season
|
var seasons []*models.Season
|
||||||
err := r.db.Where("status = ? AND end_time < ?", "active", gorm.Expr("NOW()")).Find(&seasons).Error
|
// end_time 是 bigint 毫秒时间戳,需用 EXTRACT(EPOCH FROM NOW()) * 1000 转为毫秒
|
||||||
|
err := r.db.Where("status = ? AND end_time < ?", "active", gorm.Expr("EXTRACT(EPOCH FROM NOW()) * 1000")).Find(&seasons).Error
|
||||||
return seasons, err
|
return seasons, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -54,7 +54,10 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
|
|||||||
echo "Built aichatservice" && \
|
echo "Built aichatservice" && \
|
||||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
|
||||||
-o /tmp/lasercompositor services/laserCompositor/main.go && \
|
-o /tmp/lasercompositor services/laserCompositor/main.go && \
|
||||||
echo "Built lasercompositor"
|
echo "Built lasercompositor" && \
|
||||||
|
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" \
|
||||||
|
-o /tmp/statisticservice services/statisticService/main.go && \
|
||||||
|
echo "Built statisticservice"
|
||||||
|
|
||||||
# ---- Runtime Stage: Gateway ----
|
# ---- Runtime Stage: Gateway ----
|
||||||
FROM --platform=linux/amd64 alpine:3.19 AS gateway
|
FROM --platform=linux/amd64 alpine:3.19 AS gateway
|
||||||
@ -207,3 +210,18 @@ HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=3 \
|
|||||||
CMD wget --no-verbose --tries=1 --spider http://localhost:7002/health || exit 1
|
CMD wget --no-verbose --tries=1 --spider http://localhost:7002/health || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["/app/lasercompositor"]
|
ENTRYPOINT ["/app/lasercompositor"]
|
||||||
|
|
||||||
|
# ---- Runtime Stage: StatisticService (数据看板微服务) ----
|
||||||
|
FROM --platform=linux/amd64 alpine:3.19 AS statisticservice
|
||||||
|
|
||||||
|
RUN apk add --no-cache ca-certificates tzdata
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder /tmp/statisticservice /app/statisticservice
|
||||||
|
|
||||||
|
EXPOSE 20009
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=10s --timeout=5s --start-period=15s --retries=3 \
|
||||||
|
CMD wget --no-verbose --tries=1 --spider http://localhost:21009/healthz || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/statisticservice"]
|
||||||
|
|||||||
@ -77,7 +77,8 @@ while [[ $# -gt 0 ]]; do
|
|||||||
echo ""
|
echo ""
|
||||||
echo "服务名 (可选):"
|
echo "服务名 (可选):"
|
||||||
echo " gateway, userService, socialService, assetService,"
|
echo " gateway, userService, socialService, assetService,"
|
||||||
echo " galleryService, activityService, taskService, starbookService, aiChatService"
|
echo " galleryService, activityService, taskService, starbookService, aiChatService,"
|
||||||
|
echo " laserCompositor, statisticService"
|
||||||
echo ""
|
echo ""
|
||||||
echo "示例:"
|
echo "示例:"
|
||||||
echo " $0 # 构建所有服务"
|
echo " $0 # 构建所有服务"
|
||||||
@ -99,6 +100,7 @@ while [[ $# -gt 0 ]]; do
|
|||||||
task|taskService) SERVICES+=("taskService") ;;
|
task|taskService) SERVICES+=("taskService") ;;
|
||||||
starbook|starbookService) SERVICES+=("starbookService") ;;
|
starbook|starbookService) SERVICES+=("starbookService") ;;
|
||||||
ai|aiChatService|aichatservice) SERVICES+=("aiChatService") ;;
|
ai|aiChatService|aichatservice) SERVICES+=("aiChatService") ;;
|
||||||
|
statistic|statisticService|statisticservice) SERVICES+=("statisticservice") ;;
|
||||||
all)
|
all)
|
||||||
# all 关键字,构建所有服务
|
# all 关键字,构建所有服务
|
||||||
SERVICES=()
|
SERVICES=()
|
||||||
@ -117,7 +119,7 @@ done
|
|||||||
|
|
||||||
# ==================== 服务列表 ====================
|
# ==================== 服务列表 ====================
|
||||||
# 所有可用服务及其配置(使用小写 target 名)
|
# 所有可用服务及其配置(使用小写 target 名)
|
||||||
ALL_SERVICES_NAME=("gateway" "userservice" "socialservice" "assetservice" "galleryservice" "activityservice" "taskservice" "starbookservice" "aichatservice" "lasercompositor")
|
ALL_SERVICES_NAME=("gateway" "userservice" "socialservice" "assetservice" "galleryservice" "activityservice" "taskservice" "starbookservice" "aichatservice" "lasercompositor" "statisticservice")
|
||||||
|
|
||||||
# 确定要构建的服务
|
# 确定要构建的服务
|
||||||
if [ ${#SERVICES[@]} -eq 0 ]; then
|
if [ ${#SERVICES[@]} -eq 0 ]; then
|
||||||
@ -208,6 +210,7 @@ main() {
|
|||||||
starbookservice) docker_target="starbookservice" ;;
|
starbookservice) docker_target="starbookservice" ;;
|
||||||
aichatservice) docker_target="aichatservice" ;;
|
aichatservice) docker_target="aichatservice" ;;
|
||||||
lasercompositor) docker_target="lasercompositor" ;;
|
lasercompositor) docker_target="lasercompositor" ;;
|
||||||
|
statisticservice) docker_target="statisticservice" ;;
|
||||||
# 兼容旧的大写服务名
|
# 兼容旧的大写服务名
|
||||||
userService) docker_target="userservice" ;;
|
userService) docker_target="userservice" ;;
|
||||||
socialService) docker_target="socialservice" ;;
|
socialService) docker_target="socialservice" ;;
|
||||||
@ -216,6 +219,8 @@ main() {
|
|||||||
activityService) docker_target="activityservice" ;;
|
activityService) docker_target="activityservice" ;;
|
||||||
taskService) docker_target="taskservice" ;;
|
taskService) docker_target="taskservice" ;;
|
||||||
starbookService) docker_target="starbookservice" ;;
|
starbookService) docker_target="starbookservice" ;;
|
||||||
|
statisticService) docker_target="statisticservice" ;;
|
||||||
|
statisticservice) docker_target="statisticservice" ;;
|
||||||
*) docker_target="$service" ;;
|
*) docker_target="$service" ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,18 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
# Docker 容器日志 & 系统日志清理脚本
|
# Docker 容器日志 & 镜像 & 系统日志 自动清理脚本
|
||||||
# 功能:当容器日志或 /var/log 超过阈值时自动清理
|
# 功能:
|
||||||
|
# 1. 容器日志超过阈值时自动 truncate
|
||||||
|
# 2. 清理悬空 Docker 镜像 (<none>:<none>)
|
||||||
|
# 3. 清理旧版本 topfans/* 镜像(保留 latest 和 v1.0.7)
|
||||||
|
# 4. 清理 /var/log 系统日志
|
||||||
# 使用:./cleanup-logs.sh [容器日志阈值GB] [系统日志阈值GB]
|
# 使用:./cleanup-logs.sh [容器日志阈值GB] [系统日志阈值GB]
|
||||||
# 示例:./cleanup-logs.sh 2 2
|
# 示例:./cleanup-logs.sh 2 2
|
||||||
|
# Cron 建议:0 3 * * * /opt/topfans/docker/cleanup-logs.sh 2 2 >> /var/log/cleanup-logs.log 2>&1
|
||||||
# ===================================================================
|
# ===================================================================
|
||||||
|
|
||||||
CONTAINER_THRESHOLD=${1:-2} # 默认容器日志超过 2GB 才清理
|
CONTAINER_THRESHOLD=${1:-2}
|
||||||
SYSLOG_THRESHOLD=${2:-2} # 默认系统日志超过 2GB 才清理
|
SYSLOG_THRESHOLD=${2:-2}
|
||||||
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "日志清理脚本"
|
echo "日志清理脚本"
|
||||||
@ -23,8 +28,8 @@ total_container_log=0
|
|||||||
cleared_containers=0
|
cleared_containers=0
|
||||||
|
|
||||||
for container in $(docker ps -q); do
|
for container in $(docker ps -q); do
|
||||||
log_file=$(docker inspect --format='{{.LogPath}}' "$container" 2>/dev/null)
|
log_file=$(docker inspect --format="{{.LogPath}}" "$container" 2>/dev/null)
|
||||||
container_name=$(docker inspect --format='{{.Name}}' "$container" 2>/dev/null | sed 's/^\///')
|
container_name=$(docker inspect --format="{{.Name}}" "$container" 2>/dev/null | sed "s/^\///")
|
||||||
|
|
||||||
if [ -f "$log_file" ]; then
|
if [ -f "$log_file" ]; then
|
||||||
size_bytes=$(stat -c%s "$log_file" 2>/dev/null || echo 0)
|
size_bytes=$(stat -c%s "$log_file" 2>/dev/null || echo 0)
|
||||||
@ -44,14 +49,47 @@ total_container_gb=$((total_container_log / 1024 / 1024 / 1024))
|
|||||||
echo "容器日志总大小: ${total_container_gb}GB"
|
echo "容器日志总大小: ${total_container_gb}GB"
|
||||||
echo "已清理容器数: $cleared_containers"
|
echo "已清理容器数: $cleared_containers"
|
||||||
|
|
||||||
# ========== 2. 清理 /var/log 日志 ==========
|
# ========== 2. 清理悬空 Docker 镜像 ==========
|
||||||
|
echo ""
|
||||||
|
echo "=== 检查悬空镜像 ==="
|
||||||
|
|
||||||
|
dangling_before=$(docker images -f "dangling=true" -q | wc -l)
|
||||||
|
echo "悬空镜像数量: ${dangling_before}"
|
||||||
|
|
||||||
|
if [ "$dangling_before" -gt 0 ]; then
|
||||||
|
docker image prune -f > /dev/null 2>&1
|
||||||
|
echo " ✅ 已清理 ${dangling_before} 个悬空镜像"
|
||||||
|
else
|
||||||
|
echo " (无悬空镜像)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ========== 3. 清理旧版本 topfans/* 镜像 ==========
|
||||||
|
echo ""
|
||||||
|
echo "=== 检查旧版本业务镜像 ==="
|
||||||
|
|
||||||
|
KEEP_TAGS="latest v1.0.7"
|
||||||
|
old_images=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep "^topfans/" | grep -vE ":($(echo $KEEP_TAGS | tr " " "|"))$")
|
||||||
|
|
||||||
|
old_count=0
|
||||||
|
if [ -n "$old_images" ]; then
|
||||||
|
old_count=$(echo "$old_images" | grep -c "topfans/" 2>/dev/null || echo 0)
|
||||||
|
fi
|
||||||
|
if [ "$old_count" -gt 0 ]; then
|
||||||
|
echo " 发现 ${old_count} 个旧版本镜像:"
|
||||||
|
echo "$old_images" | sed "s/^/ /"
|
||||||
|
echo "$old_images" | xargs -r docker rmi > /dev/null 2>&1
|
||||||
|
echo " ✅ 已清理"
|
||||||
|
else
|
||||||
|
echo " (无旧版本镜像)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ========== 4. 清理 /var/log 日志 ==========
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== 检查系统日志 ==="
|
echo "=== 检查系统日志 ==="
|
||||||
|
|
||||||
total_syslog_size=0
|
total_syslog_size=0
|
||||||
cleared_logs=0
|
cleared_logs=0
|
||||||
|
|
||||||
# 找出 /var/log 下超过阈值的大文件并清理
|
|
||||||
for logfile in $(find /var/log -type f -name "*.log" 2>/dev/null); do
|
for logfile in $(find /var/log -type f -name "*.log" 2>/dev/null); do
|
||||||
if [ -f "$logfile" ]; then
|
if [ -f "$logfile" ]; then
|
||||||
size_bytes=$(stat -c%s "$logfile" 2>/dev/null || echo 0)
|
size_bytes=$(stat -c%s "$logfile" 2>/dev/null || echo 0)
|
||||||
@ -67,23 +105,26 @@ for logfile in $(find /var/log -type f -name "*.log" 2>/dev/null); do
|
|||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# 也清理旧的压缩日志
|
|
||||||
for gzfile in $(find /var/log -type f -name "*.gz" 2>/dev/null | head -20); do
|
for gzfile in $(find /var/log -type f -name "*.gz" 2>/dev/null | head -20); do
|
||||||
if [ -f "$gzfile" ]; then
|
if [ -f "$gzfile" ]; then
|
||||||
rm -f "$gzfile" 2>/dev/null && echo " ✅ 已删: $gzfile"
|
rm -f "$gzfile" 2>/dev/null && echo " ✅ 已删: $gzfile"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
# 清理旧的 log.1, log.2 等轮转文件
|
|
||||||
for oldlog in $(find /var/log -type f -name "*.log.[0-9]*" 2>/dev/null); do
|
for oldlog in $(find /var/log -type f -name "*.log.[0-9]*" 2>/dev/null); do
|
||||||
rm -f "$oldlog" 2>/dev/null && echo " ✅ 已删: $oldlog"
|
rm -f "$oldlog" 2>/dev/null && echo " ✅ 已删: $oldlog"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if command -v journalctl > /dev/null 2>&1; then
|
||||||
|
journalctl --vacuum-time=3d > /dev/null 2>&1
|
||||||
|
echo " ✅ journal 已清理(保留最近 3 天)"
|
||||||
|
fi
|
||||||
|
|
||||||
total_syslog_gb=$((total_syslog_size / 1024 / 1024 / 1024))
|
total_syslog_gb=$((total_syslog_size / 1024 / 1024 / 1024))
|
||||||
echo "系统日志总大小: ${total_syslog_gb}GB"
|
echo "系统日志总大小: ${total_syslog_gb}GB"
|
||||||
echo "已清理日志数: $cleared_logs"
|
echo "已清理日志数: $cleared_logs"
|
||||||
|
|
||||||
# ========== 3. 磁盘状态 ==========
|
# ========== 5. 磁盘状态 ==========
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== 当前磁盘状态 ==="
|
echo "=== 当前磁盘状态 ==="
|
||||||
df -h /
|
df -h /
|
||||||
|
|||||||
@ -80,6 +80,7 @@ SERVICES=(
|
|||||||
"starbookservice"
|
"starbookservice"
|
||||||
"aichatservice"
|
"aichatservice"
|
||||||
"lasercompositor"
|
"lasercompositor"
|
||||||
|
"statisticservice"
|
||||||
)
|
)
|
||||||
|
|
||||||
# ==================== 服务器配置 ====================
|
# ==================== 服务器配置 ====================
|
||||||
|
|||||||
@ -333,6 +333,50 @@ services:
|
|||||||
reservations:
|
reservations:
|
||||||
memory: 256M
|
memory: 256M
|
||||||
|
|
||||||
|
# ==================== Statistic Service (数据看板微服务) ====================
|
||||||
|
statisticservice:
|
||||||
|
image: topfans/statisticservice:latest
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: docker/Dockerfile.services
|
||||||
|
target: statisticservice
|
||||||
|
container_name: topfans-statisticservice
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
<<: *common-env
|
||||||
|
PORT: 20009
|
||||||
|
STATISTIC_DB_HOST: host.docker.internal
|
||||||
|
STATISTIC_DB_PORT: 15432
|
||||||
|
STATISTIC_DB_USER: postgres
|
||||||
|
STATISTIC_DB_PASSWORD: ${DB_PASSWORD:-123456}
|
||||||
|
STATISTIC_DB_NAME: ${DB_NAME:-top-fans}
|
||||||
|
STATISTIC_DB_SSLMODE: disable
|
||||||
|
STATISTIC_DB_SCHEMA: statistic
|
||||||
|
STATISTIC_REDIS_HOST: host.docker.internal
|
||||||
|
STATISTIC_REDIS_PORT: 6379
|
||||||
|
STATISTIC_REDIS_PASSWORD: ${REDIS_PASSWORD:-123456}
|
||||||
|
STATISTIC_REDIS_DB: 0
|
||||||
|
# 跨服务调用 userService
|
||||||
|
USER_SERVICE_URL: tri://userservice:20000
|
||||||
|
depends_on:
|
||||||
|
userservice:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- topfans-net
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
expose:
|
||||||
|
- "20009"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "nc -z localhost 20009 || exit 1"]
|
||||||
|
<<: *healthcheck
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 512M
|
||||||
|
reservations:
|
||||||
|
memory: 256M
|
||||||
|
|
||||||
# ==================== API Gateway ====================
|
# ==================== API Gateway ====================
|
||||||
gateway:
|
gateway:
|
||||||
image: topfans/gateway:latest
|
image: topfans/gateway:latest
|
||||||
@ -356,6 +400,7 @@ services:
|
|||||||
DIFY_API_BASE: ${DIFY_API_BASE:-http://host.docker.internal:8081/v1}
|
DIFY_API_BASE: ${DIFY_API_BASE:-http://host.docker.internal:8081/v1}
|
||||||
DIFY_API_KEY: ${DIFY_API_KEY:-}
|
DIFY_API_KEY: ${DIFY_API_KEY:-}
|
||||||
DUBBO_AI_CHAT_SERVICE_URL: tri://aichatservice:20008
|
DUBBO_AI_CHAT_SERVICE_URL: tri://aichatservice:20008
|
||||||
|
DUBBO_STATISTIC_SERVICE_URL: tri://statisticservice:20009
|
||||||
# 镭射卡 AI 生成(MiniMax 文生图)
|
# 镭射卡 AI 生成(MiniMax 文生图)
|
||||||
MINIMAX_API_KEY: ${MINIMAX_API_KEY:-}
|
MINIMAX_API_KEY: ${MINIMAX_API_KEY:-}
|
||||||
MINIMAX_API_URL: ${MINIMAX_API_URL:-https://api.minimaxi.com/v1/image_generation}
|
MINIMAX_API_URL: ${MINIMAX_API_URL:-https://api.minimaxi.com/v1/image_generation}
|
||||||
@ -389,10 +434,14 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
aichatservice:
|
aichatservice:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
statisticservice:
|
||||||
|
condition: service_healthy
|
||||||
lasercompositor:
|
lasercompositor:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
networks:
|
networks:
|
||||||
- topfans-net
|
topfans-net:
|
||||||
|
aliases:
|
||||||
|
- newbackend
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
@ -457,6 +457,56 @@ services:
|
|||||||
memory: 128M
|
memory: 128M
|
||||||
cpus: '0.25'
|
cpus: '0.25'
|
||||||
|
|
||||||
|
# ==================== Statistic Service (数据看板微服务) ====================
|
||||||
|
statisticservice:
|
||||||
|
image: topfans/statisticservice:latest
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: docker/Dockerfile.services
|
||||||
|
target: statisticservice
|
||||||
|
container_name: topfans-statisticservice
|
||||||
|
restart: always
|
||||||
|
env_file:
|
||||||
|
- .env.prod
|
||||||
|
environment:
|
||||||
|
<<: *common-env
|
||||||
|
PORT: 20009
|
||||||
|
# statistic 服务使用独立 schema(与 common-env 中的 DB_* 解耦)
|
||||||
|
STATISTIC_DB_HOST: postgres
|
||||||
|
STATISTIC_DB_PORT: 5432
|
||||||
|
STATISTIC_DB_USER: postgres
|
||||||
|
STATISTIC_DB_PASSWORD: ${DB_PASSWORD:-postgres123}
|
||||||
|
STATISTIC_DB_NAME: topfans
|
||||||
|
STATISTIC_DB_SSLMODE: disable
|
||||||
|
STATISTIC_DB_SCHEMA: statistic
|
||||||
|
# Redis
|
||||||
|
STATISTIC_REDIS_HOST: topfans-redis
|
||||||
|
STATISTIC_REDIS_PORT: 6379
|
||||||
|
STATISTIC_REDIS_PASSWORD: ${REDIS_PASSWORD:-123456}
|
||||||
|
STATISTIC_REDIS_DB: 0
|
||||||
|
# 跨服务调用 userService(看板 GetTodayOverview 需要 crystal_balance)
|
||||||
|
USER_SERVICE_URL: tri://userservice:20000
|
||||||
|
depends_on:
|
||||||
|
userservice:
|
||||||
|
condition: service_started
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
- topfans-net
|
||||||
|
expose:
|
||||||
|
- "20009"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:21009/healthz || exit 1"]
|
||||||
|
<<: *healthcheck
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 300M
|
||||||
|
cpus: '0.5'
|
||||||
|
reservations:
|
||||||
|
memory: 128M
|
||||||
|
cpus: '0.25'
|
||||||
|
|
||||||
# ==================== API Gateway ====================
|
# ==================== API Gateway ====================
|
||||||
gateway:
|
gateway:
|
||||||
image: topfans/gateway:latest
|
image: topfans/gateway:latest
|
||||||
@ -479,6 +529,7 @@ services:
|
|||||||
DUBBO_TASK_SERVICE_URL: tri://taskservice:20006
|
DUBBO_TASK_SERVICE_URL: tri://taskservice:20006
|
||||||
DUBBO_STARBOOK_SERVICE_URL: tri://starbookservice:20005
|
DUBBO_STARBOOK_SERVICE_URL: tri://starbookservice:20005
|
||||||
DUBBO_AI_CHAT_SERVICE_URL: tri://aichatservice:20008
|
DUBBO_AI_CHAT_SERVICE_URL: tri://aichatservice:20008
|
||||||
|
DUBBO_STATISTIC_SERVICE_URL: tri://statisticservice:20009
|
||||||
LASER_COMPOSITOR_URL: http://lasercompositor:7002
|
LASER_COMPOSITOR_URL: http://lasercompositor:7002
|
||||||
# 抠图(人像扣底)、OSS、Dify、JWT、Redis 全部走 env_file: .env.prod
|
# 抠图(人像扣底)、OSS、Dify、JWT、Redis 全部走 env_file: .env.prod
|
||||||
REDIS_HOST: topfans-redis
|
REDIS_HOST: topfans-redis
|
||||||
@ -501,12 +552,16 @@ services:
|
|||||||
condition: service_started
|
condition: service_started
|
||||||
aichatservice:
|
aichatservice:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
|
statisticservice:
|
||||||
|
condition: service_started
|
||||||
lasercompositor:
|
lasercompositor:
|
||||||
condition: service_started
|
condition: service_started
|
||||||
redis:
|
redis:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
networks:
|
networks:
|
||||||
- topfans-net
|
topfans-net:
|
||||||
|
aliases:
|
||||||
|
- newbackend
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
|||||||
37
docker/sql/migrations/2026_06_08_001_statistic_events.sql
Normal file
37
docker/sql/migrations/2026_06_08_001_statistic_events.sql
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
-- statistic 服务 events 原始表
|
||||||
|
-- 创建时间: 2026-06-08
|
||||||
|
-- 说明: 事件原始表,按 received_at 按日分区
|
||||||
|
-- 关联: spec §3.2
|
||||||
|
|
||||||
|
-- 1. 创建 schema
|
||||||
|
CREATE SCHEMA IF NOT EXISTS statistic;
|
||||||
|
|
||||||
|
-- 2. 创建 events 主表(按 received_at 按日分区)
|
||||||
|
CREATE TABLE IF NOT EXISTS statistic.events (
|
||||||
|
id BIGSERIAL,
|
||||||
|
event_id UUID NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
star_id BIGINT NOT NULL,
|
||||||
|
event_type VARCHAR(64) NOT NULL,
|
||||||
|
occurred_at TIMESTAMPTZ NOT NULL,
|
||||||
|
received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
properties JSONB NOT NULL DEFAULT '{}',
|
||||||
|
|
||||||
|
PRIMARY KEY (id, received_at)
|
||||||
|
) PARTITION BY RANGE (received_at);
|
||||||
|
|
||||||
|
-- 3. 唯一约束(去重):同一 event_id 不能重复
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_events_event_id
|
||||||
|
ON statistic.events (event_id, received_at);
|
||||||
|
|
||||||
|
-- 4. 看板查询主索引(覆盖 90% 查询)
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_events_user_star_type_time
|
||||||
|
ON statistic.events (user_id, star_id, event_type, received_at DESC);
|
||||||
|
|
||||||
|
-- 5. 趋势分析索引
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_events_star_type_time
|
||||||
|
ON statistic.events (star_id, event_type, received_at DESC);
|
||||||
|
|
||||||
|
-- 6. JSONB 属性 GIN 索引
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_events_properties_gin
|
||||||
|
ON statistic.events USING GIN (properties);
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
-- statistic 服务 MV1: 每日用户水晶收益
|
||||||
|
-- 创建时间: 2026-06-08
|
||||||
|
-- 服务于: 七日收益曲线、今日收益
|
||||||
|
-- 关联: spec §3.4 MV1
|
||||||
|
|
||||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS statistic.mv_daily_user_income AS
|
||||||
|
SELECT
|
||||||
|
user_id,
|
||||||
|
star_id,
|
||||||
|
DATE(received_at AT TIME ZONE 'Asia/Shanghai') AS income_date,
|
||||||
|
SUM(
|
||||||
|
CASE
|
||||||
|
WHEN event_type IN ('exhibition.revenue', 'crystal.change')
|
||||||
|
AND COALESCE((properties->>'amount')::BIGINT, 0) > 0
|
||||||
|
THEN COALESCE((properties->>'amount')::BIGINT, 0)
|
||||||
|
ELSE 0
|
||||||
|
END
|
||||||
|
) AS total_crystal
|
||||||
|
FROM statistic.events
|
||||||
|
WHERE event_type IN ('exhibition.revenue', 'crystal.change')
|
||||||
|
GROUP BY user_id, star_id, income_date;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_daily_user_income_pk
|
||||||
|
ON statistic.mv_daily_user_income (user_id, star_id, income_date);
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
-- statistic 服务 MV2: 每日展出收益(按藏品)
|
||||||
|
-- 创建时间: 2026-06-08
|
||||||
|
-- 服务于: 展出收益中心(top5)、藏品矩阵 TOP5
|
||||||
|
-- 关联: spec §3.4 MV2
|
||||||
|
|
||||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS statistic.mv_daily_exhibition_revenue AS
|
||||||
|
SELECT
|
||||||
|
user_id,
|
||||||
|
star_id,
|
||||||
|
(properties->>'asset_id')::BIGINT AS asset_id,
|
||||||
|
DATE(received_at AT TIME ZONE 'Asia/Shanghai') AS revenue_date,
|
||||||
|
SUM(COALESCE((properties->>'duration_ms')::BIGINT, 0)) AS total_duration_ms,
|
||||||
|
SUM(COALESCE((properties->>'amount')::BIGINT, 0)) AS total_earnings
|
||||||
|
FROM statistic.events
|
||||||
|
WHERE event_type = 'exhibition.revenue'
|
||||||
|
GROUP BY user_id, star_id, asset_id, revenue_date;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_exhibition_revenue_pk
|
||||||
|
ON statistic.mv_daily_exhibition_revenue (user_id, star_id, asset_id, revenue_date);
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
-- statistic 服务 MV3: 每日点赞按等级
|
||||||
|
-- 创建时间: 2026-06-08
|
||||||
|
-- 服务于: 点赞收益按等级(累计)
|
||||||
|
-- 关联: spec §3.4 MV3
|
||||||
|
-- 关联依赖: public.assets 表
|
||||||
|
-- 修复: a.level → a.grade(assets 表没有 level 列,星册等级在 grade 字段)
|
||||||
|
-- 防御: properties->>'asset_id' 必须存在且为数字,避免 JOIN 失败
|
||||||
|
|
||||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS statistic.mv_daily_like_income AS
|
||||||
|
SELECT
|
||||||
|
e.user_id,
|
||||||
|
e.star_id,
|
||||||
|
a.grade AS asset_level,
|
||||||
|
DATE(e.received_at AT TIME ZONE 'Asia/Shanghai') AS like_date,
|
||||||
|
COUNT(*) AS like_count,
|
||||||
|
SUM(COALESCE((e.properties->>'amount')::BIGINT, 0)) AS total_crystal
|
||||||
|
FROM statistic.events e
|
||||||
|
JOIN public.assets a
|
||||||
|
ON a.id = (e.properties->>'asset_id')::BIGINT
|
||||||
|
WHERE e.event_type = 'asset.like'
|
||||||
|
AND (e.properties->>'asset_id') IS NOT NULL
|
||||||
|
AND (e.properties->>'asset_id') ~ '^[0-9]+$'
|
||||||
|
GROUP BY e.user_id, e.star_id, a.grade, DATE(e.received_at AT TIME ZONE 'Asia/Shanghai');
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_like_income_pk
|
||||||
|
ON statistic.mv_daily_like_income (user_id, star_id, asset_level, like_date);
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
-- statistic 服务 MV4: 藏品等级分布
|
||||||
|
-- 创建时间: 2026-06-08
|
||||||
|
-- 服务于: 藏品等级分布环形图
|
||||||
|
-- 关联: spec §3.4 MV4
|
||||||
|
-- 修复: 字段名按实际 assets 表 schema 调整
|
||||||
|
-- - user_id → owner_uid AS user_id
|
||||||
|
-- - status='active' AND deleted_at IS NULL → is_active=true AND deleted_at IS NULL
|
||||||
|
-- - level → COALESCE(grade::text, 'UNKNOWN')(grade 可能为 NULL)
|
||||||
|
|
||||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS statistic.mv_asset_level_distribution AS
|
||||||
|
SELECT
|
||||||
|
owner_uid AS user_id,
|
||||||
|
star_id,
|
||||||
|
COALESCE(grade::TEXT, 'UNKNOWN') AS asset_level,
|
||||||
|
COUNT(*) AS asset_count
|
||||||
|
FROM public.assets
|
||||||
|
WHERE is_active = TRUE AND deleted_at IS NULL
|
||||||
|
GROUP BY owner_uid, star_id, grade;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_mv_level_dist_pk
|
||||||
|
ON statistic.mv_asset_level_distribution (user_id, star_id, asset_level);
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
-- statistic 服务 metric_weekly_user_income: 本周收入 + 排名(预聚合)
|
||||||
|
-- 创建时间: 2026-06-08
|
||||||
|
-- 服务于: GetTodayOverview 的 week_rank + week_total_users
|
||||||
|
-- 关联: spec §3.5
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS statistic.metric_weekly_user_income (
|
||||||
|
star_id BIGINT NOT NULL,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
week_start DATE NOT NULL, -- 周一日期(Asia/Shanghai)
|
||||||
|
total_crystal BIGINT NOT NULL DEFAULT 0,
|
||||||
|
rank_in_star INT NOT NULL DEFAULT 0,
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
|
||||||
|
PRIMARY KEY (star_id, user_id, week_start)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Worker 维护,查询走索引
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_metric_weekly_rank
|
||||||
|
ON statistic.metric_weekly_user_income (star_id, week_start, rank_in_star);
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
-- statistic 服务 metric_recent_level_ups: 最近升级记录(预聚合,保留 30 天)
|
||||||
|
-- 创建时间: 2026-06-08
|
||||||
|
-- 服务于: GetAssetUpgradeProgress 的 recent[]
|
||||||
|
-- 关联: spec §3.5
|
||||||
|
-- 清理: 30 天前的记录由 worker 定时 DELETE
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS statistic.metric_recent_level_ups (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
star_id BIGINT NOT NULL,
|
||||||
|
asset_id BIGINT NOT NULL,
|
||||||
|
from_level VARCHAR(8) NOT NULL,
|
||||||
|
to_level VARCHAR(8) NOT NULL,
|
||||||
|
upgrade_time TIMESTAMPTZ NOT NULL,
|
||||||
|
asset_name VARCHAR(128),
|
||||||
|
asset_thumb VARCHAR(512)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 看板查询主索引
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_recent_level_ups_user
|
||||||
|
ON statistic.metric_recent_level_ups (user_id, star_id, upgrade_time DESC);
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
-- statistic 服务 metric_upcoming_level_ups: 即将升级进度(预聚合)
|
||||||
|
-- 创建时间: 2026-06-08
|
||||||
|
-- 服务于: GetAssetUpgradeProgress 的 upcoming[]
|
||||||
|
-- 关联: spec §3.5
|
||||||
|
-- 维护: Worker 每 15 分钟从 public.assets + 升级阈值配置全量重算
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS statistic.metric_upcoming_level_ups (
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
star_id BIGINT NOT NULL,
|
||||||
|
asset_id BIGINT NOT NULL,
|
||||||
|
like_progress INT NOT NULL, -- 0-100
|
||||||
|
duration_progress INT NOT NULL, -- 0-100
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
|
||||||
|
PRIMARY KEY (user_id, star_id, asset_id)
|
||||||
|
);
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
-- statistic 服务 refresh_log: 物化视图刷新日志
|
||||||
|
-- 创建时间: 2026-06-08
|
||||||
|
-- 关联: spec §3.6
|
||||||
|
-- 用途: 监控物化视图刷新状态 + 失败告警
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS statistic.refresh_log (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
mv_name VARCHAR(128) NOT NULL,
|
||||||
|
started_at TIMESTAMPTZ NOT NULL,
|
||||||
|
finished_at TIMESTAMPTZ,
|
||||||
|
row_count BIGINT,
|
||||||
|
status VARCHAR(16) NOT NULL, -- 'running' / 'success' / 'failed'
|
||||||
|
error_message TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 监控查询主索引
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_refresh_log_mv_time
|
||||||
|
ON statistic.refresh_log (mv_name, started_at DESC);
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
-- statistic 服务 events 表初始 7 天分区
|
||||||
|
-- 创建时间: 2026-06-08
|
||||||
|
-- 关联: spec §3.3 分区管理
|
||||||
|
-- 说明: 手动运行一次或由 partitioner worker 自动运行
|
||||||
|
-- (worker/partitioner.go 实现,自动调度见 plan Task 8)
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
DECLARE
|
||||||
|
i INT;
|
||||||
|
d DATE;
|
||||||
|
n DATE;
|
||||||
|
BEGIN
|
||||||
|
FOR i IN 0..6 LOOP
|
||||||
|
d := CURRENT_DATE + i;
|
||||||
|
n := d + 1;
|
||||||
|
EXECUTE format(
|
||||||
|
'CREATE TABLE IF NOT EXISTS statistic.events_%s PARTITION OF statistic.events FOR VALUES FROM (%L) TO (%L)',
|
||||||
|
to_char(d, 'YYYY_MM_DD'), d::text, n::text
|
||||||
|
);
|
||||||
|
END LOOP;
|
||||||
|
END $$;
|
||||||
@ -1,6 +1,7 @@
|
|||||||
# 开发环境配置
|
# 开发环境配置
|
||||||
# HBuilderX「运行」时自动加载;CLI 用 --mode development
|
# HBuilderX「运行」时自动加载;CLI 用 --mode development
|
||||||
VITE_API_BASE_URL=http://192.168.110.60:8080
|
# VITE_API_BASE_URL=http://192.168.110.60:8080
|
||||||
|
VITE_API_BASE_URL=https://api.topfans.online
|
||||||
# WebSocket 地址:如与 API 同源可省略(自动从 VITE_API_BASE_URL 推导 http→ws、https→wss)
|
# WebSocket 地址:如与 API 同源可省略(自动从 VITE_API_BASE_URL 推导 http→ws、https→wss)
|
||||||
# 独立部署时直接覆盖,例如:ws://192.168.110.60:8081
|
# 独立部署时直接覆盖,例如:ws://192.168.110.60:8081
|
||||||
VITE_WS_BASE_URL=ws://192.168.110.60:8080
|
VITE_WS_BASE_URL=ws://192.168.110.60:8080
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"appid" : "__UNI__F199FF4",
|
"appid" : "__UNI__F199FF4",
|
||||||
"description" : "",
|
"description" : "",
|
||||||
"versionName" : "1.0.5",
|
"versionName" : "1.0.5",
|
||||||
"versionCode" : 109,
|
"versionCode" : 111,
|
||||||
"transformPx" : false,
|
"transformPx" : false,
|
||||||
/* 5+App特有相关 */
|
/* 5+App特有相关 */
|
||||||
"app-plus" : {
|
"app-plus" : {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user