feat: support Yumi activity runtime config

This commit is contained in:
zhx 2026-07-18 15:32:58 +08:00
parent a2ff4768d3
commit 900a524f9b
6 changed files with 165 additions and 86 deletions

86
.env
View File

@ -1,86 +0,0 @@
APP_BIND=0.0.0.0
APP_PORT=2026
DEFAULT_REFRESH_INTERVAL_SECONDS=10
REFRESH_INTERVAL_OPTIONS=5,10,15,30,60
PROBE_TIMEOUT_SECONDS=3
PROBE_CACHE_TTL_SECONDS=5
PROBE_MAX_WORKERS=12
HOST_METRIC_CACHE_TTL_SECONDS=25
TAT_METRIC_TIMEOUT_SECONDS=30
TAT_CPU_SAMPLE_SECONDS=1
NACOS_BASE_URL=http://10.2.1.17:8848
NACOS_NAMESPACE_ID=f7a8594e-090c-4830-a3ad-b5c0f9afcc07
NACOS_NAMESPACE_NAME=RedCircleServiceProd
NACOS_DISCOVERY_GROUP=prod
NACOS_DISCOVERY_CLUSTER_NAME=DEFAULT
NACOS_CACHE_TTL_SECONDS=15
MONGO_URI=mongodb://root:123456@10.2.21.9:27017/tarab_all?authSource=admin&readPreference=secondaryPreferred&retryWrites=false&maxPoolSize=50&minPoolSize=5&maxIdleTimeMS=6000
MONGO_DB_NAME=tarab_all
MONGO_INSTANCE_ID=ins-o9mmob66
MONGO_CACHE_TTL_SECONDS=20
CPU_WARN_PERCENT=60
CPU_BAD_PERCENT=85
MEMORY_WARN_PERCENT=70
MEMORY_BAD_PERCENT=90
DISK_WARN_PERCENT=75
DISK_BAD_PERCENT=90
PROCESS_WARN_COUNT=180
PROCESS_BAD_COUNT=260
SERVICE_LATENCY_WARN_MS=1000
SERVICE_LATENCY_BAD_MS=3000
DEPLOY_REGION=me-saudi-arabia
TENCENT_SECRET_ID=IKIDMchhZEfrsiNo472DAtTpzzmLjttkOnyu
TENCENT_SECRET_KEY=nMkbLsGRO6ZqulSyJQJ0UjjU0KSKxOgl
GATEWAY_CLB_LOAD_BALANCER_ID=lb-cbiabr1q
GATEWAY_CLB_LISTENER_IDS=lbl-ktvz8m6k
GATEWAY_CLB_ALLOWED_DOMAINS=jvapi.haiyihy.com
GATEWAY_CLB_POLL_SECONDS=2
GATEWAY_CLB_TASK_TIMEOUT_SECONDS=120
GATEWAY_CLB_DRAIN_SECONDS=0
CDN_ALLOWED_DOMAINS=h5.haiyihy.com
CDN_PURGE_TASK_LIMIT=20
# Admin frontend deployment for Yumi
UPDATE_DEFAULT_ADMIN_GIT_REF=main
ADMIN_FRONTEND_ENABLED=1
ADMIN_FRONTEND_SERVICE_NAME=admin
ADMIN_FRONTEND_REPO_URL=git@gitea.haiyihy.com:hy/chatapp3-admin.git
ADMIN_FRONTEND_REPO_ROOT=/opt/deploy/sources/chatapp3-admin
ADMIN_FRONTEND_HOST=admin-platform
ADMIN_FRONTEND_IP=10.2.1.2
ADMIN_FRONTEND_INSTANCE_ID=ins-p11begwe
ADMIN_FRONTEND_SSH_HOST=10.2.1.2
ADMIN_FRONTEND_SSH_USER=root
ADMIN_FRONTEND_SSH_PORT=22
ADMIN_FRONTEND_SSH_USE_SUDO=0
ADMIN_FRONTEND_REMOTE_DIR=/opt/yumi-admin/frontend-admin
ADMIN_FRONTEND_SITE_DIR=/opt/yumi-admin/frontend-admin/site/yumiadmin-acc
ADMIN_FRONTEND_COMPOSE_FILE=/opt/yumi-admin/frontend-admin/docker-compose.yml
ADMIN_FRONTEND_COMPOSE_SERVICE=caddy
ADMIN_FRONTEND_RELEASE_META_PATH=/opt/yumi-admin/frontend-admin/.hy-app-monitor-admin-release.json
ADMIN_FRONTEND_BUILD_SCRIPT=build:antdv-next
ADMIN_FRONTEND_DIST_DIR=apps/dist
ADMIN_FRONTEND_PUBLIC_URL=https://yumiadmin-acc.global-interaction.com
CHATAPP_CRON_ENABLED=1
CHATAPP_CRON_REPO_URL=git@gitea.haiyihy.com:hy/chatapp-cron.git
CHATAPP_CRON_REPO_ROOT=/opt/deploy/sources/chatapp-cron
CHATAPP_CRON_DEFAULT_GIT_REF=main
CHATAPP_CRON_SERVICE_NAME=chatapp-cron
CHATAPP_CRON_HOST=cron-sg-1
CHATAPP_CRON_IP=10.3.1.14
CHATAPP_CRON_INSTANCE_ID=ins-nt6bsay0
CHATAPP_CRON_SSH_HOST=10.3.1.14
CHATAPP_CRON_SSH_USER=root
CHATAPP_CRON_SSH_PORT=22
CHATAPP_CRON_SSH_USE_SUDO=0
CHATAPP_CRON_REMOTE_DIR=/opt/likei/cron-sg-1
CHATAPP_CRON_COMPOSE_FILE=/opt/likei/cron-sg-1/docker-compose.yml
CHATAPP_CRON_COMPOSE_SERVICE=week-star-job
CHATAPP_CRON_BINARY_PATH=/opt/likei/cron-sg-1/week-star-job/service
CHATAPP_CRON_RELEASE_META_PATH=/opt/likei/cron-sg-1/.hy-app-monitor-chatapp-cron-release.json
# Harbor credentials for hy-app-monitor service update
HARBOR_REGISTRY=10.2.1.3:18082
HARBOR_PROJECT=likei
HARBOR_USERNAME=admin
HARBOR_PASSWORD=QkVXKcxCff_3x-pEkDCR_aAB

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ __pycache__/
*.pyc
.DS_Store
.venv/
.env

