feat: 优化在线玩家统计逻辑,更新游戏引擎构造函数并调整跳过回合物品行为。
This commit is contained in:
parent
7e5f77ffd4
commit
6b2f86da1e
@ -1,5 +1,5 @@
|
||||
# 全量服务部署 (后端 + 游戏服 + 数据库)
|
||||
# 使用方法: docker-compose -f docker-compose.all.yml up -d
|
||||
# 全量服务部署 (前端 + 后端 + 游戏服 + 数据库 + 中间件 + Nginx)
|
||||
# 使用方法: docker-compose -f docker-compose.all.yml up -d --build
|
||||
services:
|
||||
# ----------------------------------------------------
|
||||
# 1. 业务后端 (Bindbox Game Backend)
|
||||
@ -8,11 +8,11 @@ services:
|
||||
image: zfc931912343/bindbox-game:v1.15
|
||||
container_name: bindbox-game
|
||||
restart: always
|
||||
ports:
|
||||
- "9991:9991"
|
||||
# ports:
|
||||
# - "9991:9991" (Internal only)
|
||||
volumes:
|
||||
- ../bindbox_game/logs:/app/logs
|
||||
# - ../bindbox_game/configs:/app/configs # 指向 bindbox_game 目录下的配置
|
||||
- ../bindbox_game/configs:/app/configs
|
||||
environment:
|
||||
- ACTIVE_ENV=pro
|
||||
- TZ=Asia/Shanghai
|
||||
@ -23,8 +23,27 @@ services:
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
depends_on:
|
||||
- mysql
|
||||
- redis
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 2. 游戏数据库 (CockroachDB for Nakama)
|
||||
# 2. 管理后台 (Admin Web)
|
||||
# ----------------------------------------------------
|
||||
admin-web:
|
||||
build: ../bindbox_game/web/admin
|
||||
container_name: bindbox-admin-web
|
||||
restart: always
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 3. 游戏数据库 (CockroachDB for Nakama)
|
||||
# ----------------------------------------------------
|
||||
nakama-db:
|
||||
image: cockroachdb/cockroach:latest-v23.1
|
||||
@ -33,9 +52,7 @@ services:
|
||||
restart: always
|
||||
volumes:
|
||||
- nakama-db-data:/var/lib/cockroach
|
||||
ports:
|
||||
- "26257:26257"
|
||||
- "8081:8080"
|
||||
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ]
|
||||
interval: 3s
|
||||
@ -50,14 +67,14 @@ services:
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 3. 游戏服务器 (Nakama)
|
||||
# 4. 游戏服务器 (Nakama)
|
||||
# ----------------------------------------------------
|
||||
nakama:
|
||||
image: zfc931912343/bindbox-saolei:v1.6
|
||||
container_name: nakama-server
|
||||
environment:
|
||||
# 直接使用服务名访问后端
|
||||
- MINESWEEPER_BACKEND_URL=http://bindbox-game:9991/api/internal
|
||||
- TZ=Asia/Shanghai
|
||||
entrypoint:
|
||||
@ -72,10 +89,7 @@ services:
|
||||
condition: service_started
|
||||
volumes:
|
||||
- nakama-data:/nakama/data
|
||||
ports:
|
||||
- "7350:7350"
|
||||
- "7351:7351"
|
||||
- "9100:9100"
|
||||
|
||||
healthcheck:
|
||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://bindbox-game:9991/" ]
|
||||
interval: 10s
|
||||
@ -88,9 +102,147 @@ services:
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 5. MySQL Database (For Bindbox Backend)
|
||||
# ----------------------------------------------------
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: bindbox-mysql
|
||||
restart: always
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "123456"
|
||||
MYSQL_DATABASE: "bindbox_game"
|
||||
TZ: Asia/Shanghai
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 6. Redis (For Bindbox Backend)
|
||||
# ----------------------------------------------------
|
||||
redis:
|
||||
image: redis:7.0
|
||||
container_name: bindbox-redis
|
||||
restart: always
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 7. Nginx Gateway
|
||||
# ----------------------------------------------------
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
container_name: bindbox-nginx
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx/conf.d:/etc/nginx/conf.d
|
||||
- ./nginx/ssl:/etc/nginx/ssl
|
||||
depends_on:
|
||||
- bindbox-game
|
||||
- admin-web
|
||||
- nakama
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 8. Loki (Log Storage)
|
||||
# ----------------------------------------------------
|
||||
loki:
|
||||
image: grafana/loki:3.0.0
|
||||
container_name: bindbox-loki
|
||||
restart: always
|
||||
# ports:
|
||||
# - "3100:3100"
|
||||
volumes:
|
||||
- ./loki/loki-config.yaml:/etc/loki/local-config.yaml
|
||||
- loki_data:/loki
|
||||
command: -config.file=/etc/loki/local-config.yaml
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 9. Promtail (Log Collector)
|
||||
# ----------------------------------------------------
|
||||
promtail:
|
||||
image: grafana/promtail:3.0.0
|
||||
container_name: bindbox-promtail
|
||||
restart: always
|
||||
volumes:
|
||||
- ./loki/promtail-config.yaml:/etc/promtail/config.yaml
|
||||
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ../bindbox_game/logs:/var/log/bindbox-game:ro
|
||||
command: -config.file=/etc/promtail/config.yaml
|
||||
networks:
|
||||
- bindbox_net
|
||||
depends_on:
|
||||
- loki
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 10. Grafana (Visualization)
|
||||
# ----------------------------------------------------
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: bindbox-grafana
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
- GF_USERS_ALLOW_SIGN_UP=false
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
networks:
|
||||
- bindbox_net
|
||||
depends_on:
|
||||
- loki
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
volumes:
|
||||
nakama-db-data:
|
||||
nakama-data:
|
||||
mysql_data:
|
||||
redis_data:
|
||||
loki_data:
|
||||
grafana_data:
|
||||
|
||||
|
||||
networks:
|
||||
bindbox_net:
|
||||
name: bindbox_net
|
||||
|
||||
@ -1,55 +1,350 @@
|
||||
# 云端部署专用 - 扫雷游戏服务
|
||||
# 使用方法: docker-compose -f docker-compose.cloud.yml up -d
|
||||
# 全量服务部署 (云端/无源码版)
|
||||
# 使用方法:
|
||||
# 1. 确保已将 docker-compose.cloud.yml, configs/, nginx/, loki/ 目录上传到服务器同一目录
|
||||
# 2. 确保 logs/ 目录存在 (mkdir logs)
|
||||
# 3. 运行: docker-compose -f docker-compose.cloud.yml up -d
|
||||
|
||||
services:
|
||||
# ----------------------------------------------------
|
||||
# 1. 业务后端 (Bindbox Game Backend)
|
||||
# ----------------------------------------------------
|
||||
bindbox-game:
|
||||
image: zfc931912343/bindbox-game:v1.15
|
||||
container_name: bindbox-game
|
||||
restart: always
|
||||
# ports:
|
||||
# - "9991:9991" (Internal only)
|
||||
volumes:
|
||||
# 改为挂载当前目录下的 logs 和 configs
|
||||
- ./logs:/app/logs
|
||||
- ./configs:/app/configs
|
||||
environment:
|
||||
- ACTIVE_ENV=pro
|
||||
- TZ=Asia/Shanghai
|
||||
# MySQL 配置(覆盖编译时的默认值)
|
||||
- MYSQL_ADDR=mysql:3306
|
||||
- MYSQL_USER=root
|
||||
- MYSQL_PASS=bindbox2025kdy
|
||||
- MYSQL_NAME=bindbox_game
|
||||
# Redis 配置
|
||||
- REDIS_ADDR=redis:6379
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
depends_on:
|
||||
- mysql
|
||||
- redis
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 2. 游戏数据库 (CockroachDB for Nakama)
|
||||
# ----------------------------------------------------
|
||||
nakama-db:
|
||||
image: cockroachdb/cockroach:latest-v23.1
|
||||
container_name: nakama-db
|
||||
command: start-single-node --insecure --store=attrs=ssd,path=/var/lib/cockroach/
|
||||
command: start-single-node --insecure --store=attrs=ssd,path=/var/lib/cockroach/ --cache=.25 --max-sql-memory=.25
|
||||
restart: always
|
||||
volumes:
|
||||
- nakama-db-data:/var/lib/cockroach
|
||||
ports:
|
||||
- "26257:26257"
|
||||
- "8081:8080"
|
||||
healthcheck:
|
||||
test: [ "CMD", "curl", "-f", "http://localhost:8080/health?ready=1" ]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 5
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 3. 游戏服务器 (Nakama)
|
||||
# ----------------------------------------------------
|
||||
nakama:
|
||||
image: zfc931912343/bindbox-saolei:v1.3
|
||||
image: zfc931912343/bindbox-saolei:v1.6
|
||||
container_name: nakama-server
|
||||
environment:
|
||||
# 使用 Docker 内部网络访问后端服务 (需确保在同一网络下)
|
||||
- MINESWEEPER_BACKEND_URL=http://blindbox-mms-api:9991/api/internal
|
||||
- MINESWEEPER_BACKEND_URL=http://bindbox-game:9991/api/internal
|
||||
- TZ=Asia/Shanghai
|
||||
entrypoint:
|
||||
- "/bin/sh"
|
||||
- "-ecx"
|
||||
- "/nakama/nakama migrate up --database.address root@nakama-db:26257 && exec /nakama/nakama --name nakama1 --database.address root@nakama-db:26257 --logger.level DEBUG --session.token_expiry_sec 7200 --metrics.prometheus_port 9100 --runtime.path /nakama/modules"
|
||||
- "/nakama/nakama migrate up --database.address root@nakama-db:26257 && exec /nakama/nakama --name nakama1 --database.address root@nakama-db:26257 --logger.level DEBUG --session.token_expiry_sec 7200 --metrics.prometheus_port 9100 --runtime.path /nakama/modules --matchmaker.interval_sec 1 --matchmaker.max_intervals 5"
|
||||
restart: always
|
||||
depends_on:
|
||||
nakama-db:
|
||||
condition: service_healthy
|
||||
bindbox-game:
|
||||
condition: service_started
|
||||
volumes:
|
||||
- nakama-data:/nakama/data
|
||||
ports:
|
||||
- "7350:7350"
|
||||
- "7351:7351"
|
||||
- "9100:9100"
|
||||
healthcheck:
|
||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:7350/" ]
|
||||
test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://bindbox-game:9991/" ]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 4. MySQL Database
|
||||
# ----------------------------------------------------
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: bindbox-mysql
|
||||
restart: always
|
||||
ports:
|
||||
- "3306:3306" # 临时开放外部访问,用完记得关闭!
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: "bindbox2025kdy"
|
||||
MYSQL_DATABASE: "bindbox_game"
|
||||
TZ: Asia/Shanghai
|
||||
command: --default-authentication-plugin=mysql_native_password
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./mysql/init:/docker-entrypoint-initdb.d # 初始化脚本
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 5. Redis
|
||||
# ----------------------------------------------------
|
||||
redis:
|
||||
image: redis:7.0
|
||||
container_name: bindbox-redis
|
||||
restart: always
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 6. Nginx Gateway (入口)
|
||||
# ----------------------------------------------------
|
||||
nginx:
|
||||
image: nginx:latest
|
||||
container_name: bindbox-nginx
|
||||
restart: always
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./nginx/conf.d:/etc/nginx/conf.d
|
||||
- ./nginx/ssl:/etc/nginx/ssl
|
||||
- ./dist:/usr/share/nginx/html/admin
|
||||
depends_on:
|
||||
- bindbox-game
|
||||
- nakama
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 7. Loki (日志存储)
|
||||
# ----------------------------------------------------
|
||||
loki:
|
||||
image: grafana/loki:3.0.0
|
||||
container_name: bindbox-loki
|
||||
restart: always
|
||||
volumes:
|
||||
# 必须上传 loki 目录到服务器
|
||||
- ./loki/loki-config.yaml:/etc/loki/local-config.yaml
|
||||
- loki_data:/loki
|
||||
command: -config.file=/etc/loki/local-config.yaml
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 8. Promtail (日志采集)
|
||||
# ----------------------------------------------------
|
||||
promtail:
|
||||
image: grafana/promtail:3.0.0
|
||||
container_name: bindbox-promtail
|
||||
restart: always
|
||||
volumes:
|
||||
- ./loki/promtail-config.yaml:/etc/promtail/config.yaml
|
||||
- /var/lib/docker/containers:/var/lib/docker/containers:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
# 采集当前目录下的 logs 文件夹
|
||||
- ./logs:/var/log/bindbox-game:ro
|
||||
command: -config.file=/etc/promtail/config.yaml
|
||||
networks:
|
||||
- bindbox_net
|
||||
depends_on:
|
||||
- loki
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 9. Grafana (日志界面)
|
||||
# ----------------------------------------------------
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
container_name: bindbox-grafana
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
- GF_USERS_ALLOW_SIGN_UP=false
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
networks:
|
||||
- bindbox_net
|
||||
depends_on:
|
||||
- loki
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 10. Prometheus (指标采集)
|
||||
# ----------------------------------------------------
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
container_name: bindbox-prometheus
|
||||
restart: always
|
||||
volumes:
|
||||
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus_data:/prometheus
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--web.enable-lifecycle'
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 11. Nginx Exporter (Nginx指标导出)
|
||||
# ----------------------------------------------------
|
||||
nginx-exporter:
|
||||
image: nginx/nginx-prometheus-exporter:latest
|
||||
container_name: bindbox-nginx-exporter
|
||||
restart: always
|
||||
command:
|
||||
- -nginx.scrape-uri=http://nginx:80/nginx_status
|
||||
networks:
|
||||
- bindbox_net
|
||||
depends_on:
|
||||
- nginx
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 12. Redis Exporter (Redis指标导出)
|
||||
# ----------------------------------------------------
|
||||
redis-exporter:
|
||||
image: oliver006/redis_exporter:latest
|
||||
container_name: bindbox-redis-exporter
|
||||
restart: always
|
||||
environment:
|
||||
- REDIS_ADDR=redis://redis:6379
|
||||
networks:
|
||||
- bindbox_net
|
||||
depends_on:
|
||||
- redis
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 13. MySQL Exporter (MySQL指标导出)
|
||||
# ----------------------------------------------------
|
||||
mysql-exporter:
|
||||
image: prom/mysqld-exporter:latest
|
||||
container_name: bindbox-mysql-exporter
|
||||
restart: always
|
||||
command:
|
||||
- --config.my-cnf=/etc/.my.cnf
|
||||
volumes:
|
||||
- ./mysql/.my.cnf:/etc/.my.cnf:ro
|
||||
networks:
|
||||
- bindbox_net
|
||||
depends_on:
|
||||
- mysql
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# 14. Tempo (链路追踪)
|
||||
# ----------------------------------------------------
|
||||
tempo:
|
||||
image: grafana/tempo:latest
|
||||
container_name: bindbox-tempo
|
||||
restart: always
|
||||
command: [ "-config.file=/etc/tempo/tempo-config.yaml" ]
|
||||
volumes:
|
||||
- ./tempo/tempo-config.yaml:/etc/tempo/tempo-config.yaml
|
||||
- tempo_data:/var/tempo
|
||||
networks:
|
||||
- bindbox_net
|
||||
logging:
|
||||
driver: "json-file"
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
volumes:
|
||||
nakama-db-data:
|
||||
nakama-data:
|
||||
mysql_data:
|
||||
redis_data:
|
||||
loki_data:
|
||||
grafana_data:
|
||||
prometheus_data:
|
||||
tempo_data:
|
||||
|
||||
|
||||
# 必须加入后端服务所在的网络才能通过 service_name 访问
|
||||
networks:
|
||||
default:
|
||||
name: ${DOCKER_NETWORK_NAME:-bindbox_default}
|
||||
external: true
|
||||
bindbox_net:
|
||||
name: bindbox_net
|
||||
driver: bridge
|
||||
|
||||
38
loki/loki-config.yaml
Normal file
38
loki/loki-config.yaml
Normal file
@ -0,0 +1,38 @@
|
||||
auth_enabled: false
|
||||
|
||||
server:
|
||||
http_listen_port: 3100
|
||||
grpc_listen_port: 0
|
||||
|
||||
common:
|
||||
path_prefix: /loki
|
||||
storage:
|
||||
filesystem:
|
||||
chunks_directory: /loki/chunks
|
||||
rules_directory: /loki/rules
|
||||
replication_factor: 1
|
||||
ring:
|
||||
instance_addr: 127.0.0.1
|
||||
kvstore:
|
||||
store: inmemory
|
||||
|
||||
schema_config:
|
||||
configs:
|
||||
- from: 2020-10-24
|
||||
store: tsdb
|
||||
object_store: filesystem
|
||||
schema: v13
|
||||
index:
|
||||
prefix: index_
|
||||
period: 24h
|
||||
|
||||
ruler:
|
||||
alertmanager_url: http://localhost:9093
|
||||
|
||||
# Limit settings to prevent issues with large logs
|
||||
limits_config:
|
||||
reject_old_samples: true
|
||||
reject_old_samples_max_age: 168h
|
||||
ingestion_rate_mb: 20
|
||||
ingestion_burst_size_mb: 30
|
||||
volume_enabled: true
|
||||
36
loki/promtail-config.yaml
Normal file
36
loki/promtail-config.yaml
Normal file
@ -0,0 +1,36 @@
|
||||
server:
|
||||
http_listen_port: 9080
|
||||
grpc_listen_port: 0
|
||||
|
||||
positions:
|
||||
filename: /tmp/positions.yaml
|
||||
|
||||
clients:
|
||||
- url: http://loki:3100/loki/api/v1/push
|
||||
|
||||
scrape_configs:
|
||||
# 1. Scrape logs from Docker containers via socket (stdout/stderr)
|
||||
- job_name: docker
|
||||
docker_sd_configs:
|
||||
- host: unix:///var/run/docker.sock
|
||||
refresh_interval: 5s
|
||||
# filters:
|
||||
# - name: name
|
||||
# values: ["bindbox-game"] # Optional: Filter specifically if desired, but user said "all monitor"
|
||||
relabel_configs:
|
||||
- source_labels: ['__meta_docker_container_name']
|
||||
regex: '/(.*)'
|
||||
target_label: 'container'
|
||||
- source_labels: ['__meta_docker_container_log_stream']
|
||||
target_label: 'logstream'
|
||||
- source_labels: ['__meta_docker_container_label_logging_jobname']
|
||||
target_label: 'job'
|
||||
|
||||
# 2. Scrape logs from mounted log files (application logs)
|
||||
- job_name: file_logs
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost
|
||||
labels:
|
||||
job: bindbox_app_logs
|
||||
__path__: /var/log/bindbox-game/*.log
|
||||
BIN
mysql/.DS_Store
vendored
Normal file
BIN
mysql/.DS_Store
vendored
Normal file
Binary file not shown.
5
mysql/.my.cnf
Normal file
5
mysql/.my.cnf
Normal file
@ -0,0 +1,5 @@
|
||||
[client]
|
||||
user = exporter
|
||||
password = exporter123
|
||||
host = mysql
|
||||
port = 3306
|
||||
4
mysql/init/01-create-exporter-user.sql
Normal file
4
mysql/init/01-create-exporter-user.sql
Normal file
@ -0,0 +1,4 @@
|
||||
-- 创建 MySQL Exporter 监控账号
|
||||
CREATE USER IF NOT EXISTS 'exporter'@'%' IDENTIFIED BY 'exporter123';
|
||||
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
78
nginx/conf.d/default.conf
Normal file
78
nginx/conf.d/default.conf
Normal file
@ -0,0 +1,78 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name kdy.1024tool.vip;
|
||||
|
||||
# Nginx 状态监控端点 (HTTP)
|
||||
location /nginx_status {
|
||||
stub_status on;
|
||||
access_log off;
|
||||
allow 172.0.0.0/8;
|
||||
allow 192.168.0.0/16; # Docker bridge network
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
}
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTPS Server
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name kdy.1024tool.vip;
|
||||
|
||||
# SSL Config
|
||||
ssl_certificate /etc/nginx/ssl/kdy.1024tool.vip.pem;
|
||||
ssl_certificate_key /etc/nginx/ssl/kdy.1024tool.vip.key;
|
||||
|
||||
ssl_session_timeout 5m;
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
# 1. Admin Frontend (Static Dist)
|
||||
location / {
|
||||
root /usr/share/nginx/html/admin;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html; # SPA Support
|
||||
}
|
||||
|
||||
# 2. Backend API
|
||||
location /api/ {
|
||||
proxy_pass http://bindbox-game:9991/api/;
|
||||
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;
|
||||
}
|
||||
|
||||
# 3. Nakama API (HTTP / GRPC / WebSocket)
|
||||
location /v2/ {
|
||||
proxy_pass http://nakama:7350/v2/;
|
||||
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;
|
||||
}
|
||||
|
||||
# Nakama WebSocket
|
||||
location /ws {
|
||||
proxy_pass http://nakama:7350/ws;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
}
|
||||
|
||||
# Nginx 状态监控端点(仅内网访问)
|
||||
location /nginx_status {
|
||||
stub_status on;
|
||||
access_log off;
|
||||
allow 172.0.0.0/8; # Docker 内网
|
||||
allow 10.0.0.0/8; # 内网
|
||||
allow 127.0.0.1;
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
33
prometheus/prometheus.yml
Normal file
33
prometheus/prometheus.yml
Normal file
@ -0,0 +1,33 @@
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
# Prometheus 自身监控
|
||||
- job_name: 'prometheus'
|
||||
static_configs:
|
||||
- targets: ['localhost:9090']
|
||||
|
||||
# Nginx 监控
|
||||
- job_name: 'nginx'
|
||||
static_configs:
|
||||
- targets: ['nginx-exporter:9113']
|
||||
metrics_path: /metrics
|
||||
|
||||
# Redis 监控
|
||||
- job_name: 'redis'
|
||||
static_configs:
|
||||
- targets: ['redis-exporter:9121']
|
||||
metrics_path: /metrics
|
||||
|
||||
# MySQL 监控
|
||||
- job_name: 'mysql'
|
||||
static_configs:
|
||||
- targets: ['mysql-exporter:9104']
|
||||
metrics_path: /metrics
|
||||
|
||||
# Nakama 游戏服务监控 (如果开启了 metrics)
|
||||
- job_name: 'nakama'
|
||||
static_configs:
|
||||
- targets: ['nakama:9100']
|
||||
metrics_path: /metrics
|
||||
Binary file not shown.
@ -72,7 +72,8 @@ type GameState struct {
|
||||
TurnOrder []string `json:"turnOrder"`
|
||||
CurrentTurnIndex int `json:"currentTurnIndex"`
|
||||
Round int `json:"round"`
|
||||
GlobalTurnCount int `json:"globalTurnCount"` // 总回合数(用于狗狗技能)
|
||||
GlobalTurnCount int `json:"globalTurnCount"` // 总回合数(用于狗狗技能)
|
||||
LastDeadPlayerID string `json:"lastDeadPlayerId"` // 最后一个死亡的玩家ID(用于平局结算)
|
||||
WinnerID string `json:"winnerId"`
|
||||
GameStarted bool `json:"gameStarted"`
|
||||
LastMoveTimestamp int64 `json:"lastMoveTimestamp"` // Unix时间戳(秒)
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/heroiclabs/nakama-common/runtime"
|
||||
)
|
||||
@ -74,67 +73,36 @@ func RpcFindMyMatch(ctx context.Context, logger runtime.Logger, db *sql.DB, nk r
|
||||
return readObjects[0].Value, nil
|
||||
}
|
||||
|
||||
// RpcGetOnlineCount 返回当前在线玩家数量(基于心跳)
|
||||
// RpcGetOnlineCount 返回当前在线玩家数量
|
||||
func RpcGetOnlineCount(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
||||
userID, ok := ctx.Value(runtime.RUNTIME_CTX_USER_ID).(string)
|
||||
if !ok || userID == "" {
|
||||
return "", runtime.NewError("user not authenticated", 16)
|
||||
}
|
||||
|
||||
// 1. 更新当前用户的心跳时间戳
|
||||
now := time.Now().Unix()
|
||||
heartbeatData, _ := json.Marshal(map[string]int64{"ts": now})
|
||||
|
||||
_, err := nk.StorageWrite(ctx, []*runtime.StorageWrite{
|
||||
{
|
||||
Collection: "game_lobby",
|
||||
Key: "heartbeat",
|
||||
UserID: userID,
|
||||
Value: string(heartbeatData),
|
||||
PermissionRead: 0, // 不可读
|
||||
PermissionWrite: 0, // 仅服务器可写
|
||||
},
|
||||
})
|
||||
// 获取所有活跃比赛中的玩家数
|
||||
matches, err := nk.MatchList(ctx, 100, true, "", nil, nil, "*")
|
||||
if err != nil {
|
||||
logger.Warn("Failed to write heartbeat: %v", err)
|
||||
logger.Warn("Failed to list matches: %v", err)
|
||||
}
|
||||
|
||||
// 2. 读取所有用户的心跳记录
|
||||
cursor := ""
|
||||
onlineCount := 0
|
||||
expireThreshold := now - 60 // 60秒内有心跳的算在线
|
||||
|
||||
for {
|
||||
// StorageList(ctx, callerID, collection, userID, limit, cursor)
|
||||
objects, nextCursor, err := nk.StorageList(ctx, "", "game_lobby", "", 100, cursor)
|
||||
if err != nil {
|
||||
logger.Error("Failed to list heartbeats: %v", err)
|
||||
break
|
||||
}
|
||||
|
||||
for _, obj := range objects {
|
||||
var data map[string]int64
|
||||
if err := json.Unmarshal([]byte(obj.Value), &data); err != nil {
|
||||
continue
|
||||
}
|
||||
if ts, ok := data["ts"]; ok && ts >= expireThreshold {
|
||||
onlineCount++
|
||||
}
|
||||
}
|
||||
|
||||
if nextCursor == "" {
|
||||
break
|
||||
}
|
||||
cursor = nextCursor
|
||||
}
|
||||
|
||||
// 3. 获取比赛中的玩家数
|
||||
matches, _ := nk.MatchList(ctx, 100, true, "", nil, nil, "*")
|
||||
inGameCount := 0
|
||||
for _, m := range matches {
|
||||
inGameCount += int(m.GetSize())
|
||||
}
|
||||
|
||||
// 使用 Nakama 的 StreamCount 获取当前所有 WebSocket 连接数
|
||||
// Mode 0 = Notifications stream, 所有已认证的 WebSocket 连接都会加入这个流
|
||||
onlineCount, err := nk.StreamCount(0, "", "", "")
|
||||
if err != nil {
|
||||
logger.Warn("Failed to get stream count: %v", err)
|
||||
onlineCount = inGameCount // 降级为比赛中玩家数
|
||||
}
|
||||
|
||||
if onlineCount < 1 {
|
||||
onlineCount = 1 // 至少自己在线
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"online_count": onlineCount,
|
||||
"match_count": len(matches),
|
||||
|
||||
@ -55,6 +55,7 @@ func (s *PoisonStrategy) Use(state *core.GameState, user *core.Player, ctx ItemC
|
||||
})
|
||||
} else {
|
||||
target.Poisoned = true
|
||||
target.PoisonSteps = 0
|
||||
ctx.Logic.BroadcastEvent(core.GameEvent{
|
||||
Type: "item", PlayerID: user.UserID, PlayerName: user.Username, TargetID: target.UserID, TargetName: target.Username, ItemID: "poison",
|
||||
Message: fmt.Sprintf("☠️ %s 中毒了!", target.Username),
|
||||
@ -80,14 +81,6 @@ func (s *ShieldStrategy) Use(state *core.GameState, user *core.Player, ctx ItemC
|
||||
type SkipStrategy struct{}
|
||||
|
||||
func (s *SkipStrategy) Use(state *core.GameState, user *core.Player, ctx ItemContext) bool {
|
||||
if user.Character == "elephant" {
|
||||
ctx.Logger.Info("Elephant refused skip")
|
||||
ctx.Logic.BroadcastEvent(core.GameEvent{
|
||||
Type: "ability", PlayerID: user.UserID, PlayerName: user.Username, ItemID: "skip",
|
||||
Message: "🐘 大象无法使用该道具!",
|
||||
})
|
||||
return false
|
||||
}
|
||||
user.SkipTurn = true
|
||||
user.Shield = true
|
||||
ctx.Logic.BroadcastEvent(core.GameEvent{
|
||||
|
||||
@ -65,6 +65,10 @@ func (m *MockGameLogic) BroadcastEvent(event core.GameEvent) {
|
||||
m.LastEvent = &event
|
||||
}
|
||||
|
||||
func (m *MockGameLogic) SendPrivateEvent(targetID string, event core.GameEvent) {
|
||||
m.LastEvent = &event
|
||||
}
|
||||
|
||||
// --- Helper ---
|
||||
|
||||
func createTestContext(logic GameLogic) ItemContext {
|
||||
@ -217,8 +221,11 @@ func TestSkip(t *testing.T) {
|
||||
user.Character = "elephant"
|
||||
user.SkipTurn = false
|
||||
consumed = strategy.Use(state, user, ctx)
|
||||
if consumed {
|
||||
t.Error("Elephant should refuse skip")
|
||||
if !consumed {
|
||||
t.Error("Elephant should now be able to use skip")
|
||||
}
|
||||
if !user.SkipTurn {
|
||||
t.Error("Elephant skip turn should be true")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -93,8 +93,9 @@ func (e *GameEngine) ApplyDamage(state *core.GameState, target *core.Player, amo
|
||||
}
|
||||
}
|
||||
|
||||
if target.HP < 0 {
|
||||
if target.HP <= 0 {
|
||||
target.HP = 0
|
||||
state.LastDeadPlayerID = target.UserID
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -226,11 +226,12 @@ func TestItem_Skip_Effect(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestItem_Skip_ElephantCannotUse(t *testing.T) {
|
||||
func TestItem_Skip_ElephantCanUse(t *testing.T) {
|
||||
RunScenario(t, GameScenario{
|
||||
Name: "好人卡-大象无法使用",
|
||||
Name: "好人卡-大象现在可以使用",
|
||||
Players: []PlayerSetup{
|
||||
{ID: "elephant", Character: "elephant", HP: 5},
|
||||
{ID: "p2", Character: "dog", HP: 4},
|
||||
},
|
||||
Grid: []CellSetup{
|
||||
{Index: 0, Type: "item", ItemID: "skip"},
|
||||
@ -239,8 +240,8 @@ func TestItem_Skip_ElephantCannotUse(t *testing.T) {
|
||||
{Type: "move", PlayerID: "elephant", Value: 0},
|
||||
},
|
||||
Checks: []ScenarioCheck{
|
||||
{PlayerID: "elephant", Field: "skip_turn", Expected: false, Message: "大象无法使用好人卡"},
|
||||
{PlayerID: "elephant", Field: "shield", Expected: false, Message: "大象不应该获得护盾"},
|
||||
{PlayerID: "elephant", Field: "skip_turn", Expected: true, Message: "大象现在应该可以使用好人卡"},
|
||||
{PlayerID: "elephant", Field: "shield", Expected: true, Message: "大象现在应该获得护盾"},
|
||||
},
|
||||
})
|
||||
}
|
||||
@ -488,18 +489,18 @@ func TestCharacter_Dog_MagnifierAbility(t *testing.T) {
|
||||
}
|
||||
state.Grid[99].Type = "bomb" // 放一个未揭示的炸弹
|
||||
|
||||
// 直接设置 GlobalTurnCount 为 5,下一次操作将使其变为 6
|
||||
state.GlobalTurnCount = 5
|
||||
// 狗狗独立步数设为 5,下一次操作将使其变为 6
|
||||
state.Players["dog"].DogStepCount = 5
|
||||
|
||||
// 狗狗操作,GlobalTurnCount 变为 6,6 % 6 == 0,应该触发
|
||||
// 狗狗操作,DogStepCount 变为 6,6 % 6 == 0,应该触发
|
||||
engine.HandleMove(state, "dog", 0)
|
||||
|
||||
t.Logf("操作后 GlobalTurnCount=%d, RevealedCells=%d",
|
||||
state.GlobalTurnCount, len(state.Players["dog"].RevealedCells))
|
||||
t.Logf("操作后 DogStepCount=%d, RevealedCells=%d",
|
||||
state.Players["dog"].DogStepCount, len(state.Players["dog"].RevealedCells))
|
||||
|
||||
// 检查是否触发了放大镜
|
||||
if len(state.Players["dog"].RevealedCells) == 0 {
|
||||
t.Errorf("狗狗应该在 GlobalTurnCount=6 时触发放大镜能力")
|
||||
t.Errorf("狗狗应该在 DogStepCount=6 时触发放大镜能力")
|
||||
} else {
|
||||
t.Logf("狗狗放大镜触发成功,揭示了 %d 个格子", len(state.Players["dog"].RevealedCells))
|
||||
}
|
||||
@ -667,6 +668,30 @@ func TestGameFlow_GameOver(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGameFlow_DrawLastManStanding(t *testing.T) {
|
||||
engine, state := createScenarioState(GameScenario{
|
||||
Players: []PlayerSetup{
|
||||
{ID: "p1", Character: "dog", HP: 1},
|
||||
{ID: "p2", Character: "cat", HP: 1},
|
||||
},
|
||||
Grid: []CellSetup{
|
||||
{Index: 0, Type: "item", ItemID: "lightning"}, // 闪电对所有人造成1点伤害
|
||||
},
|
||||
})
|
||||
|
||||
// p1 使用闪电,所有人同时死亡
|
||||
// 按照逻辑,闪电会依次调用 ApplyDamage 给 p1, p2
|
||||
// p2 是最后一个被处理的(也是最后一个 HP 归零的),所以应该是 LastDeadPlayerID
|
||||
engine.HandleMove(state, "p1", 0)
|
||||
|
||||
if state.WinnerID != "p2" {
|
||||
t.Errorf("平局时由于p2是处理列表最后一个死亡的,应该获胜。got winner=%s", state.WinnerID)
|
||||
}
|
||||
if state.GameStarted {
|
||||
t.Error("游戏应该结束")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGameFlow_SafeAreaExpansion(t *testing.T) {
|
||||
engine, state := createScenarioState(GameScenario{
|
||||
Players: []PlayerSetup{
|
||||
|
||||
@ -88,7 +88,7 @@ func (e *GameEngine) CheckGameOver(state *core.GameState) bool {
|
||||
if len(alive) == 1 {
|
||||
winnerID = alive[0]
|
||||
} else if len(alive) == 0 {
|
||||
winnerID = "draw"
|
||||
winnerID = state.LastDeadPlayerID
|
||||
}
|
||||
state.WinnerID = winnerID
|
||||
state.GameStarted = false
|
||||
@ -102,7 +102,8 @@ func (e *GameEngine) CheckGameOver(state *core.GameState) bool {
|
||||
}
|
||||
|
||||
if winnerPlayer != nil && winnerPlayer.RealUserID > 0 {
|
||||
config.SettleGameWithBackend(e.Logger, winnerPlayer.RealUserID, winnerPlayer.Ticket, "", true, 100)
|
||||
// 分数参数暂时传宝箱数,后端奖励由配置决定
|
||||
config.SettleGameWithBackend(e.Logger, winnerPlayer.RealUserID, winnerPlayer.Ticket, "", true, winnerPlayer.ChestCount)
|
||||
} else {
|
||||
e.Logger.Error("Winner player %s has no RealUserID, cannot settle", winnerID)
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ func createTestEngine() (*GameEngine, *core.GameState) {
|
||||
charMgr := characters.NewCharacterManager(nil)
|
||||
itemMgr := items.NewItemManager()
|
||||
|
||||
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr)
|
||||
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr, nil, nil)
|
||||
|
||||
// Create simplified state
|
||||
p1 := &core.Player{UserID: "p1", Username: "P1", HP: 4, MaxHP: 4, Character: "dog", RevealedCells: make(map[int]string)}
|
||||
|
||||
@ -81,7 +81,7 @@ func createScenarioState(scenario GameScenario) (*GameEngine, *core.GameState) {
|
||||
dispatcher := &MockDispatcher{}
|
||||
charMgr := characters.NewCharacterManager(nil)
|
||||
itemMgr := items.NewItemManager()
|
||||
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr)
|
||||
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr, nil, nil)
|
||||
|
||||
// 创建玩家
|
||||
players := make(map[string]*core.Player)
|
||||
@ -406,7 +406,7 @@ func TestScenario_SafeAreaExpansion(t *testing.T) {
|
||||
dispatcher := &MockDispatcher{}
|
||||
charMgr := characters.NewCharacterManager(nil)
|
||||
itemMgr := items.NewItemManager()
|
||||
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr)
|
||||
engine := NewGameEngine(logger, dispatcher, charMgr, itemMgr, nil, nil)
|
||||
|
||||
// 创建简单网格测试扩散
|
||||
// 布局 (3x3):
|
||||
|
||||
38
tempo/tempo-config.yaml
Normal file
38
tempo/tempo-config.yaml
Normal file
@ -0,0 +1,38 @@
|
||||
server:
|
||||
http_listen_port: 3200
|
||||
|
||||
distributor:
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: 0.0.0.0:4317
|
||||
http:
|
||||
endpoint: 0.0.0.0:4318
|
||||
|
||||
ingester:
|
||||
trace_idle_period: 10s
|
||||
max_block_bytes: 1_000_000
|
||||
max_block_duration: 5m
|
||||
|
||||
compactor:
|
||||
compaction:
|
||||
compaction_window: 1h
|
||||
max_block_bytes: 100_000_000
|
||||
block_retention: 48h
|
||||
compacted_block_retention: 10m
|
||||
|
||||
storage:
|
||||
trace:
|
||||
backend: local
|
||||
local:
|
||||
path: /var/tempo/traces
|
||||
wal:
|
||||
path: /var/tempo/wal
|
||||
|
||||
metrics_generator:
|
||||
registry:
|
||||
external_labels:
|
||||
source: tempo
|
||||
storage:
|
||||
path: /var/tempo/generator/wal
|
||||
Loading…
x
Reference in New Issue
Block a user