View File

@ -83,6 +83,14 @@ GO_ENV_SPECS: list[dict[str, Any]] = [
"aliases": ("GAME_JAVA_OTHER_BASE_URL", "INVITE_JAVA_APP_BASE_URL"),
"kind": "string",
},
{
"section": "Java",
"primary": "CHATAPP_JAVA_ORDER_BASE_URL",
# LIKEI_GATEWAY_ROUTE_ORDER_URI 只是 Go 的最后兜底,同时被 Java 网关消费;
# 不能把它当等价 alias 回写,否则保存 Go 配置会意外改掉共享网关路由。
"aliases": ("GAME_JAVA_ORDER_BASE_URL",),
"kind": "string",
},
{
"section": "Java",
"primary": "CHATAPP_JAVA_WALLET_BASE_URL",
@ -329,6 +337,74 @@ GO_ENV_SPECS: list[dict[str, Any]] = [
"aliases": ("WEEK_STAR_ROCKETMQ_TAG",),
"kind": "string",
},
{
"section": "Yumi Gift Challenge",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_DEFAULT_SYS_ORIGIN",
"aliases": (),
"kind": "string",
},
{
"section": "Yumi Gift Challenge",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_DELIVERY_LEASE_SECONDS",
"aliases": (),
"kind": "positive_int",
},
{
"section": "Yumi Gift Challenge MQ",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ENABLED",
"aliases": (),
"kind": "bool",
},
{
"section": "Yumi Gift Challenge MQ",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ENDPOINT",
# LIKEI / WeekStar MQ key 仅是运行时兜底,不能作为可回写 alias这些共享 key
# 还被其他消费者使用Yumi 页面保存时必须只修改自己的命名空间。
"aliases": (),
"kind": "string",
},
{
"section": "Yumi Gift Challenge MQ",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_NAMESPACE",
"aliases": (),
"kind": "string",
},
{
"section": "Yumi Gift Challenge MQ",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ACCESS_KEY",
"aliases": (),
"kind": "string",
},
{
"section": "Yumi Gift Challenge MQ",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ACCESS_SECRET",
"aliases": (),
"kind": "string",
},
{
"section": "Yumi Gift Challenge MQ",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_SECURITY_TOKEN",
"aliases": (),
"kind": "string",
},
{
"section": "Yumi Gift Challenge MQ",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_CONSUMER_GROUP",
"aliases": (),
"kind": "string",
},
{
"section": "Yumi Gift Challenge MQ",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_TOPIC",
"aliases": (),
"kind": "string",
},
{
"section": "Yumi Gift Challenge MQ",
"primary": "CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_TAG",
"aliases": (),
"kind": "string",
},
{
"section": "Baishun",
"primary": "CHATAPP_BAISHUN_PLATFORM_BASE_URL",
@ -557,6 +633,10 @@ def validate_scalar_kind(primary_key: str, value: str, kind: str) -> None:
if kind == "int":
int(value)
return
if kind == "positive_int":
if int(value) <= 0:
raise ValueError(f"{primary_key} must be a positive integer")
return
if kind == "bool":
if value.strip().lower() not in {"1", "0", "true", "false", "yes", "no", "on", "off"}:
raise ValueError(f"{primary_key} must be a boolean")

View File

@ -231,6 +231,9 @@ CHATAPP_WEEK_STAR_TIMEZONE=Asia/Riyadh
CHATAPP_ROOM_TURNOVER_REWARD_TIMEZONE=Asia/Riyadh
CHATAPP_WEEK_STAR_SETTLE_INTERVAL_SECONDS=60
CHATAPP_ROOM_TURNOVER_REWARD_SETTLE_INTERVAL_SECONDS=60
CHATAPP_GAME_KING_SETTLE_INTERVAL_SECONDS=60
# Game King 每轮最多派送 4 组(单组硬超时 20 秒120 秒为 DB 冻结和状态修复保留余量。
CHATAPP_GAME_KING_SETTLE_TIMEOUT_SECONDS=120
CHATAPP_WEEK_STAR_ROCKETMQ_ENABLED=false
CHATAPP_WEEK_STAR_ROCKETMQ_ENDPOINT=rocketmq-proxy:8081
CHATAPP_WEEK_STAR_ROCKETMQ_ACCESS_KEY=test

View File

@ -224,12 +224,18 @@ services:
condition: service_healthy
redis:
condition: service_healthy
# Game King 的 cron 只触发 Go 内部结算接口;等待健康后启动可避免发布窗口内的无效首轮请求。
golang:
condition: service_healthy
other:
condition: service_started
wallet:
condition: service_started
env_file:
- .env
environment:
# 容器内 127.0.0.1 指向 cron 自身,必须使用 Compose 服务名访问 Golang。
CHATAPP_GO_BASE_URL: http://golang:2900
admin:
image: chatapp-test/admin:testbox

View File

@ -4,11 +4,86 @@ import unittest
from unittest.mock import patch
from core.config import HARBOR_PROJECT, HARBOR_REGISTRY, load_hosts
from monitors.yumi.runtime_config import (
GO_ENV_PRIMARY_BY_KEY,
GO_ENV_SPECS_BY_PRIMARY,
expand_host_updates,
validate_go_editor_content,
)
from monitors.yumi.service_release import build_update_targets_payload, deploy_updated_services_payload
from monitors.yumi.service_ops import service_records_by_name
class ServiceReleaseTargetsTests(unittest.TestCase):
def test_yumi_activity_runtime_envs_are_editable_with_strict_types(self) -> None:
expected_keys = {
"CHATAPP_JAVA_ORDER_BASE_URL",
"CHATAPP_YUMI_GIFT_CHALLENGE_DEFAULT_SYS_ORIGIN",
"CHATAPP_YUMI_GIFT_CHALLENGE_DELIVERY_LEASE_SECONDS",
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ENABLED",
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ENDPOINT",
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_NAMESPACE",
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ACCESS_KEY",
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ACCESS_SECRET",
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_SECURITY_TOKEN",
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_CONSUMER_GROUP",
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_TOPIC",
"CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_TAG",
}
self.assertTrue(expected_keys.issubset(GO_ENV_SPECS_BY_PRIMARY))
self.assertEqual(
GO_ENV_PRIMARY_BY_KEY["GAME_INTERNAL_CALLBACK_SECRET"],
"CHATAPP_HTTP_INTERNAL_CALLBACK_SECRET",
)
self.assertEqual(
GO_ENV_PRIMARY_BY_KEY["GAME_JAVA_ORDER_BASE_URL"],
"CHATAPP_JAVA_ORDER_BASE_URL",
)
valid = validate_go_editor_content(
"\n".join(
[
'CHATAPP_HTTP_INTERNAL_CALLBACK_SECRET="shared-secret"',
'GAME_JAVA_ORDER_BASE_URL="http://10.2.1.15:2600"',
'CHATAPP_YUMI_GIFT_CHALLENGE_DELIVERY_LEASE_SECONDS="300"',
'CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ENABLED="true"',
'CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ENDPOINT="rocketmq.example:8081"',
]
)
)
self.assertTrue(valid["ok"])
self.assertEqual(valid["values"]["CHATAPP_JAVA_ORDER_BASE_URL"], "http://10.2.1.15:2600")
invalid_lease = validate_go_editor_content(
'CHATAPP_YUMI_GIFT_CHALLENGE_DELIVERY_LEASE_SECONDS="0"'
)
self.assertFalse(invalid_lease["ok"])
self.assertIn("positive integer", invalid_lease["message"])
invalid_enabled = validate_go_editor_content(
'CHATAPP_YUMI_GIFT_CHALLENGE_ROCKETMQ_ENABLED="enabled"'
)
self.assertFalse(invalid_enabled["ok"])
self.assertIn("must be a boolean", invalid_enabled["message"])
def test_game_king_legacy_env_shape_is_preserved_when_saved(self) -> None:
updates = expand_host_updates(
{
"GAME_INTERNAL_CALLBACK_SECRET": "old-secret",
"GAME_JAVA_ORDER_BASE_URL": "http://old-order:2600",
},
{
"CHATAPP_HTTP_INTERNAL_CALLBACK_SECRET": "new-secret",
"CHATAPP_JAVA_ORDER_BASE_URL": "http://new-order:2600",
},
)
self.assertEqual(updates["GAME_INTERNAL_CALLBACK_SECRET"], "new-secret")
self.assertEqual(updates["GAME_JAVA_ORDER_BASE_URL"], "http://new-order:2600")
self.assertNotIn("CHATAPP_HTTP_INTERNAL_CALLBACK_SECRET", updates)
self.assertNotIn("CHATAPP_JAVA_ORDER_BASE_URL", updates)
def test_runtime_topology_keeps_singapore_cron_out_of_golang_release(self) -> None:
hosts = load_hosts()
cron_hosts = [host for host in hosts if host["host"] == "cron-sg-1"]